blob: 6dbbe09823ff99b12dfbad44fb4fd1906934b98b [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 Reedy0726ddf2014-08-14 21:54:43 -04005import platform
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
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000016from 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
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -040028_py_version = ' (%s)' % platform.python_version()
29
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000030def _sphinx_version():
31 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
32 major, minor, micro, level, serial = sys.version_info
33 release = '%s%s' % (major, minor)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020034 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000035 if level == 'candidate':
36 release += 'rc%s' % (serial,)
37 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000038 release += '%s%s' % (level[0], serial)
39 return release
40
Terry Jan Reedye91e7632012-02-05 15:14:20 -050041
42class HelpDialog(object):
43
44 def __init__(self):
45 self.parent = None # parent of help window
46 self.dlg = None # the help window iteself
47
48 def display(self, parent, near=None):
49 """ Display the help dialog.
50
51 parent - parent widget for the help window
52
53 near - a Toplevel widget (e.g. EditorWindow or PyShell)
54 to use as a reference for placing the help window
55 """
56 if self.dlg is None:
57 self.show_dialog(parent)
58 if near:
59 self.nearwindow(near)
60
61 def show_dialog(self, parent):
62 self.parent = parent
63 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
64 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
65 dlg.bind('<Destroy>', self.destroy, '+')
66
67 def nearwindow(self, near):
68 # Place the help dialog near the window specified by parent.
69 # Note - this may not reposition the window in Metacity
70 # if "/apps/metacity/general/disable_workarounds" is enabled
71 dlg = self.dlg
72 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
73 dlg.withdraw()
74 dlg.geometry("=+%d+%d" % geom)
75 dlg.deiconify()
76 dlg.lift()
77
78 def destroy(self, ev=None):
79 self.dlg = None
80 self.parent = None
81
82helpDialog = HelpDialog() # singleton instance
Terry Jan Reedyab4fd442014-05-19 00:12:10 -040083def _help_dialog(parent): # wrapper for htest
Terry Jan Reedy06313b72014-05-11 23:32:32 -040084 helpDialog.show_dialog(parent)
Terry Jan Reedye91e7632012-02-05 15:14:20 -050085
86
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000087class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000088 from idlelib.Percolator import Percolator
89 from idlelib.ColorDelegator import ColorDelegator
90 from idlelib.UndoDelegator import UndoDelegator
91 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
92 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000093 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000094 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000095
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000096 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000097
98 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000099 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100100 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000101 if sys.platform.count('linux'):
102 # look for html docs in a couple of standard places
103 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
104 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
105 dochome = '/var/www/html/python/index.html'
106 else:
107 basepath = '/usr/share/doc/' # standard location
108 dochome = os.path.join(basepath, pyver,
109 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000110 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100111 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +0000112 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000113 if os.path.isfile(chmfile):
114 dochome = chmfile
Ned Deilyb7601672014-03-27 20:49:14 -0700115 elif sys.platform == 'darwin':
116 # documentation may be stored inside a python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100117 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000118 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000119 dochome = os.path.normpath(dochome)
120 if os.path.isfile(dochome):
121 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000122 if sys.platform == 'darwin':
123 # Safari requires real file:-URLs
124 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000125 else:
Terry Jan Reedyb6e17782014-09-19 22:54:15 -0400126 EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
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)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400190 text.bind("<<open-config-extensions-dialog>>",
191 self.config_extensions_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000192 text.bind("<<open-module>>", self.open_module)
193 text.bind("<<do-nothing>>", lambda event: "break")
194 text.bind("<<select-all>>", self.select_all)
195 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000196 text.bind("<<find>>", self.find_event)
197 text.bind("<<find-again>>", self.find_again_event)
198 text.bind("<<find-in-files>>", self.find_in_files_event)
199 text.bind("<<find-selection>>", self.find_selection_event)
200 text.bind("<<replace>>", self.replace_event)
201 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000202 text.bind("<<smart-backspace>>",self.smart_backspace_event)
203 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
204 text.bind("<<smart-indent>>",self.smart_indent_event)
205 text.bind("<<indent-region>>",self.indent_region_event)
206 text.bind("<<dedent-region>>",self.dedent_region_event)
207 text.bind("<<comment-region>>",self.comment_region_event)
208 text.bind("<<uncomment-region>>",self.uncomment_region_event)
209 text.bind("<<tabify-region>>",self.tabify_region_event)
210 text.bind("<<untabify-region>>",self.untabify_region_event)
211 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
212 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000213 text.bind("<Left>", self.move_at_edge_if_selection(0))
214 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000215 text.bind("<<del-word-left>>", self.del_word_left)
216 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000217 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000218
David Scherer7aced172000-08-15 01:13:23 +0000219 if flist:
220 flist.inversedict[self] = key
221 if key:
222 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000223 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000224 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
225 text.bind("<<open-class-browser>>", self.open_class_browser)
226 text.bind("<<open-path-browser>>", self.open_path_browser)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400227 text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
David Scherer7aced172000-08-15 01:13:23 +0000228
Steven M. Gava898a3652001-10-07 11:10:44 +0000229 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000230 vbar['command'] = text.yview
231 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000232 text['yscrollcommand'] = vbar.set
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400233 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
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)
Ned Deilyb7601672014-03-27 20:49:14 -0700411 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000412 # 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"),
Ned Deilyccb416f2015-01-17 21:06:27 -0800434 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000435 ("help", "_Help"),
436 ]
437
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000438
David Scherer7aced172000-08-15 01:13:23 +0000439 def createmenubar(self):
440 mbar = self.menubar
441 self.menudict = menudict = {}
442 for name, label in self.menu_specs:
443 underline, label = prepstr(label)
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400444 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000445 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ned Deilyb7601672014-03-27 20:49:14 -0700446 if macosxSupport.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000447 # Insert the application menu
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400448 menudict['application'] = menu = Menu(mbar, name='apple',
449 tearoff=0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000450 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000451 self.fill_menus()
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400452 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000453 self.menudict['file'].insert_cascade(3, label='Recent Files',
454 underline=0,
455 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000456 self.base_helpmenu_length = self.menudict['help'].index(END)
457 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000458
459 def postwindowsmenu(self):
460 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000461 menu = self.menudict['windows']
462 end = menu.index("end")
463 if end is None:
464 end = -1
465 if end > self.wmenu_end:
466 menu.delete(self.wmenu_end+1, end)
467 WindowList.add_windows_to_menu(menu)
468
469 rmenu = None
470
471 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000472 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
473 if not self.rmenu:
474 self.make_rmenu()
475 rmenu = self.rmenu
476 self.event = event
477 iswin = sys.platform[:3] == 'win'
478 if iswin:
479 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200480
Roger Serwy6b2918a2013-04-07 12:15:52 -0500481 for item in self.rmenu_specs:
482 try:
483 label, eventname, verify_state = item
484 except ValueError: # see issue1207589
485 continue
486
Andrew Svetlovd1837672012-11-01 22:41:19 +0200487 if verify_state is None:
488 continue
489 state = getattr(self, verify_state)()
490 rmenu.entryconfigure(label, state=state)
491
492
David Scherer7aced172000-08-15 01:13:23 +0000493 rmenu.tk_popup(event.x_root, event.y_root)
494 if iswin:
495 self.text.config(cursor="ibeam")
496
497 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200498 # ("Label", "<<virtual-event>>", "statefuncname"), ...
499 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000500 ]
501
502 def make_rmenu(self):
503 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500504 for item in self.rmenu_specs:
505 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200506 if label is not None:
507 def command(text=self.text, eventname=eventname):
508 text.event_generate(eventname)
509 rmenu.add_command(label=label, command=command)
510 else:
511 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000512 self.rmenu = rmenu
513
Andrew Svetlovd1837672012-11-01 22:41:19 +0200514 def rmenu_check_cut(self):
515 return self.rmenu_check_copy()
516
517 def rmenu_check_copy(self):
518 try:
519 indx = self.text.index('sel.first')
520 except TclError:
521 return 'disabled'
522 else:
523 return 'normal' if indx else 'disabled'
524
525 def rmenu_check_paste(self):
526 try:
527 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
528 except TclError:
529 return 'disabled'
530 else:
531 return 'normal'
532
David Scherer7aced172000-08-15 01:13:23 +0000533 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000534 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000535
Steven M. Gava3b55a892001-11-21 05:56:26 +0000536 def config_dialog(self, event=None):
537 configDialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400538 def config_extensions_dialog(self, event=None):
539 configDialog.ConfigExtensionsDialog(self.top)
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)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200552 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000553 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:
Eric Snow6029e082014-01-25 15:32:46 -0700663 spec = importlib.util.find_spec(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400664 except (ValueError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000665 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
666 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700667 if spec is None:
Brett Cannon50793b42013-06-07 13:17:48 -0400668 tkMessageBox.showerror("Import error", "module not found",
669 parent=self.text)
David Scherer7aced172000-08-15 01:13:23 +0000670 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700671 if not isinstance(spec.loader, importlib.abc.SourceLoader):
Brett Cannon50793b42013-06-07 13:17:48 -0400672 tkMessageBox.showerror("Import error", "not a source-based module",
673 parent=self.text)
674 return
675 try:
Eric Snow02b9f9d2014-01-06 20:42:59 -0700676 file_path = spec.loader.get_filename(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400677 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)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400686 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000687
688 def open_class_browser(self, event=None):
689 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400690 if not (self.__class__.__name__ == 'PyShellEditorWindow'
691 and filename):
692 filename = self.open_module()
693 if filename is None:
694 return
David Scherer7aced172000-08-15 01:13:23 +0000695 head, tail = os.path.split(filename)
696 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000697 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000698 ClassBrowser.ClassBrowser(self.flist, base, [head])
699
700 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000701 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000702 PathBrowser.PathBrowser(self.flist)
703
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400704 def open_turtle_demo(self, event = None):
705 import subprocess
706
707 cmd = [sys.executable,
708 '-c',
709 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400710 subprocess.Popen(cmd, shell=False)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400711
David Scherer7aced172000-08-15 01:13:23 +0000712 def gotoline(self, lineno):
713 if lineno is not None and lineno > 0:
714 self.text.mark_set("insert", "%d.0" % lineno)
715 self.text.tag_remove("sel", "1.0", "end")
716 self.text.tag_add("sel", "insert", "insert +1l")
717 self.center()
718
719 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000720 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000721 return True
David Scherer7aced172000-08-15 01:13:23 +0000722 base, ext = os.path.splitext(os.path.basename(filename))
723 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000724 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000725 line = self.text.get('1.0', '1.0 lineend')
726 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000727
728 def close_hook(self):
729 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000730 self.flist.unregister_maybe_terminate(self)
731 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000732
733 def set_close_hook(self, close_hook):
734 self.close_hook = close_hook
735
736 def filename_change_hook(self):
737 if self.flist:
738 self.flist.filename_changed_edit(self)
739 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000740 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000741 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000742
Christian Heimesa156e092008-02-16 07:38:31 +0000743 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000744 if self.color:
745 return
Christian Heimesa156e092008-02-16 07:38:31 +0000746 if self.ispythonsource(self.io.filename):
747 self.color = self.ColorDelegator()
748 # can add more colorizers here...
749 if self.color:
750 self.per.removefilter(self.undo)
751 self.per.insertfilter(self.color)
752 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000753
Christian Heimesa156e092008-02-16 07:38:31 +0000754 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000755 if not self.color:
756 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000757 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000758 self.per.removefilter(self.color)
759 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000760
Steven M. Gavab77d3432002-03-02 07:16:21 +0000761 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400762 "Update the color theme"
Christian Heimesa156e092008-02-16 07:38:31 +0000763 # Called from self.filename_change_hook and from configDialog.py
764 self._rmcolorizer()
765 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000766 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000767 normal_colors = idleConf.GetHighlight(theme, 'normal')
768 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
769 select_colors = idleConf.GetHighlight(theme, 'hilite')
770 self.text.config(
771 foreground=normal_colors['foreground'],
772 background=normal_colors['background'],
773 insertbackground=cursor_color,
774 selectforeground=select_colors['foreground'],
775 selectbackground=select_colors['background'],
776 )
David Scherer7aced172000-08-15 01:13:23 +0000777
Guido van Rossum33d26892007-08-05 15:29:28 +0000778 IDENTCHARS = string.ascii_letters + string.digits + "_"
779
780 def colorize_syntax_error(self, text, pos):
781 text.tag_add("ERROR", pos)
782 char = text.get(pos)
783 if char and char in self.IDENTCHARS:
784 text.tag_add("ERROR", pos + " wordstart", pos)
785 if '\n' == text.get(pos): # error at line end
786 text.mark_set("insert", pos)
787 else:
788 text.mark_set("insert", pos + "+1c")
789 text.see(pos)
790
Steven M. Gavab1585412002-03-12 00:21:56 +0000791 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000792 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000793 # Called from configDialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400794
795 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000796
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000797 def RemoveKeybindings(self):
798 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000799 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000800 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000801 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000802 self.text.event_delete(event, *keylist)
803 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000804 xkeydefs = idleConf.GetExtensionBindings(extensionName)
805 if xkeydefs:
806 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000807 self.text.event_delete(event, *keylist)
808
809 def ApplyKeybindings(self):
810 "Update the keybindings after they are changed"
811 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000812 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000813 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000814 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000815 xkeydefs = idleConf.GetExtensionBindings(extensionName)
816 if xkeydefs:
817 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000818 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000819 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000820 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000821 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000822 for item in menu[1]:
823 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000824 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000825 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000826 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700827 end = menu.index(END)
828 if end is None:
829 # Skip empty menus
830 continue
831 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000832 for index in range(0, end):
833 if menu.type(index) == 'command':
834 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000835 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000836 itemName = menu.entrycget(index, 'label')
837 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000838 if menubarItem in menuEventDict:
839 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000840 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000841 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000842 accel = get_accelerator(keydefs, event)
843 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000844
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000845 def set_notabs_indentwidth(self):
846 "Update the indentwidth if changed and not using tabs in this window"
847 # Called from configDialog.py
848 if not self.usetabs:
849 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
850 type='int')
851
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000852 def reset_help_menu_entries(self):
853 "Update the additional help entries on the Help menu"
854 help_list = idleConf.GetAllExtraHelpSourcesList()
855 helpmenu = self.menudict['help']
856 # first delete the extra help entries, if any
857 helpmenu_length = helpmenu.index(END)
858 if helpmenu_length > self.base_helpmenu_length:
859 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
860 # then rebuild them
861 if help_list:
862 helpmenu.add_separator()
863 for entry in help_list:
864 cmd = self.__extra_help_callback(entry[1])
865 helpmenu.add_command(label=entry[0], command=cmd)
866 # and update the menu dictionary
867 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000868
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000869 def __extra_help_callback(self, helpfile):
870 "Create a callback with the helpfile value frozen at definition time"
871 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000872 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000873 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000874 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000875 try:
876 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200877 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000878 tkMessageBox.showerror(title='Document Start Failure',
879 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000880 else:
881 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000882 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000883
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000884 def update_recent_files_list(self, new_file=None):
885 "Load and update the recent files list and menus"
886 rf_list = []
887 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400888 with open(self.recent_files_path, 'r',
889 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000890 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000891 if new_file:
892 new_file = os.path.abspath(new_file) + '\n'
893 if new_file in rf_list:
894 rf_list.remove(new_file) # move to top
895 rf_list.insert(0, new_file)
896 # clean and save the recent files list
897 bad_paths = []
898 for path in rf_list:
899 if '\0' in path or not os.path.exists(path[0:-1]):
900 bad_paths.append(path)
901 rf_list = [path for path in rf_list if path not in bad_paths]
902 ulchars = "1234567890ABCDEFGHIJK"
903 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000904 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800905 with open(self.recent_files_path, 'w',
906 encoding='utf_8', errors='replace') as rf_file:
907 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200908 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800909 if not getattr(self.root, "recentfilelist_error_displayed", False):
910 self.root.recentfilelist_error_displayed = True
911 tkMessageBox.showerror(title='IDLE Error',
912 message='Unable to update Recent Files list:\n%s'
913 % str(err),
914 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000915 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000916 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000917 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700918 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000919 for i, file_name in enumerate(rf_list):
920 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000921 # make unicode string to display non-ASCII chars correctly
922 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000923 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000924 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000925 command=callback,
926 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000927
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000928 def __recent_file_callback(self, file_name):
929 def open_recent_file(fn_closure=file_name):
930 self.io.open(editFile=fn_closure)
931 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000932
David Scherer7aced172000-08-15 01:13:23 +0000933 def saved_change_hook(self):
934 short = self.short_title()
935 long = self.long_title()
936 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400937 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000938 elif short:
939 title = short
940 elif long:
941 title = long
942 else:
943 title = "Untitled"
944 icon = short or long or title
945 if not self.get_saved():
946 title = "*%s*" % title
947 icon = "*%s" % icon
948 self.top.wm_title(title)
949 self.top.wm_iconname(icon)
950
951 def get_saved(self):
952 return self.undo.get_saved()
953
954 def set_saved(self, flag):
955 self.undo.set_saved(flag)
956
957 def reset_undo(self):
958 self.undo.reset_undo()
959
960 def short_title(self):
961 filename = self.io.filename
962 if filename:
963 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500964 else:
965 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000966 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400967 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000968
969 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000970 # return unicode string to display non-ASCII chars correctly
971 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000972
973 def center_insert_event(self, event):
974 self.center()
975
976 def center(self, mark="insert"):
977 text = self.text
978 top, bot = self.getwindowlines()
979 lineno = self.getlineno(mark)
980 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000981 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000982 text.yview(float(newtop))
983
984 def getwindowlines(self):
985 text = self.text
986 top = self.getlineno("@0,0")
987 bot = self.getlineno("@0,65535")
988 if top == bot and text.winfo_height() == 1:
989 # Geometry manager hasn't run yet
990 height = int(text['height'])
991 bot = top + height - 1
992 return top, bot
993
994 def getlineno(self, mark="insert"):
995 text = self.text
996 return int(float(text.index(mark)))
997
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000998 def get_geometry(self):
999 "Return (width, height, x, y)"
1000 geom = self.top.wm_geometry()
1001 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +00001002 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001003
David Scherer7aced172000-08-15 01:13:23 +00001004 def close_event(self, event):
1005 self.close()
1006
1007 def maybesave(self):
1008 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001009 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001010 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001011 self.top.deiconify()
1012 self.top.lower()
1013 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001014 return self.io.maybesave()
1015
1016 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001017 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +00001018 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001019 self._close()
1020 return reply
1021
1022 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001023 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001024 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001025 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001026 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001027 self.io.close()
1028 self.io = None
1029 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001030 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001031 self.color.close(False)
1032 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001033 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001034 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001035 self.per.close()
1036 self.per = None
1037 self.top.destroy()
1038 if self.close_hook:
1039 # unless override: unregister from flist, terminate if last window
1040 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001041
1042 def load_extensions(self):
1043 self.extensions = {}
1044 self.load_standard_extensions()
1045
1046 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001047 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001048 if hasattr(ins, "close"):
1049 ins.close()
1050 self.extensions = {}
1051
1052 def load_standard_extensions(self):
1053 for name in self.get_standard_extension_names():
1054 try:
1055 self.load_extension(name)
1056 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001057 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001058 traceback.print_exc()
1059
1060 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001061 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001062
1063 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001064 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001065 try:
1066 mod = importlib.import_module('.' + name, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001067 except (ImportError, TypeError):
Brett Cannonaef82d32012-04-14 20:44:23 -04001068 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001069 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001070 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001071 raise
David Scherer7aced172000-08-15 01:13:23 +00001072 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001073 keydefs = idleConf.GetExtensionBindings(name)
1074 if hasattr(cls, "menudefs"):
1075 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001076 ins = cls(self)
1077 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001078 if keydefs:
1079 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001080 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001081 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001082 while methodname[:1] == '<':
1083 methodname = methodname[1:]
1084 while methodname[-1:] == '>':
1085 methodname = methodname[:-1]
1086 methodname = methodname + "_event"
1087 if hasattr(ins, methodname):
1088 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001089
1090 def apply_bindings(self, keydefs=None):
1091 if keydefs is None:
1092 keydefs = self.Bindings.default_keydefs
1093 text = self.text
1094 text.keydefs = keydefs
1095 for event, keylist in keydefs.items():
1096 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001097 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001098
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001099 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001100 """Add appropriate entries to the menus and submenus
1101
1102 Menus that are absent or None in self.menudict are ignored.
1103 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001104 if menudefs is None:
1105 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001106 if keydefs is None:
1107 keydefs = self.Bindings.default_keydefs
1108 menudict = self.menudict
1109 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001110 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001111 menu = menudict.get(mname)
1112 if not menu:
1113 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001114 for entry in entrylist:
1115 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001116 menu.add_separator()
1117 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001118 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001119 checkbutton = (label[:1] == '!')
1120 if checkbutton:
1121 label = label[1:]
1122 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001123 accelerator = get_accelerator(keydefs, eventname)
1124 def command(text=text, eventname=eventname):
1125 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001126 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001127 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001128 menu.add_checkbutton(label=label, underline=underline,
1129 command=command, accelerator=accelerator,
1130 variable=var)
1131 else:
1132 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001133 command=command,
1134 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001135
1136 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001137 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001138 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001139 value = var.get()
1140 return value
1141 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001142 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001143
1144 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001145 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001146 if var:
1147 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001148 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001149 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001150
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001151 def get_var_obj(self, name, vartype=None):
1152 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001153 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001154 # create a Tkinter variable object with self.text as master:
1155 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001156 return var
1157
1158 # Tk implementations of "virtual text methods" -- each platform
1159 # reusing IDLE's support code needs to define these for its GUI's
1160 # flavor of widget.
1161
1162 # Is character at text_index in a Python string? Return 0 for
1163 # "guaranteed no", true for anything else. This info is expensive
1164 # to compute ab initio, but is probably already known by the
1165 # platform's colorizer.
1166
1167 def is_char_in_string(self, text_index):
1168 if self.color:
1169 # Return true iff colorizer hasn't (re)gotten this far
1170 # yet, or the character is tagged as being in a string
1171 return self.text.tag_prevrange("TODO", text_index) or \
1172 "STRING" in self.text.tag_names(text_index)
1173 else:
1174 # The colorizer is missing: assume the worst
1175 return 1
1176
1177 # If a selection is defined in the text widget, return (start,
1178 # end) as Tkinter text indices, otherwise return (None, None)
1179 def get_selection_indices(self):
1180 try:
1181 first = self.text.index("sel.first")
1182 last = self.text.index("sel.last")
1183 return first, last
1184 except TclError:
1185 return None, None
1186
1187 # Return the text widget's current view of what a tab stop means
1188 # (equivalent width in spaces).
1189
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001190 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001191 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1192 return int(current)
1193
1194 # Set the text widget's current view of what a tab stop means.
1195
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001196 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001197 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001198 if self.get_tk_tabwidth() != newtabwidth:
1199 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001200 pixels = text.tk.call("font", "measure", text["font"],
1201 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001202 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001203 text.configure(tabs=pixels)
1204
Guido van Rossum33d26892007-08-05 15:29:28 +00001205### begin autoindent code ### (configuration was moved to beginning of class)
1206
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001207 def set_indentation_params(self, is_py_src, guess=True):
1208 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001209 i = self.guess_indent()
1210 if 2 <= i <= 8:
1211 self.indentwidth = i
1212 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001213 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001214 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001215
1216 def smart_backspace_event(self, event):
1217 text = self.text
1218 first, last = self.get_selection_indices()
1219 if first and last:
1220 text.delete(first, last)
1221 text.mark_set("insert", first)
1222 return "break"
1223 # Delete whitespace left, until hitting a real char or closest
1224 # preceding virtual tab stop.
1225 chars = text.get("insert linestart", "insert")
1226 if chars == '':
1227 if text.compare("insert", ">", "1.0"):
1228 # easy: delete preceding newline
1229 text.delete("insert-1c")
1230 else:
1231 text.bell() # at start of buffer
1232 return "break"
1233 if chars[-1] not in " \t":
1234 # easy: delete preceding real char
1235 text.delete("insert-1c")
1236 return "break"
1237 # Ick. It may require *inserting* spaces if we back up over a
1238 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001239 tabwidth = self.tabwidth
1240 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001241 assert have > 0
1242 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001243 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001244 if self.context_use_ps1:
1245 last_line_of_prompt = sys.ps1.split('\n')[-1]
1246 else:
1247 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001248 ncharsdeleted = 0
1249 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001250 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001251 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001252 chars = chars[:-1]
1253 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001254 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001255 if have <= want or chars[-1] not in " \t":
1256 break
1257 text.undo_block_start()
1258 text.delete("insert-%dc" % ncharsdeleted, "insert")
1259 if have < want:
1260 text.insert("insert", ' ' * (want - have))
1261 text.undo_block_stop()
1262 return "break"
1263
1264 def smart_indent_event(self, event):
1265 # if intraline selection:
1266 # delete it
1267 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001268 # do indent-region
1269 # else:
1270 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001271 text = self.text
1272 first, last = self.get_selection_indices()
1273 text.undo_block_start()
1274 try:
1275 if first and last:
1276 if index2line(first) != index2line(last):
1277 return self.indent_region_event(event)
1278 text.delete(first, last)
1279 text.mark_set("insert", first)
1280 prefix = text.get("insert linestart", "insert")
1281 raw, effective = classifyws(prefix, self.tabwidth)
1282 if raw == len(prefix):
1283 # only whitespace to the left
1284 self.reindent_to(effective + self.indentwidth)
1285 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001286 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001287 if self.usetabs:
1288 pad = '\t'
1289 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001290 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001291 n = self.indentwidth
1292 pad = ' ' * (n - effective % n)
1293 text.insert("insert", pad)
1294 text.see("insert")
1295 return "break"
1296 finally:
1297 text.undo_block_stop()
1298
1299 def newline_and_indent_event(self, event):
1300 text = self.text
1301 first, last = self.get_selection_indices()
1302 text.undo_block_start()
1303 try:
1304 if first and last:
1305 text.delete(first, last)
1306 text.mark_set("insert", first)
1307 line = text.get("insert linestart", "insert")
1308 i, n = 0, len(line)
1309 while i < n and line[i] in " \t":
1310 i = i+1
1311 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001312 # the cursor is in or at leading indentation in a continuation
1313 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001314 text.insert("insert linestart", '\n')
1315 return "break"
1316 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001317 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001318 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001319 last_line_of_prompt = sys.ps1.split('\n')[-1]
1320 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001321 line = line[:-1]
1322 i = i+1
1323 if i:
1324 text.delete("insert - %d chars" % i, "insert")
1325 # strip whitespace after insert point
1326 while text.get("insert") in " \t":
1327 text.delete("insert")
1328 # start new line
1329 text.insert("insert", '\n')
1330
1331 # adjust indentation for continuations and block
1332 # open/close first need to find the last stmt
1333 lno = index2line(text.index('insert'))
1334 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001335 if not self.context_use_ps1:
1336 for context in self.num_context_lines:
1337 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001338 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001339 rawtext = text.get(startatindex, "insert")
1340 y.set_str(rawtext)
1341 bod = y.find_good_parse_start(
1342 self.context_use_ps1,
1343 self._build_char_in_string_func(startatindex))
1344 if bod is not None or startat == 1:
1345 break
1346 y.set_lo(bod or 0)
1347 else:
1348 r = text.tag_prevrange("console", "insert")
1349 if r:
1350 startatindex = r[1]
1351 else:
1352 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001353 rawtext = text.get(startatindex, "insert")
1354 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001355 y.set_lo(0)
1356
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001357 c = y.get_continuation_type()
1358 if c != PyParse.C_NONE:
1359 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001360 if c == PyParse.C_STRING_FIRST_LINE:
1361 # after the first line of a string; do not indent at all
1362 pass
1363 elif c == PyParse.C_STRING_NEXT_LINES:
1364 # inside a string which started before this line;
1365 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001366 text.insert("insert", indent)
1367 elif c == PyParse.C_BRACKET:
1368 # line up with the first (if any) element of the
1369 # last open bracket structure; else indent one
1370 # level beyond the indent of the line with the
1371 # last open bracket
1372 self.reindent_to(y.compute_bracket_indent())
1373 elif c == PyParse.C_BACKSLASH:
1374 # if more than one line in this stmt already, just
1375 # mimic the current indent; else if initial line
1376 # has a start on an assignment stmt, indent to
1377 # beyond leftmost =; else to beyond first chunk of
1378 # non-whitespace on initial line
1379 if y.get_num_lines_in_stmt() > 1:
1380 text.insert("insert", indent)
1381 else:
1382 self.reindent_to(y.compute_backslash_indent())
1383 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001384 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001385 return "break"
1386
1387 # This line starts a brand new stmt; indent relative to
1388 # indentation of initial line of closest preceding
1389 # interesting stmt.
1390 indent = y.get_base_indent_string()
1391 text.insert("insert", indent)
1392 if y.is_block_opener():
1393 self.smart_indent_event(event)
1394 elif indent and y.is_block_closer():
1395 self.smart_backspace_event(event)
1396 return "break"
1397 finally:
1398 text.see("insert")
1399 text.undo_block_stop()
1400
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001401 # Our editwin provides a is_char_in_string function that works
1402 # with a Tk text index, but PyParse only knows about offsets into
1403 # a string. This builds a function for PyParse that accepts an
1404 # offset.
1405
1406 def _build_char_in_string_func(self, startindex):
1407 def inner(offset, _startindex=startindex,
1408 _icis=self.is_char_in_string):
1409 return _icis(_startindex + "+%dc" % offset)
1410 return inner
1411
1412 def indent_region_event(self, event):
1413 head, tail, chars, lines = self.get_region()
1414 for pos in range(len(lines)):
1415 line = lines[pos]
1416 if line:
1417 raw, effective = classifyws(line, self.tabwidth)
1418 effective = effective + self.indentwidth
1419 lines[pos] = self._make_blanks(effective) + line[raw:]
1420 self.set_region(head, tail, chars, lines)
1421 return "break"
1422
1423 def dedent_region_event(self, event):
1424 head, tail, chars, lines = self.get_region()
1425 for pos in range(len(lines)):
1426 line = lines[pos]
1427 if line:
1428 raw, effective = classifyws(line, self.tabwidth)
1429 effective = max(effective - self.indentwidth, 0)
1430 lines[pos] = self._make_blanks(effective) + line[raw:]
1431 self.set_region(head, tail, chars, lines)
1432 return "break"
1433
1434 def comment_region_event(self, event):
1435 head, tail, chars, lines = self.get_region()
1436 for pos in range(len(lines) - 1):
1437 line = lines[pos]
1438 lines[pos] = '##' + line
1439 self.set_region(head, tail, chars, lines)
1440
1441 def uncomment_region_event(self, event):
1442 head, tail, chars, lines = self.get_region()
1443 for pos in range(len(lines)):
1444 line = lines[pos]
1445 if not line:
1446 continue
1447 if line[:2] == '##':
1448 line = line[2:]
1449 elif line[:1] == '#':
1450 line = line[1:]
1451 lines[pos] = line
1452 self.set_region(head, tail, chars, lines)
1453
1454 def tabify_region_event(self, event):
1455 head, tail, chars, lines = self.get_region()
1456 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001457 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001458 for pos in range(len(lines)):
1459 line = lines[pos]
1460 if line:
1461 raw, effective = classifyws(line, tabwidth)
1462 ntabs, nspaces = divmod(effective, tabwidth)
1463 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1464 self.set_region(head, tail, chars, lines)
1465
1466 def untabify_region_event(self, event):
1467 head, tail, chars, lines = self.get_region()
1468 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001469 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001470 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001471 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001472 self.set_region(head, tail, chars, lines)
1473
1474 def toggle_tabs_event(self, event):
1475 if self.askyesno(
1476 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001477 "Turn tabs " + ("on", "off")[self.usetabs] +
1478 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001479 ("will be", "remains at")[self.usetabs] + " 8." +
1480 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001481 parent=self.text):
1482 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001483 # Try to prevent inconsistent indentation.
1484 # User must change indent width manually after using tabs.
1485 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001486 return "break"
1487
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001488 # XXX this isn't bound to anything -- see tabwidth comments
1489## def change_tabwidth_event(self, event):
1490## new = self._asktabwidth()
1491## if new != self.tabwidth:
1492## self.tabwidth = new
1493## self.set_indentation_params(0, guess=0)
1494## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001495
1496 def change_indentwidth_event(self, event):
1497 new = self.askinteger(
1498 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001499 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001500 parent=self.text,
1501 initialvalue=self.indentwidth,
1502 minvalue=2,
1503 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001504 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001505 self.indentwidth = new
1506 return "break"
1507
1508 def get_region(self):
1509 text = self.text
1510 first, last = self.get_selection_indices()
1511 if first and last:
1512 head = text.index(first + " linestart")
1513 tail = text.index(last + "-1c lineend +1c")
1514 else:
1515 head = text.index("insert linestart")
1516 tail = text.index("insert lineend +1c")
1517 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001518 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001519 return head, tail, chars, lines
1520
1521 def set_region(self, head, tail, chars, lines):
1522 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001523 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001524 if newchars == chars:
1525 text.bell()
1526 return
1527 text.tag_remove("sel", "1.0", "end")
1528 text.mark_set("insert", head)
1529 text.undo_block_start()
1530 text.delete(head, tail)
1531 text.insert(head, newchars)
1532 text.undo_block_stop()
1533 text.tag_add("sel", head, "insert")
1534
1535 # Make string that displays as n leading blanks.
1536
1537 def _make_blanks(self, n):
1538 if self.usetabs:
1539 ntabs, nspaces = divmod(n, self.tabwidth)
1540 return '\t' * ntabs + ' ' * nspaces
1541 else:
1542 return ' ' * n
1543
1544 # Delete from beginning of line to insert point, then reinsert
1545 # column logical (meaning use tabs if appropriate) spaces.
1546
1547 def reindent_to(self, column):
1548 text = self.text
1549 text.undo_block_start()
1550 if text.compare("insert linestart", "!=", "insert"):
1551 text.delete("insert linestart", "insert")
1552 if column:
1553 text.insert("insert", self._make_blanks(column))
1554 text.undo_block_stop()
1555
1556 def _asktabwidth(self):
1557 return self.askinteger(
1558 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001559 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001560 parent=self.text,
1561 initialvalue=self.indentwidth,
1562 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001563 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001564
1565 # Guess indentwidth from text content.
1566 # Return guessed indentwidth. This should not be believed unless
1567 # it's in a reasonable range (e.g., it will be 0 if no indented
1568 # blocks are found).
1569
1570 def guess_indent(self):
1571 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1572 if opener and indented:
1573 raw, indentsmall = classifyws(opener, self.tabwidth)
1574 raw, indentlarge = classifyws(indented, self.tabwidth)
1575 else:
1576 indentsmall = indentlarge = 0
1577 return indentlarge - indentsmall
1578
1579# "line.col" -> line, as an int
1580def index2line(index):
1581 return int(float(index))
1582
1583# Look at the leading whitespace in s.
1584# Return pair (# of leading ws characters,
1585# effective # of leading blanks after expanding
1586# tabs to width tabwidth)
1587
1588def classifyws(s, tabwidth):
1589 raw = effective = 0
1590 for ch in s:
1591 if ch == ' ':
1592 raw = raw + 1
1593 effective = effective + 1
1594 elif ch == '\t':
1595 raw = raw + 1
1596 effective = (effective // tabwidth + 1) * tabwidth
1597 else:
1598 break
1599 return raw, effective
1600
1601import tokenize
1602_tokenize = tokenize
1603del tokenize
1604
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001605class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001606
1607 # .run() chews over the Text widget, looking for a block opener
1608 # and the stmt following it. Returns a pair,
1609 # (line containing block opener, line containing stmt)
1610 # Either or both may be None.
1611
1612 def __init__(self, text, tabwidth):
1613 self.text = text
1614 self.tabwidth = tabwidth
1615 self.i = self.finished = 0
1616 self.blkopenline = self.indentedline = None
1617
1618 def readline(self):
1619 if self.finished:
1620 return ""
1621 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001622 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001623 if self.text.compare(mark, ">=", "end"):
1624 return ""
1625 return self.text.get(mark, mark + " lineend+1c")
1626
1627 def tokeneater(self, type, token, start, end, line,
1628 INDENT=_tokenize.INDENT,
1629 NAME=_tokenize.NAME,
1630 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1631 if self.finished:
1632 pass
1633 elif type == NAME and token in OPENERS:
1634 self.blkopenline = line
1635 elif type == INDENT and self.blkopenline:
1636 self.indentedline = line
1637 self.finished = 1
1638
1639 def run(self):
1640 save_tabsize = _tokenize.tabsize
1641 _tokenize.tabsize = self.tabwidth
1642 try:
1643 try:
Trent Nelson428de652008-03-18 22:41:35 +00001644 tokens = _tokenize.generate_tokens(self.readline)
1645 for token in tokens:
1646 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001647 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001648 # since we cut off the tokenizer early, we can trigger
1649 # spurious errors
1650 pass
1651 finally:
1652 _tokenize.tabsize = save_tabsize
1653 return self.blkopenline, self.indentedline
1654
1655### end autoindent code ###
1656
David Scherer7aced172000-08-15 01:13:23 +00001657def prepstr(s):
1658 # Helper to extract the underscore from a string, e.g.
1659 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001660 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001661 if i >= 0:
1662 s = s[:i] + s[i+1:]
1663 return i, s
1664
1665
1666keynames = {
1667 'bracketleft': '[',
1668 'bracketright': ']',
1669 'slash': '/',
1670}
1671
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001672def get_accelerator(keydefs, eventname):
1673 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001674 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1675 # if not keylist:
Ned Deilyb7601672014-03-27 20:49:14 -07001676 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001677 "<<open-module>>",
1678 "<<goto-line>>",
1679 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001680 return ""
1681 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001682 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001683 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1684 s = re.sub("Key-", "", s)
1685 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1686 s = re.sub("Control-", "Ctrl-", s)
1687 s = re.sub("-", "+", s)
1688 s = re.sub("><", " ", s)
1689 s = re.sub("<", "", s)
1690 s = re.sub(">", "", s)
1691 return s
1692
1693
1694def fixwordbreaks(root):
1695 # Make sure that Tk's double-click and next/previous word
1696 # operations use our definition of a word (i.e. an identifier)
1697 tk = root.tk
1698 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1699 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1700 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1701
1702
Terry Jan Reedycd567362014-10-17 01:31:35 -04001703def _editor_window(parent): # htest #
1704 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001705 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001706 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001707 if sys.argv[1:]:
1708 filename = sys.argv[1]
1709 else:
1710 filename = None
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001711 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001712 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001713 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001714 # Does not stop error, neither does following
1715 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001716
1717if __name__ == '__main__':
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001718 from idlelib.idle_test.htest import run
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001719 run(_help_dialog, _editor_window)