blob: 6bb5301e03f90e5063164a0bbbe720750c567a43 [file] [log] [blame]
Brett Cannonaef82d32012-04-14 20:44:23 -04001import imp
2import importlib
David Scherer7aced172000-08-15 01:13:23 +00003import os
David Scherer7aced172000-08-15 01:13:23 +00004import re
Guido van Rossum33d26892007-08-05 15:29:28 +00005import string
Brett Cannonaef82d32012-04-14 20:44:23 -04006import sys
Georg Brandl14fc4272008-05-17 18:39:55 +00007from tkinter import *
8import tkinter.simpledialog as tkSimpleDialog
9import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +000010import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000011import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000012
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000013from idlelib.MultiCall import MultiCallCreator
14from idlelib import idlever
15from idlelib import WindowList
16from idlelib import SearchDialog
17from idlelib import GrepDialog
18from idlelib import ReplaceDialog
19from idlelib import PyParse
20from idlelib.configHandler import idleConf
21from idlelib import aboutDialog, textView, configDialog
22from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000023
24# The default tab setting for a Text widget, in average-width characters.
25TK_TABWIDTH_DEFAULT = 8
26
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000027def _sphinx_version():
28 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
29 major, minor, micro, level, serial = sys.version_info
30 release = '%s%s' % (major, minor)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020031 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000032 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
Terry Jan Reedye91e7632012-02-05 15:14:20 -050066
67class HelpDialog(object):
68
69 def __init__(self):
70 self.parent = None # parent of help window
71 self.dlg = None # the help window iteself
72
73 def display(self, parent, near=None):
74 """ Display the help dialog.
75
76 parent - parent widget for the help window
77
78 near - a Toplevel widget (e.g. EditorWindow or PyShell)
79 to use as a reference for placing the help window
80 """
81 if self.dlg is None:
82 self.show_dialog(parent)
83 if near:
84 self.nearwindow(near)
85
86 def show_dialog(self, parent):
87 self.parent = parent
88 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
89 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
90 dlg.bind('<Destroy>', self.destroy, '+')
91
92 def nearwindow(self, near):
93 # Place the help dialog near the window specified by parent.
94 # Note - this may not reposition the window in Metacity
95 # if "/apps/metacity/general/disable_workarounds" is enabled
96 dlg = self.dlg
97 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
98 dlg.withdraw()
99 dlg.geometry("=+%d+%d" % geom)
100 dlg.deiconify()
101 dlg.lift()
102
103 def destroy(self, ev=None):
104 self.dlg = None
105 self.parent = None
106
107helpDialog = HelpDialog() # singleton instance
108
109
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +0000110class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000111 from idlelib.Percolator import Percolator
112 from idlelib.ColorDelegator import ColorDelegator
113 from idlelib.UndoDelegator import UndoDelegator
114 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
115 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +0000116 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000117 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +0000118
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000119 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000120
121 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000122 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100123 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000124 if sys.platform.count('linux'):
125 # look for html docs in a couple of standard places
126 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
127 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
128 dochome = '/var/www/html/python/index.html'
129 else:
130 basepath = '/usr/share/doc/' # standard location
131 dochome = os.path.join(basepath, pyver,
132 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000133 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100134 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +0000135 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000136 if os.path.isfile(chmfile):
137 dochome = chmfile
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000138 elif macosxSupport.runningAsOSXApp():
139 # documentation is stored inside the python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100140 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000141 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000142 dochome = os.path.normpath(dochome)
143 if os.path.isfile(dochome):
144 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000145 if sys.platform == 'darwin':
146 # Safari requires real file:-URLs
147 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000148 else:
Benjamin Peterson152b6572009-01-20 15:01:54 +0000149 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000150 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +0000151 self.flist = flist
152 root = root or flist.root
153 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000154 try:
155 sys.ps1
156 except AttributeError:
157 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000158 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000159 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000160 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000161 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200162 #self.top.instance_dict makes flist.inversedict available to
163 #configDialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000164 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000165 else:
166 self.tkinter_vars = {} # keys: Tkinter event names
167 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000168 self.top.instance_dict = {}
169 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000170 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000171 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000172 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200173 self.width = idleConf.GetOption('main', 'EditorWindow',
174 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000175 text_options = {
176 'name': 'text',
177 'padx': 5,
178 'wrap': 'none',
179 'width': self.width,
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200180 'height': idleConf.GetOption('main', 'EditorWindow',
181 'height', type='int')}
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000182 if TkVersion >= 8.5:
183 # Starting with tk 8.5 we have to set the new tabstyle option
184 # to 'wordprocessor' to achieve the same display of tabs as in
185 # older tk versions.
186 text_options['tabstyle'] = 'wordprocessor'
187 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000188 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000189
190 self.createmenubar()
191 self.apply_bindings()
192
193 self.top.protocol("WM_DELETE_WINDOW", self.close)
194 self.top.bind("<<close-window>>", self.close_event)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000195 if macosxSupport.runningAsOSXApp():
196 # Command-W on editorwindows doesn't work without this.
197 text.bind('<<close-window>>', self.close_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000198 # Some OS X systems have only one mouse button,
199 # so use control-click for pulldown menus there.
200 # (Note, AquaTk defines <2> as the right button if
201 # present and the Tk Text widget already binds <2>.)
202 text.bind("<Control-Button-1>",self.right_menu_event)
203 else:
204 # Elsewhere, use right-click for pulldown menus.
205 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000206 text.bind("<<cut>>", self.cut)
207 text.bind("<<copy>>", self.copy)
208 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000209 text.bind("<<center-insert>>", self.center_insert_event)
210 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000211 text.bind("<<python-docs>>", self.python_docs)
212 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000213 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000214 text.bind("<<open-module>>", self.open_module)
215 text.bind("<<do-nothing>>", lambda event: "break")
216 text.bind("<<select-all>>", self.select_all)
217 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000218 text.bind("<<find>>", self.find_event)
219 text.bind("<<find-again>>", self.find_again_event)
220 text.bind("<<find-in-files>>", self.find_in_files_event)
221 text.bind("<<find-selection>>", self.find_selection_event)
222 text.bind("<<replace>>", self.replace_event)
223 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000224 text.bind("<<smart-backspace>>",self.smart_backspace_event)
225 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
226 text.bind("<<smart-indent>>",self.smart_indent_event)
227 text.bind("<<indent-region>>",self.indent_region_event)
228 text.bind("<<dedent-region>>",self.dedent_region_event)
229 text.bind("<<comment-region>>",self.comment_region_event)
230 text.bind("<<uncomment-region>>",self.uncomment_region_event)
231 text.bind("<<tabify-region>>",self.tabify_region_event)
232 text.bind("<<untabify-region>>",self.untabify_region_event)
233 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
234 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000235 text.bind("<Left>", self.move_at_edge_if_selection(0))
236 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000237 text.bind("<<del-word-left>>", self.del_word_left)
238 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000239 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000240
David Scherer7aced172000-08-15 01:13:23 +0000241 if flist:
242 flist.inversedict[self] = key
243 if key:
244 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000245 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000246 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
247 text.bind("<<open-class-browser>>", self.open_class_browser)
248 text.bind("<<open-path-browser>>", self.open_path_browser)
249
Steven M. Gava898a3652001-10-07 11:10:44 +0000250 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000251 vbar['command'] = text.yview
252 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000253 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000254 fontWeight = 'normal'
255 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000256 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000257 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200258 idleConf.GetOption('main', 'EditorWindow',
259 'font-size', type='int'),
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000260 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000261 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
262 text.pack(side=TOP, fill=BOTH, expand=1)
263 text.focus_set()
264
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000265 # usetabs true -> literal tab characters are used by indent and
266 # dedent cmds, possibly mixed with spaces if
267 # indentwidth is not a multiple of tabwidth,
268 # which will cause Tabnanny to nag!
269 # false -> tab characters are converted to spaces by indent
270 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000271 # Although use-spaces=0 can be configured manually in config-main.def,
272 # configuration of tabs v. spaces is not supported in the configuration
273 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200274 usespaces = idleConf.GetOption('main', 'Indent',
275 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000276 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000277
278 # tabwidth is the display width of a literal tab character.
279 # CAUTION: telling Tk to use anything other than its default
280 # tab setting causes it to use an entirely different tabbing algorithm,
281 # treating tab stops as fixed distances from the left margin.
282 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000283 self.tabwidth = 8 # must remain 8 until Tk is fixed.
284
285 # indentwidth is the number of screen characters per indent level.
286 # The recommended Python indentation is four spaces.
287 self.indentwidth = self.tabwidth
288 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000289
290 # If context_use_ps1 is true, parsing searches back for a ps1 line;
291 # else searches for a popular (if, def, ...) Python stmt.
292 self.context_use_ps1 = False
293
294 # When searching backwards for a reliable place to begin parsing,
295 # first start num_context_lines[0] lines back, then
296 # num_context_lines[1] lines back if that didn't work, and so on.
297 # The last value should be huge (larger than the # of lines in a
298 # conceivable file).
299 # Making the initial values larger slows things down more often.
300 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000301 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000302 self.undo = undo = self.UndoDelegator()
303 per.insertfilter(undo)
304 text.undo_block_start = undo.undo_block_start
305 text.undo_block_stop = undo.undo_block_stop
306 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000307 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000308 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000309 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000310 self.good_load = False
311 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000312 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000313 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000314 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000315 if io.loadfile(filename):
316 self.good_load = True
317 is_py_src = self.ispythonsource(filename)
318 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000319 else:
320 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500321 self.good_load = True
322
Christian Heimesa156e092008-02-16 07:38:31 +0000323 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000324 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000325 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000326 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000327 menu = self.menudict.get('windows')
328 if menu:
329 end = menu.index("end")
330 if end is None:
331 end = -1
332 if end >= 0:
333 menu.add_separator()
334 end = end + 1
335 self.wmenu_end = end
336 WindowList.register_callback(self.postwindowsmenu)
337
338 # Some abstractions so IDLE extensions are cross-IDE
339 self.askyesno = tkMessageBox.askyesno
340 self.askinteger = tkSimpleDialog.askinteger
341 self.showerror = tkMessageBox.showerror
342
Roger Serwycaf30242013-05-20 22:13:39 -0500343 self._highlight_workaround() # Fix selection tags on Windows
344
345 def _highlight_workaround(self):
346 # On Windows, Tk removes painting of the selection
347 # tags which is different behavior than on Linux and Mac.
348 # See issue14146 for more information.
349 if not sys.platform.startswith('win'):
350 return
351
352 text = self.text
353 text.event_add("<<Highlight-FocusOut>>", "<FocusOut>")
354 text.event_add("<<Highlight-FocusIn>>", "<FocusIn>")
355 def highlight_fix(focus):
356 sel_range = text.tag_ranges("sel")
357 if sel_range:
358 if focus == 'out':
359 HILITE_CONFIG = idleConf.GetHighlight(
360 idleConf.CurrentTheme(), 'hilite')
361 text.tag_config("sel_fix", HILITE_CONFIG)
362 text.tag_raise("sel_fix")
363 text.tag_add("sel_fix", *sel_range)
364 elif focus == 'in':
365 text.tag_remove("sel_fix", "1.0", "end")
366
367 text.bind("<<Highlight-FocusOut>>",
368 lambda ev: highlight_fix("out"))
369 text.bind("<<Highlight-FocusIn>>",
370 lambda ev: highlight_fix("in"))
371
372
Martin v. Löwis307021f2005-11-27 16:59:04 +0000373 def _filename_to_unicode(self, filename):
374 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000375 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000376 return filename
377 else:
378 try:
379 return filename.decode(self.filesystemencoding)
380 except UnicodeDecodeError:
381 # XXX
382 try:
383 return filename.decode(self.encoding)
384 except UnicodeDecodeError:
385 # byte-to-byte conversion
386 return filename.decode('iso8859-1')
387
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000388 def new_callback(self, event):
389 dirname, basename = self.io.defaultfilename()
390 self.flist.new(dirname)
391 return "break"
392
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000393 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400394 if (event.state & 4) != 0 and event.keysym == "Home":
395 # state&4==Control. If <Control-Home>, use the Tk binding.
396 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000397 if self.text.index("iomark") and \
398 self.text.compare("iomark", "<=", "insert lineend") and \
399 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400400 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000401 insertpt = int(self.text.index("iomark").split(".")[1])
402 else:
403 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000404 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000405 if line[insertpt] not in (' ','\t'):
406 break
407 else:
408 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000409 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000410 if insertpt == lineat:
411 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000412 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000413 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400414 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000415 self.text.tag_remove("sel", "1.0", "end")
416 else:
417 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200418 # there was no previous selection
419 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400420 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200421 if self.text.compare(self.text.index("sel.first"), "<",
422 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400423 self.text.mark_set("my_anchor", "sel.first") # extend back
424 else:
425 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000426 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400427 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000428 if self.text.compare(first,">",last):
429 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000430 self.text.tag_remove("sel", "1.0", "end")
431 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000432 self.text.mark_set("insert", dest)
433 self.text.see("insert")
434 return "break"
435
David Scherer7aced172000-08-15 01:13:23 +0000436 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000437 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000438 if macosxSupport.runningAsOSXApp():
439 # Insert some padding to avoid obscuring some of the statusbar
440 # by the resize widget.
441 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000442 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
443 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
444 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000445 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
446 self.text.event_add("<<set-line-and-column>>",
447 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000448 self.text.after_idle(self.set_line_and_column)
449
450 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000451 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000452 self.status_bar.set_label('column', 'Col: %s' % column)
453 self.status_bar.set_label('line', 'Ln: %s' % line)
454
David Scherer7aced172000-08-15 01:13:23 +0000455 menu_specs = [
456 ("file", "_File"),
457 ("edit", "_Edit"),
458 ("format", "F_ormat"),
459 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000460 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000461 ("windows", "_Windows"),
462 ("help", "_Help"),
463 ]
464
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000465 if macosxSupport.runningAsOSXApp():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000466 menu_specs[-2] = ("windows", "_Window")
467
468
David Scherer7aced172000-08-15 01:13:23 +0000469 def createmenubar(self):
470 mbar = self.menubar
471 self.menudict = menudict = {}
472 for name, label in self.menu_specs:
473 underline, label = prepstr(label)
474 menudict[name] = menu = Menu(mbar, name=name)
475 mbar.add_cascade(label=label, menu=menu, underline=underline)
Georg Brandlaedd2892010-12-19 10:10:32 +0000476 if macosxSupport.isCarbonAquaTk(self.root):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000477 # Insert the application menu
478 menudict['application'] = menu = Menu(mbar, name='apple')
479 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000480 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000481 self.recent_files_menu = Menu(self.menubar)
482 self.menudict['file'].insert_cascade(3, label='Recent Files',
483 underline=0,
484 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000485 self.base_helpmenu_length = self.menudict['help'].index(END)
486 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000487
488 def postwindowsmenu(self):
489 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000490 menu = self.menudict['windows']
491 end = menu.index("end")
492 if end is None:
493 end = -1
494 if end > self.wmenu_end:
495 menu.delete(self.wmenu_end+1, end)
496 WindowList.add_windows_to_menu(menu)
497
498 rmenu = None
499
500 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000501 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
502 if not self.rmenu:
503 self.make_rmenu()
504 rmenu = self.rmenu
505 self.event = event
506 iswin = sys.platform[:3] == 'win'
507 if iswin:
508 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200509
Roger Serwy6b2918a2013-04-07 12:15:52 -0500510 for item in self.rmenu_specs:
511 try:
512 label, eventname, verify_state = item
513 except ValueError: # see issue1207589
514 continue
515
Andrew Svetlovd1837672012-11-01 22:41:19 +0200516 if verify_state is None:
517 continue
518 state = getattr(self, verify_state)()
519 rmenu.entryconfigure(label, state=state)
520
521
David Scherer7aced172000-08-15 01:13:23 +0000522 rmenu.tk_popup(event.x_root, event.y_root)
523 if iswin:
524 self.text.config(cursor="ibeam")
525
526 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200527 # ("Label", "<<virtual-event>>", "statefuncname"), ...
528 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000529 ]
530
531 def make_rmenu(self):
532 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500533 for item in self.rmenu_specs:
534 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200535 if label is not None:
536 def command(text=self.text, eventname=eventname):
537 text.event_generate(eventname)
538 rmenu.add_command(label=label, command=command)
539 else:
540 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000541 self.rmenu = rmenu
542
Andrew Svetlovd1837672012-11-01 22:41:19 +0200543 def rmenu_check_cut(self):
544 return self.rmenu_check_copy()
545
546 def rmenu_check_copy(self):
547 try:
548 indx = self.text.index('sel.first')
549 except TclError:
550 return 'disabled'
551 else:
552 return 'normal' if indx else 'disabled'
553
554 def rmenu_check_paste(self):
555 try:
556 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
557 except TclError:
558 return 'disabled'
559 else:
560 return 'normal'
561
David Scherer7aced172000-08-15 01:13:23 +0000562 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000563 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000564
Steven M. Gava3b55a892001-11-21 05:56:26 +0000565 def config_dialog(self, event=None):
566 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000567
David Scherer7aced172000-08-15 01:13:23 +0000568 def help_dialog(self, event=None):
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500569 if self.root:
570 parent = self.root
571 else:
572 parent = self.top
573 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000574
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000575 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000576 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000577 try:
578 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200579 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000580 tkMessageBox.showerror(title='Document Start Failure',
581 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000582 else:
583 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000584 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000585
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000586 def cut(self,event):
587 self.text.event_generate("<<Cut>>")
588 return "break"
589
590 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000591 if not self.text.tag_ranges("sel"):
592 # There is no selection, so do nothing and maybe interrupt.
593 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000594 self.text.event_generate("<<Copy>>")
595 return "break"
596
597 def paste(self,event):
598 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000599 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000600 return "break"
601
David Scherer7aced172000-08-15 01:13:23 +0000602 def select_all(self, event=None):
603 self.text.tag_add("sel", "1.0", "end-1c")
604 self.text.mark_set("insert", "1.0")
605 self.text.see("insert")
606 return "break"
607
608 def remove_selection(self, event=None):
609 self.text.tag_remove("sel", "1.0", "end")
610 self.text.see("insert")
611
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000612 def move_at_edge_if_selection(self, edge_index):
613 """Cursor move begins at start or end of selection
614
615 When a left/right cursor key is pressed create and return to Tkinter a
616 function which causes a cursor move from the associated edge of the
617 selection.
618
619 """
620 self_text_index = self.text.index
621 self_text_mark_set = self.text.mark_set
622 edges_table = ("sel.first+1c", "sel.last-1c")
623 def move_at_edge(event):
624 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
625 try:
626 self_text_index("sel.first")
627 self_text_mark_set("insert", edges_table[edge_index])
628 except TclError:
629 pass
630 return move_at_edge
631
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000632 def del_word_left(self, event):
633 self.text.event_generate('<Meta-Delete>')
634 return "break"
635
636 def del_word_right(self, event):
637 self.text.event_generate('<Meta-d>')
638 return "break"
639
Steven M. Gavac5976402002-01-04 03:06:08 +0000640 def find_event(self, event):
641 SearchDialog.find(self.text)
642 return "break"
643
644 def find_again_event(self, event):
645 SearchDialog.find_again(self.text)
646 return "break"
647
648 def find_selection_event(self, event):
649 SearchDialog.find_selection(self.text)
650 return "break"
651
652 def find_in_files_event(self, event):
653 GrepDialog.grep(self.text, self.io, self.flist)
654 return "break"
655
656 def replace_event(self, event):
657 ReplaceDialog.replace(self.text)
658 return "break"
659
660 def goto_line_event(self, event):
661 text = self.text
662 lineno = tkSimpleDialog.askinteger("Goto",
663 "Go to line number:",parent=text)
664 if lineno is None:
665 return "break"
666 if lineno <= 0:
667 text.bell()
668 return "break"
669 text.mark_set("insert", "%d.0" % lineno)
670 text.see("insert")
671
David Scherer7aced172000-08-15 01:13:23 +0000672 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000673 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000674 try:
675 name = self.text.get("sel.first", "sel.last")
676 except TclError:
677 name = ""
678 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000679 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000680 name = tkSimpleDialog.askstring("Module",
681 "Enter the name of a Python module\n"
682 "to search on sys.path and open:",
683 parent=self.text, initialvalue=name)
684 if name:
685 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000686 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000687 return
David Scherer7aced172000-08-15 01:13:23 +0000688 # XXX Ought to insert current file's directory in front of path
689 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000690 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000691 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000692 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
693 return
694 if type != imp.PY_SOURCE:
695 tkMessageBox.showerror("Unsupported type",
696 "%s is not a source module" % name, parent=self.text)
697 return
698 if f:
699 f.close()
700 if self.flist:
701 self.flist.open(file)
702 else:
703 self.io.loadfile(file)
704
705 def open_class_browser(self, event=None):
706 filename = self.io.filename
707 if not filename:
708 tkMessageBox.showerror(
709 "No filename",
710 "This buffer has no associated filename",
711 master=self.text)
712 self.text.focus_set()
713 return None
714 head, tail = os.path.split(filename)
715 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000716 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000717 ClassBrowser.ClassBrowser(self.flist, base, [head])
718
719 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000720 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000721 PathBrowser.PathBrowser(self.flist)
722
723 def gotoline(self, lineno):
724 if lineno is not None and lineno > 0:
725 self.text.mark_set("insert", "%d.0" % lineno)
726 self.text.tag_remove("sel", "1.0", "end")
727 self.text.tag_add("sel", "insert", "insert +1l")
728 self.center()
729
730 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000731 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000732 return True
David Scherer7aced172000-08-15 01:13:23 +0000733 base, ext = os.path.splitext(os.path.basename(filename))
734 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000735 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000736 line = self.text.get('1.0', '1.0 lineend')
737 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000738
739 def close_hook(self):
740 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000741 self.flist.unregister_maybe_terminate(self)
742 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000743
744 def set_close_hook(self, close_hook):
745 self.close_hook = close_hook
746
747 def filename_change_hook(self):
748 if self.flist:
749 self.flist.filename_changed_edit(self)
750 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000751 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000752 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000753
Christian Heimesa156e092008-02-16 07:38:31 +0000754 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000755 if self.color:
756 return
Christian Heimesa156e092008-02-16 07:38:31 +0000757 if self.ispythonsource(self.io.filename):
758 self.color = self.ColorDelegator()
759 # can add more colorizers here...
760 if self.color:
761 self.per.removefilter(self.undo)
762 self.per.insertfilter(self.color)
763 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000764
Christian Heimesa156e092008-02-16 07:38:31 +0000765 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000766 if not self.color:
767 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000768 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000769 self.per.removefilter(self.color)
770 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000771
Steven M. Gavab77d3432002-03-02 07:16:21 +0000772 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000773 "Update the colour theme"
774 # Called from self.filename_change_hook and from configDialog.py
775 self._rmcolorizer()
776 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000777 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000778 normal_colors = idleConf.GetHighlight(theme, 'normal')
779 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
780 select_colors = idleConf.GetHighlight(theme, 'hilite')
781 self.text.config(
782 foreground=normal_colors['foreground'],
783 background=normal_colors['background'],
784 insertbackground=cursor_color,
785 selectforeground=select_colors['foreground'],
786 selectbackground=select_colors['background'],
787 )
David Scherer7aced172000-08-15 01:13:23 +0000788
Guido van Rossum33d26892007-08-05 15:29:28 +0000789 IDENTCHARS = string.ascii_letters + string.digits + "_"
790
791 def colorize_syntax_error(self, text, pos):
792 text.tag_add("ERROR", pos)
793 char = text.get(pos)
794 if char and char in self.IDENTCHARS:
795 text.tag_add("ERROR", pos + " wordstart", pos)
796 if '\n' == text.get(pos): # error at line end
797 text.mark_set("insert", pos)
798 else:
799 text.mark_set("insert", pos + "+1c")
800 text.see(pos)
801
Steven M. Gavab1585412002-03-12 00:21:56 +0000802 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000803 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000804 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000805 fontWeight='normal'
806 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
807 fontWeight='bold'
808 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200809 idleConf.GetOption('main','EditorWindow','font-size',
810 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000811 fontWeight))
812
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000813 def RemoveKeybindings(self):
814 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000815 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000816 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000817 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000818 self.text.event_delete(event, *keylist)
819 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000820 xkeydefs = idleConf.GetExtensionBindings(extensionName)
821 if xkeydefs:
822 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000823 self.text.event_delete(event, *keylist)
824
825 def ApplyKeybindings(self):
826 "Update the keybindings after they are changed"
827 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000828 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000829 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000830 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000831 xkeydefs = idleConf.GetExtensionBindings(extensionName)
832 if xkeydefs:
833 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000834 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000835 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000836 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000837 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000838 for item in menu[1]:
839 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000840 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000841 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000842 menu = self.menudict[menubarItem]
843 end = menu.index(END) + 1
844 for index in range(0, end):
845 if menu.type(index) == 'command':
846 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000847 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000848 itemName = menu.entrycget(index, 'label')
849 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000850 if menubarItem in menuEventDict:
851 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000852 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000853 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000854 accel = get_accelerator(keydefs, event)
855 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000856
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000857 def set_notabs_indentwidth(self):
858 "Update the indentwidth if changed and not using tabs in this window"
859 # Called from configDialog.py
860 if not self.usetabs:
861 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
862 type='int')
863
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000864 def reset_help_menu_entries(self):
865 "Update the additional help entries on the Help menu"
866 help_list = idleConf.GetAllExtraHelpSourcesList()
867 helpmenu = self.menudict['help']
868 # first delete the extra help entries, if any
869 helpmenu_length = helpmenu.index(END)
870 if helpmenu_length > self.base_helpmenu_length:
871 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
872 # then rebuild them
873 if help_list:
874 helpmenu.add_separator()
875 for entry in help_list:
876 cmd = self.__extra_help_callback(entry[1])
877 helpmenu.add_command(label=entry[0], command=cmd)
878 # and update the menu dictionary
879 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000880
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000881 def __extra_help_callback(self, helpfile):
882 "Create a callback with the helpfile value frozen at definition time"
883 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000884 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000885 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000886 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000887 try:
888 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200889 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000890 tkMessageBox.showerror(title='Document Start Failure',
891 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000892 else:
893 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000894 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000895
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000896 def update_recent_files_list(self, new_file=None):
897 "Load and update the recent files list and menus"
898 rf_list = []
899 if os.path.exists(self.recent_files_path):
Ned Deily122539e2011-01-24 21:46:44 +0000900 rf_list_file = open(self.recent_files_path,'r',
901 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000902 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000903 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000904 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000905 rf_list_file.close()
906 if new_file:
907 new_file = os.path.abspath(new_file) + '\n'
908 if new_file in rf_list:
909 rf_list.remove(new_file) # move to top
910 rf_list.insert(0, new_file)
911 # clean and save the recent files list
912 bad_paths = []
913 for path in rf_list:
914 if '\0' in path or not os.path.exists(path[0:-1]):
915 bad_paths.append(path)
916 rf_list = [path for path in rf_list if path not in bad_paths]
917 ulchars = "1234567890ABCDEFGHIJK"
918 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000919 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800920 with open(self.recent_files_path, 'w',
921 encoding='utf_8', errors='replace') as rf_file:
922 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200923 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800924 if not getattr(self.root, "recentfilelist_error_displayed", False):
925 self.root.recentfilelist_error_displayed = True
926 tkMessageBox.showerror(title='IDLE Error',
927 message='Unable to update Recent Files list:\n%s'
928 % str(err),
929 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000930 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000931 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000932 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700933 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000934 for i, file_name in enumerate(rf_list):
935 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000936 # make unicode string to display non-ASCII chars correctly
937 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000938 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000939 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000940 command=callback,
941 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000942
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000943 def __recent_file_callback(self, file_name):
944 def open_recent_file(fn_closure=file_name):
945 self.io.open(editFile=fn_closure)
946 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000947
David Scherer7aced172000-08-15 01:13:23 +0000948 def saved_change_hook(self):
949 short = self.short_title()
950 long = self.long_title()
951 if short and long:
952 title = short + " - " + long
953 elif short:
954 title = short
955 elif long:
956 title = long
957 else:
958 title = "Untitled"
959 icon = short or long or title
960 if not self.get_saved():
961 title = "*%s*" % title
962 icon = "*%s" % icon
963 self.top.wm_title(title)
964 self.top.wm_iconname(icon)
965
966 def get_saved(self):
967 return self.undo.get_saved()
968
969 def set_saved(self, flag):
970 self.undo.set_saved(flag)
971
972 def reset_undo(self):
973 self.undo.reset_undo()
974
975 def short_title(self):
976 filename = self.io.filename
977 if filename:
978 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000979 # return unicode string to display non-ASCII chars correctly
980 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000981
982 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000983 # return unicode string to display non-ASCII chars correctly
984 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000985
986 def center_insert_event(self, event):
987 self.center()
988
989 def center(self, mark="insert"):
990 text = self.text
991 top, bot = self.getwindowlines()
992 lineno = self.getlineno(mark)
993 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000994 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000995 text.yview(float(newtop))
996
997 def getwindowlines(self):
998 text = self.text
999 top = self.getlineno("@0,0")
1000 bot = self.getlineno("@0,65535")
1001 if top == bot and text.winfo_height() == 1:
1002 # Geometry manager hasn't run yet
1003 height = int(text['height'])
1004 bot = top + height - 1
1005 return top, bot
1006
1007 def getlineno(self, mark="insert"):
1008 text = self.text
1009 return int(float(text.index(mark)))
1010
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001011 def get_geometry(self):
1012 "Return (width, height, x, y)"
1013 geom = self.top.wm_geometry()
1014 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +00001015 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001016
David Scherer7aced172000-08-15 01:13:23 +00001017 def close_event(self, event):
1018 self.close()
1019
1020 def maybesave(self):
1021 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001022 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001023 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001024 self.top.deiconify()
1025 self.top.lower()
1026 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001027 return self.io.maybesave()
1028
1029 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001030 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +00001031 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001032 self._close()
1033 return reply
1034
1035 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001036 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001037 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001038 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001039 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001040 self.io.close()
1041 self.io = None
1042 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001043 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001044 self.color.close(False)
1045 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001046 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001047 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001048 self.per.close()
1049 self.per = None
1050 self.top.destroy()
1051 if self.close_hook:
1052 # unless override: unregister from flist, terminate if last window
1053 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001054
1055 def load_extensions(self):
1056 self.extensions = {}
1057 self.load_standard_extensions()
1058
1059 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001060 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001061 if hasattr(ins, "close"):
1062 ins.close()
1063 self.extensions = {}
1064
1065 def load_standard_extensions(self):
1066 for name in self.get_standard_extension_names():
1067 try:
1068 self.load_extension(name)
1069 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001070 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001071 traceback.print_exc()
1072
1073 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001074 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001075
1076 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001077 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001078 try:
1079 mod = importlib.import_module('.' + name, package=__package__)
1080 except ImportError:
1081 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001082 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001083 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001084 raise
David Scherer7aced172000-08-15 01:13:23 +00001085 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001086 keydefs = idleConf.GetExtensionBindings(name)
1087 if hasattr(cls, "menudefs"):
1088 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001089 ins = cls(self)
1090 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001091 if keydefs:
1092 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001093 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001094 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001095 while methodname[:1] == '<':
1096 methodname = methodname[1:]
1097 while methodname[-1:] == '>':
1098 methodname = methodname[:-1]
1099 methodname = methodname + "_event"
1100 if hasattr(ins, methodname):
1101 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001102
1103 def apply_bindings(self, keydefs=None):
1104 if keydefs is None:
1105 keydefs = self.Bindings.default_keydefs
1106 text = self.text
1107 text.keydefs = keydefs
1108 for event, keylist in keydefs.items():
1109 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001110 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001111
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001112 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001113 """Add appropriate entries to the menus and submenus
1114
1115 Menus that are absent or None in self.menudict are ignored.
1116 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001117 if menudefs is None:
1118 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001119 if keydefs is None:
1120 keydefs = self.Bindings.default_keydefs
1121 menudict = self.menudict
1122 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001123 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001124 menu = menudict.get(mname)
1125 if not menu:
1126 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001127 for entry in entrylist:
1128 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001129 menu.add_separator()
1130 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001131 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001132 checkbutton = (label[:1] == '!')
1133 if checkbutton:
1134 label = label[1:]
1135 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001136 accelerator = get_accelerator(keydefs, eventname)
1137 def command(text=text, eventname=eventname):
1138 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001139 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001140 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001141 menu.add_checkbutton(label=label, underline=underline,
1142 command=command, accelerator=accelerator,
1143 variable=var)
1144 else:
1145 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001146 command=command,
1147 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001148
1149 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001150 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001151 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001152 value = var.get()
1153 return value
1154 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001155 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001156
1157 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001158 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001159 if var:
1160 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001161 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001162 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001163
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001164 def get_var_obj(self, name, vartype=None):
1165 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001166 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001167 # create a Tkinter variable object with self.text as master:
1168 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001169 return var
1170
1171 # Tk implementations of "virtual text methods" -- each platform
1172 # reusing IDLE's support code needs to define these for its GUI's
1173 # flavor of widget.
1174
1175 # Is character at text_index in a Python string? Return 0 for
1176 # "guaranteed no", true for anything else. This info is expensive
1177 # to compute ab initio, but is probably already known by the
1178 # platform's colorizer.
1179
1180 def is_char_in_string(self, text_index):
1181 if self.color:
1182 # Return true iff colorizer hasn't (re)gotten this far
1183 # yet, or the character is tagged as being in a string
1184 return self.text.tag_prevrange("TODO", text_index) or \
1185 "STRING" in self.text.tag_names(text_index)
1186 else:
1187 # The colorizer is missing: assume the worst
1188 return 1
1189
1190 # If a selection is defined in the text widget, return (start,
1191 # end) as Tkinter text indices, otherwise return (None, None)
1192 def get_selection_indices(self):
1193 try:
1194 first = self.text.index("sel.first")
1195 last = self.text.index("sel.last")
1196 return first, last
1197 except TclError:
1198 return None, None
1199
1200 # Return the text widget's current view of what a tab stop means
1201 # (equivalent width in spaces).
1202
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001203 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001204 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1205 return int(current)
1206
1207 # Set the text widget's current view of what a tab stop means.
1208
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001209 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001210 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001211 if self.get_tk_tabwidth() != newtabwidth:
1212 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001213 pixels = text.tk.call("font", "measure", text["font"],
1214 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001215 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001216 text.configure(tabs=pixels)
1217
Guido van Rossum33d26892007-08-05 15:29:28 +00001218### begin autoindent code ### (configuration was moved to beginning of class)
1219
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001220 def set_indentation_params(self, is_py_src, guess=True):
1221 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001222 i = self.guess_indent()
1223 if 2 <= i <= 8:
1224 self.indentwidth = i
1225 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001226 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001227 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001228
1229 def smart_backspace_event(self, event):
1230 text = self.text
1231 first, last = self.get_selection_indices()
1232 if first and last:
1233 text.delete(first, last)
1234 text.mark_set("insert", first)
1235 return "break"
1236 # Delete whitespace left, until hitting a real char or closest
1237 # preceding virtual tab stop.
1238 chars = text.get("insert linestart", "insert")
1239 if chars == '':
1240 if text.compare("insert", ">", "1.0"):
1241 # easy: delete preceding newline
1242 text.delete("insert-1c")
1243 else:
1244 text.bell() # at start of buffer
1245 return "break"
1246 if chars[-1] not in " \t":
1247 # easy: delete preceding real char
1248 text.delete("insert-1c")
1249 return "break"
1250 # Ick. It may require *inserting* spaces if we back up over a
1251 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001252 tabwidth = self.tabwidth
1253 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001254 assert have > 0
1255 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001256 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001257 if self.context_use_ps1:
1258 last_line_of_prompt = sys.ps1.split('\n')[-1]
1259 else:
1260 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001261 ncharsdeleted = 0
1262 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001263 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001264 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001265 chars = chars[:-1]
1266 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001267 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001268 if have <= want or chars[-1] not in " \t":
1269 break
1270 text.undo_block_start()
1271 text.delete("insert-%dc" % ncharsdeleted, "insert")
1272 if have < want:
1273 text.insert("insert", ' ' * (want - have))
1274 text.undo_block_stop()
1275 return "break"
1276
1277 def smart_indent_event(self, event):
1278 # if intraline selection:
1279 # delete it
1280 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001281 # do indent-region
1282 # else:
1283 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001284 text = self.text
1285 first, last = self.get_selection_indices()
1286 text.undo_block_start()
1287 try:
1288 if first and last:
1289 if index2line(first) != index2line(last):
1290 return self.indent_region_event(event)
1291 text.delete(first, last)
1292 text.mark_set("insert", first)
1293 prefix = text.get("insert linestart", "insert")
1294 raw, effective = classifyws(prefix, self.tabwidth)
1295 if raw == len(prefix):
1296 # only whitespace to the left
1297 self.reindent_to(effective + self.indentwidth)
1298 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001299 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001300 if self.usetabs:
1301 pad = '\t'
1302 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001303 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001304 n = self.indentwidth
1305 pad = ' ' * (n - effective % n)
1306 text.insert("insert", pad)
1307 text.see("insert")
1308 return "break"
1309 finally:
1310 text.undo_block_stop()
1311
1312 def newline_and_indent_event(self, event):
1313 text = self.text
1314 first, last = self.get_selection_indices()
1315 text.undo_block_start()
1316 try:
1317 if first and last:
1318 text.delete(first, last)
1319 text.mark_set("insert", first)
1320 line = text.get("insert linestart", "insert")
1321 i, n = 0, len(line)
1322 while i < n and line[i] in " \t":
1323 i = i+1
1324 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001325 # the cursor is in or at leading indentation in a continuation
1326 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001327 text.insert("insert linestart", '\n')
1328 return "break"
1329 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001330 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001331 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001332 last_line_of_prompt = sys.ps1.split('\n')[-1]
1333 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001334 line = line[:-1]
1335 i = i+1
1336 if i:
1337 text.delete("insert - %d chars" % i, "insert")
1338 # strip whitespace after insert point
1339 while text.get("insert") in " \t":
1340 text.delete("insert")
1341 # start new line
1342 text.insert("insert", '\n')
1343
1344 # adjust indentation for continuations and block
1345 # open/close first need to find the last stmt
1346 lno = index2line(text.index('insert'))
1347 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001348 if not self.context_use_ps1:
1349 for context in self.num_context_lines:
1350 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001351 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001352 rawtext = text.get(startatindex, "insert")
1353 y.set_str(rawtext)
1354 bod = y.find_good_parse_start(
1355 self.context_use_ps1,
1356 self._build_char_in_string_func(startatindex))
1357 if bod is not None or startat == 1:
1358 break
1359 y.set_lo(bod or 0)
1360 else:
1361 r = text.tag_prevrange("console", "insert")
1362 if r:
1363 startatindex = r[1]
1364 else:
1365 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001366 rawtext = text.get(startatindex, "insert")
1367 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001368 y.set_lo(0)
1369
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001370 c = y.get_continuation_type()
1371 if c != PyParse.C_NONE:
1372 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001373 if c == PyParse.C_STRING_FIRST_LINE:
1374 # after the first line of a string; do not indent at all
1375 pass
1376 elif c == PyParse.C_STRING_NEXT_LINES:
1377 # inside a string which started before this line;
1378 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001379 text.insert("insert", indent)
1380 elif c == PyParse.C_BRACKET:
1381 # line up with the first (if any) element of the
1382 # last open bracket structure; else indent one
1383 # level beyond the indent of the line with the
1384 # last open bracket
1385 self.reindent_to(y.compute_bracket_indent())
1386 elif c == PyParse.C_BACKSLASH:
1387 # if more than one line in this stmt already, just
1388 # mimic the current indent; else if initial line
1389 # has a start on an assignment stmt, indent to
1390 # beyond leftmost =; else to beyond first chunk of
1391 # non-whitespace on initial line
1392 if y.get_num_lines_in_stmt() > 1:
1393 text.insert("insert", indent)
1394 else:
1395 self.reindent_to(y.compute_backslash_indent())
1396 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001397 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001398 return "break"
1399
1400 # This line starts a brand new stmt; indent relative to
1401 # indentation of initial line of closest preceding
1402 # interesting stmt.
1403 indent = y.get_base_indent_string()
1404 text.insert("insert", indent)
1405 if y.is_block_opener():
1406 self.smart_indent_event(event)
1407 elif indent and y.is_block_closer():
1408 self.smart_backspace_event(event)
1409 return "break"
1410 finally:
1411 text.see("insert")
1412 text.undo_block_stop()
1413
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001414 # Our editwin provides a is_char_in_string function that works
1415 # with a Tk text index, but PyParse only knows about offsets into
1416 # a string. This builds a function for PyParse that accepts an
1417 # offset.
1418
1419 def _build_char_in_string_func(self, startindex):
1420 def inner(offset, _startindex=startindex,
1421 _icis=self.is_char_in_string):
1422 return _icis(_startindex + "+%dc" % offset)
1423 return inner
1424
1425 def indent_region_event(self, event):
1426 head, tail, chars, lines = self.get_region()
1427 for pos in range(len(lines)):
1428 line = lines[pos]
1429 if line:
1430 raw, effective = classifyws(line, self.tabwidth)
1431 effective = effective + self.indentwidth
1432 lines[pos] = self._make_blanks(effective) + line[raw:]
1433 self.set_region(head, tail, chars, lines)
1434 return "break"
1435
1436 def dedent_region_event(self, event):
1437 head, tail, chars, lines = self.get_region()
1438 for pos in range(len(lines)):
1439 line = lines[pos]
1440 if line:
1441 raw, effective = classifyws(line, self.tabwidth)
1442 effective = max(effective - self.indentwidth, 0)
1443 lines[pos] = self._make_blanks(effective) + line[raw:]
1444 self.set_region(head, tail, chars, lines)
1445 return "break"
1446
1447 def comment_region_event(self, event):
1448 head, tail, chars, lines = self.get_region()
1449 for pos in range(len(lines) - 1):
1450 line = lines[pos]
1451 lines[pos] = '##' + line
1452 self.set_region(head, tail, chars, lines)
1453
1454 def uncomment_region_event(self, event):
1455 head, tail, chars, lines = self.get_region()
1456 for pos in range(len(lines)):
1457 line = lines[pos]
1458 if not line:
1459 continue
1460 if line[:2] == '##':
1461 line = line[2:]
1462 elif line[:1] == '#':
1463 line = line[1:]
1464 lines[pos] = line
1465 self.set_region(head, tail, chars, lines)
1466
1467 def tabify_region_event(self, event):
1468 head, tail, chars, lines = self.get_region()
1469 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001470 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001471 for pos in range(len(lines)):
1472 line = lines[pos]
1473 if line:
1474 raw, effective = classifyws(line, tabwidth)
1475 ntabs, nspaces = divmod(effective, tabwidth)
1476 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1477 self.set_region(head, tail, chars, lines)
1478
1479 def untabify_region_event(self, event):
1480 head, tail, chars, lines = self.get_region()
1481 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001482 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001483 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001484 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001485 self.set_region(head, tail, chars, lines)
1486
1487 def toggle_tabs_event(self, event):
1488 if self.askyesno(
1489 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001490 "Turn tabs " + ("on", "off")[self.usetabs] +
1491 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001492 ("will be", "remains at")[self.usetabs] + " 8." +
1493 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001494 parent=self.text):
1495 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001496 # Try to prevent inconsistent indentation.
1497 # User must change indent width manually after using tabs.
1498 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001499 return "break"
1500
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001501 # XXX this isn't bound to anything -- see tabwidth comments
1502## def change_tabwidth_event(self, event):
1503## new = self._asktabwidth()
1504## if new != self.tabwidth:
1505## self.tabwidth = new
1506## self.set_indentation_params(0, guess=0)
1507## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001508
1509 def change_indentwidth_event(self, event):
1510 new = self.askinteger(
1511 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001512 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001513 parent=self.text,
1514 initialvalue=self.indentwidth,
1515 minvalue=2,
1516 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001517 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001518 self.indentwidth = new
1519 return "break"
1520
1521 def get_region(self):
1522 text = self.text
1523 first, last = self.get_selection_indices()
1524 if first and last:
1525 head = text.index(first + " linestart")
1526 tail = text.index(last + "-1c lineend +1c")
1527 else:
1528 head = text.index("insert linestart")
1529 tail = text.index("insert lineend +1c")
1530 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001531 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001532 return head, tail, chars, lines
1533
1534 def set_region(self, head, tail, chars, lines):
1535 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001536 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001537 if newchars == chars:
1538 text.bell()
1539 return
1540 text.tag_remove("sel", "1.0", "end")
1541 text.mark_set("insert", head)
1542 text.undo_block_start()
1543 text.delete(head, tail)
1544 text.insert(head, newchars)
1545 text.undo_block_stop()
1546 text.tag_add("sel", head, "insert")
1547
1548 # Make string that displays as n leading blanks.
1549
1550 def _make_blanks(self, n):
1551 if self.usetabs:
1552 ntabs, nspaces = divmod(n, self.tabwidth)
1553 return '\t' * ntabs + ' ' * nspaces
1554 else:
1555 return ' ' * n
1556
1557 # Delete from beginning of line to insert point, then reinsert
1558 # column logical (meaning use tabs if appropriate) spaces.
1559
1560 def reindent_to(self, column):
1561 text = self.text
1562 text.undo_block_start()
1563 if text.compare("insert linestart", "!=", "insert"):
1564 text.delete("insert linestart", "insert")
1565 if column:
1566 text.insert("insert", self._make_blanks(column))
1567 text.undo_block_stop()
1568
1569 def _asktabwidth(self):
1570 return self.askinteger(
1571 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001572 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001573 parent=self.text,
1574 initialvalue=self.indentwidth,
1575 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001576 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001577
1578 # Guess indentwidth from text content.
1579 # Return guessed indentwidth. This should not be believed unless
1580 # it's in a reasonable range (e.g., it will be 0 if no indented
1581 # blocks are found).
1582
1583 def guess_indent(self):
1584 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1585 if opener and indented:
1586 raw, indentsmall = classifyws(opener, self.tabwidth)
1587 raw, indentlarge = classifyws(indented, self.tabwidth)
1588 else:
1589 indentsmall = indentlarge = 0
1590 return indentlarge - indentsmall
1591
1592# "line.col" -> line, as an int
1593def index2line(index):
1594 return int(float(index))
1595
1596# Look at the leading whitespace in s.
1597# Return pair (# of leading ws characters,
1598# effective # of leading blanks after expanding
1599# tabs to width tabwidth)
1600
1601def classifyws(s, tabwidth):
1602 raw = effective = 0
1603 for ch in s:
1604 if ch == ' ':
1605 raw = raw + 1
1606 effective = effective + 1
1607 elif ch == '\t':
1608 raw = raw + 1
1609 effective = (effective // tabwidth + 1) * tabwidth
1610 else:
1611 break
1612 return raw, effective
1613
1614import tokenize
1615_tokenize = tokenize
1616del tokenize
1617
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001618class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001619
1620 # .run() chews over the Text widget, looking for a block opener
1621 # and the stmt following it. Returns a pair,
1622 # (line containing block opener, line containing stmt)
1623 # Either or both may be None.
1624
1625 def __init__(self, text, tabwidth):
1626 self.text = text
1627 self.tabwidth = tabwidth
1628 self.i = self.finished = 0
1629 self.blkopenline = self.indentedline = None
1630
1631 def readline(self):
1632 if self.finished:
1633 return ""
1634 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001635 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001636 if self.text.compare(mark, ">=", "end"):
1637 return ""
1638 return self.text.get(mark, mark + " lineend+1c")
1639
1640 def tokeneater(self, type, token, start, end, line,
1641 INDENT=_tokenize.INDENT,
1642 NAME=_tokenize.NAME,
1643 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1644 if self.finished:
1645 pass
1646 elif type == NAME and token in OPENERS:
1647 self.blkopenline = line
1648 elif type == INDENT and self.blkopenline:
1649 self.indentedline = line
1650 self.finished = 1
1651
1652 def run(self):
1653 save_tabsize = _tokenize.tabsize
1654 _tokenize.tabsize = self.tabwidth
1655 try:
1656 try:
Trent Nelson428de652008-03-18 22:41:35 +00001657 tokens = _tokenize.generate_tokens(self.readline)
1658 for token in tokens:
1659 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001660 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001661 # since we cut off the tokenizer early, we can trigger
1662 # spurious errors
1663 pass
1664 finally:
1665 _tokenize.tabsize = save_tabsize
1666 return self.blkopenline, self.indentedline
1667
1668### end autoindent code ###
1669
David Scherer7aced172000-08-15 01:13:23 +00001670def prepstr(s):
1671 # Helper to extract the underscore from a string, e.g.
1672 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001673 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001674 if i >= 0:
1675 s = s[:i] + s[i+1:]
1676 return i, s
1677
1678
1679keynames = {
1680 'bracketleft': '[',
1681 'bracketright': ']',
1682 'slash': '/',
1683}
1684
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001685def get_accelerator(keydefs, eventname):
1686 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001687 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1688 # if not keylist:
1689 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1690 "<<open-module>>",
1691 "<<goto-line>>",
1692 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001693 return ""
1694 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001695 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001696 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1697 s = re.sub("Key-", "", s)
1698 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1699 s = re.sub("Control-", "Ctrl-", s)
1700 s = re.sub("-", "+", s)
1701 s = re.sub("><", " ", s)
1702 s = re.sub("<", "", s)
1703 s = re.sub(">", "", s)
1704 return s
1705
1706
1707def fixwordbreaks(root):
1708 # Make sure that Tk's double-click and next/previous word
1709 # operations use our definition of a word (i.e. an identifier)
1710 tk = root.tk
1711 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1712 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1713 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1714
1715
1716def test():
1717 root = Tk()
1718 fixwordbreaks(root)
1719 root.withdraw()
1720 if sys.argv[1:]:
1721 filename = sys.argv[1]
1722 else:
1723 filename = None
1724 edit = EditorWindow(root=root, filename=filename)
1725 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001726 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001727 root.mainloop()
1728 root.destroy()
1729
1730if __name__ == '__main__':
1731 test()