blob: 810f5885585c4f8babf07f43d617ff326818bc1f [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
482 for label, eventname, verify_state in self.rmenu_specs:
483 if verify_state is None:
484 continue
485 state = getattr(self, verify_state)()
486 rmenu.entryconfigure(label, state=state)
487
488
David Scherer7aced172000-08-15 01:13:23 +0000489 rmenu.tk_popup(event.x_root, event.y_root)
490 if iswin:
491 self.text.config(cursor="ibeam")
492
493 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200494 # ("Label", "<<virtual-event>>", "statefuncname"), ...
495 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000496 ]
497
498 def make_rmenu(self):
499 rmenu = Menu(self.text, tearoff=0)
Andrew Svetlovd1837672012-11-01 22:41:19 +0200500 for label, eventname, _ in self.rmenu_specs:
501 if label is not None:
502 def command(text=self.text, eventname=eventname):
503 text.event_generate(eventname)
504 rmenu.add_command(label=label, command=command)
505 else:
506 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000507 self.rmenu = rmenu
508
Andrew Svetlovd1837672012-11-01 22:41:19 +0200509 def rmenu_check_cut(self):
510 return self.rmenu_check_copy()
511
512 def rmenu_check_copy(self):
513 try:
514 indx = self.text.index('sel.first')
515 except TclError:
516 return 'disabled'
517 else:
518 return 'normal' if indx else 'disabled'
519
520 def rmenu_check_paste(self):
521 try:
522 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
523 except TclError:
524 return 'disabled'
525 else:
526 return 'normal'
527
David Scherer7aced172000-08-15 01:13:23 +0000528 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000529 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000530
Steven M. Gava3b55a892001-11-21 05:56:26 +0000531 def config_dialog(self, event=None):
532 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000533
David Scherer7aced172000-08-15 01:13:23 +0000534 def help_dialog(self, event=None):
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500535 if self.root:
536 parent = self.root
537 else:
538 parent = self.top
539 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000540
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000541 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000542 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000543 try:
544 os.startfile(self.help_url)
545 except WindowsError as why:
546 tkMessageBox.showerror(title='Document Start Failure',
547 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000548 else:
549 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000550 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000551
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000552 def cut(self,event):
553 self.text.event_generate("<<Cut>>")
554 return "break"
555
556 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000557 if not self.text.tag_ranges("sel"):
558 # There is no selection, so do nothing and maybe interrupt.
559 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000560 self.text.event_generate("<<Copy>>")
561 return "break"
562
563 def paste(self,event):
564 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000565 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000566 return "break"
567
David Scherer7aced172000-08-15 01:13:23 +0000568 def select_all(self, event=None):
569 self.text.tag_add("sel", "1.0", "end-1c")
570 self.text.mark_set("insert", "1.0")
571 self.text.see("insert")
572 return "break"
573
574 def remove_selection(self, event=None):
575 self.text.tag_remove("sel", "1.0", "end")
576 self.text.see("insert")
577
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000578 def move_at_edge_if_selection(self, edge_index):
579 """Cursor move begins at start or end of selection
580
581 When a left/right cursor key is pressed create and return to Tkinter a
582 function which causes a cursor move from the associated edge of the
583 selection.
584
585 """
586 self_text_index = self.text.index
587 self_text_mark_set = self.text.mark_set
588 edges_table = ("sel.first+1c", "sel.last-1c")
589 def move_at_edge(event):
590 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
591 try:
592 self_text_index("sel.first")
593 self_text_mark_set("insert", edges_table[edge_index])
594 except TclError:
595 pass
596 return move_at_edge
597
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000598 def del_word_left(self, event):
599 self.text.event_generate('<Meta-Delete>')
600 return "break"
601
602 def del_word_right(self, event):
603 self.text.event_generate('<Meta-d>')
604 return "break"
605
Steven M. Gavac5976402002-01-04 03:06:08 +0000606 def find_event(self, event):
607 SearchDialog.find(self.text)
608 return "break"
609
610 def find_again_event(self, event):
611 SearchDialog.find_again(self.text)
612 return "break"
613
614 def find_selection_event(self, event):
615 SearchDialog.find_selection(self.text)
616 return "break"
617
618 def find_in_files_event(self, event):
619 GrepDialog.grep(self.text, self.io, self.flist)
620 return "break"
621
622 def replace_event(self, event):
623 ReplaceDialog.replace(self.text)
624 return "break"
625
626 def goto_line_event(self, event):
627 text = self.text
628 lineno = tkSimpleDialog.askinteger("Goto",
629 "Go to line number:",parent=text)
630 if lineno is None:
631 return "break"
632 if lineno <= 0:
633 text.bell()
634 return "break"
635 text.mark_set("insert", "%d.0" % lineno)
636 text.see("insert")
637
David Scherer7aced172000-08-15 01:13:23 +0000638 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000639 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000640 try:
641 name = self.text.get("sel.first", "sel.last")
642 except TclError:
643 name = ""
644 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000645 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000646 name = tkSimpleDialog.askstring("Module",
647 "Enter the name of a Python module\n"
648 "to search on sys.path and open:",
649 parent=self.text, initialvalue=name)
650 if name:
651 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000652 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000653 return
David Scherer7aced172000-08-15 01:13:23 +0000654 # XXX Ought to insert current file's directory in front of path
655 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000656 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000657 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000658 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
659 return
660 if type != imp.PY_SOURCE:
661 tkMessageBox.showerror("Unsupported type",
662 "%s is not a source module" % name, parent=self.text)
663 return
664 if f:
665 f.close()
666 if self.flist:
667 self.flist.open(file)
668 else:
669 self.io.loadfile(file)
670
671 def open_class_browser(self, event=None):
672 filename = self.io.filename
673 if not filename:
674 tkMessageBox.showerror(
675 "No filename",
676 "This buffer has no associated filename",
677 master=self.text)
678 self.text.focus_set()
679 return None
680 head, tail = os.path.split(filename)
681 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000682 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000683 ClassBrowser.ClassBrowser(self.flist, base, [head])
684
685 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000686 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000687 PathBrowser.PathBrowser(self.flist)
688
689 def gotoline(self, lineno):
690 if lineno is not None and lineno > 0:
691 self.text.mark_set("insert", "%d.0" % lineno)
692 self.text.tag_remove("sel", "1.0", "end")
693 self.text.tag_add("sel", "insert", "insert +1l")
694 self.center()
695
696 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000697 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000698 return True
David Scherer7aced172000-08-15 01:13:23 +0000699 base, ext = os.path.splitext(os.path.basename(filename))
700 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000701 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000702 line = self.text.get('1.0', '1.0 lineend')
703 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000704
705 def close_hook(self):
706 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000707 self.flist.unregister_maybe_terminate(self)
708 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000709
710 def set_close_hook(self, close_hook):
711 self.close_hook = close_hook
712
713 def filename_change_hook(self):
714 if self.flist:
715 self.flist.filename_changed_edit(self)
716 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000717 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000718 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000719
Christian Heimesa156e092008-02-16 07:38:31 +0000720 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000721 if self.color:
722 return
Christian Heimesa156e092008-02-16 07:38:31 +0000723 if self.ispythonsource(self.io.filename):
724 self.color = self.ColorDelegator()
725 # can add more colorizers here...
726 if self.color:
727 self.per.removefilter(self.undo)
728 self.per.insertfilter(self.color)
729 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000730
Christian Heimesa156e092008-02-16 07:38:31 +0000731 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000732 if not self.color:
733 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000734 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000735 self.per.removefilter(self.color)
736 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000737
Steven M. Gavab77d3432002-03-02 07:16:21 +0000738 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000739 "Update the colour theme"
740 # Called from self.filename_change_hook and from configDialog.py
741 self._rmcolorizer()
742 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000743 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000744 normal_colors = idleConf.GetHighlight(theme, 'normal')
745 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
746 select_colors = idleConf.GetHighlight(theme, 'hilite')
747 self.text.config(
748 foreground=normal_colors['foreground'],
749 background=normal_colors['background'],
750 insertbackground=cursor_color,
751 selectforeground=select_colors['foreground'],
752 selectbackground=select_colors['background'],
753 )
David Scherer7aced172000-08-15 01:13:23 +0000754
Guido van Rossum33d26892007-08-05 15:29:28 +0000755 IDENTCHARS = string.ascii_letters + string.digits + "_"
756
757 def colorize_syntax_error(self, text, pos):
758 text.tag_add("ERROR", pos)
759 char = text.get(pos)
760 if char and char in self.IDENTCHARS:
761 text.tag_add("ERROR", pos + " wordstart", pos)
762 if '\n' == text.get(pos): # error at line end
763 text.mark_set("insert", pos)
764 else:
765 text.mark_set("insert", pos + "+1c")
766 text.see(pos)
767
Steven M. Gavab1585412002-03-12 00:21:56 +0000768 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000769 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000770 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000771 fontWeight='normal'
772 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
773 fontWeight='bold'
774 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200775 idleConf.GetOption('main','EditorWindow','font-size',
776 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000777 fontWeight))
778
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000779 def RemoveKeybindings(self):
780 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000781 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000782 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000783 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000784 self.text.event_delete(event, *keylist)
785 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000786 xkeydefs = idleConf.GetExtensionBindings(extensionName)
787 if xkeydefs:
788 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000789 self.text.event_delete(event, *keylist)
790
791 def ApplyKeybindings(self):
792 "Update the keybindings after they are changed"
793 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000794 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000795 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000796 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000797 xkeydefs = idleConf.GetExtensionBindings(extensionName)
798 if xkeydefs:
799 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000800 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000801 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000802 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000803 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000804 for item in menu[1]:
805 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000806 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000807 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000808 menu = self.menudict[menubarItem]
809 end = menu.index(END) + 1
810 for index in range(0, end):
811 if menu.type(index) == 'command':
812 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000813 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000814 itemName = menu.entrycget(index, 'label')
815 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000816 if menubarItem in menuEventDict:
817 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000818 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000819 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000820 accel = get_accelerator(keydefs, event)
821 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000822
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000823 def set_notabs_indentwidth(self):
824 "Update the indentwidth if changed and not using tabs in this window"
825 # Called from configDialog.py
826 if not self.usetabs:
827 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
828 type='int')
829
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000830 def reset_help_menu_entries(self):
831 "Update the additional help entries on the Help menu"
832 help_list = idleConf.GetAllExtraHelpSourcesList()
833 helpmenu = self.menudict['help']
834 # first delete the extra help entries, if any
835 helpmenu_length = helpmenu.index(END)
836 if helpmenu_length > self.base_helpmenu_length:
837 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
838 # then rebuild them
839 if help_list:
840 helpmenu.add_separator()
841 for entry in help_list:
842 cmd = self.__extra_help_callback(entry[1])
843 helpmenu.add_command(label=entry[0], command=cmd)
844 # and update the menu dictionary
845 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000846
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000847 def __extra_help_callback(self, helpfile):
848 "Create a callback with the helpfile value frozen at definition time"
849 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000850 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000851 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000852 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000853 try:
854 os.startfile(helpfile)
855 except WindowsError as why:
856 tkMessageBox.showerror(title='Document Start Failure',
857 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000858 else:
859 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000860 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000861
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000862 def update_recent_files_list(self, new_file=None):
863 "Load and update the recent files list and menus"
864 rf_list = []
865 if os.path.exists(self.recent_files_path):
Ned Deily122539e2011-01-24 21:46:44 +0000866 rf_list_file = open(self.recent_files_path,'r',
867 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000868 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000869 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000870 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000871 rf_list_file.close()
872 if new_file:
873 new_file = os.path.abspath(new_file) + '\n'
874 if new_file in rf_list:
875 rf_list.remove(new_file) # move to top
876 rf_list.insert(0, new_file)
877 # clean and save the recent files list
878 bad_paths = []
879 for path in rf_list:
880 if '\0' in path or not os.path.exists(path[0:-1]):
881 bad_paths.append(path)
882 rf_list = [path for path in rf_list if path not in bad_paths]
883 ulchars = "1234567890ABCDEFGHIJK"
884 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000885 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800886 with open(self.recent_files_path, 'w',
887 encoding='utf_8', errors='replace') as rf_file:
888 rf_file.writelines(rf_list)
889 except IOError as err:
890 if not getattr(self.root, "recentfilelist_error_displayed", False):
891 self.root.recentfilelist_error_displayed = True
892 tkMessageBox.showerror(title='IDLE Error',
893 message='Unable to update Recent Files list:\n%s'
894 % str(err),
895 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000896 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000897 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000898 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700899 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000900 for i, file_name in enumerate(rf_list):
901 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000902 # make unicode string to display non-ASCII chars correctly
903 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000904 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000905 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000906 command=callback,
907 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000908
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000909 def __recent_file_callback(self, file_name):
910 def open_recent_file(fn_closure=file_name):
911 self.io.open(editFile=fn_closure)
912 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000913
David Scherer7aced172000-08-15 01:13:23 +0000914 def saved_change_hook(self):
915 short = self.short_title()
916 long = self.long_title()
917 if short and long:
918 title = short + " - " + long
919 elif short:
920 title = short
921 elif long:
922 title = long
923 else:
924 title = "Untitled"
925 icon = short or long or title
926 if not self.get_saved():
927 title = "*%s*" % title
928 icon = "*%s" % icon
929 self.top.wm_title(title)
930 self.top.wm_iconname(icon)
931
932 def get_saved(self):
933 return self.undo.get_saved()
934
935 def set_saved(self, flag):
936 self.undo.set_saved(flag)
937
938 def reset_undo(self):
939 self.undo.reset_undo()
940
941 def short_title(self):
942 filename = self.io.filename
943 if filename:
944 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000945 # return unicode string to display non-ASCII chars correctly
946 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000947
948 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000949 # return unicode string to display non-ASCII chars correctly
950 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000951
952 def center_insert_event(self, event):
953 self.center()
954
955 def center(self, mark="insert"):
956 text = self.text
957 top, bot = self.getwindowlines()
958 lineno = self.getlineno(mark)
959 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000960 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000961 text.yview(float(newtop))
962
963 def getwindowlines(self):
964 text = self.text
965 top = self.getlineno("@0,0")
966 bot = self.getlineno("@0,65535")
967 if top == bot and text.winfo_height() == 1:
968 # Geometry manager hasn't run yet
969 height = int(text['height'])
970 bot = top + height - 1
971 return top, bot
972
973 def getlineno(self, mark="insert"):
974 text = self.text
975 return int(float(text.index(mark)))
976
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000977 def get_geometry(self):
978 "Return (width, height, x, y)"
979 geom = self.top.wm_geometry()
980 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000981 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000982
David Scherer7aced172000-08-15 01:13:23 +0000983 def close_event(self, event):
984 self.close()
985
986 def maybesave(self):
987 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000988 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000989 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000990 self.top.deiconify()
991 self.top.lower()
992 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000993 return self.io.maybesave()
994
995 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000996 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000997 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000998 self._close()
999 return reply
1000
1001 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001002 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001003 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001004 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001005 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001006 self.io.close()
1007 self.io = None
1008 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001009 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001010 self.color.close(False)
1011 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001012 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001013 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001014 self.per.close()
1015 self.per = None
1016 self.top.destroy()
1017 if self.close_hook:
1018 # unless override: unregister from flist, terminate if last window
1019 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001020
1021 def load_extensions(self):
1022 self.extensions = {}
1023 self.load_standard_extensions()
1024
1025 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001026 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001027 if hasattr(ins, "close"):
1028 ins.close()
1029 self.extensions = {}
1030
1031 def load_standard_extensions(self):
1032 for name in self.get_standard_extension_names():
1033 try:
1034 self.load_extension(name)
1035 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001036 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001037 traceback.print_exc()
1038
1039 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001040 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001041
1042 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001043 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001044 try:
1045 mod = importlib.import_module('.' + name, package=__package__)
1046 except ImportError:
1047 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001048 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001049 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001050 raise
David Scherer7aced172000-08-15 01:13:23 +00001051 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001052 keydefs = idleConf.GetExtensionBindings(name)
1053 if hasattr(cls, "menudefs"):
1054 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001055 ins = cls(self)
1056 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001057 if keydefs:
1058 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001059 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001060 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001061 while methodname[:1] == '<':
1062 methodname = methodname[1:]
1063 while methodname[-1:] == '>':
1064 methodname = methodname[:-1]
1065 methodname = methodname + "_event"
1066 if hasattr(ins, methodname):
1067 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001068
1069 def apply_bindings(self, keydefs=None):
1070 if keydefs is None:
1071 keydefs = self.Bindings.default_keydefs
1072 text = self.text
1073 text.keydefs = keydefs
1074 for event, keylist in keydefs.items():
1075 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001076 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001077
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001078 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001079 """Add appropriate entries to the menus and submenus
1080
1081 Menus that are absent or None in self.menudict are ignored.
1082 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001083 if menudefs is None:
1084 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001085 if keydefs is None:
1086 keydefs = self.Bindings.default_keydefs
1087 menudict = self.menudict
1088 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001089 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001090 menu = menudict.get(mname)
1091 if not menu:
1092 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001093 for entry in entrylist:
1094 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001095 menu.add_separator()
1096 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001097 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001098 checkbutton = (label[:1] == '!')
1099 if checkbutton:
1100 label = label[1:]
1101 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001102 accelerator = get_accelerator(keydefs, eventname)
1103 def command(text=text, eventname=eventname):
1104 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001105 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001106 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001107 menu.add_checkbutton(label=label, underline=underline,
1108 command=command, accelerator=accelerator,
1109 variable=var)
1110 else:
1111 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001112 command=command,
1113 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001114
1115 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001116 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001117 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001118 value = var.get()
1119 return value
1120 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001121 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001122
1123 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001124 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001125 if var:
1126 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001127 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001128 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001129
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001130 def get_var_obj(self, name, vartype=None):
1131 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001132 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001133 # create a Tkinter variable object with self.text as master:
1134 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001135 return var
1136
1137 # Tk implementations of "virtual text methods" -- each platform
1138 # reusing IDLE's support code needs to define these for its GUI's
1139 # flavor of widget.
1140
1141 # Is character at text_index in a Python string? Return 0 for
1142 # "guaranteed no", true for anything else. This info is expensive
1143 # to compute ab initio, but is probably already known by the
1144 # platform's colorizer.
1145
1146 def is_char_in_string(self, text_index):
1147 if self.color:
1148 # Return true iff colorizer hasn't (re)gotten this far
1149 # yet, or the character is tagged as being in a string
1150 return self.text.tag_prevrange("TODO", text_index) or \
1151 "STRING" in self.text.tag_names(text_index)
1152 else:
1153 # The colorizer is missing: assume the worst
1154 return 1
1155
1156 # If a selection is defined in the text widget, return (start,
1157 # end) as Tkinter text indices, otherwise return (None, None)
1158 def get_selection_indices(self):
1159 try:
1160 first = self.text.index("sel.first")
1161 last = self.text.index("sel.last")
1162 return first, last
1163 except TclError:
1164 return None, None
1165
1166 # Return the text widget's current view of what a tab stop means
1167 # (equivalent width in spaces).
1168
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001169 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001170 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1171 return int(current)
1172
1173 # Set the text widget's current view of what a tab stop means.
1174
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001175 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001176 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001177 if self.get_tk_tabwidth() != newtabwidth:
1178 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001179 pixels = text.tk.call("font", "measure", text["font"],
1180 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001181 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001182 text.configure(tabs=pixels)
1183
Guido van Rossum33d26892007-08-05 15:29:28 +00001184### begin autoindent code ### (configuration was moved to beginning of class)
1185
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001186 def set_indentation_params(self, is_py_src, guess=True):
1187 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001188 i = self.guess_indent()
1189 if 2 <= i <= 8:
1190 self.indentwidth = i
1191 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001192 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001193 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001194
1195 def smart_backspace_event(self, event):
1196 text = self.text
1197 first, last = self.get_selection_indices()
1198 if first and last:
1199 text.delete(first, last)
1200 text.mark_set("insert", first)
1201 return "break"
1202 # Delete whitespace left, until hitting a real char or closest
1203 # preceding virtual tab stop.
1204 chars = text.get("insert linestart", "insert")
1205 if chars == '':
1206 if text.compare("insert", ">", "1.0"):
1207 # easy: delete preceding newline
1208 text.delete("insert-1c")
1209 else:
1210 text.bell() # at start of buffer
1211 return "break"
1212 if chars[-1] not in " \t":
1213 # easy: delete preceding real char
1214 text.delete("insert-1c")
1215 return "break"
1216 # Ick. It may require *inserting* spaces if we back up over a
1217 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001218 tabwidth = self.tabwidth
1219 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001220 assert have > 0
1221 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001222 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001223 if self.context_use_ps1:
1224 last_line_of_prompt = sys.ps1.split('\n')[-1]
1225 else:
1226 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001227 ncharsdeleted = 0
1228 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001229 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001230 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001231 chars = chars[:-1]
1232 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001233 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001234 if have <= want or chars[-1] not in " \t":
1235 break
1236 text.undo_block_start()
1237 text.delete("insert-%dc" % ncharsdeleted, "insert")
1238 if have < want:
1239 text.insert("insert", ' ' * (want - have))
1240 text.undo_block_stop()
1241 return "break"
1242
1243 def smart_indent_event(self, event):
1244 # if intraline selection:
1245 # delete it
1246 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001247 # do indent-region
1248 # else:
1249 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001250 text = self.text
1251 first, last = self.get_selection_indices()
1252 text.undo_block_start()
1253 try:
1254 if first and last:
1255 if index2line(first) != index2line(last):
1256 return self.indent_region_event(event)
1257 text.delete(first, last)
1258 text.mark_set("insert", first)
1259 prefix = text.get("insert linestart", "insert")
1260 raw, effective = classifyws(prefix, self.tabwidth)
1261 if raw == len(prefix):
1262 # only whitespace to the left
1263 self.reindent_to(effective + self.indentwidth)
1264 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001265 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001266 if self.usetabs:
1267 pad = '\t'
1268 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001269 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001270 n = self.indentwidth
1271 pad = ' ' * (n - effective % n)
1272 text.insert("insert", pad)
1273 text.see("insert")
1274 return "break"
1275 finally:
1276 text.undo_block_stop()
1277
1278 def newline_and_indent_event(self, event):
1279 text = self.text
1280 first, last = self.get_selection_indices()
1281 text.undo_block_start()
1282 try:
1283 if first and last:
1284 text.delete(first, last)
1285 text.mark_set("insert", first)
1286 line = text.get("insert linestart", "insert")
1287 i, n = 0, len(line)
1288 while i < n and line[i] in " \t":
1289 i = i+1
1290 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001291 # the cursor is in or at leading indentation in a continuation
1292 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001293 text.insert("insert linestart", '\n')
1294 return "break"
1295 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001296 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001297 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001298 last_line_of_prompt = sys.ps1.split('\n')[-1]
1299 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001300 line = line[:-1]
1301 i = i+1
1302 if i:
1303 text.delete("insert - %d chars" % i, "insert")
1304 # strip whitespace after insert point
1305 while text.get("insert") in " \t":
1306 text.delete("insert")
1307 # start new line
1308 text.insert("insert", '\n')
1309
1310 # adjust indentation for continuations and block
1311 # open/close first need to find the last stmt
1312 lno = index2line(text.index('insert'))
1313 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001314 if not self.context_use_ps1:
1315 for context in self.num_context_lines:
1316 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001317 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001318 rawtext = text.get(startatindex, "insert")
1319 y.set_str(rawtext)
1320 bod = y.find_good_parse_start(
1321 self.context_use_ps1,
1322 self._build_char_in_string_func(startatindex))
1323 if bod is not None or startat == 1:
1324 break
1325 y.set_lo(bod or 0)
1326 else:
1327 r = text.tag_prevrange("console", "insert")
1328 if r:
1329 startatindex = r[1]
1330 else:
1331 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001332 rawtext = text.get(startatindex, "insert")
1333 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001334 y.set_lo(0)
1335
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001336 c = y.get_continuation_type()
1337 if c != PyParse.C_NONE:
1338 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001339 if c == PyParse.C_STRING_FIRST_LINE:
1340 # after the first line of a string; do not indent at all
1341 pass
1342 elif c == PyParse.C_STRING_NEXT_LINES:
1343 # inside a string which started before this line;
1344 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001345 text.insert("insert", indent)
1346 elif c == PyParse.C_BRACKET:
1347 # line up with the first (if any) element of the
1348 # last open bracket structure; else indent one
1349 # level beyond the indent of the line with the
1350 # last open bracket
1351 self.reindent_to(y.compute_bracket_indent())
1352 elif c == PyParse.C_BACKSLASH:
1353 # if more than one line in this stmt already, just
1354 # mimic the current indent; else if initial line
1355 # has a start on an assignment stmt, indent to
1356 # beyond leftmost =; else to beyond first chunk of
1357 # non-whitespace on initial line
1358 if y.get_num_lines_in_stmt() > 1:
1359 text.insert("insert", indent)
1360 else:
1361 self.reindent_to(y.compute_backslash_indent())
1362 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001363 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001364 return "break"
1365
1366 # This line starts a brand new stmt; indent relative to
1367 # indentation of initial line of closest preceding
1368 # interesting stmt.
1369 indent = y.get_base_indent_string()
1370 text.insert("insert", indent)
1371 if y.is_block_opener():
1372 self.smart_indent_event(event)
1373 elif indent and y.is_block_closer():
1374 self.smart_backspace_event(event)
1375 return "break"
1376 finally:
1377 text.see("insert")
1378 text.undo_block_stop()
1379
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001380 # Our editwin provides a is_char_in_string function that works
1381 # with a Tk text index, but PyParse only knows about offsets into
1382 # a string. This builds a function for PyParse that accepts an
1383 # offset.
1384
1385 def _build_char_in_string_func(self, startindex):
1386 def inner(offset, _startindex=startindex,
1387 _icis=self.is_char_in_string):
1388 return _icis(_startindex + "+%dc" % offset)
1389 return inner
1390
1391 def indent_region_event(self, event):
1392 head, tail, chars, lines = self.get_region()
1393 for pos in range(len(lines)):
1394 line = lines[pos]
1395 if line:
1396 raw, effective = classifyws(line, self.tabwidth)
1397 effective = effective + self.indentwidth
1398 lines[pos] = self._make_blanks(effective) + line[raw:]
1399 self.set_region(head, tail, chars, lines)
1400 return "break"
1401
1402 def dedent_region_event(self, event):
1403 head, tail, chars, lines = self.get_region()
1404 for pos in range(len(lines)):
1405 line = lines[pos]
1406 if line:
1407 raw, effective = classifyws(line, self.tabwidth)
1408 effective = max(effective - self.indentwidth, 0)
1409 lines[pos] = self._make_blanks(effective) + line[raw:]
1410 self.set_region(head, tail, chars, lines)
1411 return "break"
1412
1413 def comment_region_event(self, event):
1414 head, tail, chars, lines = self.get_region()
1415 for pos in range(len(lines) - 1):
1416 line = lines[pos]
1417 lines[pos] = '##' + line
1418 self.set_region(head, tail, chars, lines)
1419
1420 def uncomment_region_event(self, event):
1421 head, tail, chars, lines = self.get_region()
1422 for pos in range(len(lines)):
1423 line = lines[pos]
1424 if not line:
1425 continue
1426 if line[:2] == '##':
1427 line = line[2:]
1428 elif line[:1] == '#':
1429 line = line[1:]
1430 lines[pos] = line
1431 self.set_region(head, tail, chars, lines)
1432
1433 def tabify_region_event(self, event):
1434 head, tail, chars, lines = self.get_region()
1435 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001436 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001437 for pos in range(len(lines)):
1438 line = lines[pos]
1439 if line:
1440 raw, effective = classifyws(line, tabwidth)
1441 ntabs, nspaces = divmod(effective, tabwidth)
1442 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1443 self.set_region(head, tail, chars, lines)
1444
1445 def untabify_region_event(self, event):
1446 head, tail, chars, lines = self.get_region()
1447 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001448 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001449 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001450 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001451 self.set_region(head, tail, chars, lines)
1452
1453 def toggle_tabs_event(self, event):
1454 if self.askyesno(
1455 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001456 "Turn tabs " + ("on", "off")[self.usetabs] +
1457 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001458 ("will be", "remains at")[self.usetabs] + " 8." +
1459 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001460 parent=self.text):
1461 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001462 # Try to prevent inconsistent indentation.
1463 # User must change indent width manually after using tabs.
1464 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001465 return "break"
1466
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001467 # XXX this isn't bound to anything -- see tabwidth comments
1468## def change_tabwidth_event(self, event):
1469## new = self._asktabwidth()
1470## if new != self.tabwidth:
1471## self.tabwidth = new
1472## self.set_indentation_params(0, guess=0)
1473## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001474
1475 def change_indentwidth_event(self, event):
1476 new = self.askinteger(
1477 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001478 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001479 parent=self.text,
1480 initialvalue=self.indentwidth,
1481 minvalue=2,
1482 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001483 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001484 self.indentwidth = new
1485 return "break"
1486
1487 def get_region(self):
1488 text = self.text
1489 first, last = self.get_selection_indices()
1490 if first and last:
1491 head = text.index(first + " linestart")
1492 tail = text.index(last + "-1c lineend +1c")
1493 else:
1494 head = text.index("insert linestart")
1495 tail = text.index("insert lineend +1c")
1496 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001497 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001498 return head, tail, chars, lines
1499
1500 def set_region(self, head, tail, chars, lines):
1501 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001502 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001503 if newchars == chars:
1504 text.bell()
1505 return
1506 text.tag_remove("sel", "1.0", "end")
1507 text.mark_set("insert", head)
1508 text.undo_block_start()
1509 text.delete(head, tail)
1510 text.insert(head, newchars)
1511 text.undo_block_stop()
1512 text.tag_add("sel", head, "insert")
1513
1514 # Make string that displays as n leading blanks.
1515
1516 def _make_blanks(self, n):
1517 if self.usetabs:
1518 ntabs, nspaces = divmod(n, self.tabwidth)
1519 return '\t' * ntabs + ' ' * nspaces
1520 else:
1521 return ' ' * n
1522
1523 # Delete from beginning of line to insert point, then reinsert
1524 # column logical (meaning use tabs if appropriate) spaces.
1525
1526 def reindent_to(self, column):
1527 text = self.text
1528 text.undo_block_start()
1529 if text.compare("insert linestart", "!=", "insert"):
1530 text.delete("insert linestart", "insert")
1531 if column:
1532 text.insert("insert", self._make_blanks(column))
1533 text.undo_block_stop()
1534
1535 def _asktabwidth(self):
1536 return self.askinteger(
1537 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001538 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001539 parent=self.text,
1540 initialvalue=self.indentwidth,
1541 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001542 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001543
1544 # Guess indentwidth from text content.
1545 # Return guessed indentwidth. This should not be believed unless
1546 # it's in a reasonable range (e.g., it will be 0 if no indented
1547 # blocks are found).
1548
1549 def guess_indent(self):
1550 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1551 if opener and indented:
1552 raw, indentsmall = classifyws(opener, self.tabwidth)
1553 raw, indentlarge = classifyws(indented, self.tabwidth)
1554 else:
1555 indentsmall = indentlarge = 0
1556 return indentlarge - indentsmall
1557
1558# "line.col" -> line, as an int
1559def index2line(index):
1560 return int(float(index))
1561
1562# Look at the leading whitespace in s.
1563# Return pair (# of leading ws characters,
1564# effective # of leading blanks after expanding
1565# tabs to width tabwidth)
1566
1567def classifyws(s, tabwidth):
1568 raw = effective = 0
1569 for ch in s:
1570 if ch == ' ':
1571 raw = raw + 1
1572 effective = effective + 1
1573 elif ch == '\t':
1574 raw = raw + 1
1575 effective = (effective // tabwidth + 1) * tabwidth
1576 else:
1577 break
1578 return raw, effective
1579
1580import tokenize
1581_tokenize = tokenize
1582del tokenize
1583
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001584class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001585
1586 # .run() chews over the Text widget, looking for a block opener
1587 # and the stmt following it. Returns a pair,
1588 # (line containing block opener, line containing stmt)
1589 # Either or both may be None.
1590
1591 def __init__(self, text, tabwidth):
1592 self.text = text
1593 self.tabwidth = tabwidth
1594 self.i = self.finished = 0
1595 self.blkopenline = self.indentedline = None
1596
1597 def readline(self):
1598 if self.finished:
1599 return ""
1600 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001601 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001602 if self.text.compare(mark, ">=", "end"):
1603 return ""
1604 return self.text.get(mark, mark + " lineend+1c")
1605
1606 def tokeneater(self, type, token, start, end, line,
1607 INDENT=_tokenize.INDENT,
1608 NAME=_tokenize.NAME,
1609 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1610 if self.finished:
1611 pass
1612 elif type == NAME and token in OPENERS:
1613 self.blkopenline = line
1614 elif type == INDENT and self.blkopenline:
1615 self.indentedline = line
1616 self.finished = 1
1617
1618 def run(self):
1619 save_tabsize = _tokenize.tabsize
1620 _tokenize.tabsize = self.tabwidth
1621 try:
1622 try:
Trent Nelson428de652008-03-18 22:41:35 +00001623 tokens = _tokenize.generate_tokens(self.readline)
1624 for token in tokens:
1625 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001626 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001627 # since we cut off the tokenizer early, we can trigger
1628 # spurious errors
1629 pass
1630 finally:
1631 _tokenize.tabsize = save_tabsize
1632 return self.blkopenline, self.indentedline
1633
1634### end autoindent code ###
1635
David Scherer7aced172000-08-15 01:13:23 +00001636def prepstr(s):
1637 # Helper to extract the underscore from a string, e.g.
1638 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001639 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001640 if i >= 0:
1641 s = s[:i] + s[i+1:]
1642 return i, s
1643
1644
1645keynames = {
1646 'bracketleft': '[',
1647 'bracketright': ']',
1648 'slash': '/',
1649}
1650
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001651def get_accelerator(keydefs, eventname):
1652 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001653 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1654 # if not keylist:
1655 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1656 "<<open-module>>",
1657 "<<goto-line>>",
1658 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001659 return ""
1660 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001661 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001662 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1663 s = re.sub("Key-", "", s)
1664 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1665 s = re.sub("Control-", "Ctrl-", s)
1666 s = re.sub("-", "+", s)
1667 s = re.sub("><", " ", s)
1668 s = re.sub("<", "", s)
1669 s = re.sub(">", "", s)
1670 return s
1671
1672
1673def fixwordbreaks(root):
1674 # Make sure that Tk's double-click and next/previous word
1675 # operations use our definition of a word (i.e. an identifier)
1676 tk = root.tk
1677 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1678 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1679 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1680
1681
1682def test():
1683 root = Tk()
1684 fixwordbreaks(root)
1685 root.withdraw()
1686 if sys.argv[1:]:
1687 filename = sys.argv[1]
1688 else:
1689 filename = None
1690 edit = EditorWindow(root=root, filename=filename)
1691 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001692 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001693 root.mainloop()
1694 root.destroy()
1695
1696if __name__ == '__main__':
1697 test()