blob: ffc4e88d9d456c0d61b4f62063e8e65337a6b26b [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__)
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000053 return file, filename, descr
54
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000055class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000056 from idlelib.Percolator import Percolator
57 from idlelib.ColorDelegator import ColorDelegator
58 from idlelib.UndoDelegator import UndoDelegator
59 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
60 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000061 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000062 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000063
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000064 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000065
66 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000067 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000068 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000069 if sys.platform.count('linux'):
70 # look for html docs in a couple of standard places
71 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
72 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
73 dochome = '/var/www/html/python/index.html'
74 else:
75 basepath = '/usr/share/doc/' # standard location
76 dochome = os.path.join(basepath, pyver,
77 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000078 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +000079 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000080 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +000081 if os.path.isfile(chmfile):
82 dochome = chmfile
Thomas Wouters0e3f5912006-08-11 14:57:12 +000083 elif macosxSupport.runningAsOSXApp():
84 # documentation is stored inside the python framework
85 dochome = os.path.join(sys.prefix,
86 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000087 dochome = os.path.normpath(dochome)
88 if os.path.isfile(dochome):
89 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +000090 if sys.platform == 'darwin':
91 # Safari requires real file:-URLs
92 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000093 else:
Benjamin Peterson152b6572009-01-20 15:01:54 +000094 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +000095 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000096 self.flist = flist
97 root = root or flist.root
98 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +000099 try:
100 sys.ps1
101 except AttributeError:
102 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000103 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000104 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000105 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000106 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200107 #self.top.instance_dict makes flist.inversedict available to
108 #configDialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000109 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000110 else:
111 self.tkinter_vars = {} # keys: Tkinter event names
112 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000113 self.top.instance_dict = {}
114 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000115 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000116 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000117 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000118 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000119 text_options = {
120 'name': 'text',
121 'padx': 5,
122 'wrap': 'none',
123 'width': self.width,
124 'height': idleConf.GetOption('main', 'EditorWindow', 'height')}
125 if TkVersion >= 8.5:
126 # Starting with tk 8.5 we have to set the new tabstyle option
127 # to 'wordprocessor' to achieve the same display of tabs as in
128 # older tk versions.
129 text_options['tabstyle'] = 'wordprocessor'
130 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000131 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000132
133 self.createmenubar()
134 self.apply_bindings()
135
136 self.top.protocol("WM_DELETE_WINDOW", self.close)
137 self.top.bind("<<close-window>>", self.close_event)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000138 if macosxSupport.runningAsOSXApp():
139 # Command-W on editorwindows doesn't work without this.
140 text.bind('<<close-window>>', self.close_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000141 # Some OS X systems have only one mouse button,
142 # so use control-click for pulldown menus there.
143 # (Note, AquaTk defines <2> as the right button if
144 # present and the Tk Text widget already binds <2>.)
145 text.bind("<Control-Button-1>",self.right_menu_event)
146 else:
147 # Elsewhere, use right-click for pulldown menus.
148 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000149 text.bind("<<cut>>", self.cut)
150 text.bind("<<copy>>", self.copy)
151 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000152 text.bind("<<center-insert>>", self.center_insert_event)
153 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000154 text.bind("<<python-docs>>", self.python_docs)
155 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000156 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000157 text.bind("<<open-module>>", self.open_module)
158 text.bind("<<do-nothing>>", lambda event: "break")
159 text.bind("<<select-all>>", self.select_all)
160 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000161 text.bind("<<find>>", self.find_event)
162 text.bind("<<find-again>>", self.find_again_event)
163 text.bind("<<find-in-files>>", self.find_in_files_event)
164 text.bind("<<find-selection>>", self.find_selection_event)
165 text.bind("<<replace>>", self.replace_event)
166 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000167 text.bind("<<smart-backspace>>",self.smart_backspace_event)
168 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
169 text.bind("<<smart-indent>>",self.smart_indent_event)
170 text.bind("<<indent-region>>",self.indent_region_event)
171 text.bind("<<dedent-region>>",self.dedent_region_event)
172 text.bind("<<comment-region>>",self.comment_region_event)
173 text.bind("<<uncomment-region>>",self.uncomment_region_event)
174 text.bind("<<tabify-region>>",self.tabify_region_event)
175 text.bind("<<untabify-region>>",self.untabify_region_event)
176 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
177 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000178 text.bind("<Left>", self.move_at_edge_if_selection(0))
179 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000180 text.bind("<<del-word-left>>", self.del_word_left)
181 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000182 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000183
David Scherer7aced172000-08-15 01:13:23 +0000184 if flist:
185 flist.inversedict[self] = key
186 if key:
187 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000188 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000189 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
190 text.bind("<<open-class-browser>>", self.open_class_browser)
191 text.bind("<<open-path-browser>>", self.open_path_browser)
192
Steven M. Gava898a3652001-10-07 11:10:44 +0000193 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000194 vbar['command'] = text.yview
195 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000196 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000197 fontWeight = 'normal'
198 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000199 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000200 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
201 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
202 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000203 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
204 text.pack(side=TOP, fill=BOTH, expand=1)
205 text.focus_set()
206
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000207 # usetabs true -> literal tab characters are used by indent and
208 # dedent cmds, possibly mixed with spaces if
209 # indentwidth is not a multiple of tabwidth,
210 # which will cause Tabnanny to nag!
211 # false -> tab characters are converted to spaces by indent
212 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000213 # Although use-spaces=0 can be configured manually in config-main.def,
214 # configuration of tabs v. spaces is not supported in the configuration
215 # dialog. IDLE promotes the preferred Python indentation: use spaces!
216 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
217 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000218
219 # tabwidth is the display width of a literal tab character.
220 # CAUTION: telling Tk to use anything other than its default
221 # tab setting causes it to use an entirely different tabbing algorithm,
222 # treating tab stops as fixed distances from the left margin.
223 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000224 self.tabwidth = 8 # must remain 8 until Tk is fixed.
225
226 # indentwidth is the number of screen characters per indent level.
227 # The recommended Python indentation is four spaces.
228 self.indentwidth = self.tabwidth
229 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000230
231 # If context_use_ps1 is true, parsing searches back for a ps1 line;
232 # else searches for a popular (if, def, ...) Python stmt.
233 self.context_use_ps1 = False
234
235 # When searching backwards for a reliable place to begin parsing,
236 # first start num_context_lines[0] lines back, then
237 # num_context_lines[1] lines back if that didn't work, and so on.
238 # The last value should be huge (larger than the # of lines in a
239 # conceivable file).
240 # Making the initial values larger slows things down more often.
241 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000242 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000243 self.undo = undo = self.UndoDelegator()
244 per.insertfilter(undo)
245 text.undo_block_start = undo.undo_block_start
246 text.undo_block_stop = undo.undo_block_stop
247 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000248 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000249 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000250 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000251 self.good_load = False
252 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000253 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000254 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000255 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000256 if io.loadfile(filename):
257 self.good_load = True
258 is_py_src = self.ispythonsource(filename)
259 self.set_indentation_params(is_py_src)
260 if is_py_src:
261 self.color = color = self.ColorDelegator()
262 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000263 else:
264 io.set_filename(filename)
Christian Heimesa156e092008-02-16 07:38:31 +0000265 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000266 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000267 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000268 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000269 menu = self.menudict.get('windows')
270 if menu:
271 end = menu.index("end")
272 if end is None:
273 end = -1
274 if end >= 0:
275 menu.add_separator()
276 end = end + 1
277 self.wmenu_end = end
278 WindowList.register_callback(self.postwindowsmenu)
279
280 # Some abstractions so IDLE extensions are cross-IDE
281 self.askyesno = tkMessageBox.askyesno
282 self.askinteger = tkSimpleDialog.askinteger
283 self.showerror = tkMessageBox.showerror
284
Martin v. Löwis307021f2005-11-27 16:59:04 +0000285 def _filename_to_unicode(self, filename):
286 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000287 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000288 return filename
289 else:
290 try:
291 return filename.decode(self.filesystemencoding)
292 except UnicodeDecodeError:
293 # XXX
294 try:
295 return filename.decode(self.encoding)
296 except UnicodeDecodeError:
297 # byte-to-byte conversion
298 return filename.decode('iso8859-1')
299
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000300 def new_callback(self, event):
301 dirname, basename = self.io.defaultfilename()
302 self.flist.new(dirname)
303 return "break"
304
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000305 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400306 if (event.state & 4) != 0 and event.keysym == "Home":
307 # state&4==Control. If <Control-Home>, use the Tk binding.
308 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000309 if self.text.index("iomark") and \
310 self.text.compare("iomark", "<=", "insert lineend") and \
311 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400312 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000313 insertpt = int(self.text.index("iomark").split(".")[1])
314 else:
315 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000316 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000317 if line[insertpt] not in (' ','\t'):
318 break
319 else:
320 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000321 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000322 if insertpt == lineat:
323 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000324 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000325 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400326 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000327 self.text.tag_remove("sel", "1.0", "end")
328 else:
329 if not self.text.index("sel.first"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400330 self.text.mark_set("my_anchor", "insert") # there was no previous selection
331 else:
332 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
333 self.text.mark_set("my_anchor", "sel.first") # extend back
334 else:
335 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000336 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400337 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000338 if self.text.compare(first,">",last):
339 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000340 self.text.tag_remove("sel", "1.0", "end")
341 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000342 self.text.mark_set("insert", dest)
343 self.text.see("insert")
344 return "break"
345
David Scherer7aced172000-08-15 01:13:23 +0000346 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000347 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000348 if macosxSupport.runningAsOSXApp():
349 # Insert some padding to avoid obscuring some of the statusbar
350 # by the resize widget.
351 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000352 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
353 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
354 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000355 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
356 self.text.event_add("<<set-line-and-column>>",
357 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000358 self.text.after_idle(self.set_line_and_column)
359
360 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000361 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000362 self.status_bar.set_label('column', 'Col: %s' % column)
363 self.status_bar.set_label('line', 'Ln: %s' % line)
364
David Scherer7aced172000-08-15 01:13:23 +0000365 menu_specs = [
366 ("file", "_File"),
367 ("edit", "_Edit"),
368 ("format", "F_ormat"),
369 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000370 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000371 ("windows", "_Windows"),
372 ("help", "_Help"),
373 ]
374
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000375 if macosxSupport.runningAsOSXApp():
376 del menu_specs[-3]
377 menu_specs[-2] = ("windows", "_Window")
378
379
David Scherer7aced172000-08-15 01:13:23 +0000380 def createmenubar(self):
381 mbar = self.menubar
382 self.menudict = menudict = {}
383 for name, label in self.menu_specs:
384 underline, label = prepstr(label)
385 menudict[name] = menu = Menu(mbar, name=name)
386 mbar.add_cascade(label=label, menu=menu, underline=underline)
Georg Brandlaedd2892010-12-19 10:10:32 +0000387 if macosxSupport.isCarbonAquaTk(self.root):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000388 # Insert the application menu
389 menudict['application'] = menu = Menu(mbar, name='apple')
390 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000391 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000392 self.recent_files_menu = Menu(self.menubar)
393 self.menudict['file'].insert_cascade(3, label='Recent Files',
394 underline=0,
395 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000396 self.base_helpmenu_length = self.menudict['help'].index(END)
397 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000398
399 def postwindowsmenu(self):
400 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000401 menu = self.menudict['windows']
402 end = menu.index("end")
403 if end is None:
404 end = -1
405 if end > self.wmenu_end:
406 menu.delete(self.wmenu_end+1, end)
407 WindowList.add_windows_to_menu(menu)
408
409 rmenu = None
410
411 def right_menu_event(self, event):
412 self.text.tag_remove("sel", "1.0", "end")
413 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
414 if not self.rmenu:
415 self.make_rmenu()
416 rmenu = self.rmenu
417 self.event = event
418 iswin = sys.platform[:3] == 'win'
419 if iswin:
420 self.text.config(cursor="arrow")
421 rmenu.tk_popup(event.x_root, event.y_root)
422 if iswin:
423 self.text.config(cursor="ibeam")
424
425 rmenu_specs = [
426 # ("Label", "<<virtual-event>>"), ...
427 ("Close", "<<close-window>>"), # Example
428 ]
429
430 def make_rmenu(self):
431 rmenu = Menu(self.text, tearoff=0)
432 for label, eventname in self.rmenu_specs:
433 def command(text=self.text, eventname=eventname):
434 text.event_generate(eventname)
435 rmenu.add_command(label=label, command=command)
436 self.rmenu = rmenu
437
438 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000439 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000440
Steven M. Gava3b55a892001-11-21 05:56:26 +0000441 def config_dialog(self, event=None):
442 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000443
David Scherer7aced172000-08-15 01:13:23 +0000444 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000445 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000446 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000447
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000448 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000449 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000450 try:
451 os.startfile(self.help_url)
452 except WindowsError as why:
453 tkMessageBox.showerror(title='Document Start Failure',
454 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000455 else:
456 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000457 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000458
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000459 def cut(self,event):
460 self.text.event_generate("<<Cut>>")
461 return "break"
462
463 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000464 if not self.text.tag_ranges("sel"):
465 # There is no selection, so do nothing and maybe interrupt.
466 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000467 self.text.event_generate("<<Copy>>")
468 return "break"
469
470 def paste(self,event):
471 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000472 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000473 return "break"
474
David Scherer7aced172000-08-15 01:13:23 +0000475 def select_all(self, event=None):
476 self.text.tag_add("sel", "1.0", "end-1c")
477 self.text.mark_set("insert", "1.0")
478 self.text.see("insert")
479 return "break"
480
481 def remove_selection(self, event=None):
482 self.text.tag_remove("sel", "1.0", "end")
483 self.text.see("insert")
484
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000485 def move_at_edge_if_selection(self, edge_index):
486 """Cursor move begins at start or end of selection
487
488 When a left/right cursor key is pressed create and return to Tkinter a
489 function which causes a cursor move from the associated edge of the
490 selection.
491
492 """
493 self_text_index = self.text.index
494 self_text_mark_set = self.text.mark_set
495 edges_table = ("sel.first+1c", "sel.last-1c")
496 def move_at_edge(event):
497 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
498 try:
499 self_text_index("sel.first")
500 self_text_mark_set("insert", edges_table[edge_index])
501 except TclError:
502 pass
503 return move_at_edge
504
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000505 def del_word_left(self, event):
506 self.text.event_generate('<Meta-Delete>')
507 return "break"
508
509 def del_word_right(self, event):
510 self.text.event_generate('<Meta-d>')
511 return "break"
512
Steven M. Gavac5976402002-01-04 03:06:08 +0000513 def find_event(self, event):
514 SearchDialog.find(self.text)
515 return "break"
516
517 def find_again_event(self, event):
518 SearchDialog.find_again(self.text)
519 return "break"
520
521 def find_selection_event(self, event):
522 SearchDialog.find_selection(self.text)
523 return "break"
524
525 def find_in_files_event(self, event):
526 GrepDialog.grep(self.text, self.io, self.flist)
527 return "break"
528
529 def replace_event(self, event):
530 ReplaceDialog.replace(self.text)
531 return "break"
532
533 def goto_line_event(self, event):
534 text = self.text
535 lineno = tkSimpleDialog.askinteger("Goto",
536 "Go to line number:",parent=text)
537 if lineno is None:
538 return "break"
539 if lineno <= 0:
540 text.bell()
541 return "break"
542 text.mark_set("insert", "%d.0" % lineno)
543 text.see("insert")
544
David Scherer7aced172000-08-15 01:13:23 +0000545 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000546 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000547 try:
548 name = self.text.get("sel.first", "sel.last")
549 except TclError:
550 name = ""
551 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000552 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000553 name = tkSimpleDialog.askstring("Module",
554 "Enter the name of a Python module\n"
555 "to search on sys.path and open:",
556 parent=self.text, initialvalue=name)
557 if name:
558 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000559 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000560 return
David Scherer7aced172000-08-15 01:13:23 +0000561 # XXX Ought to insert current file's directory in front of path
562 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000563 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000564 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000565 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
566 return
567 if type != imp.PY_SOURCE:
568 tkMessageBox.showerror("Unsupported type",
569 "%s is not a source module" % name, parent=self.text)
570 return
571 if f:
572 f.close()
573 if self.flist:
574 self.flist.open(file)
575 else:
576 self.io.loadfile(file)
577
578 def open_class_browser(self, event=None):
579 filename = self.io.filename
580 if not filename:
581 tkMessageBox.showerror(
582 "No filename",
583 "This buffer has no associated filename",
584 master=self.text)
585 self.text.focus_set()
586 return None
587 head, tail = os.path.split(filename)
588 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000589 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000590 ClassBrowser.ClassBrowser(self.flist, base, [head])
591
592 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000593 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000594 PathBrowser.PathBrowser(self.flist)
595
596 def gotoline(self, lineno):
597 if lineno is not None and lineno > 0:
598 self.text.mark_set("insert", "%d.0" % lineno)
599 self.text.tag_remove("sel", "1.0", "end")
600 self.text.tag_add("sel", "insert", "insert +1l")
601 self.center()
602
603 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000604 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000605 return True
David Scherer7aced172000-08-15 01:13:23 +0000606 base, ext = os.path.splitext(os.path.basename(filename))
607 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000608 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000609 line = self.text.get('1.0', '1.0 lineend')
610 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000611
612 def close_hook(self):
613 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000614 self.flist.unregister_maybe_terminate(self)
615 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000616
617 def set_close_hook(self, close_hook):
618 self.close_hook = close_hook
619
620 def filename_change_hook(self):
621 if self.flist:
622 self.flist.filename_changed_edit(self)
623 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000624 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000625 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000626
Christian Heimesa156e092008-02-16 07:38:31 +0000627 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000628 if self.color:
629 return
Christian Heimesa156e092008-02-16 07:38:31 +0000630 if self.ispythonsource(self.io.filename):
631 self.color = self.ColorDelegator()
632 # can add more colorizers here...
633 if self.color:
634 self.per.removefilter(self.undo)
635 self.per.insertfilter(self.color)
636 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000637
Christian Heimesa156e092008-02-16 07:38:31 +0000638 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000639 if not self.color:
640 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000641 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000642 self.per.removefilter(self.color)
643 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000644
Steven M. Gavab77d3432002-03-02 07:16:21 +0000645 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000646 "Update the colour theme"
647 # Called from self.filename_change_hook and from configDialog.py
648 self._rmcolorizer()
649 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000650 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000651 normal_colors = idleConf.GetHighlight(theme, 'normal')
652 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
653 select_colors = idleConf.GetHighlight(theme, 'hilite')
654 self.text.config(
655 foreground=normal_colors['foreground'],
656 background=normal_colors['background'],
657 insertbackground=cursor_color,
658 selectforeground=select_colors['foreground'],
659 selectbackground=select_colors['background'],
660 )
David Scherer7aced172000-08-15 01:13:23 +0000661
Guido van Rossum33d26892007-08-05 15:29:28 +0000662 IDENTCHARS = string.ascii_letters + string.digits + "_"
663
664 def colorize_syntax_error(self, text, pos):
665 text.tag_add("ERROR", pos)
666 char = text.get(pos)
667 if char and char in self.IDENTCHARS:
668 text.tag_add("ERROR", pos + " wordstart", pos)
669 if '\n' == text.get(pos): # error at line end
670 text.mark_set("insert", pos)
671 else:
672 text.mark_set("insert", pos + "+1c")
673 text.see(pos)
674
Steven M. Gavab1585412002-03-12 00:21:56 +0000675 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000676 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000677 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000678 fontWeight='normal'
679 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
680 fontWeight='bold'
681 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
682 idleConf.GetOption('main','EditorWindow','font-size'),
683 fontWeight))
684
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000685 def RemoveKeybindings(self):
686 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000687 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000688 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000689 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000690 self.text.event_delete(event, *keylist)
691 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000692 xkeydefs = idleConf.GetExtensionBindings(extensionName)
693 if xkeydefs:
694 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000695 self.text.event_delete(event, *keylist)
696
697 def ApplyKeybindings(self):
698 "Update the keybindings after they are changed"
699 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000700 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000701 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000702 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 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000706 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000707 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000708 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000709 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000710 for item in menu[1]:
711 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000712 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000713 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000714 menu = self.menudict[menubarItem]
715 end = menu.index(END) + 1
716 for index in range(0, end):
717 if menu.type(index) == 'command':
718 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000719 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000720 itemName = menu.entrycget(index, 'label')
721 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000722 if menubarItem in menuEventDict:
723 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000724 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000725 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000726 accel = get_accelerator(keydefs, event)
727 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000728
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000729 def set_notabs_indentwidth(self):
730 "Update the indentwidth if changed and not using tabs in this window"
731 # Called from configDialog.py
732 if not self.usetabs:
733 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
734 type='int')
735
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000736 def reset_help_menu_entries(self):
737 "Update the additional help entries on the Help menu"
738 help_list = idleConf.GetAllExtraHelpSourcesList()
739 helpmenu = self.menudict['help']
740 # first delete the extra help entries, if any
741 helpmenu_length = helpmenu.index(END)
742 if helpmenu_length > self.base_helpmenu_length:
743 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
744 # then rebuild them
745 if help_list:
746 helpmenu.add_separator()
747 for entry in help_list:
748 cmd = self.__extra_help_callback(entry[1])
749 helpmenu.add_command(label=entry[0], command=cmd)
750 # and update the menu dictionary
751 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000752
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000753 def __extra_help_callback(self, helpfile):
754 "Create a callback with the helpfile value frozen at definition time"
755 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000756 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000757 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000758 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000759 try:
760 os.startfile(helpfile)
761 except WindowsError as why:
762 tkMessageBox.showerror(title='Document Start Failure',
763 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000764 else:
765 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000766 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000767
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000768 def update_recent_files_list(self, new_file=None):
769 "Load and update the recent files list and menus"
770 rf_list = []
771 if os.path.exists(self.recent_files_path):
Ned Deily122539e2011-01-24 21:46:44 +0000772 rf_list_file = open(self.recent_files_path,'r',
773 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000774 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000775 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000776 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000777 rf_list_file.close()
778 if new_file:
779 new_file = os.path.abspath(new_file) + '\n'
780 if new_file in rf_list:
781 rf_list.remove(new_file) # move to top
782 rf_list.insert(0, new_file)
783 # clean and save the recent files list
784 bad_paths = []
785 for path in rf_list:
786 if '\0' in path or not os.path.exists(path[0:-1]):
787 bad_paths.append(path)
788 rf_list = [path for path in rf_list if path not in bad_paths]
789 ulchars = "1234567890ABCDEFGHIJK"
790 rf_list = rf_list[0:len(ulchars)]
Ned Deily122539e2011-01-24 21:46:44 +0000791 rf_file = open(self.recent_files_path, 'w',
792 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000793 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000794 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000795 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000796 rf_file.close()
797 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000798 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000799 menu = instance.recent_files_menu
800 menu.delete(1, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000801 for i, file_name in enumerate(rf_list):
802 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000803 # make unicode string to display non-ASCII chars correctly
804 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000805 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000806 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000807 command=callback,
808 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000809
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000810 def __recent_file_callback(self, file_name):
811 def open_recent_file(fn_closure=file_name):
812 self.io.open(editFile=fn_closure)
813 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000814
David Scherer7aced172000-08-15 01:13:23 +0000815 def saved_change_hook(self):
816 short = self.short_title()
817 long = self.long_title()
818 if short and long:
819 title = short + " - " + long
820 elif short:
821 title = short
822 elif long:
823 title = long
824 else:
825 title = "Untitled"
826 icon = short or long or title
827 if not self.get_saved():
828 title = "*%s*" % title
829 icon = "*%s" % icon
830 self.top.wm_title(title)
831 self.top.wm_iconname(icon)
832
833 def get_saved(self):
834 return self.undo.get_saved()
835
836 def set_saved(self, flag):
837 self.undo.set_saved(flag)
838
839 def reset_undo(self):
840 self.undo.reset_undo()
841
842 def short_title(self):
843 filename = self.io.filename
844 if filename:
845 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000846 # return unicode string to display non-ASCII chars correctly
847 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000848
849 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000850 # return unicode string to display non-ASCII chars correctly
851 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000852
853 def center_insert_event(self, event):
854 self.center()
855
856 def center(self, mark="insert"):
857 text = self.text
858 top, bot = self.getwindowlines()
859 lineno = self.getlineno(mark)
860 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000861 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000862 text.yview(float(newtop))
863
864 def getwindowlines(self):
865 text = self.text
866 top = self.getlineno("@0,0")
867 bot = self.getlineno("@0,65535")
868 if top == bot and text.winfo_height() == 1:
869 # Geometry manager hasn't run yet
870 height = int(text['height'])
871 bot = top + height - 1
872 return top, bot
873
874 def getlineno(self, mark="insert"):
875 text = self.text
876 return int(float(text.index(mark)))
877
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000878 def get_geometry(self):
879 "Return (width, height, x, y)"
880 geom = self.top.wm_geometry()
881 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000882 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000883
David Scherer7aced172000-08-15 01:13:23 +0000884 def close_event(self, event):
885 self.close()
886
887 def maybesave(self):
888 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000889 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000890 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000891 self.top.deiconify()
892 self.top.lower()
893 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000894 return self.io.maybesave()
895
896 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000897 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000898 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000899 self._close()
900 return reply
901
902 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000903 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000904 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000905 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000906 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000907 self.io.close()
908 self.io = None
909 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000910 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000911 self.color.close(False)
912 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000913 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000914 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000915 self.per.close()
916 self.per = None
917 self.top.destroy()
918 if self.close_hook:
919 # unless override: unregister from flist, terminate if last window
920 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000921
922 def load_extensions(self):
923 self.extensions = {}
924 self.load_standard_extensions()
925
926 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000927 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000928 if hasattr(ins, "close"):
929 ins.close()
930 self.extensions = {}
931
932 def load_standard_extensions(self):
933 for name in self.get_standard_extension_names():
934 try:
935 self.load_extension(name)
936 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000937 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000938 traceback.print_exc()
939
940 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000941 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000942
943 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000944 try:
945 mod = __import__(name, globals(), locals(), [])
946 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000947 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000948 raise
David Scherer7aced172000-08-15 01:13:23 +0000949 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000950 keydefs = idleConf.GetExtensionBindings(name)
951 if hasattr(cls, "menudefs"):
952 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000953 ins = cls(self)
954 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000955 if keydefs:
956 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000957 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000958 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000959 while methodname[:1] == '<':
960 methodname = methodname[1:]
961 while methodname[-1:] == '>':
962 methodname = methodname[:-1]
963 methodname = methodname + "_event"
964 if hasattr(ins, methodname):
965 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000966
967 def apply_bindings(self, keydefs=None):
968 if keydefs is None:
969 keydefs = self.Bindings.default_keydefs
970 text = self.text
971 text.keydefs = keydefs
972 for event, keylist in keydefs.items():
973 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000974 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000975
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000976 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000977 """Add appropriate entries to the menus and submenus
978
979 Menus that are absent or None in self.menudict are ignored.
980 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000981 if menudefs is None:
982 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000983 if keydefs is None:
984 keydefs = self.Bindings.default_keydefs
985 menudict = self.menudict
986 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000987 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000988 menu = menudict.get(mname)
989 if not menu:
990 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000991 for entry in entrylist:
992 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000993 menu.add_separator()
994 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000995 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000996 checkbutton = (label[:1] == '!')
997 if checkbutton:
998 label = label[1:]
999 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001000 accelerator = get_accelerator(keydefs, eventname)
1001 def command(text=text, eventname=eventname):
1002 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001003 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001004 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001005 menu.add_checkbutton(label=label, underline=underline,
1006 command=command, accelerator=accelerator,
1007 variable=var)
1008 else:
1009 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001010 command=command,
1011 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001012
1013 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001014 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001015 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001016 value = var.get()
1017 return value
1018 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001019 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001020
1021 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001022 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001023 if var:
1024 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001025 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001026 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001027
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001028 def get_var_obj(self, name, vartype=None):
1029 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001030 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001031 # create a Tkinter variable object with self.text as master:
1032 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001033 return var
1034
1035 # Tk implementations of "virtual text methods" -- each platform
1036 # reusing IDLE's support code needs to define these for its GUI's
1037 # flavor of widget.
1038
1039 # Is character at text_index in a Python string? Return 0 for
1040 # "guaranteed no", true for anything else. This info is expensive
1041 # to compute ab initio, but is probably already known by the
1042 # platform's colorizer.
1043
1044 def is_char_in_string(self, text_index):
1045 if self.color:
1046 # Return true iff colorizer hasn't (re)gotten this far
1047 # yet, or the character is tagged as being in a string
1048 return self.text.tag_prevrange("TODO", text_index) or \
1049 "STRING" in self.text.tag_names(text_index)
1050 else:
1051 # The colorizer is missing: assume the worst
1052 return 1
1053
1054 # If a selection is defined in the text widget, return (start,
1055 # end) as Tkinter text indices, otherwise return (None, None)
1056 def get_selection_indices(self):
1057 try:
1058 first = self.text.index("sel.first")
1059 last = self.text.index("sel.last")
1060 return first, last
1061 except TclError:
1062 return None, None
1063
1064 # Return the text widget's current view of what a tab stop means
1065 # (equivalent width in spaces).
1066
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001067 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001068 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1069 return int(current)
1070
1071 # Set the text widget's current view of what a tab stop means.
1072
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001073 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001074 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001075 if self.get_tk_tabwidth() != newtabwidth:
1076 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001077 pixels = text.tk.call("font", "measure", text["font"],
1078 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001079 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001080 text.configure(tabs=pixels)
1081
Guido van Rossum33d26892007-08-05 15:29:28 +00001082### begin autoindent code ### (configuration was moved to beginning of class)
1083
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001084 def set_indentation_params(self, is_py_src, guess=True):
1085 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001086 i = self.guess_indent()
1087 if 2 <= i <= 8:
1088 self.indentwidth = i
1089 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001090 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001091 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001092
1093 def smart_backspace_event(self, event):
1094 text = self.text
1095 first, last = self.get_selection_indices()
1096 if first and last:
1097 text.delete(first, last)
1098 text.mark_set("insert", first)
1099 return "break"
1100 # Delete whitespace left, until hitting a real char or closest
1101 # preceding virtual tab stop.
1102 chars = text.get("insert linestart", "insert")
1103 if chars == '':
1104 if text.compare("insert", ">", "1.0"):
1105 # easy: delete preceding newline
1106 text.delete("insert-1c")
1107 else:
1108 text.bell() # at start of buffer
1109 return "break"
1110 if chars[-1] not in " \t":
1111 # easy: delete preceding real char
1112 text.delete("insert-1c")
1113 return "break"
1114 # Ick. It may require *inserting* spaces if we back up over a
1115 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001116 tabwidth = self.tabwidth
1117 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001118 assert have > 0
1119 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001120 # Debug prompt is multilined....
1121 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001122 ncharsdeleted = 0
1123 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001124 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001125 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001126 chars = chars[:-1]
1127 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001128 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001129 if have <= want or chars[-1] not in " \t":
1130 break
1131 text.undo_block_start()
1132 text.delete("insert-%dc" % ncharsdeleted, "insert")
1133 if have < want:
1134 text.insert("insert", ' ' * (want - have))
1135 text.undo_block_stop()
1136 return "break"
1137
1138 def smart_indent_event(self, event):
1139 # if intraline selection:
1140 # delete it
1141 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001142 # do indent-region
1143 # else:
1144 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001145 text = self.text
1146 first, last = self.get_selection_indices()
1147 text.undo_block_start()
1148 try:
1149 if first and last:
1150 if index2line(first) != index2line(last):
1151 return self.indent_region_event(event)
1152 text.delete(first, last)
1153 text.mark_set("insert", first)
1154 prefix = text.get("insert linestart", "insert")
1155 raw, effective = classifyws(prefix, self.tabwidth)
1156 if raw == len(prefix):
1157 # only whitespace to the left
1158 self.reindent_to(effective + self.indentwidth)
1159 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001160 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001161 if self.usetabs:
1162 pad = '\t'
1163 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001164 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001165 n = self.indentwidth
1166 pad = ' ' * (n - effective % n)
1167 text.insert("insert", pad)
1168 text.see("insert")
1169 return "break"
1170 finally:
1171 text.undo_block_stop()
1172
1173 def newline_and_indent_event(self, event):
1174 text = self.text
1175 first, last = self.get_selection_indices()
1176 text.undo_block_start()
1177 try:
1178 if first and last:
1179 text.delete(first, last)
1180 text.mark_set("insert", first)
1181 line = text.get("insert linestart", "insert")
1182 i, n = 0, len(line)
1183 while i < n and line[i] in " \t":
1184 i = i+1
1185 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001186 # the cursor is in or at leading indentation in a continuation
1187 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001188 text.insert("insert linestart", '\n')
1189 return "break"
1190 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001191 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001192 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001193 last_line_of_prompt = sys.ps1.split('\n')[-1]
1194 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001195 line = line[:-1]
1196 i = i+1
1197 if i:
1198 text.delete("insert - %d chars" % i, "insert")
1199 # strip whitespace after insert point
1200 while text.get("insert") in " \t":
1201 text.delete("insert")
1202 # start new line
1203 text.insert("insert", '\n')
1204
1205 # adjust indentation for continuations and block
1206 # open/close first need to find the last stmt
1207 lno = index2line(text.index('insert'))
1208 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001209 if not self.context_use_ps1:
1210 for context in self.num_context_lines:
1211 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001212 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001213 rawtext = text.get(startatindex, "insert")
1214 y.set_str(rawtext)
1215 bod = y.find_good_parse_start(
1216 self.context_use_ps1,
1217 self._build_char_in_string_func(startatindex))
1218 if bod is not None or startat == 1:
1219 break
1220 y.set_lo(bod or 0)
1221 else:
1222 r = text.tag_prevrange("console", "insert")
1223 if r:
1224 startatindex = r[1]
1225 else:
1226 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001227 rawtext = text.get(startatindex, "insert")
1228 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001229 y.set_lo(0)
1230
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001231 c = y.get_continuation_type()
1232 if c != PyParse.C_NONE:
1233 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001234 if c == PyParse.C_STRING_FIRST_LINE:
1235 # after the first line of a string; do not indent at all
1236 pass
1237 elif c == PyParse.C_STRING_NEXT_LINES:
1238 # inside a string which started before this line;
1239 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001240 text.insert("insert", indent)
1241 elif c == PyParse.C_BRACKET:
1242 # line up with the first (if any) element of the
1243 # last open bracket structure; else indent one
1244 # level beyond the indent of the line with the
1245 # last open bracket
1246 self.reindent_to(y.compute_bracket_indent())
1247 elif c == PyParse.C_BACKSLASH:
1248 # if more than one line in this stmt already, just
1249 # mimic the current indent; else if initial line
1250 # has a start on an assignment stmt, indent to
1251 # beyond leftmost =; else to beyond first chunk of
1252 # non-whitespace on initial line
1253 if y.get_num_lines_in_stmt() > 1:
1254 text.insert("insert", indent)
1255 else:
1256 self.reindent_to(y.compute_backslash_indent())
1257 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001258 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001259 return "break"
1260
1261 # This line starts a brand new stmt; indent relative to
1262 # indentation of initial line of closest preceding
1263 # interesting stmt.
1264 indent = y.get_base_indent_string()
1265 text.insert("insert", indent)
1266 if y.is_block_opener():
1267 self.smart_indent_event(event)
1268 elif indent and y.is_block_closer():
1269 self.smart_backspace_event(event)
1270 return "break"
1271 finally:
1272 text.see("insert")
1273 text.undo_block_stop()
1274
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001275 # Our editwin provides a is_char_in_string function that works
1276 # with a Tk text index, but PyParse only knows about offsets into
1277 # a string. This builds a function for PyParse that accepts an
1278 # offset.
1279
1280 def _build_char_in_string_func(self, startindex):
1281 def inner(offset, _startindex=startindex,
1282 _icis=self.is_char_in_string):
1283 return _icis(_startindex + "+%dc" % offset)
1284 return inner
1285
1286 def indent_region_event(self, event):
1287 head, tail, chars, lines = self.get_region()
1288 for pos in range(len(lines)):
1289 line = lines[pos]
1290 if line:
1291 raw, effective = classifyws(line, self.tabwidth)
1292 effective = effective + self.indentwidth
1293 lines[pos] = self._make_blanks(effective) + line[raw:]
1294 self.set_region(head, tail, chars, lines)
1295 return "break"
1296
1297 def dedent_region_event(self, event):
1298 head, tail, chars, lines = self.get_region()
1299 for pos in range(len(lines)):
1300 line = lines[pos]
1301 if line:
1302 raw, effective = classifyws(line, self.tabwidth)
1303 effective = max(effective - self.indentwidth, 0)
1304 lines[pos] = self._make_blanks(effective) + line[raw:]
1305 self.set_region(head, tail, chars, lines)
1306 return "break"
1307
1308 def comment_region_event(self, event):
1309 head, tail, chars, lines = self.get_region()
1310 for pos in range(len(lines) - 1):
1311 line = lines[pos]
1312 lines[pos] = '##' + line
1313 self.set_region(head, tail, chars, lines)
1314
1315 def uncomment_region_event(self, event):
1316 head, tail, chars, lines = self.get_region()
1317 for pos in range(len(lines)):
1318 line = lines[pos]
1319 if not line:
1320 continue
1321 if line[:2] == '##':
1322 line = line[2:]
1323 elif line[:1] == '#':
1324 line = line[1:]
1325 lines[pos] = line
1326 self.set_region(head, tail, chars, lines)
1327
1328 def tabify_region_event(self, event):
1329 head, tail, chars, lines = self.get_region()
1330 tabwidth = self._asktabwidth()
1331 for pos in range(len(lines)):
1332 line = lines[pos]
1333 if line:
1334 raw, effective = classifyws(line, tabwidth)
1335 ntabs, nspaces = divmod(effective, tabwidth)
1336 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1337 self.set_region(head, tail, chars, lines)
1338
1339 def untabify_region_event(self, event):
1340 head, tail, chars, lines = self.get_region()
1341 tabwidth = self._asktabwidth()
1342 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001343 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001344 self.set_region(head, tail, chars, lines)
1345
1346 def toggle_tabs_event(self, event):
1347 if self.askyesno(
1348 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001349 "Turn tabs " + ("on", "off")[self.usetabs] +
1350 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001351 ("will be", "remains at")[self.usetabs] + " 8." +
1352 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001353 parent=self.text):
1354 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001355 # Try to prevent inconsistent indentation.
1356 # User must change indent width manually after using tabs.
1357 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001358 return "break"
1359
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001360 # XXX this isn't bound to anything -- see tabwidth comments
1361## def change_tabwidth_event(self, event):
1362## new = self._asktabwidth()
1363## if new != self.tabwidth:
1364## self.tabwidth = new
1365## self.set_indentation_params(0, guess=0)
1366## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001367
1368 def change_indentwidth_event(self, event):
1369 new = self.askinteger(
1370 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001371 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001372 parent=self.text,
1373 initialvalue=self.indentwidth,
1374 minvalue=2,
1375 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001376 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001377 self.indentwidth = new
1378 return "break"
1379
1380 def get_region(self):
1381 text = self.text
1382 first, last = self.get_selection_indices()
1383 if first and last:
1384 head = text.index(first + " linestart")
1385 tail = text.index(last + "-1c lineend +1c")
1386 else:
1387 head = text.index("insert linestart")
1388 tail = text.index("insert lineend +1c")
1389 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001390 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001391 return head, tail, chars, lines
1392
1393 def set_region(self, head, tail, chars, lines):
1394 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001395 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001396 if newchars == chars:
1397 text.bell()
1398 return
1399 text.tag_remove("sel", "1.0", "end")
1400 text.mark_set("insert", head)
1401 text.undo_block_start()
1402 text.delete(head, tail)
1403 text.insert(head, newchars)
1404 text.undo_block_stop()
1405 text.tag_add("sel", head, "insert")
1406
1407 # Make string that displays as n leading blanks.
1408
1409 def _make_blanks(self, n):
1410 if self.usetabs:
1411 ntabs, nspaces = divmod(n, self.tabwidth)
1412 return '\t' * ntabs + ' ' * nspaces
1413 else:
1414 return ' ' * n
1415
1416 # Delete from beginning of line to insert point, then reinsert
1417 # column logical (meaning use tabs if appropriate) spaces.
1418
1419 def reindent_to(self, column):
1420 text = self.text
1421 text.undo_block_start()
1422 if text.compare("insert linestart", "!=", "insert"):
1423 text.delete("insert linestart", "insert")
1424 if column:
1425 text.insert("insert", self._make_blanks(column))
1426 text.undo_block_stop()
1427
1428 def _asktabwidth(self):
1429 return self.askinteger(
1430 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001431 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001432 parent=self.text,
1433 initialvalue=self.indentwidth,
1434 minvalue=2,
1435 maxvalue=16) or self.tabwidth
1436
1437 # Guess indentwidth from text content.
1438 # Return guessed indentwidth. This should not be believed unless
1439 # it's in a reasonable range (e.g., it will be 0 if no indented
1440 # blocks are found).
1441
1442 def guess_indent(self):
1443 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1444 if opener and indented:
1445 raw, indentsmall = classifyws(opener, self.tabwidth)
1446 raw, indentlarge = classifyws(indented, self.tabwidth)
1447 else:
1448 indentsmall = indentlarge = 0
1449 return indentlarge - indentsmall
1450
1451# "line.col" -> line, as an int
1452def index2line(index):
1453 return int(float(index))
1454
1455# Look at the leading whitespace in s.
1456# Return pair (# of leading ws characters,
1457# effective # of leading blanks after expanding
1458# tabs to width tabwidth)
1459
1460def classifyws(s, tabwidth):
1461 raw = effective = 0
1462 for ch in s:
1463 if ch == ' ':
1464 raw = raw + 1
1465 effective = effective + 1
1466 elif ch == '\t':
1467 raw = raw + 1
1468 effective = (effective // tabwidth + 1) * tabwidth
1469 else:
1470 break
1471 return raw, effective
1472
1473import tokenize
1474_tokenize = tokenize
1475del tokenize
1476
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001477class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001478
1479 # .run() chews over the Text widget, looking for a block opener
1480 # and the stmt following it. Returns a pair,
1481 # (line containing block opener, line containing stmt)
1482 # Either or both may be None.
1483
1484 def __init__(self, text, tabwidth):
1485 self.text = text
1486 self.tabwidth = tabwidth
1487 self.i = self.finished = 0
1488 self.blkopenline = self.indentedline = None
1489
1490 def readline(self):
1491 if self.finished:
1492 return ""
1493 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001494 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001495 if self.text.compare(mark, ">=", "end"):
1496 return ""
1497 return self.text.get(mark, mark + " lineend+1c")
1498
1499 def tokeneater(self, type, token, start, end, line,
1500 INDENT=_tokenize.INDENT,
1501 NAME=_tokenize.NAME,
1502 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1503 if self.finished:
1504 pass
1505 elif type == NAME and token in OPENERS:
1506 self.blkopenline = line
1507 elif type == INDENT and self.blkopenline:
1508 self.indentedline = line
1509 self.finished = 1
1510
1511 def run(self):
1512 save_tabsize = _tokenize.tabsize
1513 _tokenize.tabsize = self.tabwidth
1514 try:
1515 try:
Trent Nelson428de652008-03-18 22:41:35 +00001516 tokens = _tokenize.generate_tokens(self.readline)
1517 for token in tokens:
1518 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001519 except _tokenize.TokenError:
1520 # since we cut off the tokenizer early, we can trigger
1521 # spurious errors
1522 pass
1523 finally:
1524 _tokenize.tabsize = save_tabsize
1525 return self.blkopenline, self.indentedline
1526
1527### end autoindent code ###
1528
David Scherer7aced172000-08-15 01:13:23 +00001529def prepstr(s):
1530 # Helper to extract the underscore from a string, e.g.
1531 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001532 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001533 if i >= 0:
1534 s = s[:i] + s[i+1:]
1535 return i, s
1536
1537
1538keynames = {
1539 'bracketleft': '[',
1540 'bracketright': ']',
1541 'slash': '/',
1542}
1543
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001544def get_accelerator(keydefs, eventname):
1545 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001546 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1547 # if not keylist:
1548 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1549 "<<open-module>>",
1550 "<<goto-line>>",
1551 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001552 return ""
1553 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001554 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001555 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1556 s = re.sub("Key-", "", s)
1557 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1558 s = re.sub("Control-", "Ctrl-", s)
1559 s = re.sub("-", "+", s)
1560 s = re.sub("><", " ", s)
1561 s = re.sub("<", "", s)
1562 s = re.sub(">", "", s)
1563 return s
1564
1565
1566def fixwordbreaks(root):
1567 # Make sure that Tk's double-click and next/previous word
1568 # operations use our definition of a word (i.e. an identifier)
1569 tk = root.tk
1570 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1571 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1572 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1573
1574
1575def test():
1576 root = Tk()
1577 fixwordbreaks(root)
1578 root.withdraw()
1579 if sys.argv[1:]:
1580 filename = sys.argv[1]
1581 else:
1582 filename = None
1583 edit = EditorWindow(root=root, filename=filename)
1584 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001585 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001586 root.mainloop()
1587 root.destroy()
1588
1589if __name__ == '__main__':
1590 test()