blob: 3c1816a1ee542756bc23bca588522aed2faaec98 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
Terry Jan Reedy59243652014-01-23 00:36:37 -05003from platform import python_version
David Scherer7aced172000-08-15 01:13:23 +00004import re
5import imp
Georg Brandl6634bf22008-05-20 07:13:37 +00006from Tkinter import *
7import tkSimpleDialog
8import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00009import webbrowser
Florent Xiclunad630c042010-04-02 07:24:52 +000010
11from idlelib.MultiCall import MultiCallCreator
12from idlelib import idlever
13from idlelib import WindowList
14from idlelib import SearchDialog
15from idlelib import GrepDialog
16from idlelib import ReplaceDialog
17from idlelib import PyParse
18from idlelib.configHandler import idleConf
19from idlelib import aboutDialog, textView, configDialog
20from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000021
22# The default tab setting for a Text widget, in average-width characters.
23TK_TABWIDTH_DEFAULT = 8
24
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000025def _sphinx_version():
26 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
27 major, minor, micro, level, serial = sys.version_info
28 release = '%s%s' % (major, minor)
29 if micro:
Benjamin Petersonfb234632009-06-13 15:42:23 +000030 release += '%s' % (micro,)
31 if level == 'candidate':
32 release += 'rc%s' % (serial,)
33 elif level != 'final':
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000034 release += '%s%s' % (level[0], serial)
35 return release
36
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000037def _find_module(fullname, path=None):
38 """Version of imp.find_module() that handles hierarchical module names"""
39
40 file = None
41 for tgt in fullname.split('.'):
42 if file is not None:
43 file.close() # close intermediate files
44 (file, filename, descr) = imp.find_module(tgt, path)
45 if descr[2] == imp.PY_SOURCE:
46 break # find but not load the source file
47 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000048 try:
49 path = module.__path__
50 except AttributeError:
51 raise ImportError, 'No source for module ' + module.__name__
Raymond Hettinger179816d2011-04-12 18:54:46 -070052 if descr[2] != imp.PY_SOURCE:
53 # If all of the above fails and didn't raise an exception,fallback
54 # to a straight import which can find __init__.py in a package.
55 m = __import__(fullname)
56 try:
57 filename = m.__file__
58 except AttributeError:
59 pass
60 else:
61 file = None
62 base, ext = os.path.splitext(filename)
63 if ext == '.pyc':
64 ext = '.py'
65 filename = base + ext
66 descr = filename, None, imp.PY_SOURCE
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000067 return file, filename, descr
68
Terry Jan Reedyadb87e22012-02-05 15:10:55 -050069
70class HelpDialog(object):
71
72 def __init__(self):
73 self.parent = None # parent of help window
74 self.dlg = None # the help window iteself
75
76 def display(self, parent, near=None):
77 """ Display the help dialog.
78
79 parent - parent widget for the help window
80
81 near - a Toplevel widget (e.g. EditorWindow or PyShell)
82 to use as a reference for placing the help window
83 """
84 if self.dlg is None:
85 self.show_dialog(parent)
86 if near:
87 self.nearwindow(near)
88
89 def show_dialog(self, parent):
90 self.parent = parent
91 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
92 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
93 dlg.bind('<Destroy>', self.destroy, '+')
94
95 def nearwindow(self, near):
96 # Place the help dialog near the window specified by parent.
97 # Note - this may not reposition the window in Metacity
98 # if "/apps/metacity/general/disable_workarounds" is enabled
99 dlg = self.dlg
100 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
101 dlg.withdraw()
102 dlg.geometry("=+%d+%d" % geom)
103 dlg.deiconify()
104 dlg.lift()
105
106 def destroy(self, ev=None):
107 self.dlg = None
108 self.parent = None
109
110helpDialog = HelpDialog() # singleton instance
111
112
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +0000113class EditorWindow(object):
Florent Xiclunad630c042010-04-02 07:24:52 +0000114 from idlelib.Percolator import Percolator
115 from idlelib.ColorDelegator import ColorDelegator
116 from idlelib.UndoDelegator import UndoDelegator
117 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
118 from idlelib import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +0000119 from Tkinter import Toplevel
Florent Xiclunad630c042010-04-02 07:24:52 +0000120 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +0000121
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000122 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000123
124 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000125 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +0000126 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000127 if sys.platform.count('linux'):
128 # look for html docs in a couple of standard places
129 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
130 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
131 dochome = '/var/www/html/python/index.html'
132 else:
133 basepath = '/usr/share/doc/' # standard location
134 dochome = os.path.join(basepath, pyver,
135 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000136 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +0000137 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +0000138 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000139 if os.path.isfile(chmfile):
140 dochome = chmfile
Ronald Oussoren19302d92006-06-11 14:33:36 +0000141 elif macosxSupport.runningAsOSXApp():
142 # documentation is stored inside the python framework
143 dochome = os.path.join(sys.prefix,
144 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000145 dochome = os.path.normpath(dochome)
146 if os.path.isfile(dochome):
147 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +0000148 if sys.platform == 'darwin':
149 # Safari requires real file:-URLs
150 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000151 else:
Raymond Hettinger7b4c2be2009-01-20 10:46:23 +0000152 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000153 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +0000154 self.flist = flist
155 root = root or flist.root
156 self.root = root
Kurt B. Kaiserb3c4d162006-07-24 17:13:23 +0000157 try:
158 sys.ps1
159 except AttributeError:
160 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000161 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000162 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000163 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000164 self.tkinter_vars = flist.vars
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200165 #self.top.instance_dict makes flist.inversedict available to
166 #configDialog.py so it can access all EditorWindow instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000167 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000168 else:
169 self.tkinter_vars = {} # keys: Tkinter event names
170 # values: Tkinter variable instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000171 self.top.instance_dict = {}
172 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000173 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000174 self.text_frame = text_frame = Frame(top)
Martin v. Löwis4ebbefe2006-11-22 08:50:02 +0000175 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200176 self.width = idleConf.GetOption('main','EditorWindow','width', type='int')
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000177 text_options = {
178 'name': 'text',
179 'padx': 5,
180 'wrap': 'none',
181 'width': self.width,
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200182 'height': idleConf.GetOption('main', 'EditorWindow', 'height', type='int')}
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000183 if TkVersion >= 8.5:
184 # Starting with tk 8.5 we have to set the new tabstyle option
185 # to 'wordprocessor' to achieve the same display of tabs as in
186 # older tk versions.
187 text_options['tabstyle'] = 'wordprocessor'
188 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000189 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000190
191 self.createmenubar()
192 self.apply_bindings()
193
194 self.top.protocol("WM_DELETE_WINDOW", self.close)
195 self.top.bind("<<close-window>>", self.close_event)
Ronald Oussoren17db4952006-07-23 09:41:09 +0000196 if macosxSupport.runningAsOSXApp():
197 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000198 text.bind('<<close-window>>', self.close_event)
R. David Murray3f752ab2010-12-18 17:22:18 +0000199 # Some OS X systems have only one mouse button,
200 # so use control-click for pulldown menus there.
201 # (Note, AquaTk defines <2> as the right button if
202 # present and the Tk Text widget already binds <2>.)
203 text.bind("<Control-Button-1>",self.right_menu_event)
204 else:
205 # Elsewhere, use right-click for pulldown menus.
206 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000207 text.bind("<<cut>>", self.cut)
208 text.bind("<<copy>>", self.copy)
209 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000210 text.bind("<<center-insert>>", self.center_insert_event)
211 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000212 text.bind("<<python-docs>>", self.python_docs)
213 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000214 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000215 text.bind("<<open-module>>", self.open_module)
216 text.bind("<<do-nothing>>", lambda event: "break")
217 text.bind("<<select-all>>", self.select_all)
218 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000219 text.bind("<<find>>", self.find_event)
220 text.bind("<<find-again>>", self.find_again_event)
221 text.bind("<<find-in-files>>", self.find_in_files_event)
222 text.bind("<<find-selection>>", self.find_selection_event)
223 text.bind("<<replace>>", self.replace_event)
224 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000225 text.bind("<<smart-backspace>>",self.smart_backspace_event)
226 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
227 text.bind("<<smart-indent>>",self.smart_indent_event)
228 text.bind("<<indent-region>>",self.indent_region_event)
229 text.bind("<<dedent-region>>",self.dedent_region_event)
230 text.bind("<<comment-region>>",self.comment_region_event)
231 text.bind("<<uncomment-region>>",self.uncomment_region_event)
232 text.bind("<<tabify-region>>",self.tabify_region_event)
233 text.bind("<<untabify-region>>",self.untabify_region_event)
234 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
235 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000236 text.bind("<Left>", self.move_at_edge_if_selection(0))
237 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000238 text.bind("<<del-word-left>>", self.del_word_left)
239 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000240 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000241
David Scherer7aced172000-08-15 01:13:23 +0000242 if flist:
243 flist.inversedict[self] = key
244 if key:
245 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000246 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000247 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
248 text.bind("<<open-class-browser>>", self.open_class_browser)
249 text.bind("<<open-path-browser>>", self.open_path_browser)
250
Steven M. Gava898a3652001-10-07 11:10:44 +0000251 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000252 vbar['command'] = text.yview
253 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000254 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000255 fontWeight = 'normal'
256 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000257 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000258 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200259 idleConf.GetOption('main', 'EditorWindow',
260 'font-size', type='int'),
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000261 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000262 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
263 text.pack(side=TOP, fill=BOTH, expand=1)
264 text.focus_set()
265
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000266 # usetabs true -> literal tab characters are used by indent and
267 # dedent cmds, possibly mixed with spaces if
268 # indentwidth is not a multiple of tabwidth,
269 # which will cause Tabnanny to nag!
270 # false -> tab characters are converted to spaces by indent
271 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000272 # Although use-spaces=0 can be configured manually in config-main.def,
273 # configuration of tabs v. spaces is not supported in the configuration
274 # dialog. IDLE promotes the preferred Python indentation: use spaces!
275 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
276 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
301
David Scherer7aced172000-08-15 01:13:23 +0000302 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000303
304 self.undo = undo = self.UndoDelegator()
305 per.insertfilter(undo)
306 text.undo_block_start = undo.undo_block_start
307 text.undo_block_stop = undo.undo_block_stop
308 undo.set_saved_change_hook(self.saved_change_hook)
309
310 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000311 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000312 io.set_filename_change_hook(self.filename_change_hook)
313
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000314 # Create the recent files submenu
315 self.recent_files_menu = Menu(self.menubar)
316 self.menudict['file'].insert_cascade(3, label='Recent Files',
317 underline=0,
318 menu=self.recent_files_menu)
319 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000320
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000321 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000322 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000323 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000324 io.loadfile(filename)
325 else:
326 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000327 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000328 self.saved_change_hook()
329
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000330 self.set_indentation_params(self.ispythonsource(filename))
331
David Scherer7aced172000-08-15 01:13:23 +0000332 self.load_extensions()
333
334 menu = self.menudict.get('windows')
335 if menu:
336 end = menu.index("end")
337 if end is None:
338 end = -1
339 if end >= 0:
340 menu.add_separator()
341 end = end + 1
342 self.wmenu_end = end
343 WindowList.register_callback(self.postwindowsmenu)
344
345 # Some abstractions so IDLE extensions are cross-IDE
346 self.askyesno = tkMessageBox.askyesno
347 self.askinteger = tkSimpleDialog.askinteger
348 self.showerror = tkMessageBox.showerror
349
Roger Serwy02c0ed02013-05-20 22:13:39 -0500350 self._highlight_workaround() # Fix selection tags on Windows
351
352 def _highlight_workaround(self):
353 # On Windows, Tk removes painting of the selection
354 # tags which is different behavior than on Linux and Mac.
355 # See issue14146 for more information.
356 if not sys.platform.startswith('win'):
357 return
358
359 text = self.text
360 text.event_add("<<Highlight-FocusOut>>", "<FocusOut>")
361 text.event_add("<<Highlight-FocusIn>>", "<FocusIn>")
362 def highlight_fix(focus):
363 sel_range = text.tag_ranges("sel")
364 if sel_range:
365 if focus == 'out':
366 HILITE_CONFIG = idleConf.GetHighlight(
367 idleConf.CurrentTheme(), 'hilite')
368 text.tag_config("sel_fix", HILITE_CONFIG)
369 text.tag_raise("sel_fix")
370 text.tag_add("sel_fix", *sel_range)
371 elif focus == 'in':
372 text.tag_remove("sel_fix", "1.0", "end")
373
374 text.bind("<<Highlight-FocusOut>>",
375 lambda ev: highlight_fix("out"))
376 text.bind("<<Highlight-FocusIn>>",
377 lambda ev: highlight_fix("in"))
378
379
Martin v. Löwis307021f2005-11-27 16:59:04 +0000380 def _filename_to_unicode(self, filename):
381 """convert filename to unicode in order to display it in Tk"""
382 if isinstance(filename, unicode) or not filename:
383 return filename
384 else:
385 try:
386 return filename.decode(self.filesystemencoding)
387 except UnicodeDecodeError:
388 # XXX
389 try:
390 return filename.decode(self.encoding)
391 except UnicodeDecodeError:
392 # byte-to-byte conversion
393 return filename.decode('iso8859-1')
394
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000395 def new_callback(self, event):
396 dirname, basename = self.io.defaultfilename()
397 self.flist.new(dirname)
398 return "break"
399
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000400 def home_callback(self, event):
Kurt B. Kaiser020d3d92011-03-17 00:05:38 -0400401 if (event.state & 4) != 0 and event.keysym == "Home":
402 # state&4==Control. If <Control-Home>, use the Tk binding.
403 return
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000404 if self.text.index("iomark") and \
405 self.text.compare("iomark", "<=", "insert lineend") and \
406 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400407 # In Shell on input line, go to just after prompt
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000408 insertpt = int(self.text.index("iomark").split(".")[1])
409 else:
410 line = self.text.get("insert linestart", "insert lineend")
411 for insertpt in xrange(len(line)):
412 if line[insertpt] not in (' ','\t'):
413 break
414 else:
415 insertpt=len(line)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000416 lineat = int(self.text.index("insert").split('.')[1])
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000417 if insertpt == lineat:
418 insertpt = 0
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000419 dest = "insert linestart+"+str(insertpt)+"c"
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000420 if (event.state&1) == 0:
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400421 # shift was not pressed
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000422 self.text.tag_remove("sel", "1.0", "end")
423 else:
424 if not self.text.index("sel.first"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400425 self.text.mark_set("my_anchor", "insert") # there was no previous selection
426 else:
427 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
428 self.text.mark_set("my_anchor", "sel.first") # extend back
429 else:
430 self.text.mark_set("my_anchor", "sel.last") # extend forward
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000431 first = self.text.index(dest)
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400432 last = self.text.index("my_anchor")
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000433 if self.text.compare(first,">",last):
434 first,last = last,first
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000435 self.text.tag_remove("sel", "1.0", "end")
436 self.text.tag_add("sel", first, last)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000437 self.text.mark_set("insert", dest)
438 self.text.see("insert")
439 return "break"
440
David Scherer7aced172000-08-15 01:13:23 +0000441 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000442 self.status_bar = self.MultiStatusBar(self.top)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000443 if macosxSupport.runningAsOSXApp():
444 # Insert some padding to avoid obscuring some of the statusbar
445 # by the resize widget.
446 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000447 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
448 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
449 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000450 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
451 self.text.event_add("<<set-line-and-column>>",
452 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000453 self.text.after_idle(self.set_line_and_column)
454
455 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000456 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000457 self.status_bar.set_label('column', 'Col: %s' % column)
458 self.status_bar.set_label('line', 'Ln: %s' % line)
459
David Scherer7aced172000-08-15 01:13:23 +0000460 menu_specs = [
461 ("file", "_File"),
462 ("edit", "_Edit"),
463 ("format", "F_ormat"),
464 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000465 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000466 ("windows", "_Windows"),
467 ("help", "_Help"),
468 ]
469
Ronald Oussoren19302d92006-06-11 14:33:36 +0000470 if macosxSupport.runningAsOSXApp():
Ronald Oussoren19302d92006-06-11 14:33:36 +0000471 menu_specs[-2] = ("windows", "_Window")
472
473
David Scherer7aced172000-08-15 01:13:23 +0000474 def createmenubar(self):
475 mbar = self.menubar
476 self.menudict = menudict = {}
477 for name, label in self.menu_specs:
478 underline, label = prepstr(label)
479 menudict[name] = menu = Menu(mbar, name=name)
480 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000481
Ned Deily4a705502011-01-18 04:33:22 +0000482 if macosxSupport.isCarbonAquaTk(self.root):
Ronald Oussoren19302d92006-06-11 14:33:36 +0000483 # Insert the application menu
484 menudict['application'] = menu = Menu(mbar, name='apple')
485 mbar.add_cascade(label='IDLE', menu=menu)
486
David Scherer7aced172000-08-15 01:13:23 +0000487 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000488 self.base_helpmenu_length = self.menudict['help'].index(END)
489 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000490
491 def postwindowsmenu(self):
492 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000493 menu = self.menudict['windows']
494 end = menu.index("end")
495 if end is None:
496 end = -1
497 if end > self.wmenu_end:
498 menu.delete(self.wmenu_end+1, end)
499 WindowList.add_windows_to_menu(menu)
500
501 rmenu = None
502
503 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000504 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
505 if not self.rmenu:
506 self.make_rmenu()
507 rmenu = self.rmenu
508 self.event = event
509 iswin = sys.platform[:3] == 'win'
510 if iswin:
511 self.text.config(cursor="arrow")
Andrew Svetlov5018db72012-11-01 22:39:14 +0200512
Roger Serwy231a8fd2013-04-07 12:15:52 -0500513 for item in self.rmenu_specs:
514 try:
515 label, eventname, verify_state = item
516 except ValueError: # see issue1207589
517 continue
518
Andrew Svetlov5018db72012-11-01 22:39:14 +0200519 if verify_state is None:
520 continue
521 state = getattr(self, verify_state)()
522 rmenu.entryconfigure(label, state=state)
523
David Scherer7aced172000-08-15 01:13:23 +0000524 rmenu.tk_popup(event.x_root, event.y_root)
525 if iswin:
526 self.text.config(cursor="ibeam")
527
528 rmenu_specs = [
Andrew Svetlov5018db72012-11-01 22:39:14 +0200529 # ("Label", "<<virtual-event>>", "statefuncname"), ...
530 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000531 ]
532
533 def make_rmenu(self):
534 rmenu = Menu(self.text, tearoff=0)
Roger Serwy231a8fd2013-04-07 12:15:52 -0500535 for item in self.rmenu_specs:
536 label, eventname = item[0], item[1]
Andrew Svetlov5018db72012-11-01 22:39:14 +0200537 if label is not None:
538 def command(text=self.text, eventname=eventname):
539 text.event_generate(eventname)
540 rmenu.add_command(label=label, command=command)
541 else:
542 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000543 self.rmenu = rmenu
544
Andrew Svetlov5018db72012-11-01 22:39:14 +0200545 def rmenu_check_cut(self):
546 return self.rmenu_check_copy()
547
548 def rmenu_check_copy(self):
549 try:
550 indx = self.text.index('sel.first')
551 except TclError:
552 return 'disabled'
553 else:
554 return 'normal' if indx else 'disabled'
555
556 def rmenu_check_paste(self):
557 try:
558 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
559 except TclError:
560 return 'disabled'
561 else:
562 return 'normal'
563
David Scherer7aced172000-08-15 01:13:23 +0000564 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000565 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000566
Steven M. Gava3b55a892001-11-21 05:56:26 +0000567 def config_dialog(self, event=None):
568 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000569
David Scherer7aced172000-08-15 01:13:23 +0000570 def help_dialog(self, event=None):
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500571 if self.root:
572 parent = self.root
573 else:
574 parent = self.top
575 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000576
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000577 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000578 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000579 try:
580 os.startfile(self.help_url)
581 except WindowsError as why:
582 tkMessageBox.showerror(title='Document Start Failure',
583 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000584 else:
585 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000586 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000587
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000588 def cut(self,event):
589 self.text.event_generate("<<Cut>>")
590 return "break"
591
592 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000593 if not self.text.tag_ranges("sel"):
594 # There is no selection, so do nothing and maybe interrupt.
595 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000596 self.text.event_generate("<<Copy>>")
597 return "break"
598
599 def paste(self,event):
600 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000601 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000602 return "break"
603
David Scherer7aced172000-08-15 01:13:23 +0000604 def select_all(self, event=None):
605 self.text.tag_add("sel", "1.0", "end-1c")
606 self.text.mark_set("insert", "1.0")
607 self.text.see("insert")
608 return "break"
609
610 def remove_selection(self, event=None):
611 self.text.tag_remove("sel", "1.0", "end")
612 self.text.see("insert")
613
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000614 def move_at_edge_if_selection(self, edge_index):
615 """Cursor move begins at start or end of selection
616
617 When a left/right cursor key is pressed create and return to Tkinter a
618 function which causes a cursor move from the associated edge of the
619 selection.
620
621 """
622 self_text_index = self.text.index
623 self_text_mark_set = self.text.mark_set
624 edges_table = ("sel.first+1c", "sel.last-1c")
625 def move_at_edge(event):
626 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
627 try:
628 self_text_index("sel.first")
629 self_text_mark_set("insert", edges_table[edge_index])
630 except TclError:
631 pass
632 return move_at_edge
633
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000634 def del_word_left(self, event):
635 self.text.event_generate('<Meta-Delete>')
636 return "break"
637
638 def del_word_right(self, event):
639 self.text.event_generate('<Meta-d>')
640 return "break"
641
Steven M. Gavac5976402002-01-04 03:06:08 +0000642 def find_event(self, event):
643 SearchDialog.find(self.text)
644 return "break"
645
646 def find_again_event(self, event):
647 SearchDialog.find_again(self.text)
648 return "break"
649
650 def find_selection_event(self, event):
651 SearchDialog.find_selection(self.text)
652 return "break"
653
654 def find_in_files_event(self, event):
655 GrepDialog.grep(self.text, self.io, self.flist)
656 return "break"
657
658 def replace_event(self, event):
659 ReplaceDialog.replace(self.text)
660 return "break"
661
662 def goto_line_event(self, event):
663 text = self.text
664 lineno = tkSimpleDialog.askinteger("Goto",
665 "Go to line number:",parent=text)
666 if lineno is None:
667 return "break"
668 if lineno <= 0:
669 text.bell()
670 return "break"
671 text.mark_set("insert", "%d.0" % lineno)
672 text.see("insert")
673
David Scherer7aced172000-08-15 01:13:23 +0000674 def open_module(self, event=None):
675 # XXX Shouldn't this be in IOBinding or in FileList?
676 try:
677 name = self.text.get("sel.first", "sel.last")
678 except TclError:
679 name = ""
680 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000681 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000682 name = tkSimpleDialog.askstring("Module",
683 "Enter the name of a Python module\n"
684 "to search on sys.path and open:",
685 parent=self.text, initialvalue=name)
686 if name:
687 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000688 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000689 return
David Scherer7aced172000-08-15 01:13:23 +0000690 # XXX Ought to insert current file's directory in front of path
691 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000692 (f, file, (suffix, mode, type)) = _find_module(name)
Terry Jan Reedy2b149862013-06-29 00:59:34 -0400693 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000694 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
695 return
696 if type != imp.PY_SOURCE:
697 tkMessageBox.showerror("Unsupported type",
698 "%s is not a source module" % name, parent=self.text)
699 return
700 if f:
701 f.close()
702 if self.flist:
703 self.flist.open(file)
704 else:
705 self.io.loadfile(file)
706
707 def open_class_browser(self, event=None):
708 filename = self.io.filename
709 if not filename:
710 tkMessageBox.showerror(
711 "No filename",
712 "This buffer has no associated filename",
713 master=self.text)
714 self.text.focus_set()
715 return None
716 head, tail = os.path.split(filename)
717 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000718 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000719 ClassBrowser.ClassBrowser(self.flist, base, [head])
720
721 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000722 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000723 PathBrowser.PathBrowser(self.flist)
724
725 def gotoline(self, lineno):
726 if lineno is not None and lineno > 0:
727 self.text.mark_set("insert", "%d.0" % lineno)
728 self.text.tag_remove("sel", "1.0", "end")
729 self.text.tag_add("sel", "insert", "insert +1l")
730 self.center()
731
732 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000733 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000734 return True
David Scherer7aced172000-08-15 01:13:23 +0000735 base, ext = os.path.splitext(os.path.basename(filename))
736 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000737 return True
David Scherer7aced172000-08-15 01:13:23 +0000738 try:
739 f = open(filename)
740 line = f.readline()
741 f.close()
742 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000743 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000744 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000745
746 def close_hook(self):
747 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000748 self.flist.unregister_maybe_terminate(self)
749 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000750
751 def set_close_hook(self, close_hook):
752 self.close_hook = close_hook
753
754 def filename_change_hook(self):
755 if self.flist:
756 self.flist.filename_changed_edit(self)
757 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000758 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000759 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000760
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000761 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000762 if self.color:
763 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000764 if self.ispythonsource(self.io.filename):
765 self.color = self.ColorDelegator()
766 # can add more colorizers here...
767 if self.color:
768 self.per.removefilter(self.undo)
769 self.per.insertfilter(self.color)
770 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000771
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000772 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000773 if not self.color:
774 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000775 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000776 self.per.removefilter(self.color)
777 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000778
Steven M. Gavab77d3432002-03-02 07:16:21 +0000779 def ResetColorizer(self):
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000780 "Update the colour theme"
781 # Called from self.filename_change_hook and from configDialog.py
782 self._rmcolorizer()
783 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000784 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000785 normal_colors = idleConf.GetHighlight(theme, 'normal')
786 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
787 select_colors = idleConf.GetHighlight(theme, 'hilite')
788 self.text.config(
789 foreground=normal_colors['foreground'],
790 background=normal_colors['background'],
791 insertbackground=cursor_color,
792 selectforeground=select_colors['foreground'],
793 selectbackground=select_colors['background'],
794 )
David Scherer7aced172000-08-15 01:13:23 +0000795
Steven M. Gavab1585412002-03-12 00:21:56 +0000796 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000797 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000798 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000799 fontWeight='normal'
800 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
801 fontWeight='bold'
802 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200803 idleConf.GetOption('main','EditorWindow','font-size',
804 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000805 fontWeight))
806
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000807 def RemoveKeybindings(self):
808 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000809 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000810 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000811 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000812 self.text.event_delete(event, *keylist)
813 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 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000817 self.text.event_delete(event, *keylist)
818
819 def ApplyKeybindings(self):
820 "Update the keybindings after they are changed"
821 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000822 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000823 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000824 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000825 xkeydefs = idleConf.GetExtensionBindings(extensionName)
826 if xkeydefs:
827 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000828 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000829 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000830 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000831 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000832 for item in menu[1]:
833 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000834 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000835 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000836 menu = self.menudict[menubarItem]
Ned Deily14ef0c82013-07-20 14:38:24 -0700837 end = menu.index(END)
838 if end is None:
839 # Skip empty menus
840 continue
841 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000842 for index in range(0, end):
843 if menu.type(index) == 'command':
844 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000845 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000846 itemName = menu.entrycget(index, 'label')
847 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000848 if menubarItem in menuEventDict:
849 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000850 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000851 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000852 accel = get_accelerator(keydefs, event)
853 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000854
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000855 def set_notabs_indentwidth(self):
856 "Update the indentwidth if changed and not using tabs in this window"
857 # Called from configDialog.py
858 if not self.usetabs:
859 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
860 type='int')
861
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000862 def reset_help_menu_entries(self):
863 "Update the additional help entries on the Help menu"
864 help_list = idleConf.GetAllExtraHelpSourcesList()
865 helpmenu = self.menudict['help']
866 # first delete the extra help entries, if any
867 helpmenu_length = helpmenu.index(END)
868 if helpmenu_length > self.base_helpmenu_length:
869 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
870 # then rebuild them
871 if help_list:
872 helpmenu.add_separator()
873 for entry in help_list:
874 cmd = self.__extra_help_callback(entry[1])
875 helpmenu.add_command(label=entry[0], command=cmd)
876 # and update the menu dictionary
877 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000878
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000879 def __extra_help_callback(self, helpfile):
880 "Create a callback with the helpfile value frozen at definition time"
881 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000882 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000883 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000884 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000885 try:
886 os.startfile(helpfile)
887 except WindowsError as why:
888 tkMessageBox.showerror(title='Document Start Failure',
889 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000890 else:
891 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000892 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000893
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000894 def update_recent_files_list(self, new_file=None):
895 "Load and update the recent files list and menus"
896 rf_list = []
897 if os.path.exists(self.recent_files_path):
Terry Jan Reedyf9489432013-08-04 15:39:56 -0400898 with open(self.recent_files_path, 'r') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000899 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000900 if new_file:
901 new_file = os.path.abspath(new_file) + '\n'
902 if new_file in rf_list:
903 rf_list.remove(new_file) # move to top
904 rf_list.insert(0, new_file)
905 # clean and save the recent files list
906 bad_paths = []
907 for path in rf_list:
908 if '\0' in path or not os.path.exists(path[0:-1]):
909 bad_paths.append(path)
910 rf_list = [path for path in rf_list if path not in bad_paths]
911 ulchars = "1234567890ABCDEFGHIJK"
912 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000913 try:
Ned Deily40ad0412011-12-14 14:57:43 -0800914 with open(self.recent_files_path, 'w') as rf_file:
915 rf_file.writelines(rf_list)
916 except IOError as err:
917 if not getattr(self.root, "recentfilelist_error_displayed", False):
918 self.root.recentfilelist_error_displayed = True
919 tkMessageBox.showerror(title='IDLE Error',
920 message='Unable to update Recent Files list:\n%s'
921 % str(err),
922 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000923 # for each edit window instance, construct the recent files menu
924 for instance in self.top.instance_dict.keys():
925 menu = instance.recent_files_menu
Ned Deily71489842012-05-29 10:42:34 -0700926 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000927 for i, file_name in enumerate(rf_list):
928 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000929 # make unicode string to display non-ASCII chars correctly
930 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000931 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000932 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000933 command=callback,
934 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000935
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000936 def __recent_file_callback(self, file_name):
937 def open_recent_file(fn_closure=file_name):
938 self.io.open(editFile=fn_closure)
939 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000940
David Scherer7aced172000-08-15 01:13:23 +0000941 def saved_change_hook(self):
942 short = self.short_title()
943 long = self.long_title()
944 if short and long:
945 title = short + " - " + long
946 elif short:
947 title = short
948 elif long:
949 title = long
950 else:
951 title = "Untitled"
952 icon = short or long or title
953 if not self.get_saved():
954 title = "*%s*" % title
955 icon = "*%s" % icon
956 self.top.wm_title(title)
957 self.top.wm_iconname(icon)
958
959 def get_saved(self):
960 return self.undo.get_saved()
961
962 def set_saved(self, flag):
963 self.undo.set_saved(flag)
964
965 def reset_undo(self):
966 self.undo.reset_undo()
967
968 def short_title(self):
Terry Jan Reedy59243652014-01-23 00:36:37 -0500969 pyversion = "Python " + python_version() + ": "
David Scherer7aced172000-08-15 01:13:23 +0000970 filename = self.io.filename
971 if filename:
972 filename = os.path.basename(filename)
Terry Jan Reedy59243652014-01-23 00:36:37 -0500973 else:
974 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000975 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy59243652014-01-23 00:36:37 -0500976 return pyversion + self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000977
978 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000979 # return unicode string to display non-ASCII chars correctly
980 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000981
982 def center_insert_event(self, event):
983 self.center()
984
985 def center(self, mark="insert"):
986 text = self.text
987 top, bot = self.getwindowlines()
988 lineno = self.getlineno(mark)
989 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000990 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000991 text.yview(float(newtop))
992
993 def getwindowlines(self):
994 text = self.text
995 top = self.getlineno("@0,0")
996 bot = self.getlineno("@0,65535")
997 if top == bot and text.winfo_height() == 1:
998 # Geometry manager hasn't run yet
999 height = int(text['height'])
1000 bot = top + height - 1
1001 return top, bot
1002
1003 def getlineno(self, mark="insert"):
1004 text = self.text
1005 return int(float(text.index(mark)))
1006
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001007 def get_geometry(self):
1008 "Return (width, height, x, y)"
1009 geom = self.top.wm_geometry()
1010 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
1011 tuple = (map(int, m.groups()))
1012 return tuple
1013
David Scherer7aced172000-08-15 01:13:23 +00001014 def close_event(self, event):
1015 self.close()
1016
1017 def maybesave(self):
1018 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001019 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001020 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001021 self.top.deiconify()
1022 self.top.lower()
1023 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001024 return self.io.maybesave()
1025
1026 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001027 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +00001028 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001029 self._close()
1030 return reply
1031
1032 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001033 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001034 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001035 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001036 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001037 self.io.close()
1038 self.io = None
1039 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001040 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001041 self.color.close(False)
1042 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001043 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001044 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001045 self.per.close()
1046 self.per = None
1047 self.top.destroy()
1048 if self.close_hook:
1049 # unless override: unregister from flist, terminate if last window
1050 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001051
1052 def load_extensions(self):
1053 self.extensions = {}
1054 self.load_standard_extensions()
1055
1056 def unload_extensions(self):
1057 for ins in self.extensions.values():
1058 if hasattr(ins, "close"):
1059 ins.close()
1060 self.extensions = {}
1061
1062 def load_standard_extensions(self):
1063 for name in self.get_standard_extension_names():
1064 try:
1065 self.load_extension(name)
1066 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001067 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +00001068 import traceback
1069 traceback.print_exc()
1070
1071 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001072 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001073
1074 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001075 try:
1076 mod = __import__(name, globals(), locals(), [])
1077 except ImportError:
1078 print "\nFailed to import extension: ", name
1079 return
David Scherer7aced172000-08-15 01:13:23 +00001080 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001081 keydefs = idleConf.GetExtensionBindings(name)
1082 if hasattr(cls, "menudefs"):
1083 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001084 ins = cls(self)
1085 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001086 if keydefs:
1087 self.apply_bindings(keydefs)
1088 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001089 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001090 while methodname[:1] == '<':
1091 methodname = methodname[1:]
1092 while methodname[-1:] == '>':
1093 methodname = methodname[:-1]
1094 methodname = methodname + "_event"
1095 if hasattr(ins, methodname):
1096 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001097
1098 def apply_bindings(self, keydefs=None):
1099 if keydefs is None:
1100 keydefs = self.Bindings.default_keydefs
1101 text = self.text
1102 text.keydefs = keydefs
1103 for event, keylist in keydefs.items():
1104 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001105 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001106
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001107 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001108 """Add appropriate entries to the menus and submenus
1109
1110 Menus that are absent or None in self.menudict are ignored.
1111 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001112 if menudefs is None:
1113 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001114 if keydefs is None:
1115 keydefs = self.Bindings.default_keydefs
1116 menudict = self.menudict
1117 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001118 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001119 menu = menudict.get(mname)
1120 if not menu:
1121 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001122 for entry in entrylist:
1123 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001124 menu.add_separator()
1125 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001126 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001127 checkbutton = (label[:1] == '!')
1128 if checkbutton:
1129 label = label[1:]
1130 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001131 accelerator = get_accelerator(keydefs, eventname)
1132 def command(text=text, eventname=eventname):
1133 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001134 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001135 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001136 menu.add_checkbutton(label=label, underline=underline,
1137 command=command, accelerator=accelerator,
1138 variable=var)
1139 else:
1140 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001141 command=command,
1142 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001143
1144 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001145 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001146 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001147 value = var.get()
1148 return value
1149 else:
1150 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001151
1152 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001153 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001154 if var:
1155 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001156 else:
1157 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001158
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001159 def get_var_obj(self, name, vartype=None):
1160 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001161 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001162 # create a Tkinter variable object with self.text as master:
1163 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001164 return var
1165
1166 # Tk implementations of "virtual text methods" -- each platform
1167 # reusing IDLE's support code needs to define these for its GUI's
1168 # flavor of widget.
1169
1170 # Is character at text_index in a Python string? Return 0 for
1171 # "guaranteed no", true for anything else. This info is expensive
1172 # to compute ab initio, but is probably already known by the
1173 # platform's colorizer.
1174
1175 def is_char_in_string(self, text_index):
1176 if self.color:
1177 # Return true iff colorizer hasn't (re)gotten this far
1178 # yet, or the character is tagged as being in a string
1179 return self.text.tag_prevrange("TODO", text_index) or \
1180 "STRING" in self.text.tag_names(text_index)
1181 else:
1182 # The colorizer is missing: assume the worst
1183 return 1
1184
1185 # If a selection is defined in the text widget, return (start,
1186 # end) as Tkinter text indices, otherwise return (None, None)
1187 def get_selection_indices(self):
1188 try:
1189 first = self.text.index("sel.first")
1190 last = self.text.index("sel.last")
1191 return first, last
1192 except TclError:
1193 return None, None
1194
1195 # Return the text widget's current view of what a tab stop means
1196 # (equivalent width in spaces).
1197
1198 def get_tabwidth(self):
1199 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1200 return int(current)
1201
1202 # Set the text widget's current view of what a tab stop means.
1203
1204 def set_tabwidth(self, newtabwidth):
1205 text = self.text
1206 if self.get_tabwidth() != newtabwidth:
1207 pixels = text.tk.call("font", "measure", text["font"],
1208 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001209 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001210 text.configure(tabs=pixels)
1211
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001212 # If ispythonsource and guess are true, guess a good value for
1213 # indentwidth based on file content (if possible), and if
1214 # indentwidth != tabwidth set usetabs false.
1215 # In any case, adjust the Text widget's view of what a tab
1216 # character means.
1217
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001218 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001219 if guess and ispythonsource:
1220 i = self.guess_indent()
1221 if 2 <= i <= 8:
1222 self.indentwidth = i
1223 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001224 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001225 self.set_tabwidth(self.tabwidth)
1226
1227 def smart_backspace_event(self, event):
1228 text = self.text
1229 first, last = self.get_selection_indices()
1230 if first and last:
1231 text.delete(first, last)
1232 text.mark_set("insert", first)
1233 return "break"
1234 # Delete whitespace left, until hitting a real char or closest
1235 # preceding virtual tab stop.
1236 chars = text.get("insert linestart", "insert")
1237 if chars == '':
1238 if text.compare("insert", ">", "1.0"):
1239 # easy: delete preceding newline
1240 text.delete("insert-1c")
1241 else:
1242 text.bell() # at start of buffer
1243 return "break"
1244 if chars[-1] not in " \t":
1245 # easy: delete preceding real char
1246 text.delete("insert-1c")
1247 return "break"
1248 # Ick. It may require *inserting* spaces if we back up over a
1249 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001250 tabwidth = self.tabwidth
1251 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001252 assert have > 0
1253 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001254 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001255 if self.context_use_ps1:
1256 last_line_of_prompt = sys.ps1.split('\n')[-1]
1257 else:
1258 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001259 ncharsdeleted = 0
1260 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001261 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001262 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001263 chars = chars[:-1]
1264 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001265 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001266 if have <= want or chars[-1] not in " \t":
1267 break
1268 text.undo_block_start()
1269 text.delete("insert-%dc" % ncharsdeleted, "insert")
1270 if have < want:
1271 text.insert("insert", ' ' * (want - have))
1272 text.undo_block_stop()
1273 return "break"
1274
1275 def smart_indent_event(self, event):
1276 # if intraline selection:
1277 # delete it
1278 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001279 # do indent-region
1280 # else:
1281 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001282 text = self.text
1283 first, last = self.get_selection_indices()
1284 text.undo_block_start()
1285 try:
1286 if first and last:
1287 if index2line(first) != index2line(last):
1288 return self.indent_region_event(event)
1289 text.delete(first, last)
1290 text.mark_set("insert", first)
1291 prefix = text.get("insert linestart", "insert")
1292 raw, effective = classifyws(prefix, self.tabwidth)
1293 if raw == len(prefix):
1294 # only whitespace to the left
1295 self.reindent_to(effective + self.indentwidth)
1296 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001297 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001298 if self.usetabs:
1299 pad = '\t'
1300 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001301 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001302 n = self.indentwidth
1303 pad = ' ' * (n - effective % n)
1304 text.insert("insert", pad)
1305 text.see("insert")
1306 return "break"
1307 finally:
1308 text.undo_block_stop()
1309
1310 def newline_and_indent_event(self, event):
1311 text = self.text
1312 first, last = self.get_selection_indices()
1313 text.undo_block_start()
1314 try:
1315 if first and last:
1316 text.delete(first, last)
1317 text.mark_set("insert", first)
1318 line = text.get("insert linestart", "insert")
1319 i, n = 0, len(line)
1320 while i < n and line[i] in " \t":
1321 i = i+1
1322 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001323 # the cursor is in or at leading indentation in a continuation
1324 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001325 text.insert("insert linestart", '\n')
1326 return "break"
1327 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001328 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001329 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001330 last_line_of_prompt = sys.ps1.split('\n')[-1]
1331 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001332 line = line[:-1]
1333 i = i+1
1334 if i:
1335 text.delete("insert - %d chars" % i, "insert")
1336 # strip whitespace after insert point
1337 while text.get("insert") in " \t":
1338 text.delete("insert")
1339 # start new line
1340 text.insert("insert", '\n')
1341
1342 # adjust indentation for continuations and block
1343 # open/close first need to find the last stmt
1344 lno = index2line(text.index('insert'))
1345 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001346 if not self.context_use_ps1:
1347 for context in self.num_context_lines:
1348 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001349 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001350 rawtext = text.get(startatindex, "insert")
1351 y.set_str(rawtext)
1352 bod = y.find_good_parse_start(
1353 self.context_use_ps1,
1354 self._build_char_in_string_func(startatindex))
1355 if bod is not None or startat == 1:
1356 break
1357 y.set_lo(bod or 0)
1358 else:
1359 r = text.tag_prevrange("console", "insert")
1360 if r:
1361 startatindex = r[1]
1362 else:
1363 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001364 rawtext = text.get(startatindex, "insert")
1365 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001366 y.set_lo(0)
1367
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001368 c = y.get_continuation_type()
1369 if c != PyParse.C_NONE:
1370 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001371 if c == PyParse.C_STRING_FIRST_LINE:
1372 # after the first line of a string; do not indent at all
1373 pass
1374 elif c == PyParse.C_STRING_NEXT_LINES:
1375 # inside a string which started before this line;
1376 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001377 text.insert("insert", indent)
1378 elif c == PyParse.C_BRACKET:
1379 # line up with the first (if any) element of the
1380 # last open bracket structure; else indent one
1381 # level beyond the indent of the line with the
1382 # last open bracket
1383 self.reindent_to(y.compute_bracket_indent())
1384 elif c == PyParse.C_BACKSLASH:
1385 # if more than one line in this stmt already, just
1386 # mimic the current indent; else if initial line
1387 # has a start on an assignment stmt, indent to
1388 # beyond leftmost =; else to beyond first chunk of
1389 # non-whitespace on initial line
1390 if y.get_num_lines_in_stmt() > 1:
1391 text.insert("insert", indent)
1392 else:
1393 self.reindent_to(y.compute_backslash_indent())
1394 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001395 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001396 return "break"
1397
1398 # This line starts a brand new stmt; indent relative to
1399 # indentation of initial line of closest preceding
1400 # interesting stmt.
1401 indent = y.get_base_indent_string()
1402 text.insert("insert", indent)
1403 if y.is_block_opener():
1404 self.smart_indent_event(event)
1405 elif indent and y.is_block_closer():
1406 self.smart_backspace_event(event)
1407 return "break"
1408 finally:
1409 text.see("insert")
1410 text.undo_block_stop()
1411
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001412 # Our editwin provides a is_char_in_string function that works
1413 # with a Tk text index, but PyParse only knows about offsets into
1414 # a string. This builds a function for PyParse that accepts an
1415 # offset.
1416
1417 def _build_char_in_string_func(self, startindex):
1418 def inner(offset, _startindex=startindex,
1419 _icis=self.is_char_in_string):
1420 return _icis(_startindex + "+%dc" % offset)
1421 return inner
1422
1423 def indent_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 = effective + self.indentwidth
1430 lines[pos] = self._make_blanks(effective) + line[raw:]
1431 self.set_region(head, tail, chars, lines)
1432 return "break"
1433
1434 def dedent_region_event(self, event):
1435 head, tail, chars, lines = self.get_region()
1436 for pos in range(len(lines)):
1437 line = lines[pos]
1438 if line:
1439 raw, effective = classifyws(line, self.tabwidth)
1440 effective = max(effective - self.indentwidth, 0)
1441 lines[pos] = self._make_blanks(effective) + line[raw:]
1442 self.set_region(head, tail, chars, lines)
1443 return "break"
1444
1445 def comment_region_event(self, event):
1446 head, tail, chars, lines = self.get_region()
1447 for pos in range(len(lines) - 1):
1448 line = lines[pos]
1449 lines[pos] = '##' + line
1450 self.set_region(head, tail, chars, lines)
1451
1452 def uncomment_region_event(self, event):
1453 head, tail, chars, lines = self.get_region()
1454 for pos in range(len(lines)):
1455 line = lines[pos]
1456 if not line:
1457 continue
1458 if line[:2] == '##':
1459 line = line[2:]
1460 elif line[:1] == '#':
1461 line = line[1:]
1462 lines[pos] = line
1463 self.set_region(head, tail, chars, lines)
1464
1465 def tabify_region_event(self, event):
1466 head, tail, chars, lines = self.get_region()
1467 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001468 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001469 for pos in range(len(lines)):
1470 line = lines[pos]
1471 if line:
1472 raw, effective = classifyws(line, tabwidth)
1473 ntabs, nspaces = divmod(effective, tabwidth)
1474 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1475 self.set_region(head, tail, chars, lines)
1476
1477 def untabify_region_event(self, event):
1478 head, tail, chars, lines = self.get_region()
1479 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001480 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001481 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001482 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001483 self.set_region(head, tail, chars, lines)
1484
1485 def toggle_tabs_event(self, event):
1486 if self.askyesno(
1487 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001488 "Turn tabs " + ("on", "off")[self.usetabs] +
1489 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001490 ("will be", "remains at")[self.usetabs] + " 8." +
1491 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001492 parent=self.text):
1493 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001494 # Try to prevent inconsistent indentation.
1495 # User must change indent width manually after using tabs.
1496 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001497 return "break"
1498
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001499 # XXX this isn't bound to anything -- see tabwidth comments
1500## def change_tabwidth_event(self, event):
1501## new = self._asktabwidth()
1502## if new != self.tabwidth:
1503## self.tabwidth = new
1504## self.set_indentation_params(0, guess=0)
1505## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001506
1507 def change_indentwidth_event(self, event):
1508 new = self.askinteger(
1509 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001510 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001511 parent=self.text,
1512 initialvalue=self.indentwidth,
1513 minvalue=2,
1514 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001515 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001516 self.indentwidth = new
1517 return "break"
1518
1519 def get_region(self):
1520 text = self.text
1521 first, last = self.get_selection_indices()
1522 if first and last:
1523 head = text.index(first + " linestart")
1524 tail = text.index(last + "-1c lineend +1c")
1525 else:
1526 head = text.index("insert linestart")
1527 tail = text.index("insert lineend +1c")
1528 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001529 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001530 return head, tail, chars, lines
1531
1532 def set_region(self, head, tail, chars, lines):
1533 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001534 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001535 if newchars == chars:
1536 text.bell()
1537 return
1538 text.tag_remove("sel", "1.0", "end")
1539 text.mark_set("insert", head)
1540 text.undo_block_start()
1541 text.delete(head, tail)
1542 text.insert(head, newchars)
1543 text.undo_block_stop()
1544 text.tag_add("sel", head, "insert")
1545
1546 # Make string that displays as n leading blanks.
1547
1548 def _make_blanks(self, n):
1549 if self.usetabs:
1550 ntabs, nspaces = divmod(n, self.tabwidth)
1551 return '\t' * ntabs + ' ' * nspaces
1552 else:
1553 return ' ' * n
1554
1555 # Delete from beginning of line to insert point, then reinsert
1556 # column logical (meaning use tabs if appropriate) spaces.
1557
1558 def reindent_to(self, column):
1559 text = self.text
1560 text.undo_block_start()
1561 if text.compare("insert linestart", "!=", "insert"):
1562 text.delete("insert linestart", "insert")
1563 if column:
1564 text.insert("insert", self._make_blanks(column))
1565 text.undo_block_stop()
1566
1567 def _asktabwidth(self):
1568 return self.askinteger(
1569 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001570 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001571 parent=self.text,
1572 initialvalue=self.indentwidth,
1573 minvalue=2,
Roger Serwy75b249c2013-04-06 20:26:53 -05001574 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001575
1576 # Guess indentwidth from text content.
1577 # Return guessed indentwidth. This should not be believed unless
1578 # it's in a reasonable range (e.g., it will be 0 if no indented
1579 # blocks are found).
1580
1581 def guess_indent(self):
1582 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1583 if opener and indented:
1584 raw, indentsmall = classifyws(opener, self.tabwidth)
1585 raw, indentlarge = classifyws(indented, self.tabwidth)
1586 else:
1587 indentsmall = indentlarge = 0
1588 return indentlarge - indentsmall
1589
1590# "line.col" -> line, as an int
1591def index2line(index):
1592 return int(float(index))
1593
1594# Look at the leading whitespace in s.
1595# Return pair (# of leading ws characters,
1596# effective # of leading blanks after expanding
1597# tabs to width tabwidth)
1598
1599def classifyws(s, tabwidth):
1600 raw = effective = 0
1601 for ch in s:
1602 if ch == ' ':
1603 raw = raw + 1
1604 effective = effective + 1
1605 elif ch == '\t':
1606 raw = raw + 1
1607 effective = (effective // tabwidth + 1) * tabwidth
1608 else:
1609 break
1610 return raw, effective
1611
1612import tokenize
1613_tokenize = tokenize
1614del tokenize
1615
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001616class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001617
1618 # .run() chews over the Text widget, looking for a block opener
1619 # and the stmt following it. Returns a pair,
1620 # (line containing block opener, line containing stmt)
1621 # Either or both may be None.
1622
1623 def __init__(self, text, tabwidth):
1624 self.text = text
1625 self.tabwidth = tabwidth
1626 self.i = self.finished = 0
1627 self.blkopenline = self.indentedline = None
1628
1629 def readline(self):
1630 if self.finished:
1631 return ""
1632 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001633 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001634 if self.text.compare(mark, ">=", "end"):
1635 return ""
1636 return self.text.get(mark, mark + " lineend+1c")
1637
1638 def tokeneater(self, type, token, start, end, line,
1639 INDENT=_tokenize.INDENT,
1640 NAME=_tokenize.NAME,
1641 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1642 if self.finished:
1643 pass
1644 elif type == NAME and token in OPENERS:
1645 self.blkopenline = line
1646 elif type == INDENT and self.blkopenline:
1647 self.indentedline = line
1648 self.finished = 1
1649
1650 def run(self):
1651 save_tabsize = _tokenize.tabsize
1652 _tokenize.tabsize = self.tabwidth
1653 try:
1654 try:
1655 _tokenize.tokenize(self.readline, self.tokeneater)
Serhiy Storchaka61006a22012-12-27 21:34:23 +02001656 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001657 # since we cut off the tokenizer early, we can trigger
1658 # spurious errors
1659 pass
1660 finally:
1661 _tokenize.tabsize = save_tabsize
1662 return self.blkopenline, self.indentedline
1663
1664### end autoindent code ###
1665
David Scherer7aced172000-08-15 01:13:23 +00001666def prepstr(s):
1667 # Helper to extract the underscore from a string, e.g.
1668 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001669 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001670 if i >= 0:
1671 s = s[:i] + s[i+1:]
1672 return i, s
1673
1674
1675keynames = {
1676 'bracketleft': '[',
1677 'bracketright': ']',
1678 'slash': '/',
1679}
1680
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001681def get_accelerator(keydefs, eventname):
1682 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001683 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1684 # if not keylist:
1685 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1686 "<<open-module>>",
1687 "<<goto-line>>",
1688 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001689 return ""
1690 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001691 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001692 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1693 s = re.sub("Key-", "", s)
1694 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1695 s = re.sub("Control-", "Ctrl-", s)
1696 s = re.sub("-", "+", s)
1697 s = re.sub("><", " ", s)
1698 s = re.sub("<", "", s)
1699 s = re.sub(">", "", s)
1700 return s
1701
1702
1703def fixwordbreaks(root):
1704 # Make sure that Tk's double-click and next/previous word
1705 # operations use our definition of a word (i.e. an identifier)
1706 tk = root.tk
1707 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1708 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1709 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1710
1711
1712def test():
1713 root = Tk()
1714 fixwordbreaks(root)
1715 root.withdraw()
1716 if sys.argv[1:]:
1717 filename = sys.argv[1]
1718 else:
1719 filename = None
1720 edit = EditorWindow(root=root, filename=filename)
1721 edit.set_close_hook(root.quit)
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001722 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001723 root.mainloop()
1724 root.destroy()
1725
1726if __name__ == '__main__':
1727 test()