blob: 06fb137d932e915af466b27719360654080809c4 [file] [log] [blame]
Brett Cannonaef82d32012-04-14 20:44:23 -04001import importlib
Brett Cannon50793b42013-06-07 13:17:48 -04002import importlib.abc
Eric Snow6029e082014-01-25 15:32:46 -07003import importlib.util
David Scherer7aced172000-08-15 01:13:23 +00004import os
Terry Jan Reedy94338de2014-01-23 00:36:46 -05005from platform import python_version
David Scherer7aced172000-08-15 01:13:23 +00006import re
Guido van Rossum33d26892007-08-05 15:29:28 +00007import string
Brett Cannonaef82d32012-04-14 20:44:23 -04008import sys
Georg Brandl14fc4272008-05-17 18:39:55 +00009from tkinter import *
10import tkinter.simpledialog as tkSimpleDialog
11import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +000012import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000013import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000014
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000015from idlelib.MultiCall import MultiCallCreator
16from idlelib import idlever
17from idlelib import WindowList
18from idlelib import SearchDialog
19from idlelib import GrepDialog
20from idlelib import ReplaceDialog
21from idlelib import PyParse
22from idlelib.configHandler import idleConf
23from idlelib import aboutDialog, textView, configDialog
24from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000025
26# The default tab setting for a Text widget, in average-width characters.
27TK_TABWIDTH_DEFAULT = 8
28
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000029def _sphinx_version():
30 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
31 major, minor, micro, level, serial = sys.version_info
32 release = '%s%s' % (major, minor)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020033 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000034 if level == 'candidate':
35 release += 'rc%s' % (serial,)
36 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000037 release += '%s%s' % (level[0], serial)
38 return release
39
Terry Jan Reedye91e7632012-02-05 15:14:20 -050040
41class HelpDialog(object):
42
43 def __init__(self):
44 self.parent = None # parent of help window
45 self.dlg = None # the help window iteself
46
47 def display(self, parent, near=None):
48 """ Display the help dialog.
49
50 parent - parent widget for the help window
51
52 near - a Toplevel widget (e.g. EditorWindow or PyShell)
53 to use as a reference for placing the help window
54 """
55 if self.dlg is None:
56 self.show_dialog(parent)
57 if near:
58 self.nearwindow(near)
59
60 def show_dialog(self, parent):
61 self.parent = parent
62 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
63 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
64 dlg.bind('<Destroy>', self.destroy, '+')
65
66 def nearwindow(self, near):
67 # Place the help dialog near the window specified by parent.
68 # Note - this may not reposition the window in Metacity
69 # if "/apps/metacity/general/disable_workarounds" is enabled
70 dlg = self.dlg
71 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
72 dlg.withdraw()
73 dlg.geometry("=+%d+%d" % geom)
74 dlg.deiconify()
75 dlg.lift()
76
77 def destroy(self, ev=None):
78 self.dlg = None
79 self.parent = None
80
81helpDialog = HelpDialog() # singleton instance
Terry Jan Reedy06313b72014-05-11 23:32:32 -040082def _Help_dialog(parent): # wrapper for htest
83 helpDialog.show_dialog(parent)
Terry Jan Reedye91e7632012-02-05 15:14:20 -050084
85
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000086class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000087 from idlelib.Percolator import Percolator
88 from idlelib.ColorDelegator import ColorDelegator
89 from idlelib.UndoDelegator import UndoDelegator
90 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
91 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000092 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000093 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000094
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000095 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000096
97 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000098 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +010099 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000100 if sys.platform.count('linux'):
101 # look for html docs in a couple of standard places
102 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
103 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
104 dochome = '/var/www/html/python/index.html'
105 else:
106 basepath = '/usr/share/doc/' # standard location
107 dochome = os.path.join(basepath, pyver,
108 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000109 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100110 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +0000111 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000112 if os.path.isfile(chmfile):
113 dochome = chmfile
Ned Deilyb7601672014-03-27 20:49:14 -0700114 elif sys.platform == 'darwin':
115 # documentation may be stored inside a python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100116 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000117 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000118 dochome = os.path.normpath(dochome)
119 if os.path.isfile(dochome):
120 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000121 if sys.platform == 'darwin':
122 # Safari requires real file:-URLs
123 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000124 else:
Benjamin Peterson152b6572009-01-20 15:01:54 +0000125 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000126 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +0000127 self.flist = flist
128 root = root or flist.root
129 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000130 try:
131 sys.ps1
132 except AttributeError:
133 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000134 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000135 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000136 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000137 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200138 #self.top.instance_dict makes flist.inversedict available to
139 #configDialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000140 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000141 else:
142 self.tkinter_vars = {} # keys: Tkinter event names
143 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000144 self.top.instance_dict = {}
145 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000146 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000147 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000148 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200149 self.width = idleConf.GetOption('main', 'EditorWindow',
150 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000151 text_options = {
152 'name': 'text',
153 'padx': 5,
154 'wrap': 'none',
155 'width': self.width,
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200156 'height': idleConf.GetOption('main', 'EditorWindow',
157 'height', type='int')}
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000158 if TkVersion >= 8.5:
159 # Starting with tk 8.5 we have to set the new tabstyle option
160 # to 'wordprocessor' to achieve the same display of tabs as in
161 # older tk versions.
162 text_options['tabstyle'] = 'wordprocessor'
163 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000164 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000165
166 self.createmenubar()
167 self.apply_bindings()
168
169 self.top.protocol("WM_DELETE_WINDOW", self.close)
170 self.top.bind("<<close-window>>", self.close_event)
Ned Deilyb7601672014-03-27 20:49:14 -0700171 if macosxSupport.isAquaTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000172 # Command-W on editorwindows doesn't work without this.
173 text.bind('<<close-window>>', self.close_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000174 # Some OS X systems have only one mouse button,
175 # so use control-click for pulldown menus there.
176 # (Note, AquaTk defines <2> as the right button if
177 # present and the Tk Text widget already binds <2>.)
178 text.bind("<Control-Button-1>",self.right_menu_event)
179 else:
180 # Elsewhere, use right-click for pulldown menus.
181 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000182 text.bind("<<cut>>", self.cut)
183 text.bind("<<copy>>", self.copy)
184 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000185 text.bind("<<center-insert>>", self.center_insert_event)
186 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000187 text.bind("<<python-docs>>", self.python_docs)
188 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000189 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000190 text.bind("<<open-module>>", self.open_module)
191 text.bind("<<do-nothing>>", lambda event: "break")
192 text.bind("<<select-all>>", self.select_all)
193 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000194 text.bind("<<find>>", self.find_event)
195 text.bind("<<find-again>>", self.find_again_event)
196 text.bind("<<find-in-files>>", self.find_in_files_event)
197 text.bind("<<find-selection>>", self.find_selection_event)
198 text.bind("<<replace>>", self.replace_event)
199 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000200 text.bind("<<smart-backspace>>",self.smart_backspace_event)
201 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
202 text.bind("<<smart-indent>>",self.smart_indent_event)
203 text.bind("<<indent-region>>",self.indent_region_event)
204 text.bind("<<dedent-region>>",self.dedent_region_event)
205 text.bind("<<comment-region>>",self.comment_region_event)
206 text.bind("<<uncomment-region>>",self.uncomment_region_event)
207 text.bind("<<tabify-region>>",self.tabify_region_event)
208 text.bind("<<untabify-region>>",self.untabify_region_event)
209 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
210 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000211 text.bind("<Left>", self.move_at_edge_if_selection(0))
212 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000213 text.bind("<<del-word-left>>", self.del_word_left)
214 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000215 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000216
David Scherer7aced172000-08-15 01:13:23 +0000217 if flist:
218 flist.inversedict[self] = key
219 if key:
220 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000221 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000222 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
223 text.bind("<<open-class-browser>>", self.open_class_browser)
224 text.bind("<<open-path-browser>>", self.open_path_browser)
225
Steven M. Gava898a3652001-10-07 11:10:44 +0000226 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000227 vbar['command'] = text.yview
228 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000229 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000230 fontWeight = 'normal'
231 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000232 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000233 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200234 idleConf.GetOption('main', 'EditorWindow',
235 'font-size', type='int'),
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000236 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000237 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
238 text.pack(side=TOP, fill=BOTH, expand=1)
239 text.focus_set()
240
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000241 # usetabs true -> literal tab characters are used by indent and
242 # dedent cmds, possibly mixed with spaces if
243 # indentwidth is not a multiple of tabwidth,
244 # which will cause Tabnanny to nag!
245 # false -> tab characters are converted to spaces by indent
246 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000247 # Although use-spaces=0 can be configured manually in config-main.def,
248 # configuration of tabs v. spaces is not supported in the configuration
249 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200250 usespaces = idleConf.GetOption('main', 'Indent',
251 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000252 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000253
254 # tabwidth is the display width of a literal tab character.
255 # CAUTION: telling Tk to use anything other than its default
256 # tab setting causes it to use an entirely different tabbing algorithm,
257 # treating tab stops as fixed distances from the left margin.
258 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000259 self.tabwidth = 8 # must remain 8 until Tk is fixed.
260
261 # indentwidth is the number of screen characters per indent level.
262 # The recommended Python indentation is four spaces.
263 self.indentwidth = self.tabwidth
264 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000265
266 # If context_use_ps1 is true, parsing searches back for a ps1 line;
267 # else searches for a popular (if, def, ...) Python stmt.
268 self.context_use_ps1 = False
269
270 # When searching backwards for a reliable place to begin parsing,
271 # first start num_context_lines[0] lines back, then
272 # num_context_lines[1] lines back if that didn't work, and so on.
273 # The last value should be huge (larger than the # of lines in a
274 # conceivable file).
275 # Making the initial values larger slows things down more often.
276 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000277 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000278 self.undo = undo = self.UndoDelegator()
279 per.insertfilter(undo)
280 text.undo_block_start = undo.undo_block_start
281 text.undo_block_stop = undo.undo_block_stop
282 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000283 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000284 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000285 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000286 self.good_load = False
287 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000288 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000289 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000290 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000291 if io.loadfile(filename):
292 self.good_load = True
293 is_py_src = self.ispythonsource(filename)
294 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000295 else:
296 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500297 self.good_load = True
298
Christian Heimesa156e092008-02-16 07:38:31 +0000299 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000300 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000301 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000302 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000303 menu = self.menudict.get('windows')
304 if menu:
305 end = menu.index("end")
306 if end is None:
307 end = -1
308 if end >= 0:
309 menu.add_separator()
310 end = end + 1
311 self.wmenu_end = end
312 WindowList.register_callback(self.postwindowsmenu)
313
314 # Some abstractions so IDLE extensions are cross-IDE
315 self.askyesno = tkMessageBox.askyesno
316 self.askinteger = tkSimpleDialog.askinteger
317 self.showerror = tkMessageBox.showerror
318
Roger Serwycaf30242013-05-20 22:13:39 -0500319 self._highlight_workaround() # Fix selection tags on Windows
320
321 def _highlight_workaround(self):
322 # On Windows, Tk removes painting of the selection
323 # tags which is different behavior than on Linux and Mac.
324 # See issue14146 for more information.
325 if not sys.platform.startswith('win'):
326 return
327
328 text = self.text
329 text.event_add("<<Highlight-FocusOut>>", "<FocusOut>")
330 text.event_add("<<Highlight-FocusIn>>", "<FocusIn>")
331 def highlight_fix(focus):
332 sel_range = text.tag_ranges("sel")
333 if sel_range:
334 if focus == 'out':
335 HILITE_CONFIG = idleConf.GetHighlight(
336 idleConf.CurrentTheme(), 'hilite')
337 text.tag_config("sel_fix", HILITE_CONFIG)
338 text.tag_raise("sel_fix")
339 text.tag_add("sel_fix", *sel_range)
340 elif focus == 'in':
341 text.tag_remove("sel_fix", "1.0", "end")
342
343 text.bind("<<Highlight-FocusOut>>",
344 lambda ev: highlight_fix("out"))
345 text.bind("<<Highlight-FocusIn>>",
346 lambda ev: highlight_fix("in"))
347
348
Martin v. Löwis307021f2005-11-27 16:59:04 +0000349 def _filename_to_unicode(self, filename):
350 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000351 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000352 return filename
353 else:
354 try:
355 return filename.decode(self.filesystemencoding)
356 except UnicodeDecodeError:
357 # XXX
358 try:
359 return filename.decode(self.encoding)
360 except UnicodeDecodeError:
361 # byte-to-byte conversion
362 return filename.decode('iso8859-1')
363
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000364 def new_callback(self, event):
365 dirname, basename = self.io.defaultfilename()
366 self.flist.new(dirname)
367 return "break"
368
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000369 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400370 if (event.state & 4) != 0 and event.keysym == "Home":
371 # state&4==Control. If <Control-Home>, use the Tk binding.
372 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000373 if self.text.index("iomark") and \
374 self.text.compare("iomark", "<=", "insert lineend") and \
375 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400376 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000377 insertpt = int(self.text.index("iomark").split(".")[1])
378 else:
379 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000380 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000381 if line[insertpt] not in (' ','\t'):
382 break
383 else:
384 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000385 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000386 if insertpt == lineat:
387 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000388 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000389 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400390 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000391 self.text.tag_remove("sel", "1.0", "end")
392 else:
393 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200394 # there was no previous selection
395 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400396 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200397 if self.text.compare(self.text.index("sel.first"), "<",
398 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400399 self.text.mark_set("my_anchor", "sel.first") # extend back
400 else:
401 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000402 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400403 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000404 if self.text.compare(first,">",last):
405 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000406 self.text.tag_remove("sel", "1.0", "end")
407 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000408 self.text.mark_set("insert", dest)
409 self.text.see("insert")
410 return "break"
411
David Scherer7aced172000-08-15 01:13:23 +0000412 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000413 self.status_bar = self.MultiStatusBar(self.top)
Ned Deilyb7601672014-03-27 20:49:14 -0700414 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000415 # Insert some padding to avoid obscuring some of the statusbar
416 # by the resize widget.
417 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000418 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
419 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
420 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000421 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
422 self.text.event_add("<<set-line-and-column>>",
423 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000424 self.text.after_idle(self.set_line_and_column)
425
426 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000427 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000428 self.status_bar.set_label('column', 'Col: %s' % column)
429 self.status_bar.set_label('line', 'Ln: %s' % line)
430
David Scherer7aced172000-08-15 01:13:23 +0000431 menu_specs = [
432 ("file", "_File"),
433 ("edit", "_Edit"),
434 ("format", "F_ormat"),
435 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000436 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000437 ("windows", "_Windows"),
438 ("help", "_Help"),
439 ]
440
Ned Deilyb7601672014-03-27 20:49:14 -0700441 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000442 menu_specs[-2] = ("windows", "_Window")
443
444
David Scherer7aced172000-08-15 01:13:23 +0000445 def createmenubar(self):
446 mbar = self.menubar
447 self.menudict = menudict = {}
448 for name, label in self.menu_specs:
449 underline, label = prepstr(label)
450 menudict[name] = menu = Menu(mbar, name=name)
451 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ned Deilyb7601672014-03-27 20:49:14 -0700452 if macosxSupport.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000453 # Insert the application menu
454 menudict['application'] = menu = Menu(mbar, name='apple')
455 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000456 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000457 self.recent_files_menu = Menu(self.menubar)
458 self.menudict['file'].insert_cascade(3, label='Recent Files',
459 underline=0,
460 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000461 self.base_helpmenu_length = self.menudict['help'].index(END)
462 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000463
464 def postwindowsmenu(self):
465 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000466 menu = self.menudict['windows']
467 end = menu.index("end")
468 if end is None:
469 end = -1
470 if end > self.wmenu_end:
471 menu.delete(self.wmenu_end+1, end)
472 WindowList.add_windows_to_menu(menu)
473
474 rmenu = None
475
476 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000477 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
478 if not self.rmenu:
479 self.make_rmenu()
480 rmenu = self.rmenu
481 self.event = event
482 iswin = sys.platform[:3] == 'win'
483 if iswin:
484 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200485
Roger Serwy6b2918a2013-04-07 12:15:52 -0500486 for item in self.rmenu_specs:
487 try:
488 label, eventname, verify_state = item
489 except ValueError: # see issue1207589
490 continue
491
Andrew Svetlovd1837672012-11-01 22:41:19 +0200492 if verify_state is None:
493 continue
494 state = getattr(self, verify_state)()
495 rmenu.entryconfigure(label, state=state)
496
497
David Scherer7aced172000-08-15 01:13:23 +0000498 rmenu.tk_popup(event.x_root, event.y_root)
499 if iswin:
500 self.text.config(cursor="ibeam")
501
502 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200503 # ("Label", "<<virtual-event>>", "statefuncname"), ...
504 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000505 ]
506
507 def make_rmenu(self):
508 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500509 for item in self.rmenu_specs:
510 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200511 if label is not None:
512 def command(text=self.text, eventname=eventname):
513 text.event_generate(eventname)
514 rmenu.add_command(label=label, command=command)
515 else:
516 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000517 self.rmenu = rmenu
518
Andrew Svetlovd1837672012-11-01 22:41:19 +0200519 def rmenu_check_cut(self):
520 return self.rmenu_check_copy()
521
522 def rmenu_check_copy(self):
523 try:
524 indx = self.text.index('sel.first')
525 except TclError:
526 return 'disabled'
527 else:
528 return 'normal' if indx else 'disabled'
529
530 def rmenu_check_paste(self):
531 try:
532 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
533 except TclError:
534 return 'disabled'
535 else:
536 return 'normal'
537
David Scherer7aced172000-08-15 01:13:23 +0000538 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000539 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000540
Steven M. Gava3b55a892001-11-21 05:56:26 +0000541 def config_dialog(self, event=None):
542 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000543
David Scherer7aced172000-08-15 01:13:23 +0000544 def help_dialog(self, event=None):
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500545 if self.root:
546 parent = self.root
547 else:
548 parent = self.top
549 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000550
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000551 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000552 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000553 try:
554 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200555 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000556 tkMessageBox.showerror(title='Document Start Failure',
557 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000558 else:
559 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000560 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000561
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000562 def cut(self,event):
563 self.text.event_generate("<<Cut>>")
564 return "break"
565
566 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000567 if not self.text.tag_ranges("sel"):
568 # There is no selection, so do nothing and maybe interrupt.
569 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000570 self.text.event_generate("<<Copy>>")
571 return "break"
572
573 def paste(self,event):
574 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000575 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000576 return "break"
577
David Scherer7aced172000-08-15 01:13:23 +0000578 def select_all(self, event=None):
579 self.text.tag_add("sel", "1.0", "end-1c")
580 self.text.mark_set("insert", "1.0")
581 self.text.see("insert")
582 return "break"
583
584 def remove_selection(self, event=None):
585 self.text.tag_remove("sel", "1.0", "end")
586 self.text.see("insert")
587
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000588 def move_at_edge_if_selection(self, edge_index):
589 """Cursor move begins at start or end of selection
590
591 When a left/right cursor key is pressed create and return to Tkinter a
592 function which causes a cursor move from the associated edge of the
593 selection.
594
595 """
596 self_text_index = self.text.index
597 self_text_mark_set = self.text.mark_set
598 edges_table = ("sel.first+1c", "sel.last-1c")
599 def move_at_edge(event):
600 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
601 try:
602 self_text_index("sel.first")
603 self_text_mark_set("insert", edges_table[edge_index])
604 except TclError:
605 pass
606 return move_at_edge
607
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000608 def del_word_left(self, event):
609 self.text.event_generate('<Meta-Delete>')
610 return "break"
611
612 def del_word_right(self, event):
613 self.text.event_generate('<Meta-d>')
614 return "break"
615
Steven M. Gavac5976402002-01-04 03:06:08 +0000616 def find_event(self, event):
617 SearchDialog.find(self.text)
618 return "break"
619
620 def find_again_event(self, event):
621 SearchDialog.find_again(self.text)
622 return "break"
623
624 def find_selection_event(self, event):
625 SearchDialog.find_selection(self.text)
626 return "break"
627
628 def find_in_files_event(self, event):
629 GrepDialog.grep(self.text, self.io, self.flist)
630 return "break"
631
632 def replace_event(self, event):
633 ReplaceDialog.replace(self.text)
634 return "break"
635
636 def goto_line_event(self, event):
637 text = self.text
638 lineno = tkSimpleDialog.askinteger("Goto",
639 "Go to line number:",parent=text)
640 if lineno is None:
641 return "break"
642 if lineno <= 0:
643 text.bell()
644 return "break"
645 text.mark_set("insert", "%d.0" % lineno)
646 text.see("insert")
647
David Scherer7aced172000-08-15 01:13:23 +0000648 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000649 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000650 try:
651 name = self.text.get("sel.first", "sel.last")
652 except TclError:
653 name = ""
654 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000655 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000656 name = tkSimpleDialog.askstring("Module",
657 "Enter the name of a Python module\n"
658 "to search on sys.path and open:",
659 parent=self.text, initialvalue=name)
660 if name:
661 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000662 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000663 return
David Scherer7aced172000-08-15 01:13:23 +0000664 # XXX Ought to insert current file's directory in front of path
665 try:
Eric Snow6029e082014-01-25 15:32:46 -0700666 spec = importlib.util.find_spec(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400667 except (ValueError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000668 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
669 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700670 if spec is None:
Brett Cannon50793b42013-06-07 13:17:48 -0400671 tkMessageBox.showerror("Import error", "module not found",
672 parent=self.text)
David Scherer7aced172000-08-15 01:13:23 +0000673 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700674 if not isinstance(spec.loader, importlib.abc.SourceLoader):
Brett Cannon50793b42013-06-07 13:17:48 -0400675 tkMessageBox.showerror("Import error", "not a source-based module",
676 parent=self.text)
677 return
678 try:
Eric Snow02b9f9d2014-01-06 20:42:59 -0700679 file_path = spec.loader.get_filename(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400680 except AttributeError:
681 tkMessageBox.showerror("Import error",
682 "loader does not support get_filename",
683 parent=self.text)
684 return
David Scherer7aced172000-08-15 01:13:23 +0000685 if self.flist:
Brett Cannon50793b42013-06-07 13:17:48 -0400686 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000687 else:
Brett Cannon50793b42013-06-07 13:17:48 -0400688 self.io.loadfile(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000689
690 def open_class_browser(self, event=None):
691 filename = self.io.filename
692 if not filename:
693 tkMessageBox.showerror(
694 "No filename",
695 "This buffer has no associated filename",
696 master=self.text)
697 self.text.focus_set()
698 return None
699 head, tail = os.path.split(filename)
700 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000701 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000702 ClassBrowser.ClassBrowser(self.flist, base, [head])
703
704 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000705 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000706 PathBrowser.PathBrowser(self.flist)
707
708 def gotoline(self, lineno):
709 if lineno is not None and lineno > 0:
710 self.text.mark_set("insert", "%d.0" % lineno)
711 self.text.tag_remove("sel", "1.0", "end")
712 self.text.tag_add("sel", "insert", "insert +1l")
713 self.center()
714
715 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000716 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000717 return True
David Scherer7aced172000-08-15 01:13:23 +0000718 base, ext = os.path.splitext(os.path.basename(filename))
719 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000720 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000721 line = self.text.get('1.0', '1.0 lineend')
722 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000723
724 def close_hook(self):
725 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000726 self.flist.unregister_maybe_terminate(self)
727 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000728
729 def set_close_hook(self, close_hook):
730 self.close_hook = close_hook
731
732 def filename_change_hook(self):
733 if self.flist:
734 self.flist.filename_changed_edit(self)
735 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000736 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000737 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000738
Christian Heimesa156e092008-02-16 07:38:31 +0000739 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000740 if self.color:
741 return
Christian Heimesa156e092008-02-16 07:38:31 +0000742 if self.ispythonsource(self.io.filename):
743 self.color = self.ColorDelegator()
744 # can add more colorizers here...
745 if self.color:
746 self.per.removefilter(self.undo)
747 self.per.insertfilter(self.color)
748 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000749
Christian Heimesa156e092008-02-16 07:38:31 +0000750 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000751 if not self.color:
752 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000753 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000754 self.per.removefilter(self.color)
755 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000756
Steven M. Gavab77d3432002-03-02 07:16:21 +0000757 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000758 "Update the colour theme"
759 # Called from self.filename_change_hook and from configDialog.py
760 self._rmcolorizer()
761 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000762 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000763 normal_colors = idleConf.GetHighlight(theme, 'normal')
764 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
765 select_colors = idleConf.GetHighlight(theme, 'hilite')
766 self.text.config(
767 foreground=normal_colors['foreground'],
768 background=normal_colors['background'],
769 insertbackground=cursor_color,
770 selectforeground=select_colors['foreground'],
771 selectbackground=select_colors['background'],
772 )
David Scherer7aced172000-08-15 01:13:23 +0000773
Guido van Rossum33d26892007-08-05 15:29:28 +0000774 IDENTCHARS = string.ascii_letters + string.digits + "_"
775
776 def colorize_syntax_error(self, text, pos):
777 text.tag_add("ERROR", pos)
778 char = text.get(pos)
779 if char and char in self.IDENTCHARS:
780 text.tag_add("ERROR", pos + " wordstart", pos)
781 if '\n' == text.get(pos): # error at line end
782 text.mark_set("insert", pos)
783 else:
784 text.mark_set("insert", pos + "+1c")
785 text.see(pos)
786
Steven M. Gavab1585412002-03-12 00:21:56 +0000787 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000788 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000789 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000790 fontWeight='normal'
791 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
792 fontWeight='bold'
793 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200794 idleConf.GetOption('main','EditorWindow','font-size',
795 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000796 fontWeight))
797
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000798 def RemoveKeybindings(self):
799 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000800 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000801 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000802 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000803 self.text.event_delete(event, *keylist)
804 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 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000808 self.text.event_delete(event, *keylist)
809
810 def ApplyKeybindings(self):
811 "Update the keybindings after they are changed"
812 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000813 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000814 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000815 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000816 xkeydefs = idleConf.GetExtensionBindings(extensionName)
817 if xkeydefs:
818 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000819 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000820 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000821 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000822 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000823 for item in menu[1]:
824 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000825 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000826 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000827 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700828 end = menu.index(END)
829 if end is None:
830 # Skip empty menus
831 continue
832 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000833 for index in range(0, end):
834 if menu.type(index) == 'command':
835 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000836 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000837 itemName = menu.entrycget(index, 'label')
838 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000839 if menubarItem in menuEventDict:
840 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000841 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000842 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000843 accel = get_accelerator(keydefs, event)
844 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000845
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000846 def set_notabs_indentwidth(self):
847 "Update the indentwidth if changed and not using tabs in this window"
848 # Called from configDialog.py
849 if not self.usetabs:
850 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
851 type='int')
852
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000853 def reset_help_menu_entries(self):
854 "Update the additional help entries on the Help menu"
855 help_list = idleConf.GetAllExtraHelpSourcesList()
856 helpmenu = self.menudict['help']
857 # first delete the extra help entries, if any
858 helpmenu_length = helpmenu.index(END)
859 if helpmenu_length > self.base_helpmenu_length:
860 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
861 # then rebuild them
862 if help_list:
863 helpmenu.add_separator()
864 for entry in help_list:
865 cmd = self.__extra_help_callback(entry[1])
866 helpmenu.add_command(label=entry[0], command=cmd)
867 # and update the menu dictionary
868 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000869
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000870 def __extra_help_callback(self, helpfile):
871 "Create a callback with the helpfile value frozen at definition time"
872 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000873 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000874 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000875 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000876 try:
877 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200878 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000879 tkMessageBox.showerror(title='Document Start Failure',
880 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000881 else:
882 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000883 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000884
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000885 def update_recent_files_list(self, new_file=None):
886 "Load and update the recent files list and menus"
887 rf_list = []
888 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400889 with open(self.recent_files_path, 'r',
890 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000891 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000892 if new_file:
893 new_file = os.path.abspath(new_file) + '\n'
894 if new_file in rf_list:
895 rf_list.remove(new_file) # move to top
896 rf_list.insert(0, new_file)
897 # clean and save the recent files list
898 bad_paths = []
899 for path in rf_list:
900 if '\0' in path or not os.path.exists(path[0:-1]):
901 bad_paths.append(path)
902 rf_list = [path for path in rf_list if path not in bad_paths]
903 ulchars = "1234567890ABCDEFGHIJK"
904 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000905 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800906 with open(self.recent_files_path, 'w',
907 encoding='utf_8', errors='replace') as rf_file:
908 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200909 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800910 if not getattr(self.root, "recentfilelist_error_displayed", False):
911 self.root.recentfilelist_error_displayed = True
912 tkMessageBox.showerror(title='IDLE Error',
913 message='Unable to update Recent Files list:\n%s'
914 % str(err),
915 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000916 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000917 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000918 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700919 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000920 for i, file_name in enumerate(rf_list):
921 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000922 # make unicode string to display non-ASCII chars correctly
923 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000924 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000925 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000926 command=callback,
927 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000928
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000929 def __recent_file_callback(self, file_name):
930 def open_recent_file(fn_closure=file_name):
931 self.io.open(editFile=fn_closure)
932 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000933
David Scherer7aced172000-08-15 01:13:23 +0000934 def saved_change_hook(self):
935 short = self.short_title()
936 long = self.long_title()
937 if short and long:
938 title = short + " - " + long
939 elif short:
940 title = short
941 elif long:
942 title = long
943 else:
944 title = "Untitled"
945 icon = short or long or title
946 if not self.get_saved():
947 title = "*%s*" % title
948 icon = "*%s" % icon
949 self.top.wm_title(title)
950 self.top.wm_iconname(icon)
951
952 def get_saved(self):
953 return self.undo.get_saved()
954
955 def set_saved(self, flag):
956 self.undo.set_saved(flag)
957
958 def reset_undo(self):
959 self.undo.reset_undo()
960
961 def short_title(self):
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500962 pyversion = "Python " + python_version() + ": "
David Scherer7aced172000-08-15 01:13:23 +0000963 filename = self.io.filename
964 if filename:
965 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500966 else:
967 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000968 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500969 return pyversion + self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000970
971 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000972 # return unicode string to display non-ASCII chars correctly
973 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000974
975 def center_insert_event(self, event):
976 self.center()
977
978 def center(self, mark="insert"):
979 text = self.text
980 top, bot = self.getwindowlines()
981 lineno = self.getlineno(mark)
982 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000983 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000984 text.yview(float(newtop))
985
986 def getwindowlines(self):
987 text = self.text
988 top = self.getlineno("@0,0")
989 bot = self.getlineno("@0,65535")
990 if top == bot and text.winfo_height() == 1:
991 # Geometry manager hasn't run yet
992 height = int(text['height'])
993 bot = top + height - 1
994 return top, bot
995
996 def getlineno(self, mark="insert"):
997 text = self.text
998 return int(float(text.index(mark)))
999
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001000 def get_geometry(self):
1001 "Return (width, height, x, y)"
1002 geom = self.top.wm_geometry()
1003 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +00001004 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001005
David Scherer7aced172000-08-15 01:13:23 +00001006 def close_event(self, event):
1007 self.close()
1008
1009 def maybesave(self):
1010 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001011 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001012 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001013 self.top.deiconify()
1014 self.top.lower()
1015 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001016 return self.io.maybesave()
1017
1018 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001019 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +00001020 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001021 self._close()
1022 return reply
1023
1024 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001025 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001026 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001027 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001028 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001029 self.io.close()
1030 self.io = None
1031 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001032 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001033 self.color.close(False)
1034 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001035 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001036 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001037 self.per.close()
1038 self.per = None
1039 self.top.destroy()
1040 if self.close_hook:
1041 # unless override: unregister from flist, terminate if last window
1042 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001043
1044 def load_extensions(self):
1045 self.extensions = {}
1046 self.load_standard_extensions()
1047
1048 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001049 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001050 if hasattr(ins, "close"):
1051 ins.close()
1052 self.extensions = {}
1053
1054 def load_standard_extensions(self):
1055 for name in self.get_standard_extension_names():
1056 try:
1057 self.load_extension(name)
1058 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001059 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001060 traceback.print_exc()
1061
1062 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001063 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001064
1065 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001066 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001067 try:
1068 mod = importlib.import_module('.' + name, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001069 except (ImportError, TypeError):
Brett Cannonaef82d32012-04-14 20:44:23 -04001070 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001071 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001072 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001073 raise
David Scherer7aced172000-08-15 01:13:23 +00001074 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001075 keydefs = idleConf.GetExtensionBindings(name)
1076 if hasattr(cls, "menudefs"):
1077 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001078 ins = cls(self)
1079 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001080 if keydefs:
1081 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001082 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001083 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001084 while methodname[:1] == '<':
1085 methodname = methodname[1:]
1086 while methodname[-1:] == '>':
1087 methodname = methodname[:-1]
1088 methodname = methodname + "_event"
1089 if hasattr(ins, methodname):
1090 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001091
1092 def apply_bindings(self, keydefs=None):
1093 if keydefs is None:
1094 keydefs = self.Bindings.default_keydefs
1095 text = self.text
1096 text.keydefs = keydefs
1097 for event, keylist in keydefs.items():
1098 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001099 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001100
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001101 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001102 """Add appropriate entries to the menus and submenus
1103
1104 Menus that are absent or None in self.menudict are ignored.
1105 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001106 if menudefs is None:
1107 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001108 if keydefs is None:
1109 keydefs = self.Bindings.default_keydefs
1110 menudict = self.menudict
1111 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001112 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001113 menu = menudict.get(mname)
1114 if not menu:
1115 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001116 for entry in entrylist:
1117 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001118 menu.add_separator()
1119 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001120 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001121 checkbutton = (label[:1] == '!')
1122 if checkbutton:
1123 label = label[1:]
1124 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001125 accelerator = get_accelerator(keydefs, eventname)
1126 def command(text=text, eventname=eventname):
1127 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001128 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001129 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001130 menu.add_checkbutton(label=label, underline=underline,
1131 command=command, accelerator=accelerator,
1132 variable=var)
1133 else:
1134 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001135 command=command,
1136 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001137
1138 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001139 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001140 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001141 value = var.get()
1142 return value
1143 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001144 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001145
1146 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001147 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001148 if var:
1149 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001150 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001151 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001152
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001153 def get_var_obj(self, name, vartype=None):
1154 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001155 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001156 # create a Tkinter variable object with self.text as master:
1157 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001158 return var
1159
1160 # Tk implementations of "virtual text methods" -- each platform
1161 # reusing IDLE's support code needs to define these for its GUI's
1162 # flavor of widget.
1163
1164 # Is character at text_index in a Python string? Return 0 for
1165 # "guaranteed no", true for anything else. This info is expensive
1166 # to compute ab initio, but is probably already known by the
1167 # platform's colorizer.
1168
1169 def is_char_in_string(self, text_index):
1170 if self.color:
1171 # Return true iff colorizer hasn't (re)gotten this far
1172 # yet, or the character is tagged as being in a string
1173 return self.text.tag_prevrange("TODO", text_index) or \
1174 "STRING" in self.text.tag_names(text_index)
1175 else:
1176 # The colorizer is missing: assume the worst
1177 return 1
1178
1179 # If a selection is defined in the text widget, return (start,
1180 # end) as Tkinter text indices, otherwise return (None, None)
1181 def get_selection_indices(self):
1182 try:
1183 first = self.text.index("sel.first")
1184 last = self.text.index("sel.last")
1185 return first, last
1186 except TclError:
1187 return None, None
1188
1189 # Return the text widget's current view of what a tab stop means
1190 # (equivalent width in spaces).
1191
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001192 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001193 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1194 return int(current)
1195
1196 # Set the text widget's current view of what a tab stop means.
1197
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001198 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001199 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001200 if self.get_tk_tabwidth() != newtabwidth:
1201 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001202 pixels = text.tk.call("font", "measure", text["font"],
1203 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001204 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001205 text.configure(tabs=pixels)
1206
Guido van Rossum33d26892007-08-05 15:29:28 +00001207### begin autoindent code ### (configuration was moved to beginning of class)
1208
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001209 def set_indentation_params(self, is_py_src, guess=True):
1210 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001211 i = self.guess_indent()
1212 if 2 <= i <= 8:
1213 self.indentwidth = i
1214 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001215 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001216 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001217
1218 def smart_backspace_event(self, event):
1219 text = self.text
1220 first, last = self.get_selection_indices()
1221 if first and last:
1222 text.delete(first, last)
1223 text.mark_set("insert", first)
1224 return "break"
1225 # Delete whitespace left, until hitting a real char or closest
1226 # preceding virtual tab stop.
1227 chars = text.get("insert linestart", "insert")
1228 if chars == '':
1229 if text.compare("insert", ">", "1.0"):
1230 # easy: delete preceding newline
1231 text.delete("insert-1c")
1232 else:
1233 text.bell() # at start of buffer
1234 return "break"
1235 if chars[-1] not in " \t":
1236 # easy: delete preceding real char
1237 text.delete("insert-1c")
1238 return "break"
1239 # Ick. It may require *inserting* spaces if we back up over a
1240 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001241 tabwidth = self.tabwidth
1242 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001243 assert have > 0
1244 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001245 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001246 if self.context_use_ps1:
1247 last_line_of_prompt = sys.ps1.split('\n')[-1]
1248 else:
1249 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001250 ncharsdeleted = 0
1251 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001252 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001253 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001254 chars = chars[:-1]
1255 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001256 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001257 if have <= want or chars[-1] not in " \t":
1258 break
1259 text.undo_block_start()
1260 text.delete("insert-%dc" % ncharsdeleted, "insert")
1261 if have < want:
1262 text.insert("insert", ' ' * (want - have))
1263 text.undo_block_stop()
1264 return "break"
1265
1266 def smart_indent_event(self, event):
1267 # if intraline selection:
1268 # delete it
1269 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001270 # do indent-region
1271 # else:
1272 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001273 text = self.text
1274 first, last = self.get_selection_indices()
1275 text.undo_block_start()
1276 try:
1277 if first and last:
1278 if index2line(first) != index2line(last):
1279 return self.indent_region_event(event)
1280 text.delete(first, last)
1281 text.mark_set("insert", first)
1282 prefix = text.get("insert linestart", "insert")
1283 raw, effective = classifyws(prefix, self.tabwidth)
1284 if raw == len(prefix):
1285 # only whitespace to the left
1286 self.reindent_to(effective + self.indentwidth)
1287 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001288 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001289 if self.usetabs:
1290 pad = '\t'
1291 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001292 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001293 n = self.indentwidth
1294 pad = ' ' * (n - effective % n)
1295 text.insert("insert", pad)
1296 text.see("insert")
1297 return "break"
1298 finally:
1299 text.undo_block_stop()
1300
1301 def newline_and_indent_event(self, event):
1302 text = self.text
1303 first, last = self.get_selection_indices()
1304 text.undo_block_start()
1305 try:
1306 if first and last:
1307 text.delete(first, last)
1308 text.mark_set("insert", first)
1309 line = text.get("insert linestart", "insert")
1310 i, n = 0, len(line)
1311 while i < n and line[i] in " \t":
1312 i = i+1
1313 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001314 # the cursor is in or at leading indentation in a continuation
1315 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001316 text.insert("insert linestart", '\n')
1317 return "break"
1318 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001319 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001320 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001321 last_line_of_prompt = sys.ps1.split('\n')[-1]
1322 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001323 line = line[:-1]
1324 i = i+1
1325 if i:
1326 text.delete("insert - %d chars" % i, "insert")
1327 # strip whitespace after insert point
1328 while text.get("insert") in " \t":
1329 text.delete("insert")
1330 # start new line
1331 text.insert("insert", '\n')
1332
1333 # adjust indentation for continuations and block
1334 # open/close first need to find the last stmt
1335 lno = index2line(text.index('insert'))
1336 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001337 if not self.context_use_ps1:
1338 for context in self.num_context_lines:
1339 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001340 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001341 rawtext = text.get(startatindex, "insert")
1342 y.set_str(rawtext)
1343 bod = y.find_good_parse_start(
1344 self.context_use_ps1,
1345 self._build_char_in_string_func(startatindex))
1346 if bod is not None or startat == 1:
1347 break
1348 y.set_lo(bod or 0)
1349 else:
1350 r = text.tag_prevrange("console", "insert")
1351 if r:
1352 startatindex = r[1]
1353 else:
1354 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001355 rawtext = text.get(startatindex, "insert")
1356 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001357 y.set_lo(0)
1358
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001359 c = y.get_continuation_type()
1360 if c != PyParse.C_NONE:
1361 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001362 if c == PyParse.C_STRING_FIRST_LINE:
1363 # after the first line of a string; do not indent at all
1364 pass
1365 elif c == PyParse.C_STRING_NEXT_LINES:
1366 # inside a string which started before this line;
1367 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001368 text.insert("insert", indent)
1369 elif c == PyParse.C_BRACKET:
1370 # line up with the first (if any) element of the
1371 # last open bracket structure; else indent one
1372 # level beyond the indent of the line with the
1373 # last open bracket
1374 self.reindent_to(y.compute_bracket_indent())
1375 elif c == PyParse.C_BACKSLASH:
1376 # if more than one line in this stmt already, just
1377 # mimic the current indent; else if initial line
1378 # has a start on an assignment stmt, indent to
1379 # beyond leftmost =; else to beyond first chunk of
1380 # non-whitespace on initial line
1381 if y.get_num_lines_in_stmt() > 1:
1382 text.insert("insert", indent)
1383 else:
1384 self.reindent_to(y.compute_backslash_indent())
1385 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001386 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001387 return "break"
1388
1389 # This line starts a brand new stmt; indent relative to
1390 # indentation of initial line of closest preceding
1391 # interesting stmt.
1392 indent = y.get_base_indent_string()
1393 text.insert("insert", indent)
1394 if y.is_block_opener():
1395 self.smart_indent_event(event)
1396 elif indent and y.is_block_closer():
1397 self.smart_backspace_event(event)
1398 return "break"
1399 finally:
1400 text.see("insert")
1401 text.undo_block_stop()
1402
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001403 # Our editwin provides a is_char_in_string function that works
1404 # with a Tk text index, but PyParse only knows about offsets into
1405 # a string. This builds a function for PyParse that accepts an
1406 # offset.
1407
1408 def _build_char_in_string_func(self, startindex):
1409 def inner(offset, _startindex=startindex,
1410 _icis=self.is_char_in_string):
1411 return _icis(_startindex + "+%dc" % offset)
1412 return inner
1413
1414 def indent_region_event(self, event):
1415 head, tail, chars, lines = self.get_region()
1416 for pos in range(len(lines)):
1417 line = lines[pos]
1418 if line:
1419 raw, effective = classifyws(line, self.tabwidth)
1420 effective = effective + self.indentwidth
1421 lines[pos] = self._make_blanks(effective) + line[raw:]
1422 self.set_region(head, tail, chars, lines)
1423 return "break"
1424
1425 def dedent_region_event(self, event):
1426 head, tail, chars, lines = self.get_region()
1427 for pos in range(len(lines)):
1428 line = lines[pos]
1429 if line:
1430 raw, effective = classifyws(line, self.tabwidth)
1431 effective = max(effective - self.indentwidth, 0)
1432 lines[pos] = self._make_blanks(effective) + line[raw:]
1433 self.set_region(head, tail, chars, lines)
1434 return "break"
1435
1436 def comment_region_event(self, event):
1437 head, tail, chars, lines = self.get_region()
1438 for pos in range(len(lines) - 1):
1439 line = lines[pos]
1440 lines[pos] = '##' + line
1441 self.set_region(head, tail, chars, lines)
1442
1443 def uncomment_region_event(self, event):
1444 head, tail, chars, lines = self.get_region()
1445 for pos in range(len(lines)):
1446 line = lines[pos]
1447 if not line:
1448 continue
1449 if line[:2] == '##':
1450 line = line[2:]
1451 elif line[:1] == '#':
1452 line = line[1:]
1453 lines[pos] = line
1454 self.set_region(head, tail, chars, lines)
1455
1456 def tabify_region_event(self, event):
1457 head, tail, chars, lines = self.get_region()
1458 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001459 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001460 for pos in range(len(lines)):
1461 line = lines[pos]
1462 if line:
1463 raw, effective = classifyws(line, tabwidth)
1464 ntabs, nspaces = divmod(effective, tabwidth)
1465 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1466 self.set_region(head, tail, chars, lines)
1467
1468 def untabify_region_event(self, event):
1469 head, tail, chars, lines = self.get_region()
1470 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001471 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001472 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001473 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001474 self.set_region(head, tail, chars, lines)
1475
1476 def toggle_tabs_event(self, event):
1477 if self.askyesno(
1478 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001479 "Turn tabs " + ("on", "off")[self.usetabs] +
1480 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001481 ("will be", "remains at")[self.usetabs] + " 8." +
1482 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001483 parent=self.text):
1484 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001485 # Try to prevent inconsistent indentation.
1486 # User must change indent width manually after using tabs.
1487 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001488 return "break"
1489
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001490 # XXX this isn't bound to anything -- see tabwidth comments
1491## def change_tabwidth_event(self, event):
1492## new = self._asktabwidth()
1493## if new != self.tabwidth:
1494## self.tabwidth = new
1495## self.set_indentation_params(0, guess=0)
1496## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001497
1498 def change_indentwidth_event(self, event):
1499 new = self.askinteger(
1500 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001501 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001502 parent=self.text,
1503 initialvalue=self.indentwidth,
1504 minvalue=2,
1505 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001506 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001507 self.indentwidth = new
1508 return "break"
1509
1510 def get_region(self):
1511 text = self.text
1512 first, last = self.get_selection_indices()
1513 if first and last:
1514 head = text.index(first + " linestart")
1515 tail = text.index(last + "-1c lineend +1c")
1516 else:
1517 head = text.index("insert linestart")
1518 tail = text.index("insert lineend +1c")
1519 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001520 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001521 return head, tail, chars, lines
1522
1523 def set_region(self, head, tail, chars, lines):
1524 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001525 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001526 if newchars == chars:
1527 text.bell()
1528 return
1529 text.tag_remove("sel", "1.0", "end")
1530 text.mark_set("insert", head)
1531 text.undo_block_start()
1532 text.delete(head, tail)
1533 text.insert(head, newchars)
1534 text.undo_block_stop()
1535 text.tag_add("sel", head, "insert")
1536
1537 # Make string that displays as n leading blanks.
1538
1539 def _make_blanks(self, n):
1540 if self.usetabs:
1541 ntabs, nspaces = divmod(n, self.tabwidth)
1542 return '\t' * ntabs + ' ' * nspaces
1543 else:
1544 return ' ' * n
1545
1546 # Delete from beginning of line to insert point, then reinsert
1547 # column logical (meaning use tabs if appropriate) spaces.
1548
1549 def reindent_to(self, column):
1550 text = self.text
1551 text.undo_block_start()
1552 if text.compare("insert linestart", "!=", "insert"):
1553 text.delete("insert linestart", "insert")
1554 if column:
1555 text.insert("insert", self._make_blanks(column))
1556 text.undo_block_stop()
1557
1558 def _asktabwidth(self):
1559 return self.askinteger(
1560 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001561 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001562 parent=self.text,
1563 initialvalue=self.indentwidth,
1564 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001565 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001566
1567 # Guess indentwidth from text content.
1568 # Return guessed indentwidth. This should not be believed unless
1569 # it's in a reasonable range (e.g., it will be 0 if no indented
1570 # blocks are found).
1571
1572 def guess_indent(self):
1573 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1574 if opener and indented:
1575 raw, indentsmall = classifyws(opener, self.tabwidth)
1576 raw, indentlarge = classifyws(indented, self.tabwidth)
1577 else:
1578 indentsmall = indentlarge = 0
1579 return indentlarge - indentsmall
1580
1581# "line.col" -> line, as an int
1582def index2line(index):
1583 return int(float(index))
1584
1585# Look at the leading whitespace in s.
1586# Return pair (# of leading ws characters,
1587# effective # of leading blanks after expanding
1588# tabs to width tabwidth)
1589
1590def classifyws(s, tabwidth):
1591 raw = effective = 0
1592 for ch in s:
1593 if ch == ' ':
1594 raw = raw + 1
1595 effective = effective + 1
1596 elif ch == '\t':
1597 raw = raw + 1
1598 effective = (effective // tabwidth + 1) * tabwidth
1599 else:
1600 break
1601 return raw, effective
1602
1603import tokenize
1604_tokenize = tokenize
1605del tokenize
1606
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001607class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001608
1609 # .run() chews over the Text widget, looking for a block opener
1610 # and the stmt following it. Returns a pair,
1611 # (line containing block opener, line containing stmt)
1612 # Either or both may be None.
1613
1614 def __init__(self, text, tabwidth):
1615 self.text = text
1616 self.tabwidth = tabwidth
1617 self.i = self.finished = 0
1618 self.blkopenline = self.indentedline = None
1619
1620 def readline(self):
1621 if self.finished:
1622 return ""
1623 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001624 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001625 if self.text.compare(mark, ">=", "end"):
1626 return ""
1627 return self.text.get(mark, mark + " lineend+1c")
1628
1629 def tokeneater(self, type, token, start, end, line,
1630 INDENT=_tokenize.INDENT,
1631 NAME=_tokenize.NAME,
1632 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1633 if self.finished:
1634 pass
1635 elif type == NAME and token in OPENERS:
1636 self.blkopenline = line
1637 elif type == INDENT and self.blkopenline:
1638 self.indentedline = line
1639 self.finished = 1
1640
1641 def run(self):
1642 save_tabsize = _tokenize.tabsize
1643 _tokenize.tabsize = self.tabwidth
1644 try:
1645 try:
Trent Nelson428de652008-03-18 22:41:35 +00001646 tokens = _tokenize.generate_tokens(self.readline)
1647 for token in tokens:
1648 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001649 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001650 # since we cut off the tokenizer early, we can trigger
1651 # spurious errors
1652 pass
1653 finally:
1654 _tokenize.tabsize = save_tabsize
1655 return self.blkopenline, self.indentedline
1656
1657### end autoindent code ###
1658
David Scherer7aced172000-08-15 01:13:23 +00001659def prepstr(s):
1660 # Helper to extract the underscore from a string, e.g.
1661 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001662 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001663 if i >= 0:
1664 s = s[:i] + s[i+1:]
1665 return i, s
1666
1667
1668keynames = {
1669 'bracketleft': '[',
1670 'bracketright': ']',
1671 'slash': '/',
1672}
1673
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001674def get_accelerator(keydefs, eventname):
1675 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001676 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1677 # if not keylist:
Ned Deilyb7601672014-03-27 20:49:14 -07001678 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001679 "<<open-module>>",
1680 "<<goto-line>>",
1681 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001682 return ""
1683 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001684 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001685 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1686 s = re.sub("Key-", "", s)
1687 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1688 s = re.sub("Control-", "Ctrl-", s)
1689 s = re.sub("-", "+", s)
1690 s = re.sub("><", " ", s)
1691 s = re.sub("<", "", s)
1692 s = re.sub(">", "", s)
1693 return s
1694
1695
1696def fixwordbreaks(root):
1697 # Make sure that Tk's double-click and next/previous word
1698 # operations use our definition of a word (i.e. an identifier)
1699 tk = root.tk
1700 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1701 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1702 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1703
1704
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001705def _Editor_window(parent):
1706 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001707 fixwordbreaks(root)
1708 root.withdraw()
1709 if sys.argv[1:]:
1710 filename = sys.argv[1]
1711 else:
1712 filename = None
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001713 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001714 edit = EditorWindow(root=root, filename=filename)
1715 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001716 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001717
1718if __name__ == '__main__':
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001719 from idlelib.idle_test.htest import run
1720 if len(sys.argv) <= 1:
1721 run(_Help_dialog)
1722 run(_Editor_window)