blob: d18305783a893f6641489e0a387f4c90e2db6d20 [file] [log] [blame]
Brett Cannonaef82d32012-04-14 20:44:23 -04001import importlib
Brett Cannon50793b42013-06-07 13:17:48 -04002import importlib.abc
Eric Snow6029e082014-01-25 15:32:46 -07003import importlib.util
David Scherer7aced172000-08-15 01:13:23 +00004import os
Terry Jan Reedy94338de2014-01-23 00:36:46 -05005from platform import python_version
David Scherer7aced172000-08-15 01:13:23 +00006import re
Guido van Rossum33d26892007-08-05 15:29:28 +00007import string
Brett Cannonaef82d32012-04-14 20:44:23 -04008import sys
Georg Brandl14fc4272008-05-17 18:39:55 +00009from tkinter import *
10import tkinter.simpledialog as tkSimpleDialog
11import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +000012import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000013import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000014
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000015from idlelib.MultiCall import MultiCallCreator
16from idlelib import idlever
17from idlelib import WindowList
18from idlelib import SearchDialog
19from idlelib import GrepDialog
20from idlelib import ReplaceDialog
21from idlelib import PyParse
22from idlelib.configHandler import idleConf
23from idlelib import aboutDialog, textView, configDialog
24from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000025
26# The default tab setting for a Text widget, in average-width characters.
27TK_TABWIDTH_DEFAULT = 8
28
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000029def _sphinx_version():
30 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
31 major, minor, micro, level, serial = sys.version_info
32 release = '%s%s' % (major, minor)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020033 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000034 if level == 'candidate':
35 release += 'rc%s' % (serial,)
36 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000037 release += '%s%s' % (level[0], serial)
38 return release
39
Terry Jan Reedye91e7632012-02-05 15:14:20 -050040
41class HelpDialog(object):
42
43 def __init__(self):
44 self.parent = None # parent of help window
45 self.dlg = None # the help window iteself
46
47 def display(self, parent, near=None):
48 """ Display the help dialog.
49
50 parent - parent widget for the help window
51
52 near - a Toplevel widget (e.g. EditorWindow or PyShell)
53 to use as a reference for placing the help window
54 """
55 if self.dlg is None:
56 self.show_dialog(parent)
57 if near:
58 self.nearwindow(near)
59
60 def show_dialog(self, parent):
61 self.parent = parent
62 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
63 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
64 dlg.bind('<Destroy>', self.destroy, '+')
65
66 def nearwindow(self, near):
67 # Place the help dialog near the window specified by parent.
68 # Note - this may not reposition the window in Metacity
69 # if "/apps/metacity/general/disable_workarounds" is enabled
70 dlg = self.dlg
71 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
72 dlg.withdraw()
73 dlg.geometry("=+%d+%d" % geom)
74 dlg.deiconify()
75 dlg.lift()
76
77 def destroy(self, ev=None):
78 self.dlg = None
79 self.parent = None
80
81helpDialog = HelpDialog() # singleton instance
82
83
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000084class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000085 from idlelib.Percolator import Percolator
86 from idlelib.ColorDelegator import ColorDelegator
87 from idlelib.UndoDelegator import UndoDelegator
88 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
89 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000090 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000091 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000092
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000093 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000094
95 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000096 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +010097 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000098 if sys.platform.count('linux'):
99 # look for html docs in a couple of standard places
100 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
101 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
102 dochome = '/var/www/html/python/index.html'
103 else:
104 basepath = '/usr/share/doc/' # standard location
105 dochome = os.path.join(basepath, pyver,
106 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000107 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100108 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +0000109 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000110 if os.path.isfile(chmfile):
111 dochome = chmfile
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000112 elif macosxSupport.runningAsOSXApp():
113 # documentation is stored inside the python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100114 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000115 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000116 dochome = os.path.normpath(dochome)
117 if os.path.isfile(dochome):
118 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000119 if sys.platform == 'darwin':
120 # Safari requires real file:-URLs
121 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000122 else:
Benjamin Peterson152b6572009-01-20 15:01:54 +0000123 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000124 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +0000125 self.flist = flist
126 root = root or flist.root
127 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000128 try:
129 sys.ps1
130 except AttributeError:
131 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000132 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000133 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000134 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000135 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200136 #self.top.instance_dict makes flist.inversedict available to
137 #configDialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000138 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000139 else:
140 self.tkinter_vars = {} # keys: Tkinter event names
141 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000142 self.top.instance_dict = {}
143 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000144 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000145 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000146 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200147 self.width = idleConf.GetOption('main', 'EditorWindow',
148 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000149 text_options = {
150 'name': 'text',
151 'padx': 5,
152 'wrap': 'none',
153 'width': self.width,
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200154 'height': idleConf.GetOption('main', 'EditorWindow',
155 'height', type='int')}
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000156 if TkVersion >= 8.5:
157 # Starting with tk 8.5 we have to set the new tabstyle option
158 # to 'wordprocessor' to achieve the same display of tabs as in
159 # older tk versions.
160 text_options['tabstyle'] = 'wordprocessor'
161 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000162 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000163
164 self.createmenubar()
165 self.apply_bindings()
166
167 self.top.protocol("WM_DELETE_WINDOW", self.close)
168 self.top.bind("<<close-window>>", self.close_event)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000169 if macosxSupport.runningAsOSXApp():
170 # Command-W on editorwindows doesn't work without this.
171 text.bind('<<close-window>>', self.close_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000172 # Some OS X systems have only one mouse button,
173 # so use control-click for pulldown menus there.
174 # (Note, AquaTk defines <2> as the right button if
175 # present and the Tk Text widget already binds <2>.)
176 text.bind("<Control-Button-1>",self.right_menu_event)
177 else:
178 # Elsewhere, use right-click for pulldown menus.
179 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000180 text.bind("<<cut>>", self.cut)
181 text.bind("<<copy>>", self.copy)
182 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000183 text.bind("<<center-insert>>", self.center_insert_event)
184 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000185 text.bind("<<python-docs>>", self.python_docs)
186 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000187 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000188 text.bind("<<open-module>>", self.open_module)
189 text.bind("<<do-nothing>>", lambda event: "break")
190 text.bind("<<select-all>>", self.select_all)
191 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000192 text.bind("<<find>>", self.find_event)
193 text.bind("<<find-again>>", self.find_again_event)
194 text.bind("<<find-in-files>>", self.find_in_files_event)
195 text.bind("<<find-selection>>", self.find_selection_event)
196 text.bind("<<replace>>", self.replace_event)
197 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000198 text.bind("<<smart-backspace>>",self.smart_backspace_event)
199 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
200 text.bind("<<smart-indent>>",self.smart_indent_event)
201 text.bind("<<indent-region>>",self.indent_region_event)
202 text.bind("<<dedent-region>>",self.dedent_region_event)
203 text.bind("<<comment-region>>",self.comment_region_event)
204 text.bind("<<uncomment-region>>",self.uncomment_region_event)
205 text.bind("<<tabify-region>>",self.tabify_region_event)
206 text.bind("<<untabify-region>>",self.untabify_region_event)
207 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
208 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000209 text.bind("<Left>", self.move_at_edge_if_selection(0))
210 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000211 text.bind("<<del-word-left>>", self.del_word_left)
212 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000213 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000214
David Scherer7aced172000-08-15 01:13:23 +0000215 if flist:
216 flist.inversedict[self] = key
217 if key:
218 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000219 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000220 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
221 text.bind("<<open-class-browser>>", self.open_class_browser)
222 text.bind("<<open-path-browser>>", self.open_path_browser)
223
Steven M. Gava898a3652001-10-07 11:10:44 +0000224 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000225 vbar['command'] = text.yview
226 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000227 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000228 fontWeight = 'normal'
229 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000230 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000231 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200232 idleConf.GetOption('main', 'EditorWindow',
233 'font-size', type='int'),
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000234 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000235 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
236 text.pack(side=TOP, fill=BOTH, expand=1)
237 text.focus_set()
238
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000239 # usetabs true -> literal tab characters are used by indent and
240 # dedent cmds, possibly mixed with spaces if
241 # indentwidth is not a multiple of tabwidth,
242 # which will cause Tabnanny to nag!
243 # false -> tab characters are converted to spaces by indent
244 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000245 # Although use-spaces=0 can be configured manually in config-main.def,
246 # configuration of tabs v. spaces is not supported in the configuration
247 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200248 usespaces = idleConf.GetOption('main', 'Indent',
249 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000250 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000251
252 # tabwidth is the display width of a literal tab character.
253 # CAUTION: telling Tk to use anything other than its default
254 # tab setting causes it to use an entirely different tabbing algorithm,
255 # treating tab stops as fixed distances from the left margin.
256 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000257 self.tabwidth = 8 # must remain 8 until Tk is fixed.
258
259 # indentwidth is the number of screen characters per indent level.
260 # The recommended Python indentation is four spaces.
261 self.indentwidth = self.tabwidth
262 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000263
264 # If context_use_ps1 is true, parsing searches back for a ps1 line;
265 # else searches for a popular (if, def, ...) Python stmt.
266 self.context_use_ps1 = False
267
268 # When searching backwards for a reliable place to begin parsing,
269 # first start num_context_lines[0] lines back, then
270 # num_context_lines[1] lines back if that didn't work, and so on.
271 # The last value should be huge (larger than the # of lines in a
272 # conceivable file).
273 # Making the initial values larger slows things down more often.
274 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000275 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000276 self.undo = undo = self.UndoDelegator()
277 per.insertfilter(undo)
278 text.undo_block_start = undo.undo_block_start
279 text.undo_block_stop = undo.undo_block_stop
280 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000281 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000282 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000283 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000284 self.good_load = False
285 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000286 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000287 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000288 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000289 if io.loadfile(filename):
290 self.good_load = True
291 is_py_src = self.ispythonsource(filename)
292 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000293 else:
294 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500295 self.good_load = True
296
Christian Heimesa156e092008-02-16 07:38:31 +0000297 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000298 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000299 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000300 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000301 menu = self.menudict.get('windows')
302 if menu:
303 end = menu.index("end")
304 if end is None:
305 end = -1
306 if end >= 0:
307 menu.add_separator()
308 end = end + 1
309 self.wmenu_end = end
310 WindowList.register_callback(self.postwindowsmenu)
311
312 # Some abstractions so IDLE extensions are cross-IDE
313 self.askyesno = tkMessageBox.askyesno
314 self.askinteger = tkSimpleDialog.askinteger
315 self.showerror = tkMessageBox.showerror
316
Roger Serwycaf30242013-05-20 22:13:39 -0500317 self._highlight_workaround() # Fix selection tags on Windows
318
319 def _highlight_workaround(self):
320 # On Windows, Tk removes painting of the selection
321 # tags which is different behavior than on Linux and Mac.
322 # See issue14146 for more information.
323 if not sys.platform.startswith('win'):
324 return
325
326 text = self.text
327 text.event_add("<<Highlight-FocusOut>>", "<FocusOut>")
328 text.event_add("<<Highlight-FocusIn>>", "<FocusIn>")
329 def highlight_fix(focus):
330 sel_range = text.tag_ranges("sel")
331 if sel_range:
332 if focus == 'out':
333 HILITE_CONFIG = idleConf.GetHighlight(
334 idleConf.CurrentTheme(), 'hilite')
335 text.tag_config("sel_fix", HILITE_CONFIG)
336 text.tag_raise("sel_fix")
337 text.tag_add("sel_fix", *sel_range)
338 elif focus == 'in':
339 text.tag_remove("sel_fix", "1.0", "end")
340
341 text.bind("<<Highlight-FocusOut>>",
342 lambda ev: highlight_fix("out"))
343 text.bind("<<Highlight-FocusIn>>",
344 lambda ev: highlight_fix("in"))
345
346
Martin v. Löwis307021f2005-11-27 16:59:04 +0000347 def _filename_to_unicode(self, filename):
348 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000349 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000350 return filename
351 else:
352 try:
353 return filename.decode(self.filesystemencoding)
354 except UnicodeDecodeError:
355 # XXX
356 try:
357 return filename.decode(self.encoding)
358 except UnicodeDecodeError:
359 # byte-to-byte conversion
360 return filename.decode('iso8859-1')
361
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000362 def new_callback(self, event):
363 dirname, basename = self.io.defaultfilename()
364 self.flist.new(dirname)
365 return "break"
366
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000367 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400368 if (event.state & 4) != 0 and event.keysym == "Home":
369 # state&4==Control. If <Control-Home>, use the Tk binding.
370 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000371 if self.text.index("iomark") and \
372 self.text.compare("iomark", "<=", "insert lineend") and \
373 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400374 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000375 insertpt = int(self.text.index("iomark").split(".")[1])
376 else:
377 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000378 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000379 if line[insertpt] not in (' ','\t'):
380 break
381 else:
382 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000383 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000384 if insertpt == lineat:
385 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000386 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000387 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400388 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000389 self.text.tag_remove("sel", "1.0", "end")
390 else:
391 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200392 # there was no previous selection
393 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400394 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200395 if self.text.compare(self.text.index("sel.first"), "<",
396 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400397 self.text.mark_set("my_anchor", "sel.first") # extend back
398 else:
399 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000400 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400401 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000402 if self.text.compare(first,">",last):
403 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000404 self.text.tag_remove("sel", "1.0", "end")
405 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000406 self.text.mark_set("insert", dest)
407 self.text.see("insert")
408 return "break"
409
David Scherer7aced172000-08-15 01:13:23 +0000410 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000411 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000412 if macosxSupport.runningAsOSXApp():
413 # Insert some padding to avoid obscuring some of the statusbar
414 # by the resize widget.
415 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000416 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
417 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
418 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000419 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
420 self.text.event_add("<<set-line-and-column>>",
421 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000422 self.text.after_idle(self.set_line_and_column)
423
424 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000425 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000426 self.status_bar.set_label('column', 'Col: %s' % column)
427 self.status_bar.set_label('line', 'Ln: %s' % line)
428
David Scherer7aced172000-08-15 01:13:23 +0000429 menu_specs = [
430 ("file", "_File"),
431 ("edit", "_Edit"),
432 ("format", "F_ormat"),
433 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000434 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000435 ("windows", "_Windows"),
436 ("help", "_Help"),
437 ]
438
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000439 if macosxSupport.runningAsOSXApp():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000440 menu_specs[-2] = ("windows", "_Window")
441
442
David Scherer7aced172000-08-15 01:13:23 +0000443 def createmenubar(self):
444 mbar = self.menubar
445 self.menudict = menudict = {}
446 for name, label in self.menu_specs:
447 underline, label = prepstr(label)
448 menudict[name] = menu = Menu(mbar, name=name)
449 mbar.add_cascade(label=label, menu=menu, underline=underline)
Georg Brandlaedd2892010-12-19 10:10:32 +0000450 if macosxSupport.isCarbonAquaTk(self.root):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000451 # Insert the application menu
452 menudict['application'] = menu = Menu(mbar, name='apple')
453 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000454 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000455 self.recent_files_menu = Menu(self.menubar)
456 self.menudict['file'].insert_cascade(3, label='Recent Files',
457 underline=0,
458 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000459 self.base_helpmenu_length = self.menudict['help'].index(END)
460 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000461
462 def postwindowsmenu(self):
463 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000464 menu = self.menudict['windows']
465 end = menu.index("end")
466 if end is None:
467 end = -1
468 if end > self.wmenu_end:
469 menu.delete(self.wmenu_end+1, end)
470 WindowList.add_windows_to_menu(menu)
471
472 rmenu = None
473
474 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000475 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
476 if not self.rmenu:
477 self.make_rmenu()
478 rmenu = self.rmenu
479 self.event = event
480 iswin = sys.platform[:3] == 'win'
481 if iswin:
482 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200483
Roger Serwy6b2918a2013-04-07 12:15:52 -0500484 for item in self.rmenu_specs:
485 try:
486 label, eventname, verify_state = item
487 except ValueError: # see issue1207589
488 continue
489
Andrew Svetlovd1837672012-11-01 22:41:19 +0200490 if verify_state is None:
491 continue
492 state = getattr(self, verify_state)()
493 rmenu.entryconfigure(label, state=state)
494
495
David Scherer7aced172000-08-15 01:13:23 +0000496 rmenu.tk_popup(event.x_root, event.y_root)
497 if iswin:
498 self.text.config(cursor="ibeam")
499
500 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200501 # ("Label", "<<virtual-event>>", "statefuncname"), ...
502 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000503 ]
504
505 def make_rmenu(self):
506 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500507 for item in self.rmenu_specs:
508 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200509 if label is not None:
510 def command(text=self.text, eventname=eventname):
511 text.event_generate(eventname)
512 rmenu.add_command(label=label, command=command)
513 else:
514 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000515 self.rmenu = rmenu
516
Andrew Svetlovd1837672012-11-01 22:41:19 +0200517 def rmenu_check_cut(self):
518 return self.rmenu_check_copy()
519
520 def rmenu_check_copy(self):
521 try:
522 indx = self.text.index('sel.first')
523 except TclError:
524 return 'disabled'
525 else:
526 return 'normal' if indx else 'disabled'
527
528 def rmenu_check_paste(self):
529 try:
530 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
531 except TclError:
532 return 'disabled'
533 else:
534 return 'normal'
535
David Scherer7aced172000-08-15 01:13:23 +0000536 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000537 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000538
Steven M. Gava3b55a892001-11-21 05:56:26 +0000539 def config_dialog(self, event=None):
540 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000541
David Scherer7aced172000-08-15 01:13:23 +0000542 def help_dialog(self, event=None):
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500543 if self.root:
544 parent = self.root
545 else:
546 parent = self.top
547 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000548
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000549 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000550 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000551 try:
552 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200553 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000554 tkMessageBox.showerror(title='Document Start Failure',
555 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000556 else:
557 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000558 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000559
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000560 def cut(self,event):
561 self.text.event_generate("<<Cut>>")
562 return "break"
563
564 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000565 if not self.text.tag_ranges("sel"):
566 # There is no selection, so do nothing and maybe interrupt.
567 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000568 self.text.event_generate("<<Copy>>")
569 return "break"
570
571 def paste(self,event):
572 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000573 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000574 return "break"
575
David Scherer7aced172000-08-15 01:13:23 +0000576 def select_all(self, event=None):
577 self.text.tag_add("sel", "1.0", "end-1c")
578 self.text.mark_set("insert", "1.0")
579 self.text.see("insert")
580 return "break"
581
582 def remove_selection(self, event=None):
583 self.text.tag_remove("sel", "1.0", "end")
584 self.text.see("insert")
585
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000586 def move_at_edge_if_selection(self, edge_index):
587 """Cursor move begins at start or end of selection
588
589 When a left/right cursor key is pressed create and return to Tkinter a
590 function which causes a cursor move from the associated edge of the
591 selection.
592
593 """
594 self_text_index = self.text.index
595 self_text_mark_set = self.text.mark_set
596 edges_table = ("sel.first+1c", "sel.last-1c")
597 def move_at_edge(event):
598 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
599 try:
600 self_text_index("sel.first")
601 self_text_mark_set("insert", edges_table[edge_index])
602 except TclError:
603 pass
604 return move_at_edge
605
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000606 def del_word_left(self, event):
607 self.text.event_generate('<Meta-Delete>')
608 return "break"
609
610 def del_word_right(self, event):
611 self.text.event_generate('<Meta-d>')
612 return "break"
613
Steven M. Gavac5976402002-01-04 03:06:08 +0000614 def find_event(self, event):
615 SearchDialog.find(self.text)
616 return "break"
617
618 def find_again_event(self, event):
619 SearchDialog.find_again(self.text)
620 return "break"
621
622 def find_selection_event(self, event):
623 SearchDialog.find_selection(self.text)
624 return "break"
625
626 def find_in_files_event(self, event):
627 GrepDialog.grep(self.text, self.io, self.flist)
628 return "break"
629
630 def replace_event(self, event):
631 ReplaceDialog.replace(self.text)
632 return "break"
633
634 def goto_line_event(self, event):
635 text = self.text
636 lineno = tkSimpleDialog.askinteger("Goto",
637 "Go to line number:",parent=text)
638 if lineno is None:
639 return "break"
640 if lineno <= 0:
641 text.bell()
642 return "break"
643 text.mark_set("insert", "%d.0" % lineno)
644 text.see("insert")
645
David Scherer7aced172000-08-15 01:13:23 +0000646 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000647 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000648 try:
649 name = self.text.get("sel.first", "sel.last")
650 except TclError:
651 name = ""
652 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000653 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000654 name = tkSimpleDialog.askstring("Module",
655 "Enter the name of a Python module\n"
656 "to search on sys.path and open:",
657 parent=self.text, initialvalue=name)
658 if name:
659 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000660 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000661 return
David Scherer7aced172000-08-15 01:13:23 +0000662 # XXX Ought to insert current file's directory in front of path
663 try:
Eric Snow6029e082014-01-25 15:32:46 -0700664 spec = importlib.util.find_spec(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400665 except (ValueError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000666 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
667 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700668 if spec is None:
Brett Cannon50793b42013-06-07 13:17:48 -0400669 tkMessageBox.showerror("Import error", "module not found",
670 parent=self.text)
David Scherer7aced172000-08-15 01:13:23 +0000671 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700672 if not isinstance(spec.loader, importlib.abc.SourceLoader):
Brett Cannon50793b42013-06-07 13:17:48 -0400673 tkMessageBox.showerror("Import error", "not a source-based module",
674 parent=self.text)
675 return
676 try:
Eric Snow02b9f9d2014-01-06 20:42:59 -0700677 file_path = spec.loader.get_filename(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400678 except AttributeError:
679 tkMessageBox.showerror("Import error",
680 "loader does not support get_filename",
681 parent=self.text)
682 return
David Scherer7aced172000-08-15 01:13:23 +0000683 if self.flist:
Brett Cannon50793b42013-06-07 13:17:48 -0400684 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000685 else:
Brett Cannon50793b42013-06-07 13:17:48 -0400686 self.io.loadfile(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000687
688 def open_class_browser(self, event=None):
689 filename = self.io.filename
690 if not filename:
691 tkMessageBox.showerror(
692 "No filename",
693 "This buffer has no associated filename",
694 master=self.text)
695 self.text.focus_set()
696 return None
697 head, tail = os.path.split(filename)
698 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000699 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000700 ClassBrowser.ClassBrowser(self.flist, base, [head])
701
702 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000703 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000704 PathBrowser.PathBrowser(self.flist)
705
706 def gotoline(self, lineno):
707 if lineno is not None and lineno > 0:
708 self.text.mark_set("insert", "%d.0" % lineno)
709 self.text.tag_remove("sel", "1.0", "end")
710 self.text.tag_add("sel", "insert", "insert +1l")
711 self.center()
712
713 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000714 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000715 return True
David Scherer7aced172000-08-15 01:13:23 +0000716 base, ext = os.path.splitext(os.path.basename(filename))
717 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000718 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000719 line = self.text.get('1.0', '1.0 lineend')
720 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000721
722 def close_hook(self):
723 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000724 self.flist.unregister_maybe_terminate(self)
725 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000726
727 def set_close_hook(self, close_hook):
728 self.close_hook = close_hook
729
730 def filename_change_hook(self):
731 if self.flist:
732 self.flist.filename_changed_edit(self)
733 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000734 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000735 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000736
Christian Heimesa156e092008-02-16 07:38:31 +0000737 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000738 if self.color:
739 return
Christian Heimesa156e092008-02-16 07:38:31 +0000740 if self.ispythonsource(self.io.filename):
741 self.color = self.ColorDelegator()
742 # can add more colorizers here...
743 if self.color:
744 self.per.removefilter(self.undo)
745 self.per.insertfilter(self.color)
746 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000747
Christian Heimesa156e092008-02-16 07:38:31 +0000748 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000749 if not self.color:
750 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000751 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000752 self.per.removefilter(self.color)
753 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000754
Steven M. Gavab77d3432002-03-02 07:16:21 +0000755 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000756 "Update the colour theme"
757 # Called from self.filename_change_hook and from configDialog.py
758 self._rmcolorizer()
759 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000760 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000761 normal_colors = idleConf.GetHighlight(theme, 'normal')
762 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
763 select_colors = idleConf.GetHighlight(theme, 'hilite')
764 self.text.config(
765 foreground=normal_colors['foreground'],
766 background=normal_colors['background'],
767 insertbackground=cursor_color,
768 selectforeground=select_colors['foreground'],
769 selectbackground=select_colors['background'],
770 )
David Scherer7aced172000-08-15 01:13:23 +0000771
Guido van Rossum33d26892007-08-05 15:29:28 +0000772 IDENTCHARS = string.ascii_letters + string.digits + "_"
773
774 def colorize_syntax_error(self, text, pos):
775 text.tag_add("ERROR", pos)
776 char = text.get(pos)
777 if char and char in self.IDENTCHARS:
778 text.tag_add("ERROR", pos + " wordstart", pos)
779 if '\n' == text.get(pos): # error at line end
780 text.mark_set("insert", pos)
781 else:
782 text.mark_set("insert", pos + "+1c")
783 text.see(pos)
784
Steven M. Gavab1585412002-03-12 00:21:56 +0000785 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000786 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000787 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000788 fontWeight='normal'
789 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
790 fontWeight='bold'
791 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200792 idleConf.GetOption('main','EditorWindow','font-size',
793 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000794 fontWeight))
795
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000796 def RemoveKeybindings(self):
797 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000798 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000799 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000800 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000801 self.text.event_delete(event, *keylist)
802 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000803 xkeydefs = idleConf.GetExtensionBindings(extensionName)
804 if xkeydefs:
805 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000806 self.text.event_delete(event, *keylist)
807
808 def ApplyKeybindings(self):
809 "Update the keybindings after they are changed"
810 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000811 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000812 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000813 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000814 xkeydefs = idleConf.GetExtensionBindings(extensionName)
815 if xkeydefs:
816 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000817 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000818 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000819 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000820 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000821 for item in menu[1]:
822 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000823 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000824 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000825 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700826 end = menu.index(END)
827 if end is None:
828 # Skip empty menus
829 continue
830 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000831 for index in range(0, end):
832 if menu.type(index) == 'command':
833 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000834 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000835 itemName = menu.entrycget(index, 'label')
836 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000837 if menubarItem in menuEventDict:
838 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000839 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000840 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000841 accel = get_accelerator(keydefs, event)
842 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000843
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000844 def set_notabs_indentwidth(self):
845 "Update the indentwidth if changed and not using tabs in this window"
846 # Called from configDialog.py
847 if not self.usetabs:
848 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
849 type='int')
850
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000851 def reset_help_menu_entries(self):
852 "Update the additional help entries on the Help menu"
853 help_list = idleConf.GetAllExtraHelpSourcesList()
854 helpmenu = self.menudict['help']
855 # first delete the extra help entries, if any
856 helpmenu_length = helpmenu.index(END)
857 if helpmenu_length > self.base_helpmenu_length:
858 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
859 # then rebuild them
860 if help_list:
861 helpmenu.add_separator()
862 for entry in help_list:
863 cmd = self.__extra_help_callback(entry[1])
864 helpmenu.add_command(label=entry[0], command=cmd)
865 # and update the menu dictionary
866 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000867
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000868 def __extra_help_callback(self, helpfile):
869 "Create a callback with the helpfile value frozen at definition time"
870 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000871 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000872 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000873 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000874 try:
875 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200876 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000877 tkMessageBox.showerror(title='Document Start Failure',
878 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000879 else:
880 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000881 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000882
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000883 def update_recent_files_list(self, new_file=None):
884 "Load and update the recent files list and menus"
885 rf_list = []
886 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400887 with open(self.recent_files_path, 'r',
888 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000889 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000890 if new_file:
891 new_file = os.path.abspath(new_file) + '\n'
892 if new_file in rf_list:
893 rf_list.remove(new_file) # move to top
894 rf_list.insert(0, new_file)
895 # clean and save the recent files list
896 bad_paths = []
897 for path in rf_list:
898 if '\0' in path or not os.path.exists(path[0:-1]):
899 bad_paths.append(path)
900 rf_list = [path for path in rf_list if path not in bad_paths]
901 ulchars = "1234567890ABCDEFGHIJK"
902 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000903 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800904 with open(self.recent_files_path, 'w',
905 encoding='utf_8', errors='replace') as rf_file:
906 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200907 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800908 if not getattr(self.root, "recentfilelist_error_displayed", False):
909 self.root.recentfilelist_error_displayed = True
910 tkMessageBox.showerror(title='IDLE Error',
911 message='Unable to update Recent Files list:\n%s'
912 % str(err),
913 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000914 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000915 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000916 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700917 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000918 for i, file_name in enumerate(rf_list):
919 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000920 # make unicode string to display non-ASCII chars correctly
921 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000922 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000923 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000924 command=callback,
925 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000926
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000927 def __recent_file_callback(self, file_name):
928 def open_recent_file(fn_closure=file_name):
929 self.io.open(editFile=fn_closure)
930 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000931
David Scherer7aced172000-08-15 01:13:23 +0000932 def saved_change_hook(self):
933 short = self.short_title()
934 long = self.long_title()
935 if short and long:
936 title = short + " - " + long
937 elif short:
938 title = short
939 elif long:
940 title = long
941 else:
942 title = "Untitled"
943 icon = short or long or title
944 if not self.get_saved():
945 title = "*%s*" % title
946 icon = "*%s" % icon
947 self.top.wm_title(title)
948 self.top.wm_iconname(icon)
949
950 def get_saved(self):
951 return self.undo.get_saved()
952
953 def set_saved(self, flag):
954 self.undo.set_saved(flag)
955
956 def reset_undo(self):
957 self.undo.reset_undo()
958
959 def short_title(self):
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500960 pyversion = "Python " + python_version() + ": "
David Scherer7aced172000-08-15 01:13:23 +0000961 filename = self.io.filename
962 if filename:
963 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500964 else:
965 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000966 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500967 return pyversion + self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000968
969 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000970 # return unicode string to display non-ASCII chars correctly
971 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000972
973 def center_insert_event(self, event):
974 self.center()
975
976 def center(self, mark="insert"):
977 text = self.text
978 top, bot = self.getwindowlines()
979 lineno = self.getlineno(mark)
980 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000981 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000982 text.yview(float(newtop))
983
984 def getwindowlines(self):
985 text = self.text
986 top = self.getlineno("@0,0")
987 bot = self.getlineno("@0,65535")
988 if top == bot and text.winfo_height() == 1:
989 # Geometry manager hasn't run yet
990 height = int(text['height'])
991 bot = top + height - 1
992 return top, bot
993
994 def getlineno(self, mark="insert"):
995 text = self.text
996 return int(float(text.index(mark)))
997
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000998 def get_geometry(self):
999 "Return (width, height, x, y)"
1000 geom = self.top.wm_geometry()
1001 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +00001002 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001003
David Scherer7aced172000-08-15 01:13:23 +00001004 def close_event(self, event):
1005 self.close()
1006
1007 def maybesave(self):
1008 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001009 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001010 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001011 self.top.deiconify()
1012 self.top.lower()
1013 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001014 return self.io.maybesave()
1015
1016 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001017 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +00001018 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001019 self._close()
1020 return reply
1021
1022 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001023 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001024 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001025 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001026 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001027 self.io.close()
1028 self.io = None
1029 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001030 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001031 self.color.close(False)
1032 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001033 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001034 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001035 self.per.close()
1036 self.per = None
1037 self.top.destroy()
1038 if self.close_hook:
1039 # unless override: unregister from flist, terminate if last window
1040 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001041
1042 def load_extensions(self):
1043 self.extensions = {}
1044 self.load_standard_extensions()
1045
1046 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001047 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001048 if hasattr(ins, "close"):
1049 ins.close()
1050 self.extensions = {}
1051
1052 def load_standard_extensions(self):
1053 for name in self.get_standard_extension_names():
1054 try:
1055 self.load_extension(name)
1056 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001057 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001058 traceback.print_exc()
1059
1060 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001061 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001062
1063 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001064 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001065 try:
1066 mod = importlib.import_module('.' + name, package=__package__)
1067 except ImportError:
1068 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001069 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001070 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001071 raise
David Scherer7aced172000-08-15 01:13:23 +00001072 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001073 keydefs = idleConf.GetExtensionBindings(name)
1074 if hasattr(cls, "menudefs"):
1075 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001076 ins = cls(self)
1077 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001078 if keydefs:
1079 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001080 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001081 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001082 while methodname[:1] == '<':
1083 methodname = methodname[1:]
1084 while methodname[-1:] == '>':
1085 methodname = methodname[:-1]
1086 methodname = methodname + "_event"
1087 if hasattr(ins, methodname):
1088 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001089
1090 def apply_bindings(self, keydefs=None):
1091 if keydefs is None:
1092 keydefs = self.Bindings.default_keydefs
1093 text = self.text
1094 text.keydefs = keydefs
1095 for event, keylist in keydefs.items():
1096 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001097 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001098
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001099 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001100 """Add appropriate entries to the menus and submenus
1101
1102 Menus that are absent or None in self.menudict are ignored.
1103 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001104 if menudefs is None:
1105 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001106 if keydefs is None:
1107 keydefs = self.Bindings.default_keydefs
1108 menudict = self.menudict
1109 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001110 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001111 menu = menudict.get(mname)
1112 if not menu:
1113 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001114 for entry in entrylist:
1115 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001116 menu.add_separator()
1117 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001118 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001119 checkbutton = (label[:1] == '!')
1120 if checkbutton:
1121 label = label[1:]
1122 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001123 accelerator = get_accelerator(keydefs, eventname)
1124 def command(text=text, eventname=eventname):
1125 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001126 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001127 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001128 menu.add_checkbutton(label=label, underline=underline,
1129 command=command, accelerator=accelerator,
1130 variable=var)
1131 else:
1132 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001133 command=command,
1134 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001135
1136 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001137 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001138 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001139 value = var.get()
1140 return value
1141 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001142 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001143
1144 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001145 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001146 if var:
1147 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001148 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001149 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001150
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001151 def get_var_obj(self, name, vartype=None):
1152 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001153 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001154 # create a Tkinter variable object with self.text as master:
1155 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001156 return var
1157
1158 # Tk implementations of "virtual text methods" -- each platform
1159 # reusing IDLE's support code needs to define these for its GUI's
1160 # flavor of widget.
1161
1162 # Is character at text_index in a Python string? Return 0 for
1163 # "guaranteed no", true for anything else. This info is expensive
1164 # to compute ab initio, but is probably already known by the
1165 # platform's colorizer.
1166
1167 def is_char_in_string(self, text_index):
1168 if self.color:
1169 # Return true iff colorizer hasn't (re)gotten this far
1170 # yet, or the character is tagged as being in a string
1171 return self.text.tag_prevrange("TODO", text_index) or \
1172 "STRING" in self.text.tag_names(text_index)
1173 else:
1174 # The colorizer is missing: assume the worst
1175 return 1
1176
1177 # If a selection is defined in the text widget, return (start,
1178 # end) as Tkinter text indices, otherwise return (None, None)
1179 def get_selection_indices(self):
1180 try:
1181 first = self.text.index("sel.first")
1182 last = self.text.index("sel.last")
1183 return first, last
1184 except TclError:
1185 return None, None
1186
1187 # Return the text widget's current view of what a tab stop means
1188 # (equivalent width in spaces).
1189
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001190 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001191 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1192 return int(current)
1193
1194 # Set the text widget's current view of what a tab stop means.
1195
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001196 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001197 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001198 if self.get_tk_tabwidth() != newtabwidth:
1199 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001200 pixels = text.tk.call("font", "measure", text["font"],
1201 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001202 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001203 text.configure(tabs=pixels)
1204
Guido van Rossum33d26892007-08-05 15:29:28 +00001205### begin autoindent code ### (configuration was moved to beginning of class)
1206
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001207 def set_indentation_params(self, is_py_src, guess=True):
1208 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001209 i = self.guess_indent()
1210 if 2 <= i <= 8:
1211 self.indentwidth = i
1212 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001213 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001214 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001215
1216 def smart_backspace_event(self, event):
1217 text = self.text
1218 first, last = self.get_selection_indices()
1219 if first and last:
1220 text.delete(first, last)
1221 text.mark_set("insert", first)
1222 return "break"
1223 # Delete whitespace left, until hitting a real char or closest
1224 # preceding virtual tab stop.
1225 chars = text.get("insert linestart", "insert")
1226 if chars == '':
1227 if text.compare("insert", ">", "1.0"):
1228 # easy: delete preceding newline
1229 text.delete("insert-1c")
1230 else:
1231 text.bell() # at start of buffer
1232 return "break"
1233 if chars[-1] not in " \t":
1234 # easy: delete preceding real char
1235 text.delete("insert-1c")
1236 return "break"
1237 # Ick. It may require *inserting* spaces if we back up over a
1238 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001239 tabwidth = self.tabwidth
1240 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001241 assert have > 0
1242 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001243 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001244 if self.context_use_ps1:
1245 last_line_of_prompt = sys.ps1.split('\n')[-1]
1246 else:
1247 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001248 ncharsdeleted = 0
1249 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001250 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001251 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001252 chars = chars[:-1]
1253 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001254 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001255 if have <= want or chars[-1] not in " \t":
1256 break
1257 text.undo_block_start()
1258 text.delete("insert-%dc" % ncharsdeleted, "insert")
1259 if have < want:
1260 text.insert("insert", ' ' * (want - have))
1261 text.undo_block_stop()
1262 return "break"
1263
1264 def smart_indent_event(self, event):
1265 # if intraline selection:
1266 # delete it
1267 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001268 # do indent-region
1269 # else:
1270 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001271 text = self.text
1272 first, last = self.get_selection_indices()
1273 text.undo_block_start()
1274 try:
1275 if first and last:
1276 if index2line(first) != index2line(last):
1277 return self.indent_region_event(event)
1278 text.delete(first, last)
1279 text.mark_set("insert", first)
1280 prefix = text.get("insert linestart", "insert")
1281 raw, effective = classifyws(prefix, self.tabwidth)
1282 if raw == len(prefix):
1283 # only whitespace to the left
1284 self.reindent_to(effective + self.indentwidth)
1285 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001286 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001287 if self.usetabs:
1288 pad = '\t'
1289 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001290 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001291 n = self.indentwidth
1292 pad = ' ' * (n - effective % n)
1293 text.insert("insert", pad)
1294 text.see("insert")
1295 return "break"
1296 finally:
1297 text.undo_block_stop()
1298
1299 def newline_and_indent_event(self, event):
1300 text = self.text
1301 first, last = self.get_selection_indices()
1302 text.undo_block_start()
1303 try:
1304 if first and last:
1305 text.delete(first, last)
1306 text.mark_set("insert", first)
1307 line = text.get("insert linestart", "insert")
1308 i, n = 0, len(line)
1309 while i < n and line[i] in " \t":
1310 i = i+1
1311 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001312 # the cursor is in or at leading indentation in a continuation
1313 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001314 text.insert("insert linestart", '\n')
1315 return "break"
1316 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001317 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001318 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001319 last_line_of_prompt = sys.ps1.split('\n')[-1]
1320 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001321 line = line[:-1]
1322 i = i+1
1323 if i:
1324 text.delete("insert - %d chars" % i, "insert")
1325 # strip whitespace after insert point
1326 while text.get("insert") in " \t":
1327 text.delete("insert")
1328 # start new line
1329 text.insert("insert", '\n')
1330
1331 # adjust indentation for continuations and block
1332 # open/close first need to find the last stmt
1333 lno = index2line(text.index('insert'))
1334 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001335 if not self.context_use_ps1:
1336 for context in self.num_context_lines:
1337 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001338 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001339 rawtext = text.get(startatindex, "insert")
1340 y.set_str(rawtext)
1341 bod = y.find_good_parse_start(
1342 self.context_use_ps1,
1343 self._build_char_in_string_func(startatindex))
1344 if bod is not None or startat == 1:
1345 break
1346 y.set_lo(bod or 0)
1347 else:
1348 r = text.tag_prevrange("console", "insert")
1349 if r:
1350 startatindex = r[1]
1351 else:
1352 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001353 rawtext = text.get(startatindex, "insert")
1354 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001355 y.set_lo(0)
1356
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001357 c = y.get_continuation_type()
1358 if c != PyParse.C_NONE:
1359 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001360 if c == PyParse.C_STRING_FIRST_LINE:
1361 # after the first line of a string; do not indent at all
1362 pass
1363 elif c == PyParse.C_STRING_NEXT_LINES:
1364 # inside a string which started before this line;
1365 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001366 text.insert("insert", indent)
1367 elif c == PyParse.C_BRACKET:
1368 # line up with the first (if any) element of the
1369 # last open bracket structure; else indent one
1370 # level beyond the indent of the line with the
1371 # last open bracket
1372 self.reindent_to(y.compute_bracket_indent())
1373 elif c == PyParse.C_BACKSLASH:
1374 # if more than one line in this stmt already, just
1375 # mimic the current indent; else if initial line
1376 # has a start on an assignment stmt, indent to
1377 # beyond leftmost =; else to beyond first chunk of
1378 # non-whitespace on initial line
1379 if y.get_num_lines_in_stmt() > 1:
1380 text.insert("insert", indent)
1381 else:
1382 self.reindent_to(y.compute_backslash_indent())
1383 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001384 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001385 return "break"
1386
1387 # This line starts a brand new stmt; indent relative to
1388 # indentation of initial line of closest preceding
1389 # interesting stmt.
1390 indent = y.get_base_indent_string()
1391 text.insert("insert", indent)
1392 if y.is_block_opener():
1393 self.smart_indent_event(event)
1394 elif indent and y.is_block_closer():
1395 self.smart_backspace_event(event)
1396 return "break"
1397 finally:
1398 text.see("insert")
1399 text.undo_block_stop()
1400
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001401 # Our editwin provides a is_char_in_string function that works
1402 # with a Tk text index, but PyParse only knows about offsets into
1403 # a string. This builds a function for PyParse that accepts an
1404 # offset.
1405
1406 def _build_char_in_string_func(self, startindex):
1407 def inner(offset, _startindex=startindex,
1408 _icis=self.is_char_in_string):
1409 return _icis(_startindex + "+%dc" % offset)
1410 return inner
1411
1412 def indent_region_event(self, event):
1413 head, tail, chars, lines = self.get_region()
1414 for pos in range(len(lines)):
1415 line = lines[pos]
1416 if line:
1417 raw, effective = classifyws(line, self.tabwidth)
1418 effective = effective + self.indentwidth
1419 lines[pos] = self._make_blanks(effective) + line[raw:]
1420 self.set_region(head, tail, chars, lines)
1421 return "break"
1422
1423 def dedent_region_event(self, event):
1424 head, tail, chars, lines = self.get_region()
1425 for pos in range(len(lines)):
1426 line = lines[pos]
1427 if line:
1428 raw, effective = classifyws(line, self.tabwidth)
1429 effective = max(effective - self.indentwidth, 0)
1430 lines[pos] = self._make_blanks(effective) + line[raw:]
1431 self.set_region(head, tail, chars, lines)
1432 return "break"
1433
1434 def comment_region_event(self, event):
1435 head, tail, chars, lines = self.get_region()
1436 for pos in range(len(lines) - 1):
1437 line = lines[pos]
1438 lines[pos] = '##' + line
1439 self.set_region(head, tail, chars, lines)
1440
1441 def uncomment_region_event(self, event):
1442 head, tail, chars, lines = self.get_region()
1443 for pos in range(len(lines)):
1444 line = lines[pos]
1445 if not line:
1446 continue
1447 if line[:2] == '##':
1448 line = line[2:]
1449 elif line[:1] == '#':
1450 line = line[1:]
1451 lines[pos] = line
1452 self.set_region(head, tail, chars, lines)
1453
1454 def tabify_region_event(self, event):
1455 head, tail, chars, lines = self.get_region()
1456 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001457 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001458 for pos in range(len(lines)):
1459 line = lines[pos]
1460 if line:
1461 raw, effective = classifyws(line, tabwidth)
1462 ntabs, nspaces = divmod(effective, tabwidth)
1463 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1464 self.set_region(head, tail, chars, lines)
1465
1466 def untabify_region_event(self, event):
1467 head, tail, chars, lines = self.get_region()
1468 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001469 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001470 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001471 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001472 self.set_region(head, tail, chars, lines)
1473
1474 def toggle_tabs_event(self, event):
1475 if self.askyesno(
1476 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001477 "Turn tabs " + ("on", "off")[self.usetabs] +
1478 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001479 ("will be", "remains at")[self.usetabs] + " 8." +
1480 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001481 parent=self.text):
1482 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001483 # Try to prevent inconsistent indentation.
1484 # User must change indent width manually after using tabs.
1485 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001486 return "break"
1487
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001488 # XXX this isn't bound to anything -- see tabwidth comments
1489## def change_tabwidth_event(self, event):
1490## new = self._asktabwidth()
1491## if new != self.tabwidth:
1492## self.tabwidth = new
1493## self.set_indentation_params(0, guess=0)
1494## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001495
1496 def change_indentwidth_event(self, event):
1497 new = self.askinteger(
1498 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001499 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001500 parent=self.text,
1501 initialvalue=self.indentwidth,
1502 minvalue=2,
1503 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001504 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001505 self.indentwidth = new
1506 return "break"
1507
1508 def get_region(self):
1509 text = self.text
1510 first, last = self.get_selection_indices()
1511 if first and last:
1512 head = text.index(first + " linestart")
1513 tail = text.index(last + "-1c lineend +1c")
1514 else:
1515 head = text.index("insert linestart")
1516 tail = text.index("insert lineend +1c")
1517 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001518 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001519 return head, tail, chars, lines
1520
1521 def set_region(self, head, tail, chars, lines):
1522 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001523 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001524 if newchars == chars:
1525 text.bell()
1526 return
1527 text.tag_remove("sel", "1.0", "end")
1528 text.mark_set("insert", head)
1529 text.undo_block_start()
1530 text.delete(head, tail)
1531 text.insert(head, newchars)
1532 text.undo_block_stop()
1533 text.tag_add("sel", head, "insert")
1534
1535 # Make string that displays as n leading blanks.
1536
1537 def _make_blanks(self, n):
1538 if self.usetabs:
1539 ntabs, nspaces = divmod(n, self.tabwidth)
1540 return '\t' * ntabs + ' ' * nspaces
1541 else:
1542 return ' ' * n
1543
1544 # Delete from beginning of line to insert point, then reinsert
1545 # column logical (meaning use tabs if appropriate) spaces.
1546
1547 def reindent_to(self, column):
1548 text = self.text
1549 text.undo_block_start()
1550 if text.compare("insert linestart", "!=", "insert"):
1551 text.delete("insert linestart", "insert")
1552 if column:
1553 text.insert("insert", self._make_blanks(column))
1554 text.undo_block_stop()
1555
1556 def _asktabwidth(self):
1557 return self.askinteger(
1558 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001559 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001560 parent=self.text,
1561 initialvalue=self.indentwidth,
1562 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001563 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001564
1565 # Guess indentwidth from text content.
1566 # Return guessed indentwidth. This should not be believed unless
1567 # it's in a reasonable range (e.g., it will be 0 if no indented
1568 # blocks are found).
1569
1570 def guess_indent(self):
1571 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1572 if opener and indented:
1573 raw, indentsmall = classifyws(opener, self.tabwidth)
1574 raw, indentlarge = classifyws(indented, self.tabwidth)
1575 else:
1576 indentsmall = indentlarge = 0
1577 return indentlarge - indentsmall
1578
1579# "line.col" -> line, as an int
1580def index2line(index):
1581 return int(float(index))
1582
1583# Look at the leading whitespace in s.
1584# Return pair (# of leading ws characters,
1585# effective # of leading blanks after expanding
1586# tabs to width tabwidth)
1587
1588def classifyws(s, tabwidth):
1589 raw = effective = 0
1590 for ch in s:
1591 if ch == ' ':
1592 raw = raw + 1
1593 effective = effective + 1
1594 elif ch == '\t':
1595 raw = raw + 1
1596 effective = (effective // tabwidth + 1) * tabwidth
1597 else:
1598 break
1599 return raw, effective
1600
1601import tokenize
1602_tokenize = tokenize
1603del tokenize
1604
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001605class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001606
1607 # .run() chews over the Text widget, looking for a block opener
1608 # and the stmt following it. Returns a pair,
1609 # (line containing block opener, line containing stmt)
1610 # Either or both may be None.
1611
1612 def __init__(self, text, tabwidth):
1613 self.text = text
1614 self.tabwidth = tabwidth
1615 self.i = self.finished = 0
1616 self.blkopenline = self.indentedline = None
1617
1618 def readline(self):
1619 if self.finished:
1620 return ""
1621 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001622 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001623 if self.text.compare(mark, ">=", "end"):
1624 return ""
1625 return self.text.get(mark, mark + " lineend+1c")
1626
1627 def tokeneater(self, type, token, start, end, line,
1628 INDENT=_tokenize.INDENT,
1629 NAME=_tokenize.NAME,
1630 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1631 if self.finished:
1632 pass
1633 elif type == NAME and token in OPENERS:
1634 self.blkopenline = line
1635 elif type == INDENT and self.blkopenline:
1636 self.indentedline = line
1637 self.finished = 1
1638
1639 def run(self):
1640 save_tabsize = _tokenize.tabsize
1641 _tokenize.tabsize = self.tabwidth
1642 try:
1643 try:
Trent Nelson428de652008-03-18 22:41:35 +00001644 tokens = _tokenize.generate_tokens(self.readline)
1645 for token in tokens:
1646 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001647 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001648 # since we cut off the tokenizer early, we can trigger
1649 # spurious errors
1650 pass
1651 finally:
1652 _tokenize.tabsize = save_tabsize
1653 return self.blkopenline, self.indentedline
1654
1655### end autoindent code ###
1656
David Scherer7aced172000-08-15 01:13:23 +00001657def prepstr(s):
1658 # Helper to extract the underscore from a string, e.g.
1659 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001660 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001661 if i >= 0:
1662 s = s[:i] + s[i+1:]
1663 return i, s
1664
1665
1666keynames = {
1667 'bracketleft': '[',
1668 'bracketright': ']',
1669 'slash': '/',
1670}
1671
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001672def get_accelerator(keydefs, eventname):
1673 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001674 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1675 # if not keylist:
1676 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1677 "<<open-module>>",
1678 "<<goto-line>>",
1679 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001680 return ""
1681 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001682 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001683 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1684 s = re.sub("Key-", "", s)
1685 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1686 s = re.sub("Control-", "Ctrl-", s)
1687 s = re.sub("-", "+", s)
1688 s = re.sub("><", " ", s)
1689 s = re.sub("<", "", s)
1690 s = re.sub(">", "", s)
1691 return s
1692
1693
1694def fixwordbreaks(root):
1695 # Make sure that Tk's double-click and next/previous word
1696 # operations use our definition of a word (i.e. an identifier)
1697 tk = root.tk
1698 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1699 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1700 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1701
1702
1703def test():
1704 root = Tk()
1705 fixwordbreaks(root)
1706 root.withdraw()
1707 if sys.argv[1:]:
1708 filename = sys.argv[1]
1709 else:
1710 filename = None
1711 edit = EditorWindow(root=root, filename=filename)
1712 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001713 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001714 root.mainloop()
1715 root.destroy()
1716
1717if __name__ == '__main__':
1718 test()