blob: 4bf111148284f91453974210e374c28a610fe463 [file] [log] [blame]
Brett Cannonaef82d32012-04-14 20:44:23 -04001import importlib
Brett Cannon50793b42013-06-07 13:17:48 -04002import importlib.abc
David Scherer7aced172000-08-15 01:13:23 +00003import os
Terry Jan Reedy94338de2014-01-23 00:36:46 -05004from platform import python_version
David Scherer7aced172000-08-15 01:13:23 +00005import re
Guido van Rossum33d26892007-08-05 15:29:28 +00006import string
Brett Cannonaef82d32012-04-14 20:44:23 -04007import sys
Georg Brandl14fc4272008-05-17 18:39:55 +00008from tkinter import *
9import tkinter.simpledialog as tkSimpleDialog
10import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +000011import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000012import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000013
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000014from idlelib.MultiCall import MultiCallCreator
15from idlelib import idlever
16from idlelib import WindowList
17from idlelib import SearchDialog
18from idlelib import GrepDialog
19from idlelib import ReplaceDialog
20from idlelib import PyParse
21from idlelib.configHandler import idleConf
22from idlelib import aboutDialog, textView, configDialog
23from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000024
25# The default tab setting for a Text widget, in average-width characters.
26TK_TABWIDTH_DEFAULT = 8
27
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000028def _sphinx_version():
29 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
30 major, minor, micro, level, serial = sys.version_info
31 release = '%s%s' % (major, minor)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020032 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000033 if level == 'candidate':
34 release += 'rc%s' % (serial,)
35 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000036 release += '%s%s' % (level[0], serial)
37 return release
38
Terry Jan Reedye91e7632012-02-05 15:14:20 -050039
40class HelpDialog(object):
41
42 def __init__(self):
43 self.parent = None # parent of help window
44 self.dlg = None # the help window iteself
45
46 def display(self, parent, near=None):
47 """ Display the help dialog.
48
49 parent - parent widget for the help window
50
51 near - a Toplevel widget (e.g. EditorWindow or PyShell)
52 to use as a reference for placing the help window
53 """
54 if self.dlg is None:
55 self.show_dialog(parent)
56 if near:
57 self.nearwindow(near)
58
59 def show_dialog(self, parent):
60 self.parent = parent
61 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
62 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
63 dlg.bind('<Destroy>', self.destroy, '+')
64
65 def nearwindow(self, near):
66 # Place the help dialog near the window specified by parent.
67 # Note - this may not reposition the window in Metacity
68 # if "/apps/metacity/general/disable_workarounds" is enabled
69 dlg = self.dlg
70 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
71 dlg.withdraw()
72 dlg.geometry("=+%d+%d" % geom)
73 dlg.deiconify()
74 dlg.lift()
75
76 def destroy(self, ev=None):
77 self.dlg = None
78 self.parent = None
79
80helpDialog = HelpDialog() # singleton instance
81
82
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000083class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000084 from idlelib.Percolator import Percolator
85 from idlelib.ColorDelegator import ColorDelegator
86 from idlelib.UndoDelegator import UndoDelegator
87 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
88 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000089 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000090 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000091
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000092 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000093
94 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000095 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +010096 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000097 if sys.platform.count('linux'):
98 # look for html docs in a couple of standard places
99 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
100 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
101 dochome = '/var/www/html/python/index.html'
102 else:
103 basepath = '/usr/share/doc/' # standard location
104 dochome = os.path.join(basepath, pyver,
105 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000106 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100107 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +0000108 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000109 if os.path.isfile(chmfile):
110 dochome = chmfile
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000111 elif macosxSupport.runningAsOSXApp():
112 # documentation is stored inside the python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100113 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000114 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000115 dochome = os.path.normpath(dochome)
116 if os.path.isfile(dochome):
117 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000118 if sys.platform == 'darwin':
119 # Safari requires real file:-URLs
120 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000121 else:
Benjamin Peterson152b6572009-01-20 15:01:54 +0000122 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000123 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +0000124 self.flist = flist
125 root = root or flist.root
126 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000127 try:
128 sys.ps1
129 except AttributeError:
130 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000131 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000132 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000133 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000134 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200135 #self.top.instance_dict makes flist.inversedict available to
136 #configDialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000137 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000138 else:
139 self.tkinter_vars = {} # keys: Tkinter event names
140 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000141 self.top.instance_dict = {}
142 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000143 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000144 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000145 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200146 self.width = idleConf.GetOption('main', 'EditorWindow',
147 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000148 text_options = {
149 'name': 'text',
150 'padx': 5,
151 'wrap': 'none',
152 'width': self.width,
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200153 'height': idleConf.GetOption('main', 'EditorWindow',
154 'height', type='int')}
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000155 if TkVersion >= 8.5:
156 # Starting with tk 8.5 we have to set the new tabstyle option
157 # to 'wordprocessor' to achieve the same display of tabs as in
158 # older tk versions.
159 text_options['tabstyle'] = 'wordprocessor'
160 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000161 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000162
163 self.createmenubar()
164 self.apply_bindings()
165
166 self.top.protocol("WM_DELETE_WINDOW", self.close)
167 self.top.bind("<<close-window>>", self.close_event)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000168 if macosxSupport.runningAsOSXApp():
169 # Command-W on editorwindows doesn't work without this.
170 text.bind('<<close-window>>', self.close_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000171 # Some OS X systems have only one mouse button,
172 # so use control-click for pulldown menus there.
173 # (Note, AquaTk defines <2> as the right button if
174 # present and the Tk Text widget already binds <2>.)
175 text.bind("<Control-Button-1>",self.right_menu_event)
176 else:
177 # Elsewhere, use right-click for pulldown menus.
178 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000179 text.bind("<<cut>>", self.cut)
180 text.bind("<<copy>>", self.copy)
181 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000182 text.bind("<<center-insert>>", self.center_insert_event)
183 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000184 text.bind("<<python-docs>>", self.python_docs)
185 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000186 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000187 text.bind("<<open-module>>", self.open_module)
188 text.bind("<<do-nothing>>", lambda event: "break")
189 text.bind("<<select-all>>", self.select_all)
190 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000191 text.bind("<<find>>", self.find_event)
192 text.bind("<<find-again>>", self.find_again_event)
193 text.bind("<<find-in-files>>", self.find_in_files_event)
194 text.bind("<<find-selection>>", self.find_selection_event)
195 text.bind("<<replace>>", self.replace_event)
196 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000197 text.bind("<<smart-backspace>>",self.smart_backspace_event)
198 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
199 text.bind("<<smart-indent>>",self.smart_indent_event)
200 text.bind("<<indent-region>>",self.indent_region_event)
201 text.bind("<<dedent-region>>",self.dedent_region_event)
202 text.bind("<<comment-region>>",self.comment_region_event)
203 text.bind("<<uncomment-region>>",self.uncomment_region_event)
204 text.bind("<<tabify-region>>",self.tabify_region_event)
205 text.bind("<<untabify-region>>",self.untabify_region_event)
206 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
207 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000208 text.bind("<Left>", self.move_at_edge_if_selection(0))
209 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000210 text.bind("<<del-word-left>>", self.del_word_left)
211 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000212 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000213
David Scherer7aced172000-08-15 01:13:23 +0000214 if flist:
215 flist.inversedict[self] = key
216 if key:
217 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000218 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000219 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
220 text.bind("<<open-class-browser>>", self.open_class_browser)
221 text.bind("<<open-path-browser>>", self.open_path_browser)
222
Steven M. Gava898a3652001-10-07 11:10:44 +0000223 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000224 vbar['command'] = text.yview
225 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000226 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000227 fontWeight = 'normal'
228 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000229 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000230 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200231 idleConf.GetOption('main', 'EditorWindow',
232 'font-size', type='int'),
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000233 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000234 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
235 text.pack(side=TOP, fill=BOTH, expand=1)
236 text.focus_set()
237
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000238 # usetabs true -> literal tab characters are used by indent and
239 # dedent cmds, possibly mixed with spaces if
240 # indentwidth is not a multiple of tabwidth,
241 # which will cause Tabnanny to nag!
242 # false -> tab characters are converted to spaces by indent
243 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000244 # Although use-spaces=0 can be configured manually in config-main.def,
245 # configuration of tabs v. spaces is not supported in the configuration
246 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200247 usespaces = idleConf.GetOption('main', 'Indent',
248 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000249 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000250
251 # tabwidth is the display width of a literal tab character.
252 # CAUTION: telling Tk to use anything other than its default
253 # tab setting causes it to use an entirely different tabbing algorithm,
254 # treating tab stops as fixed distances from the left margin.
255 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000256 self.tabwidth = 8 # must remain 8 until Tk is fixed.
257
258 # indentwidth is the number of screen characters per indent level.
259 # The recommended Python indentation is four spaces.
260 self.indentwidth = self.tabwidth
261 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000262
263 # If context_use_ps1 is true, parsing searches back for a ps1 line;
264 # else searches for a popular (if, def, ...) Python stmt.
265 self.context_use_ps1 = False
266
267 # When searching backwards for a reliable place to begin parsing,
268 # first start num_context_lines[0] lines back, then
269 # num_context_lines[1] lines back if that didn't work, and so on.
270 # The last value should be huge (larger than the # of lines in a
271 # conceivable file).
272 # Making the initial values larger slows things down more often.
273 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000274 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000275 self.undo = undo = self.UndoDelegator()
276 per.insertfilter(undo)
277 text.undo_block_start = undo.undo_block_start
278 text.undo_block_stop = undo.undo_block_stop
279 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000280 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000281 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000282 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000283 self.good_load = False
284 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000285 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000286 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000287 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000288 if io.loadfile(filename):
289 self.good_load = True
290 is_py_src = self.ispythonsource(filename)
291 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000292 else:
293 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500294 self.good_load = True
295
Christian Heimesa156e092008-02-16 07:38:31 +0000296 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000297 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000298 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000299 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000300 menu = self.menudict.get('windows')
301 if menu:
302 end = menu.index("end")
303 if end is None:
304 end = -1
305 if end >= 0:
306 menu.add_separator()
307 end = end + 1
308 self.wmenu_end = end
309 WindowList.register_callback(self.postwindowsmenu)
310
311 # Some abstractions so IDLE extensions are cross-IDE
312 self.askyesno = tkMessageBox.askyesno
313 self.askinteger = tkSimpleDialog.askinteger
314 self.showerror = tkMessageBox.showerror
315
Roger Serwycaf30242013-05-20 22:13:39 -0500316 self._highlight_workaround() # Fix selection tags on Windows
317
318 def _highlight_workaround(self):
319 # On Windows, Tk removes painting of the selection
320 # tags which is different behavior than on Linux and Mac.
321 # See issue14146 for more information.
322 if not sys.platform.startswith('win'):
323 return
324
325 text = self.text
326 text.event_add("<<Highlight-FocusOut>>", "<FocusOut>")
327 text.event_add("<<Highlight-FocusIn>>", "<FocusIn>")
328 def highlight_fix(focus):
329 sel_range = text.tag_ranges("sel")
330 if sel_range:
331 if focus == 'out':
332 HILITE_CONFIG = idleConf.GetHighlight(
333 idleConf.CurrentTheme(), 'hilite')
334 text.tag_config("sel_fix", HILITE_CONFIG)
335 text.tag_raise("sel_fix")
336 text.tag_add("sel_fix", *sel_range)
337 elif focus == 'in':
338 text.tag_remove("sel_fix", "1.0", "end")
339
340 text.bind("<<Highlight-FocusOut>>",
341 lambda ev: highlight_fix("out"))
342 text.bind("<<Highlight-FocusIn>>",
343 lambda ev: highlight_fix("in"))
344
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():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000439 menu_specs[-2] = ("windows", "_Window")
440
441
David Scherer7aced172000-08-15 01:13:23 +0000442 def createmenubar(self):
443 mbar = self.menubar
444 self.menudict = menudict = {}
445 for name, label in self.menu_specs:
446 underline, label = prepstr(label)
447 menudict[name] = menu = Menu(mbar, name=name)
448 mbar.add_cascade(label=label, menu=menu, underline=underline)
Georg Brandlaedd2892010-12-19 10:10:32 +0000449 if macosxSupport.isCarbonAquaTk(self.root):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000450 # Insert the application menu
451 menudict['application'] = menu = Menu(mbar, name='apple')
452 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000453 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000454 self.recent_files_menu = Menu(self.menubar)
455 self.menudict['file'].insert_cascade(3, label='Recent Files',
456 underline=0,
457 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000458 self.base_helpmenu_length = self.menudict['help'].index(END)
459 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000460
461 def postwindowsmenu(self):
462 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000463 menu = self.menudict['windows']
464 end = menu.index("end")
465 if end is None:
466 end = -1
467 if end > self.wmenu_end:
468 menu.delete(self.wmenu_end+1, end)
469 WindowList.add_windows_to_menu(menu)
470
471 rmenu = None
472
473 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000474 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
475 if not self.rmenu:
476 self.make_rmenu()
477 rmenu = self.rmenu
478 self.event = event
479 iswin = sys.platform[:3] == 'win'
480 if iswin:
481 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200482
Roger Serwy6b2918a2013-04-07 12:15:52 -0500483 for item in self.rmenu_specs:
484 try:
485 label, eventname, verify_state = item
486 except ValueError: # see issue1207589
487 continue
488
Andrew Svetlovd1837672012-11-01 22:41:19 +0200489 if verify_state is None:
490 continue
491 state = getattr(self, verify_state)()
492 rmenu.entryconfigure(label, state=state)
493
494
David Scherer7aced172000-08-15 01:13:23 +0000495 rmenu.tk_popup(event.x_root, event.y_root)
496 if iswin:
497 self.text.config(cursor="ibeam")
498
499 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200500 # ("Label", "<<virtual-event>>", "statefuncname"), ...
501 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000502 ]
503
504 def make_rmenu(self):
505 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500506 for item in self.rmenu_specs:
507 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200508 if label is not None:
509 def command(text=self.text, eventname=eventname):
510 text.event_generate(eventname)
511 rmenu.add_command(label=label, command=command)
512 else:
513 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000514 self.rmenu = rmenu
515
Andrew Svetlovd1837672012-11-01 22:41:19 +0200516 def rmenu_check_cut(self):
517 return self.rmenu_check_copy()
518
519 def rmenu_check_copy(self):
520 try:
521 indx = self.text.index('sel.first')
522 except TclError:
523 return 'disabled'
524 else:
525 return 'normal' if indx else 'disabled'
526
527 def rmenu_check_paste(self):
528 try:
529 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
530 except TclError:
531 return 'disabled'
532 else:
533 return 'normal'
534
David Scherer7aced172000-08-15 01:13:23 +0000535 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000536 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000537
Steven M. Gava3b55a892001-11-21 05:56:26 +0000538 def config_dialog(self, event=None):
539 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000540
David Scherer7aced172000-08-15 01:13:23 +0000541 def help_dialog(self, event=None):
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500542 if self.root:
543 parent = self.root
544 else:
545 parent = self.top
546 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000547
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000548 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000549 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000550 try:
551 os.startfile(self.help_url)
552 except WindowsError as why:
553 tkMessageBox.showerror(title='Document Start Failure',
554 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000555 else:
556 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000557 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000558
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000559 def cut(self,event):
560 self.text.event_generate("<<Cut>>")
561 return "break"
562
563 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000564 if not self.text.tag_ranges("sel"):
565 # There is no selection, so do nothing and maybe interrupt.
566 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000567 self.text.event_generate("<<Copy>>")
568 return "break"
569
570 def paste(self,event):
571 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000572 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000573 return "break"
574
David Scherer7aced172000-08-15 01:13:23 +0000575 def select_all(self, event=None):
576 self.text.tag_add("sel", "1.0", "end-1c")
577 self.text.mark_set("insert", "1.0")
578 self.text.see("insert")
579 return "break"
580
581 def remove_selection(self, event=None):
582 self.text.tag_remove("sel", "1.0", "end")
583 self.text.see("insert")
584
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000585 def move_at_edge_if_selection(self, edge_index):
586 """Cursor move begins at start or end of selection
587
588 When a left/right cursor key is pressed create and return to Tkinter a
589 function which causes a cursor move from the associated edge of the
590 selection.
591
592 """
593 self_text_index = self.text.index
594 self_text_mark_set = self.text.mark_set
595 edges_table = ("sel.first+1c", "sel.last-1c")
596 def move_at_edge(event):
597 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
598 try:
599 self_text_index("sel.first")
600 self_text_mark_set("insert", edges_table[edge_index])
601 except TclError:
602 pass
603 return move_at_edge
604
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000605 def del_word_left(self, event):
606 self.text.event_generate('<Meta-Delete>')
607 return "break"
608
609 def del_word_right(self, event):
610 self.text.event_generate('<Meta-d>')
611 return "break"
612
Steven M. Gavac5976402002-01-04 03:06:08 +0000613 def find_event(self, event):
614 SearchDialog.find(self.text)
615 return "break"
616
617 def find_again_event(self, event):
618 SearchDialog.find_again(self.text)
619 return "break"
620
621 def find_selection_event(self, event):
622 SearchDialog.find_selection(self.text)
623 return "break"
624
625 def find_in_files_event(self, event):
626 GrepDialog.grep(self.text, self.io, self.flist)
627 return "break"
628
629 def replace_event(self, event):
630 ReplaceDialog.replace(self.text)
631 return "break"
632
633 def goto_line_event(self, event):
634 text = self.text
635 lineno = tkSimpleDialog.askinteger("Goto",
636 "Go to line number:",parent=text)
637 if lineno is None:
638 return "break"
639 if lineno <= 0:
640 text.bell()
641 return "break"
642 text.mark_set("insert", "%d.0" % lineno)
643 text.see("insert")
644
David Scherer7aced172000-08-15 01:13:23 +0000645 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000646 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000647 try:
648 name = self.text.get("sel.first", "sel.last")
649 except TclError:
650 name = ""
651 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000652 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000653 name = tkSimpleDialog.askstring("Module",
654 "Enter the name of a Python module\n"
655 "to search on sys.path and open:",
656 parent=self.text, initialvalue=name)
657 if name:
658 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000659 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000660 return
David Scherer7aced172000-08-15 01:13:23 +0000661 # XXX Ought to insert current file's directory in front of path
662 try:
Brett Cannon50793b42013-06-07 13:17:48 -0400663 loader = importlib.find_loader(name)
664 except (ValueError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000665 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
666 return
Brett Cannon50793b42013-06-07 13:17:48 -0400667 if loader is None:
668 tkMessageBox.showerror("Import error", "module not found",
669 parent=self.text)
David Scherer7aced172000-08-15 01:13:23 +0000670 return
Brett Cannon50793b42013-06-07 13:17:48 -0400671 if not isinstance(loader, importlib.abc.SourceLoader):
672 tkMessageBox.showerror("Import error", "not a source-based module",
673 parent=self.text)
674 return
675 try:
676 file_path = loader.get_filename(name)
677 except AttributeError:
678 tkMessageBox.showerror("Import error",
679 "loader does not support get_filename",
680 parent=self.text)
681 return
David Scherer7aced172000-08-15 01:13:23 +0000682 if self.flist:
Brett Cannon50793b42013-06-07 13:17:48 -0400683 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000684 else:
Brett Cannon50793b42013-06-07 13:17:48 -0400685 self.io.loadfile(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000686
687 def open_class_browser(self, event=None):
688 filename = self.io.filename
689 if not filename:
690 tkMessageBox.showerror(
691 "No filename",
692 "This buffer has no associated filename",
693 master=self.text)
694 self.text.focus_set()
695 return None
696 head, tail = os.path.split(filename)
697 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000698 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000699 ClassBrowser.ClassBrowser(self.flist, base, [head])
700
701 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000702 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000703 PathBrowser.PathBrowser(self.flist)
704
705 def gotoline(self, lineno):
706 if lineno is not None and lineno > 0:
707 self.text.mark_set("insert", "%d.0" % lineno)
708 self.text.tag_remove("sel", "1.0", "end")
709 self.text.tag_add("sel", "insert", "insert +1l")
710 self.center()
711
712 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000713 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000714 return True
David Scherer7aced172000-08-15 01:13:23 +0000715 base, ext = os.path.splitext(os.path.basename(filename))
716 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000717 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000718 line = self.text.get('1.0', '1.0 lineend')
719 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000720
721 def close_hook(self):
722 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000723 self.flist.unregister_maybe_terminate(self)
724 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000725
726 def set_close_hook(self, close_hook):
727 self.close_hook = close_hook
728
729 def filename_change_hook(self):
730 if self.flist:
731 self.flist.filename_changed_edit(self)
732 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000733 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000734 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000735
Christian Heimesa156e092008-02-16 07:38:31 +0000736 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000737 if self.color:
738 return
Christian Heimesa156e092008-02-16 07:38:31 +0000739 if self.ispythonsource(self.io.filename):
740 self.color = self.ColorDelegator()
741 # can add more colorizers here...
742 if self.color:
743 self.per.removefilter(self.undo)
744 self.per.insertfilter(self.color)
745 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000746
Christian Heimesa156e092008-02-16 07:38:31 +0000747 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000748 if not self.color:
749 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000750 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000751 self.per.removefilter(self.color)
752 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000753
Steven M. Gavab77d3432002-03-02 07:16:21 +0000754 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000755 "Update the colour theme"
756 # Called from self.filename_change_hook and from configDialog.py
757 self._rmcolorizer()
758 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000759 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000760 normal_colors = idleConf.GetHighlight(theme, 'normal')
761 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
762 select_colors = idleConf.GetHighlight(theme, 'hilite')
763 self.text.config(
764 foreground=normal_colors['foreground'],
765 background=normal_colors['background'],
766 insertbackground=cursor_color,
767 selectforeground=select_colors['foreground'],
768 selectbackground=select_colors['background'],
769 )
David Scherer7aced172000-08-15 01:13:23 +0000770
Guido van Rossum33d26892007-08-05 15:29:28 +0000771 IDENTCHARS = string.ascii_letters + string.digits + "_"
772
773 def colorize_syntax_error(self, text, pos):
774 text.tag_add("ERROR", pos)
775 char = text.get(pos)
776 if char and char in self.IDENTCHARS:
777 text.tag_add("ERROR", pos + " wordstart", pos)
778 if '\n' == text.get(pos): # error at line end
779 text.mark_set("insert", pos)
780 else:
781 text.mark_set("insert", pos + "+1c")
782 text.see(pos)
783
Steven M. Gavab1585412002-03-12 00:21:56 +0000784 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000785 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000786 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000787 fontWeight='normal'
788 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
789 fontWeight='bold'
790 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200791 idleConf.GetOption('main','EditorWindow','font-size',
792 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000793 fontWeight))
794
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000795 def RemoveKeybindings(self):
796 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000797 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000798 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000799 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000800 self.text.event_delete(event, *keylist)
801 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 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000805 self.text.event_delete(event, *keylist)
806
807 def ApplyKeybindings(self):
808 "Update the keybindings after they are changed"
809 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000810 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000811 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000812 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000813 xkeydefs = idleConf.GetExtensionBindings(extensionName)
814 if xkeydefs:
815 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000816 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000817 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000818 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000819 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000820 for item in menu[1]:
821 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000822 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000823 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000824 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700825 end = menu.index(END)
826 if end is None:
827 # Skip empty menus
828 continue
829 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000830 for index in range(0, end):
831 if menu.type(index) == 'command':
832 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000833 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000834 itemName = menu.entrycget(index, 'label')
835 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000836 if menubarItem in menuEventDict:
837 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000838 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000839 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000840 accel = get_accelerator(keydefs, event)
841 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000842
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000843 def set_notabs_indentwidth(self):
844 "Update the indentwidth if changed and not using tabs in this window"
845 # Called from configDialog.py
846 if not self.usetabs:
847 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
848 type='int')
849
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000850 def reset_help_menu_entries(self):
851 "Update the additional help entries on the Help menu"
852 help_list = idleConf.GetAllExtraHelpSourcesList()
853 helpmenu = self.menudict['help']
854 # first delete the extra help entries, if any
855 helpmenu_length = helpmenu.index(END)
856 if helpmenu_length > self.base_helpmenu_length:
857 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
858 # then rebuild them
859 if help_list:
860 helpmenu.add_separator()
861 for entry in help_list:
862 cmd = self.__extra_help_callback(entry[1])
863 helpmenu.add_command(label=entry[0], command=cmd)
864 # and update the menu dictionary
865 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000866
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000867 def __extra_help_callback(self, helpfile):
868 "Create a callback with the helpfile value frozen at definition time"
869 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000870 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000871 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000872 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000873 try:
874 os.startfile(helpfile)
875 except WindowsError as why:
876 tkMessageBox.showerror(title='Document Start Failure',
877 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000878 else:
879 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000880 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000881
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000882 def update_recent_files_list(self, new_file=None):
883 "Load and update the recent files list and menus"
884 rf_list = []
885 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400886 with open(self.recent_files_path, 'r',
887 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000888 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000889 if new_file:
890 new_file = os.path.abspath(new_file) + '\n'
891 if new_file in rf_list:
892 rf_list.remove(new_file) # move to top
893 rf_list.insert(0, new_file)
894 # clean and save the recent files list
895 bad_paths = []
896 for path in rf_list:
897 if '\0' in path or not os.path.exists(path[0:-1]):
898 bad_paths.append(path)
899 rf_list = [path for path in rf_list if path not in bad_paths]
900 ulchars = "1234567890ABCDEFGHIJK"
901 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000902 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800903 with open(self.recent_files_path, 'w',
904 encoding='utf_8', errors='replace') as rf_file:
905 rf_file.writelines(rf_list)
Terry Jan Reedyba6c0d32013-06-08 00:22:45 -0400906 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800907 if not getattr(self.root, "recentfilelist_error_displayed", False):
908 self.root.recentfilelist_error_displayed = True
909 tkMessageBox.showerror(title='IDLE Error',
910 message='Unable to update Recent Files list:\n%s'
911 % str(err),
912 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000913 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000914 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000915 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700916 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000917 for i, file_name in enumerate(rf_list):
918 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000919 # make unicode string to display non-ASCII chars correctly
920 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000921 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000922 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000923 command=callback,
924 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000925
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000926 def __recent_file_callback(self, file_name):
927 def open_recent_file(fn_closure=file_name):
928 self.io.open(editFile=fn_closure)
929 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000930
David Scherer7aced172000-08-15 01:13:23 +0000931 def saved_change_hook(self):
932 short = self.short_title()
933 long = self.long_title()
934 if short and long:
935 title = short + " - " + long
936 elif short:
937 title = short
938 elif long:
939 title = long
940 else:
941 title = "Untitled"
942 icon = short or long or title
943 if not self.get_saved():
944 title = "*%s*" % title
945 icon = "*%s" % icon
946 self.top.wm_title(title)
947 self.top.wm_iconname(icon)
948
949 def get_saved(self):
950 return self.undo.get_saved()
951
952 def set_saved(self, flag):
953 self.undo.set_saved(flag)
954
955 def reset_undo(self):
956 self.undo.reset_undo()
957
958 def short_title(self):
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500959 pyversion = "Python " + python_version() + ": "
David Scherer7aced172000-08-15 01:13:23 +0000960 filename = self.io.filename
961 if filename:
962 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500963 else:
964 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000965 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500966 return pyversion + self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000967
968 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000969 # return unicode string to display non-ASCII chars correctly
970 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000971
972 def center_insert_event(self, event):
973 self.center()
974
975 def center(self, mark="insert"):
976 text = self.text
977 top, bot = self.getwindowlines()
978 lineno = self.getlineno(mark)
979 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000980 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000981 text.yview(float(newtop))
982
983 def getwindowlines(self):
984 text = self.text
985 top = self.getlineno("@0,0")
986 bot = self.getlineno("@0,65535")
987 if top == bot and text.winfo_height() == 1:
988 # Geometry manager hasn't run yet
989 height = int(text['height'])
990 bot = top + height - 1
991 return top, bot
992
993 def getlineno(self, mark="insert"):
994 text = self.text
995 return int(float(text.index(mark)))
996
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000997 def get_geometry(self):
998 "Return (width, height, x, y)"
999 geom = self.top.wm_geometry()
1000 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +00001001 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001002
David Scherer7aced172000-08-15 01:13:23 +00001003 def close_event(self, event):
1004 self.close()
1005
1006 def maybesave(self):
1007 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001008 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001009 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001010 self.top.deiconify()
1011 self.top.lower()
1012 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001013 return self.io.maybesave()
1014
1015 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001016 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +00001017 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001018 self._close()
1019 return reply
1020
1021 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001022 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001023 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001024 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001025 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001026 self.io.close()
1027 self.io = None
1028 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001029 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001030 self.color.close(False)
1031 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001032 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001033 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001034 self.per.close()
1035 self.per = None
1036 self.top.destroy()
1037 if self.close_hook:
1038 # unless override: unregister from flist, terminate if last window
1039 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001040
1041 def load_extensions(self):
1042 self.extensions = {}
1043 self.load_standard_extensions()
1044
1045 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001046 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001047 if hasattr(ins, "close"):
1048 ins.close()
1049 self.extensions = {}
1050
1051 def load_standard_extensions(self):
1052 for name in self.get_standard_extension_names():
1053 try:
1054 self.load_extension(name)
1055 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001056 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001057 traceback.print_exc()
1058
1059 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001060 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001061
1062 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001063 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001064 try:
1065 mod = importlib.import_module('.' + name, package=__package__)
1066 except ImportError:
1067 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001068 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001069 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001070 raise
David Scherer7aced172000-08-15 01:13:23 +00001071 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001072 keydefs = idleConf.GetExtensionBindings(name)
1073 if hasattr(cls, "menudefs"):
1074 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001075 ins = cls(self)
1076 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001077 if keydefs:
1078 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001079 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001080 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001081 while methodname[:1] == '<':
1082 methodname = methodname[1:]
1083 while methodname[-1:] == '>':
1084 methodname = methodname[:-1]
1085 methodname = methodname + "_event"
1086 if hasattr(ins, methodname):
1087 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001088
1089 def apply_bindings(self, keydefs=None):
1090 if keydefs is None:
1091 keydefs = self.Bindings.default_keydefs
1092 text = self.text
1093 text.keydefs = keydefs
1094 for event, keylist in keydefs.items():
1095 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001096 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001097
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001098 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001099 """Add appropriate entries to the menus and submenus
1100
1101 Menus that are absent or None in self.menudict are ignored.
1102 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001103 if menudefs is None:
1104 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001105 if keydefs is None:
1106 keydefs = self.Bindings.default_keydefs
1107 menudict = self.menudict
1108 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001109 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001110 menu = menudict.get(mname)
1111 if not menu:
1112 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001113 for entry in entrylist:
1114 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001115 menu.add_separator()
1116 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001117 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001118 checkbutton = (label[:1] == '!')
1119 if checkbutton:
1120 label = label[1:]
1121 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001122 accelerator = get_accelerator(keydefs, eventname)
1123 def command(text=text, eventname=eventname):
1124 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001125 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001126 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001127 menu.add_checkbutton(label=label, underline=underline,
1128 command=command, accelerator=accelerator,
1129 variable=var)
1130 else:
1131 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001132 command=command,
1133 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001134
1135 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001136 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001137 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001138 value = var.get()
1139 return value
1140 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001141 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001142
1143 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001144 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001145 if var:
1146 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001147 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001148 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001149
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001150 def get_var_obj(self, name, vartype=None):
1151 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001152 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001153 # create a Tkinter variable object with self.text as master:
1154 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001155 return var
1156
1157 # Tk implementations of "virtual text methods" -- each platform
1158 # reusing IDLE's support code needs to define these for its GUI's
1159 # flavor of widget.
1160
1161 # Is character at text_index in a Python string? Return 0 for
1162 # "guaranteed no", true for anything else. This info is expensive
1163 # to compute ab initio, but is probably already known by the
1164 # platform's colorizer.
1165
1166 def is_char_in_string(self, text_index):
1167 if self.color:
1168 # Return true iff colorizer hasn't (re)gotten this far
1169 # yet, or the character is tagged as being in a string
1170 return self.text.tag_prevrange("TODO", text_index) or \
1171 "STRING" in self.text.tag_names(text_index)
1172 else:
1173 # The colorizer is missing: assume the worst
1174 return 1
1175
1176 # If a selection is defined in the text widget, return (start,
1177 # end) as Tkinter text indices, otherwise return (None, None)
1178 def get_selection_indices(self):
1179 try:
1180 first = self.text.index("sel.first")
1181 last = self.text.index("sel.last")
1182 return first, last
1183 except TclError:
1184 return None, None
1185
1186 # Return the text widget's current view of what a tab stop means
1187 # (equivalent width in spaces).
1188
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001189 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001190 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1191 return int(current)
1192
1193 # Set the text widget's current view of what a tab stop means.
1194
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001195 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001196 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001197 if self.get_tk_tabwidth() != newtabwidth:
1198 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001199 pixels = text.tk.call("font", "measure", text["font"],
1200 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001201 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001202 text.configure(tabs=pixels)
1203
Guido van Rossum33d26892007-08-05 15:29:28 +00001204### begin autoindent code ### (configuration was moved to beginning of class)
1205
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001206 def set_indentation_params(self, is_py_src, guess=True):
1207 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001208 i = self.guess_indent()
1209 if 2 <= i <= 8:
1210 self.indentwidth = i
1211 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001212 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001213 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001214
1215 def smart_backspace_event(self, event):
1216 text = self.text
1217 first, last = self.get_selection_indices()
1218 if first and last:
1219 text.delete(first, last)
1220 text.mark_set("insert", first)
1221 return "break"
1222 # Delete whitespace left, until hitting a real char or closest
1223 # preceding virtual tab stop.
1224 chars = text.get("insert linestart", "insert")
1225 if chars == '':
1226 if text.compare("insert", ">", "1.0"):
1227 # easy: delete preceding newline
1228 text.delete("insert-1c")
1229 else:
1230 text.bell() # at start of buffer
1231 return "break"
1232 if chars[-1] not in " \t":
1233 # easy: delete preceding real char
1234 text.delete("insert-1c")
1235 return "break"
1236 # Ick. It may require *inserting* spaces if we back up over a
1237 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001238 tabwidth = self.tabwidth
1239 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001240 assert have > 0
1241 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001242 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001243 if self.context_use_ps1:
1244 last_line_of_prompt = sys.ps1.split('\n')[-1]
1245 else:
1246 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001247 ncharsdeleted = 0
1248 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001249 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001250 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001251 chars = chars[:-1]
1252 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001253 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001254 if have <= want or chars[-1] not in " \t":
1255 break
1256 text.undo_block_start()
1257 text.delete("insert-%dc" % ncharsdeleted, "insert")
1258 if have < want:
1259 text.insert("insert", ' ' * (want - have))
1260 text.undo_block_stop()
1261 return "break"
1262
1263 def smart_indent_event(self, event):
1264 # if intraline selection:
1265 # delete it
1266 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001267 # do indent-region
1268 # else:
1269 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001270 text = self.text
1271 first, last = self.get_selection_indices()
1272 text.undo_block_start()
1273 try:
1274 if first and last:
1275 if index2line(first) != index2line(last):
1276 return self.indent_region_event(event)
1277 text.delete(first, last)
1278 text.mark_set("insert", first)
1279 prefix = text.get("insert linestart", "insert")
1280 raw, effective = classifyws(prefix, self.tabwidth)
1281 if raw == len(prefix):
1282 # only whitespace to the left
1283 self.reindent_to(effective + self.indentwidth)
1284 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001285 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001286 if self.usetabs:
1287 pad = '\t'
1288 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001289 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001290 n = self.indentwidth
1291 pad = ' ' * (n - effective % n)
1292 text.insert("insert", pad)
1293 text.see("insert")
1294 return "break"
1295 finally:
1296 text.undo_block_stop()
1297
1298 def newline_and_indent_event(self, event):
1299 text = self.text
1300 first, last = self.get_selection_indices()
1301 text.undo_block_start()
1302 try:
1303 if first and last:
1304 text.delete(first, last)
1305 text.mark_set("insert", first)
1306 line = text.get("insert linestart", "insert")
1307 i, n = 0, len(line)
1308 while i < n and line[i] in " \t":
1309 i = i+1
1310 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001311 # the cursor is in or at leading indentation in a continuation
1312 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001313 text.insert("insert linestart", '\n')
1314 return "break"
1315 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001316 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001317 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001318 last_line_of_prompt = sys.ps1.split('\n')[-1]
1319 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001320 line = line[:-1]
1321 i = i+1
1322 if i:
1323 text.delete("insert - %d chars" % i, "insert")
1324 # strip whitespace after insert point
1325 while text.get("insert") in " \t":
1326 text.delete("insert")
1327 # start new line
1328 text.insert("insert", '\n')
1329
1330 # adjust indentation for continuations and block
1331 # open/close first need to find the last stmt
1332 lno = index2line(text.index('insert'))
1333 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001334 if not self.context_use_ps1:
1335 for context in self.num_context_lines:
1336 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001337 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001338 rawtext = text.get(startatindex, "insert")
1339 y.set_str(rawtext)
1340 bod = y.find_good_parse_start(
1341 self.context_use_ps1,
1342 self._build_char_in_string_func(startatindex))
1343 if bod is not None or startat == 1:
1344 break
1345 y.set_lo(bod or 0)
1346 else:
1347 r = text.tag_prevrange("console", "insert")
1348 if r:
1349 startatindex = r[1]
1350 else:
1351 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001352 rawtext = text.get(startatindex, "insert")
1353 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001354 y.set_lo(0)
1355
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001356 c = y.get_continuation_type()
1357 if c != PyParse.C_NONE:
1358 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001359 if c == PyParse.C_STRING_FIRST_LINE:
1360 # after the first line of a string; do not indent at all
1361 pass
1362 elif c == PyParse.C_STRING_NEXT_LINES:
1363 # inside a string which started before this line;
1364 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001365 text.insert("insert", indent)
1366 elif c == PyParse.C_BRACKET:
1367 # line up with the first (if any) element of the
1368 # last open bracket structure; else indent one
1369 # level beyond the indent of the line with the
1370 # last open bracket
1371 self.reindent_to(y.compute_bracket_indent())
1372 elif c == PyParse.C_BACKSLASH:
1373 # if more than one line in this stmt already, just
1374 # mimic the current indent; else if initial line
1375 # has a start on an assignment stmt, indent to
1376 # beyond leftmost =; else to beyond first chunk of
1377 # non-whitespace on initial line
1378 if y.get_num_lines_in_stmt() > 1:
1379 text.insert("insert", indent)
1380 else:
1381 self.reindent_to(y.compute_backslash_indent())
1382 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001383 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001384 return "break"
1385
1386 # This line starts a brand new stmt; indent relative to
1387 # indentation of initial line of closest preceding
1388 # interesting stmt.
1389 indent = y.get_base_indent_string()
1390 text.insert("insert", indent)
1391 if y.is_block_opener():
1392 self.smart_indent_event(event)
1393 elif indent and y.is_block_closer():
1394 self.smart_backspace_event(event)
1395 return "break"
1396 finally:
1397 text.see("insert")
1398 text.undo_block_stop()
1399
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001400 # Our editwin provides a is_char_in_string function that works
1401 # with a Tk text index, but PyParse only knows about offsets into
1402 # a string. This builds a function for PyParse that accepts an
1403 # offset.
1404
1405 def _build_char_in_string_func(self, startindex):
1406 def inner(offset, _startindex=startindex,
1407 _icis=self.is_char_in_string):
1408 return _icis(_startindex + "+%dc" % offset)
1409 return inner
1410
1411 def indent_region_event(self, event):
1412 head, tail, chars, lines = self.get_region()
1413 for pos in range(len(lines)):
1414 line = lines[pos]
1415 if line:
1416 raw, effective = classifyws(line, self.tabwidth)
1417 effective = effective + self.indentwidth
1418 lines[pos] = self._make_blanks(effective) + line[raw:]
1419 self.set_region(head, tail, chars, lines)
1420 return "break"
1421
1422 def dedent_region_event(self, event):
1423 head, tail, chars, lines = self.get_region()
1424 for pos in range(len(lines)):
1425 line = lines[pos]
1426 if line:
1427 raw, effective = classifyws(line, self.tabwidth)
1428 effective = max(effective - self.indentwidth, 0)
1429 lines[pos] = self._make_blanks(effective) + line[raw:]
1430 self.set_region(head, tail, chars, lines)
1431 return "break"
1432
1433 def comment_region_event(self, event):
1434 head, tail, chars, lines = self.get_region()
1435 for pos in range(len(lines) - 1):
1436 line = lines[pos]
1437 lines[pos] = '##' + line
1438 self.set_region(head, tail, chars, lines)
1439
1440 def uncomment_region_event(self, event):
1441 head, tail, chars, lines = self.get_region()
1442 for pos in range(len(lines)):
1443 line = lines[pos]
1444 if not line:
1445 continue
1446 if line[:2] == '##':
1447 line = line[2:]
1448 elif line[:1] == '#':
1449 line = line[1:]
1450 lines[pos] = line
1451 self.set_region(head, tail, chars, lines)
1452
1453 def tabify_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)):
1458 line = lines[pos]
1459 if line:
1460 raw, effective = classifyws(line, tabwidth)
1461 ntabs, nspaces = divmod(effective, tabwidth)
1462 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1463 self.set_region(head, tail, chars, lines)
1464
1465 def untabify_region_event(self, event):
1466 head, tail, chars, lines = self.get_region()
1467 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001468 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001469 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001470 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001471 self.set_region(head, tail, chars, lines)
1472
1473 def toggle_tabs_event(self, event):
1474 if self.askyesno(
1475 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001476 "Turn tabs " + ("on", "off")[self.usetabs] +
1477 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001478 ("will be", "remains at")[self.usetabs] + " 8." +
1479 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001480 parent=self.text):
1481 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001482 # Try to prevent inconsistent indentation.
1483 # User must change indent width manually after using tabs.
1484 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001485 return "break"
1486
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001487 # XXX this isn't bound to anything -- see tabwidth comments
1488## def change_tabwidth_event(self, event):
1489## new = self._asktabwidth()
1490## if new != self.tabwidth:
1491## self.tabwidth = new
1492## self.set_indentation_params(0, guess=0)
1493## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001494
1495 def change_indentwidth_event(self, event):
1496 new = self.askinteger(
1497 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001498 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001499 parent=self.text,
1500 initialvalue=self.indentwidth,
1501 minvalue=2,
1502 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001503 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001504 self.indentwidth = new
1505 return "break"
1506
1507 def get_region(self):
1508 text = self.text
1509 first, last = self.get_selection_indices()
1510 if first and last:
1511 head = text.index(first + " linestart")
1512 tail = text.index(last + "-1c lineend +1c")
1513 else:
1514 head = text.index("insert linestart")
1515 tail = text.index("insert lineend +1c")
1516 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001517 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001518 return head, tail, chars, lines
1519
1520 def set_region(self, head, tail, chars, lines):
1521 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001522 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001523 if newchars == chars:
1524 text.bell()
1525 return
1526 text.tag_remove("sel", "1.0", "end")
1527 text.mark_set("insert", head)
1528 text.undo_block_start()
1529 text.delete(head, tail)
1530 text.insert(head, newchars)
1531 text.undo_block_stop()
1532 text.tag_add("sel", head, "insert")
1533
1534 # Make string that displays as n leading blanks.
1535
1536 def _make_blanks(self, n):
1537 if self.usetabs:
1538 ntabs, nspaces = divmod(n, self.tabwidth)
1539 return '\t' * ntabs + ' ' * nspaces
1540 else:
1541 return ' ' * n
1542
1543 # Delete from beginning of line to insert point, then reinsert
1544 # column logical (meaning use tabs if appropriate) spaces.
1545
1546 def reindent_to(self, column):
1547 text = self.text
1548 text.undo_block_start()
1549 if text.compare("insert linestart", "!=", "insert"):
1550 text.delete("insert linestart", "insert")
1551 if column:
1552 text.insert("insert", self._make_blanks(column))
1553 text.undo_block_stop()
1554
1555 def _asktabwidth(self):
1556 return self.askinteger(
1557 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001558 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001559 parent=self.text,
1560 initialvalue=self.indentwidth,
1561 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001562 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001563
1564 # Guess indentwidth from text content.
1565 # Return guessed indentwidth. This should not be believed unless
1566 # it's in a reasonable range (e.g., it will be 0 if no indented
1567 # blocks are found).
1568
1569 def guess_indent(self):
1570 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1571 if opener and indented:
1572 raw, indentsmall = classifyws(opener, self.tabwidth)
1573 raw, indentlarge = classifyws(indented, self.tabwidth)
1574 else:
1575 indentsmall = indentlarge = 0
1576 return indentlarge - indentsmall
1577
1578# "line.col" -> line, as an int
1579def index2line(index):
1580 return int(float(index))
1581
1582# Look at the leading whitespace in s.
1583# Return pair (# of leading ws characters,
1584# effective # of leading blanks after expanding
1585# tabs to width tabwidth)
1586
1587def classifyws(s, tabwidth):
1588 raw = effective = 0
1589 for ch in s:
1590 if ch == ' ':
1591 raw = raw + 1
1592 effective = effective + 1
1593 elif ch == '\t':
1594 raw = raw + 1
1595 effective = (effective // tabwidth + 1) * tabwidth
1596 else:
1597 break
1598 return raw, effective
1599
1600import tokenize
1601_tokenize = tokenize
1602del tokenize
1603
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001604class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001605
1606 # .run() chews over the Text widget, looking for a block opener
1607 # and the stmt following it. Returns a pair,
1608 # (line containing block opener, line containing stmt)
1609 # Either or both may be None.
1610
1611 def __init__(self, text, tabwidth):
1612 self.text = text
1613 self.tabwidth = tabwidth
1614 self.i = self.finished = 0
1615 self.blkopenline = self.indentedline = None
1616
1617 def readline(self):
1618 if self.finished:
1619 return ""
1620 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001621 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001622 if self.text.compare(mark, ">=", "end"):
1623 return ""
1624 return self.text.get(mark, mark + " lineend+1c")
1625
1626 def tokeneater(self, type, token, start, end, line,
1627 INDENT=_tokenize.INDENT,
1628 NAME=_tokenize.NAME,
1629 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1630 if self.finished:
1631 pass
1632 elif type == NAME and token in OPENERS:
1633 self.blkopenline = line
1634 elif type == INDENT and self.blkopenline:
1635 self.indentedline = line
1636 self.finished = 1
1637
1638 def run(self):
1639 save_tabsize = _tokenize.tabsize
1640 _tokenize.tabsize = self.tabwidth
1641 try:
1642 try:
Trent Nelson428de652008-03-18 22:41:35 +00001643 tokens = _tokenize.generate_tokens(self.readline)
1644 for token in tokens:
1645 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001646 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001647 # since we cut off the tokenizer early, we can trigger
1648 # spurious errors
1649 pass
1650 finally:
1651 _tokenize.tabsize = save_tabsize
1652 return self.blkopenline, self.indentedline
1653
1654### end autoindent code ###
1655
David Scherer7aced172000-08-15 01:13:23 +00001656def prepstr(s):
1657 # Helper to extract the underscore from a string, e.g.
1658 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001659 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001660 if i >= 0:
1661 s = s[:i] + s[i+1:]
1662 return i, s
1663
1664
1665keynames = {
1666 'bracketleft': '[',
1667 'bracketright': ']',
1668 'slash': '/',
1669}
1670
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001671def get_accelerator(keydefs, eventname):
1672 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001673 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1674 # if not keylist:
1675 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1676 "<<open-module>>",
1677 "<<goto-line>>",
1678 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001679 return ""
1680 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001681 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001682 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1683 s = re.sub("Key-", "", s)
1684 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1685 s = re.sub("Control-", "Ctrl-", s)
1686 s = re.sub("-", "+", s)
1687 s = re.sub("><", " ", s)
1688 s = re.sub("<", "", s)
1689 s = re.sub(">", "", s)
1690 return s
1691
1692
1693def fixwordbreaks(root):
1694 # Make sure that Tk's double-click and next/previous word
1695 # operations use our definition of a word (i.e. an identifier)
1696 tk = root.tk
1697 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1698 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1699 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1700
1701
1702def test():
1703 root = Tk()
1704 fixwordbreaks(root)
1705 root.withdraw()
1706 if sys.argv[1:]:
1707 filename = sys.argv[1]
1708 else:
1709 filename = None
1710 edit = EditorWindow(root=root, filename=filename)
1711 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001712 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001713 root.mainloop()
1714 root.destroy()
1715
1716if __name__ == '__main__':
1717 test()