blob: fdcf1a051bee31d60deb3588165041ed26fea460 [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():
466 del menu_specs[-3]
467 menu_specs[-2] = ("windows", "_Window")
468
469
David Scherer7aced172000-08-15 01:13:23 +0000470 def createmenubar(self):
471 mbar = self.menubar
472 self.menudict = menudict = {}
473 for name, label in self.menu_specs:
474 underline, label = prepstr(label)
475 menudict[name] = menu = Menu(mbar, name=name)
476 mbar.add_cascade(label=label, menu=menu, underline=underline)
Georg Brandlaedd2892010-12-19 10:10:32 +0000477 if macosxSupport.isCarbonAquaTk(self.root):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000478 # Insert the application menu
479 menudict['application'] = menu = Menu(mbar, name='apple')
480 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000481 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000482 self.recent_files_menu = Menu(self.menubar)
483 self.menudict['file'].insert_cascade(3, label='Recent Files',
484 underline=0,
485 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000486 self.base_helpmenu_length = self.menudict['help'].index(END)
487 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000488
489 def postwindowsmenu(self):
490 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000491 menu = self.menudict['windows']
492 end = menu.index("end")
493 if end is None:
494 end = -1
495 if end > self.wmenu_end:
496 menu.delete(self.wmenu_end+1, end)
497 WindowList.add_windows_to_menu(menu)
498
499 rmenu = None
500
501 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000502 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
503 if not self.rmenu:
504 self.make_rmenu()
505 rmenu = self.rmenu
506 self.event = event
507 iswin = sys.platform[:3] == 'win'
508 if iswin:
509 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200510
Roger Serwy6b2918a2013-04-07 12:15:52 -0500511 for item in self.rmenu_specs:
512 try:
513 label, eventname, verify_state = item
514 except ValueError: # see issue1207589
515 continue
516
Andrew Svetlovd1837672012-11-01 22:41:19 +0200517 if verify_state is None:
518 continue
519 state = getattr(self, verify_state)()
520 rmenu.entryconfigure(label, state=state)
521
522
David Scherer7aced172000-08-15 01:13:23 +0000523 rmenu.tk_popup(event.x_root, event.y_root)
524 if iswin:
525 self.text.config(cursor="ibeam")
526
527 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200528 # ("Label", "<<virtual-event>>", "statefuncname"), ...
529 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000530 ]
531
532 def make_rmenu(self):
533 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500534 for item in self.rmenu_specs:
535 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200536 if label is not None:
537 def command(text=self.text, eventname=eventname):
538 text.event_generate(eventname)
539 rmenu.add_command(label=label, command=command)
540 else:
541 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000542 self.rmenu = rmenu
543
Andrew Svetlovd1837672012-11-01 22:41:19 +0200544 def rmenu_check_cut(self):
545 return self.rmenu_check_copy()
546
547 def rmenu_check_copy(self):
548 try:
549 indx = self.text.index('sel.first')
550 except TclError:
551 return 'disabled'
552 else:
553 return 'normal' if indx else 'disabled'
554
555 def rmenu_check_paste(self):
556 try:
557 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
558 except TclError:
559 return 'disabled'
560 else:
561 return 'normal'
562
David Scherer7aced172000-08-15 01:13:23 +0000563 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000564 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000565
Steven M. Gava3b55a892001-11-21 05:56:26 +0000566 def config_dialog(self, event=None):
567 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000568
David Scherer7aced172000-08-15 01:13:23 +0000569 def help_dialog(self, event=None):
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500570 if self.root:
571 parent = self.root
572 else:
573 parent = self.top
574 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000575
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000576 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000577 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000578 try:
579 os.startfile(self.help_url)
580 except WindowsError as why:
581 tkMessageBox.showerror(title='Document Start Failure',
582 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000583 else:
584 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000585 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000586
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000587 def cut(self,event):
588 self.text.event_generate("<<Cut>>")
589 return "break"
590
591 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000592 if not self.text.tag_ranges("sel"):
593 # There is no selection, so do nothing and maybe interrupt.
594 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000595 self.text.event_generate("<<Copy>>")
596 return "break"
597
598 def paste(self,event):
599 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000600 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000601 return "break"
602
David Scherer7aced172000-08-15 01:13:23 +0000603 def select_all(self, event=None):
604 self.text.tag_add("sel", "1.0", "end-1c")
605 self.text.mark_set("insert", "1.0")
606 self.text.see("insert")
607 return "break"
608
609 def remove_selection(self, event=None):
610 self.text.tag_remove("sel", "1.0", "end")
611 self.text.see("insert")
612
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000613 def move_at_edge_if_selection(self, edge_index):
614 """Cursor move begins at start or end of selection
615
616 When a left/right cursor key is pressed create and return to Tkinter a
617 function which causes a cursor move from the associated edge of the
618 selection.
619
620 """
621 self_text_index = self.text.index
622 self_text_mark_set = self.text.mark_set
623 edges_table = ("sel.first+1c", "sel.last-1c")
624 def move_at_edge(event):
625 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
626 try:
627 self_text_index("sel.first")
628 self_text_mark_set("insert", edges_table[edge_index])
629 except TclError:
630 pass
631 return move_at_edge
632
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000633 def del_word_left(self, event):
634 self.text.event_generate('<Meta-Delete>')
635 return "break"
636
637 def del_word_right(self, event):
638 self.text.event_generate('<Meta-d>')
639 return "break"
640
Steven M. Gavac5976402002-01-04 03:06:08 +0000641 def find_event(self, event):
642 SearchDialog.find(self.text)
643 return "break"
644
645 def find_again_event(self, event):
646 SearchDialog.find_again(self.text)
647 return "break"
648
649 def find_selection_event(self, event):
650 SearchDialog.find_selection(self.text)
651 return "break"
652
653 def find_in_files_event(self, event):
654 GrepDialog.grep(self.text, self.io, self.flist)
655 return "break"
656
657 def replace_event(self, event):
658 ReplaceDialog.replace(self.text)
659 return "break"
660
661 def goto_line_event(self, event):
662 text = self.text
663 lineno = tkSimpleDialog.askinteger("Goto",
664 "Go to line number:",parent=text)
665 if lineno is None:
666 return "break"
667 if lineno <= 0:
668 text.bell()
669 return "break"
670 text.mark_set("insert", "%d.0" % lineno)
671 text.see("insert")
672
David Scherer7aced172000-08-15 01:13:23 +0000673 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000674 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000675 try:
676 name = self.text.get("sel.first", "sel.last")
677 except TclError:
678 name = ""
679 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000680 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000681 name = tkSimpleDialog.askstring("Module",
682 "Enter the name of a Python module\n"
683 "to search on sys.path and open:",
684 parent=self.text, initialvalue=name)
685 if name:
686 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000687 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000688 return
David Scherer7aced172000-08-15 01:13:23 +0000689 # XXX Ought to insert current file's directory in front of path
690 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000691 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000692 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000693 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
694 return
695 if type != imp.PY_SOURCE:
696 tkMessageBox.showerror("Unsupported type",
697 "%s is not a source module" % name, parent=self.text)
698 return
699 if f:
700 f.close()
701 if self.flist:
702 self.flist.open(file)
703 else:
704 self.io.loadfile(file)
705
706 def open_class_browser(self, event=None):
707 filename = self.io.filename
708 if not filename:
709 tkMessageBox.showerror(
710 "No filename",
711 "This buffer has no associated filename",
712 master=self.text)
713 self.text.focus_set()
714 return None
715 head, tail = os.path.split(filename)
716 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000717 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000718 ClassBrowser.ClassBrowser(self.flist, base, [head])
719
720 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000721 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000722 PathBrowser.PathBrowser(self.flist)
723
724 def gotoline(self, lineno):
725 if lineno is not None and lineno > 0:
726 self.text.mark_set("insert", "%d.0" % lineno)
727 self.text.tag_remove("sel", "1.0", "end")
728 self.text.tag_add("sel", "insert", "insert +1l")
729 self.center()
730
731 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000732 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000733 return True
David Scherer7aced172000-08-15 01:13:23 +0000734 base, ext = os.path.splitext(os.path.basename(filename))
735 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000736 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000737 line = self.text.get('1.0', '1.0 lineend')
738 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000739
740 def close_hook(self):
741 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000742 self.flist.unregister_maybe_terminate(self)
743 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000744
745 def set_close_hook(self, close_hook):
746 self.close_hook = close_hook
747
748 def filename_change_hook(self):
749 if self.flist:
750 self.flist.filename_changed_edit(self)
751 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000752 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000753 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000754
Christian Heimesa156e092008-02-16 07:38:31 +0000755 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000756 if self.color:
757 return
Christian Heimesa156e092008-02-16 07:38:31 +0000758 if self.ispythonsource(self.io.filename):
759 self.color = self.ColorDelegator()
760 # can add more colorizers here...
761 if self.color:
762 self.per.removefilter(self.undo)
763 self.per.insertfilter(self.color)
764 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000765
Christian Heimesa156e092008-02-16 07:38:31 +0000766 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000767 if not self.color:
768 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000769 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000770 self.per.removefilter(self.color)
771 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000772
Steven M. Gavab77d3432002-03-02 07:16:21 +0000773 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000774 "Update the colour theme"
775 # Called from self.filename_change_hook and from configDialog.py
776 self._rmcolorizer()
777 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000778 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000779 normal_colors = idleConf.GetHighlight(theme, 'normal')
780 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
781 select_colors = idleConf.GetHighlight(theme, 'hilite')
782 self.text.config(
783 foreground=normal_colors['foreground'],
784 background=normal_colors['background'],
785 insertbackground=cursor_color,
786 selectforeground=select_colors['foreground'],
787 selectbackground=select_colors['background'],
788 )
David Scherer7aced172000-08-15 01:13:23 +0000789
Guido van Rossum33d26892007-08-05 15:29:28 +0000790 IDENTCHARS = string.ascii_letters + string.digits + "_"
791
792 def colorize_syntax_error(self, text, pos):
793 text.tag_add("ERROR", pos)
794 char = text.get(pos)
795 if char and char in self.IDENTCHARS:
796 text.tag_add("ERROR", pos + " wordstart", pos)
797 if '\n' == text.get(pos): # error at line end
798 text.mark_set("insert", pos)
799 else:
800 text.mark_set("insert", pos + "+1c")
801 text.see(pos)
802
Steven M. Gavab1585412002-03-12 00:21:56 +0000803 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000804 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000805 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000806 fontWeight='normal'
807 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
808 fontWeight='bold'
809 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200810 idleConf.GetOption('main','EditorWindow','font-size',
811 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000812 fontWeight))
813
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000814 def RemoveKeybindings(self):
815 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000816 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000817 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000818 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000819 self.text.event_delete(event, *keylist)
820 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000821 xkeydefs = idleConf.GetExtensionBindings(extensionName)
822 if xkeydefs:
823 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000824 self.text.event_delete(event, *keylist)
825
826 def ApplyKeybindings(self):
827 "Update the keybindings after they are changed"
828 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000829 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000830 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000831 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000832 xkeydefs = idleConf.GetExtensionBindings(extensionName)
833 if xkeydefs:
834 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000835 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000836 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000837 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000838 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000839 for item in menu[1]:
840 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000841 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000842 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000843 menu = self.menudict[menubarItem]
844 end = menu.index(END) + 1
845 for index in range(0, end):
846 if menu.type(index) == 'command':
847 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000848 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000849 itemName = menu.entrycget(index, 'label')
850 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000851 if menubarItem in menuEventDict:
852 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000853 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000854 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000855 accel = get_accelerator(keydefs, event)
856 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000857
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000858 def set_notabs_indentwidth(self):
859 "Update the indentwidth if changed and not using tabs in this window"
860 # Called from configDialog.py
861 if not self.usetabs:
862 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
863 type='int')
864
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000865 def reset_help_menu_entries(self):
866 "Update the additional help entries on the Help menu"
867 help_list = idleConf.GetAllExtraHelpSourcesList()
868 helpmenu = self.menudict['help']
869 # first delete the extra help entries, if any
870 helpmenu_length = helpmenu.index(END)
871 if helpmenu_length > self.base_helpmenu_length:
872 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
873 # then rebuild them
874 if help_list:
875 helpmenu.add_separator()
876 for entry in help_list:
877 cmd = self.__extra_help_callback(entry[1])
878 helpmenu.add_command(label=entry[0], command=cmd)
879 # and update the menu dictionary
880 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000881
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000882 def __extra_help_callback(self, helpfile):
883 "Create a callback with the helpfile value frozen at definition time"
884 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000885 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000886 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000887 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000888 try:
889 os.startfile(helpfile)
890 except WindowsError as why:
891 tkMessageBox.showerror(title='Document Start Failure',
892 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000893 else:
894 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000895 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000896
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000897 def update_recent_files_list(self, new_file=None):
898 "Load and update the recent files list and menus"
899 rf_list = []
900 if os.path.exists(self.recent_files_path):
Ned Deily122539e2011-01-24 21:46:44 +0000901 rf_list_file = open(self.recent_files_path,'r',
902 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000903 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000904 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000905 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000906 rf_list_file.close()
907 if new_file:
908 new_file = os.path.abspath(new_file) + '\n'
909 if new_file in rf_list:
910 rf_list.remove(new_file) # move to top
911 rf_list.insert(0, new_file)
912 # clean and save the recent files list
913 bad_paths = []
914 for path in rf_list:
915 if '\0' in path or not os.path.exists(path[0:-1]):
916 bad_paths.append(path)
917 rf_list = [path for path in rf_list if path not in bad_paths]
918 ulchars = "1234567890ABCDEFGHIJK"
919 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000920 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800921 with open(self.recent_files_path, 'w',
922 encoding='utf_8', errors='replace') as rf_file:
923 rf_file.writelines(rf_list)
924 except IOError as err:
925 if not getattr(self.root, "recentfilelist_error_displayed", False):
926 self.root.recentfilelist_error_displayed = True
927 tkMessageBox.showerror(title='IDLE Error',
928 message='Unable to update Recent Files list:\n%s'
929 % str(err),
930 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000931 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000932 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000933 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700934 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000935 for i, file_name in enumerate(rf_list):
936 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000937 # make unicode string to display non-ASCII chars correctly
938 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000939 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000940 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000941 command=callback,
942 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000943
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000944 def __recent_file_callback(self, file_name):
945 def open_recent_file(fn_closure=file_name):
946 self.io.open(editFile=fn_closure)
947 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000948
David Scherer7aced172000-08-15 01:13:23 +0000949 def saved_change_hook(self):
950 short = self.short_title()
951 long = self.long_title()
952 if short and long:
953 title = short + " - " + long
954 elif short:
955 title = short
956 elif long:
957 title = long
958 else:
959 title = "Untitled"
960 icon = short or long or title
961 if not self.get_saved():
962 title = "*%s*" % title
963 icon = "*%s" % icon
964 self.top.wm_title(title)
965 self.top.wm_iconname(icon)
966
967 def get_saved(self):
968 return self.undo.get_saved()
969
970 def set_saved(self, flag):
971 self.undo.set_saved(flag)
972
973 def reset_undo(self):
974 self.undo.reset_undo()
975
976 def short_title(self):
977 filename = self.io.filename
978 if filename:
979 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000980 # return unicode string to display non-ASCII chars correctly
981 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000982
983 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000984 # return unicode string to display non-ASCII chars correctly
985 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000986
987 def center_insert_event(self, event):
988 self.center()
989
990 def center(self, mark="insert"):
991 text = self.text
992 top, bot = self.getwindowlines()
993 lineno = self.getlineno(mark)
994 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000995 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000996 text.yview(float(newtop))
997
998 def getwindowlines(self):
999 text = self.text
1000 top = self.getlineno("@0,0")
1001 bot = self.getlineno("@0,65535")
1002 if top == bot and text.winfo_height() == 1:
1003 # Geometry manager hasn't run yet
1004 height = int(text['height'])
1005 bot = top + height - 1
1006 return top, bot
1007
1008 def getlineno(self, mark="insert"):
1009 text = self.text
1010 return int(float(text.index(mark)))
1011
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001012 def get_geometry(self):
1013 "Return (width, height, x, y)"
1014 geom = self.top.wm_geometry()
1015 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +00001016 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001017
David Scherer7aced172000-08-15 01:13:23 +00001018 def close_event(self, event):
1019 self.close()
1020
1021 def maybesave(self):
1022 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001023 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001024 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001025 self.top.deiconify()
1026 self.top.lower()
1027 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001028 return self.io.maybesave()
1029
1030 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001031 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +00001032 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001033 self._close()
1034 return reply
1035
1036 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001037 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001038 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001039 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001040 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001041 self.io.close()
1042 self.io = None
1043 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001044 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001045 self.color.close(False)
1046 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001047 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001048 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001049 self.per.close()
1050 self.per = None
1051 self.top.destroy()
1052 if self.close_hook:
1053 # unless override: unregister from flist, terminate if last window
1054 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001055
1056 def load_extensions(self):
1057 self.extensions = {}
1058 self.load_standard_extensions()
1059
1060 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001061 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001062 if hasattr(ins, "close"):
1063 ins.close()
1064 self.extensions = {}
1065
1066 def load_standard_extensions(self):
1067 for name in self.get_standard_extension_names():
1068 try:
1069 self.load_extension(name)
1070 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001071 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001072 traceback.print_exc()
1073
1074 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001075 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001076
1077 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001078 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001079 try:
1080 mod = importlib.import_module('.' + name, package=__package__)
1081 except ImportError:
1082 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001083 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001084 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001085 raise
David Scherer7aced172000-08-15 01:13:23 +00001086 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001087 keydefs = idleConf.GetExtensionBindings(name)
1088 if hasattr(cls, "menudefs"):
1089 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001090 ins = cls(self)
1091 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001092 if keydefs:
1093 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001094 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001095 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001096 while methodname[:1] == '<':
1097 methodname = methodname[1:]
1098 while methodname[-1:] == '>':
1099 methodname = methodname[:-1]
1100 methodname = methodname + "_event"
1101 if hasattr(ins, methodname):
1102 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001103
1104 def apply_bindings(self, keydefs=None):
1105 if keydefs is None:
1106 keydefs = self.Bindings.default_keydefs
1107 text = self.text
1108 text.keydefs = keydefs
1109 for event, keylist in keydefs.items():
1110 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001111 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001112
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001113 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001114 """Add appropriate entries to the menus and submenus
1115
1116 Menus that are absent or None in self.menudict are ignored.
1117 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001118 if menudefs is None:
1119 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001120 if keydefs is None:
1121 keydefs = self.Bindings.default_keydefs
1122 menudict = self.menudict
1123 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001124 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001125 menu = menudict.get(mname)
1126 if not menu:
1127 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001128 for entry in entrylist:
1129 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001130 menu.add_separator()
1131 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001132 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001133 checkbutton = (label[:1] == '!')
1134 if checkbutton:
1135 label = label[1:]
1136 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001137 accelerator = get_accelerator(keydefs, eventname)
1138 def command(text=text, eventname=eventname):
1139 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001140 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001141 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001142 menu.add_checkbutton(label=label, underline=underline,
1143 command=command, accelerator=accelerator,
1144 variable=var)
1145 else:
1146 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001147 command=command,
1148 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001149
1150 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001151 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001152 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001153 value = var.get()
1154 return value
1155 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001156 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001157
1158 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001159 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001160 if var:
1161 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001162 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001163 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001164
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001165 def get_var_obj(self, name, vartype=None):
1166 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001167 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001168 # create a Tkinter variable object with self.text as master:
1169 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001170 return var
1171
1172 # Tk implementations of "virtual text methods" -- each platform
1173 # reusing IDLE's support code needs to define these for its GUI's
1174 # flavor of widget.
1175
1176 # Is character at text_index in a Python string? Return 0 for
1177 # "guaranteed no", true for anything else. This info is expensive
1178 # to compute ab initio, but is probably already known by the
1179 # platform's colorizer.
1180
1181 def is_char_in_string(self, text_index):
1182 if self.color:
1183 # Return true iff colorizer hasn't (re)gotten this far
1184 # yet, or the character is tagged as being in a string
1185 return self.text.tag_prevrange("TODO", text_index) or \
1186 "STRING" in self.text.tag_names(text_index)
1187 else:
1188 # The colorizer is missing: assume the worst
1189 return 1
1190
1191 # If a selection is defined in the text widget, return (start,
1192 # end) as Tkinter text indices, otherwise return (None, None)
1193 def get_selection_indices(self):
1194 try:
1195 first = self.text.index("sel.first")
1196 last = self.text.index("sel.last")
1197 return first, last
1198 except TclError:
1199 return None, None
1200
1201 # Return the text widget's current view of what a tab stop means
1202 # (equivalent width in spaces).
1203
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001204 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001205 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1206 return int(current)
1207
1208 # Set the text widget's current view of what a tab stop means.
1209
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001210 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001211 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001212 if self.get_tk_tabwidth() != newtabwidth:
1213 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001214 pixels = text.tk.call("font", "measure", text["font"],
1215 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001216 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001217 text.configure(tabs=pixels)
1218
Guido van Rossum33d26892007-08-05 15:29:28 +00001219### begin autoindent code ### (configuration was moved to beginning of class)
1220
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001221 def set_indentation_params(self, is_py_src, guess=True):
1222 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001223 i = self.guess_indent()
1224 if 2 <= i <= 8:
1225 self.indentwidth = i
1226 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001227 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001228 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001229
1230 def smart_backspace_event(self, event):
1231 text = self.text
1232 first, last = self.get_selection_indices()
1233 if first and last:
1234 text.delete(first, last)
1235 text.mark_set("insert", first)
1236 return "break"
1237 # Delete whitespace left, until hitting a real char or closest
1238 # preceding virtual tab stop.
1239 chars = text.get("insert linestart", "insert")
1240 if chars == '':
1241 if text.compare("insert", ">", "1.0"):
1242 # easy: delete preceding newline
1243 text.delete("insert-1c")
1244 else:
1245 text.bell() # at start of buffer
1246 return "break"
1247 if chars[-1] not in " \t":
1248 # easy: delete preceding real char
1249 text.delete("insert-1c")
1250 return "break"
1251 # Ick. It may require *inserting* spaces if we back up over a
1252 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001253 tabwidth = self.tabwidth
1254 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001255 assert have > 0
1256 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001257 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001258 if self.context_use_ps1:
1259 last_line_of_prompt = sys.ps1.split('\n')[-1]
1260 else:
1261 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001262 ncharsdeleted = 0
1263 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001264 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001265 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001266 chars = chars[:-1]
1267 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001268 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001269 if have <= want or chars[-1] not in " \t":
1270 break
1271 text.undo_block_start()
1272 text.delete("insert-%dc" % ncharsdeleted, "insert")
1273 if have < want:
1274 text.insert("insert", ' ' * (want - have))
1275 text.undo_block_stop()
1276 return "break"
1277
1278 def smart_indent_event(self, event):
1279 # if intraline selection:
1280 # delete it
1281 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001282 # do indent-region
1283 # else:
1284 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001285 text = self.text
1286 first, last = self.get_selection_indices()
1287 text.undo_block_start()
1288 try:
1289 if first and last:
1290 if index2line(first) != index2line(last):
1291 return self.indent_region_event(event)
1292 text.delete(first, last)
1293 text.mark_set("insert", first)
1294 prefix = text.get("insert linestart", "insert")
1295 raw, effective = classifyws(prefix, self.tabwidth)
1296 if raw == len(prefix):
1297 # only whitespace to the left
1298 self.reindent_to(effective + self.indentwidth)
1299 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001300 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001301 if self.usetabs:
1302 pad = '\t'
1303 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001304 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001305 n = self.indentwidth
1306 pad = ' ' * (n - effective % n)
1307 text.insert("insert", pad)
1308 text.see("insert")
1309 return "break"
1310 finally:
1311 text.undo_block_stop()
1312
1313 def newline_and_indent_event(self, event):
1314 text = self.text
1315 first, last = self.get_selection_indices()
1316 text.undo_block_start()
1317 try:
1318 if first and last:
1319 text.delete(first, last)
1320 text.mark_set("insert", first)
1321 line = text.get("insert linestart", "insert")
1322 i, n = 0, len(line)
1323 while i < n and line[i] in " \t":
1324 i = i+1
1325 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001326 # the cursor is in or at leading indentation in a continuation
1327 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001328 text.insert("insert linestart", '\n')
1329 return "break"
1330 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001331 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001332 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001333 last_line_of_prompt = sys.ps1.split('\n')[-1]
1334 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001335 line = line[:-1]
1336 i = i+1
1337 if i:
1338 text.delete("insert - %d chars" % i, "insert")
1339 # strip whitespace after insert point
1340 while text.get("insert") in " \t":
1341 text.delete("insert")
1342 # start new line
1343 text.insert("insert", '\n')
1344
1345 # adjust indentation for continuations and block
1346 # open/close first need to find the last stmt
1347 lno = index2line(text.index('insert'))
1348 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001349 if not self.context_use_ps1:
1350 for context in self.num_context_lines:
1351 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001352 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001353 rawtext = text.get(startatindex, "insert")
1354 y.set_str(rawtext)
1355 bod = y.find_good_parse_start(
1356 self.context_use_ps1,
1357 self._build_char_in_string_func(startatindex))
1358 if bod is not None or startat == 1:
1359 break
1360 y.set_lo(bod or 0)
1361 else:
1362 r = text.tag_prevrange("console", "insert")
1363 if r:
1364 startatindex = r[1]
1365 else:
1366 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001367 rawtext = text.get(startatindex, "insert")
1368 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001369 y.set_lo(0)
1370
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001371 c = y.get_continuation_type()
1372 if c != PyParse.C_NONE:
1373 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001374 if c == PyParse.C_STRING_FIRST_LINE:
1375 # after the first line of a string; do not indent at all
1376 pass
1377 elif c == PyParse.C_STRING_NEXT_LINES:
1378 # inside a string which started before this line;
1379 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001380 text.insert("insert", indent)
1381 elif c == PyParse.C_BRACKET:
1382 # line up with the first (if any) element of the
1383 # last open bracket structure; else indent one
1384 # level beyond the indent of the line with the
1385 # last open bracket
1386 self.reindent_to(y.compute_bracket_indent())
1387 elif c == PyParse.C_BACKSLASH:
1388 # if more than one line in this stmt already, just
1389 # mimic the current indent; else if initial line
1390 # has a start on an assignment stmt, indent to
1391 # beyond leftmost =; else to beyond first chunk of
1392 # non-whitespace on initial line
1393 if y.get_num_lines_in_stmt() > 1:
1394 text.insert("insert", indent)
1395 else:
1396 self.reindent_to(y.compute_backslash_indent())
1397 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001398 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001399 return "break"
1400
1401 # This line starts a brand new stmt; indent relative to
1402 # indentation of initial line of closest preceding
1403 # interesting stmt.
1404 indent = y.get_base_indent_string()
1405 text.insert("insert", indent)
1406 if y.is_block_opener():
1407 self.smart_indent_event(event)
1408 elif indent and y.is_block_closer():
1409 self.smart_backspace_event(event)
1410 return "break"
1411 finally:
1412 text.see("insert")
1413 text.undo_block_stop()
1414
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001415 # Our editwin provides a is_char_in_string function that works
1416 # with a Tk text index, but PyParse only knows about offsets into
1417 # a string. This builds a function for PyParse that accepts an
1418 # offset.
1419
1420 def _build_char_in_string_func(self, startindex):
1421 def inner(offset, _startindex=startindex,
1422 _icis=self.is_char_in_string):
1423 return _icis(_startindex + "+%dc" % offset)
1424 return inner
1425
1426 def indent_region_event(self, event):
1427 head, tail, chars, lines = self.get_region()
1428 for pos in range(len(lines)):
1429 line = lines[pos]
1430 if line:
1431 raw, effective = classifyws(line, self.tabwidth)
1432 effective = effective + self.indentwidth
1433 lines[pos] = self._make_blanks(effective) + line[raw:]
1434 self.set_region(head, tail, chars, lines)
1435 return "break"
1436
1437 def dedent_region_event(self, event):
1438 head, tail, chars, lines = self.get_region()
1439 for pos in range(len(lines)):
1440 line = lines[pos]
1441 if line:
1442 raw, effective = classifyws(line, self.tabwidth)
1443 effective = max(effective - self.indentwidth, 0)
1444 lines[pos] = self._make_blanks(effective) + line[raw:]
1445 self.set_region(head, tail, chars, lines)
1446 return "break"
1447
1448 def comment_region_event(self, event):
1449 head, tail, chars, lines = self.get_region()
1450 for pos in range(len(lines) - 1):
1451 line = lines[pos]
1452 lines[pos] = '##' + line
1453 self.set_region(head, tail, chars, lines)
1454
1455 def uncomment_region_event(self, event):
1456 head, tail, chars, lines = self.get_region()
1457 for pos in range(len(lines)):
1458 line = lines[pos]
1459 if not line:
1460 continue
1461 if line[:2] == '##':
1462 line = line[2:]
1463 elif line[:1] == '#':
1464 line = line[1:]
1465 lines[pos] = line
1466 self.set_region(head, tail, chars, lines)
1467
1468 def tabify_region_event(self, event):
1469 head, tail, chars, lines = self.get_region()
1470 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001471 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001472 for pos in range(len(lines)):
1473 line = lines[pos]
1474 if line:
1475 raw, effective = classifyws(line, tabwidth)
1476 ntabs, nspaces = divmod(effective, tabwidth)
1477 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1478 self.set_region(head, tail, chars, lines)
1479
1480 def untabify_region_event(self, event):
1481 head, tail, chars, lines = self.get_region()
1482 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001483 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001484 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001485 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001486 self.set_region(head, tail, chars, lines)
1487
1488 def toggle_tabs_event(self, event):
1489 if self.askyesno(
1490 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001491 "Turn tabs " + ("on", "off")[self.usetabs] +
1492 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001493 ("will be", "remains at")[self.usetabs] + " 8." +
1494 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001495 parent=self.text):
1496 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001497 # Try to prevent inconsistent indentation.
1498 # User must change indent width manually after using tabs.
1499 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001500 return "break"
1501
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001502 # XXX this isn't bound to anything -- see tabwidth comments
1503## def change_tabwidth_event(self, event):
1504## new = self._asktabwidth()
1505## if new != self.tabwidth:
1506## self.tabwidth = new
1507## self.set_indentation_params(0, guess=0)
1508## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001509
1510 def change_indentwidth_event(self, event):
1511 new = self.askinteger(
1512 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001513 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001514 parent=self.text,
1515 initialvalue=self.indentwidth,
1516 minvalue=2,
1517 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001518 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001519 self.indentwidth = new
1520 return "break"
1521
1522 def get_region(self):
1523 text = self.text
1524 first, last = self.get_selection_indices()
1525 if first and last:
1526 head = text.index(first + " linestart")
1527 tail = text.index(last + "-1c lineend +1c")
1528 else:
1529 head = text.index("insert linestart")
1530 tail = text.index("insert lineend +1c")
1531 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001532 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001533 return head, tail, chars, lines
1534
1535 def set_region(self, head, tail, chars, lines):
1536 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001537 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001538 if newchars == chars:
1539 text.bell()
1540 return
1541 text.tag_remove("sel", "1.0", "end")
1542 text.mark_set("insert", head)
1543 text.undo_block_start()
1544 text.delete(head, tail)
1545 text.insert(head, newchars)
1546 text.undo_block_stop()
1547 text.tag_add("sel", head, "insert")
1548
1549 # Make string that displays as n leading blanks.
1550
1551 def _make_blanks(self, n):
1552 if self.usetabs:
1553 ntabs, nspaces = divmod(n, self.tabwidth)
1554 return '\t' * ntabs + ' ' * nspaces
1555 else:
1556 return ' ' * n
1557
1558 # Delete from beginning of line to insert point, then reinsert
1559 # column logical (meaning use tabs if appropriate) spaces.
1560
1561 def reindent_to(self, column):
1562 text = self.text
1563 text.undo_block_start()
1564 if text.compare("insert linestart", "!=", "insert"):
1565 text.delete("insert linestart", "insert")
1566 if column:
1567 text.insert("insert", self._make_blanks(column))
1568 text.undo_block_stop()
1569
1570 def _asktabwidth(self):
1571 return self.askinteger(
1572 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001573 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001574 parent=self.text,
1575 initialvalue=self.indentwidth,
1576 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001577 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001578
1579 # Guess indentwidth from text content.
1580 # Return guessed indentwidth. This should not be believed unless
1581 # it's in a reasonable range (e.g., it will be 0 if no indented
1582 # blocks are found).
1583
1584 def guess_indent(self):
1585 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1586 if opener and indented:
1587 raw, indentsmall = classifyws(opener, self.tabwidth)
1588 raw, indentlarge = classifyws(indented, self.tabwidth)
1589 else:
1590 indentsmall = indentlarge = 0
1591 return indentlarge - indentsmall
1592
1593# "line.col" -> line, as an int
1594def index2line(index):
1595 return int(float(index))
1596
1597# Look at the leading whitespace in s.
1598# Return pair (# of leading ws characters,
1599# effective # of leading blanks after expanding
1600# tabs to width tabwidth)
1601
1602def classifyws(s, tabwidth):
1603 raw = effective = 0
1604 for ch in s:
1605 if ch == ' ':
1606 raw = raw + 1
1607 effective = effective + 1
1608 elif ch == '\t':
1609 raw = raw + 1
1610 effective = (effective // tabwidth + 1) * tabwidth
1611 else:
1612 break
1613 return raw, effective
1614
1615import tokenize
1616_tokenize = tokenize
1617del tokenize
1618
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001619class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001620
1621 # .run() chews over the Text widget, looking for a block opener
1622 # and the stmt following it. Returns a pair,
1623 # (line containing block opener, line containing stmt)
1624 # Either or both may be None.
1625
1626 def __init__(self, text, tabwidth):
1627 self.text = text
1628 self.tabwidth = tabwidth
1629 self.i = self.finished = 0
1630 self.blkopenline = self.indentedline = None
1631
1632 def readline(self):
1633 if self.finished:
1634 return ""
1635 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001636 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001637 if self.text.compare(mark, ">=", "end"):
1638 return ""
1639 return self.text.get(mark, mark + " lineend+1c")
1640
1641 def tokeneater(self, type, token, start, end, line,
1642 INDENT=_tokenize.INDENT,
1643 NAME=_tokenize.NAME,
1644 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1645 if self.finished:
1646 pass
1647 elif type == NAME and token in OPENERS:
1648 self.blkopenline = line
1649 elif type == INDENT and self.blkopenline:
1650 self.indentedline = line
1651 self.finished = 1
1652
1653 def run(self):
1654 save_tabsize = _tokenize.tabsize
1655 _tokenize.tabsize = self.tabwidth
1656 try:
1657 try:
Trent Nelson428de652008-03-18 22:41:35 +00001658 tokens = _tokenize.generate_tokens(self.readline)
1659 for token in tokens:
1660 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001661 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001662 # since we cut off the tokenizer early, we can trigger
1663 # spurious errors
1664 pass
1665 finally:
1666 _tokenize.tabsize = save_tabsize
1667 return self.blkopenline, self.indentedline
1668
1669### end autoindent code ###
1670
David Scherer7aced172000-08-15 01:13:23 +00001671def prepstr(s):
1672 # Helper to extract the underscore from a string, e.g.
1673 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001674 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001675 if i >= 0:
1676 s = s[:i] + s[i+1:]
1677 return i, s
1678
1679
1680keynames = {
1681 'bracketleft': '[',
1682 'bracketright': ']',
1683 'slash': '/',
1684}
1685
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001686def get_accelerator(keydefs, eventname):
1687 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001688 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1689 # if not keylist:
1690 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1691 "<<open-module>>",
1692 "<<goto-line>>",
1693 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001694 return ""
1695 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001696 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001697 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1698 s = re.sub("Key-", "", s)
1699 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1700 s = re.sub("Control-", "Ctrl-", s)
1701 s = re.sub("-", "+", s)
1702 s = re.sub("><", " ", s)
1703 s = re.sub("<", "", s)
1704 s = re.sub(">", "", s)
1705 return s
1706
1707
1708def fixwordbreaks(root):
1709 # Make sure that Tk's double-click and next/previous word
1710 # operations use our definition of a word (i.e. an identifier)
1711 tk = root.tk
1712 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1713 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1714 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1715
1716
1717def test():
1718 root = Tk()
1719 fixwordbreaks(root)
1720 root.withdraw()
1721 if sys.argv[1:]:
1722 filename = sys.argv[1]
1723 else:
1724 filename = None
1725 edit = EditorWindow(root=root, filename=filename)
1726 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001727 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001728 root.mainloop()
1729 root.destroy()
1730
1731if __name__ == '__main__':
1732 test()