blob: 203a195593628ef27f9add13dc400d7f70856bbf [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
David Scherer7aced172000-08-15 01:13:23 +00003import re
Guido van Rossum33d26892007-08-05 15:29:28 +00004import string
David Scherer7aced172000-08-15 01:13:23 +00005import imp
Georg Brandl14fc4272008-05-17 18:39:55 +00006from tkinter import *
7import tkinter.simpledialog as tkSimpleDialog
8import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +00009import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000010import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000011
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000012from idlelib.MultiCall import MultiCallCreator
13from idlelib import idlever
14from idlelib import WindowList
15from idlelib import SearchDialog
16from idlelib import GrepDialog
17from idlelib import ReplaceDialog
18from idlelib import PyParse
19from idlelib.configHandler import idleConf
20from idlelib import aboutDialog, textView, configDialog
21from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000022
23# The default tab setting for a Text widget, in average-width characters.
24TK_TABWIDTH_DEFAULT = 8
25
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000026def _sphinx_version():
27 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
28 major, minor, micro, level, serial = sys.version_info
29 release = '%s%s' % (major, minor)
30 if micro:
Benjamin Petersonb48f6342009-06-22 19:36:31 +000031 release += '%s' % (micro,)
32 if level == 'candidate':
33 release += 'rc%s' % (serial,)
34 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000035 release += '%s%s' % (level[0], serial)
36 return release
37
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000038def _find_module(fullname, path=None):
39 """Version of imp.find_module() that handles hierarchical module names"""
40
41 file = None
42 for tgt in fullname.split('.'):
43 if file is not None:
44 file.close() # close intermediate files
45 (file, filename, descr) = imp.find_module(tgt, path)
46 if descr[2] == imp.PY_SOURCE:
47 break # find but not load the source file
48 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000049 try:
50 path = module.__path__
51 except AttributeError:
Kurt B. Kaiserad667422007-08-23 01:06:15 +000052 raise ImportError('No source for module ' + module.__name__)
Raymond Hettingerf6445e82011-04-12 18:30:14 -070053 if descr[2] != imp.PY_SOURCE:
54 # If all of the above fails and didn't raise an exception,fallback
55 # to a straight import which can find __init__.py in a package.
56 m = __import__(fullname)
57 try:
58 filename = m.__file__
59 except AttributeError:
60 pass
61 else:
62 file = None
Raymond Hettinger2df393c2011-04-12 18:57:55 -070063 descr = os.path.splitext(filename)[1], None, imp.PY_SOURCE
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000064 return file, filename, descr
65
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000066class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000067 from idlelib.Percolator import Percolator
68 from idlelib.ColorDelegator import ColorDelegator
69 from idlelib.UndoDelegator import UndoDelegator
70 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
71 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000072 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000073 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000074
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000075 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000076
77 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000078 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000079 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000080 if sys.platform.count('linux'):
81 # look for html docs in a couple of standard places
82 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
83 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
84 dochome = '/var/www/html/python/index.html'
85 else:
86 basepath = '/usr/share/doc/' # standard location
87 dochome = os.path.join(basepath, pyver,
88 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000089 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +000090 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000091 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +000092 if os.path.isfile(chmfile):
93 dochome = chmfile
Thomas Wouters0e3f5912006-08-11 14:57:12 +000094 elif macosxSupport.runningAsOSXApp():
95 # documentation is stored inside the python framework
96 dochome = os.path.join(sys.prefix,
97 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000098 dochome = os.path.normpath(dochome)
99 if os.path.isfile(dochome):
100 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000101 if sys.platform == 'darwin':
102 # Safari requires real file:-URLs
103 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000104 else:
Benjamin Peterson152b6572009-01-20 15:01:54 +0000105 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000106 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +0000107 self.flist = flist
108 root = root or flist.root
109 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000110 try:
111 sys.ps1
112 except AttributeError:
113 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000114 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000115 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000116 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000117 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200118 #self.top.instance_dict makes flist.inversedict available to
119 #configDialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000120 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000121 else:
122 self.tkinter_vars = {} # keys: Tkinter event names
123 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000124 self.top.instance_dict = {}
125 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000126 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000127 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000128 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000129 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000130 text_options = {
131 'name': 'text',
132 'padx': 5,
133 'wrap': 'none',
134 'width': self.width,
135 'height': idleConf.GetOption('main', 'EditorWindow', 'height')}
136 if TkVersion >= 8.5:
137 # Starting with tk 8.5 we have to set the new tabstyle option
138 # to 'wordprocessor' to achieve the same display of tabs as in
139 # older tk versions.
140 text_options['tabstyle'] = 'wordprocessor'
141 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000142 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000143
144 self.createmenubar()
145 self.apply_bindings()
146
147 self.top.protocol("WM_DELETE_WINDOW", self.close)
148 self.top.bind("<<close-window>>", self.close_event)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000149 if macosxSupport.runningAsOSXApp():
150 # Command-W on editorwindows doesn't work without this.
151 text.bind('<<close-window>>', self.close_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000152 # Some OS X systems have only one mouse button,
153 # so use control-click for pulldown menus there.
154 # (Note, AquaTk defines <2> as the right button if
155 # present and the Tk Text widget already binds <2>.)
156 text.bind("<Control-Button-1>",self.right_menu_event)
157 else:
158 # Elsewhere, use right-click for pulldown menus.
159 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000160 text.bind("<<cut>>", self.cut)
161 text.bind("<<copy>>", self.copy)
162 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000163 text.bind("<<center-insert>>", self.center_insert_event)
164 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000165 text.bind("<<python-docs>>", self.python_docs)
166 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000167 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000168 text.bind("<<open-module>>", self.open_module)
169 text.bind("<<do-nothing>>", lambda event: "break")
170 text.bind("<<select-all>>", self.select_all)
171 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000172 text.bind("<<find>>", self.find_event)
173 text.bind("<<find-again>>", self.find_again_event)
174 text.bind("<<find-in-files>>", self.find_in_files_event)
175 text.bind("<<find-selection>>", self.find_selection_event)
176 text.bind("<<replace>>", self.replace_event)
177 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000178 text.bind("<<smart-backspace>>",self.smart_backspace_event)
179 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
180 text.bind("<<smart-indent>>",self.smart_indent_event)
181 text.bind("<<indent-region>>",self.indent_region_event)
182 text.bind("<<dedent-region>>",self.dedent_region_event)
183 text.bind("<<comment-region>>",self.comment_region_event)
184 text.bind("<<uncomment-region>>",self.uncomment_region_event)
185 text.bind("<<tabify-region>>",self.tabify_region_event)
186 text.bind("<<untabify-region>>",self.untabify_region_event)
187 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
188 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000189 text.bind("<Left>", self.move_at_edge_if_selection(0))
190 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000191 text.bind("<<del-word-left>>", self.del_word_left)
192 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000193 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000194
David Scherer7aced172000-08-15 01:13:23 +0000195 if flist:
196 flist.inversedict[self] = key
197 if key:
198 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000199 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000200 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
201 text.bind("<<open-class-browser>>", self.open_class_browser)
202 text.bind("<<open-path-browser>>", self.open_path_browser)
203
Steven M. Gava898a3652001-10-07 11:10:44 +0000204 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000205 vbar['command'] = text.yview
206 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000207 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000208 fontWeight = 'normal'
209 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000210 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000211 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
212 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
213 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000214 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
215 text.pack(side=TOP, fill=BOTH, expand=1)
216 text.focus_set()
217
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000218 # usetabs true -> literal tab characters are used by indent and
219 # dedent cmds, possibly mixed with spaces if
220 # indentwidth is not a multiple of tabwidth,
221 # which will cause Tabnanny to nag!
222 # false -> tab characters are converted to spaces by indent
223 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000224 # Although use-spaces=0 can be configured manually in config-main.def,
225 # configuration of tabs v. spaces is not supported in the configuration
226 # dialog. IDLE promotes the preferred Python indentation: use spaces!
227 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
228 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000229
230 # tabwidth is the display width of a literal tab character.
231 # CAUTION: telling Tk to use anything other than its default
232 # tab setting causes it to use an entirely different tabbing algorithm,
233 # treating tab stops as fixed distances from the left margin.
234 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000235 self.tabwidth = 8 # must remain 8 until Tk is fixed.
236
237 # indentwidth is the number of screen characters per indent level.
238 # The recommended Python indentation is four spaces.
239 self.indentwidth = self.tabwidth
240 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000241
242 # If context_use_ps1 is true, parsing searches back for a ps1 line;
243 # else searches for a popular (if, def, ...) Python stmt.
244 self.context_use_ps1 = False
245
246 # When searching backwards for a reliable place to begin parsing,
247 # first start num_context_lines[0] lines back, then
248 # num_context_lines[1] lines back if that didn't work, and so on.
249 # The last value should be huge (larger than the # of lines in a
250 # conceivable file).
251 # Making the initial values larger slows things down more often.
252 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000253 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000254 self.undo = undo = self.UndoDelegator()
255 per.insertfilter(undo)
256 text.undo_block_start = undo.undo_block_start
257 text.undo_block_stop = undo.undo_block_stop
258 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000259 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000260 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000261 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000262 self.good_load = False
263 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000264 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000265 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000266 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000267 if io.loadfile(filename):
268 self.good_load = True
269 is_py_src = self.ispythonsource(filename)
270 self.set_indentation_params(is_py_src)
271 if is_py_src:
272 self.color = color = self.ColorDelegator()
273 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000274 else:
275 io.set_filename(filename)
Christian Heimesa156e092008-02-16 07:38:31 +0000276 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000277 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000278 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000279 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000280 menu = self.menudict.get('windows')
281 if menu:
282 end = menu.index("end")
283 if end is None:
284 end = -1
285 if end >= 0:
286 menu.add_separator()
287 end = end + 1
288 self.wmenu_end = end
289 WindowList.register_callback(self.postwindowsmenu)
290
291 # Some abstractions so IDLE extensions are cross-IDE
292 self.askyesno = tkMessageBox.askyesno
293 self.askinteger = tkSimpleDialog.askinteger
294 self.showerror = tkMessageBox.showerror
295
Martin v. Löwis307021f2005-11-27 16:59:04 +0000296 def _filename_to_unicode(self, filename):
297 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000298 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000299 return filename
300 else:
301 try:
302 return filename.decode(self.filesystemencoding)
303 except UnicodeDecodeError:
304 # XXX
305 try:
306 return filename.decode(self.encoding)
307 except UnicodeDecodeError:
308 # byte-to-byte conversion
309 return filename.decode('iso8859-1')
310
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000311 def new_callback(self, event):
312 dirname, basename = self.io.defaultfilename()
313 self.flist.new(dirname)
314 return "break"
315
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000316 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400317 if (event.state & 4) != 0 and event.keysym == "Home":
318 # state&4==Control. If <Control-Home>, use the Tk binding.
319 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000320 if self.text.index("iomark") and \
321 self.text.compare("iomark", "<=", "insert lineend") and \
322 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400323 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000324 insertpt = int(self.text.index("iomark").split(".")[1])
325 else:
326 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000327 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000328 if line[insertpt] not in (' ','\t'):
329 break
330 else:
331 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000332 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000333 if insertpt == lineat:
334 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000335 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000336 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400337 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000338 self.text.tag_remove("sel", "1.0", "end")
339 else:
340 if not self.text.index("sel.first"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400341 self.text.mark_set("my_anchor", "insert") # there was no previous selection
342 else:
343 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
344 self.text.mark_set("my_anchor", "sel.first") # extend back
345 else:
346 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000347 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400348 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000349 if self.text.compare(first,">",last):
350 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000351 self.text.tag_remove("sel", "1.0", "end")
352 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000353 self.text.mark_set("insert", dest)
354 self.text.see("insert")
355 return "break"
356
David Scherer7aced172000-08-15 01:13:23 +0000357 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000358 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000359 if macosxSupport.runningAsOSXApp():
360 # Insert some padding to avoid obscuring some of the statusbar
361 # by the resize widget.
362 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000363 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
364 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
365 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000366 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
367 self.text.event_add("<<set-line-and-column>>",
368 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000369 self.text.after_idle(self.set_line_and_column)
370
371 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000372 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000373 self.status_bar.set_label('column', 'Col: %s' % column)
374 self.status_bar.set_label('line', 'Ln: %s' % line)
375
David Scherer7aced172000-08-15 01:13:23 +0000376 menu_specs = [
377 ("file", "_File"),
378 ("edit", "_Edit"),
379 ("format", "F_ormat"),
380 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000381 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000382 ("windows", "_Windows"),
383 ("help", "_Help"),
384 ]
385
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000386 if macosxSupport.runningAsOSXApp():
387 del menu_specs[-3]
388 menu_specs[-2] = ("windows", "_Window")
389
390
David Scherer7aced172000-08-15 01:13:23 +0000391 def createmenubar(self):
392 mbar = self.menubar
393 self.menudict = menudict = {}
394 for name, label in self.menu_specs:
395 underline, label = prepstr(label)
396 menudict[name] = menu = Menu(mbar, name=name)
397 mbar.add_cascade(label=label, menu=menu, underline=underline)
Georg Brandlaedd2892010-12-19 10:10:32 +0000398 if macosxSupport.isCarbonAquaTk(self.root):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000399 # Insert the application menu
400 menudict['application'] = menu = Menu(mbar, name='apple')
401 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000402 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000403 self.recent_files_menu = Menu(self.menubar)
404 self.menudict['file'].insert_cascade(3, label='Recent Files',
405 underline=0,
406 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000407 self.base_helpmenu_length = self.menudict['help'].index(END)
408 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000409
410 def postwindowsmenu(self):
411 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000412 menu = self.menudict['windows']
413 end = menu.index("end")
414 if end is None:
415 end = -1
416 if end > self.wmenu_end:
417 menu.delete(self.wmenu_end+1, end)
418 WindowList.add_windows_to_menu(menu)
419
420 rmenu = None
421
422 def right_menu_event(self, event):
423 self.text.tag_remove("sel", "1.0", "end")
424 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
425 if not self.rmenu:
426 self.make_rmenu()
427 rmenu = self.rmenu
428 self.event = event
429 iswin = sys.platform[:3] == 'win'
430 if iswin:
431 self.text.config(cursor="arrow")
432 rmenu.tk_popup(event.x_root, event.y_root)
433 if iswin:
434 self.text.config(cursor="ibeam")
435
436 rmenu_specs = [
437 # ("Label", "<<virtual-event>>"), ...
438 ("Close", "<<close-window>>"), # Example
439 ]
440
441 def make_rmenu(self):
442 rmenu = Menu(self.text, tearoff=0)
443 for label, eventname in self.rmenu_specs:
444 def command(text=self.text, eventname=eventname):
445 text.event_generate(eventname)
446 rmenu.add_command(label=label, command=command)
447 self.rmenu = rmenu
448
449 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000450 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000451
Steven M. Gava3b55a892001-11-21 05:56:26 +0000452 def config_dialog(self, event=None):
453 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000454
David Scherer7aced172000-08-15 01:13:23 +0000455 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000456 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000457 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000458
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000459 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000460 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000461 try:
462 os.startfile(self.help_url)
463 except WindowsError as why:
464 tkMessageBox.showerror(title='Document Start Failure',
465 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000466 else:
467 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000468 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000469
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000470 def cut(self,event):
471 self.text.event_generate("<<Cut>>")
472 return "break"
473
474 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000475 if not self.text.tag_ranges("sel"):
476 # There is no selection, so do nothing and maybe interrupt.
477 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000478 self.text.event_generate("<<Copy>>")
479 return "break"
480
481 def paste(self,event):
482 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000483 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000484 return "break"
485
David Scherer7aced172000-08-15 01:13:23 +0000486 def select_all(self, event=None):
487 self.text.tag_add("sel", "1.0", "end-1c")
488 self.text.mark_set("insert", "1.0")
489 self.text.see("insert")
490 return "break"
491
492 def remove_selection(self, event=None):
493 self.text.tag_remove("sel", "1.0", "end")
494 self.text.see("insert")
495
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000496 def move_at_edge_if_selection(self, edge_index):
497 """Cursor move begins at start or end of selection
498
499 When a left/right cursor key is pressed create and return to Tkinter a
500 function which causes a cursor move from the associated edge of the
501 selection.
502
503 """
504 self_text_index = self.text.index
505 self_text_mark_set = self.text.mark_set
506 edges_table = ("sel.first+1c", "sel.last-1c")
507 def move_at_edge(event):
508 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
509 try:
510 self_text_index("sel.first")
511 self_text_mark_set("insert", edges_table[edge_index])
512 except TclError:
513 pass
514 return move_at_edge
515
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000516 def del_word_left(self, event):
517 self.text.event_generate('<Meta-Delete>')
518 return "break"
519
520 def del_word_right(self, event):
521 self.text.event_generate('<Meta-d>')
522 return "break"
523
Steven M. Gavac5976402002-01-04 03:06:08 +0000524 def find_event(self, event):
525 SearchDialog.find(self.text)
526 return "break"
527
528 def find_again_event(self, event):
529 SearchDialog.find_again(self.text)
530 return "break"
531
532 def find_selection_event(self, event):
533 SearchDialog.find_selection(self.text)
534 return "break"
535
536 def find_in_files_event(self, event):
537 GrepDialog.grep(self.text, self.io, self.flist)
538 return "break"
539
540 def replace_event(self, event):
541 ReplaceDialog.replace(self.text)
542 return "break"
543
544 def goto_line_event(self, event):
545 text = self.text
546 lineno = tkSimpleDialog.askinteger("Goto",
547 "Go to line number:",parent=text)
548 if lineno is None:
549 return "break"
550 if lineno <= 0:
551 text.bell()
552 return "break"
553 text.mark_set("insert", "%d.0" % lineno)
554 text.see("insert")
555
David Scherer7aced172000-08-15 01:13:23 +0000556 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000557 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000558 try:
559 name = self.text.get("sel.first", "sel.last")
560 except TclError:
561 name = ""
562 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000563 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000564 name = tkSimpleDialog.askstring("Module",
565 "Enter the name of a Python module\n"
566 "to search on sys.path and open:",
567 parent=self.text, initialvalue=name)
568 if name:
569 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000570 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000571 return
David Scherer7aced172000-08-15 01:13:23 +0000572 # XXX Ought to insert current file's directory in front of path
573 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000574 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000575 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000576 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
577 return
578 if type != imp.PY_SOURCE:
579 tkMessageBox.showerror("Unsupported type",
580 "%s is not a source module" % name, parent=self.text)
581 return
582 if f:
583 f.close()
584 if self.flist:
585 self.flist.open(file)
586 else:
587 self.io.loadfile(file)
588
589 def open_class_browser(self, event=None):
590 filename = self.io.filename
591 if not filename:
592 tkMessageBox.showerror(
593 "No filename",
594 "This buffer has no associated filename",
595 master=self.text)
596 self.text.focus_set()
597 return None
598 head, tail = os.path.split(filename)
599 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000600 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000601 ClassBrowser.ClassBrowser(self.flist, base, [head])
602
603 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000604 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000605 PathBrowser.PathBrowser(self.flist)
606
607 def gotoline(self, lineno):
608 if lineno is not None and lineno > 0:
609 self.text.mark_set("insert", "%d.0" % lineno)
610 self.text.tag_remove("sel", "1.0", "end")
611 self.text.tag_add("sel", "insert", "insert +1l")
612 self.center()
613
614 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000615 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000616 return True
David Scherer7aced172000-08-15 01:13:23 +0000617 base, ext = os.path.splitext(os.path.basename(filename))
618 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000619 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000620 line = self.text.get('1.0', '1.0 lineend')
621 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000622
623 def close_hook(self):
624 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000625 self.flist.unregister_maybe_terminate(self)
626 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000627
628 def set_close_hook(self, close_hook):
629 self.close_hook = close_hook
630
631 def filename_change_hook(self):
632 if self.flist:
633 self.flist.filename_changed_edit(self)
634 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000635 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000636 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000637
Christian Heimesa156e092008-02-16 07:38:31 +0000638 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000639 if self.color:
640 return
Christian Heimesa156e092008-02-16 07:38:31 +0000641 if self.ispythonsource(self.io.filename):
642 self.color = self.ColorDelegator()
643 # can add more colorizers here...
644 if self.color:
645 self.per.removefilter(self.undo)
646 self.per.insertfilter(self.color)
647 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000648
Christian Heimesa156e092008-02-16 07:38:31 +0000649 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000650 if not self.color:
651 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000652 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000653 self.per.removefilter(self.color)
654 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000655
Steven M. Gavab77d3432002-03-02 07:16:21 +0000656 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000657 "Update the colour theme"
658 # Called from self.filename_change_hook and from configDialog.py
659 self._rmcolorizer()
660 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000661 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000662 normal_colors = idleConf.GetHighlight(theme, 'normal')
663 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
664 select_colors = idleConf.GetHighlight(theme, 'hilite')
665 self.text.config(
666 foreground=normal_colors['foreground'],
667 background=normal_colors['background'],
668 insertbackground=cursor_color,
669 selectforeground=select_colors['foreground'],
670 selectbackground=select_colors['background'],
671 )
David Scherer7aced172000-08-15 01:13:23 +0000672
Guido van Rossum33d26892007-08-05 15:29:28 +0000673 IDENTCHARS = string.ascii_letters + string.digits + "_"
674
675 def colorize_syntax_error(self, text, pos):
676 text.tag_add("ERROR", pos)
677 char = text.get(pos)
678 if char and char in self.IDENTCHARS:
679 text.tag_add("ERROR", pos + " wordstart", pos)
680 if '\n' == text.get(pos): # error at line end
681 text.mark_set("insert", pos)
682 else:
683 text.mark_set("insert", pos + "+1c")
684 text.see(pos)
685
Steven M. Gavab1585412002-03-12 00:21:56 +0000686 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000687 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000688 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000689 fontWeight='normal'
690 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
691 fontWeight='bold'
692 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
693 idleConf.GetOption('main','EditorWindow','font-size'),
694 fontWeight))
695
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000696 def RemoveKeybindings(self):
697 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000698 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000699 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000700 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000701 self.text.event_delete(event, *keylist)
702 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000703 xkeydefs = idleConf.GetExtensionBindings(extensionName)
704 if xkeydefs:
705 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000706 self.text.event_delete(event, *keylist)
707
708 def ApplyKeybindings(self):
709 "Update the keybindings after they are changed"
710 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000711 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000712 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000713 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000714 xkeydefs = idleConf.GetExtensionBindings(extensionName)
715 if xkeydefs:
716 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000717 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000718 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000719 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000720 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000721 for item in menu[1]:
722 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000723 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000724 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000725 menu = self.menudict[menubarItem]
726 end = menu.index(END) + 1
727 for index in range(0, end):
728 if menu.type(index) == 'command':
729 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000730 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000731 itemName = menu.entrycget(index, 'label')
732 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000733 if menubarItem in menuEventDict:
734 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000735 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000736 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000737 accel = get_accelerator(keydefs, event)
738 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000739
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000740 def set_notabs_indentwidth(self):
741 "Update the indentwidth if changed and not using tabs in this window"
742 # Called from configDialog.py
743 if not self.usetabs:
744 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
745 type='int')
746
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000747 def reset_help_menu_entries(self):
748 "Update the additional help entries on the Help menu"
749 help_list = idleConf.GetAllExtraHelpSourcesList()
750 helpmenu = self.menudict['help']
751 # first delete the extra help entries, if any
752 helpmenu_length = helpmenu.index(END)
753 if helpmenu_length > self.base_helpmenu_length:
754 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
755 # then rebuild them
756 if help_list:
757 helpmenu.add_separator()
758 for entry in help_list:
759 cmd = self.__extra_help_callback(entry[1])
760 helpmenu.add_command(label=entry[0], command=cmd)
761 # and update the menu dictionary
762 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000763
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000764 def __extra_help_callback(self, helpfile):
765 "Create a callback with the helpfile value frozen at definition time"
766 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000767 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000768 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000769 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000770 try:
771 os.startfile(helpfile)
772 except WindowsError as why:
773 tkMessageBox.showerror(title='Document Start Failure',
774 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000775 else:
776 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000777 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000778
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000779 def update_recent_files_list(self, new_file=None):
780 "Load and update the recent files list and menus"
781 rf_list = []
782 if os.path.exists(self.recent_files_path):
Ned Deily122539e2011-01-24 21:46:44 +0000783 rf_list_file = open(self.recent_files_path,'r',
784 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000785 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000786 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000787 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000788 rf_list_file.close()
789 if new_file:
790 new_file = os.path.abspath(new_file) + '\n'
791 if new_file in rf_list:
792 rf_list.remove(new_file) # move to top
793 rf_list.insert(0, new_file)
794 # clean and save the recent files list
795 bad_paths = []
796 for path in rf_list:
797 if '\0' in path or not os.path.exists(path[0:-1]):
798 bad_paths.append(path)
799 rf_list = [path for path in rf_list if path not in bad_paths]
800 ulchars = "1234567890ABCDEFGHIJK"
801 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000802 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800803 with open(self.recent_files_path, 'w',
804 encoding='utf_8', errors='replace') as rf_file:
805 rf_file.writelines(rf_list)
806 except IOError as err:
807 if not getattr(self.root, "recentfilelist_error_displayed", False):
808 self.root.recentfilelist_error_displayed = True
809 tkMessageBox.showerror(title='IDLE Error',
810 message='Unable to update Recent Files list:\n%s'
811 % str(err),
812 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000813 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000814 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000815 menu = instance.recent_files_menu
816 menu.delete(1, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000817 for i, file_name in enumerate(rf_list):
818 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000819 # make unicode string to display non-ASCII chars correctly
820 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000821 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000822 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000823 command=callback,
824 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000825
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000826 def __recent_file_callback(self, file_name):
827 def open_recent_file(fn_closure=file_name):
828 self.io.open(editFile=fn_closure)
829 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000830
David Scherer7aced172000-08-15 01:13:23 +0000831 def saved_change_hook(self):
832 short = self.short_title()
833 long = self.long_title()
834 if short and long:
835 title = short + " - " + long
836 elif short:
837 title = short
838 elif long:
839 title = long
840 else:
841 title = "Untitled"
842 icon = short or long or title
843 if not self.get_saved():
844 title = "*%s*" % title
845 icon = "*%s" % icon
846 self.top.wm_title(title)
847 self.top.wm_iconname(icon)
848
849 def get_saved(self):
850 return self.undo.get_saved()
851
852 def set_saved(self, flag):
853 self.undo.set_saved(flag)
854
855 def reset_undo(self):
856 self.undo.reset_undo()
857
858 def short_title(self):
859 filename = self.io.filename
860 if filename:
861 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000862 # return unicode string to display non-ASCII chars correctly
863 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000864
865 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000866 # return unicode string to display non-ASCII chars correctly
867 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000868
869 def center_insert_event(self, event):
870 self.center()
871
872 def center(self, mark="insert"):
873 text = self.text
874 top, bot = self.getwindowlines()
875 lineno = self.getlineno(mark)
876 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000877 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000878 text.yview(float(newtop))
879
880 def getwindowlines(self):
881 text = self.text
882 top = self.getlineno("@0,0")
883 bot = self.getlineno("@0,65535")
884 if top == bot and text.winfo_height() == 1:
885 # Geometry manager hasn't run yet
886 height = int(text['height'])
887 bot = top + height - 1
888 return top, bot
889
890 def getlineno(self, mark="insert"):
891 text = self.text
892 return int(float(text.index(mark)))
893
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000894 def get_geometry(self):
895 "Return (width, height, x, y)"
896 geom = self.top.wm_geometry()
897 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000898 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000899
David Scherer7aced172000-08-15 01:13:23 +0000900 def close_event(self, event):
901 self.close()
902
903 def maybesave(self):
904 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000905 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000906 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000907 self.top.deiconify()
908 self.top.lower()
909 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000910 return self.io.maybesave()
911
912 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000913 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000914 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000915 self._close()
916 return reply
917
918 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000919 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000920 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000921 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000922 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000923 self.io.close()
924 self.io = None
925 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000926 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000927 self.color.close(False)
928 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000929 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000930 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000931 self.per.close()
932 self.per = None
933 self.top.destroy()
934 if self.close_hook:
935 # unless override: unregister from flist, terminate if last window
936 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000937
938 def load_extensions(self):
939 self.extensions = {}
940 self.load_standard_extensions()
941
942 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000943 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000944 if hasattr(ins, "close"):
945 ins.close()
946 self.extensions = {}
947
948 def load_standard_extensions(self):
949 for name in self.get_standard_extension_names():
950 try:
951 self.load_extension(name)
952 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000953 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000954 traceback.print_exc()
955
956 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000957 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000958
959 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000960 try:
961 mod = __import__(name, globals(), locals(), [])
962 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000963 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000964 raise
David Scherer7aced172000-08-15 01:13:23 +0000965 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000966 keydefs = idleConf.GetExtensionBindings(name)
967 if hasattr(cls, "menudefs"):
968 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000969 ins = cls(self)
970 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000971 if keydefs:
972 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000973 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000974 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000975 while methodname[:1] == '<':
976 methodname = methodname[1:]
977 while methodname[-1:] == '>':
978 methodname = methodname[:-1]
979 methodname = methodname + "_event"
980 if hasattr(ins, methodname):
981 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000982
983 def apply_bindings(self, keydefs=None):
984 if keydefs is None:
985 keydefs = self.Bindings.default_keydefs
986 text = self.text
987 text.keydefs = keydefs
988 for event, keylist in keydefs.items():
989 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000990 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000991
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000992 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000993 """Add appropriate entries to the menus and submenus
994
995 Menus that are absent or None in self.menudict are ignored.
996 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000997 if menudefs is None:
998 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000999 if keydefs is None:
1000 keydefs = self.Bindings.default_keydefs
1001 menudict = self.menudict
1002 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001003 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001004 menu = menudict.get(mname)
1005 if not menu:
1006 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001007 for entry in entrylist:
1008 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001009 menu.add_separator()
1010 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001011 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001012 checkbutton = (label[:1] == '!')
1013 if checkbutton:
1014 label = label[1:]
1015 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001016 accelerator = get_accelerator(keydefs, eventname)
1017 def command(text=text, eventname=eventname):
1018 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001019 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001020 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001021 menu.add_checkbutton(label=label, underline=underline,
1022 command=command, accelerator=accelerator,
1023 variable=var)
1024 else:
1025 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001026 command=command,
1027 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001028
1029 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001030 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001031 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001032 value = var.get()
1033 return value
1034 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001035 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001036
1037 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001038 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001039 if var:
1040 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001041 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001042 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001043
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001044 def get_var_obj(self, name, vartype=None):
1045 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001046 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001047 # create a Tkinter variable object with self.text as master:
1048 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001049 return var
1050
1051 # Tk implementations of "virtual text methods" -- each platform
1052 # reusing IDLE's support code needs to define these for its GUI's
1053 # flavor of widget.
1054
1055 # Is character at text_index in a Python string? Return 0 for
1056 # "guaranteed no", true for anything else. This info is expensive
1057 # to compute ab initio, but is probably already known by the
1058 # platform's colorizer.
1059
1060 def is_char_in_string(self, text_index):
1061 if self.color:
1062 # Return true iff colorizer hasn't (re)gotten this far
1063 # yet, or the character is tagged as being in a string
1064 return self.text.tag_prevrange("TODO", text_index) or \
1065 "STRING" in self.text.tag_names(text_index)
1066 else:
1067 # The colorizer is missing: assume the worst
1068 return 1
1069
1070 # If a selection is defined in the text widget, return (start,
1071 # end) as Tkinter text indices, otherwise return (None, None)
1072 def get_selection_indices(self):
1073 try:
1074 first = self.text.index("sel.first")
1075 last = self.text.index("sel.last")
1076 return first, last
1077 except TclError:
1078 return None, None
1079
1080 # Return the text widget's current view of what a tab stop means
1081 # (equivalent width in spaces).
1082
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001083 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001084 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1085 return int(current)
1086
1087 # Set the text widget's current view of what a tab stop means.
1088
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001089 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001090 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001091 if self.get_tk_tabwidth() != newtabwidth:
1092 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001093 pixels = text.tk.call("font", "measure", text["font"],
1094 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001095 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001096 text.configure(tabs=pixels)
1097
Guido van Rossum33d26892007-08-05 15:29:28 +00001098### begin autoindent code ### (configuration was moved to beginning of class)
1099
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001100 def set_indentation_params(self, is_py_src, guess=True):
1101 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001102 i = self.guess_indent()
1103 if 2 <= i <= 8:
1104 self.indentwidth = i
1105 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001106 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001107 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001108
1109 def smart_backspace_event(self, event):
1110 text = self.text
1111 first, last = self.get_selection_indices()
1112 if first and last:
1113 text.delete(first, last)
1114 text.mark_set("insert", first)
1115 return "break"
1116 # Delete whitespace left, until hitting a real char or closest
1117 # preceding virtual tab stop.
1118 chars = text.get("insert linestart", "insert")
1119 if chars == '':
1120 if text.compare("insert", ">", "1.0"):
1121 # easy: delete preceding newline
1122 text.delete("insert-1c")
1123 else:
1124 text.bell() # at start of buffer
1125 return "break"
1126 if chars[-1] not in " \t":
1127 # easy: delete preceding real char
1128 text.delete("insert-1c")
1129 return "break"
1130 # Ick. It may require *inserting* spaces if we back up over a
1131 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001132 tabwidth = self.tabwidth
1133 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001134 assert have > 0
1135 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001136 # Debug prompt is multilined....
1137 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001138 ncharsdeleted = 0
1139 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001140 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001141 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001142 chars = chars[:-1]
1143 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001144 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001145 if have <= want or chars[-1] not in " \t":
1146 break
1147 text.undo_block_start()
1148 text.delete("insert-%dc" % ncharsdeleted, "insert")
1149 if have < want:
1150 text.insert("insert", ' ' * (want - have))
1151 text.undo_block_stop()
1152 return "break"
1153
1154 def smart_indent_event(self, event):
1155 # if intraline selection:
1156 # delete it
1157 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001158 # do indent-region
1159 # else:
1160 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001161 text = self.text
1162 first, last = self.get_selection_indices()
1163 text.undo_block_start()
1164 try:
1165 if first and last:
1166 if index2line(first) != index2line(last):
1167 return self.indent_region_event(event)
1168 text.delete(first, last)
1169 text.mark_set("insert", first)
1170 prefix = text.get("insert linestart", "insert")
1171 raw, effective = classifyws(prefix, self.tabwidth)
1172 if raw == len(prefix):
1173 # only whitespace to the left
1174 self.reindent_to(effective + self.indentwidth)
1175 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001176 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001177 if self.usetabs:
1178 pad = '\t'
1179 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001180 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001181 n = self.indentwidth
1182 pad = ' ' * (n - effective % n)
1183 text.insert("insert", pad)
1184 text.see("insert")
1185 return "break"
1186 finally:
1187 text.undo_block_stop()
1188
1189 def newline_and_indent_event(self, event):
1190 text = self.text
1191 first, last = self.get_selection_indices()
1192 text.undo_block_start()
1193 try:
1194 if first and last:
1195 text.delete(first, last)
1196 text.mark_set("insert", first)
1197 line = text.get("insert linestart", "insert")
1198 i, n = 0, len(line)
1199 while i < n and line[i] in " \t":
1200 i = i+1
1201 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001202 # the cursor is in or at leading indentation in a continuation
1203 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001204 text.insert("insert linestart", '\n')
1205 return "break"
1206 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001207 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001208 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001209 last_line_of_prompt = sys.ps1.split('\n')[-1]
1210 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001211 line = line[:-1]
1212 i = i+1
1213 if i:
1214 text.delete("insert - %d chars" % i, "insert")
1215 # strip whitespace after insert point
1216 while text.get("insert") in " \t":
1217 text.delete("insert")
1218 # start new line
1219 text.insert("insert", '\n')
1220
1221 # adjust indentation for continuations and block
1222 # open/close first need to find the last stmt
1223 lno = index2line(text.index('insert'))
1224 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001225 if not self.context_use_ps1:
1226 for context in self.num_context_lines:
1227 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001228 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001229 rawtext = text.get(startatindex, "insert")
1230 y.set_str(rawtext)
1231 bod = y.find_good_parse_start(
1232 self.context_use_ps1,
1233 self._build_char_in_string_func(startatindex))
1234 if bod is not None or startat == 1:
1235 break
1236 y.set_lo(bod or 0)
1237 else:
1238 r = text.tag_prevrange("console", "insert")
1239 if r:
1240 startatindex = r[1]
1241 else:
1242 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001243 rawtext = text.get(startatindex, "insert")
1244 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001245 y.set_lo(0)
1246
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001247 c = y.get_continuation_type()
1248 if c != PyParse.C_NONE:
1249 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001250 if c == PyParse.C_STRING_FIRST_LINE:
1251 # after the first line of a string; do not indent at all
1252 pass
1253 elif c == PyParse.C_STRING_NEXT_LINES:
1254 # inside a string which started before this line;
1255 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001256 text.insert("insert", indent)
1257 elif c == PyParse.C_BRACKET:
1258 # line up with the first (if any) element of the
1259 # last open bracket structure; else indent one
1260 # level beyond the indent of the line with the
1261 # last open bracket
1262 self.reindent_to(y.compute_bracket_indent())
1263 elif c == PyParse.C_BACKSLASH:
1264 # if more than one line in this stmt already, just
1265 # mimic the current indent; else if initial line
1266 # has a start on an assignment stmt, indent to
1267 # beyond leftmost =; else to beyond first chunk of
1268 # non-whitespace on initial line
1269 if y.get_num_lines_in_stmt() > 1:
1270 text.insert("insert", indent)
1271 else:
1272 self.reindent_to(y.compute_backslash_indent())
1273 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001274 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001275 return "break"
1276
1277 # This line starts a brand new stmt; indent relative to
1278 # indentation of initial line of closest preceding
1279 # interesting stmt.
1280 indent = y.get_base_indent_string()
1281 text.insert("insert", indent)
1282 if y.is_block_opener():
1283 self.smart_indent_event(event)
1284 elif indent and y.is_block_closer():
1285 self.smart_backspace_event(event)
1286 return "break"
1287 finally:
1288 text.see("insert")
1289 text.undo_block_stop()
1290
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001291 # Our editwin provides a is_char_in_string function that works
1292 # with a Tk text index, but PyParse only knows about offsets into
1293 # a string. This builds a function for PyParse that accepts an
1294 # offset.
1295
1296 def _build_char_in_string_func(self, startindex):
1297 def inner(offset, _startindex=startindex,
1298 _icis=self.is_char_in_string):
1299 return _icis(_startindex + "+%dc" % offset)
1300 return inner
1301
1302 def indent_region_event(self, event):
1303 head, tail, chars, lines = self.get_region()
1304 for pos in range(len(lines)):
1305 line = lines[pos]
1306 if line:
1307 raw, effective = classifyws(line, self.tabwidth)
1308 effective = effective + self.indentwidth
1309 lines[pos] = self._make_blanks(effective) + line[raw:]
1310 self.set_region(head, tail, chars, lines)
1311 return "break"
1312
1313 def dedent_region_event(self, event):
1314 head, tail, chars, lines = self.get_region()
1315 for pos in range(len(lines)):
1316 line = lines[pos]
1317 if line:
1318 raw, effective = classifyws(line, self.tabwidth)
1319 effective = max(effective - self.indentwidth, 0)
1320 lines[pos] = self._make_blanks(effective) + line[raw:]
1321 self.set_region(head, tail, chars, lines)
1322 return "break"
1323
1324 def comment_region_event(self, event):
1325 head, tail, chars, lines = self.get_region()
1326 for pos in range(len(lines) - 1):
1327 line = lines[pos]
1328 lines[pos] = '##' + line
1329 self.set_region(head, tail, chars, lines)
1330
1331 def uncomment_region_event(self, event):
1332 head, tail, chars, lines = self.get_region()
1333 for pos in range(len(lines)):
1334 line = lines[pos]
1335 if not line:
1336 continue
1337 if line[:2] == '##':
1338 line = line[2:]
1339 elif line[:1] == '#':
1340 line = line[1:]
1341 lines[pos] = line
1342 self.set_region(head, tail, chars, lines)
1343
1344 def tabify_region_event(self, event):
1345 head, tail, chars, lines = self.get_region()
1346 tabwidth = self._asktabwidth()
1347 for pos in range(len(lines)):
1348 line = lines[pos]
1349 if line:
1350 raw, effective = classifyws(line, tabwidth)
1351 ntabs, nspaces = divmod(effective, tabwidth)
1352 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1353 self.set_region(head, tail, chars, lines)
1354
1355 def untabify_region_event(self, event):
1356 head, tail, chars, lines = self.get_region()
1357 tabwidth = self._asktabwidth()
1358 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001359 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001360 self.set_region(head, tail, chars, lines)
1361
1362 def toggle_tabs_event(self, event):
1363 if self.askyesno(
1364 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001365 "Turn tabs " + ("on", "off")[self.usetabs] +
1366 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001367 ("will be", "remains at")[self.usetabs] + " 8." +
1368 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001369 parent=self.text):
1370 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001371 # Try to prevent inconsistent indentation.
1372 # User must change indent width manually after using tabs.
1373 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001374 return "break"
1375
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001376 # XXX this isn't bound to anything -- see tabwidth comments
1377## def change_tabwidth_event(self, event):
1378## new = self._asktabwidth()
1379## if new != self.tabwidth:
1380## self.tabwidth = new
1381## self.set_indentation_params(0, guess=0)
1382## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001383
1384 def change_indentwidth_event(self, event):
1385 new = self.askinteger(
1386 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001387 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001388 parent=self.text,
1389 initialvalue=self.indentwidth,
1390 minvalue=2,
1391 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001392 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001393 self.indentwidth = new
1394 return "break"
1395
1396 def get_region(self):
1397 text = self.text
1398 first, last = self.get_selection_indices()
1399 if first and last:
1400 head = text.index(first + " linestart")
1401 tail = text.index(last + "-1c lineend +1c")
1402 else:
1403 head = text.index("insert linestart")
1404 tail = text.index("insert lineend +1c")
1405 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001406 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001407 return head, tail, chars, lines
1408
1409 def set_region(self, head, tail, chars, lines):
1410 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001411 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001412 if newchars == chars:
1413 text.bell()
1414 return
1415 text.tag_remove("sel", "1.0", "end")
1416 text.mark_set("insert", head)
1417 text.undo_block_start()
1418 text.delete(head, tail)
1419 text.insert(head, newchars)
1420 text.undo_block_stop()
1421 text.tag_add("sel", head, "insert")
1422
1423 # Make string that displays as n leading blanks.
1424
1425 def _make_blanks(self, n):
1426 if self.usetabs:
1427 ntabs, nspaces = divmod(n, self.tabwidth)
1428 return '\t' * ntabs + ' ' * nspaces
1429 else:
1430 return ' ' * n
1431
1432 # Delete from beginning of line to insert point, then reinsert
1433 # column logical (meaning use tabs if appropriate) spaces.
1434
1435 def reindent_to(self, column):
1436 text = self.text
1437 text.undo_block_start()
1438 if text.compare("insert linestart", "!=", "insert"):
1439 text.delete("insert linestart", "insert")
1440 if column:
1441 text.insert("insert", self._make_blanks(column))
1442 text.undo_block_stop()
1443
1444 def _asktabwidth(self):
1445 return self.askinteger(
1446 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001447 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001448 parent=self.text,
1449 initialvalue=self.indentwidth,
1450 minvalue=2,
1451 maxvalue=16) or self.tabwidth
1452
1453 # Guess indentwidth from text content.
1454 # Return guessed indentwidth. This should not be believed unless
1455 # it's in a reasonable range (e.g., it will be 0 if no indented
1456 # blocks are found).
1457
1458 def guess_indent(self):
1459 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1460 if opener and indented:
1461 raw, indentsmall = classifyws(opener, self.tabwidth)
1462 raw, indentlarge = classifyws(indented, self.tabwidth)
1463 else:
1464 indentsmall = indentlarge = 0
1465 return indentlarge - indentsmall
1466
1467# "line.col" -> line, as an int
1468def index2line(index):
1469 return int(float(index))
1470
1471# Look at the leading whitespace in s.
1472# Return pair (# of leading ws characters,
1473# effective # of leading blanks after expanding
1474# tabs to width tabwidth)
1475
1476def classifyws(s, tabwidth):
1477 raw = effective = 0
1478 for ch in s:
1479 if ch == ' ':
1480 raw = raw + 1
1481 effective = effective + 1
1482 elif ch == '\t':
1483 raw = raw + 1
1484 effective = (effective // tabwidth + 1) * tabwidth
1485 else:
1486 break
1487 return raw, effective
1488
1489import tokenize
1490_tokenize = tokenize
1491del tokenize
1492
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001493class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001494
1495 # .run() chews over the Text widget, looking for a block opener
1496 # and the stmt following it. Returns a pair,
1497 # (line containing block opener, line containing stmt)
1498 # Either or both may be None.
1499
1500 def __init__(self, text, tabwidth):
1501 self.text = text
1502 self.tabwidth = tabwidth
1503 self.i = self.finished = 0
1504 self.blkopenline = self.indentedline = None
1505
1506 def readline(self):
1507 if self.finished:
1508 return ""
1509 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001510 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001511 if self.text.compare(mark, ">=", "end"):
1512 return ""
1513 return self.text.get(mark, mark + " lineend+1c")
1514
1515 def tokeneater(self, type, token, start, end, line,
1516 INDENT=_tokenize.INDENT,
1517 NAME=_tokenize.NAME,
1518 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1519 if self.finished:
1520 pass
1521 elif type == NAME and token in OPENERS:
1522 self.blkopenline = line
1523 elif type == INDENT and self.blkopenline:
1524 self.indentedline = line
1525 self.finished = 1
1526
1527 def run(self):
1528 save_tabsize = _tokenize.tabsize
1529 _tokenize.tabsize = self.tabwidth
1530 try:
1531 try:
Trent Nelson428de652008-03-18 22:41:35 +00001532 tokens = _tokenize.generate_tokens(self.readline)
1533 for token in tokens:
1534 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001535 except _tokenize.TokenError:
1536 # since we cut off the tokenizer early, we can trigger
1537 # spurious errors
1538 pass
1539 finally:
1540 _tokenize.tabsize = save_tabsize
1541 return self.blkopenline, self.indentedline
1542
1543### end autoindent code ###
1544
David Scherer7aced172000-08-15 01:13:23 +00001545def prepstr(s):
1546 # Helper to extract the underscore from a string, e.g.
1547 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001548 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001549 if i >= 0:
1550 s = s[:i] + s[i+1:]
1551 return i, s
1552
1553
1554keynames = {
1555 'bracketleft': '[',
1556 'bracketright': ']',
1557 'slash': '/',
1558}
1559
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001560def get_accelerator(keydefs, eventname):
1561 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001562 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1563 # if not keylist:
1564 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1565 "<<open-module>>",
1566 "<<goto-line>>",
1567 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001568 return ""
1569 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001570 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001571 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1572 s = re.sub("Key-", "", s)
1573 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1574 s = re.sub("Control-", "Ctrl-", s)
1575 s = re.sub("-", "+", s)
1576 s = re.sub("><", " ", s)
1577 s = re.sub("<", "", s)
1578 s = re.sub(">", "", s)
1579 return s
1580
1581
1582def fixwordbreaks(root):
1583 # Make sure that Tk's double-click and next/previous word
1584 # operations use our definition of a word (i.e. an identifier)
1585 tk = root.tk
1586 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1587 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1588 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1589
1590
1591def test():
1592 root = Tk()
1593 fixwordbreaks(root)
1594 root.withdraw()
1595 if sys.argv[1:]:
1596 filename = sys.argv[1]
1597 else:
1598 filename = None
1599 edit = EditorWindow(root=root, filename=filename)
1600 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001601 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001602 root.mainloop()
1603 root.destroy()
1604
1605if __name__ == '__main__':
1606 test()