blob: 34ef89d06e4c68827e0bcebd25a3fb98b66693dc [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)
David Scherer7aced172000-08-15 01:13:23 +0000194 text.bind("<<open-module>>", self.open_module)
195 text.bind("<<do-nothing>>", lambda event: "break")
196 text.bind("<<select-all>>", self.select_all)
197 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000198 text.bind("<<find>>", self.find_event)
199 text.bind("<<find-again>>", self.find_again_event)
200 text.bind("<<find-in-files>>", self.find_in_files_event)
201 text.bind("<<find-selection>>", self.find_selection_event)
202 text.bind("<<replace>>", self.replace_event)
203 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000204 text.bind("<<smart-backspace>>",self.smart_backspace_event)
205 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
206 text.bind("<<smart-indent>>",self.smart_indent_event)
207 text.bind("<<indent-region>>",self.indent_region_event)
208 text.bind("<<dedent-region>>",self.dedent_region_event)
209 text.bind("<<comment-region>>",self.comment_region_event)
210 text.bind("<<uncomment-region>>",self.uncomment_region_event)
211 text.bind("<<tabify-region>>",self.tabify_region_event)
212 text.bind("<<untabify-region>>",self.untabify_region_event)
213 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
214 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000215 text.bind("<Left>", self.move_at_edge_if_selection(0))
216 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000217 text.bind("<<del-word-left>>", self.del_word_left)
218 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000219 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000220
David Scherer7aced172000-08-15 01:13:23 +0000221 if flist:
222 flist.inversedict[self] = key
223 if key:
224 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000225 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000226 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
227 text.bind("<<open-class-browser>>", self.open_class_browser)
228 text.bind("<<open-path-browser>>", self.open_path_browser)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400229 text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
David Scherer7aced172000-08-15 01:13:23 +0000230
Steven M. Gava898a3652001-10-07 11:10:44 +0000231 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000232 vbar['command'] = text.yview
233 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000234 text['yscrollcommand'] = vbar.set
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400235 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000236 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
237 text.pack(side=TOP, fill=BOTH, expand=1)
238 text.focus_set()
239
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000240 # usetabs true -> literal tab characters are used by indent and
241 # dedent cmds, possibly mixed with spaces if
242 # indentwidth is not a multiple of tabwidth,
243 # which will cause Tabnanny to nag!
244 # false -> tab characters are converted to spaces by indent
245 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000246 # Although use-spaces=0 can be configured manually in config-main.def,
247 # configuration of tabs v. spaces is not supported in the configuration
248 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200249 usespaces = idleConf.GetOption('main', 'Indent',
250 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000251 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000252
253 # tabwidth is the display width of a literal tab character.
254 # CAUTION: telling Tk to use anything other than its default
255 # tab setting causes it to use an entirely different tabbing algorithm,
256 # treating tab stops as fixed distances from the left margin.
257 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000258 self.tabwidth = 8 # must remain 8 until Tk is fixed.
259
260 # indentwidth is the number of screen characters per indent level.
261 # The recommended Python indentation is four spaces.
262 self.indentwidth = self.tabwidth
263 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000264
265 # If context_use_ps1 is true, parsing searches back for a ps1 line;
266 # else searches for a popular (if, def, ...) Python stmt.
267 self.context_use_ps1 = False
268
269 # When searching backwards for a reliable place to begin parsing,
270 # first start num_context_lines[0] lines back, then
271 # num_context_lines[1] lines back if that didn't work, and so on.
272 # The last value should be huge (larger than the # of lines in a
273 # conceivable file).
274 # Making the initial values larger slows things down more often.
275 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000276 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000277 self.undo = undo = self.UndoDelegator()
278 per.insertfilter(undo)
279 text.undo_block_start = undo.undo_block_start
280 text.undo_block_stop = undo.undo_block_stop
281 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000282 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000283 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000284 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000285 self.good_load = False
286 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000287 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000288 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000289 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000290 if io.loadfile(filename):
291 self.good_load = True
292 is_py_src = self.ispythonsource(filename)
293 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000294 else:
295 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500296 self.good_load = True
297
Christian Heimesa156e092008-02-16 07:38:31 +0000298 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000299 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000300 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000301 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000302 menu = self.menudict.get('windows')
303 if menu:
304 end = menu.index("end")
305 if end is None:
306 end = -1
307 if end >= 0:
308 menu.add_separator()
309 end = end + 1
310 self.wmenu_end = end
311 WindowList.register_callback(self.postwindowsmenu)
312
313 # Some abstractions so IDLE extensions are cross-IDE
314 self.askyesno = tkMessageBox.askyesno
315 self.askinteger = tkSimpleDialog.askinteger
316 self.showerror = tkMessageBox.showerror
317
Martin v. Löwis307021f2005-11-27 16:59:04 +0000318 def _filename_to_unicode(self, filename):
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400319 """Return filename as BMP unicode so diplayable in Tk."""
320 # Decode bytes to unicode.
321 if isinstance(filename, bytes):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000322 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400323 filename = filename.decode(self.filesystemencoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000324 except UnicodeDecodeError:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000325 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400326 filename = filename.decode(self.encoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000327 except UnicodeDecodeError:
328 # byte-to-byte conversion
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400329 filename = filename.decode('iso8859-1')
330 # Replace non-BMP char with diamond questionmark.
331 return re.sub('[\U00010000-\U0010FFFF]', '\ufffd', filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000332
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000333 def new_callback(self, event):
334 dirname, basename = self.io.defaultfilename()
335 self.flist.new(dirname)
336 return "break"
337
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000338 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400339 if (event.state & 4) != 0 and event.keysym == "Home":
340 # state&4==Control. If <Control-Home>, use the Tk binding.
341 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000342 if self.text.index("iomark") and \
343 self.text.compare("iomark", "<=", "insert lineend") and \
344 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400345 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000346 insertpt = int(self.text.index("iomark").split(".")[1])
347 else:
348 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000349 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000350 if line[insertpt] not in (' ','\t'):
351 break
352 else:
353 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000354 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000355 if insertpt == lineat:
356 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000357 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000358 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400359 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000360 self.text.tag_remove("sel", "1.0", "end")
361 else:
362 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200363 # there was no previous selection
364 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400365 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200366 if self.text.compare(self.text.index("sel.first"), "<",
367 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400368 self.text.mark_set("my_anchor", "sel.first") # extend back
369 else:
370 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000371 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400372 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000373 if self.text.compare(first,">",last):
374 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000375 self.text.tag_remove("sel", "1.0", "end")
376 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000377 self.text.mark_set("insert", dest)
378 self.text.see("insert")
379 return "break"
380
David Scherer7aced172000-08-15 01:13:23 +0000381 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000382 self.status_bar = self.MultiStatusBar(self.top)
Ned Deilyb7601672014-03-27 20:49:14 -0700383 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000384 # Insert some padding to avoid obscuring some of the statusbar
385 # by the resize widget.
386 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000387 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
388 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
389 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000390 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
391 self.text.event_add("<<set-line-and-column>>",
392 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000393 self.text.after_idle(self.set_line_and_column)
394
395 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000396 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000397 self.status_bar.set_label('column', 'Col: %s' % column)
398 self.status_bar.set_label('line', 'Ln: %s' % line)
399
David Scherer7aced172000-08-15 01:13:23 +0000400 menu_specs = [
401 ("file", "_File"),
402 ("edit", "_Edit"),
403 ("format", "F_ormat"),
404 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000405 ("options", "_Options"),
Ned Deilyccb416f2015-01-17 21:06:27 -0800406 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000407 ("help", "_Help"),
408 ]
409
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000410
David Scherer7aced172000-08-15 01:13:23 +0000411 def createmenubar(self):
412 mbar = self.menubar
413 self.menudict = menudict = {}
414 for name, label in self.menu_specs:
415 underline, label = prepstr(label)
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400416 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000417 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ned Deilyb7601672014-03-27 20:49:14 -0700418 if macosxSupport.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000419 # Insert the application menu
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400420 menudict['application'] = menu = Menu(mbar, name='apple',
421 tearoff=0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000422 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000423 self.fill_menus()
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400424 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000425 self.menudict['file'].insert_cascade(3, label='Recent Files',
426 underline=0,
427 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000428 self.base_helpmenu_length = self.menudict['help'].index(END)
429 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000430
431 def postwindowsmenu(self):
432 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000433 menu = self.menudict['windows']
434 end = menu.index("end")
435 if end is None:
436 end = -1
437 if end > self.wmenu_end:
438 menu.delete(self.wmenu_end+1, end)
439 WindowList.add_windows_to_menu(menu)
440
441 rmenu = None
442
443 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000444 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
445 if not self.rmenu:
446 self.make_rmenu()
447 rmenu = self.rmenu
448 self.event = event
449 iswin = sys.platform[:3] == 'win'
450 if iswin:
451 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200452
Roger Serwy6b2918a2013-04-07 12:15:52 -0500453 for item in self.rmenu_specs:
454 try:
455 label, eventname, verify_state = item
456 except ValueError: # see issue1207589
457 continue
458
Andrew Svetlovd1837672012-11-01 22:41:19 +0200459 if verify_state is None:
460 continue
461 state = getattr(self, verify_state)()
462 rmenu.entryconfigure(label, state=state)
463
464
David Scherer7aced172000-08-15 01:13:23 +0000465 rmenu.tk_popup(event.x_root, event.y_root)
466 if iswin:
467 self.text.config(cursor="ibeam")
468
469 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200470 # ("Label", "<<virtual-event>>", "statefuncname"), ...
471 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000472 ]
473
474 def make_rmenu(self):
475 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500476 for item in self.rmenu_specs:
477 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200478 if label is not None:
479 def command(text=self.text, eventname=eventname):
480 text.event_generate(eventname)
481 rmenu.add_command(label=label, command=command)
482 else:
483 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000484 self.rmenu = rmenu
485
Andrew Svetlovd1837672012-11-01 22:41:19 +0200486 def rmenu_check_cut(self):
487 return self.rmenu_check_copy()
488
489 def rmenu_check_copy(self):
490 try:
491 indx = self.text.index('sel.first')
492 except TclError:
493 return 'disabled'
494 else:
495 return 'normal' if indx else 'disabled'
496
497 def rmenu_check_paste(self):
498 try:
499 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
500 except TclError:
501 return 'disabled'
502 else:
503 return 'normal'
504
David Scherer7aced172000-08-15 01:13:23 +0000505 def about_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400506 "Handle Help 'About IDLE' event."
507 # Synchronize with macosxSupport.overrideRootMenu.about_dialog.
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000508 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000509
Steven M. Gava3b55a892001-11-21 05:56:26 +0000510 def config_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400511 "Handle Options 'Configure IDLE' event."
512 # Synchronize with macosxSupport.overrideRootMenu.config_dialog.
Steven M. Gava3b55a892001-11-21 05:56:26 +0000513 configDialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400514
David Scherer7aced172000-08-15 01:13:23 +0000515 def help_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400516 "Handle Help 'IDLE Help' event."
517 # Synchronize with macosxSupport.overrideRootMenu.help_dialog.
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500518 if self.root:
519 parent = self.root
520 else:
521 parent = self.top
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400522 help.show_idlehelp(parent)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000523
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000524 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000525 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000526 try:
527 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200528 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000529 tkMessageBox.showerror(title='Document Start Failure',
530 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000531 else:
532 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000533 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000534
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000535 def cut(self,event):
536 self.text.event_generate("<<Cut>>")
537 return "break"
538
539 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000540 if not self.text.tag_ranges("sel"):
541 # There is no selection, so do nothing and maybe interrupt.
542 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000543 self.text.event_generate("<<Copy>>")
544 return "break"
545
546 def paste(self,event):
547 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000548 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000549 return "break"
550
David Scherer7aced172000-08-15 01:13:23 +0000551 def select_all(self, event=None):
552 self.text.tag_add("sel", "1.0", "end-1c")
553 self.text.mark_set("insert", "1.0")
554 self.text.see("insert")
555 return "break"
556
557 def remove_selection(self, event=None):
558 self.text.tag_remove("sel", "1.0", "end")
559 self.text.see("insert")
560
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000561 def move_at_edge_if_selection(self, edge_index):
562 """Cursor move begins at start or end of selection
563
564 When a left/right cursor key is pressed create and return to Tkinter a
565 function which causes a cursor move from the associated edge of the
566 selection.
567
568 """
569 self_text_index = self.text.index
570 self_text_mark_set = self.text.mark_set
571 edges_table = ("sel.first+1c", "sel.last-1c")
572 def move_at_edge(event):
573 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
574 try:
575 self_text_index("sel.first")
576 self_text_mark_set("insert", edges_table[edge_index])
577 except TclError:
578 pass
579 return move_at_edge
580
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000581 def del_word_left(self, event):
582 self.text.event_generate('<Meta-Delete>')
583 return "break"
584
585 def del_word_right(self, event):
586 self.text.event_generate('<Meta-d>')
587 return "break"
588
Steven M. Gavac5976402002-01-04 03:06:08 +0000589 def find_event(self, event):
590 SearchDialog.find(self.text)
591 return "break"
592
593 def find_again_event(self, event):
594 SearchDialog.find_again(self.text)
595 return "break"
596
597 def find_selection_event(self, event):
598 SearchDialog.find_selection(self.text)
599 return "break"
600
601 def find_in_files_event(self, event):
602 GrepDialog.grep(self.text, self.io, self.flist)
603 return "break"
604
605 def replace_event(self, event):
606 ReplaceDialog.replace(self.text)
607 return "break"
608
609 def goto_line_event(self, event):
610 text = self.text
611 lineno = tkSimpleDialog.askinteger("Goto",
612 "Go to line number:",parent=text)
613 if lineno is None:
614 return "break"
615 if lineno <= 0:
616 text.bell()
617 return "break"
618 text.mark_set("insert", "%d.0" % lineno)
619 text.see("insert")
620
David Scherer7aced172000-08-15 01:13:23 +0000621 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000622 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000623 try:
624 name = self.text.get("sel.first", "sel.last")
625 except TclError:
626 name = ""
627 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000628 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000629 name = tkSimpleDialog.askstring("Module",
630 "Enter the name of a Python module\n"
631 "to search on sys.path and open:",
632 parent=self.text, initialvalue=name)
633 if name:
634 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000635 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000636 return
David Scherer7aced172000-08-15 01:13:23 +0000637 # XXX Ought to insert current file's directory in front of path
638 try:
Eric Snow6029e082014-01-25 15:32:46 -0700639 spec = importlib.util.find_spec(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400640 except (ValueError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000641 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
642 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700643 if spec is None:
Brett Cannon50793b42013-06-07 13:17:48 -0400644 tkMessageBox.showerror("Import error", "module not found",
645 parent=self.text)
David Scherer7aced172000-08-15 01:13:23 +0000646 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700647 if not isinstance(spec.loader, importlib.abc.SourceLoader):
Brett Cannon50793b42013-06-07 13:17:48 -0400648 tkMessageBox.showerror("Import error", "not a source-based module",
649 parent=self.text)
650 return
651 try:
Eric Snow02b9f9d2014-01-06 20:42:59 -0700652 file_path = spec.loader.get_filename(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400653 except AttributeError:
654 tkMessageBox.showerror("Import error",
655 "loader does not support get_filename",
656 parent=self.text)
657 return
David Scherer7aced172000-08-15 01:13:23 +0000658 if self.flist:
Brett Cannon50793b42013-06-07 13:17:48 -0400659 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000660 else:
Brett Cannon50793b42013-06-07 13:17:48 -0400661 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400662 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000663
664 def open_class_browser(self, event=None):
665 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400666 if not (self.__class__.__name__ == 'PyShellEditorWindow'
667 and filename):
668 filename = self.open_module()
669 if filename is None:
670 return
David Scherer7aced172000-08-15 01:13:23 +0000671 head, tail = os.path.split(filename)
672 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000673 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000674 ClassBrowser.ClassBrowser(self.flist, base, [head])
675
676 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000677 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000678 PathBrowser.PathBrowser(self.flist)
679
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400680 def open_turtle_demo(self, event = None):
681 import subprocess
682
683 cmd = [sys.executable,
684 '-c',
685 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400686 subprocess.Popen(cmd, shell=False)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400687
David Scherer7aced172000-08-15 01:13:23 +0000688 def gotoline(self, lineno):
689 if lineno is not None and lineno > 0:
690 self.text.mark_set("insert", "%d.0" % lineno)
691 self.text.tag_remove("sel", "1.0", "end")
692 self.text.tag_add("sel", "insert", "insert +1l")
693 self.center()
694
695 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000696 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000697 return True
David Scherer7aced172000-08-15 01:13:23 +0000698 base, ext = os.path.splitext(os.path.basename(filename))
699 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000700 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000701 line = self.text.get('1.0', '1.0 lineend')
702 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000703
704 def close_hook(self):
705 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000706 self.flist.unregister_maybe_terminate(self)
707 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000708
709 def set_close_hook(self, close_hook):
710 self.close_hook = close_hook
711
712 def filename_change_hook(self):
713 if self.flist:
714 self.flist.filename_changed_edit(self)
715 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000716 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000717 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000718
Christian Heimesa156e092008-02-16 07:38:31 +0000719 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000720 if self.color:
721 return
Christian Heimesa156e092008-02-16 07:38:31 +0000722 if self.ispythonsource(self.io.filename):
723 self.color = self.ColorDelegator()
724 # can add more colorizers here...
725 if self.color:
726 self.per.removefilter(self.undo)
727 self.per.insertfilter(self.color)
728 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000729
Christian Heimesa156e092008-02-16 07:38:31 +0000730 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000731 if not self.color:
732 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000733 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000734 self.per.removefilter(self.color)
735 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000736
Steven M. Gavab77d3432002-03-02 07:16:21 +0000737 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400738 "Update the color theme"
Christian Heimesa156e092008-02-16 07:38:31 +0000739 # Called from self.filename_change_hook and from configDialog.py
740 self._rmcolorizer()
741 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000742 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000743 normal_colors = idleConf.GetHighlight(theme, 'normal')
744 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
745 select_colors = idleConf.GetHighlight(theme, 'hilite')
746 self.text.config(
747 foreground=normal_colors['foreground'],
748 background=normal_colors['background'],
749 insertbackground=cursor_color,
750 selectforeground=select_colors['foreground'],
751 selectbackground=select_colors['background'],
752 )
Terry Jan Reedyd3517062015-09-28 04:52:49 -0400753 if TkVersion >= 8.5:
754 self.text.config(
755 inactiveselectbackground=select_colors['background'])
David Scherer7aced172000-08-15 01:13:23 +0000756
Guido van Rossum33d26892007-08-05 15:29:28 +0000757 IDENTCHARS = string.ascii_letters + string.digits + "_"
758
759 def colorize_syntax_error(self, text, pos):
760 text.tag_add("ERROR", pos)
761 char = text.get(pos)
762 if char and char in self.IDENTCHARS:
763 text.tag_add("ERROR", pos + " wordstart", pos)
764 if '\n' == text.get(pos): # error at line end
765 text.mark_set("insert", pos)
766 else:
767 text.mark_set("insert", pos + "+1c")
768 text.see(pos)
769
Steven M. Gavab1585412002-03-12 00:21:56 +0000770 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000771 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000772 # Called from configDialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400773
774 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000775
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000776 def RemoveKeybindings(self):
777 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000778 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000779 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000780 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000781 self.text.event_delete(event, *keylist)
782 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000783 xkeydefs = idleConf.GetExtensionBindings(extensionName)
784 if xkeydefs:
785 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000786 self.text.event_delete(event, *keylist)
787
788 def ApplyKeybindings(self):
789 "Update the keybindings after they are changed"
790 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000791 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000792 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000793 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000794 xkeydefs = idleConf.GetExtensionBindings(extensionName)
795 if xkeydefs:
796 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000797 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000798 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000799 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000800 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000801 for item in menu[1]:
802 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000803 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000804 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000805 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700806 end = menu.index(END)
807 if end is None:
808 # Skip empty menus
809 continue
810 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000811 for index in range(0, end):
812 if menu.type(index) == 'command':
813 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000814 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000815 itemName = menu.entrycget(index, 'label')
816 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000817 if menubarItem in menuEventDict:
818 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000819 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000820 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000821 accel = get_accelerator(keydefs, event)
822 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000823
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000824 def set_notabs_indentwidth(self):
825 "Update the indentwidth if changed and not using tabs in this window"
826 # Called from configDialog.py
827 if not self.usetabs:
828 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
829 type='int')
830
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000831 def reset_help_menu_entries(self):
832 "Update the additional help entries on the Help menu"
833 help_list = idleConf.GetAllExtraHelpSourcesList()
834 helpmenu = self.menudict['help']
835 # first delete the extra help entries, if any
836 helpmenu_length = helpmenu.index(END)
837 if helpmenu_length > self.base_helpmenu_length:
838 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
839 # then rebuild them
840 if help_list:
841 helpmenu.add_separator()
842 for entry in help_list:
843 cmd = self.__extra_help_callback(entry[1])
844 helpmenu.add_command(label=entry[0], command=cmd)
845 # and update the menu dictionary
846 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000847
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000848 def __extra_help_callback(self, helpfile):
849 "Create a callback with the helpfile value frozen at definition time"
850 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000851 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000852 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000853 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000854 try:
855 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200856 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000857 tkMessageBox.showerror(title='Document Start Failure',
858 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000859 else:
860 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000861 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000862
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000863 def update_recent_files_list(self, new_file=None):
864 "Load and update the recent files list and menus"
865 rf_list = []
866 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400867 with open(self.recent_files_path, 'r',
868 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000869 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000870 if new_file:
871 new_file = os.path.abspath(new_file) + '\n'
872 if new_file in rf_list:
873 rf_list.remove(new_file) # move to top
874 rf_list.insert(0, new_file)
875 # clean and save the recent files list
876 bad_paths = []
877 for path in rf_list:
878 if '\0' in path or not os.path.exists(path[0:-1]):
879 bad_paths.append(path)
880 rf_list = [path for path in rf_list if path not in bad_paths]
881 ulchars = "1234567890ABCDEFGHIJK"
882 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000883 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800884 with open(self.recent_files_path, 'w',
885 encoding='utf_8', errors='replace') as rf_file:
886 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200887 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800888 if not getattr(self.root, "recentfilelist_error_displayed", False):
889 self.root.recentfilelist_error_displayed = True
Terry Jan Reedy4c973e92015-10-27 03:38:02 -0400890 tkMessageBox.showwarning(title='IDLE Warning',
891 message="Cannot update File menu Recent Files list. "
892 "Your operating system says:\n%s\n"
893 "Select OK and IDLE will continue without updating."
894 % self._filename_to_unicode(str(err)),
Ned Deilyf505b742011-12-14 14:58:24 -0800895 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000896 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000897 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000898 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700899 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000900 for i, file_name in enumerate(rf_list):
901 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000902 # make unicode string to display non-ASCII chars correctly
903 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000904 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000905 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000906 command=callback,
907 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000908
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000909 def __recent_file_callback(self, file_name):
910 def open_recent_file(fn_closure=file_name):
911 self.io.open(editFile=fn_closure)
912 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000913
David Scherer7aced172000-08-15 01:13:23 +0000914 def saved_change_hook(self):
915 short = self.short_title()
916 long = self.long_title()
917 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400918 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000919 elif short:
920 title = short
921 elif long:
922 title = long
923 else:
924 title = "Untitled"
925 icon = short or long or title
926 if not self.get_saved():
927 title = "*%s*" % title
928 icon = "*%s" % icon
929 self.top.wm_title(title)
930 self.top.wm_iconname(icon)
931
932 def get_saved(self):
933 return self.undo.get_saved()
934
935 def set_saved(self, flag):
936 self.undo.set_saved(flag)
937
938 def reset_undo(self):
939 self.undo.reset_undo()
940
941 def short_title(self):
942 filename = self.io.filename
943 if filename:
944 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500945 else:
946 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000947 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400948 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000949
950 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000951 # return unicode string to display non-ASCII chars correctly
952 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000953
954 def center_insert_event(self, event):
955 self.center()
956
957 def center(self, mark="insert"):
958 text = self.text
959 top, bot = self.getwindowlines()
960 lineno = self.getlineno(mark)
961 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000962 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000963 text.yview(float(newtop))
964
965 def getwindowlines(self):
966 text = self.text
967 top = self.getlineno("@0,0")
968 bot = self.getlineno("@0,65535")
969 if top == bot and text.winfo_height() == 1:
970 # Geometry manager hasn't run yet
971 height = int(text['height'])
972 bot = top + height - 1
973 return top, bot
974
975 def getlineno(self, mark="insert"):
976 text = self.text
977 return int(float(text.index(mark)))
978
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000979 def get_geometry(self):
980 "Return (width, height, x, y)"
981 geom = self.top.wm_geometry()
982 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000983 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000984
David Scherer7aced172000-08-15 01:13:23 +0000985 def close_event(self, event):
986 self.close()
987
988 def maybesave(self):
989 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000990 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000991 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000992 self.top.deiconify()
993 self.top.lower()
994 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000995 return self.io.maybesave()
996
997 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000998 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000999 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001000 self._close()
1001 return reply
1002
1003 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001004 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001005 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001006 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001007 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001008 self.io.close()
1009 self.io = None
1010 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001011 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001012 self.color.close(False)
1013 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001014 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001015 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001016 self.per.close()
1017 self.per = None
1018 self.top.destroy()
1019 if self.close_hook:
1020 # unless override: unregister from flist, terminate if last window
1021 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001022
1023 def load_extensions(self):
1024 self.extensions = {}
1025 self.load_standard_extensions()
1026
1027 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001028 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001029 if hasattr(ins, "close"):
1030 ins.close()
1031 self.extensions = {}
1032
1033 def load_standard_extensions(self):
1034 for name in self.get_standard_extension_names():
1035 try:
1036 self.load_extension(name)
1037 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001038 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001039 traceback.print_exc()
1040
1041 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001042 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001043
1044 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001045 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001046 try:
1047 mod = importlib.import_module('.' + name, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001048 except (ImportError, TypeError):
Brett Cannonaef82d32012-04-14 20:44:23 -04001049 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001050 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001051 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001052 raise
David Scherer7aced172000-08-15 01:13:23 +00001053 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001054 keydefs = idleConf.GetExtensionBindings(name)
1055 if hasattr(cls, "menudefs"):
1056 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001057 ins = cls(self)
1058 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001059 if keydefs:
1060 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001061 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001062 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001063 while methodname[:1] == '<':
1064 methodname = methodname[1:]
1065 while methodname[-1:] == '>':
1066 methodname = methodname[:-1]
1067 methodname = methodname + "_event"
1068 if hasattr(ins, methodname):
1069 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001070
1071 def apply_bindings(self, keydefs=None):
1072 if keydefs is None:
1073 keydefs = self.Bindings.default_keydefs
1074 text = self.text
1075 text.keydefs = keydefs
1076 for event, keylist in keydefs.items():
1077 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001078 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001079
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001080 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001081 """Add appropriate entries to the menus and submenus
1082
1083 Menus that are absent or None in self.menudict are ignored.
1084 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001085 if menudefs is None:
1086 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001087 if keydefs is None:
1088 keydefs = self.Bindings.default_keydefs
1089 menudict = self.menudict
1090 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001091 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001092 menu = menudict.get(mname)
1093 if not menu:
1094 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001095 for entry in entrylist:
1096 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001097 menu.add_separator()
1098 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001099 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001100 checkbutton = (label[:1] == '!')
1101 if checkbutton:
1102 label = label[1:]
1103 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001104 accelerator = get_accelerator(keydefs, eventname)
1105 def command(text=text, eventname=eventname):
1106 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001107 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001108 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001109 menu.add_checkbutton(label=label, underline=underline,
1110 command=command, accelerator=accelerator,
1111 variable=var)
1112 else:
1113 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001114 command=command,
1115 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001116
1117 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001118 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001119 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001120 value = var.get()
1121 return value
1122 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001123 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001124
1125 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001126 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001127 if var:
1128 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001129 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001130 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001131
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001132 def get_var_obj(self, name, vartype=None):
1133 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001134 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001135 # create a Tkinter variable object with self.text as master:
1136 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001137 return var
1138
1139 # Tk implementations of "virtual text methods" -- each platform
1140 # reusing IDLE's support code needs to define these for its GUI's
1141 # flavor of widget.
1142
1143 # Is character at text_index in a Python string? Return 0 for
1144 # "guaranteed no", true for anything else. This info is expensive
1145 # to compute ab initio, but is probably already known by the
1146 # platform's colorizer.
1147
1148 def is_char_in_string(self, text_index):
1149 if self.color:
1150 # Return true iff colorizer hasn't (re)gotten this far
1151 # yet, or the character is tagged as being in a string
1152 return self.text.tag_prevrange("TODO", text_index) or \
1153 "STRING" in self.text.tag_names(text_index)
1154 else:
1155 # The colorizer is missing: assume the worst
1156 return 1
1157
1158 # If a selection is defined in the text widget, return (start,
1159 # end) as Tkinter text indices, otherwise return (None, None)
1160 def get_selection_indices(self):
1161 try:
1162 first = self.text.index("sel.first")
1163 last = self.text.index("sel.last")
1164 return first, last
1165 except TclError:
1166 return None, None
1167
1168 # Return the text widget's current view of what a tab stop means
1169 # (equivalent width in spaces).
1170
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001171 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001172 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1173 return int(current)
1174
1175 # Set the text widget's current view of what a tab stop means.
1176
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001177 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001178 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001179 if self.get_tk_tabwidth() != newtabwidth:
1180 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001181 pixels = text.tk.call("font", "measure", text["font"],
1182 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001183 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001184 text.configure(tabs=pixels)
1185
Guido van Rossum33d26892007-08-05 15:29:28 +00001186### begin autoindent code ### (configuration was moved to beginning of class)
1187
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001188 def set_indentation_params(self, is_py_src, guess=True):
1189 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001190 i = self.guess_indent()
1191 if 2 <= i <= 8:
1192 self.indentwidth = i
1193 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001194 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001195 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001196
1197 def smart_backspace_event(self, event):
1198 text = self.text
1199 first, last = self.get_selection_indices()
1200 if first and last:
1201 text.delete(first, last)
1202 text.mark_set("insert", first)
1203 return "break"
1204 # Delete whitespace left, until hitting a real char or closest
1205 # preceding virtual tab stop.
1206 chars = text.get("insert linestart", "insert")
1207 if chars == '':
1208 if text.compare("insert", ">", "1.0"):
1209 # easy: delete preceding newline
1210 text.delete("insert-1c")
1211 else:
1212 text.bell() # at start of buffer
1213 return "break"
1214 if chars[-1] not in " \t":
1215 # easy: delete preceding real char
1216 text.delete("insert-1c")
1217 return "break"
1218 # Ick. It may require *inserting* spaces if we back up over a
1219 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001220 tabwidth = self.tabwidth
1221 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001222 assert have > 0
1223 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001224 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001225 if self.context_use_ps1:
1226 last_line_of_prompt = sys.ps1.split('\n')[-1]
1227 else:
1228 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001229 ncharsdeleted = 0
1230 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001231 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001232 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001233 chars = chars[:-1]
1234 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001235 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001236 if have <= want or chars[-1] not in " \t":
1237 break
1238 text.undo_block_start()
1239 text.delete("insert-%dc" % ncharsdeleted, "insert")
1240 if have < want:
1241 text.insert("insert", ' ' * (want - have))
1242 text.undo_block_stop()
1243 return "break"
1244
1245 def smart_indent_event(self, event):
1246 # if intraline selection:
1247 # delete it
1248 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001249 # do indent-region
1250 # else:
1251 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001252 text = self.text
1253 first, last = self.get_selection_indices()
1254 text.undo_block_start()
1255 try:
1256 if first and last:
1257 if index2line(first) != index2line(last):
1258 return self.indent_region_event(event)
1259 text.delete(first, last)
1260 text.mark_set("insert", first)
1261 prefix = text.get("insert linestart", "insert")
1262 raw, effective = classifyws(prefix, self.tabwidth)
1263 if raw == len(prefix):
1264 # only whitespace to the left
1265 self.reindent_to(effective + self.indentwidth)
1266 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001267 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001268 if self.usetabs:
1269 pad = '\t'
1270 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001271 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001272 n = self.indentwidth
1273 pad = ' ' * (n - effective % n)
1274 text.insert("insert", pad)
1275 text.see("insert")
1276 return "break"
1277 finally:
1278 text.undo_block_stop()
1279
1280 def newline_and_indent_event(self, event):
1281 text = self.text
1282 first, last = self.get_selection_indices()
1283 text.undo_block_start()
1284 try:
1285 if first and last:
1286 text.delete(first, last)
1287 text.mark_set("insert", first)
1288 line = text.get("insert linestart", "insert")
1289 i, n = 0, len(line)
1290 while i < n and line[i] in " \t":
1291 i = i+1
1292 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001293 # the cursor is in or at leading indentation in a continuation
1294 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001295 text.insert("insert linestart", '\n')
1296 return "break"
1297 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001298 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001299 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001300 last_line_of_prompt = sys.ps1.split('\n')[-1]
1301 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001302 line = line[:-1]
1303 i = i+1
1304 if i:
1305 text.delete("insert - %d chars" % i, "insert")
1306 # strip whitespace after insert point
1307 while text.get("insert") in " \t":
1308 text.delete("insert")
1309 # start new line
1310 text.insert("insert", '\n')
1311
1312 # adjust indentation for continuations and block
1313 # open/close first need to find the last stmt
1314 lno = index2line(text.index('insert'))
1315 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001316 if not self.context_use_ps1:
1317 for context in self.num_context_lines:
1318 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001319 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001320 rawtext = text.get(startatindex, "insert")
1321 y.set_str(rawtext)
1322 bod = y.find_good_parse_start(
1323 self.context_use_ps1,
1324 self._build_char_in_string_func(startatindex))
1325 if bod is not None or startat == 1:
1326 break
1327 y.set_lo(bod or 0)
1328 else:
1329 r = text.tag_prevrange("console", "insert")
1330 if r:
1331 startatindex = r[1]
1332 else:
1333 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001334 rawtext = text.get(startatindex, "insert")
1335 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001336 y.set_lo(0)
1337
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001338 c = y.get_continuation_type()
1339 if c != PyParse.C_NONE:
1340 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001341 if c == PyParse.C_STRING_FIRST_LINE:
1342 # after the first line of a string; do not indent at all
1343 pass
1344 elif c == PyParse.C_STRING_NEXT_LINES:
1345 # inside a string which started before this line;
1346 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001347 text.insert("insert", indent)
1348 elif c == PyParse.C_BRACKET:
1349 # line up with the first (if any) element of the
1350 # last open bracket structure; else indent one
1351 # level beyond the indent of the line with the
1352 # last open bracket
1353 self.reindent_to(y.compute_bracket_indent())
1354 elif c == PyParse.C_BACKSLASH:
1355 # if more than one line in this stmt already, just
1356 # mimic the current indent; else if initial line
1357 # has a start on an assignment stmt, indent to
1358 # beyond leftmost =; else to beyond first chunk of
1359 # non-whitespace on initial line
1360 if y.get_num_lines_in_stmt() > 1:
1361 text.insert("insert", indent)
1362 else:
1363 self.reindent_to(y.compute_backslash_indent())
1364 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001365 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001366 return "break"
1367
1368 # This line starts a brand new stmt; indent relative to
1369 # indentation of initial line of closest preceding
1370 # interesting stmt.
1371 indent = y.get_base_indent_string()
1372 text.insert("insert", indent)
1373 if y.is_block_opener():
1374 self.smart_indent_event(event)
1375 elif indent and y.is_block_closer():
1376 self.smart_backspace_event(event)
1377 return "break"
1378 finally:
1379 text.see("insert")
1380 text.undo_block_stop()
1381
Martin Panter7462b6492015-11-02 03:37:02 +00001382 # Our editwin provides an is_char_in_string function that works
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001383 # with a Tk text index, but PyParse only knows about offsets into
1384 # a string. This builds a function for PyParse that accepts an
1385 # offset.
1386
1387 def _build_char_in_string_func(self, startindex):
1388 def inner(offset, _startindex=startindex,
1389 _icis=self.is_char_in_string):
1390 return _icis(_startindex + "+%dc" % offset)
1391 return inner
1392
1393 def indent_region_event(self, event):
1394 head, tail, chars, lines = self.get_region()
1395 for pos in range(len(lines)):
1396 line = lines[pos]
1397 if line:
1398 raw, effective = classifyws(line, self.tabwidth)
1399 effective = effective + self.indentwidth
1400 lines[pos] = self._make_blanks(effective) + line[raw:]
1401 self.set_region(head, tail, chars, lines)
1402 return "break"
1403
1404 def dedent_region_event(self, event):
1405 head, tail, chars, lines = self.get_region()
1406 for pos in range(len(lines)):
1407 line = lines[pos]
1408 if line:
1409 raw, effective = classifyws(line, self.tabwidth)
1410 effective = max(effective - self.indentwidth, 0)
1411 lines[pos] = self._make_blanks(effective) + line[raw:]
1412 self.set_region(head, tail, chars, lines)
1413 return "break"
1414
1415 def comment_region_event(self, event):
1416 head, tail, chars, lines = self.get_region()
1417 for pos in range(len(lines) - 1):
1418 line = lines[pos]
1419 lines[pos] = '##' + line
1420 self.set_region(head, tail, chars, lines)
1421
1422 def uncomment_region_event(self, event):
1423 head, tail, chars, lines = self.get_region()
1424 for pos in range(len(lines)):
1425 line = lines[pos]
1426 if not line:
1427 continue
1428 if line[:2] == '##':
1429 line = line[2:]
1430 elif line[:1] == '#':
1431 line = line[1:]
1432 lines[pos] = line
1433 self.set_region(head, tail, chars, lines)
1434
1435 def tabify_region_event(self, event):
1436 head, tail, chars, lines = self.get_region()
1437 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001438 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001439 for pos in range(len(lines)):
1440 line = lines[pos]
1441 if line:
1442 raw, effective = classifyws(line, tabwidth)
1443 ntabs, nspaces = divmod(effective, tabwidth)
1444 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1445 self.set_region(head, tail, chars, lines)
1446
1447 def untabify_region_event(self, event):
1448 head, tail, chars, lines = self.get_region()
1449 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001450 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001451 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001452 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001453 self.set_region(head, tail, chars, lines)
1454
1455 def toggle_tabs_event(self, event):
1456 if self.askyesno(
1457 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001458 "Turn tabs " + ("on", "off")[self.usetabs] +
1459 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001460 ("will be", "remains at")[self.usetabs] + " 8." +
1461 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001462 parent=self.text):
1463 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001464 # Try to prevent inconsistent indentation.
1465 # User must change indent width manually after using tabs.
1466 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001467 return "break"
1468
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001469 # XXX this isn't bound to anything -- see tabwidth comments
1470## def change_tabwidth_event(self, event):
1471## new = self._asktabwidth()
1472## if new != self.tabwidth:
1473## self.tabwidth = new
1474## self.set_indentation_params(0, guess=0)
1475## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001476
1477 def change_indentwidth_event(self, event):
1478 new = self.askinteger(
1479 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001480 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001481 parent=self.text,
1482 initialvalue=self.indentwidth,
1483 minvalue=2,
1484 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001485 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001486 self.indentwidth = new
1487 return "break"
1488
1489 def get_region(self):
1490 text = self.text
1491 first, last = self.get_selection_indices()
1492 if first and last:
1493 head = text.index(first + " linestart")
1494 tail = text.index(last + "-1c lineend +1c")
1495 else:
1496 head = text.index("insert linestart")
1497 tail = text.index("insert lineend +1c")
1498 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001499 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001500 return head, tail, chars, lines
1501
1502 def set_region(self, head, tail, chars, lines):
1503 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001504 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001505 if newchars == chars:
1506 text.bell()
1507 return
1508 text.tag_remove("sel", "1.0", "end")
1509 text.mark_set("insert", head)
1510 text.undo_block_start()
1511 text.delete(head, tail)
1512 text.insert(head, newchars)
1513 text.undo_block_stop()
1514 text.tag_add("sel", head, "insert")
1515
1516 # Make string that displays as n leading blanks.
1517
1518 def _make_blanks(self, n):
1519 if self.usetabs:
1520 ntabs, nspaces = divmod(n, self.tabwidth)
1521 return '\t' * ntabs + ' ' * nspaces
1522 else:
1523 return ' ' * n
1524
1525 # Delete from beginning of line to insert point, then reinsert
1526 # column logical (meaning use tabs if appropriate) spaces.
1527
1528 def reindent_to(self, column):
1529 text = self.text
1530 text.undo_block_start()
1531 if text.compare("insert linestart", "!=", "insert"):
1532 text.delete("insert linestart", "insert")
1533 if column:
1534 text.insert("insert", self._make_blanks(column))
1535 text.undo_block_stop()
1536
1537 def _asktabwidth(self):
1538 return self.askinteger(
1539 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001540 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001541 parent=self.text,
1542 initialvalue=self.indentwidth,
1543 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001544 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001545
1546 # Guess indentwidth from text content.
1547 # Return guessed indentwidth. This should not be believed unless
1548 # it's in a reasonable range (e.g., it will be 0 if no indented
1549 # blocks are found).
1550
1551 def guess_indent(self):
1552 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1553 if opener and indented:
1554 raw, indentsmall = classifyws(opener, self.tabwidth)
1555 raw, indentlarge = classifyws(indented, self.tabwidth)
1556 else:
1557 indentsmall = indentlarge = 0
1558 return indentlarge - indentsmall
1559
1560# "line.col" -> line, as an int
1561def index2line(index):
1562 return int(float(index))
1563
1564# Look at the leading whitespace in s.
1565# Return pair (# of leading ws characters,
1566# effective # of leading blanks after expanding
1567# tabs to width tabwidth)
1568
1569def classifyws(s, tabwidth):
1570 raw = effective = 0
1571 for ch in s:
1572 if ch == ' ':
1573 raw = raw + 1
1574 effective = effective + 1
1575 elif ch == '\t':
1576 raw = raw + 1
1577 effective = (effective // tabwidth + 1) * tabwidth
1578 else:
1579 break
1580 return raw, effective
1581
1582import tokenize
1583_tokenize = tokenize
1584del tokenize
1585
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001586class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001587
1588 # .run() chews over the Text widget, looking for a block opener
1589 # and the stmt following it. Returns a pair,
1590 # (line containing block opener, line containing stmt)
1591 # Either or both may be None.
1592
1593 def __init__(self, text, tabwidth):
1594 self.text = text
1595 self.tabwidth = tabwidth
1596 self.i = self.finished = 0
1597 self.blkopenline = self.indentedline = None
1598
1599 def readline(self):
1600 if self.finished:
1601 return ""
1602 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001603 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001604 if self.text.compare(mark, ">=", "end"):
1605 return ""
1606 return self.text.get(mark, mark + " lineend+1c")
1607
1608 def tokeneater(self, type, token, start, end, line,
1609 INDENT=_tokenize.INDENT,
1610 NAME=_tokenize.NAME,
1611 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1612 if self.finished:
1613 pass
1614 elif type == NAME and token in OPENERS:
1615 self.blkopenline = line
1616 elif type == INDENT and self.blkopenline:
1617 self.indentedline = line
1618 self.finished = 1
1619
1620 def run(self):
1621 save_tabsize = _tokenize.tabsize
1622 _tokenize.tabsize = self.tabwidth
1623 try:
1624 try:
Trent Nelson428de652008-03-18 22:41:35 +00001625 tokens = _tokenize.generate_tokens(self.readline)
1626 for token in tokens:
1627 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001628 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001629 # since we cut off the tokenizer early, we can trigger
1630 # spurious errors
1631 pass
1632 finally:
1633 _tokenize.tabsize = save_tabsize
1634 return self.blkopenline, self.indentedline
1635
1636### end autoindent code ###
1637
David Scherer7aced172000-08-15 01:13:23 +00001638def prepstr(s):
1639 # Helper to extract the underscore from a string, e.g.
1640 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001641 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001642 if i >= 0:
1643 s = s[:i] + s[i+1:]
1644 return i, s
1645
1646
1647keynames = {
1648 'bracketleft': '[',
1649 'bracketright': ']',
1650 'slash': '/',
1651}
1652
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001653def get_accelerator(keydefs, eventname):
1654 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001655 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1656 # if not keylist:
Ned Deilyb7601672014-03-27 20:49:14 -07001657 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001658 "<<open-module>>",
1659 "<<goto-line>>",
1660 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001661 return ""
1662 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001663 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001664 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1665 s = re.sub("Key-", "", s)
1666 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1667 s = re.sub("Control-", "Ctrl-", s)
1668 s = re.sub("-", "+", s)
1669 s = re.sub("><", " ", s)
1670 s = re.sub("<", "", s)
1671 s = re.sub(">", "", s)
1672 return s
1673
1674
1675def fixwordbreaks(root):
1676 # Make sure that Tk's double-click and next/previous word
1677 # operations use our definition of a word (i.e. an identifier)
1678 tk = root.tk
1679 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1680 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1681 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1682
1683
Terry Jan Reedycd567362014-10-17 01:31:35 -04001684def _editor_window(parent): # htest #
1685 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001686 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001687 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001688 if sys.argv[1:]:
1689 filename = sys.argv[1]
1690 else:
1691 filename = None
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001692 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001693 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001694 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001695 # Does not stop error, neither does following
1696 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001697
1698if __name__ == '__main__':
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001699 from idlelib.idle_test.htest import run
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04001700 run(_editor_window)