blob: a634d1f323a0957205a1e703a85e1f3b2a9e3c33 [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
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040024from idlelib import help
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
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -040029_py_version = ' (%s)' % platform.python_version()
30
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000031def _sphinx_version():
32 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
33 major, minor, micro, level, serial = sys.version_info
34 release = '%s%s' % (major, minor)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020035 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000036 if level == 'candidate':
37 release += 'rc%s' % (serial,)
38 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000039 release += '%s%s' % (level[0], serial)
40 return release
41
Terry Jan Reedye91e7632012-02-05 15:14:20 -050042
43class HelpDialog(object):
44
45 def __init__(self):
46 self.parent = None # parent of help window
47 self.dlg = None # the help window iteself
48
49 def display(self, parent, near=None):
50 """ Display the help dialog.
51
52 parent - parent widget for the help window
53
54 near - a Toplevel widget (e.g. EditorWindow or PyShell)
55 to use as a reference for placing the help window
56 """
Terry Jan Reedya0ae7892015-09-22 22:59:40 -040057 import warnings as w
58 w.warn("EditorWindow.HelpDialog is no longer used by Idle.\n"
59 "It will be removed in 3.6 or later.\n"
60 "It has been replaced by private help.HelpWindow\n",
61 DeprecationWarning, stacklevel=2)
Terry Jan Reedye91e7632012-02-05 15:14:20 -050062 if self.dlg is None:
63 self.show_dialog(parent)
64 if near:
65 self.nearwindow(near)
66
67 def show_dialog(self, parent):
68 self.parent = parent
69 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
70 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
71 dlg.bind('<Destroy>', self.destroy, '+')
72
73 def nearwindow(self, near):
74 # Place the help dialog near the window specified by parent.
75 # Note - this may not reposition the window in Metacity
76 # if "/apps/metacity/general/disable_workarounds" is enabled
77 dlg = self.dlg
78 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
79 dlg.withdraw()
80 dlg.geometry("=+%d+%d" % geom)
81 dlg.deiconify()
82 dlg.lift()
83
84 def destroy(self, ev=None):
85 self.dlg = None
86 self.parent = None
87
Terry Jan Reedy96f802a2015-09-20 23:05:25 -040088helpDialog = HelpDialog() # singleton instance, no longer used
Terry Jan Reedye91e7632012-02-05 15:14:20 -050089
90
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000091class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000092 from idlelib.Percolator import Percolator
93 from idlelib.ColorDelegator import ColorDelegator
94 from idlelib.UndoDelegator import UndoDelegator
95 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
96 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000097 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000098 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000099
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000100 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000101
102 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000103 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100104 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000105 if sys.platform.count('linux'):
106 # look for html docs in a couple of standard places
107 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
108 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
109 dochome = '/var/www/html/python/index.html'
110 else:
111 basepath = '/usr/share/doc/' # standard location
112 dochome = os.path.join(basepath, pyver,
113 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000114 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100115 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +0000116 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000117 if os.path.isfile(chmfile):
118 dochome = chmfile
Ned Deilyb7601672014-03-27 20:49:14 -0700119 elif sys.platform == 'darwin':
120 # documentation may be stored inside a python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100121 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000122 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000123 dochome = os.path.normpath(dochome)
124 if os.path.isfile(dochome):
125 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000126 if sys.platform == 'darwin':
127 # Safari requires real file:-URLs
128 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000129 else:
Terry Jan Reedyb6e17782014-09-19 22:54:15 -0400130 EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
David Scherer7aced172000-08-15 01:13:23 +0000131 self.flist = flist
132 root = root or flist.root
133 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000134 try:
135 sys.ps1
136 except AttributeError:
137 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000138 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000139 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000140 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000141 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200142 #self.top.instance_dict makes flist.inversedict available to
143 #configDialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000144 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000145 else:
146 self.tkinter_vars = {} # keys: Tkinter event names
147 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000148 self.top.instance_dict = {}
149 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000150 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000151 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000152 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200153 self.width = idleConf.GetOption('main', 'EditorWindow',
154 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000155 text_options = {
156 'name': 'text',
157 'padx': 5,
158 'wrap': 'none',
159 'width': self.width,
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200160 'height': idleConf.GetOption('main', 'EditorWindow',
161 'height', type='int')}
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000162 if TkVersion >= 8.5:
163 # Starting with tk 8.5 we have to set the new tabstyle option
164 # to 'wordprocessor' to achieve the same display of tabs as in
165 # older tk versions.
166 text_options['tabstyle'] = 'wordprocessor'
167 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000168 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000169
170 self.createmenubar()
171 self.apply_bindings()
172
173 self.top.protocol("WM_DELETE_WINDOW", self.close)
174 self.top.bind("<<close-window>>", self.close_event)
Ned Deilyb7601672014-03-27 20:49:14 -0700175 if macosxSupport.isAquaTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000176 # Command-W on editorwindows doesn't work without this.
177 text.bind('<<close-window>>', self.close_event)
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400178 # Some OS X systems have only one mouse button, so use
179 # control-click for popup context menus there. For two
180 # buttons, AquaTk defines <2> as the right button, not <3>.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000181 text.bind("<Control-Button-1>",self.right_menu_event)
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400182 text.bind("<2>", self.right_menu_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000183 else:
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400184 # Elsewhere, use right-click for popup menus.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000185 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000186 text.bind("<<cut>>", self.cut)
187 text.bind("<<copy>>", self.copy)
188 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000189 text.bind("<<center-insert>>", self.center_insert_event)
190 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000191 text.bind("<<python-docs>>", self.python_docs)
192 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000193 text.bind("<<open-config-dialog>>", self.config_dialog)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400194 text.bind("<<open-config-extensions-dialog>>",
195 self.config_extensions_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000196 text.bind("<<open-module>>", self.open_module)
197 text.bind("<<do-nothing>>", lambda event: "break")
198 text.bind("<<select-all>>", self.select_all)
199 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000200 text.bind("<<find>>", self.find_event)
201 text.bind("<<find-again>>", self.find_again_event)
202 text.bind("<<find-in-files>>", self.find_in_files_event)
203 text.bind("<<find-selection>>", self.find_selection_event)
204 text.bind("<<replace>>", self.replace_event)
205 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000206 text.bind("<<smart-backspace>>",self.smart_backspace_event)
207 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
208 text.bind("<<smart-indent>>",self.smart_indent_event)
209 text.bind("<<indent-region>>",self.indent_region_event)
210 text.bind("<<dedent-region>>",self.dedent_region_event)
211 text.bind("<<comment-region>>",self.comment_region_event)
212 text.bind("<<uncomment-region>>",self.uncomment_region_event)
213 text.bind("<<tabify-region>>",self.tabify_region_event)
214 text.bind("<<untabify-region>>",self.untabify_region_event)
215 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
216 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000217 text.bind("<Left>", self.move_at_edge_if_selection(0))
218 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000219 text.bind("<<del-word-left>>", self.del_word_left)
220 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000221 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000222
David Scherer7aced172000-08-15 01:13:23 +0000223 if flist:
224 flist.inversedict[self] = key
225 if key:
226 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000227 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000228 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
229 text.bind("<<open-class-browser>>", self.open_class_browser)
230 text.bind("<<open-path-browser>>", self.open_path_browser)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400231 text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
David Scherer7aced172000-08-15 01:13:23 +0000232
Steven M. Gava898a3652001-10-07 11:10:44 +0000233 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000234 vbar['command'] = text.yview
235 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000236 text['yscrollcommand'] = vbar.set
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400237 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000238 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
239 text.pack(side=TOP, fill=BOTH, expand=1)
240 text.focus_set()
241
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000242 # usetabs true -> literal tab characters are used by indent and
243 # dedent cmds, possibly mixed with spaces if
244 # indentwidth is not a multiple of tabwidth,
245 # which will cause Tabnanny to nag!
246 # false -> tab characters are converted to spaces by indent
247 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000248 # Although use-spaces=0 can be configured manually in config-main.def,
249 # configuration of tabs v. spaces is not supported in the configuration
250 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200251 usespaces = idleConf.GetOption('main', 'Indent',
252 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000253 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000254
255 # tabwidth is the display width of a literal tab character.
256 # CAUTION: telling Tk to use anything other than its default
257 # tab setting causes it to use an entirely different tabbing algorithm,
258 # treating tab stops as fixed distances from the left margin.
259 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000260 self.tabwidth = 8 # must remain 8 until Tk is fixed.
261
262 # indentwidth is the number of screen characters per indent level.
263 # The recommended Python indentation is four spaces.
264 self.indentwidth = self.tabwidth
265 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000266
267 # If context_use_ps1 is true, parsing searches back for a ps1 line;
268 # else searches for a popular (if, def, ...) Python stmt.
269 self.context_use_ps1 = False
270
271 # When searching backwards for a reliable place to begin parsing,
272 # first start num_context_lines[0] lines back, then
273 # num_context_lines[1] lines back if that didn't work, and so on.
274 # The last value should be huge (larger than the # of lines in a
275 # conceivable file).
276 # Making the initial values larger slows things down more often.
277 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000278 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000279 self.undo = undo = self.UndoDelegator()
280 per.insertfilter(undo)
281 text.undo_block_start = undo.undo_block_start
282 text.undo_block_stop = undo.undo_block_stop
283 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000284 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000285 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000286 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000287 self.good_load = False
288 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000289 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000290 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000291 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000292 if io.loadfile(filename):
293 self.good_load = True
294 is_py_src = self.ispythonsource(filename)
295 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000296 else:
297 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500298 self.good_load = True
299
Christian Heimesa156e092008-02-16 07:38:31 +0000300 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000301 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000302 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000303 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000304 menu = self.menudict.get('windows')
305 if menu:
306 end = menu.index("end")
307 if end is None:
308 end = -1
309 if end >= 0:
310 menu.add_separator()
311 end = end + 1
312 self.wmenu_end = end
313 WindowList.register_callback(self.postwindowsmenu)
314
315 # Some abstractions so IDLE extensions are cross-IDE
316 self.askyesno = tkMessageBox.askyesno
317 self.askinteger = tkSimpleDialog.askinteger
318 self.showerror = tkMessageBox.showerror
319
Martin v. Löwis307021f2005-11-27 16:59:04 +0000320 def _filename_to_unicode(self, filename):
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400321 """Return filename as BMP unicode so diplayable in Tk."""
322 # Decode bytes to unicode.
323 if isinstance(filename, bytes):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000324 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400325 filename = filename.decode(self.filesystemencoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000326 except UnicodeDecodeError:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000327 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400328 filename = filename.decode(self.encoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000329 except UnicodeDecodeError:
330 # byte-to-byte conversion
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400331 filename = filename.decode('iso8859-1')
332 # Replace non-BMP char with diamond questionmark.
333 return re.sub('[\U00010000-\U0010FFFF]', '\ufffd', filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000334
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000335 def new_callback(self, event):
336 dirname, basename = self.io.defaultfilename()
337 self.flist.new(dirname)
338 return "break"
339
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000340 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400341 if (event.state & 4) != 0 and event.keysym == "Home":
342 # state&4==Control. If <Control-Home>, use the Tk binding.
343 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000344 if self.text.index("iomark") and \
345 self.text.compare("iomark", "<=", "insert lineend") and \
346 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400347 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000348 insertpt = int(self.text.index("iomark").split(".")[1])
349 else:
350 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000351 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000352 if line[insertpt] not in (' ','\t'):
353 break
354 else:
355 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000356 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000357 if insertpt == lineat:
358 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000359 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000360 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400361 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000362 self.text.tag_remove("sel", "1.0", "end")
363 else:
364 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200365 # there was no previous selection
366 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400367 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200368 if self.text.compare(self.text.index("sel.first"), "<",
369 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400370 self.text.mark_set("my_anchor", "sel.first") # extend back
371 else:
372 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000373 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400374 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000375 if self.text.compare(first,">",last):
376 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000377 self.text.tag_remove("sel", "1.0", "end")
378 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000379 self.text.mark_set("insert", dest)
380 self.text.see("insert")
381 return "break"
382
David Scherer7aced172000-08-15 01:13:23 +0000383 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000384 self.status_bar = self.MultiStatusBar(self.top)
Ned Deilyb7601672014-03-27 20:49:14 -0700385 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000386 # Insert some padding to avoid obscuring some of the statusbar
387 # by the resize widget.
388 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000389 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
390 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
391 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000392 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
393 self.text.event_add("<<set-line-and-column>>",
394 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000395 self.text.after_idle(self.set_line_and_column)
396
397 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000398 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000399 self.status_bar.set_label('column', 'Col: %s' % column)
400 self.status_bar.set_label('line', 'Ln: %s' % line)
401
David Scherer7aced172000-08-15 01:13:23 +0000402 menu_specs = [
403 ("file", "_File"),
404 ("edit", "_Edit"),
405 ("format", "F_ormat"),
406 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000407 ("options", "_Options"),
Ned Deilyccb416f2015-01-17 21:06:27 -0800408 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000409 ("help", "_Help"),
410 ]
411
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000412
David Scherer7aced172000-08-15 01:13:23 +0000413 def createmenubar(self):
414 mbar = self.menubar
415 self.menudict = menudict = {}
416 for name, label in self.menu_specs:
417 underline, label = prepstr(label)
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400418 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000419 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ned Deilyb7601672014-03-27 20:49:14 -0700420 if macosxSupport.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000421 # Insert the application menu
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400422 menudict['application'] = menu = Menu(mbar, name='apple',
423 tearoff=0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000424 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000425 self.fill_menus()
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400426 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000427 self.menudict['file'].insert_cascade(3, label='Recent Files',
428 underline=0,
429 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000430 self.base_helpmenu_length = self.menudict['help'].index(END)
431 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000432
433 def postwindowsmenu(self):
434 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000435 menu = self.menudict['windows']
436 end = menu.index("end")
437 if end is None:
438 end = -1
439 if end > self.wmenu_end:
440 menu.delete(self.wmenu_end+1, end)
441 WindowList.add_windows_to_menu(menu)
442
443 rmenu = None
444
445 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000446 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
447 if not self.rmenu:
448 self.make_rmenu()
449 rmenu = self.rmenu
450 self.event = event
451 iswin = sys.platform[:3] == 'win'
452 if iswin:
453 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200454
Roger Serwy6b2918a2013-04-07 12:15:52 -0500455 for item in self.rmenu_specs:
456 try:
457 label, eventname, verify_state = item
458 except ValueError: # see issue1207589
459 continue
460
Andrew Svetlovd1837672012-11-01 22:41:19 +0200461 if verify_state is None:
462 continue
463 state = getattr(self, verify_state)()
464 rmenu.entryconfigure(label, state=state)
465
466
David Scherer7aced172000-08-15 01:13:23 +0000467 rmenu.tk_popup(event.x_root, event.y_root)
468 if iswin:
469 self.text.config(cursor="ibeam")
470
471 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200472 # ("Label", "<<virtual-event>>", "statefuncname"), ...
473 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000474 ]
475
476 def make_rmenu(self):
477 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500478 for item in self.rmenu_specs:
479 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200480 if label is not None:
481 def command(text=self.text, eventname=eventname):
482 text.event_generate(eventname)
483 rmenu.add_command(label=label, command=command)
484 else:
485 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000486 self.rmenu = rmenu
487
Andrew Svetlovd1837672012-11-01 22:41:19 +0200488 def rmenu_check_cut(self):
489 return self.rmenu_check_copy()
490
491 def rmenu_check_copy(self):
492 try:
493 indx = self.text.index('sel.first')
494 except TclError:
495 return 'disabled'
496 else:
497 return 'normal' if indx else 'disabled'
498
499 def rmenu_check_paste(self):
500 try:
501 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
502 except TclError:
503 return 'disabled'
504 else:
505 return 'normal'
506
David Scherer7aced172000-08-15 01:13:23 +0000507 def about_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400508 "Handle Help 'About IDLE' event."
509 # Synchronize with macosxSupport.overrideRootMenu.about_dialog.
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000510 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000511
Steven M. Gava3b55a892001-11-21 05:56:26 +0000512 def config_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400513 "Handle Options 'Configure IDLE' event."
514 # Synchronize with macosxSupport.overrideRootMenu.config_dialog.
Steven M. Gava3b55a892001-11-21 05:56:26 +0000515 configDialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400516
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400517 def config_extensions_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400518 "Handle Options 'Configure Extensions' event."
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400519 configDialog.ConfigExtensionsDialog(self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000520
David Scherer7aced172000-08-15 01:13:23 +0000521 def help_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400522 "Handle Help 'IDLE Help' event."
523 # Synchronize with macosxSupport.overrideRootMenu.help_dialog.
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500524 if self.root:
525 parent = self.root
526 else:
527 parent = self.top
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400528 help.show_idlehelp(parent)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000529
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000530 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000531 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000532 try:
533 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200534 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000535 tkMessageBox.showerror(title='Document Start Failure',
536 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000537 else:
538 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000539 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000540
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000541 def cut(self,event):
542 self.text.event_generate("<<Cut>>")
543 return "break"
544
545 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000546 if not self.text.tag_ranges("sel"):
547 # There is no selection, so do nothing and maybe interrupt.
548 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000549 self.text.event_generate("<<Copy>>")
550 return "break"
551
552 def paste(self,event):
553 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000554 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000555 return "break"
556
David Scherer7aced172000-08-15 01:13:23 +0000557 def select_all(self, event=None):
558 self.text.tag_add("sel", "1.0", "end-1c")
559 self.text.mark_set("insert", "1.0")
560 self.text.see("insert")
561 return "break"
562
563 def remove_selection(self, event=None):
564 self.text.tag_remove("sel", "1.0", "end")
565 self.text.see("insert")
566
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000567 def move_at_edge_if_selection(self, edge_index):
568 """Cursor move begins at start or end of selection
569
570 When a left/right cursor key is pressed create and return to Tkinter a
571 function which causes a cursor move from the associated edge of the
572 selection.
573
574 """
575 self_text_index = self.text.index
576 self_text_mark_set = self.text.mark_set
577 edges_table = ("sel.first+1c", "sel.last-1c")
578 def move_at_edge(event):
579 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
580 try:
581 self_text_index("sel.first")
582 self_text_mark_set("insert", edges_table[edge_index])
583 except TclError:
584 pass
585 return move_at_edge
586
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000587 def del_word_left(self, event):
588 self.text.event_generate('<Meta-Delete>')
589 return "break"
590
591 def del_word_right(self, event):
592 self.text.event_generate('<Meta-d>')
593 return "break"
594
Steven M. Gavac5976402002-01-04 03:06:08 +0000595 def find_event(self, event):
596 SearchDialog.find(self.text)
597 return "break"
598
599 def find_again_event(self, event):
600 SearchDialog.find_again(self.text)
601 return "break"
602
603 def find_selection_event(self, event):
604 SearchDialog.find_selection(self.text)
605 return "break"
606
607 def find_in_files_event(self, event):
608 GrepDialog.grep(self.text, self.io, self.flist)
609 return "break"
610
611 def replace_event(self, event):
612 ReplaceDialog.replace(self.text)
613 return "break"
614
615 def goto_line_event(self, event):
616 text = self.text
617 lineno = tkSimpleDialog.askinteger("Goto",
618 "Go to line number:",parent=text)
619 if lineno is None:
620 return "break"
621 if lineno <= 0:
622 text.bell()
623 return "break"
624 text.mark_set("insert", "%d.0" % lineno)
625 text.see("insert")
626
David Scherer7aced172000-08-15 01:13:23 +0000627 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000628 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000629 try:
630 name = self.text.get("sel.first", "sel.last")
631 except TclError:
632 name = ""
633 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000634 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000635 name = tkSimpleDialog.askstring("Module",
636 "Enter the name of a Python module\n"
637 "to search on sys.path and open:",
638 parent=self.text, initialvalue=name)
639 if name:
640 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000641 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000642 return
David Scherer7aced172000-08-15 01:13:23 +0000643 # XXX Ought to insert current file's directory in front of path
644 try:
Eric Snow6029e082014-01-25 15:32:46 -0700645 spec = importlib.util.find_spec(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400646 except (ValueError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000647 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
648 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700649 if spec is None:
Brett Cannon50793b42013-06-07 13:17:48 -0400650 tkMessageBox.showerror("Import error", "module not found",
651 parent=self.text)
David Scherer7aced172000-08-15 01:13:23 +0000652 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700653 if not isinstance(spec.loader, importlib.abc.SourceLoader):
Brett Cannon50793b42013-06-07 13:17:48 -0400654 tkMessageBox.showerror("Import error", "not a source-based module",
655 parent=self.text)
656 return
657 try:
Eric Snow02b9f9d2014-01-06 20:42:59 -0700658 file_path = spec.loader.get_filename(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400659 except AttributeError:
660 tkMessageBox.showerror("Import error",
661 "loader does not support get_filename",
662 parent=self.text)
663 return
David Scherer7aced172000-08-15 01:13:23 +0000664 if self.flist:
Brett Cannon50793b42013-06-07 13:17:48 -0400665 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000666 else:
Brett Cannon50793b42013-06-07 13:17:48 -0400667 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400668 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000669
670 def open_class_browser(self, event=None):
671 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400672 if not (self.__class__.__name__ == 'PyShellEditorWindow'
673 and filename):
674 filename = self.open_module()
675 if filename is None:
676 return
David Scherer7aced172000-08-15 01:13:23 +0000677 head, tail = os.path.split(filename)
678 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000679 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000680 ClassBrowser.ClassBrowser(self.flist, base, [head])
681
682 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000683 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000684 PathBrowser.PathBrowser(self.flist)
685
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400686 def open_turtle_demo(self, event = None):
687 import subprocess
688
689 cmd = [sys.executable,
690 '-c',
691 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400692 subprocess.Popen(cmd, shell=False)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400693
David Scherer7aced172000-08-15 01:13:23 +0000694 def gotoline(self, lineno):
695 if lineno is not None and lineno > 0:
696 self.text.mark_set("insert", "%d.0" % lineno)
697 self.text.tag_remove("sel", "1.0", "end")
698 self.text.tag_add("sel", "insert", "insert +1l")
699 self.center()
700
701 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000702 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000703 return True
David Scherer7aced172000-08-15 01:13:23 +0000704 base, ext = os.path.splitext(os.path.basename(filename))
705 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000706 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000707 line = self.text.get('1.0', '1.0 lineend')
708 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000709
710 def close_hook(self):
711 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000712 self.flist.unregister_maybe_terminate(self)
713 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000714
715 def set_close_hook(self, close_hook):
716 self.close_hook = close_hook
717
718 def filename_change_hook(self):
719 if self.flist:
720 self.flist.filename_changed_edit(self)
721 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000722 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000723 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000724
Christian Heimesa156e092008-02-16 07:38:31 +0000725 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000726 if self.color:
727 return
Christian Heimesa156e092008-02-16 07:38:31 +0000728 if self.ispythonsource(self.io.filename):
729 self.color = self.ColorDelegator()
730 # can add more colorizers here...
731 if self.color:
732 self.per.removefilter(self.undo)
733 self.per.insertfilter(self.color)
734 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000735
Christian Heimesa156e092008-02-16 07:38:31 +0000736 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000737 if not self.color:
738 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000739 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000740 self.per.removefilter(self.color)
741 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000742
Steven M. Gavab77d3432002-03-02 07:16:21 +0000743 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400744 "Update the color theme"
Christian Heimesa156e092008-02-16 07:38:31 +0000745 # Called from self.filename_change_hook and from configDialog.py
746 self._rmcolorizer()
747 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000748 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000749 normal_colors = idleConf.GetHighlight(theme, 'normal')
750 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
751 select_colors = idleConf.GetHighlight(theme, 'hilite')
752 self.text.config(
753 foreground=normal_colors['foreground'],
754 background=normal_colors['background'],
755 insertbackground=cursor_color,
756 selectforeground=select_colors['foreground'],
757 selectbackground=select_colors['background'],
758 )
Terry Jan Reedyd3517062015-09-28 04:52:49 -0400759 if TkVersion >= 8.5:
760 self.text.config(
761 inactiveselectbackground=select_colors['background'])
David Scherer7aced172000-08-15 01:13:23 +0000762
Guido van Rossum33d26892007-08-05 15:29:28 +0000763 IDENTCHARS = string.ascii_letters + string.digits + "_"
764
765 def colorize_syntax_error(self, text, pos):
766 text.tag_add("ERROR", pos)
767 char = text.get(pos)
768 if char and char in self.IDENTCHARS:
769 text.tag_add("ERROR", pos + " wordstart", pos)
770 if '\n' == text.get(pos): # error at line end
771 text.mark_set("insert", pos)
772 else:
773 text.mark_set("insert", pos + "+1c")
774 text.see(pos)
775
Steven M. Gavab1585412002-03-12 00:21:56 +0000776 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000777 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000778 # Called from configDialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400779
780 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000781
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000782 def RemoveKeybindings(self):
783 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000784 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000785 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000786 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000787 self.text.event_delete(event, *keylist)
788 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000789 xkeydefs = idleConf.GetExtensionBindings(extensionName)
790 if xkeydefs:
791 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000792 self.text.event_delete(event, *keylist)
793
794 def ApplyKeybindings(self):
795 "Update the keybindings after they are changed"
796 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000797 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000798 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000799 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000800 xkeydefs = idleConf.GetExtensionBindings(extensionName)
801 if xkeydefs:
802 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000803 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000804 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000805 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000806 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000807 for item in menu[1]:
808 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000809 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000810 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000811 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700812 end = menu.index(END)
813 if end is None:
814 # Skip empty menus
815 continue
816 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000817 for index in range(0, end):
818 if menu.type(index) == 'command':
819 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000820 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000821 itemName = menu.entrycget(index, 'label')
822 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000823 if menubarItem in menuEventDict:
824 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000825 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000826 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000827 accel = get_accelerator(keydefs, event)
828 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000829
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000830 def set_notabs_indentwidth(self):
831 "Update the indentwidth if changed and not using tabs in this window"
832 # Called from configDialog.py
833 if not self.usetabs:
834 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
835 type='int')
836
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000837 def reset_help_menu_entries(self):
838 "Update the additional help entries on the Help menu"
839 help_list = idleConf.GetAllExtraHelpSourcesList()
840 helpmenu = self.menudict['help']
841 # first delete the extra help entries, if any
842 helpmenu_length = helpmenu.index(END)
843 if helpmenu_length > self.base_helpmenu_length:
844 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
845 # then rebuild them
846 if help_list:
847 helpmenu.add_separator()
848 for entry in help_list:
849 cmd = self.__extra_help_callback(entry[1])
850 helpmenu.add_command(label=entry[0], command=cmd)
851 # and update the menu dictionary
852 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000853
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000854 def __extra_help_callback(self, helpfile):
855 "Create a callback with the helpfile value frozen at definition time"
856 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000857 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000858 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000859 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000860 try:
861 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200862 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000863 tkMessageBox.showerror(title='Document Start Failure',
864 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000865 else:
866 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000867 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000868
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000869 def update_recent_files_list(self, new_file=None):
870 "Load and update the recent files list and menus"
871 rf_list = []
872 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400873 with open(self.recent_files_path, 'r',
874 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000875 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000876 if new_file:
877 new_file = os.path.abspath(new_file) + '\n'
878 if new_file in rf_list:
879 rf_list.remove(new_file) # move to top
880 rf_list.insert(0, new_file)
881 # clean and save the recent files list
882 bad_paths = []
883 for path in rf_list:
884 if '\0' in path or not os.path.exists(path[0:-1]):
885 bad_paths.append(path)
886 rf_list = [path for path in rf_list if path not in bad_paths]
887 ulchars = "1234567890ABCDEFGHIJK"
888 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000889 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800890 with open(self.recent_files_path, 'w',
891 encoding='utf_8', errors='replace') as rf_file:
892 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200893 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800894 if not getattr(self.root, "recentfilelist_error_displayed", False):
895 self.root.recentfilelist_error_displayed = True
896 tkMessageBox.showerror(title='IDLE Error',
897 message='Unable to update Recent Files list:\n%s'
898 % str(err),
899 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000900 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000901 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000902 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700903 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000904 for i, file_name in enumerate(rf_list):
905 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000906 # make unicode string to display non-ASCII chars correctly
907 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000908 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000909 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000910 command=callback,
911 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000912
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000913 def __recent_file_callback(self, file_name):
914 def open_recent_file(fn_closure=file_name):
915 self.io.open(editFile=fn_closure)
916 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000917
David Scherer7aced172000-08-15 01:13:23 +0000918 def saved_change_hook(self):
919 short = self.short_title()
920 long = self.long_title()
921 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400922 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000923 elif short:
924 title = short
925 elif long:
926 title = long
927 else:
928 title = "Untitled"
929 icon = short or long or title
930 if not self.get_saved():
931 title = "*%s*" % title
932 icon = "*%s" % icon
933 self.top.wm_title(title)
934 self.top.wm_iconname(icon)
935
936 def get_saved(self):
937 return self.undo.get_saved()
938
939 def set_saved(self, flag):
940 self.undo.set_saved(flag)
941
942 def reset_undo(self):
943 self.undo.reset_undo()
944
945 def short_title(self):
946 filename = self.io.filename
947 if filename:
948 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500949 else:
950 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000951 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400952 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000953
954 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000955 # return unicode string to display non-ASCII chars correctly
956 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000957
958 def center_insert_event(self, event):
959 self.center()
960
961 def center(self, mark="insert"):
962 text = self.text
963 top, bot = self.getwindowlines()
964 lineno = self.getlineno(mark)
965 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000966 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000967 text.yview(float(newtop))
968
969 def getwindowlines(self):
970 text = self.text
971 top = self.getlineno("@0,0")
972 bot = self.getlineno("@0,65535")
973 if top == bot and text.winfo_height() == 1:
974 # Geometry manager hasn't run yet
975 height = int(text['height'])
976 bot = top + height - 1
977 return top, bot
978
979 def getlineno(self, mark="insert"):
980 text = self.text
981 return int(float(text.index(mark)))
982
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000983 def get_geometry(self):
984 "Return (width, height, x, y)"
985 geom = self.top.wm_geometry()
986 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000987 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000988
David Scherer7aced172000-08-15 01:13:23 +0000989 def close_event(self, event):
990 self.close()
991
992 def maybesave(self):
993 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000994 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000995 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000996 self.top.deiconify()
997 self.top.lower()
998 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000999 return self.io.maybesave()
1000
1001 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001002 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +00001003 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001004 self._close()
1005 return reply
1006
1007 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001008 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001009 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001010 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001011 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001012 self.io.close()
1013 self.io = None
1014 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001015 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001016 self.color.close(False)
1017 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001018 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001019 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001020 self.per.close()
1021 self.per = None
1022 self.top.destroy()
1023 if self.close_hook:
1024 # unless override: unregister from flist, terminate if last window
1025 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001026
1027 def load_extensions(self):
1028 self.extensions = {}
1029 self.load_standard_extensions()
1030
1031 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001032 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001033 if hasattr(ins, "close"):
1034 ins.close()
1035 self.extensions = {}
1036
1037 def load_standard_extensions(self):
1038 for name in self.get_standard_extension_names():
1039 try:
1040 self.load_extension(name)
1041 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001042 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001043 traceback.print_exc()
1044
1045 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001046 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001047
1048 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001049 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001050 try:
1051 mod = importlib.import_module('.' + name, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001052 except (ImportError, TypeError):
Brett Cannonaef82d32012-04-14 20:44:23 -04001053 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001054 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001055 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001056 raise
David Scherer7aced172000-08-15 01:13:23 +00001057 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001058 keydefs = idleConf.GetExtensionBindings(name)
1059 if hasattr(cls, "menudefs"):
1060 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001061 ins = cls(self)
1062 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001063 if keydefs:
1064 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001065 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001066 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001067 while methodname[:1] == '<':
1068 methodname = methodname[1:]
1069 while methodname[-1:] == '>':
1070 methodname = methodname[:-1]
1071 methodname = methodname + "_event"
1072 if hasattr(ins, methodname):
1073 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001074
1075 def apply_bindings(self, keydefs=None):
1076 if keydefs is None:
1077 keydefs = self.Bindings.default_keydefs
1078 text = self.text
1079 text.keydefs = keydefs
1080 for event, keylist in keydefs.items():
1081 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001082 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001083
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001084 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001085 """Add appropriate entries to the menus and submenus
1086
1087 Menus that are absent or None in self.menudict are ignored.
1088 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001089 if menudefs is None:
1090 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001091 if keydefs is None:
1092 keydefs = self.Bindings.default_keydefs
1093 menudict = self.menudict
1094 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001095 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001096 menu = menudict.get(mname)
1097 if not menu:
1098 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001099 for entry in entrylist:
1100 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001101 menu.add_separator()
1102 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001103 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001104 checkbutton = (label[:1] == '!')
1105 if checkbutton:
1106 label = label[1:]
1107 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001108 accelerator = get_accelerator(keydefs, eventname)
1109 def command(text=text, eventname=eventname):
1110 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001111 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001112 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001113 menu.add_checkbutton(label=label, underline=underline,
1114 command=command, accelerator=accelerator,
1115 variable=var)
1116 else:
1117 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001118 command=command,
1119 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001120
1121 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001122 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001123 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001124 value = var.get()
1125 return value
1126 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001127 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001128
1129 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001130 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001131 if var:
1132 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001133 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001134 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001135
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001136 def get_var_obj(self, name, vartype=None):
1137 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001138 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001139 # create a Tkinter variable object with self.text as master:
1140 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001141 return var
1142
1143 # Tk implementations of "virtual text methods" -- each platform
1144 # reusing IDLE's support code needs to define these for its GUI's
1145 # flavor of widget.
1146
1147 # Is character at text_index in a Python string? Return 0 for
1148 # "guaranteed no", true for anything else. This info is expensive
1149 # to compute ab initio, but is probably already known by the
1150 # platform's colorizer.
1151
1152 def is_char_in_string(self, text_index):
1153 if self.color:
1154 # Return true iff colorizer hasn't (re)gotten this far
1155 # yet, or the character is tagged as being in a string
1156 return self.text.tag_prevrange("TODO", text_index) or \
1157 "STRING" in self.text.tag_names(text_index)
1158 else:
1159 # The colorizer is missing: assume the worst
1160 return 1
1161
1162 # If a selection is defined in the text widget, return (start,
1163 # end) as Tkinter text indices, otherwise return (None, None)
1164 def get_selection_indices(self):
1165 try:
1166 first = self.text.index("sel.first")
1167 last = self.text.index("sel.last")
1168 return first, last
1169 except TclError:
1170 return None, None
1171
1172 # Return the text widget's current view of what a tab stop means
1173 # (equivalent width in spaces).
1174
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001175 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001176 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1177 return int(current)
1178
1179 # Set the text widget's current view of what a tab stop means.
1180
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001181 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001182 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001183 if self.get_tk_tabwidth() != newtabwidth:
1184 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001185 pixels = text.tk.call("font", "measure", text["font"],
1186 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001187 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001188 text.configure(tabs=pixels)
1189
Guido van Rossum33d26892007-08-05 15:29:28 +00001190### begin autoindent code ### (configuration was moved to beginning of class)
1191
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001192 def set_indentation_params(self, is_py_src, guess=True):
1193 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001194 i = self.guess_indent()
1195 if 2 <= i <= 8:
1196 self.indentwidth = i
1197 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001198 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001199 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001200
1201 def smart_backspace_event(self, event):
1202 text = self.text
1203 first, last = self.get_selection_indices()
1204 if first and last:
1205 text.delete(first, last)
1206 text.mark_set("insert", first)
1207 return "break"
1208 # Delete whitespace left, until hitting a real char or closest
1209 # preceding virtual tab stop.
1210 chars = text.get("insert linestart", "insert")
1211 if chars == '':
1212 if text.compare("insert", ">", "1.0"):
1213 # easy: delete preceding newline
1214 text.delete("insert-1c")
1215 else:
1216 text.bell() # at start of buffer
1217 return "break"
1218 if chars[-1] not in " \t":
1219 # easy: delete preceding real char
1220 text.delete("insert-1c")
1221 return "break"
1222 # Ick. It may require *inserting* spaces if we back up over a
1223 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001224 tabwidth = self.tabwidth
1225 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001226 assert have > 0
1227 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001228 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001229 if self.context_use_ps1:
1230 last_line_of_prompt = sys.ps1.split('\n')[-1]
1231 else:
1232 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001233 ncharsdeleted = 0
1234 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001235 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001236 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001237 chars = chars[:-1]
1238 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001239 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001240 if have <= want or chars[-1] not in " \t":
1241 break
1242 text.undo_block_start()
1243 text.delete("insert-%dc" % ncharsdeleted, "insert")
1244 if have < want:
1245 text.insert("insert", ' ' * (want - have))
1246 text.undo_block_stop()
1247 return "break"
1248
1249 def smart_indent_event(self, event):
1250 # if intraline selection:
1251 # delete it
1252 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001253 # do indent-region
1254 # else:
1255 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001256 text = self.text
1257 first, last = self.get_selection_indices()
1258 text.undo_block_start()
1259 try:
1260 if first and last:
1261 if index2line(first) != index2line(last):
1262 return self.indent_region_event(event)
1263 text.delete(first, last)
1264 text.mark_set("insert", first)
1265 prefix = text.get("insert linestart", "insert")
1266 raw, effective = classifyws(prefix, self.tabwidth)
1267 if raw == len(prefix):
1268 # only whitespace to the left
1269 self.reindent_to(effective + self.indentwidth)
1270 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001271 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001272 if self.usetabs:
1273 pad = '\t'
1274 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001275 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001276 n = self.indentwidth
1277 pad = ' ' * (n - effective % n)
1278 text.insert("insert", pad)
1279 text.see("insert")
1280 return "break"
1281 finally:
1282 text.undo_block_stop()
1283
1284 def newline_and_indent_event(self, event):
1285 text = self.text
1286 first, last = self.get_selection_indices()
1287 text.undo_block_start()
1288 try:
1289 if first and last:
1290 text.delete(first, last)
1291 text.mark_set("insert", first)
1292 line = text.get("insert linestart", "insert")
1293 i, n = 0, len(line)
1294 while i < n and line[i] in " \t":
1295 i = i+1
1296 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001297 # the cursor is in or at leading indentation in a continuation
1298 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001299 text.insert("insert linestart", '\n')
1300 return "break"
1301 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001302 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001303 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001304 last_line_of_prompt = sys.ps1.split('\n')[-1]
1305 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001306 line = line[:-1]
1307 i = i+1
1308 if i:
1309 text.delete("insert - %d chars" % i, "insert")
1310 # strip whitespace after insert point
1311 while text.get("insert") in " \t":
1312 text.delete("insert")
1313 # start new line
1314 text.insert("insert", '\n')
1315
1316 # adjust indentation for continuations and block
1317 # open/close first need to find the last stmt
1318 lno = index2line(text.index('insert'))
1319 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001320 if not self.context_use_ps1:
1321 for context in self.num_context_lines:
1322 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001323 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001324 rawtext = text.get(startatindex, "insert")
1325 y.set_str(rawtext)
1326 bod = y.find_good_parse_start(
1327 self.context_use_ps1,
1328 self._build_char_in_string_func(startatindex))
1329 if bod is not None or startat == 1:
1330 break
1331 y.set_lo(bod or 0)
1332 else:
1333 r = text.tag_prevrange("console", "insert")
1334 if r:
1335 startatindex = r[1]
1336 else:
1337 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001338 rawtext = text.get(startatindex, "insert")
1339 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001340 y.set_lo(0)
1341
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001342 c = y.get_continuation_type()
1343 if c != PyParse.C_NONE:
1344 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001345 if c == PyParse.C_STRING_FIRST_LINE:
1346 # after the first line of a string; do not indent at all
1347 pass
1348 elif c == PyParse.C_STRING_NEXT_LINES:
1349 # inside a string which started before this line;
1350 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001351 text.insert("insert", indent)
1352 elif c == PyParse.C_BRACKET:
1353 # line up with the first (if any) element of the
1354 # last open bracket structure; else indent one
1355 # level beyond the indent of the line with the
1356 # last open bracket
1357 self.reindent_to(y.compute_bracket_indent())
1358 elif c == PyParse.C_BACKSLASH:
1359 # if more than one line in this stmt already, just
1360 # mimic the current indent; else if initial line
1361 # has a start on an assignment stmt, indent to
1362 # beyond leftmost =; else to beyond first chunk of
1363 # non-whitespace on initial line
1364 if y.get_num_lines_in_stmt() > 1:
1365 text.insert("insert", indent)
1366 else:
1367 self.reindent_to(y.compute_backslash_indent())
1368 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001369 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001370 return "break"
1371
1372 # This line starts a brand new stmt; indent relative to
1373 # indentation of initial line of closest preceding
1374 # interesting stmt.
1375 indent = y.get_base_indent_string()
1376 text.insert("insert", indent)
1377 if y.is_block_opener():
1378 self.smart_indent_event(event)
1379 elif indent and y.is_block_closer():
1380 self.smart_backspace_event(event)
1381 return "break"
1382 finally:
1383 text.see("insert")
1384 text.undo_block_stop()
1385
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001386 # Our editwin provides a is_char_in_string function that works
1387 # with a Tk text index, but PyParse only knows about offsets into
1388 # a string. This builds a function for PyParse that accepts an
1389 # offset.
1390
1391 def _build_char_in_string_func(self, startindex):
1392 def inner(offset, _startindex=startindex,
1393 _icis=self.is_char_in_string):
1394 return _icis(_startindex + "+%dc" % offset)
1395 return inner
1396
1397 def indent_region_event(self, event):
1398 head, tail, chars, lines = self.get_region()
1399 for pos in range(len(lines)):
1400 line = lines[pos]
1401 if line:
1402 raw, effective = classifyws(line, self.tabwidth)
1403 effective = effective + self.indentwidth
1404 lines[pos] = self._make_blanks(effective) + line[raw:]
1405 self.set_region(head, tail, chars, lines)
1406 return "break"
1407
1408 def dedent_region_event(self, event):
1409 head, tail, chars, lines = self.get_region()
1410 for pos in range(len(lines)):
1411 line = lines[pos]
1412 if line:
1413 raw, effective = classifyws(line, self.tabwidth)
1414 effective = max(effective - self.indentwidth, 0)
1415 lines[pos] = self._make_blanks(effective) + line[raw:]
1416 self.set_region(head, tail, chars, lines)
1417 return "break"
1418
1419 def comment_region_event(self, event):
1420 head, tail, chars, lines = self.get_region()
1421 for pos in range(len(lines) - 1):
1422 line = lines[pos]
1423 lines[pos] = '##' + line
1424 self.set_region(head, tail, chars, lines)
1425
1426 def uncomment_region_event(self, event):
1427 head, tail, chars, lines = self.get_region()
1428 for pos in range(len(lines)):
1429 line = lines[pos]
1430 if not line:
1431 continue
1432 if line[:2] == '##':
1433 line = line[2:]
1434 elif line[:1] == '#':
1435 line = line[1:]
1436 lines[pos] = line
1437 self.set_region(head, tail, chars, lines)
1438
1439 def tabify_region_event(self, event):
1440 head, tail, chars, lines = self.get_region()
1441 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001442 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001443 for pos in range(len(lines)):
1444 line = lines[pos]
1445 if line:
1446 raw, effective = classifyws(line, tabwidth)
1447 ntabs, nspaces = divmod(effective, tabwidth)
1448 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1449 self.set_region(head, tail, chars, lines)
1450
1451 def untabify_region_event(self, event):
1452 head, tail, chars, lines = self.get_region()
1453 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001454 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001455 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001456 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001457 self.set_region(head, tail, chars, lines)
1458
1459 def toggle_tabs_event(self, event):
1460 if self.askyesno(
1461 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001462 "Turn tabs " + ("on", "off")[self.usetabs] +
1463 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001464 ("will be", "remains at")[self.usetabs] + " 8." +
1465 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001466 parent=self.text):
1467 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001468 # Try to prevent inconsistent indentation.
1469 # User must change indent width manually after using tabs.
1470 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001471 return "break"
1472
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001473 # XXX this isn't bound to anything -- see tabwidth comments
1474## def change_tabwidth_event(self, event):
1475## new = self._asktabwidth()
1476## if new != self.tabwidth:
1477## self.tabwidth = new
1478## self.set_indentation_params(0, guess=0)
1479## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001480
1481 def change_indentwidth_event(self, event):
1482 new = self.askinteger(
1483 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001484 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001485 parent=self.text,
1486 initialvalue=self.indentwidth,
1487 minvalue=2,
1488 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001489 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001490 self.indentwidth = new
1491 return "break"
1492
1493 def get_region(self):
1494 text = self.text
1495 first, last = self.get_selection_indices()
1496 if first and last:
1497 head = text.index(first + " linestart")
1498 tail = text.index(last + "-1c lineend +1c")
1499 else:
1500 head = text.index("insert linestart")
1501 tail = text.index("insert lineend +1c")
1502 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001503 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001504 return head, tail, chars, lines
1505
1506 def set_region(self, head, tail, chars, lines):
1507 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001508 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001509 if newchars == chars:
1510 text.bell()
1511 return
1512 text.tag_remove("sel", "1.0", "end")
1513 text.mark_set("insert", head)
1514 text.undo_block_start()
1515 text.delete(head, tail)
1516 text.insert(head, newchars)
1517 text.undo_block_stop()
1518 text.tag_add("sel", head, "insert")
1519
1520 # Make string that displays as n leading blanks.
1521
1522 def _make_blanks(self, n):
1523 if self.usetabs:
1524 ntabs, nspaces = divmod(n, self.tabwidth)
1525 return '\t' * ntabs + ' ' * nspaces
1526 else:
1527 return ' ' * n
1528
1529 # Delete from beginning of line to insert point, then reinsert
1530 # column logical (meaning use tabs if appropriate) spaces.
1531
1532 def reindent_to(self, column):
1533 text = self.text
1534 text.undo_block_start()
1535 if text.compare("insert linestart", "!=", "insert"):
1536 text.delete("insert linestart", "insert")
1537 if column:
1538 text.insert("insert", self._make_blanks(column))
1539 text.undo_block_stop()
1540
1541 def _asktabwidth(self):
1542 return self.askinteger(
1543 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001544 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001545 parent=self.text,
1546 initialvalue=self.indentwidth,
1547 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001548 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001549
1550 # Guess indentwidth from text content.
1551 # Return guessed indentwidth. This should not be believed unless
1552 # it's in a reasonable range (e.g., it will be 0 if no indented
1553 # blocks are found).
1554
1555 def guess_indent(self):
1556 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1557 if opener and indented:
1558 raw, indentsmall = classifyws(opener, self.tabwidth)
1559 raw, indentlarge = classifyws(indented, self.tabwidth)
1560 else:
1561 indentsmall = indentlarge = 0
1562 return indentlarge - indentsmall
1563
1564# "line.col" -> line, as an int
1565def index2line(index):
1566 return int(float(index))
1567
1568# Look at the leading whitespace in s.
1569# Return pair (# of leading ws characters,
1570# effective # of leading blanks after expanding
1571# tabs to width tabwidth)
1572
1573def classifyws(s, tabwidth):
1574 raw = effective = 0
1575 for ch in s:
1576 if ch == ' ':
1577 raw = raw + 1
1578 effective = effective + 1
1579 elif ch == '\t':
1580 raw = raw + 1
1581 effective = (effective // tabwidth + 1) * tabwidth
1582 else:
1583 break
1584 return raw, effective
1585
1586import tokenize
1587_tokenize = tokenize
1588del tokenize
1589
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001590class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001591
1592 # .run() chews over the Text widget, looking for a block opener
1593 # and the stmt following it. Returns a pair,
1594 # (line containing block opener, line containing stmt)
1595 # Either or both may be None.
1596
1597 def __init__(self, text, tabwidth):
1598 self.text = text
1599 self.tabwidth = tabwidth
1600 self.i = self.finished = 0
1601 self.blkopenline = self.indentedline = None
1602
1603 def readline(self):
1604 if self.finished:
1605 return ""
1606 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001607 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001608 if self.text.compare(mark, ">=", "end"):
1609 return ""
1610 return self.text.get(mark, mark + " lineend+1c")
1611
1612 def tokeneater(self, type, token, start, end, line,
1613 INDENT=_tokenize.INDENT,
1614 NAME=_tokenize.NAME,
1615 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1616 if self.finished:
1617 pass
1618 elif type == NAME and token in OPENERS:
1619 self.blkopenline = line
1620 elif type == INDENT and self.blkopenline:
1621 self.indentedline = line
1622 self.finished = 1
1623
1624 def run(self):
1625 save_tabsize = _tokenize.tabsize
1626 _tokenize.tabsize = self.tabwidth
1627 try:
1628 try:
Trent Nelson428de652008-03-18 22:41:35 +00001629 tokens = _tokenize.generate_tokens(self.readline)
1630 for token in tokens:
1631 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001632 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001633 # since we cut off the tokenizer early, we can trigger
1634 # spurious errors
1635 pass
1636 finally:
1637 _tokenize.tabsize = save_tabsize
1638 return self.blkopenline, self.indentedline
1639
1640### end autoindent code ###
1641
David Scherer7aced172000-08-15 01:13:23 +00001642def prepstr(s):
1643 # Helper to extract the underscore from a string, e.g.
1644 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001645 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001646 if i >= 0:
1647 s = s[:i] + s[i+1:]
1648 return i, s
1649
1650
1651keynames = {
1652 'bracketleft': '[',
1653 'bracketright': ']',
1654 'slash': '/',
1655}
1656
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001657def get_accelerator(keydefs, eventname):
1658 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001659 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1660 # if not keylist:
Ned Deilyb7601672014-03-27 20:49:14 -07001661 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001662 "<<open-module>>",
1663 "<<goto-line>>",
1664 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001665 return ""
1666 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001667 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001668 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1669 s = re.sub("Key-", "", s)
1670 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1671 s = re.sub("Control-", "Ctrl-", s)
1672 s = re.sub("-", "+", s)
1673 s = re.sub("><", " ", s)
1674 s = re.sub("<", "", s)
1675 s = re.sub(">", "", s)
1676 return s
1677
1678
1679def fixwordbreaks(root):
1680 # Make sure that Tk's double-click and next/previous word
1681 # operations use our definition of a word (i.e. an identifier)
1682 tk = root.tk
1683 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1684 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1685 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1686
1687
Terry Jan Reedycd567362014-10-17 01:31:35 -04001688def _editor_window(parent): # htest #
1689 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001690 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001691 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001692 if sys.argv[1:]:
1693 filename = sys.argv[1]
1694 else:
1695 filename = None
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001696 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001697 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001698 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001699 # Does not stop error, neither does following
1700 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001701
1702if __name__ == '__main__':
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001703 from idlelib.idle_test.htest import run
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04001704 run(_editor_window)