blob: 52dfb3da13e44b790eeadf74652770e7b03c42f5 [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)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500324 self.good_load = True
325
Christian Heimesa156e092008-02-16 07:38:31 +0000326 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000327 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000328 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000329 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000330 menu = self.menudict.get('windows')
331 if menu:
332 end = menu.index("end")
333 if end is None:
334 end = -1
335 if end >= 0:
336 menu.add_separator()
337 end = end + 1
338 self.wmenu_end = end
339 WindowList.register_callback(self.postwindowsmenu)
340
341 # Some abstractions so IDLE extensions are cross-IDE
342 self.askyesno = tkMessageBox.askyesno
343 self.askinteger = tkSimpleDialog.askinteger
344 self.showerror = tkMessageBox.showerror
345
Martin v. Löwis307021f2005-11-27 16:59:04 +0000346 def _filename_to_unicode(self, filename):
347 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000348 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000349 return filename
350 else:
351 try:
352 return filename.decode(self.filesystemencoding)
353 except UnicodeDecodeError:
354 # XXX
355 try:
356 return filename.decode(self.encoding)
357 except UnicodeDecodeError:
358 # byte-to-byte conversion
359 return filename.decode('iso8859-1')
360
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000361 def new_callback(self, event):
362 dirname, basename = self.io.defaultfilename()
363 self.flist.new(dirname)
364 return "break"
365
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000366 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400367 if (event.state & 4) != 0 and event.keysym == "Home":
368 # state&4==Control. If <Control-Home>, use the Tk binding.
369 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000370 if self.text.index("iomark") and \
371 self.text.compare("iomark", "<=", "insert lineend") and \
372 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400373 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000374 insertpt = int(self.text.index("iomark").split(".")[1])
375 else:
376 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000377 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000378 if line[insertpt] not in (' ','\t'):
379 break
380 else:
381 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000382 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000383 if insertpt == lineat:
384 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000385 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000386 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400387 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000388 self.text.tag_remove("sel", "1.0", "end")
389 else:
390 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200391 # there was no previous selection
392 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400393 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200394 if self.text.compare(self.text.index("sel.first"), "<",
395 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400396 self.text.mark_set("my_anchor", "sel.first") # extend back
397 else:
398 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000399 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400400 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000401 if self.text.compare(first,">",last):
402 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000403 self.text.tag_remove("sel", "1.0", "end")
404 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000405 self.text.mark_set("insert", dest)
406 self.text.see("insert")
407 return "break"
408
David Scherer7aced172000-08-15 01:13:23 +0000409 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000410 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000411 if macosxSupport.runningAsOSXApp():
412 # Insert some padding to avoid obscuring some of the statusbar
413 # by the resize widget.
414 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000415 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
416 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
417 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000418 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
419 self.text.event_add("<<set-line-and-column>>",
420 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000421 self.text.after_idle(self.set_line_and_column)
422
423 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000424 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000425 self.status_bar.set_label('column', 'Col: %s' % column)
426 self.status_bar.set_label('line', 'Ln: %s' % line)
427
David Scherer7aced172000-08-15 01:13:23 +0000428 menu_specs = [
429 ("file", "_File"),
430 ("edit", "_Edit"),
431 ("format", "F_ormat"),
432 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000433 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000434 ("windows", "_Windows"),
435 ("help", "_Help"),
436 ]
437
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000438 if macosxSupport.runningAsOSXApp():
439 del menu_specs[-3]
440 menu_specs[-2] = ("windows", "_Window")
441
442
David Scherer7aced172000-08-15 01:13:23 +0000443 def createmenubar(self):
444 mbar = self.menubar
445 self.menudict = menudict = {}
446 for name, label in self.menu_specs:
447 underline, label = prepstr(label)
448 menudict[name] = menu = Menu(mbar, name=name)
449 mbar.add_cascade(label=label, menu=menu, underline=underline)
Georg Brandlaedd2892010-12-19 10:10:32 +0000450 if macosxSupport.isCarbonAquaTk(self.root):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000451 # Insert the application menu
452 menudict['application'] = menu = Menu(mbar, name='apple')
453 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000454 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000455 self.recent_files_menu = Menu(self.menubar)
456 self.menudict['file'].insert_cascade(3, label='Recent Files',
457 underline=0,
458 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000459 self.base_helpmenu_length = self.menudict['help'].index(END)
460 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000461
462 def postwindowsmenu(self):
463 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000464 menu = self.menudict['windows']
465 end = menu.index("end")
466 if end is None:
467 end = -1
468 if end > self.wmenu_end:
469 menu.delete(self.wmenu_end+1, end)
470 WindowList.add_windows_to_menu(menu)
471
472 rmenu = None
473
474 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000475 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
476 if not self.rmenu:
477 self.make_rmenu()
478 rmenu = self.rmenu
479 self.event = event
480 iswin = sys.platform[:3] == 'win'
481 if iswin:
482 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200483
Roger Serwy6b2918a2013-04-07 12:15:52 -0500484 for item in self.rmenu_specs:
485 try:
486 label, eventname, verify_state = item
487 except ValueError: # see issue1207589
488 continue
489
Andrew Svetlovd1837672012-11-01 22:41:19 +0200490 if verify_state is None:
491 continue
492 state = getattr(self, verify_state)()
493 rmenu.entryconfigure(label, state=state)
494
495
David Scherer7aced172000-08-15 01:13:23 +0000496 rmenu.tk_popup(event.x_root, event.y_root)
497 if iswin:
498 self.text.config(cursor="ibeam")
499
500 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200501 # ("Label", "<<virtual-event>>", "statefuncname"), ...
502 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000503 ]
504
505 def make_rmenu(self):
506 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500507 for item in self.rmenu_specs:
508 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200509 if label is not None:
510 def command(text=self.text, eventname=eventname):
511 text.event_generate(eventname)
512 rmenu.add_command(label=label, command=command)
513 else:
514 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000515 self.rmenu = rmenu
516
Andrew Svetlovd1837672012-11-01 22:41:19 +0200517 def rmenu_check_cut(self):
518 return self.rmenu_check_copy()
519
520 def rmenu_check_copy(self):
521 try:
522 indx = self.text.index('sel.first')
523 except TclError:
524 return 'disabled'
525 else:
526 return 'normal' if indx else 'disabled'
527
528 def rmenu_check_paste(self):
529 try:
530 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
531 except TclError:
532 return 'disabled'
533 else:
534 return 'normal'
535
David Scherer7aced172000-08-15 01:13:23 +0000536 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000537 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000538
Steven M. Gava3b55a892001-11-21 05:56:26 +0000539 def config_dialog(self, event=None):
540 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000541
David Scherer7aced172000-08-15 01:13:23 +0000542 def help_dialog(self, event=None):
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500543 if self.root:
544 parent = self.root
545 else:
546 parent = self.top
547 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000548
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000549 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000550 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000551 try:
552 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200553 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000554 tkMessageBox.showerror(title='Document Start Failure',
555 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000556 else:
557 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000558 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000559
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000560 def cut(self,event):
561 self.text.event_generate("<<Cut>>")
562 return "break"
563
564 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000565 if not self.text.tag_ranges("sel"):
566 # There is no selection, so do nothing and maybe interrupt.
567 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000568 self.text.event_generate("<<Copy>>")
569 return "break"
570
571 def paste(self,event):
572 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000573 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000574 return "break"
575
David Scherer7aced172000-08-15 01:13:23 +0000576 def select_all(self, event=None):
577 self.text.tag_add("sel", "1.0", "end-1c")
578 self.text.mark_set("insert", "1.0")
579 self.text.see("insert")
580 return "break"
581
582 def remove_selection(self, event=None):
583 self.text.tag_remove("sel", "1.0", "end")
584 self.text.see("insert")
585
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000586 def move_at_edge_if_selection(self, edge_index):
587 """Cursor move begins at start or end of selection
588
589 When a left/right cursor key is pressed create and return to Tkinter a
590 function which causes a cursor move from the associated edge of the
591 selection.
592
593 """
594 self_text_index = self.text.index
595 self_text_mark_set = self.text.mark_set
596 edges_table = ("sel.first+1c", "sel.last-1c")
597 def move_at_edge(event):
598 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
599 try:
600 self_text_index("sel.first")
601 self_text_mark_set("insert", edges_table[edge_index])
602 except TclError:
603 pass
604 return move_at_edge
605
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000606 def del_word_left(self, event):
607 self.text.event_generate('<Meta-Delete>')
608 return "break"
609
610 def del_word_right(self, event):
611 self.text.event_generate('<Meta-d>')
612 return "break"
613
Steven M. Gavac5976402002-01-04 03:06:08 +0000614 def find_event(self, event):
615 SearchDialog.find(self.text)
616 return "break"
617
618 def find_again_event(self, event):
619 SearchDialog.find_again(self.text)
620 return "break"
621
622 def find_selection_event(self, event):
623 SearchDialog.find_selection(self.text)
624 return "break"
625
626 def find_in_files_event(self, event):
627 GrepDialog.grep(self.text, self.io, self.flist)
628 return "break"
629
630 def replace_event(self, event):
631 ReplaceDialog.replace(self.text)
632 return "break"
633
634 def goto_line_event(self, event):
635 text = self.text
636 lineno = tkSimpleDialog.askinteger("Goto",
637 "Go to line number:",parent=text)
638 if lineno is None:
639 return "break"
640 if lineno <= 0:
641 text.bell()
642 return "break"
643 text.mark_set("insert", "%d.0" % lineno)
644 text.see("insert")
645
David Scherer7aced172000-08-15 01:13:23 +0000646 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000647 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000648 try:
649 name = self.text.get("sel.first", "sel.last")
650 except TclError:
651 name = ""
652 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000653 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000654 name = tkSimpleDialog.askstring("Module",
655 "Enter the name of a Python module\n"
656 "to search on sys.path and open:",
657 parent=self.text, initialvalue=name)
658 if name:
659 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000660 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000661 return
David Scherer7aced172000-08-15 01:13:23 +0000662 # XXX Ought to insert current file's directory in front of path
663 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000664 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000665 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000666 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
667 return
668 if type != imp.PY_SOURCE:
669 tkMessageBox.showerror("Unsupported type",
670 "%s is not a source module" % name, parent=self.text)
671 return
672 if f:
673 f.close()
674 if self.flist:
675 self.flist.open(file)
676 else:
677 self.io.loadfile(file)
678
679 def open_class_browser(self, event=None):
680 filename = self.io.filename
681 if not filename:
682 tkMessageBox.showerror(
683 "No filename",
684 "This buffer has no associated filename",
685 master=self.text)
686 self.text.focus_set()
687 return None
688 head, tail = os.path.split(filename)
689 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000690 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000691 ClassBrowser.ClassBrowser(self.flist, base, [head])
692
693 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000694 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000695 PathBrowser.PathBrowser(self.flist)
696
697 def gotoline(self, lineno):
698 if lineno is not None and lineno > 0:
699 self.text.mark_set("insert", "%d.0" % lineno)
700 self.text.tag_remove("sel", "1.0", "end")
701 self.text.tag_add("sel", "insert", "insert +1l")
702 self.center()
703
704 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000705 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000706 return True
David Scherer7aced172000-08-15 01:13:23 +0000707 base, ext = os.path.splitext(os.path.basename(filename))
708 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000709 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000710 line = self.text.get('1.0', '1.0 lineend')
711 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000712
713 def close_hook(self):
714 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000715 self.flist.unregister_maybe_terminate(self)
716 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000717
718 def set_close_hook(self, close_hook):
719 self.close_hook = close_hook
720
721 def filename_change_hook(self):
722 if self.flist:
723 self.flist.filename_changed_edit(self)
724 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000725 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000726 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000727
Christian Heimesa156e092008-02-16 07:38:31 +0000728 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000729 if self.color:
730 return
Christian Heimesa156e092008-02-16 07:38:31 +0000731 if self.ispythonsource(self.io.filename):
732 self.color = self.ColorDelegator()
733 # can add more colorizers here...
734 if self.color:
735 self.per.removefilter(self.undo)
736 self.per.insertfilter(self.color)
737 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000738
Christian Heimesa156e092008-02-16 07:38:31 +0000739 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000740 if not self.color:
741 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000742 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000743 self.per.removefilter(self.color)
744 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000745
Steven M. Gavab77d3432002-03-02 07:16:21 +0000746 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000747 "Update the colour theme"
748 # Called from self.filename_change_hook and from configDialog.py
749 self._rmcolorizer()
750 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000751 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000752 normal_colors = idleConf.GetHighlight(theme, 'normal')
753 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
754 select_colors = idleConf.GetHighlight(theme, 'hilite')
755 self.text.config(
756 foreground=normal_colors['foreground'],
757 background=normal_colors['background'],
758 insertbackground=cursor_color,
759 selectforeground=select_colors['foreground'],
760 selectbackground=select_colors['background'],
761 )
David Scherer7aced172000-08-15 01:13:23 +0000762
Guido van Rossum33d26892007-08-05 15:29:28 +0000763 IDENTCHARS = string.ascii_letters + string.digits + "_"
764
765 def colorize_syntax_error(self, text, pos):
766 text.tag_add("ERROR", pos)
767 char = text.get(pos)
768 if char and char in self.IDENTCHARS:
769 text.tag_add("ERROR", pos + " wordstart", pos)
770 if '\n' == text.get(pos): # error at line end
771 text.mark_set("insert", pos)
772 else:
773 text.mark_set("insert", pos + "+1c")
774 text.see(pos)
775
Steven M. Gavab1585412002-03-12 00:21:56 +0000776 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000777 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000778 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000779 fontWeight='normal'
780 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
781 fontWeight='bold'
782 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200783 idleConf.GetOption('main','EditorWindow','font-size',
784 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000785 fontWeight))
786
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000787 def RemoveKeybindings(self):
788 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000789 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000790 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000791 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000792 self.text.event_delete(event, *keylist)
793 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000794 xkeydefs = idleConf.GetExtensionBindings(extensionName)
795 if xkeydefs:
796 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000797 self.text.event_delete(event, *keylist)
798
799 def ApplyKeybindings(self):
800 "Update the keybindings after they are changed"
801 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000802 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000803 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000804 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000805 xkeydefs = idleConf.GetExtensionBindings(extensionName)
806 if xkeydefs:
807 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000808 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000809 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000810 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000811 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000812 for item in menu[1]:
813 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000814 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000815 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000816 menu = self.menudict[menubarItem]
817 end = menu.index(END) + 1
818 for index in range(0, end):
819 if menu.type(index) == 'command':
820 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000821 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000822 itemName = menu.entrycget(index, 'label')
823 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000824 if menubarItem in menuEventDict:
825 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000826 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000827 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000828 accel = get_accelerator(keydefs, event)
829 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000830
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000831 def set_notabs_indentwidth(self):
832 "Update the indentwidth if changed and not using tabs in this window"
833 # Called from configDialog.py
834 if not self.usetabs:
835 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
836 type='int')
837
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000838 def reset_help_menu_entries(self):
839 "Update the additional help entries on the Help menu"
840 help_list = idleConf.GetAllExtraHelpSourcesList()
841 helpmenu = self.menudict['help']
842 # first delete the extra help entries, if any
843 helpmenu_length = helpmenu.index(END)
844 if helpmenu_length > self.base_helpmenu_length:
845 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
846 # then rebuild them
847 if help_list:
848 helpmenu.add_separator()
849 for entry in help_list:
850 cmd = self.__extra_help_callback(entry[1])
851 helpmenu.add_command(label=entry[0], command=cmd)
852 # and update the menu dictionary
853 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000854
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000855 def __extra_help_callback(self, helpfile):
856 "Create a callback with the helpfile value frozen at definition time"
857 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000858 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000859 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000860 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000861 try:
862 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200863 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000864 tkMessageBox.showerror(title='Document Start Failure',
865 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000866 else:
867 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000868 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000869
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000870 def update_recent_files_list(self, new_file=None):
871 "Load and update the recent files list and menus"
872 rf_list = []
873 if os.path.exists(self.recent_files_path):
Ned Deily122539e2011-01-24 21:46:44 +0000874 rf_list_file = open(self.recent_files_path,'r',
875 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000876 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000877 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000878 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000879 rf_list_file.close()
880 if new_file:
881 new_file = os.path.abspath(new_file) + '\n'
882 if new_file in rf_list:
883 rf_list.remove(new_file) # move to top
884 rf_list.insert(0, new_file)
885 # clean and save the recent files list
886 bad_paths = []
887 for path in rf_list:
888 if '\0' in path or not os.path.exists(path[0:-1]):
889 bad_paths.append(path)
890 rf_list = [path for path in rf_list if path not in bad_paths]
891 ulchars = "1234567890ABCDEFGHIJK"
892 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000893 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800894 with open(self.recent_files_path, 'w',
895 encoding='utf_8', errors='replace') as rf_file:
896 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200897 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800898 if not getattr(self.root, "recentfilelist_error_displayed", False):
899 self.root.recentfilelist_error_displayed = True
900 tkMessageBox.showerror(title='IDLE Error',
901 message='Unable to update Recent Files list:\n%s'
902 % str(err),
903 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000904 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000905 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000906 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700907 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000908 for i, file_name in enumerate(rf_list):
909 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000910 # make unicode string to display non-ASCII chars correctly
911 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000912 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000913 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000914 command=callback,
915 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000916
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000917 def __recent_file_callback(self, file_name):
918 def open_recent_file(fn_closure=file_name):
919 self.io.open(editFile=fn_closure)
920 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000921
David Scherer7aced172000-08-15 01:13:23 +0000922 def saved_change_hook(self):
923 short = self.short_title()
924 long = self.long_title()
925 if short and long:
926 title = short + " - " + long
927 elif short:
928 title = short
929 elif long:
930 title = long
931 else:
932 title = "Untitled"
933 icon = short or long or title
934 if not self.get_saved():
935 title = "*%s*" % title
936 icon = "*%s" % icon
937 self.top.wm_title(title)
938 self.top.wm_iconname(icon)
939
940 def get_saved(self):
941 return self.undo.get_saved()
942
943 def set_saved(self, flag):
944 self.undo.set_saved(flag)
945
946 def reset_undo(self):
947 self.undo.reset_undo()
948
949 def short_title(self):
950 filename = self.io.filename
951 if filename:
952 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000953 # return unicode string to display non-ASCII chars correctly
954 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000955
956 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000957 # return unicode string to display non-ASCII chars correctly
958 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000959
960 def center_insert_event(self, event):
961 self.center()
962
963 def center(self, mark="insert"):
964 text = self.text
965 top, bot = self.getwindowlines()
966 lineno = self.getlineno(mark)
967 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000968 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000969 text.yview(float(newtop))
970
971 def getwindowlines(self):
972 text = self.text
973 top = self.getlineno("@0,0")
974 bot = self.getlineno("@0,65535")
975 if top == bot and text.winfo_height() == 1:
976 # Geometry manager hasn't run yet
977 height = int(text['height'])
978 bot = top + height - 1
979 return top, bot
980
981 def getlineno(self, mark="insert"):
982 text = self.text
983 return int(float(text.index(mark)))
984
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000985 def get_geometry(self):
986 "Return (width, height, x, y)"
987 geom = self.top.wm_geometry()
988 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000989 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000990
David Scherer7aced172000-08-15 01:13:23 +0000991 def close_event(self, event):
992 self.close()
993
994 def maybesave(self):
995 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000996 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000997 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000998 self.top.deiconify()
999 self.top.lower()
1000 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001001 return self.io.maybesave()
1002
1003 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001004 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +00001005 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001006 self._close()
1007 return reply
1008
1009 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001010 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001011 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001012 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001013 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001014 self.io.close()
1015 self.io = None
1016 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001017 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001018 self.color.close(False)
1019 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001020 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001021 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001022 self.per.close()
1023 self.per = None
1024 self.top.destroy()
1025 if self.close_hook:
1026 # unless override: unregister from flist, terminate if last window
1027 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001028
1029 def load_extensions(self):
1030 self.extensions = {}
1031 self.load_standard_extensions()
1032
1033 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001034 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001035 if hasattr(ins, "close"):
1036 ins.close()
1037 self.extensions = {}
1038
1039 def load_standard_extensions(self):
1040 for name in self.get_standard_extension_names():
1041 try:
1042 self.load_extension(name)
1043 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001044 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001045 traceback.print_exc()
1046
1047 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001048 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001049
1050 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001051 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001052 try:
1053 mod = importlib.import_module('.' + name, package=__package__)
1054 except ImportError:
1055 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001056 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001057 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001058 raise
David Scherer7aced172000-08-15 01:13:23 +00001059 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001060 keydefs = idleConf.GetExtensionBindings(name)
1061 if hasattr(cls, "menudefs"):
1062 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001063 ins = cls(self)
1064 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001065 if keydefs:
1066 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001067 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001068 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001069 while methodname[:1] == '<':
1070 methodname = methodname[1:]
1071 while methodname[-1:] == '>':
1072 methodname = methodname[:-1]
1073 methodname = methodname + "_event"
1074 if hasattr(ins, methodname):
1075 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001076
1077 def apply_bindings(self, keydefs=None):
1078 if keydefs is None:
1079 keydefs = self.Bindings.default_keydefs
1080 text = self.text
1081 text.keydefs = keydefs
1082 for event, keylist in keydefs.items():
1083 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001084 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001085
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001086 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001087 """Add appropriate entries to the menus and submenus
1088
1089 Menus that are absent or None in self.menudict are ignored.
1090 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001091 if menudefs is None:
1092 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001093 if keydefs is None:
1094 keydefs = self.Bindings.default_keydefs
1095 menudict = self.menudict
1096 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001097 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001098 menu = menudict.get(mname)
1099 if not menu:
1100 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001101 for entry in entrylist:
1102 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001103 menu.add_separator()
1104 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001105 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001106 checkbutton = (label[:1] == '!')
1107 if checkbutton:
1108 label = label[1:]
1109 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001110 accelerator = get_accelerator(keydefs, eventname)
1111 def command(text=text, eventname=eventname):
1112 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001113 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001114 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001115 menu.add_checkbutton(label=label, underline=underline,
1116 command=command, accelerator=accelerator,
1117 variable=var)
1118 else:
1119 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001120 command=command,
1121 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001122
1123 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001124 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001125 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001126 value = var.get()
1127 return value
1128 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001129 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001130
1131 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001132 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001133 if var:
1134 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001135 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001136 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001137
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001138 def get_var_obj(self, name, vartype=None):
1139 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001140 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001141 # create a Tkinter variable object with self.text as master:
1142 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001143 return var
1144
1145 # Tk implementations of "virtual text methods" -- each platform
1146 # reusing IDLE's support code needs to define these for its GUI's
1147 # flavor of widget.
1148
1149 # Is character at text_index in a Python string? Return 0 for
1150 # "guaranteed no", true for anything else. This info is expensive
1151 # to compute ab initio, but is probably already known by the
1152 # platform's colorizer.
1153
1154 def is_char_in_string(self, text_index):
1155 if self.color:
1156 # Return true iff colorizer hasn't (re)gotten this far
1157 # yet, or the character is tagged as being in a string
1158 return self.text.tag_prevrange("TODO", text_index) or \
1159 "STRING" in self.text.tag_names(text_index)
1160 else:
1161 # The colorizer is missing: assume the worst
1162 return 1
1163
1164 # If a selection is defined in the text widget, return (start,
1165 # end) as Tkinter text indices, otherwise return (None, None)
1166 def get_selection_indices(self):
1167 try:
1168 first = self.text.index("sel.first")
1169 last = self.text.index("sel.last")
1170 return first, last
1171 except TclError:
1172 return None, None
1173
1174 # Return the text widget's current view of what a tab stop means
1175 # (equivalent width in spaces).
1176
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001177 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001178 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1179 return int(current)
1180
1181 # Set the text widget's current view of what a tab stop means.
1182
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001183 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001184 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001185 if self.get_tk_tabwidth() != newtabwidth:
1186 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001187 pixels = text.tk.call("font", "measure", text["font"],
1188 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001189 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001190 text.configure(tabs=pixels)
1191
Guido van Rossum33d26892007-08-05 15:29:28 +00001192### begin autoindent code ### (configuration was moved to beginning of class)
1193
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001194 def set_indentation_params(self, is_py_src, guess=True):
1195 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001196 i = self.guess_indent()
1197 if 2 <= i <= 8:
1198 self.indentwidth = i
1199 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001200 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001201 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001202
1203 def smart_backspace_event(self, event):
1204 text = self.text
1205 first, last = self.get_selection_indices()
1206 if first and last:
1207 text.delete(first, last)
1208 text.mark_set("insert", first)
1209 return "break"
1210 # Delete whitespace left, until hitting a real char or closest
1211 # preceding virtual tab stop.
1212 chars = text.get("insert linestart", "insert")
1213 if chars == '':
1214 if text.compare("insert", ">", "1.0"):
1215 # easy: delete preceding newline
1216 text.delete("insert-1c")
1217 else:
1218 text.bell() # at start of buffer
1219 return "break"
1220 if chars[-1] not in " \t":
1221 # easy: delete preceding real char
1222 text.delete("insert-1c")
1223 return "break"
1224 # Ick. It may require *inserting* spaces if we back up over a
1225 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001226 tabwidth = self.tabwidth
1227 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001228 assert have > 0
1229 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001230 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001231 if self.context_use_ps1:
1232 last_line_of_prompt = sys.ps1.split('\n')[-1]
1233 else:
1234 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001235 ncharsdeleted = 0
1236 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001237 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001238 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001239 chars = chars[:-1]
1240 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001241 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001242 if have <= want or chars[-1] not in " \t":
1243 break
1244 text.undo_block_start()
1245 text.delete("insert-%dc" % ncharsdeleted, "insert")
1246 if have < want:
1247 text.insert("insert", ' ' * (want - have))
1248 text.undo_block_stop()
1249 return "break"
1250
1251 def smart_indent_event(self, event):
1252 # if intraline selection:
1253 # delete it
1254 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001255 # do indent-region
1256 # else:
1257 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001258 text = self.text
1259 first, last = self.get_selection_indices()
1260 text.undo_block_start()
1261 try:
1262 if first and last:
1263 if index2line(first) != index2line(last):
1264 return self.indent_region_event(event)
1265 text.delete(first, last)
1266 text.mark_set("insert", first)
1267 prefix = text.get("insert linestart", "insert")
1268 raw, effective = classifyws(prefix, self.tabwidth)
1269 if raw == len(prefix):
1270 # only whitespace to the left
1271 self.reindent_to(effective + self.indentwidth)
1272 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001273 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001274 if self.usetabs:
1275 pad = '\t'
1276 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001277 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001278 n = self.indentwidth
1279 pad = ' ' * (n - effective % n)
1280 text.insert("insert", pad)
1281 text.see("insert")
1282 return "break"
1283 finally:
1284 text.undo_block_stop()
1285
1286 def newline_and_indent_event(self, event):
1287 text = self.text
1288 first, last = self.get_selection_indices()
1289 text.undo_block_start()
1290 try:
1291 if first and last:
1292 text.delete(first, last)
1293 text.mark_set("insert", first)
1294 line = text.get("insert linestart", "insert")
1295 i, n = 0, len(line)
1296 while i < n and line[i] in " \t":
1297 i = i+1
1298 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001299 # the cursor is in or at leading indentation in a continuation
1300 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001301 text.insert("insert linestart", '\n')
1302 return "break"
1303 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001304 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001305 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001306 last_line_of_prompt = sys.ps1.split('\n')[-1]
1307 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001308 line = line[:-1]
1309 i = i+1
1310 if i:
1311 text.delete("insert - %d chars" % i, "insert")
1312 # strip whitespace after insert point
1313 while text.get("insert") in " \t":
1314 text.delete("insert")
1315 # start new line
1316 text.insert("insert", '\n')
1317
1318 # adjust indentation for continuations and block
1319 # open/close first need to find the last stmt
1320 lno = index2line(text.index('insert'))
1321 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001322 if not self.context_use_ps1:
1323 for context in self.num_context_lines:
1324 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001325 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001326 rawtext = text.get(startatindex, "insert")
1327 y.set_str(rawtext)
1328 bod = y.find_good_parse_start(
1329 self.context_use_ps1,
1330 self._build_char_in_string_func(startatindex))
1331 if bod is not None or startat == 1:
1332 break
1333 y.set_lo(bod or 0)
1334 else:
1335 r = text.tag_prevrange("console", "insert")
1336 if r:
1337 startatindex = r[1]
1338 else:
1339 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001340 rawtext = text.get(startatindex, "insert")
1341 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001342 y.set_lo(0)
1343
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001344 c = y.get_continuation_type()
1345 if c != PyParse.C_NONE:
1346 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001347 if c == PyParse.C_STRING_FIRST_LINE:
1348 # after the first line of a string; do not indent at all
1349 pass
1350 elif c == PyParse.C_STRING_NEXT_LINES:
1351 # inside a string which started before this line;
1352 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001353 text.insert("insert", indent)
1354 elif c == PyParse.C_BRACKET:
1355 # line up with the first (if any) element of the
1356 # last open bracket structure; else indent one
1357 # level beyond the indent of the line with the
1358 # last open bracket
1359 self.reindent_to(y.compute_bracket_indent())
1360 elif c == PyParse.C_BACKSLASH:
1361 # if more than one line in this stmt already, just
1362 # mimic the current indent; else if initial line
1363 # has a start on an assignment stmt, indent to
1364 # beyond leftmost =; else to beyond first chunk of
1365 # non-whitespace on initial line
1366 if y.get_num_lines_in_stmt() > 1:
1367 text.insert("insert", indent)
1368 else:
1369 self.reindent_to(y.compute_backslash_indent())
1370 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001371 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001372 return "break"
1373
1374 # This line starts a brand new stmt; indent relative to
1375 # indentation of initial line of closest preceding
1376 # interesting stmt.
1377 indent = y.get_base_indent_string()
1378 text.insert("insert", indent)
1379 if y.is_block_opener():
1380 self.smart_indent_event(event)
1381 elif indent and y.is_block_closer():
1382 self.smart_backspace_event(event)
1383 return "break"
1384 finally:
1385 text.see("insert")
1386 text.undo_block_stop()
1387
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001388 # Our editwin provides a is_char_in_string function that works
1389 # with a Tk text index, but PyParse only knows about offsets into
1390 # a string. This builds a function for PyParse that accepts an
1391 # offset.
1392
1393 def _build_char_in_string_func(self, startindex):
1394 def inner(offset, _startindex=startindex,
1395 _icis=self.is_char_in_string):
1396 return _icis(_startindex + "+%dc" % offset)
1397 return inner
1398
1399 def indent_region_event(self, event):
1400 head, tail, chars, lines = self.get_region()
1401 for pos in range(len(lines)):
1402 line = lines[pos]
1403 if line:
1404 raw, effective = classifyws(line, self.tabwidth)
1405 effective = effective + self.indentwidth
1406 lines[pos] = self._make_blanks(effective) + line[raw:]
1407 self.set_region(head, tail, chars, lines)
1408 return "break"
1409
1410 def dedent_region_event(self, event):
1411 head, tail, chars, lines = self.get_region()
1412 for pos in range(len(lines)):
1413 line = lines[pos]
1414 if line:
1415 raw, effective = classifyws(line, self.tabwidth)
1416 effective = max(effective - self.indentwidth, 0)
1417 lines[pos] = self._make_blanks(effective) + line[raw:]
1418 self.set_region(head, tail, chars, lines)
1419 return "break"
1420
1421 def comment_region_event(self, event):
1422 head, tail, chars, lines = self.get_region()
1423 for pos in range(len(lines) - 1):
1424 line = lines[pos]
1425 lines[pos] = '##' + line
1426 self.set_region(head, tail, chars, lines)
1427
1428 def uncomment_region_event(self, event):
1429 head, tail, chars, lines = self.get_region()
1430 for pos in range(len(lines)):
1431 line = lines[pos]
1432 if not line:
1433 continue
1434 if line[:2] == '##':
1435 line = line[2:]
1436 elif line[:1] == '#':
1437 line = line[1:]
1438 lines[pos] = line
1439 self.set_region(head, tail, chars, lines)
1440
1441 def tabify_region_event(self, event):
1442 head, tail, chars, lines = self.get_region()
1443 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001444 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001445 for pos in range(len(lines)):
1446 line = lines[pos]
1447 if line:
1448 raw, effective = classifyws(line, tabwidth)
1449 ntabs, nspaces = divmod(effective, tabwidth)
1450 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1451 self.set_region(head, tail, chars, lines)
1452
1453 def untabify_region_event(self, event):
1454 head, tail, chars, lines = self.get_region()
1455 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001456 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001457 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001458 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001459 self.set_region(head, tail, chars, lines)
1460
1461 def toggle_tabs_event(self, event):
1462 if self.askyesno(
1463 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001464 "Turn tabs " + ("on", "off")[self.usetabs] +
1465 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001466 ("will be", "remains at")[self.usetabs] + " 8." +
1467 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001468 parent=self.text):
1469 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001470 # Try to prevent inconsistent indentation.
1471 # User must change indent width manually after using tabs.
1472 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001473 return "break"
1474
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001475 # XXX this isn't bound to anything -- see tabwidth comments
1476## def change_tabwidth_event(self, event):
1477## new = self._asktabwidth()
1478## if new != self.tabwidth:
1479## self.tabwidth = new
1480## self.set_indentation_params(0, guess=0)
1481## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001482
1483 def change_indentwidth_event(self, event):
1484 new = self.askinteger(
1485 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001486 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001487 parent=self.text,
1488 initialvalue=self.indentwidth,
1489 minvalue=2,
1490 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001491 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001492 self.indentwidth = new
1493 return "break"
1494
1495 def get_region(self):
1496 text = self.text
1497 first, last = self.get_selection_indices()
1498 if first and last:
1499 head = text.index(first + " linestart")
1500 tail = text.index(last + "-1c lineend +1c")
1501 else:
1502 head = text.index("insert linestart")
1503 tail = text.index("insert lineend +1c")
1504 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001505 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001506 return head, tail, chars, lines
1507
1508 def set_region(self, head, tail, chars, lines):
1509 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001510 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001511 if newchars == chars:
1512 text.bell()
1513 return
1514 text.tag_remove("sel", "1.0", "end")
1515 text.mark_set("insert", head)
1516 text.undo_block_start()
1517 text.delete(head, tail)
1518 text.insert(head, newchars)
1519 text.undo_block_stop()
1520 text.tag_add("sel", head, "insert")
1521
1522 # Make string that displays as n leading blanks.
1523
1524 def _make_blanks(self, n):
1525 if self.usetabs:
1526 ntabs, nspaces = divmod(n, self.tabwidth)
1527 return '\t' * ntabs + ' ' * nspaces
1528 else:
1529 return ' ' * n
1530
1531 # Delete from beginning of line to insert point, then reinsert
1532 # column logical (meaning use tabs if appropriate) spaces.
1533
1534 def reindent_to(self, column):
1535 text = self.text
1536 text.undo_block_start()
1537 if text.compare("insert linestart", "!=", "insert"):
1538 text.delete("insert linestart", "insert")
1539 if column:
1540 text.insert("insert", self._make_blanks(column))
1541 text.undo_block_stop()
1542
1543 def _asktabwidth(self):
1544 return self.askinteger(
1545 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001546 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001547 parent=self.text,
1548 initialvalue=self.indentwidth,
1549 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001550 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001551
1552 # Guess indentwidth from text content.
1553 # Return guessed indentwidth. This should not be believed unless
1554 # it's in a reasonable range (e.g., it will be 0 if no indented
1555 # blocks are found).
1556
1557 def guess_indent(self):
1558 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1559 if opener and indented:
1560 raw, indentsmall = classifyws(opener, self.tabwidth)
1561 raw, indentlarge = classifyws(indented, self.tabwidth)
1562 else:
1563 indentsmall = indentlarge = 0
1564 return indentlarge - indentsmall
1565
1566# "line.col" -> line, as an int
1567def index2line(index):
1568 return int(float(index))
1569
1570# Look at the leading whitespace in s.
1571# Return pair (# of leading ws characters,
1572# effective # of leading blanks after expanding
1573# tabs to width tabwidth)
1574
1575def classifyws(s, tabwidth):
1576 raw = effective = 0
1577 for ch in s:
1578 if ch == ' ':
1579 raw = raw + 1
1580 effective = effective + 1
1581 elif ch == '\t':
1582 raw = raw + 1
1583 effective = (effective // tabwidth + 1) * tabwidth
1584 else:
1585 break
1586 return raw, effective
1587
1588import tokenize
1589_tokenize = tokenize
1590del tokenize
1591
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001592class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001593
1594 # .run() chews over the Text widget, looking for a block opener
1595 # and the stmt following it. Returns a pair,
1596 # (line containing block opener, line containing stmt)
1597 # Either or both may be None.
1598
1599 def __init__(self, text, tabwidth):
1600 self.text = text
1601 self.tabwidth = tabwidth
1602 self.i = self.finished = 0
1603 self.blkopenline = self.indentedline = None
1604
1605 def readline(self):
1606 if self.finished:
1607 return ""
1608 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001609 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001610 if self.text.compare(mark, ">=", "end"):
1611 return ""
1612 return self.text.get(mark, mark + " lineend+1c")
1613
1614 def tokeneater(self, type, token, start, end, line,
1615 INDENT=_tokenize.INDENT,
1616 NAME=_tokenize.NAME,
1617 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1618 if self.finished:
1619 pass
1620 elif type == NAME and token in OPENERS:
1621 self.blkopenline = line
1622 elif type == INDENT and self.blkopenline:
1623 self.indentedline = line
1624 self.finished = 1
1625
1626 def run(self):
1627 save_tabsize = _tokenize.tabsize
1628 _tokenize.tabsize = self.tabwidth
1629 try:
1630 try:
Trent Nelson428de652008-03-18 22:41:35 +00001631 tokens = _tokenize.generate_tokens(self.readline)
1632 for token in tokens:
1633 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001634 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001635 # since we cut off the tokenizer early, we can trigger
1636 # spurious errors
1637 pass
1638 finally:
1639 _tokenize.tabsize = save_tabsize
1640 return self.blkopenline, self.indentedline
1641
1642### end autoindent code ###
1643
David Scherer7aced172000-08-15 01:13:23 +00001644def prepstr(s):
1645 # Helper to extract the underscore from a string, e.g.
1646 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001647 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001648 if i >= 0:
1649 s = s[:i] + s[i+1:]
1650 return i, s
1651
1652
1653keynames = {
1654 'bracketleft': '[',
1655 'bracketright': ']',
1656 'slash': '/',
1657}
1658
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001659def get_accelerator(keydefs, eventname):
1660 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001661 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1662 # if not keylist:
1663 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1664 "<<open-module>>",
1665 "<<goto-line>>",
1666 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001667 return ""
1668 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001669 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001670 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1671 s = re.sub("Key-", "", s)
1672 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1673 s = re.sub("Control-", "Ctrl-", s)
1674 s = re.sub("-", "+", s)
1675 s = re.sub("><", " ", s)
1676 s = re.sub("<", "", s)
1677 s = re.sub(">", "", s)
1678 return s
1679
1680
1681def fixwordbreaks(root):
1682 # Make sure that Tk's double-click and next/previous word
1683 # operations use our definition of a word (i.e. an identifier)
1684 tk = root.tk
1685 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1686 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1687 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1688
1689
1690def test():
1691 root = Tk()
1692 fixwordbreaks(root)
1693 root.withdraw()
1694 if sys.argv[1:]:
1695 filename = sys.argv[1]
1696 else:
1697 filename = None
1698 edit = EditorWindow(root=root, filename=filename)
1699 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001700 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001701 root.mainloop()
1702 root.destroy()
1703
1704if __name__ == '__main__':
1705 test()