blob: 27c989cb117e0d0869b3f0c250cba3328e3345f7 [file] [log] [blame]
Brett Cannonaef82d32012-04-14 20:44:23 -04001import imp
2import importlib
David Scherer7aced172000-08-15 01:13:23 +00003import os
David Scherer7aced172000-08-15 01:13:23 +00004import re
Guido van Rossum33d26892007-08-05 15:29:28 +00005import string
Brett Cannonaef82d32012-04-14 20:44:23 -04006import sys
Georg Brandl14fc4272008-05-17 18:39:55 +00007from tkinter import *
8import tkinter.simpledialog as tkSimpleDialog
9import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +000010import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000011import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000012
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000013from idlelib.MultiCall import MultiCallCreator
14from idlelib import idlever
15from idlelib import WindowList
16from idlelib import SearchDialog
17from idlelib import GrepDialog
18from idlelib import ReplaceDialog
19from idlelib import PyParse
20from idlelib.configHandler import idleConf
21from idlelib import aboutDialog, textView, configDialog
22from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000023
24# The default tab setting for a Text widget, in average-width characters.
25TK_TABWIDTH_DEFAULT = 8
26
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000027def _sphinx_version():
28 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
29 major, minor, micro, level, serial = sys.version_info
30 release = '%s%s' % (major, minor)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020031 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000032 if level == 'candidate':
33 release += 'rc%s' % (serial,)
34 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000035 release += '%s%s' % (level[0], serial)
36 return release
37
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000038def _find_module(fullname, path=None):
39 """Version of imp.find_module() that handles hierarchical module names"""
40
41 file = None
42 for tgt in fullname.split('.'):
43 if file is not None:
44 file.close() # close intermediate files
45 (file, filename, descr) = imp.find_module(tgt, path)
46 if descr[2] == imp.PY_SOURCE:
47 break # find but not load the source file
48 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000049 try:
50 path = module.__path__
51 except AttributeError:
Kurt B. Kaiserad667422007-08-23 01:06:15 +000052 raise ImportError('No source for module ' + module.__name__)
Raymond Hettingerf6445e82011-04-12 18:30:14 -070053 if descr[2] != imp.PY_SOURCE:
54 # If all of the above fails and didn't raise an exception,fallback
55 # to a straight import which can find __init__.py in a package.
56 m = __import__(fullname)
57 try:
58 filename = m.__file__
59 except AttributeError:
60 pass
61 else:
62 file = None
Raymond Hettinger2df393c2011-04-12 18:57:55 -070063 descr = os.path.splitext(filename)[1], None, imp.PY_SOURCE
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000064 return file, filename, descr
65
Terry Jan Reedye91e7632012-02-05 15:14:20 -050066
67class HelpDialog(object):
68
69 def __init__(self):
70 self.parent = None # parent of help window
71 self.dlg = None # the help window iteself
72
73 def display(self, parent, near=None):
74 """ Display the help dialog.
75
76 parent - parent widget for the help window
77
78 near - a Toplevel widget (e.g. EditorWindow or PyShell)
79 to use as a reference for placing the help window
80 """
81 if self.dlg is None:
82 self.show_dialog(parent)
83 if near:
84 self.nearwindow(near)
85
86 def show_dialog(self, parent):
87 self.parent = parent
88 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
89 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
90 dlg.bind('<Destroy>', self.destroy, '+')
91
92 def nearwindow(self, near):
93 # Place the help dialog near the window specified by parent.
94 # Note - this may not reposition the window in Metacity
95 # if "/apps/metacity/general/disable_workarounds" is enabled
96 dlg = self.dlg
97 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
98 dlg.withdraw()
99 dlg.geometry("=+%d+%d" % geom)
100 dlg.deiconify()
101 dlg.lift()
102
103 def destroy(self, ev=None):
104 self.dlg = None
105 self.parent = None
106
107helpDialog = HelpDialog() # singleton instance
108
109
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +0000110class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000111 from idlelib.Percolator import Percolator
112 from idlelib.ColorDelegator import ColorDelegator
113 from idlelib.UndoDelegator import UndoDelegator
114 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
115 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +0000116 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000117 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +0000118
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000119 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000120
121 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000122 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100123 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000124 if sys.platform.count('linux'):
125 # look for html docs in a couple of standard places
126 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
127 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
128 dochome = '/var/www/html/python/index.html'
129 else:
130 basepath = '/usr/share/doc/' # standard location
131 dochome = os.path.join(basepath, pyver,
132 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000133 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100134 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +0000135 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000136 if os.path.isfile(chmfile):
137 dochome = chmfile
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000138 elif macosxSupport.runningAsOSXApp():
139 # documentation is stored inside the python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100140 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000141 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000142 dochome = os.path.normpath(dochome)
143 if os.path.isfile(dochome):
144 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000145 if sys.platform == 'darwin':
146 # Safari requires real file:-URLs
147 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000148 else:
Benjamin Peterson152b6572009-01-20 15:01:54 +0000149 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000150 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +0000151 self.flist = flist
152 root = root or flist.root
153 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000154 try:
155 sys.ps1
156 except AttributeError:
157 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000158 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000159 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000160 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000161 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200162 #self.top.instance_dict makes flist.inversedict available to
163 #configDialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000164 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000165 else:
166 self.tkinter_vars = {} # keys: Tkinter event names
167 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000168 self.top.instance_dict = {}
169 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000170 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000171 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000172 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200173 self.width = idleConf.GetOption('main', 'EditorWindow',
174 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000175 text_options = {
176 'name': 'text',
177 'padx': 5,
178 'wrap': 'none',
179 'width': self.width,
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200180 'height': idleConf.GetOption('main', 'EditorWindow',
181 'height', type='int')}
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000182 if TkVersion >= 8.5:
183 # Starting with tk 8.5 we have to set the new tabstyle option
184 # to 'wordprocessor' to achieve the same display of tabs as in
185 # older tk versions.
186 text_options['tabstyle'] = 'wordprocessor'
187 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000188 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000189
190 self.createmenubar()
191 self.apply_bindings()
192
193 self.top.protocol("WM_DELETE_WINDOW", self.close)
194 self.top.bind("<<close-window>>", self.close_event)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000195 if macosxSupport.runningAsOSXApp():
196 # Command-W on editorwindows doesn't work without this.
197 text.bind('<<close-window>>', self.close_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000198 # Some OS X systems have only one mouse button,
199 # so use control-click for pulldown menus there.
200 # (Note, AquaTk defines <2> as the right button if
201 # present and the Tk Text widget already binds <2>.)
202 text.bind("<Control-Button-1>",self.right_menu_event)
203 else:
204 # Elsewhere, use right-click for pulldown menus.
205 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000206 text.bind("<<cut>>", self.cut)
207 text.bind("<<copy>>", self.copy)
208 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000209 text.bind("<<center-insert>>", self.center_insert_event)
210 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000211 text.bind("<<python-docs>>", self.python_docs)
212 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000213 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000214 text.bind("<<open-module>>", self.open_module)
215 text.bind("<<do-nothing>>", lambda event: "break")
216 text.bind("<<select-all>>", self.select_all)
217 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000218 text.bind("<<find>>", self.find_event)
219 text.bind("<<find-again>>", self.find_again_event)
220 text.bind("<<find-in-files>>", self.find_in_files_event)
221 text.bind("<<find-selection>>", self.find_selection_event)
222 text.bind("<<replace>>", self.replace_event)
223 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000224 text.bind("<<smart-backspace>>",self.smart_backspace_event)
225 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
226 text.bind("<<smart-indent>>",self.smart_indent_event)
227 text.bind("<<indent-region>>",self.indent_region_event)
228 text.bind("<<dedent-region>>",self.dedent_region_event)
229 text.bind("<<comment-region>>",self.comment_region_event)
230 text.bind("<<uncomment-region>>",self.uncomment_region_event)
231 text.bind("<<tabify-region>>",self.tabify_region_event)
232 text.bind("<<untabify-region>>",self.untabify_region_event)
233 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
234 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000235 text.bind("<Left>", self.move_at_edge_if_selection(0))
236 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000237 text.bind("<<del-word-left>>", self.del_word_left)
238 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000239 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000240
David Scherer7aced172000-08-15 01:13:23 +0000241 if flist:
242 flist.inversedict[self] = key
243 if key:
244 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000245 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000246 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
247 text.bind("<<open-class-browser>>", self.open_class_browser)
248 text.bind("<<open-path-browser>>", self.open_path_browser)
249
Steven M. Gava898a3652001-10-07 11:10:44 +0000250 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000251 vbar['command'] = text.yview
252 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000253 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000254 fontWeight = 'normal'
255 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000256 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000257 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200258 idleConf.GetOption('main', 'EditorWindow',
259 'font-size', type='int'),
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000260 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000261 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
262 text.pack(side=TOP, fill=BOTH, expand=1)
263 text.focus_set()
264
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000265 # usetabs true -> literal tab characters are used by indent and
266 # dedent cmds, possibly mixed with spaces if
267 # indentwidth is not a multiple of tabwidth,
268 # which will cause Tabnanny to nag!
269 # false -> tab characters are converted to spaces by indent
270 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000271 # Although use-spaces=0 can be configured manually in config-main.def,
272 # configuration of tabs v. spaces is not supported in the configuration
273 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200274 usespaces = idleConf.GetOption('main', 'Indent',
275 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000276 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000277
278 # tabwidth is the display width of a literal tab character.
279 # CAUTION: telling Tk to use anything other than its default
280 # tab setting causes it to use an entirely different tabbing algorithm,
281 # treating tab stops as fixed distances from the left margin.
282 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000283 self.tabwidth = 8 # must remain 8 until Tk is fixed.
284
285 # indentwidth is the number of screen characters per indent level.
286 # The recommended Python indentation is four spaces.
287 self.indentwidth = self.tabwidth
288 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000289
290 # If context_use_ps1 is true, parsing searches back for a ps1 line;
291 # else searches for a popular (if, def, ...) Python stmt.
292 self.context_use_ps1 = False
293
294 # When searching backwards for a reliable place to begin parsing,
295 # first start num_context_lines[0] lines back, then
296 # num_context_lines[1] lines back if that didn't work, and so on.
297 # The last value should be huge (larger than the # of lines in a
298 # conceivable file).
299 # Making the initial values larger slows things down more often.
300 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000301 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000302 self.undo = undo = self.UndoDelegator()
303 per.insertfilter(undo)
304 text.undo_block_start = undo.undo_block_start
305 text.undo_block_stop = undo.undo_block_stop
306 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000307 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000308 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000309 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000310 self.good_load = False
311 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000312 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000313 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000314 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000315 if io.loadfile(filename):
316 self.good_load = True
317 is_py_src = self.ispythonsource(filename)
318 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000319 else:
320 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500321 self.good_load = True
322
Christian Heimesa156e092008-02-16 07:38:31 +0000323 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000324 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000325 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000326 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000327 menu = self.menudict.get('windows')
328 if menu:
329 end = menu.index("end")
330 if end is None:
331 end = -1
332 if end >= 0:
333 menu.add_separator()
334 end = end + 1
335 self.wmenu_end = end
336 WindowList.register_callback(self.postwindowsmenu)
337
338 # Some abstractions so IDLE extensions are cross-IDE
339 self.askyesno = tkMessageBox.askyesno
340 self.askinteger = tkSimpleDialog.askinteger
341 self.showerror = tkMessageBox.showerror
342
Martin v. Löwis307021f2005-11-27 16:59:04 +0000343 def _filename_to_unicode(self, filename):
344 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000345 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000346 return filename
347 else:
348 try:
349 return filename.decode(self.filesystemencoding)
350 except UnicodeDecodeError:
351 # XXX
352 try:
353 return filename.decode(self.encoding)
354 except UnicodeDecodeError:
355 # byte-to-byte conversion
356 return filename.decode('iso8859-1')
357
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000358 def new_callback(self, event):
359 dirname, basename = self.io.defaultfilename()
360 self.flist.new(dirname)
361 return "break"
362
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000363 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400364 if (event.state & 4) != 0 and event.keysym == "Home":
365 # state&4==Control. If <Control-Home>, use the Tk binding.
366 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000367 if self.text.index("iomark") and \
368 self.text.compare("iomark", "<=", "insert lineend") and \
369 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400370 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000371 insertpt = int(self.text.index("iomark").split(".")[1])
372 else:
373 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000374 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000375 if line[insertpt] not in (' ','\t'):
376 break
377 else:
378 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000379 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000380 if insertpt == lineat:
381 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000382 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000383 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400384 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000385 self.text.tag_remove("sel", "1.0", "end")
386 else:
387 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200388 # there was no previous selection
389 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400390 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200391 if self.text.compare(self.text.index("sel.first"), "<",
392 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400393 self.text.mark_set("my_anchor", "sel.first") # extend back
394 else:
395 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000396 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400397 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000398 if self.text.compare(first,">",last):
399 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000400 self.text.tag_remove("sel", "1.0", "end")
401 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000402 self.text.mark_set("insert", dest)
403 self.text.see("insert")
404 return "break"
405
David Scherer7aced172000-08-15 01:13:23 +0000406 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000407 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000408 if macosxSupport.runningAsOSXApp():
409 # Insert some padding to avoid obscuring some of the statusbar
410 # by the resize widget.
411 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000412 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
413 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
414 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000415 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
416 self.text.event_add("<<set-line-and-column>>",
417 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000418 self.text.after_idle(self.set_line_and_column)
419
420 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000421 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000422 self.status_bar.set_label('column', 'Col: %s' % column)
423 self.status_bar.set_label('line', 'Ln: %s' % line)
424
David Scherer7aced172000-08-15 01:13:23 +0000425 menu_specs = [
426 ("file", "_File"),
427 ("edit", "_Edit"),
428 ("format", "F_ormat"),
429 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000430 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000431 ("windows", "_Windows"),
432 ("help", "_Help"),
433 ]
434
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000435 if macosxSupport.runningAsOSXApp():
436 del menu_specs[-3]
437 menu_specs[-2] = ("windows", "_Window")
438
439
David Scherer7aced172000-08-15 01:13:23 +0000440 def createmenubar(self):
441 mbar = self.menubar
442 self.menudict = menudict = {}
443 for name, label in self.menu_specs:
444 underline, label = prepstr(label)
445 menudict[name] = menu = Menu(mbar, name=name)
446 mbar.add_cascade(label=label, menu=menu, underline=underline)
Georg Brandlaedd2892010-12-19 10:10:32 +0000447 if macosxSupport.isCarbonAquaTk(self.root):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000448 # Insert the application menu
449 menudict['application'] = menu = Menu(mbar, name='apple')
450 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000451 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000452 self.recent_files_menu = Menu(self.menubar)
453 self.menudict['file'].insert_cascade(3, label='Recent Files',
454 underline=0,
455 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000456 self.base_helpmenu_length = self.menudict['help'].index(END)
457 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000458
459 def postwindowsmenu(self):
460 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000461 menu = self.menudict['windows']
462 end = menu.index("end")
463 if end is None:
464 end = -1
465 if end > self.wmenu_end:
466 menu.delete(self.wmenu_end+1, end)
467 WindowList.add_windows_to_menu(menu)
468
469 rmenu = None
470
471 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000472 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
473 if not self.rmenu:
474 self.make_rmenu()
475 rmenu = self.rmenu
476 self.event = event
477 iswin = sys.platform[:3] == 'win'
478 if iswin:
479 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200480
Roger Serwy6b2918a2013-04-07 12:15:52 -0500481 for item in self.rmenu_specs:
482 try:
483 label, eventname, verify_state = item
484 except ValueError: # see issue1207589
485 continue
486
Andrew Svetlovd1837672012-11-01 22:41:19 +0200487 if verify_state is None:
488 continue
489 state = getattr(self, verify_state)()
490 rmenu.entryconfigure(label, state=state)
491
492
David Scherer7aced172000-08-15 01:13:23 +0000493 rmenu.tk_popup(event.x_root, event.y_root)
494 if iswin:
495 self.text.config(cursor="ibeam")
496
497 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200498 # ("Label", "<<virtual-event>>", "statefuncname"), ...
499 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000500 ]
501
502 def make_rmenu(self):
503 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500504 for item in self.rmenu_specs:
505 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200506 if label is not None:
507 def command(text=self.text, eventname=eventname):
508 text.event_generate(eventname)
509 rmenu.add_command(label=label, command=command)
510 else:
511 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000512 self.rmenu = rmenu
513
Andrew Svetlovd1837672012-11-01 22:41:19 +0200514 def rmenu_check_cut(self):
515 return self.rmenu_check_copy()
516
517 def rmenu_check_copy(self):
518 try:
519 indx = self.text.index('sel.first')
520 except TclError:
521 return 'disabled'
522 else:
523 return 'normal' if indx else 'disabled'
524
525 def rmenu_check_paste(self):
526 try:
527 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
528 except TclError:
529 return 'disabled'
530 else:
531 return 'normal'
532
David Scherer7aced172000-08-15 01:13:23 +0000533 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000534 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000535
Steven M. Gava3b55a892001-11-21 05:56:26 +0000536 def config_dialog(self, event=None):
537 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000538
David Scherer7aced172000-08-15 01:13:23 +0000539 def help_dialog(self, event=None):
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500540 if self.root:
541 parent = self.root
542 else:
543 parent = self.top
544 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000545
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000546 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000547 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000548 try:
549 os.startfile(self.help_url)
550 except WindowsError as why:
551 tkMessageBox.showerror(title='Document Start Failure',
552 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000553 else:
554 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000555 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000556
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000557 def cut(self,event):
558 self.text.event_generate("<<Cut>>")
559 return "break"
560
561 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000562 if not self.text.tag_ranges("sel"):
563 # There is no selection, so do nothing and maybe interrupt.
564 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000565 self.text.event_generate("<<Copy>>")
566 return "break"
567
568 def paste(self,event):
569 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000570 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000571 return "break"
572
David Scherer7aced172000-08-15 01:13:23 +0000573 def select_all(self, event=None):
574 self.text.tag_add("sel", "1.0", "end-1c")
575 self.text.mark_set("insert", "1.0")
576 self.text.see("insert")
577 return "break"
578
579 def remove_selection(self, event=None):
580 self.text.tag_remove("sel", "1.0", "end")
581 self.text.see("insert")
582
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000583 def move_at_edge_if_selection(self, edge_index):
584 """Cursor move begins at start or end of selection
585
586 When a left/right cursor key is pressed create and return to Tkinter a
587 function which causes a cursor move from the associated edge of the
588 selection.
589
590 """
591 self_text_index = self.text.index
592 self_text_mark_set = self.text.mark_set
593 edges_table = ("sel.first+1c", "sel.last-1c")
594 def move_at_edge(event):
595 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
596 try:
597 self_text_index("sel.first")
598 self_text_mark_set("insert", edges_table[edge_index])
599 except TclError:
600 pass
601 return move_at_edge
602
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000603 def del_word_left(self, event):
604 self.text.event_generate('<Meta-Delete>')
605 return "break"
606
607 def del_word_right(self, event):
608 self.text.event_generate('<Meta-d>')
609 return "break"
610
Steven M. Gavac5976402002-01-04 03:06:08 +0000611 def find_event(self, event):
612 SearchDialog.find(self.text)
613 return "break"
614
615 def find_again_event(self, event):
616 SearchDialog.find_again(self.text)
617 return "break"
618
619 def find_selection_event(self, event):
620 SearchDialog.find_selection(self.text)
621 return "break"
622
623 def find_in_files_event(self, event):
624 GrepDialog.grep(self.text, self.io, self.flist)
625 return "break"
626
627 def replace_event(self, event):
628 ReplaceDialog.replace(self.text)
629 return "break"
630
631 def goto_line_event(self, event):
632 text = self.text
633 lineno = tkSimpleDialog.askinteger("Goto",
634 "Go to line number:",parent=text)
635 if lineno is None:
636 return "break"
637 if lineno <= 0:
638 text.bell()
639 return "break"
640 text.mark_set("insert", "%d.0" % lineno)
641 text.see("insert")
642
David Scherer7aced172000-08-15 01:13:23 +0000643 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000644 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000645 try:
646 name = self.text.get("sel.first", "sel.last")
647 except TclError:
648 name = ""
649 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000650 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000651 name = tkSimpleDialog.askstring("Module",
652 "Enter the name of a Python module\n"
653 "to search on sys.path and open:",
654 parent=self.text, initialvalue=name)
655 if name:
656 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000657 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000658 return
David Scherer7aced172000-08-15 01:13:23 +0000659 # XXX Ought to insert current file's directory in front of path
660 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000661 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000662 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000663 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
664 return
665 if type != imp.PY_SOURCE:
666 tkMessageBox.showerror("Unsupported type",
667 "%s is not a source module" % name, parent=self.text)
668 return
669 if f:
670 f.close()
671 if self.flist:
672 self.flist.open(file)
673 else:
674 self.io.loadfile(file)
675
676 def open_class_browser(self, event=None):
677 filename = self.io.filename
678 if not filename:
679 tkMessageBox.showerror(
680 "No filename",
681 "This buffer has no associated filename",
682 master=self.text)
683 self.text.focus_set()
684 return None
685 head, tail = os.path.split(filename)
686 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000687 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000688 ClassBrowser.ClassBrowser(self.flist, base, [head])
689
690 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000691 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000692 PathBrowser.PathBrowser(self.flist)
693
694 def gotoline(self, lineno):
695 if lineno is not None and lineno > 0:
696 self.text.mark_set("insert", "%d.0" % lineno)
697 self.text.tag_remove("sel", "1.0", "end")
698 self.text.tag_add("sel", "insert", "insert +1l")
699 self.center()
700
701 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000702 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000703 return True
David Scherer7aced172000-08-15 01:13:23 +0000704 base, ext = os.path.splitext(os.path.basename(filename))
705 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000706 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000707 line = self.text.get('1.0', '1.0 lineend')
708 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000709
710 def close_hook(self):
711 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000712 self.flist.unregister_maybe_terminate(self)
713 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000714
715 def set_close_hook(self, close_hook):
716 self.close_hook = close_hook
717
718 def filename_change_hook(self):
719 if self.flist:
720 self.flist.filename_changed_edit(self)
721 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000722 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000723 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000724
Christian Heimesa156e092008-02-16 07:38:31 +0000725 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000726 if self.color:
727 return
Christian Heimesa156e092008-02-16 07:38:31 +0000728 if self.ispythonsource(self.io.filename):
729 self.color = self.ColorDelegator()
730 # can add more colorizers here...
731 if self.color:
732 self.per.removefilter(self.undo)
733 self.per.insertfilter(self.color)
734 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000735
Christian Heimesa156e092008-02-16 07:38:31 +0000736 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000737 if not self.color:
738 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000739 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000740 self.per.removefilter(self.color)
741 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000742
Steven M. Gavab77d3432002-03-02 07:16:21 +0000743 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000744 "Update the colour theme"
745 # Called from self.filename_change_hook and from configDialog.py
746 self._rmcolorizer()
747 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000748 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000749 normal_colors = idleConf.GetHighlight(theme, 'normal')
750 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
751 select_colors = idleConf.GetHighlight(theme, 'hilite')
752 self.text.config(
753 foreground=normal_colors['foreground'],
754 background=normal_colors['background'],
755 insertbackground=cursor_color,
756 selectforeground=select_colors['foreground'],
757 selectbackground=select_colors['background'],
758 )
David Scherer7aced172000-08-15 01:13:23 +0000759
Guido van Rossum33d26892007-08-05 15:29:28 +0000760 IDENTCHARS = string.ascii_letters + string.digits + "_"
761
762 def colorize_syntax_error(self, text, pos):
763 text.tag_add("ERROR", pos)
764 char = text.get(pos)
765 if char and char in self.IDENTCHARS:
766 text.tag_add("ERROR", pos + " wordstart", pos)
767 if '\n' == text.get(pos): # error at line end
768 text.mark_set("insert", pos)
769 else:
770 text.mark_set("insert", pos + "+1c")
771 text.see(pos)
772
Steven M. Gavab1585412002-03-12 00:21:56 +0000773 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000774 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000775 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000776 fontWeight='normal'
777 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
778 fontWeight='bold'
779 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200780 idleConf.GetOption('main','EditorWindow','font-size',
781 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000782 fontWeight))
783
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000784 def RemoveKeybindings(self):
785 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000786 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000787 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000788 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000789 self.text.event_delete(event, *keylist)
790 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000791 xkeydefs = idleConf.GetExtensionBindings(extensionName)
792 if xkeydefs:
793 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000794 self.text.event_delete(event, *keylist)
795
796 def ApplyKeybindings(self):
797 "Update the keybindings after they are changed"
798 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000799 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000800 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000801 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000802 xkeydefs = idleConf.GetExtensionBindings(extensionName)
803 if xkeydefs:
804 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000805 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000806 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000807 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000808 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000809 for item in menu[1]:
810 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000811 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000812 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000813 menu = self.menudict[menubarItem]
814 end = menu.index(END) + 1
815 for index in range(0, end):
816 if menu.type(index) == 'command':
817 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000818 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000819 itemName = menu.entrycget(index, 'label')
820 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000821 if menubarItem in menuEventDict:
822 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000823 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000824 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000825 accel = get_accelerator(keydefs, event)
826 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000827
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000828 def set_notabs_indentwidth(self):
829 "Update the indentwidth if changed and not using tabs in this window"
830 # Called from configDialog.py
831 if not self.usetabs:
832 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
833 type='int')
834
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000835 def reset_help_menu_entries(self):
836 "Update the additional help entries on the Help menu"
837 help_list = idleConf.GetAllExtraHelpSourcesList()
838 helpmenu = self.menudict['help']
839 # first delete the extra help entries, if any
840 helpmenu_length = helpmenu.index(END)
841 if helpmenu_length > self.base_helpmenu_length:
842 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
843 # then rebuild them
844 if help_list:
845 helpmenu.add_separator()
846 for entry in help_list:
847 cmd = self.__extra_help_callback(entry[1])
848 helpmenu.add_command(label=entry[0], command=cmd)
849 # and update the menu dictionary
850 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000851
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000852 def __extra_help_callback(self, helpfile):
853 "Create a callback with the helpfile value frozen at definition time"
854 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000855 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000856 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000857 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000858 try:
859 os.startfile(helpfile)
860 except WindowsError as why:
861 tkMessageBox.showerror(title='Document Start Failure',
862 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000863 else:
864 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000865 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000866
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000867 def update_recent_files_list(self, new_file=None):
868 "Load and update the recent files list and menus"
869 rf_list = []
870 if os.path.exists(self.recent_files_path):
Ned Deily122539e2011-01-24 21:46:44 +0000871 rf_list_file = open(self.recent_files_path,'r',
872 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000873 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000874 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000875 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000876 rf_list_file.close()
877 if new_file:
878 new_file = os.path.abspath(new_file) + '\n'
879 if new_file in rf_list:
880 rf_list.remove(new_file) # move to top
881 rf_list.insert(0, new_file)
882 # clean and save the recent files list
883 bad_paths = []
884 for path in rf_list:
885 if '\0' in path or not os.path.exists(path[0:-1]):
886 bad_paths.append(path)
887 rf_list = [path for path in rf_list if path not in bad_paths]
888 ulchars = "1234567890ABCDEFGHIJK"
889 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000890 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800891 with open(self.recent_files_path, 'w',
892 encoding='utf_8', errors='replace') as rf_file:
893 rf_file.writelines(rf_list)
894 except IOError as err:
895 if not getattr(self.root, "recentfilelist_error_displayed", False):
896 self.root.recentfilelist_error_displayed = True
897 tkMessageBox.showerror(title='IDLE Error',
898 message='Unable to update Recent Files list:\n%s'
899 % str(err),
900 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000901 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000902 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000903 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700904 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000905 for i, file_name in enumerate(rf_list):
906 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000907 # make unicode string to display non-ASCII chars correctly
908 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000909 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000910 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000911 command=callback,
912 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000913
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000914 def __recent_file_callback(self, file_name):
915 def open_recent_file(fn_closure=file_name):
916 self.io.open(editFile=fn_closure)
917 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000918
David Scherer7aced172000-08-15 01:13:23 +0000919 def saved_change_hook(self):
920 short = self.short_title()
921 long = self.long_title()
922 if short and long:
923 title = short + " - " + long
924 elif short:
925 title = short
926 elif long:
927 title = long
928 else:
929 title = "Untitled"
930 icon = short or long or title
931 if not self.get_saved():
932 title = "*%s*" % title
933 icon = "*%s" % icon
934 self.top.wm_title(title)
935 self.top.wm_iconname(icon)
936
937 def get_saved(self):
938 return self.undo.get_saved()
939
940 def set_saved(self, flag):
941 self.undo.set_saved(flag)
942
943 def reset_undo(self):
944 self.undo.reset_undo()
945
946 def short_title(self):
947 filename = self.io.filename
948 if filename:
949 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000950 # return unicode string to display non-ASCII chars correctly
951 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000952
953 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000954 # return unicode string to display non-ASCII chars correctly
955 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000956
957 def center_insert_event(self, event):
958 self.center()
959
960 def center(self, mark="insert"):
961 text = self.text
962 top, bot = self.getwindowlines()
963 lineno = self.getlineno(mark)
964 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000965 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000966 text.yview(float(newtop))
967
968 def getwindowlines(self):
969 text = self.text
970 top = self.getlineno("@0,0")
971 bot = self.getlineno("@0,65535")
972 if top == bot and text.winfo_height() == 1:
973 # Geometry manager hasn't run yet
974 height = int(text['height'])
975 bot = top + height - 1
976 return top, bot
977
978 def getlineno(self, mark="insert"):
979 text = self.text
980 return int(float(text.index(mark)))
981
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000982 def get_geometry(self):
983 "Return (width, height, x, y)"
984 geom = self.top.wm_geometry()
985 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000986 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000987
David Scherer7aced172000-08-15 01:13:23 +0000988 def close_event(self, event):
989 self.close()
990
991 def maybesave(self):
992 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000993 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000994 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000995 self.top.deiconify()
996 self.top.lower()
997 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000998 return self.io.maybesave()
999
1000 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001001 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +00001002 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001003 self._close()
1004 return reply
1005
1006 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001007 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001008 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001009 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001010 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001011 self.io.close()
1012 self.io = None
1013 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001014 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001015 self.color.close(False)
1016 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001017 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001018 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001019 self.per.close()
1020 self.per = None
1021 self.top.destroy()
1022 if self.close_hook:
1023 # unless override: unregister from flist, terminate if last window
1024 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001025
1026 def load_extensions(self):
1027 self.extensions = {}
1028 self.load_standard_extensions()
1029
1030 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001031 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001032 if hasattr(ins, "close"):
1033 ins.close()
1034 self.extensions = {}
1035
1036 def load_standard_extensions(self):
1037 for name in self.get_standard_extension_names():
1038 try:
1039 self.load_extension(name)
1040 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001041 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001042 traceback.print_exc()
1043
1044 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001045 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001046
1047 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001048 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001049 try:
1050 mod = importlib.import_module('.' + name, package=__package__)
1051 except ImportError:
1052 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001053 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001054 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001055 raise
David Scherer7aced172000-08-15 01:13:23 +00001056 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001057 keydefs = idleConf.GetExtensionBindings(name)
1058 if hasattr(cls, "menudefs"):
1059 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001060 ins = cls(self)
1061 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001062 if keydefs:
1063 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001064 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001065 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001066 while methodname[:1] == '<':
1067 methodname = methodname[1:]
1068 while methodname[-1:] == '>':
1069 methodname = methodname[:-1]
1070 methodname = methodname + "_event"
1071 if hasattr(ins, methodname):
1072 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001073
1074 def apply_bindings(self, keydefs=None):
1075 if keydefs is None:
1076 keydefs = self.Bindings.default_keydefs
1077 text = self.text
1078 text.keydefs = keydefs
1079 for event, keylist in keydefs.items():
1080 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001081 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001082
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001083 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001084 """Add appropriate entries to the menus and submenus
1085
1086 Menus that are absent or None in self.menudict are ignored.
1087 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001088 if menudefs is None:
1089 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001090 if keydefs is None:
1091 keydefs = self.Bindings.default_keydefs
1092 menudict = self.menudict
1093 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001094 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001095 menu = menudict.get(mname)
1096 if not menu:
1097 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001098 for entry in entrylist:
1099 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001100 menu.add_separator()
1101 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001102 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001103 checkbutton = (label[:1] == '!')
1104 if checkbutton:
1105 label = label[1:]
1106 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001107 accelerator = get_accelerator(keydefs, eventname)
1108 def command(text=text, eventname=eventname):
1109 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001110 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001111 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001112 menu.add_checkbutton(label=label, underline=underline,
1113 command=command, accelerator=accelerator,
1114 variable=var)
1115 else:
1116 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001117 command=command,
1118 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001119
1120 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001121 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001122 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001123 value = var.get()
1124 return value
1125 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001126 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001127
1128 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001129 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001130 if var:
1131 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001132 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001133 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001134
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001135 def get_var_obj(self, name, vartype=None):
1136 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001137 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001138 # create a Tkinter variable object with self.text as master:
1139 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001140 return var
1141
1142 # Tk implementations of "virtual text methods" -- each platform
1143 # reusing IDLE's support code needs to define these for its GUI's
1144 # flavor of widget.
1145
1146 # Is character at text_index in a Python string? Return 0 for
1147 # "guaranteed no", true for anything else. This info is expensive
1148 # to compute ab initio, but is probably already known by the
1149 # platform's colorizer.
1150
1151 def is_char_in_string(self, text_index):
1152 if self.color:
1153 # Return true iff colorizer hasn't (re)gotten this far
1154 # yet, or the character is tagged as being in a string
1155 return self.text.tag_prevrange("TODO", text_index) or \
1156 "STRING" in self.text.tag_names(text_index)
1157 else:
1158 # The colorizer is missing: assume the worst
1159 return 1
1160
1161 # If a selection is defined in the text widget, return (start,
1162 # end) as Tkinter text indices, otherwise return (None, None)
1163 def get_selection_indices(self):
1164 try:
1165 first = self.text.index("sel.first")
1166 last = self.text.index("sel.last")
1167 return first, last
1168 except TclError:
1169 return None, None
1170
1171 # Return the text widget's current view of what a tab stop means
1172 # (equivalent width in spaces).
1173
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001174 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001175 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1176 return int(current)
1177
1178 # Set the text widget's current view of what a tab stop means.
1179
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001180 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001181 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001182 if self.get_tk_tabwidth() != newtabwidth:
1183 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001184 pixels = text.tk.call("font", "measure", text["font"],
1185 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001186 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001187 text.configure(tabs=pixels)
1188
Guido van Rossum33d26892007-08-05 15:29:28 +00001189### begin autoindent code ### (configuration was moved to beginning of class)
1190
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001191 def set_indentation_params(self, is_py_src, guess=True):
1192 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001193 i = self.guess_indent()
1194 if 2 <= i <= 8:
1195 self.indentwidth = i
1196 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001197 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001198 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001199
1200 def smart_backspace_event(self, event):
1201 text = self.text
1202 first, last = self.get_selection_indices()
1203 if first and last:
1204 text.delete(first, last)
1205 text.mark_set("insert", first)
1206 return "break"
1207 # Delete whitespace left, until hitting a real char or closest
1208 # preceding virtual tab stop.
1209 chars = text.get("insert linestart", "insert")
1210 if chars == '':
1211 if text.compare("insert", ">", "1.0"):
1212 # easy: delete preceding newline
1213 text.delete("insert-1c")
1214 else:
1215 text.bell() # at start of buffer
1216 return "break"
1217 if chars[-1] not in " \t":
1218 # easy: delete preceding real char
1219 text.delete("insert-1c")
1220 return "break"
1221 # Ick. It may require *inserting* spaces if we back up over a
1222 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001223 tabwidth = self.tabwidth
1224 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001225 assert have > 0
1226 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001227 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001228 if self.context_use_ps1:
1229 last_line_of_prompt = sys.ps1.split('\n')[-1]
1230 else:
1231 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001232 ncharsdeleted = 0
1233 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001234 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001235 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001236 chars = chars[:-1]
1237 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001238 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001239 if have <= want or chars[-1] not in " \t":
1240 break
1241 text.undo_block_start()
1242 text.delete("insert-%dc" % ncharsdeleted, "insert")
1243 if have < want:
1244 text.insert("insert", ' ' * (want - have))
1245 text.undo_block_stop()
1246 return "break"
1247
1248 def smart_indent_event(self, event):
1249 # if intraline selection:
1250 # delete it
1251 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001252 # do indent-region
1253 # else:
1254 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001255 text = self.text
1256 first, last = self.get_selection_indices()
1257 text.undo_block_start()
1258 try:
1259 if first and last:
1260 if index2line(first) != index2line(last):
1261 return self.indent_region_event(event)
1262 text.delete(first, last)
1263 text.mark_set("insert", first)
1264 prefix = text.get("insert linestart", "insert")
1265 raw, effective = classifyws(prefix, self.tabwidth)
1266 if raw == len(prefix):
1267 # only whitespace to the left
1268 self.reindent_to(effective + self.indentwidth)
1269 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001270 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001271 if self.usetabs:
1272 pad = '\t'
1273 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001274 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001275 n = self.indentwidth
1276 pad = ' ' * (n - effective % n)
1277 text.insert("insert", pad)
1278 text.see("insert")
1279 return "break"
1280 finally:
1281 text.undo_block_stop()
1282
1283 def newline_and_indent_event(self, event):
1284 text = self.text
1285 first, last = self.get_selection_indices()
1286 text.undo_block_start()
1287 try:
1288 if first and last:
1289 text.delete(first, last)
1290 text.mark_set("insert", first)
1291 line = text.get("insert linestart", "insert")
1292 i, n = 0, len(line)
1293 while i < n and line[i] in " \t":
1294 i = i+1
1295 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001296 # the cursor is in or at leading indentation in a continuation
1297 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001298 text.insert("insert linestart", '\n')
1299 return "break"
1300 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001301 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001302 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001303 last_line_of_prompt = sys.ps1.split('\n')[-1]
1304 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001305 line = line[:-1]
1306 i = i+1
1307 if i:
1308 text.delete("insert - %d chars" % i, "insert")
1309 # strip whitespace after insert point
1310 while text.get("insert") in " \t":
1311 text.delete("insert")
1312 # start new line
1313 text.insert("insert", '\n')
1314
1315 # adjust indentation for continuations and block
1316 # open/close first need to find the last stmt
1317 lno = index2line(text.index('insert'))
1318 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001319 if not self.context_use_ps1:
1320 for context in self.num_context_lines:
1321 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001322 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001323 rawtext = text.get(startatindex, "insert")
1324 y.set_str(rawtext)
1325 bod = y.find_good_parse_start(
1326 self.context_use_ps1,
1327 self._build_char_in_string_func(startatindex))
1328 if bod is not None or startat == 1:
1329 break
1330 y.set_lo(bod or 0)
1331 else:
1332 r = text.tag_prevrange("console", "insert")
1333 if r:
1334 startatindex = r[1]
1335 else:
1336 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001337 rawtext = text.get(startatindex, "insert")
1338 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001339 y.set_lo(0)
1340
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001341 c = y.get_continuation_type()
1342 if c != PyParse.C_NONE:
1343 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001344 if c == PyParse.C_STRING_FIRST_LINE:
1345 # after the first line of a string; do not indent at all
1346 pass
1347 elif c == PyParse.C_STRING_NEXT_LINES:
1348 # inside a string which started before this line;
1349 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001350 text.insert("insert", indent)
1351 elif c == PyParse.C_BRACKET:
1352 # line up with the first (if any) element of the
1353 # last open bracket structure; else indent one
1354 # level beyond the indent of the line with the
1355 # last open bracket
1356 self.reindent_to(y.compute_bracket_indent())
1357 elif c == PyParse.C_BACKSLASH:
1358 # if more than one line in this stmt already, just
1359 # mimic the current indent; else if initial line
1360 # has a start on an assignment stmt, indent to
1361 # beyond leftmost =; else to beyond first chunk of
1362 # non-whitespace on initial line
1363 if y.get_num_lines_in_stmt() > 1:
1364 text.insert("insert", indent)
1365 else:
1366 self.reindent_to(y.compute_backslash_indent())
1367 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001368 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001369 return "break"
1370
1371 # This line starts a brand new stmt; indent relative to
1372 # indentation of initial line of closest preceding
1373 # interesting stmt.
1374 indent = y.get_base_indent_string()
1375 text.insert("insert", indent)
1376 if y.is_block_opener():
1377 self.smart_indent_event(event)
1378 elif indent and y.is_block_closer():
1379 self.smart_backspace_event(event)
1380 return "break"
1381 finally:
1382 text.see("insert")
1383 text.undo_block_stop()
1384
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001385 # Our editwin provides a is_char_in_string function that works
1386 # with a Tk text index, but PyParse only knows about offsets into
1387 # a string. This builds a function for PyParse that accepts an
1388 # offset.
1389
1390 def _build_char_in_string_func(self, startindex):
1391 def inner(offset, _startindex=startindex,
1392 _icis=self.is_char_in_string):
1393 return _icis(_startindex + "+%dc" % offset)
1394 return inner
1395
1396 def indent_region_event(self, event):
1397 head, tail, chars, lines = self.get_region()
1398 for pos in range(len(lines)):
1399 line = lines[pos]
1400 if line:
1401 raw, effective = classifyws(line, self.tabwidth)
1402 effective = effective + self.indentwidth
1403 lines[pos] = self._make_blanks(effective) + line[raw:]
1404 self.set_region(head, tail, chars, lines)
1405 return "break"
1406
1407 def dedent_region_event(self, event):
1408 head, tail, chars, lines = self.get_region()
1409 for pos in range(len(lines)):
1410 line = lines[pos]
1411 if line:
1412 raw, effective = classifyws(line, self.tabwidth)
1413 effective = max(effective - self.indentwidth, 0)
1414 lines[pos] = self._make_blanks(effective) + line[raw:]
1415 self.set_region(head, tail, chars, lines)
1416 return "break"
1417
1418 def comment_region_event(self, event):
1419 head, tail, chars, lines = self.get_region()
1420 for pos in range(len(lines) - 1):
1421 line = lines[pos]
1422 lines[pos] = '##' + line
1423 self.set_region(head, tail, chars, lines)
1424
1425 def uncomment_region_event(self, event):
1426 head, tail, chars, lines = self.get_region()
1427 for pos in range(len(lines)):
1428 line = lines[pos]
1429 if not line:
1430 continue
1431 if line[:2] == '##':
1432 line = line[2:]
1433 elif line[:1] == '#':
1434 line = line[1:]
1435 lines[pos] = line
1436 self.set_region(head, tail, chars, lines)
1437
1438 def tabify_region_event(self, event):
1439 head, tail, chars, lines = self.get_region()
1440 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001441 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001442 for pos in range(len(lines)):
1443 line = lines[pos]
1444 if line:
1445 raw, effective = classifyws(line, tabwidth)
1446 ntabs, nspaces = divmod(effective, tabwidth)
1447 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1448 self.set_region(head, tail, chars, lines)
1449
1450 def untabify_region_event(self, event):
1451 head, tail, chars, lines = self.get_region()
1452 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001453 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001454 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001455 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001456 self.set_region(head, tail, chars, lines)
1457
1458 def toggle_tabs_event(self, event):
1459 if self.askyesno(
1460 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001461 "Turn tabs " + ("on", "off")[self.usetabs] +
1462 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001463 ("will be", "remains at")[self.usetabs] + " 8." +
1464 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001465 parent=self.text):
1466 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001467 # Try to prevent inconsistent indentation.
1468 # User must change indent width manually after using tabs.
1469 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001470 return "break"
1471
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001472 # XXX this isn't bound to anything -- see tabwidth comments
1473## def change_tabwidth_event(self, event):
1474## new = self._asktabwidth()
1475## if new != self.tabwidth:
1476## self.tabwidth = new
1477## self.set_indentation_params(0, guess=0)
1478## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001479
1480 def change_indentwidth_event(self, event):
1481 new = self.askinteger(
1482 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001483 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001484 parent=self.text,
1485 initialvalue=self.indentwidth,
1486 minvalue=2,
1487 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001488 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001489 self.indentwidth = new
1490 return "break"
1491
1492 def get_region(self):
1493 text = self.text
1494 first, last = self.get_selection_indices()
1495 if first and last:
1496 head = text.index(first + " linestart")
1497 tail = text.index(last + "-1c lineend +1c")
1498 else:
1499 head = text.index("insert linestart")
1500 tail = text.index("insert lineend +1c")
1501 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001502 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001503 return head, tail, chars, lines
1504
1505 def set_region(self, head, tail, chars, lines):
1506 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001507 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001508 if newchars == chars:
1509 text.bell()
1510 return
1511 text.tag_remove("sel", "1.0", "end")
1512 text.mark_set("insert", head)
1513 text.undo_block_start()
1514 text.delete(head, tail)
1515 text.insert(head, newchars)
1516 text.undo_block_stop()
1517 text.tag_add("sel", head, "insert")
1518
1519 # Make string that displays as n leading blanks.
1520
1521 def _make_blanks(self, n):
1522 if self.usetabs:
1523 ntabs, nspaces = divmod(n, self.tabwidth)
1524 return '\t' * ntabs + ' ' * nspaces
1525 else:
1526 return ' ' * n
1527
1528 # Delete from beginning of line to insert point, then reinsert
1529 # column logical (meaning use tabs if appropriate) spaces.
1530
1531 def reindent_to(self, column):
1532 text = self.text
1533 text.undo_block_start()
1534 if text.compare("insert linestart", "!=", "insert"):
1535 text.delete("insert linestart", "insert")
1536 if column:
1537 text.insert("insert", self._make_blanks(column))
1538 text.undo_block_stop()
1539
1540 def _asktabwidth(self):
1541 return self.askinteger(
1542 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001543 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001544 parent=self.text,
1545 initialvalue=self.indentwidth,
1546 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001547 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001548
1549 # Guess indentwidth from text content.
1550 # Return guessed indentwidth. This should not be believed unless
1551 # it's in a reasonable range (e.g., it will be 0 if no indented
1552 # blocks are found).
1553
1554 def guess_indent(self):
1555 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1556 if opener and indented:
1557 raw, indentsmall = classifyws(opener, self.tabwidth)
1558 raw, indentlarge = classifyws(indented, self.tabwidth)
1559 else:
1560 indentsmall = indentlarge = 0
1561 return indentlarge - indentsmall
1562
1563# "line.col" -> line, as an int
1564def index2line(index):
1565 return int(float(index))
1566
1567# Look at the leading whitespace in s.
1568# Return pair (# of leading ws characters,
1569# effective # of leading blanks after expanding
1570# tabs to width tabwidth)
1571
1572def classifyws(s, tabwidth):
1573 raw = effective = 0
1574 for ch in s:
1575 if ch == ' ':
1576 raw = raw + 1
1577 effective = effective + 1
1578 elif ch == '\t':
1579 raw = raw + 1
1580 effective = (effective // tabwidth + 1) * tabwidth
1581 else:
1582 break
1583 return raw, effective
1584
1585import tokenize
1586_tokenize = tokenize
1587del tokenize
1588
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001589class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001590
1591 # .run() chews over the Text widget, looking for a block opener
1592 # and the stmt following it. Returns a pair,
1593 # (line containing block opener, line containing stmt)
1594 # Either or both may be None.
1595
1596 def __init__(self, text, tabwidth):
1597 self.text = text
1598 self.tabwidth = tabwidth
1599 self.i = self.finished = 0
1600 self.blkopenline = self.indentedline = None
1601
1602 def readline(self):
1603 if self.finished:
1604 return ""
1605 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001606 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001607 if self.text.compare(mark, ">=", "end"):
1608 return ""
1609 return self.text.get(mark, mark + " lineend+1c")
1610
1611 def tokeneater(self, type, token, start, end, line,
1612 INDENT=_tokenize.INDENT,
1613 NAME=_tokenize.NAME,
1614 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1615 if self.finished:
1616 pass
1617 elif type == NAME and token in OPENERS:
1618 self.blkopenline = line
1619 elif type == INDENT and self.blkopenline:
1620 self.indentedline = line
1621 self.finished = 1
1622
1623 def run(self):
1624 save_tabsize = _tokenize.tabsize
1625 _tokenize.tabsize = self.tabwidth
1626 try:
1627 try:
Trent Nelson428de652008-03-18 22:41:35 +00001628 tokens = _tokenize.generate_tokens(self.readline)
1629 for token in tokens:
1630 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001631 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001632 # since we cut off the tokenizer early, we can trigger
1633 # spurious errors
1634 pass
1635 finally:
1636 _tokenize.tabsize = save_tabsize
1637 return self.blkopenline, self.indentedline
1638
1639### end autoindent code ###
1640
David Scherer7aced172000-08-15 01:13:23 +00001641def prepstr(s):
1642 # Helper to extract the underscore from a string, e.g.
1643 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001644 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001645 if i >= 0:
1646 s = s[:i] + s[i+1:]
1647 return i, s
1648
1649
1650keynames = {
1651 'bracketleft': '[',
1652 'bracketright': ']',
1653 'slash': '/',
1654}
1655
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001656def get_accelerator(keydefs, eventname):
1657 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001658 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1659 # if not keylist:
1660 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1661 "<<open-module>>",
1662 "<<goto-line>>",
1663 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001664 return ""
1665 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001666 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001667 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1668 s = re.sub("Key-", "", s)
1669 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1670 s = re.sub("Control-", "Ctrl-", s)
1671 s = re.sub("-", "+", s)
1672 s = re.sub("><", " ", s)
1673 s = re.sub("<", "", s)
1674 s = re.sub(">", "", s)
1675 return s
1676
1677
1678def fixwordbreaks(root):
1679 # Make sure that Tk's double-click and next/previous word
1680 # operations use our definition of a word (i.e. an identifier)
1681 tk = root.tk
1682 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1683 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1684 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1685
1686
1687def test():
1688 root = Tk()
1689 fixwordbreaks(root)
1690 root.withdraw()
1691 if sys.argv[1:]:
1692 filename = sys.argv[1]
1693 else:
1694 filename = None
1695 edit = EditorWindow(root=root, filename=filename)
1696 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001697 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001698 root.mainloop()
1699 root.destroy()
1700
1701if __name__ == '__main__':
1702 test()