blob: ae475cb9f9a6a59e99b53fbbe49315c80017b26a [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
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04009import tokenize
10import traceback
11import webbrowser
12
Georg Brandl14fc4272008-05-17 18:39:55 +000013from tkinter import *
Terry Jan Reedy01e35752016-06-10 18:19:21 -040014from tkinter.ttk import Scrollbar
Georg Brandl14fc4272008-05-17 18:39:55 +000015import tkinter.simpledialog as tkSimpleDialog
16import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +000017
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040018from idlelib.config import idleConf
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040019from idlelib import configdialog
20from idlelib import grep
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040021from idlelib import help
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040022from idlelib import help_about
23from idlelib import macosx
24from idlelib.multicall import MultiCallCreator
25from idlelib import pyparse
26from idlelib import query
27from idlelib import replace
28from idlelib import search
29from idlelib import textview
30from idlelib import windows
David Scherer7aced172000-08-15 01:13:23 +000031
32# The default tab setting for a Text widget, in average-width characters.
33TK_TABWIDTH_DEFAULT = 8
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -040034_py_version = ' (%s)' % platform.python_version()
35
Terry Jan Reedy7c153412016-06-26 17:48:02 -040036
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000037def _sphinx_version():
38 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
39 major, minor, micro, level, serial = sys.version_info
40 release = '%s%s' % (major, minor)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020041 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000042 if level == 'candidate':
43 release += 'rc%s' % (serial,)
44 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000045 release += '%s%s' % (level[0], serial)
46 return release
47
Terry Jan Reedye91e7632012-02-05 15:14:20 -050048
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000049class EditorWindow(object):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040050 from idlelib.percolator import Percolator
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -040051 from idlelib.colorizer import ColorDelegator, color_config
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040052 from idlelib.undo import UndoDelegator
Terry Jan Reedy7c153412016-06-26 17:48:02 -040053 from idlelib.iomenu import IOBinding, encoding
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040054 from idlelib import mainmenu
Guilherme Polo5424b0a2008-05-25 15:26:44 +000055 from tkinter import Toplevel
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040056 from idlelib.statusbar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000057
Terry Jan Reedy7c153412016-06-26 17:48:02 -040058 filesystemencoding = sys.getfilesystemencoding() # for file names
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000059 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000060
61 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000062 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +010063 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000064 if sys.platform.count('linux'):
65 # look for html docs in a couple of standard places
66 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
67 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
68 dochome = '/var/www/html/python/index.html'
69 else:
70 basepath = '/usr/share/doc/' # standard location
71 dochome = os.path.join(basepath, pyver,
72 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000073 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +010074 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000075 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +000076 if os.path.isfile(chmfile):
77 dochome = chmfile
Ned Deilyb7601672014-03-27 20:49:14 -070078 elif sys.platform == 'darwin':
79 # documentation may be stored inside a python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +010080 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +000081 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000082 dochome = os.path.normpath(dochome)
83 if os.path.isfile(dochome):
84 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +000085 if sys.platform == 'darwin':
86 # Safari requires real file:-URLs
87 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000088 else:
Terry Jan Reedyb6e17782014-09-19 22:54:15 -040089 EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
David Scherer7aced172000-08-15 01:13:23 +000090 self.flist = flist
91 root = root or flist.root
92 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +000093 try:
94 sys.ps1
95 except AttributeError:
96 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +000097 self.menubar = Menu(root)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040098 self.top = top = windows.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000099 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000100 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200101 #self.top.instance_dict makes flist.inversedict available to
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400102 #configdialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000103 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000104 else:
105 self.tkinter_vars = {} # keys: Tkinter event names
106 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000107 self.top.instance_dict = {}
108 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000109 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000110 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000111 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200112 self.width = idleConf.GetOption('main', 'EditorWindow',
113 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000114 text_options = {
115 'name': 'text',
116 'padx': 5,
117 'wrap': 'none',
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500118 'highlightthickness': 0,
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000119 'width': self.width,
Terry Jan Reedy1080d132016-06-09 21:09:15 -0400120 'tabstyle': 'wordprocessor', # new in 8.5
121 'height': idleConf.GetOption(
122 'main', 'EditorWindow', 'height', type='int'),
123 }
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000124 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000125 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000126
127 self.createmenubar()
128 self.apply_bindings()
129
130 self.top.protocol("WM_DELETE_WINDOW", self.close)
131 self.top.bind("<<close-window>>", self.close_event)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400132 if macosx.isAquaTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000133 # Command-W on editorwindows doesn't work without this.
134 text.bind('<<close-window>>', self.close_event)
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400135 # Some OS X systems have only one mouse button, so use
136 # control-click for popup context menus there. For two
137 # buttons, AquaTk defines <2> as the right button, not <3>.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000138 text.bind("<Control-Button-1>",self.right_menu_event)
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400139 text.bind("<2>", self.right_menu_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000140 else:
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400141 # Elsewhere, use right-click for popup menus.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000142 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000143 text.bind("<<cut>>", self.cut)
144 text.bind("<<copy>>", self.copy)
145 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000146 text.bind("<<center-insert>>", self.center_insert_event)
147 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000148 text.bind("<<python-docs>>", self.python_docs)
149 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000150 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000151 text.bind("<<open-module>>", self.open_module)
152 text.bind("<<do-nothing>>", lambda event: "break")
153 text.bind("<<select-all>>", self.select_all)
154 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000155 text.bind("<<find>>", self.find_event)
156 text.bind("<<find-again>>", self.find_again_event)
157 text.bind("<<find-in-files>>", self.find_in_files_event)
158 text.bind("<<find-selection>>", self.find_selection_event)
159 text.bind("<<replace>>", self.replace_event)
160 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000161 text.bind("<<smart-backspace>>",self.smart_backspace_event)
162 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
163 text.bind("<<smart-indent>>",self.smart_indent_event)
164 text.bind("<<indent-region>>",self.indent_region_event)
165 text.bind("<<dedent-region>>",self.dedent_region_event)
166 text.bind("<<comment-region>>",self.comment_region_event)
167 text.bind("<<uncomment-region>>",self.uncomment_region_event)
168 text.bind("<<tabify-region>>",self.tabify_region_event)
169 text.bind("<<untabify-region>>",self.untabify_region_event)
170 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
171 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000172 text.bind("<Left>", self.move_at_edge_if_selection(0))
173 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000174 text.bind("<<del-word-left>>", self.del_word_left)
175 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000176 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000177
David Scherer7aced172000-08-15 01:13:23 +0000178 if flist:
179 flist.inversedict[self] = key
180 if key:
181 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000182 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000183 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
184 text.bind("<<open-class-browser>>", self.open_class_browser)
185 text.bind("<<open-path-browser>>", self.open_path_browser)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400186 text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
David Scherer7aced172000-08-15 01:13:23 +0000187
Steven M. Gava898a3652001-10-07 11:10:44 +0000188 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000189 vbar['command'] = text.yview
190 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000191 text['yscrollcommand'] = vbar.set
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400192 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000193 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
194 text.pack(side=TOP, fill=BOTH, expand=1)
195 text.focus_set()
196
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000197 # usetabs true -> literal tab characters are used by indent and
198 # dedent cmds, possibly mixed with spaces if
199 # indentwidth is not a multiple of tabwidth,
200 # which will cause Tabnanny to nag!
201 # false -> tab characters are converted to spaces by indent
202 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000203 # Although use-spaces=0 can be configured manually in config-main.def,
204 # configuration of tabs v. spaces is not supported in the configuration
205 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200206 usespaces = idleConf.GetOption('main', 'Indent',
207 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000208 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000209
210 # tabwidth is the display width of a literal tab character.
211 # CAUTION: telling Tk to use anything other than its default
212 # tab setting causes it to use an entirely different tabbing algorithm,
213 # treating tab stops as fixed distances from the left margin.
214 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000215 self.tabwidth = 8 # must remain 8 until Tk is fixed.
216
217 # indentwidth is the number of screen characters per indent level.
218 # The recommended Python indentation is four spaces.
219 self.indentwidth = self.tabwidth
220 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000221
222 # If context_use_ps1 is true, parsing searches back for a ps1 line;
223 # else searches for a popular (if, def, ...) Python stmt.
224 self.context_use_ps1 = False
225
226 # When searching backwards for a reliable place to begin parsing,
227 # first start num_context_lines[0] lines back, then
228 # num_context_lines[1] lines back if that didn't work, and so on.
229 # The last value should be huge (larger than the # of lines in a
230 # conceivable file).
231 # Making the initial values larger slows things down more often.
232 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000233 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000234 self.undo = undo = self.UndoDelegator()
235 per.insertfilter(undo)
236 text.undo_block_start = undo.undo_block_start
237 text.undo_block_stop = undo.undo_block_stop
238 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000239 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000240 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000241 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000242 self.good_load = False
243 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000244 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000245 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000246 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000247 if io.loadfile(filename):
248 self.good_load = True
249 is_py_src = self.ispythonsource(filename)
250 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000251 else:
252 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500253 self.good_load = True
254
Christian Heimesa156e092008-02-16 07:38:31 +0000255 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000256 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000257 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000258 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000259 menu = self.menudict.get('windows')
260 if menu:
261 end = menu.index("end")
262 if end is None:
263 end = -1
264 if end >= 0:
265 menu.add_separator()
266 end = end + 1
267 self.wmenu_end = end
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400268 windows.register_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000269
270 # Some abstractions so IDLE extensions are cross-IDE
271 self.askyesno = tkMessageBox.askyesno
272 self.askinteger = tkSimpleDialog.askinteger
273 self.showerror = tkMessageBox.showerror
274
Martin v. Löwis307021f2005-11-27 16:59:04 +0000275 def _filename_to_unicode(self, filename):
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400276 """Return filename as BMP unicode so diplayable in Tk."""
277 # Decode bytes to unicode.
278 if isinstance(filename, bytes):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000279 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400280 filename = filename.decode(self.filesystemencoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000281 except UnicodeDecodeError:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000282 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400283 filename = filename.decode(self.encoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000284 except UnicodeDecodeError:
285 # byte-to-byte conversion
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400286 filename = filename.decode('iso8859-1')
287 # Replace non-BMP char with diamond questionmark.
288 return re.sub('[\U00010000-\U0010FFFF]', '\ufffd', filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000289
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000290 def new_callback(self, event):
291 dirname, basename = self.io.defaultfilename()
292 self.flist.new(dirname)
293 return "break"
294
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000295 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400296 if (event.state & 4) != 0 and event.keysym == "Home":
297 # state&4==Control. If <Control-Home>, use the Tk binding.
298 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000299 if self.text.index("iomark") and \
300 self.text.compare("iomark", "<=", "insert lineend") and \
301 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400302 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000303 insertpt = int(self.text.index("iomark").split(".")[1])
304 else:
305 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000306 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000307 if line[insertpt] not in (' ','\t'):
308 break
309 else:
310 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000311 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000312 if insertpt == lineat:
313 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000314 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000315 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400316 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000317 self.text.tag_remove("sel", "1.0", "end")
318 else:
319 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200320 # there was no previous selection
321 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400322 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200323 if self.text.compare(self.text.index("sel.first"), "<",
324 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400325 self.text.mark_set("my_anchor", "sel.first") # extend back
326 else:
327 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000328 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400329 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000330 if self.text.compare(first,">",last):
331 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000332 self.text.tag_remove("sel", "1.0", "end")
333 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000334 self.text.mark_set("insert", dest)
335 self.text.see("insert")
336 return "break"
337
David Scherer7aced172000-08-15 01:13:23 +0000338 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000339 self.status_bar = self.MultiStatusBar(self.top)
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500340 sep = Frame(self.top, height=1, borderwidth=1, background='grey75')
Ned Deilyb7601672014-03-27 20:49:14 -0700341 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000342 # Insert some padding to avoid obscuring some of the statusbar
343 # by the resize widget.
344 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000345 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
346 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
347 self.status_bar.pack(side=BOTTOM, fill=X)
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500348 sep.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000349 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
350 self.text.event_add("<<set-line-and-column>>",
351 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000352 self.text.after_idle(self.set_line_and_column)
353
354 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000355 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000356 self.status_bar.set_label('column', 'Col: %s' % column)
357 self.status_bar.set_label('line', 'Ln: %s' % line)
358
David Scherer7aced172000-08-15 01:13:23 +0000359 menu_specs = [
360 ("file", "_File"),
361 ("edit", "_Edit"),
362 ("format", "F_ormat"),
363 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000364 ("options", "_Options"),
Ned Deilyccb416f2015-01-17 21:06:27 -0800365 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000366 ("help", "_Help"),
367 ]
368
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000369
David Scherer7aced172000-08-15 01:13:23 +0000370 def createmenubar(self):
371 mbar = self.menubar
372 self.menudict = menudict = {}
373 for name, label in self.menu_specs:
374 underline, label = prepstr(label)
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400375 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000376 mbar.add_cascade(label=label, menu=menu, underline=underline)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400377 if macosx.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000378 # Insert the application menu
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400379 menudict['application'] = menu = Menu(mbar, name='apple',
380 tearoff=0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000381 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000382 self.fill_menus()
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400383 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000384 self.menudict['file'].insert_cascade(3, label='Recent Files',
385 underline=0,
386 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000387 self.base_helpmenu_length = self.menudict['help'].index(END)
388 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000389
390 def postwindowsmenu(self):
391 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000392 menu = self.menudict['windows']
393 end = menu.index("end")
394 if end is None:
395 end = -1
396 if end > self.wmenu_end:
397 menu.delete(self.wmenu_end+1, end)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400398 windows.add_windows_to_menu(menu)
David Scherer7aced172000-08-15 01:13:23 +0000399
400 rmenu = None
401
402 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000403 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
404 if not self.rmenu:
405 self.make_rmenu()
406 rmenu = self.rmenu
407 self.event = event
408 iswin = sys.platform[:3] == 'win'
409 if iswin:
410 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200411
Roger Serwy6b2918a2013-04-07 12:15:52 -0500412 for item in self.rmenu_specs:
413 try:
414 label, eventname, verify_state = item
415 except ValueError: # see issue1207589
416 continue
417
Andrew Svetlovd1837672012-11-01 22:41:19 +0200418 if verify_state is None:
419 continue
420 state = getattr(self, verify_state)()
421 rmenu.entryconfigure(label, state=state)
422
423
David Scherer7aced172000-08-15 01:13:23 +0000424 rmenu.tk_popup(event.x_root, event.y_root)
425 if iswin:
426 self.text.config(cursor="ibeam")
427
428 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200429 # ("Label", "<<virtual-event>>", "statefuncname"), ...
430 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000431 ]
432
433 def make_rmenu(self):
434 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500435 for item in self.rmenu_specs:
436 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200437 if label is not None:
438 def command(text=self.text, eventname=eventname):
439 text.event_generate(eventname)
440 rmenu.add_command(label=label, command=command)
441 else:
442 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000443 self.rmenu = rmenu
444
Andrew Svetlovd1837672012-11-01 22:41:19 +0200445 def rmenu_check_cut(self):
446 return self.rmenu_check_copy()
447
448 def rmenu_check_copy(self):
449 try:
450 indx = self.text.index('sel.first')
451 except TclError:
452 return 'disabled'
453 else:
454 return 'normal' if indx else 'disabled'
455
456 def rmenu_check_paste(self):
457 try:
458 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
459 except TclError:
460 return 'disabled'
461 else:
462 return 'normal'
463
David Scherer7aced172000-08-15 01:13:23 +0000464 def about_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400465 "Handle Help 'About IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400466 # Synchronize with macosx.overrideRootMenu.about_dialog.
467 help_about.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000468
Steven M. Gava3b55a892001-11-21 05:56:26 +0000469 def config_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400470 "Handle Options 'Configure IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400471 # Synchronize with macosx.overrideRootMenu.config_dialog.
472 configdialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400473
David Scherer7aced172000-08-15 01:13:23 +0000474 def help_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400475 "Handle Help 'IDLE Help' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400476 # Synchronize with macosx.overrideRootMenu.help_dialog.
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500477 if self.root:
478 parent = self.root
479 else:
480 parent = self.top
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400481 help.show_idlehelp(parent)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000482
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000483 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000484 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000485 try:
486 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200487 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000488 tkMessageBox.showerror(title='Document Start Failure',
489 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000490 else:
491 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000492 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000493
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000494 def cut(self,event):
495 self.text.event_generate("<<Cut>>")
496 return "break"
497
498 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000499 if not self.text.tag_ranges("sel"):
500 # There is no selection, so do nothing and maybe interrupt.
501 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000502 self.text.event_generate("<<Copy>>")
503 return "break"
504
505 def paste(self,event):
506 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000507 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000508 return "break"
509
David Scherer7aced172000-08-15 01:13:23 +0000510 def select_all(self, event=None):
511 self.text.tag_add("sel", "1.0", "end-1c")
512 self.text.mark_set("insert", "1.0")
513 self.text.see("insert")
514 return "break"
515
516 def remove_selection(self, event=None):
517 self.text.tag_remove("sel", "1.0", "end")
518 self.text.see("insert")
519
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000520 def move_at_edge_if_selection(self, edge_index):
521 """Cursor move begins at start or end of selection
522
523 When a left/right cursor key is pressed create and return to Tkinter a
524 function which causes a cursor move from the associated edge of the
525 selection.
526
527 """
528 self_text_index = self.text.index
529 self_text_mark_set = self.text.mark_set
530 edges_table = ("sel.first+1c", "sel.last-1c")
531 def move_at_edge(event):
532 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
533 try:
534 self_text_index("sel.first")
535 self_text_mark_set("insert", edges_table[edge_index])
536 except TclError:
537 pass
538 return move_at_edge
539
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000540 def del_word_left(self, event):
541 self.text.event_generate('<Meta-Delete>')
542 return "break"
543
544 def del_word_right(self, event):
545 self.text.event_generate('<Meta-d>')
546 return "break"
547
Steven M. Gavac5976402002-01-04 03:06:08 +0000548 def find_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400549 search.find(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000550 return "break"
551
552 def find_again_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400553 search.find_again(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000554 return "break"
555
556 def find_selection_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400557 search.find_selection(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000558 return "break"
559
560 def find_in_files_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400561 grep.grep(self.text, self.io, self.flist)
Steven M. Gavac5976402002-01-04 03:06:08 +0000562 return "break"
563
564 def replace_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400565 replace.replace(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000566 return "break"
567
568 def goto_line_event(self, event):
569 text = self.text
570 lineno = tkSimpleDialog.askinteger("Goto",
571 "Go to line number:",parent=text)
572 if lineno is None:
573 return "break"
574 if lineno <= 0:
575 text.bell()
576 return "break"
577 text.mark_set("insert", "%d.0" % lineno)
578 text.see("insert")
579
David Scherer7aced172000-08-15 01:13:23 +0000580 def open_module(self, event=None):
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400581 """Get module name from user and open it.
582
583 Return module path or None for calls by open_class_browser
584 when latter is not invoked in named editor window.
585 """
586 # XXX This, open_class_browser, and open_path_browser
587 # would fit better in iomenu.IOBinding.
David Scherer7aced172000-08-15 01:13:23 +0000588 try:
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400589 name = self.text.get("sel.first", "sel.last").strip()
David Scherer7aced172000-08-15 01:13:23 +0000590 except TclError:
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400591 name = ''
592 file_path = query.ModuleName(
593 self.text, "Open Module",
594 "Enter the name of a Python module\n"
595 "to search on sys.path and open:",
596 name).result
597 if file_path is not None:
598 if self.flist:
599 self.flist.open(file_path)
600 else:
601 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400602 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000603
604 def open_class_browser(self, event=None):
605 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400606 if not (self.__class__.__name__ == 'PyShellEditorWindow'
607 and filename):
608 filename = self.open_module()
609 if filename is None:
610 return
David Scherer7aced172000-08-15 01:13:23 +0000611 head, tail = os.path.split(filename)
612 base, ext = os.path.splitext(tail)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400613 from idlelib import browser
614 browser.ClassBrowser(self.flist, base, [head])
David Scherer7aced172000-08-15 01:13:23 +0000615
616 def open_path_browser(self, event=None):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400617 from idlelib import pathbrowser
618 pathbrowser.PathBrowser(self.flist)
David Scherer7aced172000-08-15 01:13:23 +0000619
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400620 def open_turtle_demo(self, event = None):
621 import subprocess
622
623 cmd = [sys.executable,
624 '-c',
625 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400626 subprocess.Popen(cmd, shell=False)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400627
David Scherer7aced172000-08-15 01:13:23 +0000628 def gotoline(self, lineno):
629 if lineno is not None and lineno > 0:
630 self.text.mark_set("insert", "%d.0" % lineno)
631 self.text.tag_remove("sel", "1.0", "end")
632 self.text.tag_add("sel", "insert", "insert +1l")
633 self.center()
634
635 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000636 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000637 return True
David Scherer7aced172000-08-15 01:13:23 +0000638 base, ext = os.path.splitext(os.path.basename(filename))
639 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000640 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000641 line = self.text.get('1.0', '1.0 lineend')
642 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000643
644 def close_hook(self):
645 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000646 self.flist.unregister_maybe_terminate(self)
647 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000648
649 def set_close_hook(self, close_hook):
650 self.close_hook = close_hook
651
652 def filename_change_hook(self):
653 if self.flist:
654 self.flist.filename_changed_edit(self)
655 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000656 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000657 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000658
Christian Heimesa156e092008-02-16 07:38:31 +0000659 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000660 if self.color:
661 return
Christian Heimesa156e092008-02-16 07:38:31 +0000662 if self.ispythonsource(self.io.filename):
663 self.color = self.ColorDelegator()
664 # can add more colorizers here...
665 if self.color:
666 self.per.removefilter(self.undo)
667 self.per.insertfilter(self.color)
668 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000669
Christian Heimesa156e092008-02-16 07:38:31 +0000670 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000671 if not self.color:
672 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000673 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000674 self.per.removefilter(self.color)
675 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000676
Steven M. Gavab77d3432002-03-02 07:16:21 +0000677 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400678 "Update the color theme"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400679 # Called from self.filename_change_hook and from configdialog.py
Christian Heimesa156e092008-02-16 07:38:31 +0000680 self._rmcolorizer()
681 self._addcolorizer()
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -0400682 EditorWindow.color_config(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000683
Guido van Rossum33d26892007-08-05 15:29:28 +0000684 IDENTCHARS = string.ascii_letters + string.digits + "_"
685
686 def colorize_syntax_error(self, text, pos):
687 text.tag_add("ERROR", pos)
688 char = text.get(pos)
689 if char and char in self.IDENTCHARS:
690 text.tag_add("ERROR", pos + " wordstart", pos)
691 if '\n' == text.get(pos): # error at line end
692 text.mark_set("insert", pos)
693 else:
694 text.mark_set("insert", pos + "+1c")
695 text.see(pos)
696
Steven M. Gavab1585412002-03-12 00:21:56 +0000697 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000698 "Update the text widgets' font if it is changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400699 # Called from configdialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400700
701 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000702
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000703 def RemoveKeybindings(self):
704 "Remove the keybindings before they are changed."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400705 # Called from configdialog.py
706 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000707 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000708 self.text.event_delete(event, *keylist)
709 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000710 xkeydefs = idleConf.GetExtensionBindings(extensionName)
711 if xkeydefs:
712 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000713 self.text.event_delete(event, *keylist)
714
715 def ApplyKeybindings(self):
716 "Update the keybindings after they are changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400717 # Called from configdialog.py
718 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000719 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000720 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000721 xkeydefs = idleConf.GetExtensionBindings(extensionName)
722 if xkeydefs:
723 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000724 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000725 menuEventDict = {}
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400726 for menu in self.mainmenu.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000727 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000728 for item in menu[1]:
729 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000730 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000731 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000732 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700733 end = menu.index(END)
734 if end is None:
735 # Skip empty menus
736 continue
737 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000738 for index in range(0, end):
739 if menu.type(index) == 'command':
740 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000741 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000742 itemName = menu.entrycget(index, 'label')
743 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000744 if menubarItem in menuEventDict:
745 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000746 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000747 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000748 accel = get_accelerator(keydefs, event)
749 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000750
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000751 def set_notabs_indentwidth(self):
752 "Update the indentwidth if changed and not using tabs in this window"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400753 # Called from configdialog.py
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000754 if not self.usetabs:
755 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
756 type='int')
757
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000758 def reset_help_menu_entries(self):
759 "Update the additional help entries on the Help menu"
760 help_list = idleConf.GetAllExtraHelpSourcesList()
761 helpmenu = self.menudict['help']
762 # first delete the extra help entries, if any
763 helpmenu_length = helpmenu.index(END)
764 if helpmenu_length > self.base_helpmenu_length:
765 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
766 # then rebuild them
767 if help_list:
768 helpmenu.add_separator()
769 for entry in help_list:
770 cmd = self.__extra_help_callback(entry[1])
771 helpmenu.add_command(label=entry[0], command=cmd)
772 # and update the menu dictionary
773 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000774
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000775 def __extra_help_callback(self, helpfile):
776 "Create a callback with the helpfile value frozen at definition time"
777 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000778 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000779 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000780 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000781 try:
782 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200783 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000784 tkMessageBox.showerror(title='Document Start Failure',
785 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000786 else:
787 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000788 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000789
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000790 def update_recent_files_list(self, new_file=None):
791 "Load and update the recent files list and menus"
792 rf_list = []
793 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400794 with open(self.recent_files_path, 'r',
795 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000796 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000797 if new_file:
798 new_file = os.path.abspath(new_file) + '\n'
799 if new_file in rf_list:
800 rf_list.remove(new_file) # move to top
801 rf_list.insert(0, new_file)
802 # clean and save the recent files list
803 bad_paths = []
804 for path in rf_list:
805 if '\0' in path or not os.path.exists(path[0:-1]):
806 bad_paths.append(path)
807 rf_list = [path for path in rf_list if path not in bad_paths]
808 ulchars = "1234567890ABCDEFGHIJK"
809 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000810 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800811 with open(self.recent_files_path, 'w',
812 encoding='utf_8', errors='replace') as rf_file:
813 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200814 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800815 if not getattr(self.root, "recentfilelist_error_displayed", False):
816 self.root.recentfilelist_error_displayed = True
Terry Jan Reedy4c973e92015-10-27 03:38:02 -0400817 tkMessageBox.showwarning(title='IDLE Warning',
818 message="Cannot update File menu Recent Files list. "
819 "Your operating system says:\n%s\n"
820 "Select OK and IDLE will continue without updating."
821 % self._filename_to_unicode(str(err)),
Ned Deilyf505b742011-12-14 14:58:24 -0800822 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000823 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000824 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000825 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700826 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000827 for i, file_name in enumerate(rf_list):
828 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000829 # make unicode string to display non-ASCII chars correctly
830 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000831 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000832 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000833 command=callback,
834 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000835
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000836 def __recent_file_callback(self, file_name):
837 def open_recent_file(fn_closure=file_name):
838 self.io.open(editFile=fn_closure)
839 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000840
David Scherer7aced172000-08-15 01:13:23 +0000841 def saved_change_hook(self):
842 short = self.short_title()
843 long = self.long_title()
844 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400845 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000846 elif short:
847 title = short
848 elif long:
849 title = long
850 else:
851 title = "Untitled"
852 icon = short or long or title
853 if not self.get_saved():
854 title = "*%s*" % title
855 icon = "*%s" % icon
856 self.top.wm_title(title)
857 self.top.wm_iconname(icon)
858
859 def get_saved(self):
860 return self.undo.get_saved()
861
862 def set_saved(self, flag):
863 self.undo.set_saved(flag)
864
865 def reset_undo(self):
866 self.undo.reset_undo()
867
868 def short_title(self):
869 filename = self.io.filename
870 if filename:
871 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500872 else:
873 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000874 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400875 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000876
877 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000878 # return unicode string to display non-ASCII chars correctly
879 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000880
881 def center_insert_event(self, event):
882 self.center()
883
884 def center(self, mark="insert"):
885 text = self.text
886 top, bot = self.getwindowlines()
887 lineno = self.getlineno(mark)
888 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000889 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000890 text.yview(float(newtop))
891
892 def getwindowlines(self):
893 text = self.text
894 top = self.getlineno("@0,0")
895 bot = self.getlineno("@0,65535")
896 if top == bot and text.winfo_height() == 1:
897 # Geometry manager hasn't run yet
898 height = int(text['height'])
899 bot = top + height - 1
900 return top, bot
901
902 def getlineno(self, mark="insert"):
903 text = self.text
904 return int(float(text.index(mark)))
905
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000906 def get_geometry(self):
907 "Return (width, height, x, y)"
908 geom = self.top.wm_geometry()
909 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000910 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000911
David Scherer7aced172000-08-15 01:13:23 +0000912 def close_event(self, event):
913 self.close()
914
915 def maybesave(self):
916 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000917 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000918 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000919 self.top.deiconify()
920 self.top.lower()
921 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000922 return self.io.maybesave()
923
924 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000925 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000926 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000927 self._close()
928 return reply
929
930 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000931 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000932 self.update_recent_files_list(new_file=self.io.filename)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400933 windows.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000934 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000935 self.io.close()
936 self.io = None
937 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000938 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000939 self.color.close(False)
940 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000941 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000942 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000943 self.per.close()
944 self.per = None
945 self.top.destroy()
946 if self.close_hook:
947 # unless override: unregister from flist, terminate if last window
948 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000949
950 def load_extensions(self):
951 self.extensions = {}
952 self.load_standard_extensions()
953
954 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000955 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000956 if hasattr(ins, "close"):
957 ins.close()
958 self.extensions = {}
959
960 def load_standard_extensions(self):
961 for name in self.get_standard_extension_names():
962 try:
963 self.load_extension(name)
964 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000965 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000966 traceback.print_exc()
967
968 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000969 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000970
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400971 extfiles = { # map config-extension section names to new file names
972 'AutoComplete': 'autocomplete',
973 'AutoExpand': 'autoexpand',
974 'CallTips': 'calltips',
975 'CodeContext': 'codecontext',
976 'FormatParagraph': 'paragraph',
977 'ParenMatch': 'parenmatch',
978 'RstripExtension': 'rstrip',
979 'ScriptBinding': 'runscript',
980 'ZoomHeight': 'zoomheight',
981 }
982
David Scherer7aced172000-08-15 01:13:23 +0000983 def load_extension(self, name):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400984 fname = self.extfiles.get(name, name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000985 try:
Brett Cannonaef82d32012-04-14 20:44:23 -0400986 try:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400987 mod = importlib.import_module('.' + fname, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -0400988 except (ImportError, TypeError):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400989 mod = importlib.import_module(fname)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000990 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000991 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000992 raise
David Scherer7aced172000-08-15 01:13:23 +0000993 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000994 keydefs = idleConf.GetExtensionBindings(name)
995 if hasattr(cls, "menudefs"):
996 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000997 ins = cls(self)
998 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000999 if keydefs:
1000 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001001 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001002 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001003 while methodname[:1] == '<':
1004 methodname = methodname[1:]
1005 while methodname[-1:] == '>':
1006 methodname = methodname[:-1]
1007 methodname = methodname + "_event"
1008 if hasattr(ins, methodname):
1009 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001010
1011 def apply_bindings(self, keydefs=None):
1012 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001013 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001014 text = self.text
1015 text.keydefs = keydefs
1016 for event, keylist in keydefs.items():
1017 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001018 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001019
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001020 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001021 """Add appropriate entries to the menus and submenus
1022
1023 Menus that are absent or None in self.menudict are ignored.
1024 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001025 if menudefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001026 menudefs = self.mainmenu.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001027 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001028 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001029 menudict = self.menudict
1030 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001031 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001032 menu = menudict.get(mname)
1033 if not menu:
1034 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001035 for entry in entrylist:
1036 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001037 menu.add_separator()
1038 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001039 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001040 checkbutton = (label[:1] == '!')
1041 if checkbutton:
1042 label = label[1:]
1043 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001044 accelerator = get_accelerator(keydefs, eventname)
1045 def command(text=text, eventname=eventname):
1046 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001047 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001048 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001049 menu.add_checkbutton(label=label, underline=underline,
1050 command=command, accelerator=accelerator,
1051 variable=var)
1052 else:
1053 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001054 command=command,
1055 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001056
1057 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001058 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001059 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001060 value = var.get()
1061 return value
1062 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001063 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001064
1065 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001066 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001067 if var:
1068 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001069 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001070 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001071
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001072 def get_var_obj(self, name, vartype=None):
1073 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001074 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001075 # create a Tkinter variable object with self.text as master:
1076 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001077 return var
1078
1079 # Tk implementations of "virtual text methods" -- each platform
1080 # reusing IDLE's support code needs to define these for its GUI's
1081 # flavor of widget.
1082
1083 # Is character at text_index in a Python string? Return 0 for
1084 # "guaranteed no", true for anything else. This info is expensive
1085 # to compute ab initio, but is probably already known by the
1086 # platform's colorizer.
1087
1088 def is_char_in_string(self, text_index):
1089 if self.color:
1090 # Return true iff colorizer hasn't (re)gotten this far
1091 # yet, or the character is tagged as being in a string
1092 return self.text.tag_prevrange("TODO", text_index) or \
1093 "STRING" in self.text.tag_names(text_index)
1094 else:
1095 # The colorizer is missing: assume the worst
1096 return 1
1097
1098 # If a selection is defined in the text widget, return (start,
1099 # end) as Tkinter text indices, otherwise return (None, None)
1100 def get_selection_indices(self):
1101 try:
1102 first = self.text.index("sel.first")
1103 last = self.text.index("sel.last")
1104 return first, last
1105 except TclError:
1106 return None, None
1107
1108 # Return the text widget's current view of what a tab stop means
1109 # (equivalent width in spaces).
1110
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001111 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001112 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1113 return int(current)
1114
1115 # Set the text widget's current view of what a tab stop means.
1116
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001117 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001118 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001119 if self.get_tk_tabwidth() != newtabwidth:
1120 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001121 pixels = text.tk.call("font", "measure", text["font"],
1122 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001123 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001124 text.configure(tabs=pixels)
1125
Guido van Rossum33d26892007-08-05 15:29:28 +00001126### begin autoindent code ### (configuration was moved to beginning of class)
1127
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001128 def set_indentation_params(self, is_py_src, guess=True):
1129 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001130 i = self.guess_indent()
1131 if 2 <= i <= 8:
1132 self.indentwidth = i
1133 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001134 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001135 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001136
1137 def smart_backspace_event(self, event):
1138 text = self.text
1139 first, last = self.get_selection_indices()
1140 if first and last:
1141 text.delete(first, last)
1142 text.mark_set("insert", first)
1143 return "break"
1144 # Delete whitespace left, until hitting a real char or closest
1145 # preceding virtual tab stop.
1146 chars = text.get("insert linestart", "insert")
1147 if chars == '':
1148 if text.compare("insert", ">", "1.0"):
1149 # easy: delete preceding newline
1150 text.delete("insert-1c")
1151 else:
1152 text.bell() # at start of buffer
1153 return "break"
1154 if chars[-1] not in " \t":
1155 # easy: delete preceding real char
1156 text.delete("insert-1c")
1157 return "break"
1158 # Ick. It may require *inserting* spaces if we back up over a
1159 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001160 tabwidth = self.tabwidth
1161 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001162 assert have > 0
1163 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001164 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001165 if self.context_use_ps1:
1166 last_line_of_prompt = sys.ps1.split('\n')[-1]
1167 else:
1168 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001169 ncharsdeleted = 0
1170 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001171 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001172 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001173 chars = chars[:-1]
1174 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001175 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001176 if have <= want or chars[-1] not in " \t":
1177 break
1178 text.undo_block_start()
1179 text.delete("insert-%dc" % ncharsdeleted, "insert")
1180 if have < want:
1181 text.insert("insert", ' ' * (want - have))
1182 text.undo_block_stop()
1183 return "break"
1184
1185 def smart_indent_event(self, event):
1186 # if intraline selection:
1187 # delete it
1188 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001189 # do indent-region
1190 # else:
1191 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001192 text = self.text
1193 first, last = self.get_selection_indices()
1194 text.undo_block_start()
1195 try:
1196 if first and last:
1197 if index2line(first) != index2line(last):
1198 return self.indent_region_event(event)
1199 text.delete(first, last)
1200 text.mark_set("insert", first)
1201 prefix = text.get("insert linestart", "insert")
1202 raw, effective = classifyws(prefix, self.tabwidth)
1203 if raw == len(prefix):
1204 # only whitespace to the left
1205 self.reindent_to(effective + self.indentwidth)
1206 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001207 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001208 if self.usetabs:
1209 pad = '\t'
1210 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001211 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001212 n = self.indentwidth
1213 pad = ' ' * (n - effective % n)
1214 text.insert("insert", pad)
1215 text.see("insert")
1216 return "break"
1217 finally:
1218 text.undo_block_stop()
1219
1220 def newline_and_indent_event(self, event):
1221 text = self.text
1222 first, last = self.get_selection_indices()
1223 text.undo_block_start()
1224 try:
1225 if first and last:
1226 text.delete(first, last)
1227 text.mark_set("insert", first)
1228 line = text.get("insert linestart", "insert")
1229 i, n = 0, len(line)
1230 while i < n and line[i] in " \t":
1231 i = i+1
1232 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001233 # the cursor is in or at leading indentation in a continuation
1234 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001235 text.insert("insert linestart", '\n')
1236 return "break"
1237 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001238 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001239 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001240 last_line_of_prompt = sys.ps1.split('\n')[-1]
1241 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001242 line = line[:-1]
1243 i = i+1
1244 if i:
1245 text.delete("insert - %d chars" % i, "insert")
1246 # strip whitespace after insert point
1247 while text.get("insert") in " \t":
1248 text.delete("insert")
1249 # start new line
1250 text.insert("insert", '\n')
1251
1252 # adjust indentation for continuations and block
1253 # open/close first need to find the last stmt
1254 lno = index2line(text.index('insert'))
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001255 y = pyparse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001256 if not self.context_use_ps1:
1257 for context in self.num_context_lines:
1258 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001259 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001260 rawtext = text.get(startatindex, "insert")
1261 y.set_str(rawtext)
1262 bod = y.find_good_parse_start(
1263 self.context_use_ps1,
1264 self._build_char_in_string_func(startatindex))
1265 if bod is not None or startat == 1:
1266 break
1267 y.set_lo(bod or 0)
1268 else:
1269 r = text.tag_prevrange("console", "insert")
1270 if r:
1271 startatindex = r[1]
1272 else:
1273 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001274 rawtext = text.get(startatindex, "insert")
1275 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001276 y.set_lo(0)
1277
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001278 c = y.get_continuation_type()
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001279 if c != pyparse.C_NONE:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001280 # The current stmt hasn't ended yet.
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001281 if c == pyparse.C_STRING_FIRST_LINE:
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001282 # after the first line of a string; do not indent at all
1283 pass
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001284 elif c == pyparse.C_STRING_NEXT_LINES:
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001285 # inside a string which started before this line;
1286 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001287 text.insert("insert", indent)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001288 elif c == pyparse.C_BRACKET:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001289 # line up with the first (if any) element of the
1290 # last open bracket structure; else indent one
1291 # level beyond the indent of the line with the
1292 # last open bracket
1293 self.reindent_to(y.compute_bracket_indent())
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001294 elif c == pyparse.C_BACKSLASH:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001295 # if more than one line in this stmt already, just
1296 # mimic the current indent; else if initial line
1297 # has a start on an assignment stmt, indent to
1298 # beyond leftmost =; else to beyond first chunk of
1299 # non-whitespace on initial line
1300 if y.get_num_lines_in_stmt() > 1:
1301 text.insert("insert", indent)
1302 else:
1303 self.reindent_to(y.compute_backslash_indent())
1304 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001305 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001306 return "break"
1307
1308 # This line starts a brand new stmt; indent relative to
1309 # indentation of initial line of closest preceding
1310 # interesting stmt.
1311 indent = y.get_base_indent_string()
1312 text.insert("insert", indent)
1313 if y.is_block_opener():
1314 self.smart_indent_event(event)
1315 elif indent and y.is_block_closer():
1316 self.smart_backspace_event(event)
1317 return "break"
1318 finally:
1319 text.see("insert")
1320 text.undo_block_stop()
1321
Martin Panter7462b6492015-11-02 03:37:02 +00001322 # Our editwin provides an is_char_in_string function that works
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001323 # with a Tk text index, but PyParse only knows about offsets into
1324 # a string. This builds a function for PyParse that accepts an
1325 # offset.
1326
1327 def _build_char_in_string_func(self, startindex):
1328 def inner(offset, _startindex=startindex,
1329 _icis=self.is_char_in_string):
1330 return _icis(_startindex + "+%dc" % offset)
1331 return inner
1332
1333 def indent_region_event(self, event):
1334 head, tail, chars, lines = self.get_region()
1335 for pos in range(len(lines)):
1336 line = lines[pos]
1337 if line:
1338 raw, effective = classifyws(line, self.tabwidth)
1339 effective = effective + self.indentwidth
1340 lines[pos] = self._make_blanks(effective) + line[raw:]
1341 self.set_region(head, tail, chars, lines)
1342 return "break"
1343
1344 def dedent_region_event(self, event):
1345 head, tail, chars, lines = self.get_region()
1346 for pos in range(len(lines)):
1347 line = lines[pos]
1348 if line:
1349 raw, effective = classifyws(line, self.tabwidth)
1350 effective = max(effective - self.indentwidth, 0)
1351 lines[pos] = self._make_blanks(effective) + line[raw:]
1352 self.set_region(head, tail, chars, lines)
1353 return "break"
1354
1355 def comment_region_event(self, event):
1356 head, tail, chars, lines = self.get_region()
1357 for pos in range(len(lines) - 1):
1358 line = lines[pos]
1359 lines[pos] = '##' + line
1360 self.set_region(head, tail, chars, lines)
1361
1362 def uncomment_region_event(self, event):
1363 head, tail, chars, lines = self.get_region()
1364 for pos in range(len(lines)):
1365 line = lines[pos]
1366 if not line:
1367 continue
1368 if line[:2] == '##':
1369 line = line[2:]
1370 elif line[:1] == '#':
1371 line = line[1:]
1372 lines[pos] = line
1373 self.set_region(head, tail, chars, lines)
1374
1375 def tabify_region_event(self, event):
1376 head, tail, chars, lines = self.get_region()
1377 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001378 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001379 for pos in range(len(lines)):
1380 line = lines[pos]
1381 if line:
1382 raw, effective = classifyws(line, tabwidth)
1383 ntabs, nspaces = divmod(effective, tabwidth)
1384 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1385 self.set_region(head, tail, chars, lines)
1386
1387 def untabify_region_event(self, event):
1388 head, tail, chars, lines = self.get_region()
1389 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001390 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001391 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001392 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001393 self.set_region(head, tail, chars, lines)
1394
1395 def toggle_tabs_event(self, event):
1396 if self.askyesno(
1397 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001398 "Turn tabs " + ("on", "off")[self.usetabs] +
1399 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001400 ("will be", "remains at")[self.usetabs] + " 8." +
1401 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001402 parent=self.text):
1403 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001404 # Try to prevent inconsistent indentation.
1405 # User must change indent width manually after using tabs.
1406 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001407 return "break"
1408
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001409 # XXX this isn't bound to anything -- see tabwidth comments
1410## def change_tabwidth_event(self, event):
1411## new = self._asktabwidth()
1412## if new != self.tabwidth:
1413## self.tabwidth = new
1414## self.set_indentation_params(0, guess=0)
1415## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001416
1417 def change_indentwidth_event(self, event):
1418 new = self.askinteger(
1419 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001420 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001421 parent=self.text,
1422 initialvalue=self.indentwidth,
1423 minvalue=2,
1424 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001425 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001426 self.indentwidth = new
1427 return "break"
1428
1429 def get_region(self):
1430 text = self.text
1431 first, last = self.get_selection_indices()
1432 if first and last:
1433 head = text.index(first + " linestart")
1434 tail = text.index(last + "-1c lineend +1c")
1435 else:
1436 head = text.index("insert linestart")
1437 tail = text.index("insert lineend +1c")
1438 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001439 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001440 return head, tail, chars, lines
1441
1442 def set_region(self, head, tail, chars, lines):
1443 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001444 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001445 if newchars == chars:
1446 text.bell()
1447 return
1448 text.tag_remove("sel", "1.0", "end")
1449 text.mark_set("insert", head)
1450 text.undo_block_start()
1451 text.delete(head, tail)
1452 text.insert(head, newchars)
1453 text.undo_block_stop()
1454 text.tag_add("sel", head, "insert")
1455
1456 # Make string that displays as n leading blanks.
1457
1458 def _make_blanks(self, n):
1459 if self.usetabs:
1460 ntabs, nspaces = divmod(n, self.tabwidth)
1461 return '\t' * ntabs + ' ' * nspaces
1462 else:
1463 return ' ' * n
1464
1465 # Delete from beginning of line to insert point, then reinsert
1466 # column logical (meaning use tabs if appropriate) spaces.
1467
1468 def reindent_to(self, column):
1469 text = self.text
1470 text.undo_block_start()
1471 if text.compare("insert linestart", "!=", "insert"):
1472 text.delete("insert linestart", "insert")
1473 if column:
1474 text.insert("insert", self._make_blanks(column))
1475 text.undo_block_stop()
1476
1477 def _asktabwidth(self):
1478 return self.askinteger(
1479 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001480 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001481 parent=self.text,
1482 initialvalue=self.indentwidth,
1483 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001484 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001485
1486 # Guess indentwidth from text content.
1487 # Return guessed indentwidth. This should not be believed unless
1488 # it's in a reasonable range (e.g., it will be 0 if no indented
1489 # blocks are found).
1490
1491 def guess_indent(self):
1492 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1493 if opener and indented:
1494 raw, indentsmall = classifyws(opener, self.tabwidth)
1495 raw, indentlarge = classifyws(indented, self.tabwidth)
1496 else:
1497 indentsmall = indentlarge = 0
1498 return indentlarge - indentsmall
1499
1500# "line.col" -> line, as an int
1501def index2line(index):
1502 return int(float(index))
1503
1504# Look at the leading whitespace in s.
1505# Return pair (# of leading ws characters,
1506# effective # of leading blanks after expanding
1507# tabs to width tabwidth)
1508
1509def classifyws(s, tabwidth):
1510 raw = effective = 0
1511 for ch in s:
1512 if ch == ' ':
1513 raw = raw + 1
1514 effective = effective + 1
1515 elif ch == '\t':
1516 raw = raw + 1
1517 effective = (effective // tabwidth + 1) * tabwidth
1518 else:
1519 break
1520 return raw, effective
1521
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001522
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001523class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001524
1525 # .run() chews over the Text widget, looking for a block opener
1526 # and the stmt following it. Returns a pair,
1527 # (line containing block opener, line containing stmt)
1528 # Either or both may be None.
1529
1530 def __init__(self, text, tabwidth):
1531 self.text = text
1532 self.tabwidth = tabwidth
1533 self.i = self.finished = 0
1534 self.blkopenline = self.indentedline = None
1535
1536 def readline(self):
1537 if self.finished:
1538 return ""
1539 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001540 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001541 if self.text.compare(mark, ">=", "end"):
1542 return ""
1543 return self.text.get(mark, mark + " lineend+1c")
1544
1545 def tokeneater(self, type, token, start, end, line,
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001546 INDENT=tokenize.INDENT,
1547 NAME=tokenize.NAME,
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001548 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1549 if self.finished:
1550 pass
1551 elif type == NAME and token in OPENERS:
1552 self.blkopenline = line
1553 elif type == INDENT and self.blkopenline:
1554 self.indentedline = line
1555 self.finished = 1
1556
1557 def run(self):
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001558 save_tabsize = tokenize.tabsize
1559 tokenize.tabsize = self.tabwidth
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001560 try:
1561 try:
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001562 tokens = tokenize.generate_tokens(self.readline)
Trent Nelson428de652008-03-18 22:41:35 +00001563 for token in tokens:
1564 self.tokeneater(*token)
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001565 except (tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001566 # since we cut off the tokenizer early, we can trigger
1567 # spurious errors
1568 pass
1569 finally:
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001570 tokenize.tabsize = save_tabsize
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001571 return self.blkopenline, self.indentedline
1572
1573### end autoindent code ###
1574
David Scherer7aced172000-08-15 01:13:23 +00001575def prepstr(s):
1576 # Helper to extract the underscore from a string, e.g.
1577 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001578 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001579 if i >= 0:
1580 s = s[:i] + s[i+1:]
1581 return i, s
1582
1583
1584keynames = {
1585 'bracketleft': '[',
1586 'bracketright': ']',
1587 'slash': '/',
1588}
1589
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001590def get_accelerator(keydefs, eventname):
1591 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001592 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1593 # if not keylist:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001594 if (not keylist) or (macosx.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001595 "<<open-module>>",
1596 "<<goto-line>>",
1597 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001598 return ""
1599 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001600 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001601 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1602 s = re.sub("Key-", "", s)
1603 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1604 s = re.sub("Control-", "Ctrl-", s)
1605 s = re.sub("-", "+", s)
1606 s = re.sub("><", " ", s)
1607 s = re.sub("<", "", s)
1608 s = re.sub(">", "", s)
1609 return s
1610
1611
1612def fixwordbreaks(root):
1613 # Make sure that Tk's double-click and next/previous word
1614 # operations use our definition of a word (i.e. an identifier)
1615 tk = root.tk
1616 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1617 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1618 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1619
1620
Terry Jan Reedycd567362014-10-17 01:31:35 -04001621def _editor_window(parent): # htest #
1622 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001623 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001624 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001625 if sys.argv[1:]:
1626 filename = sys.argv[1]
1627 else:
1628 filename = None
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001629 macosx.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001630 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001631 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001632 # Does not stop error, neither does following
1633 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001634
1635if __name__ == '__main__':
Terry Jan Reedy7c153412016-06-26 17:48:02 -04001636 import unittest
1637 unittest.main('idlelib.idle_test.test_editor', verbosity=2, exit=False)
1638
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001639 from idlelib.idle_test.htest import run
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04001640 run(_editor_window)