blob: 343b6e4230496294aa6e27f809b9980e8abaf507 [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)
319 if is_py_src:
320 self.color = color = self.ColorDelegator()
321 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000322 else:
323 io.set_filename(filename)
Christian Heimesa156e092008-02-16 07:38:31 +0000324 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000325 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000326 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000327 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000328 menu = self.menudict.get('windows')
329 if menu:
330 end = menu.index("end")
331 if end is None:
332 end = -1
333 if end >= 0:
334 menu.add_separator()
335 end = end + 1
336 self.wmenu_end = end
337 WindowList.register_callback(self.postwindowsmenu)
338
339 # Some abstractions so IDLE extensions are cross-IDE
340 self.askyesno = tkMessageBox.askyesno
341 self.askinteger = tkSimpleDialog.askinteger
342 self.showerror = tkMessageBox.showerror
343
Martin v. Löwis307021f2005-11-27 16:59:04 +0000344 def _filename_to_unicode(self, filename):
345 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000346 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000347 return filename
348 else:
349 try:
350 return filename.decode(self.filesystemencoding)
351 except UnicodeDecodeError:
352 # XXX
353 try:
354 return filename.decode(self.encoding)
355 except UnicodeDecodeError:
356 # byte-to-byte conversion
357 return filename.decode('iso8859-1')
358
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000359 def new_callback(self, event):
360 dirname, basename = self.io.defaultfilename()
361 self.flist.new(dirname)
362 return "break"
363
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000364 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400365 if (event.state & 4) != 0 and event.keysym == "Home":
366 # state&4==Control. If <Control-Home>, use the Tk binding.
367 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000368 if self.text.index("iomark") and \
369 self.text.compare("iomark", "<=", "insert lineend") and \
370 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400371 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000372 insertpt = int(self.text.index("iomark").split(".")[1])
373 else:
374 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000375 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000376 if line[insertpt] not in (' ','\t'):
377 break
378 else:
379 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000380 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000381 if insertpt == lineat:
382 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000383 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000384 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400385 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000386 self.text.tag_remove("sel", "1.0", "end")
387 else:
388 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200389 # there was no previous selection
390 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400391 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200392 if self.text.compare(self.text.index("sel.first"), "<",
393 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400394 self.text.mark_set("my_anchor", "sel.first") # extend back
395 else:
396 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000397 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400398 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000399 if self.text.compare(first,">",last):
400 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000401 self.text.tag_remove("sel", "1.0", "end")
402 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000403 self.text.mark_set("insert", dest)
404 self.text.see("insert")
405 return "break"
406
David Scherer7aced172000-08-15 01:13:23 +0000407 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000408 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000409 if macosxSupport.runningAsOSXApp():
410 # Insert some padding to avoid obscuring some of the statusbar
411 # by the resize widget.
412 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000413 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
414 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
415 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000416 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
417 self.text.event_add("<<set-line-and-column>>",
418 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000419 self.text.after_idle(self.set_line_and_column)
420
421 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000422 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000423 self.status_bar.set_label('column', 'Col: %s' % column)
424 self.status_bar.set_label('line', 'Ln: %s' % line)
425
David Scherer7aced172000-08-15 01:13:23 +0000426 menu_specs = [
427 ("file", "_File"),
428 ("edit", "_Edit"),
429 ("format", "F_ormat"),
430 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000431 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000432 ("windows", "_Windows"),
433 ("help", "_Help"),
434 ]
435
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000436 if macosxSupport.runningAsOSXApp():
437 del menu_specs[-3]
438 menu_specs[-2] = ("windows", "_Window")
439
440
David Scherer7aced172000-08-15 01:13:23 +0000441 def createmenubar(self):
442 mbar = self.menubar
443 self.menudict = menudict = {}
444 for name, label in self.menu_specs:
445 underline, label = prepstr(label)
446 menudict[name] = menu = Menu(mbar, name=name)
447 mbar.add_cascade(label=label, menu=menu, underline=underline)
Georg Brandlaedd2892010-12-19 10:10:32 +0000448 if macosxSupport.isCarbonAquaTk(self.root):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000449 # Insert the application menu
450 menudict['application'] = menu = Menu(mbar, name='apple')
451 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000452 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000453 self.recent_files_menu = Menu(self.menubar)
454 self.menudict['file'].insert_cascade(3, label='Recent Files',
455 underline=0,
456 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000457 self.base_helpmenu_length = self.menudict['help'].index(END)
458 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000459
460 def postwindowsmenu(self):
461 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000462 menu = self.menudict['windows']
463 end = menu.index("end")
464 if end is None:
465 end = -1
466 if end > self.wmenu_end:
467 menu.delete(self.wmenu_end+1, end)
468 WindowList.add_windows_to_menu(menu)
469
470 rmenu = None
471
472 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000473 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
474 if not self.rmenu:
475 self.make_rmenu()
476 rmenu = self.rmenu
477 self.event = event
478 iswin = sys.platform[:3] == 'win'
479 if iswin:
480 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200481
Roger Serwy6b2918a2013-04-07 12:15:52 -0500482 for item in self.rmenu_specs:
483 try:
484 label, eventname, verify_state = item
485 except ValueError: # see issue1207589
486 continue
487
Andrew Svetlovd1837672012-11-01 22:41:19 +0200488 if verify_state is None:
489 continue
490 state = getattr(self, verify_state)()
491 rmenu.entryconfigure(label, state=state)
492
493
David Scherer7aced172000-08-15 01:13:23 +0000494 rmenu.tk_popup(event.x_root, event.y_root)
495 if iswin:
496 self.text.config(cursor="ibeam")
497
498 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200499 # ("Label", "<<virtual-event>>", "statefuncname"), ...
500 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000501 ]
502
503 def make_rmenu(self):
504 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500505 for item in self.rmenu_specs:
506 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200507 if label is not None:
508 def command(text=self.text, eventname=eventname):
509 text.event_generate(eventname)
510 rmenu.add_command(label=label, command=command)
511 else:
512 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000513 self.rmenu = rmenu
514
Andrew Svetlovd1837672012-11-01 22:41:19 +0200515 def rmenu_check_cut(self):
516 return self.rmenu_check_copy()
517
518 def rmenu_check_copy(self):
519 try:
520 indx = self.text.index('sel.first')
521 except TclError:
522 return 'disabled'
523 else:
524 return 'normal' if indx else 'disabled'
525
526 def rmenu_check_paste(self):
527 try:
528 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
529 except TclError:
530 return 'disabled'
531 else:
532 return 'normal'
533
David Scherer7aced172000-08-15 01:13:23 +0000534 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000535 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000536
Steven M. Gava3b55a892001-11-21 05:56:26 +0000537 def config_dialog(self, event=None):
538 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000539
David Scherer7aced172000-08-15 01:13:23 +0000540 def help_dialog(self, event=None):
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500541 if self.root:
542 parent = self.root
543 else:
544 parent = self.top
545 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000546
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000547 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000548 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000549 try:
550 os.startfile(self.help_url)
551 except WindowsError as why:
552 tkMessageBox.showerror(title='Document Start Failure',
553 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000554 else:
555 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000556 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000557
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000558 def cut(self,event):
559 self.text.event_generate("<<Cut>>")
560 return "break"
561
562 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000563 if not self.text.tag_ranges("sel"):
564 # There is no selection, so do nothing and maybe interrupt.
565 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000566 self.text.event_generate("<<Copy>>")
567 return "break"
568
569 def paste(self,event):
570 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000571 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000572 return "break"
573
David Scherer7aced172000-08-15 01:13:23 +0000574 def select_all(self, event=None):
575 self.text.tag_add("sel", "1.0", "end-1c")
576 self.text.mark_set("insert", "1.0")
577 self.text.see("insert")
578 return "break"
579
580 def remove_selection(self, event=None):
581 self.text.tag_remove("sel", "1.0", "end")
582 self.text.see("insert")
583
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000584 def move_at_edge_if_selection(self, edge_index):
585 """Cursor move begins at start or end of selection
586
587 When a left/right cursor key is pressed create and return to Tkinter a
588 function which causes a cursor move from the associated edge of the
589 selection.
590
591 """
592 self_text_index = self.text.index
593 self_text_mark_set = self.text.mark_set
594 edges_table = ("sel.first+1c", "sel.last-1c")
595 def move_at_edge(event):
596 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
597 try:
598 self_text_index("sel.first")
599 self_text_mark_set("insert", edges_table[edge_index])
600 except TclError:
601 pass
602 return move_at_edge
603
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000604 def del_word_left(self, event):
605 self.text.event_generate('<Meta-Delete>')
606 return "break"
607
608 def del_word_right(self, event):
609 self.text.event_generate('<Meta-d>')
610 return "break"
611
Steven M. Gavac5976402002-01-04 03:06:08 +0000612 def find_event(self, event):
613 SearchDialog.find(self.text)
614 return "break"
615
616 def find_again_event(self, event):
617 SearchDialog.find_again(self.text)
618 return "break"
619
620 def find_selection_event(self, event):
621 SearchDialog.find_selection(self.text)
622 return "break"
623
624 def find_in_files_event(self, event):
625 GrepDialog.grep(self.text, self.io, self.flist)
626 return "break"
627
628 def replace_event(self, event):
629 ReplaceDialog.replace(self.text)
630 return "break"
631
632 def goto_line_event(self, event):
633 text = self.text
634 lineno = tkSimpleDialog.askinteger("Goto",
635 "Go to line number:",parent=text)
636 if lineno is None:
637 return "break"
638 if lineno <= 0:
639 text.bell()
640 return "break"
641 text.mark_set("insert", "%d.0" % lineno)
642 text.see("insert")
643
David Scherer7aced172000-08-15 01:13:23 +0000644 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000645 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000646 try:
647 name = self.text.get("sel.first", "sel.last")
648 except TclError:
649 name = ""
650 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000651 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000652 name = tkSimpleDialog.askstring("Module",
653 "Enter the name of a Python module\n"
654 "to search on sys.path and open:",
655 parent=self.text, initialvalue=name)
656 if name:
657 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000658 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000659 return
David Scherer7aced172000-08-15 01:13:23 +0000660 # XXX Ought to insert current file's directory in front of path
661 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000662 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000663 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000664 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
665 return
666 if type != imp.PY_SOURCE:
667 tkMessageBox.showerror("Unsupported type",
668 "%s is not a source module" % name, parent=self.text)
669 return
670 if f:
671 f.close()
672 if self.flist:
673 self.flist.open(file)
674 else:
675 self.io.loadfile(file)
676
677 def open_class_browser(self, event=None):
678 filename = self.io.filename
679 if not filename:
680 tkMessageBox.showerror(
681 "No filename",
682 "This buffer has no associated filename",
683 master=self.text)
684 self.text.focus_set()
685 return None
686 head, tail = os.path.split(filename)
687 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000688 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000689 ClassBrowser.ClassBrowser(self.flist, base, [head])
690
691 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000692 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000693 PathBrowser.PathBrowser(self.flist)
694
695 def gotoline(self, lineno):
696 if lineno is not None and lineno > 0:
697 self.text.mark_set("insert", "%d.0" % lineno)
698 self.text.tag_remove("sel", "1.0", "end")
699 self.text.tag_add("sel", "insert", "insert +1l")
700 self.center()
701
702 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000703 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000704 return True
David Scherer7aced172000-08-15 01:13:23 +0000705 base, ext = os.path.splitext(os.path.basename(filename))
706 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000707 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000708 line = self.text.get('1.0', '1.0 lineend')
709 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000710
711 def close_hook(self):
712 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000713 self.flist.unregister_maybe_terminate(self)
714 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000715
716 def set_close_hook(self, close_hook):
717 self.close_hook = close_hook
718
719 def filename_change_hook(self):
720 if self.flist:
721 self.flist.filename_changed_edit(self)
722 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000723 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000724 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000725
Christian Heimesa156e092008-02-16 07:38:31 +0000726 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000727 if self.color:
728 return
Christian Heimesa156e092008-02-16 07:38:31 +0000729 if self.ispythonsource(self.io.filename):
730 self.color = self.ColorDelegator()
731 # can add more colorizers here...
732 if self.color:
733 self.per.removefilter(self.undo)
734 self.per.insertfilter(self.color)
735 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000736
Christian Heimesa156e092008-02-16 07:38:31 +0000737 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000738 if not self.color:
739 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000740 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000741 self.per.removefilter(self.color)
742 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000743
Steven M. Gavab77d3432002-03-02 07:16:21 +0000744 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000745 "Update the colour theme"
746 # Called from self.filename_change_hook and from configDialog.py
747 self._rmcolorizer()
748 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000749 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000750 normal_colors = idleConf.GetHighlight(theme, 'normal')
751 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
752 select_colors = idleConf.GetHighlight(theme, 'hilite')
753 self.text.config(
754 foreground=normal_colors['foreground'],
755 background=normal_colors['background'],
756 insertbackground=cursor_color,
757 selectforeground=select_colors['foreground'],
758 selectbackground=select_colors['background'],
759 )
David Scherer7aced172000-08-15 01:13:23 +0000760
Guido van Rossum33d26892007-08-05 15:29:28 +0000761 IDENTCHARS = string.ascii_letters + string.digits + "_"
762
763 def colorize_syntax_error(self, text, pos):
764 text.tag_add("ERROR", pos)
765 char = text.get(pos)
766 if char and char in self.IDENTCHARS:
767 text.tag_add("ERROR", pos + " wordstart", pos)
768 if '\n' == text.get(pos): # error at line end
769 text.mark_set("insert", pos)
770 else:
771 text.mark_set("insert", pos + "+1c")
772 text.see(pos)
773
Steven M. Gavab1585412002-03-12 00:21:56 +0000774 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000775 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000776 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000777 fontWeight='normal'
778 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
779 fontWeight='bold'
780 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200781 idleConf.GetOption('main','EditorWindow','font-size',
782 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000783 fontWeight))
784
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000785 def RemoveKeybindings(self):
786 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000787 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000788 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000789 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000790 self.text.event_delete(event, *keylist)
791 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000792 xkeydefs = idleConf.GetExtensionBindings(extensionName)
793 if xkeydefs:
794 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000795 self.text.event_delete(event, *keylist)
796
797 def ApplyKeybindings(self):
798 "Update the keybindings after they are changed"
799 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000800 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000801 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000802 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000803 xkeydefs = idleConf.GetExtensionBindings(extensionName)
804 if xkeydefs:
805 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000806 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000807 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000808 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000809 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000810 for item in menu[1]:
811 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000812 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000813 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000814 menu = self.menudict[menubarItem]
815 end = menu.index(END) + 1
816 for index in range(0, end):
817 if menu.type(index) == 'command':
818 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000819 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000820 itemName = menu.entrycget(index, 'label')
821 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000822 if menubarItem in menuEventDict:
823 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000824 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000825 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000826 accel = get_accelerator(keydefs, event)
827 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000828
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000829 def set_notabs_indentwidth(self):
830 "Update the indentwidth if changed and not using tabs in this window"
831 # Called from configDialog.py
832 if not self.usetabs:
833 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
834 type='int')
835
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000836 def reset_help_menu_entries(self):
837 "Update the additional help entries on the Help menu"
838 help_list = idleConf.GetAllExtraHelpSourcesList()
839 helpmenu = self.menudict['help']
840 # first delete the extra help entries, if any
841 helpmenu_length = helpmenu.index(END)
842 if helpmenu_length > self.base_helpmenu_length:
843 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
844 # then rebuild them
845 if help_list:
846 helpmenu.add_separator()
847 for entry in help_list:
848 cmd = self.__extra_help_callback(entry[1])
849 helpmenu.add_command(label=entry[0], command=cmd)
850 # and update the menu dictionary
851 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000852
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000853 def __extra_help_callback(self, helpfile):
854 "Create a callback with the helpfile value frozen at definition time"
855 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000856 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000857 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000858 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000859 try:
860 os.startfile(helpfile)
861 except WindowsError as why:
862 tkMessageBox.showerror(title='Document Start Failure',
863 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000864 else:
865 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000866 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000867
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000868 def update_recent_files_list(self, new_file=None):
869 "Load and update the recent files list and menus"
870 rf_list = []
871 if os.path.exists(self.recent_files_path):
Ned Deily122539e2011-01-24 21:46:44 +0000872 rf_list_file = open(self.recent_files_path,'r',
873 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000874 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000875 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000876 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000877 rf_list_file.close()
878 if new_file:
879 new_file = os.path.abspath(new_file) + '\n'
880 if new_file in rf_list:
881 rf_list.remove(new_file) # move to top
882 rf_list.insert(0, new_file)
883 # clean and save the recent files list
884 bad_paths = []
885 for path in rf_list:
886 if '\0' in path or not os.path.exists(path[0:-1]):
887 bad_paths.append(path)
888 rf_list = [path for path in rf_list if path not in bad_paths]
889 ulchars = "1234567890ABCDEFGHIJK"
890 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000891 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800892 with open(self.recent_files_path, 'w',
893 encoding='utf_8', errors='replace') as rf_file:
894 rf_file.writelines(rf_list)
895 except IOError as err:
896 if not getattr(self.root, "recentfilelist_error_displayed", False):
897 self.root.recentfilelist_error_displayed = True
898 tkMessageBox.showerror(title='IDLE Error',
899 message='Unable to update Recent Files list:\n%s'
900 % str(err),
901 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000902 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000903 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000904 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700905 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000906 for i, file_name in enumerate(rf_list):
907 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000908 # make unicode string to display non-ASCII chars correctly
909 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000910 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000911 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000912 command=callback,
913 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000914
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000915 def __recent_file_callback(self, file_name):
916 def open_recent_file(fn_closure=file_name):
917 self.io.open(editFile=fn_closure)
918 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000919
David Scherer7aced172000-08-15 01:13:23 +0000920 def saved_change_hook(self):
921 short = self.short_title()
922 long = self.long_title()
923 if short and long:
924 title = short + " - " + long
925 elif short:
926 title = short
927 elif long:
928 title = long
929 else:
930 title = "Untitled"
931 icon = short or long or title
932 if not self.get_saved():
933 title = "*%s*" % title
934 icon = "*%s" % icon
935 self.top.wm_title(title)
936 self.top.wm_iconname(icon)
937
938 def get_saved(self):
939 return self.undo.get_saved()
940
941 def set_saved(self, flag):
942 self.undo.set_saved(flag)
943
944 def reset_undo(self):
945 self.undo.reset_undo()
946
947 def short_title(self):
948 filename = self.io.filename
949 if filename:
950 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000951 # return unicode string to display non-ASCII chars correctly
952 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000953
954 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000955 # return unicode string to display non-ASCII chars correctly
956 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000957
958 def center_insert_event(self, event):
959 self.center()
960
961 def center(self, mark="insert"):
962 text = self.text
963 top, bot = self.getwindowlines()
964 lineno = self.getlineno(mark)
965 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000966 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000967 text.yview(float(newtop))
968
969 def getwindowlines(self):
970 text = self.text
971 top = self.getlineno("@0,0")
972 bot = self.getlineno("@0,65535")
973 if top == bot and text.winfo_height() == 1:
974 # Geometry manager hasn't run yet
975 height = int(text['height'])
976 bot = top + height - 1
977 return top, bot
978
979 def getlineno(self, mark="insert"):
980 text = self.text
981 return int(float(text.index(mark)))
982
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000983 def get_geometry(self):
984 "Return (width, height, x, y)"
985 geom = self.top.wm_geometry()
986 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000987 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000988
David Scherer7aced172000-08-15 01:13:23 +0000989 def close_event(self, event):
990 self.close()
991
992 def maybesave(self):
993 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000994 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000995 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000996 self.top.deiconify()
997 self.top.lower()
998 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000999 return self.io.maybesave()
1000
1001 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001002 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +00001003 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001004 self._close()
1005 return reply
1006
1007 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001008 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001009 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001010 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001011 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001012 self.io.close()
1013 self.io = None
1014 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001015 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001016 self.color.close(False)
1017 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001018 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001019 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001020 self.per.close()
1021 self.per = None
1022 self.top.destroy()
1023 if self.close_hook:
1024 # unless override: unregister from flist, terminate if last window
1025 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001026
1027 def load_extensions(self):
1028 self.extensions = {}
1029 self.load_standard_extensions()
1030
1031 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001032 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001033 if hasattr(ins, "close"):
1034 ins.close()
1035 self.extensions = {}
1036
1037 def load_standard_extensions(self):
1038 for name in self.get_standard_extension_names():
1039 try:
1040 self.load_extension(name)
1041 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001042 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001043 traceback.print_exc()
1044
1045 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001046 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001047
1048 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001049 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001050 try:
1051 mod = importlib.import_module('.' + name, package=__package__)
1052 except ImportError:
1053 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001054 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001055 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001056 raise
David Scherer7aced172000-08-15 01:13:23 +00001057 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001058 keydefs = idleConf.GetExtensionBindings(name)
1059 if hasattr(cls, "menudefs"):
1060 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001061 ins = cls(self)
1062 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001063 if keydefs:
1064 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001065 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001066 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001067 while methodname[:1] == '<':
1068 methodname = methodname[1:]
1069 while methodname[-1:] == '>':
1070 methodname = methodname[:-1]
1071 methodname = methodname + "_event"
1072 if hasattr(ins, methodname):
1073 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001074
1075 def apply_bindings(self, keydefs=None):
1076 if keydefs is None:
1077 keydefs = self.Bindings.default_keydefs
1078 text = self.text
1079 text.keydefs = keydefs
1080 for event, keylist in keydefs.items():
1081 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001082 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001083
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001084 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001085 """Add appropriate entries to the menus and submenus
1086
1087 Menus that are absent or None in self.menudict are ignored.
1088 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001089 if menudefs is None:
1090 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001091 if keydefs is None:
1092 keydefs = self.Bindings.default_keydefs
1093 menudict = self.menudict
1094 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001095 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001096 menu = menudict.get(mname)
1097 if not menu:
1098 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001099 for entry in entrylist:
1100 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001101 menu.add_separator()
1102 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001103 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001104 checkbutton = (label[:1] == '!')
1105 if checkbutton:
1106 label = label[1:]
1107 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001108 accelerator = get_accelerator(keydefs, eventname)
1109 def command(text=text, eventname=eventname):
1110 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001111 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001112 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001113 menu.add_checkbutton(label=label, underline=underline,
1114 command=command, accelerator=accelerator,
1115 variable=var)
1116 else:
1117 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001118 command=command,
1119 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001120
1121 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001122 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001123 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001124 value = var.get()
1125 return value
1126 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001127 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001128
1129 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001130 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001131 if var:
1132 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001133 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001134 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001135
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001136 def get_var_obj(self, name, vartype=None):
1137 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001138 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001139 # create a Tkinter variable object with self.text as master:
1140 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001141 return var
1142
1143 # Tk implementations of "virtual text methods" -- each platform
1144 # reusing IDLE's support code needs to define these for its GUI's
1145 # flavor of widget.
1146
1147 # Is character at text_index in a Python string? Return 0 for
1148 # "guaranteed no", true for anything else. This info is expensive
1149 # to compute ab initio, but is probably already known by the
1150 # platform's colorizer.
1151
1152 def is_char_in_string(self, text_index):
1153 if self.color:
1154 # Return true iff colorizer hasn't (re)gotten this far
1155 # yet, or the character is tagged as being in a string
1156 return self.text.tag_prevrange("TODO", text_index) or \
1157 "STRING" in self.text.tag_names(text_index)
1158 else:
1159 # The colorizer is missing: assume the worst
1160 return 1
1161
1162 # If a selection is defined in the text widget, return (start,
1163 # end) as Tkinter text indices, otherwise return (None, None)
1164 def get_selection_indices(self):
1165 try:
1166 first = self.text.index("sel.first")
1167 last = self.text.index("sel.last")
1168 return first, last
1169 except TclError:
1170 return None, None
1171
1172 # Return the text widget's current view of what a tab stop means
1173 # (equivalent width in spaces).
1174
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001175 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001176 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1177 return int(current)
1178
1179 # Set the text widget's current view of what a tab stop means.
1180
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001181 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001182 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001183 if self.get_tk_tabwidth() != newtabwidth:
1184 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001185 pixels = text.tk.call("font", "measure", text["font"],
1186 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001187 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001188 text.configure(tabs=pixels)
1189
Guido van Rossum33d26892007-08-05 15:29:28 +00001190### begin autoindent code ### (configuration was moved to beginning of class)
1191
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001192 def set_indentation_params(self, is_py_src, guess=True):
1193 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001194 i = self.guess_indent()
1195 if 2 <= i <= 8:
1196 self.indentwidth = i
1197 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001198 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001199 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001200
1201 def smart_backspace_event(self, event):
1202 text = self.text
1203 first, last = self.get_selection_indices()
1204 if first and last:
1205 text.delete(first, last)
1206 text.mark_set("insert", first)
1207 return "break"
1208 # Delete whitespace left, until hitting a real char or closest
1209 # preceding virtual tab stop.
1210 chars = text.get("insert linestart", "insert")
1211 if chars == '':
1212 if text.compare("insert", ">", "1.0"):
1213 # easy: delete preceding newline
1214 text.delete("insert-1c")
1215 else:
1216 text.bell() # at start of buffer
1217 return "break"
1218 if chars[-1] not in " \t":
1219 # easy: delete preceding real char
1220 text.delete("insert-1c")
1221 return "break"
1222 # Ick. It may require *inserting* spaces if we back up over a
1223 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001224 tabwidth = self.tabwidth
1225 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001226 assert have > 0
1227 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001228 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001229 if self.context_use_ps1:
1230 last_line_of_prompt = sys.ps1.split('\n')[-1]
1231 else:
1232 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001233 ncharsdeleted = 0
1234 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001235 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001236 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001237 chars = chars[:-1]
1238 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001239 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001240 if have <= want or chars[-1] not in " \t":
1241 break
1242 text.undo_block_start()
1243 text.delete("insert-%dc" % ncharsdeleted, "insert")
1244 if have < want:
1245 text.insert("insert", ' ' * (want - have))
1246 text.undo_block_stop()
1247 return "break"
1248
1249 def smart_indent_event(self, event):
1250 # if intraline selection:
1251 # delete it
1252 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001253 # do indent-region
1254 # else:
1255 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001256 text = self.text
1257 first, last = self.get_selection_indices()
1258 text.undo_block_start()
1259 try:
1260 if first and last:
1261 if index2line(first) != index2line(last):
1262 return self.indent_region_event(event)
1263 text.delete(first, last)
1264 text.mark_set("insert", first)
1265 prefix = text.get("insert linestart", "insert")
1266 raw, effective = classifyws(prefix, self.tabwidth)
1267 if raw == len(prefix):
1268 # only whitespace to the left
1269 self.reindent_to(effective + self.indentwidth)
1270 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001271 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001272 if self.usetabs:
1273 pad = '\t'
1274 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001275 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001276 n = self.indentwidth
1277 pad = ' ' * (n - effective % n)
1278 text.insert("insert", pad)
1279 text.see("insert")
1280 return "break"
1281 finally:
1282 text.undo_block_stop()
1283
1284 def newline_and_indent_event(self, event):
1285 text = self.text
1286 first, last = self.get_selection_indices()
1287 text.undo_block_start()
1288 try:
1289 if first and last:
1290 text.delete(first, last)
1291 text.mark_set("insert", first)
1292 line = text.get("insert linestart", "insert")
1293 i, n = 0, len(line)
1294 while i < n and line[i] in " \t":
1295 i = i+1
1296 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001297 # the cursor is in or at leading indentation in a continuation
1298 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001299 text.insert("insert linestart", '\n')
1300 return "break"
1301 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001302 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001303 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001304 last_line_of_prompt = sys.ps1.split('\n')[-1]
1305 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001306 line = line[:-1]
1307 i = i+1
1308 if i:
1309 text.delete("insert - %d chars" % i, "insert")
1310 # strip whitespace after insert point
1311 while text.get("insert") in " \t":
1312 text.delete("insert")
1313 # start new line
1314 text.insert("insert", '\n')
1315
1316 # adjust indentation for continuations and block
1317 # open/close first need to find the last stmt
1318 lno = index2line(text.index('insert'))
1319 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001320 if not self.context_use_ps1:
1321 for context in self.num_context_lines:
1322 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001323 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001324 rawtext = text.get(startatindex, "insert")
1325 y.set_str(rawtext)
1326 bod = y.find_good_parse_start(
1327 self.context_use_ps1,
1328 self._build_char_in_string_func(startatindex))
1329 if bod is not None or startat == 1:
1330 break
1331 y.set_lo(bod or 0)
1332 else:
1333 r = text.tag_prevrange("console", "insert")
1334 if r:
1335 startatindex = r[1]
1336 else:
1337 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001338 rawtext = text.get(startatindex, "insert")
1339 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001340 y.set_lo(0)
1341
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001342 c = y.get_continuation_type()
1343 if c != PyParse.C_NONE:
1344 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001345 if c == PyParse.C_STRING_FIRST_LINE:
1346 # after the first line of a string; do not indent at all
1347 pass
1348 elif c == PyParse.C_STRING_NEXT_LINES:
1349 # inside a string which started before this line;
1350 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001351 text.insert("insert", indent)
1352 elif c == PyParse.C_BRACKET:
1353 # line up with the first (if any) element of the
1354 # last open bracket structure; else indent one
1355 # level beyond the indent of the line with the
1356 # last open bracket
1357 self.reindent_to(y.compute_bracket_indent())
1358 elif c == PyParse.C_BACKSLASH:
1359 # if more than one line in this stmt already, just
1360 # mimic the current indent; else if initial line
1361 # has a start on an assignment stmt, indent to
1362 # beyond leftmost =; else to beyond first chunk of
1363 # non-whitespace on initial line
1364 if y.get_num_lines_in_stmt() > 1:
1365 text.insert("insert", indent)
1366 else:
1367 self.reindent_to(y.compute_backslash_indent())
1368 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001369 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001370 return "break"
1371
1372 # This line starts a brand new stmt; indent relative to
1373 # indentation of initial line of closest preceding
1374 # interesting stmt.
1375 indent = y.get_base_indent_string()
1376 text.insert("insert", indent)
1377 if y.is_block_opener():
1378 self.smart_indent_event(event)
1379 elif indent and y.is_block_closer():
1380 self.smart_backspace_event(event)
1381 return "break"
1382 finally:
1383 text.see("insert")
1384 text.undo_block_stop()
1385
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001386 # Our editwin provides a is_char_in_string function that works
1387 # with a Tk text index, but PyParse only knows about offsets into
1388 # a string. This builds a function for PyParse that accepts an
1389 # offset.
1390
1391 def _build_char_in_string_func(self, startindex):
1392 def inner(offset, _startindex=startindex,
1393 _icis=self.is_char_in_string):
1394 return _icis(_startindex + "+%dc" % offset)
1395 return inner
1396
1397 def indent_region_event(self, event):
1398 head, tail, chars, lines = self.get_region()
1399 for pos in range(len(lines)):
1400 line = lines[pos]
1401 if line:
1402 raw, effective = classifyws(line, self.tabwidth)
1403 effective = effective + self.indentwidth
1404 lines[pos] = self._make_blanks(effective) + line[raw:]
1405 self.set_region(head, tail, chars, lines)
1406 return "break"
1407
1408 def dedent_region_event(self, event):
1409 head, tail, chars, lines = self.get_region()
1410 for pos in range(len(lines)):
1411 line = lines[pos]
1412 if line:
1413 raw, effective = classifyws(line, self.tabwidth)
1414 effective = max(effective - self.indentwidth, 0)
1415 lines[pos] = self._make_blanks(effective) + line[raw:]
1416 self.set_region(head, tail, chars, lines)
1417 return "break"
1418
1419 def comment_region_event(self, event):
1420 head, tail, chars, lines = self.get_region()
1421 for pos in range(len(lines) - 1):
1422 line = lines[pos]
1423 lines[pos] = '##' + line
1424 self.set_region(head, tail, chars, lines)
1425
1426 def uncomment_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 not line:
1431 continue
1432 if line[:2] == '##':
1433 line = line[2:]
1434 elif line[:1] == '#':
1435 line = line[1:]
1436 lines[pos] = line
1437 self.set_region(head, tail, chars, lines)
1438
1439 def tabify_region_event(self, event):
1440 head, tail, chars, lines = self.get_region()
1441 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001442 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001443 for pos in range(len(lines)):
1444 line = lines[pos]
1445 if line:
1446 raw, effective = classifyws(line, tabwidth)
1447 ntabs, nspaces = divmod(effective, tabwidth)
1448 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1449 self.set_region(head, tail, chars, lines)
1450
1451 def untabify_region_event(self, event):
1452 head, tail, chars, lines = self.get_region()
1453 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001454 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001455 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001456 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001457 self.set_region(head, tail, chars, lines)
1458
1459 def toggle_tabs_event(self, event):
1460 if self.askyesno(
1461 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001462 "Turn tabs " + ("on", "off")[self.usetabs] +
1463 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001464 ("will be", "remains at")[self.usetabs] + " 8." +
1465 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001466 parent=self.text):
1467 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001468 # Try to prevent inconsistent indentation.
1469 # User must change indent width manually after using tabs.
1470 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001471 return "break"
1472
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001473 # XXX this isn't bound to anything -- see tabwidth comments
1474## def change_tabwidth_event(self, event):
1475## new = self._asktabwidth()
1476## if new != self.tabwidth:
1477## self.tabwidth = new
1478## self.set_indentation_params(0, guess=0)
1479## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001480
1481 def change_indentwidth_event(self, event):
1482 new = self.askinteger(
1483 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001484 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001485 parent=self.text,
1486 initialvalue=self.indentwidth,
1487 minvalue=2,
1488 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001489 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001490 self.indentwidth = new
1491 return "break"
1492
1493 def get_region(self):
1494 text = self.text
1495 first, last = self.get_selection_indices()
1496 if first and last:
1497 head = text.index(first + " linestart")
1498 tail = text.index(last + "-1c lineend +1c")
1499 else:
1500 head = text.index("insert linestart")
1501 tail = text.index("insert lineend +1c")
1502 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001503 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001504 return head, tail, chars, lines
1505
1506 def set_region(self, head, tail, chars, lines):
1507 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001508 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001509 if newchars == chars:
1510 text.bell()
1511 return
1512 text.tag_remove("sel", "1.0", "end")
1513 text.mark_set("insert", head)
1514 text.undo_block_start()
1515 text.delete(head, tail)
1516 text.insert(head, newchars)
1517 text.undo_block_stop()
1518 text.tag_add("sel", head, "insert")
1519
1520 # Make string that displays as n leading blanks.
1521
1522 def _make_blanks(self, n):
1523 if self.usetabs:
1524 ntabs, nspaces = divmod(n, self.tabwidth)
1525 return '\t' * ntabs + ' ' * nspaces
1526 else:
1527 return ' ' * n
1528
1529 # Delete from beginning of line to insert point, then reinsert
1530 # column logical (meaning use tabs if appropriate) spaces.
1531
1532 def reindent_to(self, column):
1533 text = self.text
1534 text.undo_block_start()
1535 if text.compare("insert linestart", "!=", "insert"):
1536 text.delete("insert linestart", "insert")
1537 if column:
1538 text.insert("insert", self._make_blanks(column))
1539 text.undo_block_stop()
1540
1541 def _asktabwidth(self):
1542 return self.askinteger(
1543 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001544 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001545 parent=self.text,
1546 initialvalue=self.indentwidth,
1547 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001548 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001549
1550 # Guess indentwidth from text content.
1551 # Return guessed indentwidth. This should not be believed unless
1552 # it's in a reasonable range (e.g., it will be 0 if no indented
1553 # blocks are found).
1554
1555 def guess_indent(self):
1556 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1557 if opener and indented:
1558 raw, indentsmall = classifyws(opener, self.tabwidth)
1559 raw, indentlarge = classifyws(indented, self.tabwidth)
1560 else:
1561 indentsmall = indentlarge = 0
1562 return indentlarge - indentsmall
1563
1564# "line.col" -> line, as an int
1565def index2line(index):
1566 return int(float(index))
1567
1568# Look at the leading whitespace in s.
1569# Return pair (# of leading ws characters,
1570# effective # of leading blanks after expanding
1571# tabs to width tabwidth)
1572
1573def classifyws(s, tabwidth):
1574 raw = effective = 0
1575 for ch in s:
1576 if ch == ' ':
1577 raw = raw + 1
1578 effective = effective + 1
1579 elif ch == '\t':
1580 raw = raw + 1
1581 effective = (effective // tabwidth + 1) * tabwidth
1582 else:
1583 break
1584 return raw, effective
1585
1586import tokenize
1587_tokenize = tokenize
1588del tokenize
1589
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001590class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001591
1592 # .run() chews over the Text widget, looking for a block opener
1593 # and the stmt following it. Returns a pair,
1594 # (line containing block opener, line containing stmt)
1595 # Either or both may be None.
1596
1597 def __init__(self, text, tabwidth):
1598 self.text = text
1599 self.tabwidth = tabwidth
1600 self.i = self.finished = 0
1601 self.blkopenline = self.indentedline = None
1602
1603 def readline(self):
1604 if self.finished:
1605 return ""
1606 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001607 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001608 if self.text.compare(mark, ">=", "end"):
1609 return ""
1610 return self.text.get(mark, mark + " lineend+1c")
1611
1612 def tokeneater(self, type, token, start, end, line,
1613 INDENT=_tokenize.INDENT,
1614 NAME=_tokenize.NAME,
1615 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1616 if self.finished:
1617 pass
1618 elif type == NAME and token in OPENERS:
1619 self.blkopenline = line
1620 elif type == INDENT and self.blkopenline:
1621 self.indentedline = line
1622 self.finished = 1
1623
1624 def run(self):
1625 save_tabsize = _tokenize.tabsize
1626 _tokenize.tabsize = self.tabwidth
1627 try:
1628 try:
Trent Nelson428de652008-03-18 22:41:35 +00001629 tokens = _tokenize.generate_tokens(self.readline)
1630 for token in tokens:
1631 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001632 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001633 # since we cut off the tokenizer early, we can trigger
1634 # spurious errors
1635 pass
1636 finally:
1637 _tokenize.tabsize = save_tabsize
1638 return self.blkopenline, self.indentedline
1639
1640### end autoindent code ###
1641
David Scherer7aced172000-08-15 01:13:23 +00001642def prepstr(s):
1643 # Helper to extract the underscore from a string, e.g.
1644 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001645 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001646 if i >= 0:
1647 s = s[:i] + s[i+1:]
1648 return i, s
1649
1650
1651keynames = {
1652 'bracketleft': '[',
1653 'bracketright': ']',
1654 'slash': '/',
1655}
1656
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001657def get_accelerator(keydefs, eventname):
1658 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001659 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1660 # if not keylist:
1661 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1662 "<<open-module>>",
1663 "<<goto-line>>",
1664 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001665 return ""
1666 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001667 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001668 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1669 s = re.sub("Key-", "", s)
1670 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1671 s = re.sub("Control-", "Ctrl-", s)
1672 s = re.sub("-", "+", s)
1673 s = re.sub("><", " ", s)
1674 s = re.sub("<", "", s)
1675 s = re.sub(">", "", s)
1676 return s
1677
1678
1679def fixwordbreaks(root):
1680 # Make sure that Tk's double-click and next/previous word
1681 # operations use our definition of a word (i.e. an identifier)
1682 tk = root.tk
1683 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1684 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1685 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1686
1687
1688def test():
1689 root = Tk()
1690 fixwordbreaks(root)
1691 root.withdraw()
1692 if sys.argv[1:]:
1693 filename = sys.argv[1]
1694 else:
1695 filename = None
1696 edit = EditorWindow(root=root, filename=filename)
1697 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001698 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001699 root.mainloop()
1700 root.destroy()
1701
1702if __name__ == '__main__':
1703 test()