blob: 7f910e76f96e7b7356804091ac488b826eb6b390 [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 *
Terry Jan Reedy01e35752016-06-10 18:19:21 -040010from tkinter.ttk import Scrollbar
Georg Brandl14fc4272008-05-17 18:39:55 +000011import tkinter.simpledialog as tkSimpleDialog
12import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +000013import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000014import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000015
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040016from idlelib.multicall import MultiCallCreator
17from idlelib import windows
18from idlelib import search
19from idlelib import grep
20from idlelib import replace
21from idlelib import pyparse
22from idlelib.config import idleConf
23from idlelib import help_about, textview, configdialog
24from idlelib import macosx
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040025from idlelib import help
David Scherer7aced172000-08-15 01:13:23 +000026
27# The default tab setting for a Text widget, in average-width characters.
28TK_TABWIDTH_DEFAULT = 8
29
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -040030_py_version = ' (%s)' % platform.python_version()
31
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000032def _sphinx_version():
33 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
34 major, minor, micro, level, serial = sys.version_info
35 release = '%s%s' % (major, minor)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020036 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000037 if level == 'candidate':
38 release += 'rc%s' % (serial,)
39 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000040 release += '%s%s' % (level[0], serial)
41 return release
42
Terry Jan Reedye91e7632012-02-05 15:14:20 -050043
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000044class EditorWindow(object):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040045 from idlelib.percolator import Percolator
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -040046 from idlelib.colorizer import ColorDelegator, color_config
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040047 from idlelib.undo import UndoDelegator
48 from idlelib.iomenu import IOBinding, filesystemencoding, encoding
49 from idlelib import mainmenu
Guilherme Polo5424b0a2008-05-25 15:26:44 +000050 from tkinter import Toplevel
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040051 from idlelib.statusbar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000052
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000053 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000054
55 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000056 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +010057 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000058 if sys.platform.count('linux'):
59 # look for html docs in a couple of standard places
60 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
61 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
62 dochome = '/var/www/html/python/index.html'
63 else:
64 basepath = '/usr/share/doc/' # standard location
65 dochome = os.path.join(basepath, pyver,
66 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000067 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +010068 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000069 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +000070 if os.path.isfile(chmfile):
71 dochome = chmfile
Ned Deilyb7601672014-03-27 20:49:14 -070072 elif sys.platform == 'darwin':
73 # documentation may be stored inside a python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +010074 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +000075 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000076 dochome = os.path.normpath(dochome)
77 if os.path.isfile(dochome):
78 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +000079 if sys.platform == 'darwin':
80 # Safari requires real file:-URLs
81 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000082 else:
Terry Jan Reedyb6e17782014-09-19 22:54:15 -040083 EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
David Scherer7aced172000-08-15 01:13:23 +000084 self.flist = flist
85 root = root or flist.root
86 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +000087 try:
88 sys.ps1
89 except AttributeError:
90 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +000091 self.menubar = Menu(root)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040092 self.top = top = windows.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000093 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +000094 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +020095 #self.top.instance_dict makes flist.inversedict available to
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040096 #configdialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +000097 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +000098 else:
99 self.tkinter_vars = {} # keys: Tkinter event names
100 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000101 self.top.instance_dict = {}
102 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000103 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000104 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000105 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200106 self.width = idleConf.GetOption('main', 'EditorWindow',
107 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000108 text_options = {
109 'name': 'text',
110 'padx': 5,
111 'wrap': 'none',
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500112 'highlightthickness': 0,
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000113 'width': self.width,
Terry Jan Reedy1080d132016-06-09 21:09:15 -0400114 'tabstyle': 'wordprocessor', # new in 8.5
115 'height': idleConf.GetOption(
116 'main', 'EditorWindow', 'height', type='int'),
117 }
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000118 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000119 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000120
121 self.createmenubar()
122 self.apply_bindings()
123
124 self.top.protocol("WM_DELETE_WINDOW", self.close)
125 self.top.bind("<<close-window>>", self.close_event)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400126 if macosx.isAquaTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000127 # Command-W on editorwindows doesn't work without this.
128 text.bind('<<close-window>>', self.close_event)
Terry Jan Reedy3c7eccd02015-09-22 21:10:27 -0400129 # Some OS X systems have only one mouse button, so use
130 # control-click for popup context menus there. For two
131 # buttons, AquaTk defines <2> as the right button, not <3>.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000132 text.bind("<Control-Button-1>",self.right_menu_event)
Terry Jan Reedy3c7eccd02015-09-22 21:10:27 -0400133 text.bind("<2>", self.right_menu_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000134 else:
Terry Jan Reedy3c7eccd02015-09-22 21:10:27 -0400135 # Elsewhere, use right-click for popup menus.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000136 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000137 text.bind("<<cut>>", self.cut)
138 text.bind("<<copy>>", self.copy)
139 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000140 text.bind("<<center-insert>>", self.center_insert_event)
141 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000142 text.bind("<<python-docs>>", self.python_docs)
143 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000144 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000145 text.bind("<<open-module>>", self.open_module)
146 text.bind("<<do-nothing>>", lambda event: "break")
147 text.bind("<<select-all>>", self.select_all)
148 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000149 text.bind("<<find>>", self.find_event)
150 text.bind("<<find-again>>", self.find_again_event)
151 text.bind("<<find-in-files>>", self.find_in_files_event)
152 text.bind("<<find-selection>>", self.find_selection_event)
153 text.bind("<<replace>>", self.replace_event)
154 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000155 text.bind("<<smart-backspace>>",self.smart_backspace_event)
156 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
157 text.bind("<<smart-indent>>",self.smart_indent_event)
158 text.bind("<<indent-region>>",self.indent_region_event)
159 text.bind("<<dedent-region>>",self.dedent_region_event)
160 text.bind("<<comment-region>>",self.comment_region_event)
161 text.bind("<<uncomment-region>>",self.uncomment_region_event)
162 text.bind("<<tabify-region>>",self.tabify_region_event)
163 text.bind("<<untabify-region>>",self.untabify_region_event)
164 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
165 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000166 text.bind("<Left>", self.move_at_edge_if_selection(0))
167 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000168 text.bind("<<del-word-left>>", self.del_word_left)
169 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000170 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000171
David Scherer7aced172000-08-15 01:13:23 +0000172 if flist:
173 flist.inversedict[self] = key
174 if key:
175 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000176 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000177 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
178 text.bind("<<open-class-browser>>", self.open_class_browser)
179 text.bind("<<open-path-browser>>", self.open_path_browser)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400180 text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
David Scherer7aced172000-08-15 01:13:23 +0000181
Steven M. Gava898a3652001-10-07 11:10:44 +0000182 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000183 vbar['command'] = text.yview
184 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000185 text['yscrollcommand'] = vbar.set
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400186 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000187 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
188 text.pack(side=TOP, fill=BOTH, expand=1)
189 text.focus_set()
190
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000191 # usetabs true -> literal tab characters are used by indent and
192 # dedent cmds, possibly mixed with spaces if
193 # indentwidth is not a multiple of tabwidth,
194 # which will cause Tabnanny to nag!
195 # false -> tab characters are converted to spaces by indent
196 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000197 # Although use-spaces=0 can be configured manually in config-main.def,
198 # configuration of tabs v. spaces is not supported in the configuration
199 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200200 usespaces = idleConf.GetOption('main', 'Indent',
201 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000202 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000203
204 # tabwidth is the display width of a literal tab character.
205 # CAUTION: telling Tk to use anything other than its default
206 # tab setting causes it to use an entirely different tabbing algorithm,
207 # treating tab stops as fixed distances from the left margin.
208 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000209 self.tabwidth = 8 # must remain 8 until Tk is fixed.
210
211 # indentwidth is the number of screen characters per indent level.
212 # The recommended Python indentation is four spaces.
213 self.indentwidth = self.tabwidth
214 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000215
216 # If context_use_ps1 is true, parsing searches back for a ps1 line;
217 # else searches for a popular (if, def, ...) Python stmt.
218 self.context_use_ps1 = False
219
220 # When searching backwards for a reliable place to begin parsing,
221 # first start num_context_lines[0] lines back, then
222 # num_context_lines[1] lines back if that didn't work, and so on.
223 # The last value should be huge (larger than the # of lines in a
224 # conceivable file).
225 # Making the initial values larger slows things down more often.
226 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000227 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000228 self.undo = undo = self.UndoDelegator()
229 per.insertfilter(undo)
230 text.undo_block_start = undo.undo_block_start
231 text.undo_block_stop = undo.undo_block_stop
232 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000233 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000234 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000235 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000236 self.good_load = False
237 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000238 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000239 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000240 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000241 if io.loadfile(filename):
242 self.good_load = True
243 is_py_src = self.ispythonsource(filename)
244 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000245 else:
246 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500247 self.good_load = True
248
Christian Heimesa156e092008-02-16 07:38:31 +0000249 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000250 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000251 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000252 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000253 menu = self.menudict.get('windows')
254 if menu:
255 end = menu.index("end")
256 if end is None:
257 end = -1
258 if end >= 0:
259 menu.add_separator()
260 end = end + 1
261 self.wmenu_end = end
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400262 windows.register_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000263
264 # Some abstractions so IDLE extensions are cross-IDE
265 self.askyesno = tkMessageBox.askyesno
266 self.askinteger = tkSimpleDialog.askinteger
267 self.showerror = tkMessageBox.showerror
268
Martin v. Löwis307021f2005-11-27 16:59:04 +0000269 def _filename_to_unicode(self, filename):
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400270 """Return filename as BMP unicode so diplayable in Tk."""
271 # Decode bytes to unicode.
272 if isinstance(filename, bytes):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000273 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400274 filename = filename.decode(self.filesystemencoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000275 except UnicodeDecodeError:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000276 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400277 filename = filename.decode(self.encoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000278 except UnicodeDecodeError:
279 # byte-to-byte conversion
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400280 filename = filename.decode('iso8859-1')
281 # Replace non-BMP char with diamond questionmark.
282 return re.sub('[\U00010000-\U0010FFFF]', '\ufffd', filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000283
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000284 def new_callback(self, event):
285 dirname, basename = self.io.defaultfilename()
286 self.flist.new(dirname)
287 return "break"
288
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000289 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400290 if (event.state & 4) != 0 and event.keysym == "Home":
291 # state&4==Control. If <Control-Home>, use the Tk binding.
292 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000293 if self.text.index("iomark") and \
294 self.text.compare("iomark", "<=", "insert lineend") and \
295 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400296 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000297 insertpt = int(self.text.index("iomark").split(".")[1])
298 else:
299 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000300 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000301 if line[insertpt] not in (' ','\t'):
302 break
303 else:
304 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000305 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000306 if insertpt == lineat:
307 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000308 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000309 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400310 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000311 self.text.tag_remove("sel", "1.0", "end")
312 else:
313 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200314 # there was no previous selection
315 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400316 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200317 if self.text.compare(self.text.index("sel.first"), "<",
318 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400319 self.text.mark_set("my_anchor", "sel.first") # extend back
320 else:
321 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000322 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400323 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000324 if self.text.compare(first,">",last):
325 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000326 self.text.tag_remove("sel", "1.0", "end")
327 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000328 self.text.mark_set("insert", dest)
329 self.text.see("insert")
330 return "break"
331
David Scherer7aced172000-08-15 01:13:23 +0000332 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000333 self.status_bar = self.MultiStatusBar(self.top)
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500334 sep = Frame(self.top, height=1, borderwidth=1, background='grey75')
Ned Deilyb7601672014-03-27 20:49:14 -0700335 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000336 # Insert some padding to avoid obscuring some of the statusbar
337 # by the resize widget.
338 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000339 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
340 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
341 self.status_bar.pack(side=BOTTOM, fill=X)
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500342 sep.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000343 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
344 self.text.event_add("<<set-line-and-column>>",
345 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000346 self.text.after_idle(self.set_line_and_column)
347
348 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000349 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000350 self.status_bar.set_label('column', 'Col: %s' % column)
351 self.status_bar.set_label('line', 'Ln: %s' % line)
352
David Scherer7aced172000-08-15 01:13:23 +0000353 menu_specs = [
354 ("file", "_File"),
355 ("edit", "_Edit"),
356 ("format", "F_ormat"),
357 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000358 ("options", "_Options"),
Ned Deilyccb416f2015-01-17 21:06:27 -0800359 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000360 ("help", "_Help"),
361 ]
362
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000363
David Scherer7aced172000-08-15 01:13:23 +0000364 def createmenubar(self):
365 mbar = self.menubar
366 self.menudict = menudict = {}
367 for name, label in self.menu_specs:
368 underline, label = prepstr(label)
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400369 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000370 mbar.add_cascade(label=label, menu=menu, underline=underline)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400371 if macosx.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000372 # Insert the application menu
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400373 menudict['application'] = menu = Menu(mbar, name='apple',
374 tearoff=0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000375 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000376 self.fill_menus()
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400377 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000378 self.menudict['file'].insert_cascade(3, label='Recent Files',
379 underline=0,
380 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000381 self.base_helpmenu_length = self.menudict['help'].index(END)
382 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000383
384 def postwindowsmenu(self):
385 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000386 menu = self.menudict['windows']
387 end = menu.index("end")
388 if end is None:
389 end = -1
390 if end > self.wmenu_end:
391 menu.delete(self.wmenu_end+1, end)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400392 windows.add_windows_to_menu(menu)
David Scherer7aced172000-08-15 01:13:23 +0000393
394 rmenu = None
395
396 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000397 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
398 if not self.rmenu:
399 self.make_rmenu()
400 rmenu = self.rmenu
401 self.event = event
402 iswin = sys.platform[:3] == 'win'
403 if iswin:
404 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200405
Roger Serwy6b2918a2013-04-07 12:15:52 -0500406 for item in self.rmenu_specs:
407 try:
408 label, eventname, verify_state = item
409 except ValueError: # see issue1207589
410 continue
411
Andrew Svetlovd1837672012-11-01 22:41:19 +0200412 if verify_state is None:
413 continue
414 state = getattr(self, verify_state)()
415 rmenu.entryconfigure(label, state=state)
416
417
David Scherer7aced172000-08-15 01:13:23 +0000418 rmenu.tk_popup(event.x_root, event.y_root)
419 if iswin:
420 self.text.config(cursor="ibeam")
421
422 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200423 # ("Label", "<<virtual-event>>", "statefuncname"), ...
424 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000425 ]
426
427 def make_rmenu(self):
428 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500429 for item in self.rmenu_specs:
430 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200431 if label is not None:
432 def command(text=self.text, eventname=eventname):
433 text.event_generate(eventname)
434 rmenu.add_command(label=label, command=command)
435 else:
436 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000437 self.rmenu = rmenu
438
Andrew Svetlovd1837672012-11-01 22:41:19 +0200439 def rmenu_check_cut(self):
440 return self.rmenu_check_copy()
441
442 def rmenu_check_copy(self):
443 try:
444 indx = self.text.index('sel.first')
445 except TclError:
446 return 'disabled'
447 else:
448 return 'normal' if indx else 'disabled'
449
450 def rmenu_check_paste(self):
451 try:
452 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
453 except TclError:
454 return 'disabled'
455 else:
456 return 'normal'
457
David Scherer7aced172000-08-15 01:13:23 +0000458 def about_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400459 "Handle Help 'About IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400460 # Synchronize with macosx.overrideRootMenu.about_dialog.
461 help_about.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000462
Steven M. Gava3b55a892001-11-21 05:56:26 +0000463 def config_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400464 "Handle Options 'Configure IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400465 # Synchronize with macosx.overrideRootMenu.config_dialog.
466 configdialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400467
David Scherer7aced172000-08-15 01:13:23 +0000468 def help_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400469 "Handle Help 'IDLE Help' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400470 # Synchronize with macosx.overrideRootMenu.help_dialog.
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500471 if self.root:
472 parent = self.root
473 else:
474 parent = self.top
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400475 help.show_idlehelp(parent)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000476
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000477 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000478 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000479 try:
480 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200481 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000482 tkMessageBox.showerror(title='Document Start Failure',
483 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000484 else:
485 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000486 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000487
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000488 def cut(self,event):
489 self.text.event_generate("<<Cut>>")
490 return "break"
491
492 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000493 if not self.text.tag_ranges("sel"):
494 # There is no selection, so do nothing and maybe interrupt.
495 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000496 self.text.event_generate("<<Copy>>")
497 return "break"
498
499 def paste(self,event):
500 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000501 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000502 return "break"
503
David Scherer7aced172000-08-15 01:13:23 +0000504 def select_all(self, event=None):
505 self.text.tag_add("sel", "1.0", "end-1c")
506 self.text.mark_set("insert", "1.0")
507 self.text.see("insert")
508 return "break"
509
510 def remove_selection(self, event=None):
511 self.text.tag_remove("sel", "1.0", "end")
512 self.text.see("insert")
513
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000514 def move_at_edge_if_selection(self, edge_index):
515 """Cursor move begins at start or end of selection
516
517 When a left/right cursor key is pressed create and return to Tkinter a
518 function which causes a cursor move from the associated edge of the
519 selection.
520
521 """
522 self_text_index = self.text.index
523 self_text_mark_set = self.text.mark_set
524 edges_table = ("sel.first+1c", "sel.last-1c")
525 def move_at_edge(event):
526 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
527 try:
528 self_text_index("sel.first")
529 self_text_mark_set("insert", edges_table[edge_index])
530 except TclError:
531 pass
532 return move_at_edge
533
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000534 def del_word_left(self, event):
535 self.text.event_generate('<Meta-Delete>')
536 return "break"
537
538 def del_word_right(self, event):
539 self.text.event_generate('<Meta-d>')
540 return "break"
541
Steven M. Gavac5976402002-01-04 03:06:08 +0000542 def find_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400543 search.find(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000544 return "break"
545
546 def find_again_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400547 search.find_again(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000548 return "break"
549
550 def find_selection_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400551 search.find_selection(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000552 return "break"
553
554 def find_in_files_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400555 grep.grep(self.text, self.io, self.flist)
Steven M. Gavac5976402002-01-04 03:06:08 +0000556 return "break"
557
558 def replace_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400559 replace.replace(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000560 return "break"
561
562 def goto_line_event(self, event):
563 text = self.text
564 lineno = tkSimpleDialog.askinteger("Goto",
565 "Go to line number:",parent=text)
566 if lineno is None:
567 return "break"
568 if lineno <= 0:
569 text.bell()
570 return "break"
571 text.mark_set("insert", "%d.0" % lineno)
572 text.see("insert")
573
David Scherer7aced172000-08-15 01:13:23 +0000574 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000575 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000576 try:
577 name = self.text.get("sel.first", "sel.last")
578 except TclError:
579 name = ""
580 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000581 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000582 name = tkSimpleDialog.askstring("Module",
583 "Enter the name of a Python module\n"
584 "to search on sys.path and open:",
585 parent=self.text, initialvalue=name)
586 if name:
587 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000588 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000589 return
David Scherer7aced172000-08-15 01:13:23 +0000590 # XXX Ought to insert current file's directory in front of path
591 try:
Eric Snow6029e082014-01-25 15:32:46 -0700592 spec = importlib.util.find_spec(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400593 except (ValueError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000594 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
595 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700596 if spec is None:
Brett Cannon50793b42013-06-07 13:17:48 -0400597 tkMessageBox.showerror("Import error", "module not found",
598 parent=self.text)
David Scherer7aced172000-08-15 01:13:23 +0000599 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700600 if not isinstance(spec.loader, importlib.abc.SourceLoader):
Brett Cannon50793b42013-06-07 13:17:48 -0400601 tkMessageBox.showerror("Import error", "not a source-based module",
602 parent=self.text)
603 return
604 try:
Eric Snow02b9f9d2014-01-06 20:42:59 -0700605 file_path = spec.loader.get_filename(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400606 except AttributeError:
607 tkMessageBox.showerror("Import error",
608 "loader does not support get_filename",
609 parent=self.text)
610 return
David Scherer7aced172000-08-15 01:13:23 +0000611 if self.flist:
Brett Cannon50793b42013-06-07 13:17:48 -0400612 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000613 else:
Brett Cannon50793b42013-06-07 13:17:48 -0400614 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400615 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000616
617 def open_class_browser(self, event=None):
618 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400619 if not (self.__class__.__name__ == 'PyShellEditorWindow'
620 and filename):
621 filename = self.open_module()
622 if filename is None:
623 return
David Scherer7aced172000-08-15 01:13:23 +0000624 head, tail = os.path.split(filename)
625 base, ext = os.path.splitext(tail)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400626 from idlelib import browser
627 browser.ClassBrowser(self.flist, base, [head])
David Scherer7aced172000-08-15 01:13:23 +0000628
629 def open_path_browser(self, event=None):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400630 from idlelib import pathbrowser
631 pathbrowser.PathBrowser(self.flist)
David Scherer7aced172000-08-15 01:13:23 +0000632
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400633 def open_turtle_demo(self, event = None):
634 import subprocess
635
636 cmd = [sys.executable,
637 '-c',
638 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400639 subprocess.Popen(cmd, shell=False)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400640
David Scherer7aced172000-08-15 01:13:23 +0000641 def gotoline(self, lineno):
642 if lineno is not None and lineno > 0:
643 self.text.mark_set("insert", "%d.0" % lineno)
644 self.text.tag_remove("sel", "1.0", "end")
645 self.text.tag_add("sel", "insert", "insert +1l")
646 self.center()
647
648 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000649 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000650 return True
David Scherer7aced172000-08-15 01:13:23 +0000651 base, ext = os.path.splitext(os.path.basename(filename))
652 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000653 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000654 line = self.text.get('1.0', '1.0 lineend')
655 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000656
657 def close_hook(self):
658 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000659 self.flist.unregister_maybe_terminate(self)
660 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000661
662 def set_close_hook(self, close_hook):
663 self.close_hook = close_hook
664
665 def filename_change_hook(self):
666 if self.flist:
667 self.flist.filename_changed_edit(self)
668 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000669 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000670 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000671
Christian Heimesa156e092008-02-16 07:38:31 +0000672 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000673 if self.color:
674 return
Christian Heimesa156e092008-02-16 07:38:31 +0000675 if self.ispythonsource(self.io.filename):
676 self.color = self.ColorDelegator()
677 # can add more colorizers here...
678 if self.color:
679 self.per.removefilter(self.undo)
680 self.per.insertfilter(self.color)
681 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000682
Christian Heimesa156e092008-02-16 07:38:31 +0000683 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000684 if not self.color:
685 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000686 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000687 self.per.removefilter(self.color)
688 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000689
Steven M. Gavab77d3432002-03-02 07:16:21 +0000690 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400691 "Update the color theme"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400692 # Called from self.filename_change_hook and from configdialog.py
Christian Heimesa156e092008-02-16 07:38:31 +0000693 self._rmcolorizer()
694 self._addcolorizer()
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -0400695 EditorWindow.color_config(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000696
Guido van Rossum33d26892007-08-05 15:29:28 +0000697 IDENTCHARS = string.ascii_letters + string.digits + "_"
698
699 def colorize_syntax_error(self, text, pos):
700 text.tag_add("ERROR", pos)
701 char = text.get(pos)
702 if char and char in self.IDENTCHARS:
703 text.tag_add("ERROR", pos + " wordstart", pos)
704 if '\n' == text.get(pos): # error at line end
705 text.mark_set("insert", pos)
706 else:
707 text.mark_set("insert", pos + "+1c")
708 text.see(pos)
709
Steven M. Gavab1585412002-03-12 00:21:56 +0000710 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000711 "Update the text widgets' font if it is changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400712 # Called from configdialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400713
714 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000715
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000716 def RemoveKeybindings(self):
717 "Remove the keybindings before they are changed."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400718 # Called from configdialog.py
719 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000720 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000721 self.text.event_delete(event, *keylist)
722 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000723 xkeydefs = idleConf.GetExtensionBindings(extensionName)
724 if xkeydefs:
725 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000726 self.text.event_delete(event, *keylist)
727
728 def ApplyKeybindings(self):
729 "Update the keybindings after they are changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400730 # Called from configdialog.py
731 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000732 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000733 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000734 xkeydefs = idleConf.GetExtensionBindings(extensionName)
735 if xkeydefs:
736 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000737 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000738 menuEventDict = {}
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400739 for menu in self.mainmenu.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000740 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000741 for item in menu[1]:
742 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000743 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000744 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000745 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700746 end = menu.index(END)
747 if end is None:
748 # Skip empty menus
749 continue
750 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000751 for index in range(0, end):
752 if menu.type(index) == 'command':
753 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000754 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000755 itemName = menu.entrycget(index, 'label')
756 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000757 if menubarItem in menuEventDict:
758 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000759 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000760 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000761 accel = get_accelerator(keydefs, event)
762 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000763
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000764 def set_notabs_indentwidth(self):
765 "Update the indentwidth if changed and not using tabs in this window"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400766 # Called from configdialog.py
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000767 if not self.usetabs:
768 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
769 type='int')
770
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000771 def reset_help_menu_entries(self):
772 "Update the additional help entries on the Help menu"
773 help_list = idleConf.GetAllExtraHelpSourcesList()
774 helpmenu = self.menudict['help']
775 # first delete the extra help entries, if any
776 helpmenu_length = helpmenu.index(END)
777 if helpmenu_length > self.base_helpmenu_length:
778 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
779 # then rebuild them
780 if help_list:
781 helpmenu.add_separator()
782 for entry in help_list:
783 cmd = self.__extra_help_callback(entry[1])
784 helpmenu.add_command(label=entry[0], command=cmd)
785 # and update the menu dictionary
786 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000787
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000788 def __extra_help_callback(self, helpfile):
789 "Create a callback with the helpfile value frozen at definition time"
790 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000791 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000792 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000793 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000794 try:
795 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200796 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000797 tkMessageBox.showerror(title='Document Start Failure',
798 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000799 else:
800 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000801 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000802
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000803 def update_recent_files_list(self, new_file=None):
804 "Load and update the recent files list and menus"
805 rf_list = []
806 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400807 with open(self.recent_files_path, 'r',
808 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000809 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000810 if new_file:
811 new_file = os.path.abspath(new_file) + '\n'
812 if new_file in rf_list:
813 rf_list.remove(new_file) # move to top
814 rf_list.insert(0, new_file)
815 # clean and save the recent files list
816 bad_paths = []
817 for path in rf_list:
818 if '\0' in path or not os.path.exists(path[0:-1]):
819 bad_paths.append(path)
820 rf_list = [path for path in rf_list if path not in bad_paths]
821 ulchars = "1234567890ABCDEFGHIJK"
822 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000823 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800824 with open(self.recent_files_path, 'w',
825 encoding='utf_8', errors='replace') as rf_file:
826 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200827 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800828 if not getattr(self.root, "recentfilelist_error_displayed", False):
829 self.root.recentfilelist_error_displayed = True
Terry Jan Reedy4c973e92015-10-27 03:38:02 -0400830 tkMessageBox.showwarning(title='IDLE Warning',
831 message="Cannot update File menu Recent Files list. "
832 "Your operating system says:\n%s\n"
833 "Select OK and IDLE will continue without updating."
834 % self._filename_to_unicode(str(err)),
Ned Deilyf505b742011-12-14 14:58:24 -0800835 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000836 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000837 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000838 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700839 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000840 for i, file_name in enumerate(rf_list):
841 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000842 # make unicode string to display non-ASCII chars correctly
843 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000844 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000845 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000846 command=callback,
847 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000848
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000849 def __recent_file_callback(self, file_name):
850 def open_recent_file(fn_closure=file_name):
851 self.io.open(editFile=fn_closure)
852 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000853
David Scherer7aced172000-08-15 01:13:23 +0000854 def saved_change_hook(self):
855 short = self.short_title()
856 long = self.long_title()
857 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400858 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000859 elif short:
860 title = short
861 elif long:
862 title = long
863 else:
864 title = "Untitled"
865 icon = short or long or title
866 if not self.get_saved():
867 title = "*%s*" % title
868 icon = "*%s" % icon
869 self.top.wm_title(title)
870 self.top.wm_iconname(icon)
871
872 def get_saved(self):
873 return self.undo.get_saved()
874
875 def set_saved(self, flag):
876 self.undo.set_saved(flag)
877
878 def reset_undo(self):
879 self.undo.reset_undo()
880
881 def short_title(self):
882 filename = self.io.filename
883 if filename:
884 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500885 else:
886 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000887 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400888 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000889
890 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000891 # return unicode string to display non-ASCII chars correctly
892 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000893
894 def center_insert_event(self, event):
895 self.center()
896
897 def center(self, mark="insert"):
898 text = self.text
899 top, bot = self.getwindowlines()
900 lineno = self.getlineno(mark)
901 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000902 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000903 text.yview(float(newtop))
904
905 def getwindowlines(self):
906 text = self.text
907 top = self.getlineno("@0,0")
908 bot = self.getlineno("@0,65535")
909 if top == bot and text.winfo_height() == 1:
910 # Geometry manager hasn't run yet
911 height = int(text['height'])
912 bot = top + height - 1
913 return top, bot
914
915 def getlineno(self, mark="insert"):
916 text = self.text
917 return int(float(text.index(mark)))
918
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000919 def get_geometry(self):
920 "Return (width, height, x, y)"
921 geom = self.top.wm_geometry()
922 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000923 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000924
David Scherer7aced172000-08-15 01:13:23 +0000925 def close_event(self, event):
926 self.close()
927
928 def maybesave(self):
929 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000930 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000931 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000932 self.top.deiconify()
933 self.top.lower()
934 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000935 return self.io.maybesave()
936
937 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000938 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000939 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000940 self._close()
941 return reply
942
943 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000944 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000945 self.update_recent_files_list(new_file=self.io.filename)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400946 windows.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000947 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000948 self.io.close()
949 self.io = None
950 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000951 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000952 self.color.close(False)
953 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000954 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000955 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000956 self.per.close()
957 self.per = None
958 self.top.destroy()
959 if self.close_hook:
960 # unless override: unregister from flist, terminate if last window
961 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000962
963 def load_extensions(self):
964 self.extensions = {}
965 self.load_standard_extensions()
966
967 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000968 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000969 if hasattr(ins, "close"):
970 ins.close()
971 self.extensions = {}
972
973 def load_standard_extensions(self):
974 for name in self.get_standard_extension_names():
975 try:
976 self.load_extension(name)
977 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000978 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000979 traceback.print_exc()
980
981 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000982 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000983
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400984 extfiles = { # map config-extension section names to new file names
985 'AutoComplete': 'autocomplete',
986 'AutoExpand': 'autoexpand',
987 'CallTips': 'calltips',
988 'CodeContext': 'codecontext',
989 'FormatParagraph': 'paragraph',
990 'ParenMatch': 'parenmatch',
991 'RstripExtension': 'rstrip',
992 'ScriptBinding': 'runscript',
993 'ZoomHeight': 'zoomheight',
994 }
995
David Scherer7aced172000-08-15 01:13:23 +0000996 def load_extension(self, name):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400997 fname = self.extfiles.get(name, name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000998 try:
Brett Cannonaef82d32012-04-14 20:44:23 -0400999 try:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001000 mod = importlib.import_module('.' + fname, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001001 except (ImportError, TypeError):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001002 mod = importlib.import_module(fname)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001003 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001004 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001005 raise
David Scherer7aced172000-08-15 01:13:23 +00001006 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001007 keydefs = idleConf.GetExtensionBindings(name)
1008 if hasattr(cls, "menudefs"):
1009 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001010 ins = cls(self)
1011 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001012 if keydefs:
1013 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001014 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001015 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001016 while methodname[:1] == '<':
1017 methodname = methodname[1:]
1018 while methodname[-1:] == '>':
1019 methodname = methodname[:-1]
1020 methodname = methodname + "_event"
1021 if hasattr(ins, methodname):
1022 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001023
1024 def apply_bindings(self, keydefs=None):
1025 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001026 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001027 text = self.text
1028 text.keydefs = keydefs
1029 for event, keylist in keydefs.items():
1030 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001031 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001032
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001033 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001034 """Add appropriate entries to the menus and submenus
1035
1036 Menus that are absent or None in self.menudict are ignored.
1037 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001038 if menudefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001039 menudefs = self.mainmenu.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001040 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001041 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001042 menudict = self.menudict
1043 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001044 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001045 menu = menudict.get(mname)
1046 if not menu:
1047 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001048 for entry in entrylist:
1049 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001050 menu.add_separator()
1051 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001052 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001053 checkbutton = (label[:1] == '!')
1054 if checkbutton:
1055 label = label[1:]
1056 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001057 accelerator = get_accelerator(keydefs, eventname)
1058 def command(text=text, eventname=eventname):
1059 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001060 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001061 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001062 menu.add_checkbutton(label=label, underline=underline,
1063 command=command, accelerator=accelerator,
1064 variable=var)
1065 else:
1066 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001067 command=command,
1068 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001069
1070 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001071 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001072 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001073 value = var.get()
1074 return value
1075 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001076 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001077
1078 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001079 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001080 if var:
1081 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001082 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001083 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001084
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001085 def get_var_obj(self, name, vartype=None):
1086 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001087 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001088 # create a Tkinter variable object with self.text as master:
1089 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001090 return var
1091
1092 # Tk implementations of "virtual text methods" -- each platform
1093 # reusing IDLE's support code needs to define these for its GUI's
1094 # flavor of widget.
1095
1096 # Is character at text_index in a Python string? Return 0 for
1097 # "guaranteed no", true for anything else. This info is expensive
1098 # to compute ab initio, but is probably already known by the
1099 # platform's colorizer.
1100
1101 def is_char_in_string(self, text_index):
1102 if self.color:
1103 # Return true iff colorizer hasn't (re)gotten this far
1104 # yet, or the character is tagged as being in a string
1105 return self.text.tag_prevrange("TODO", text_index) or \
1106 "STRING" in self.text.tag_names(text_index)
1107 else:
1108 # The colorizer is missing: assume the worst
1109 return 1
1110
1111 # If a selection is defined in the text widget, return (start,
1112 # end) as Tkinter text indices, otherwise return (None, None)
1113 def get_selection_indices(self):
1114 try:
1115 first = self.text.index("sel.first")
1116 last = self.text.index("sel.last")
1117 return first, last
1118 except TclError:
1119 return None, None
1120
1121 # Return the text widget's current view of what a tab stop means
1122 # (equivalent width in spaces).
1123
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001124 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001125 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1126 return int(current)
1127
1128 # Set the text widget's current view of what a tab stop means.
1129
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001130 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001131 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001132 if self.get_tk_tabwidth() != newtabwidth:
1133 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001134 pixels = text.tk.call("font", "measure", text["font"],
1135 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001136 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001137 text.configure(tabs=pixels)
1138
Guido van Rossum33d26892007-08-05 15:29:28 +00001139### begin autoindent code ### (configuration was moved to beginning of class)
1140
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001141 def set_indentation_params(self, is_py_src, guess=True):
1142 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001143 i = self.guess_indent()
1144 if 2 <= i <= 8:
1145 self.indentwidth = i
1146 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001147 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001148 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001149
1150 def smart_backspace_event(self, event):
1151 text = self.text
1152 first, last = self.get_selection_indices()
1153 if first and last:
1154 text.delete(first, last)
1155 text.mark_set("insert", first)
1156 return "break"
1157 # Delete whitespace left, until hitting a real char or closest
1158 # preceding virtual tab stop.
1159 chars = text.get("insert linestart", "insert")
1160 if chars == '':
1161 if text.compare("insert", ">", "1.0"):
1162 # easy: delete preceding newline
1163 text.delete("insert-1c")
1164 else:
1165 text.bell() # at start of buffer
1166 return "break"
1167 if chars[-1] not in " \t":
1168 # easy: delete preceding real char
1169 text.delete("insert-1c")
1170 return "break"
1171 # Ick. It may require *inserting* spaces if we back up over a
1172 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001173 tabwidth = self.tabwidth
1174 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001175 assert have > 0
1176 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001177 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001178 if self.context_use_ps1:
1179 last_line_of_prompt = sys.ps1.split('\n')[-1]
1180 else:
1181 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001182 ncharsdeleted = 0
1183 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001184 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001185 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001186 chars = chars[:-1]
1187 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001188 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001189 if have <= want or chars[-1] not in " \t":
1190 break
1191 text.undo_block_start()
1192 text.delete("insert-%dc" % ncharsdeleted, "insert")
1193 if have < want:
1194 text.insert("insert", ' ' * (want - have))
1195 text.undo_block_stop()
1196 return "break"
1197
1198 def smart_indent_event(self, event):
1199 # if intraline selection:
1200 # delete it
1201 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001202 # do indent-region
1203 # else:
1204 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001205 text = self.text
1206 first, last = self.get_selection_indices()
1207 text.undo_block_start()
1208 try:
1209 if first and last:
1210 if index2line(first) != index2line(last):
1211 return self.indent_region_event(event)
1212 text.delete(first, last)
1213 text.mark_set("insert", first)
1214 prefix = text.get("insert linestart", "insert")
1215 raw, effective = classifyws(prefix, self.tabwidth)
1216 if raw == len(prefix):
1217 # only whitespace to the left
1218 self.reindent_to(effective + self.indentwidth)
1219 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001220 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001221 if self.usetabs:
1222 pad = '\t'
1223 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001224 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001225 n = self.indentwidth
1226 pad = ' ' * (n - effective % n)
1227 text.insert("insert", pad)
1228 text.see("insert")
1229 return "break"
1230 finally:
1231 text.undo_block_stop()
1232
1233 def newline_and_indent_event(self, event):
1234 text = self.text
1235 first, last = self.get_selection_indices()
1236 text.undo_block_start()
1237 try:
1238 if first and last:
1239 text.delete(first, last)
1240 text.mark_set("insert", first)
1241 line = text.get("insert linestart", "insert")
1242 i, n = 0, len(line)
1243 while i < n and line[i] in " \t":
1244 i = i+1
1245 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001246 # the cursor is in or at leading indentation in a continuation
1247 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001248 text.insert("insert linestart", '\n')
1249 return "break"
1250 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001251 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001252 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001253 last_line_of_prompt = sys.ps1.split('\n')[-1]
1254 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001255 line = line[:-1]
1256 i = i+1
1257 if i:
1258 text.delete("insert - %d chars" % i, "insert")
1259 # strip whitespace after insert point
1260 while text.get("insert") in " \t":
1261 text.delete("insert")
1262 # start new line
1263 text.insert("insert", '\n')
1264
1265 # adjust indentation for continuations and block
1266 # open/close first need to find the last stmt
1267 lno = index2line(text.index('insert'))
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001268 y = pyparse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001269 if not self.context_use_ps1:
1270 for context in self.num_context_lines:
1271 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001272 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001273 rawtext = text.get(startatindex, "insert")
1274 y.set_str(rawtext)
1275 bod = y.find_good_parse_start(
1276 self.context_use_ps1,
1277 self._build_char_in_string_func(startatindex))
1278 if bod is not None or startat == 1:
1279 break
1280 y.set_lo(bod or 0)
1281 else:
1282 r = text.tag_prevrange("console", "insert")
1283 if r:
1284 startatindex = r[1]
1285 else:
1286 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001287 rawtext = text.get(startatindex, "insert")
1288 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001289 y.set_lo(0)
1290
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001291 c = y.get_continuation_type()
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001292 if c != pyparse.C_NONE:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001293 # The current stmt hasn't ended yet.
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001294 if c == pyparse.C_STRING_FIRST_LINE:
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001295 # after the first line of a string; do not indent at all
1296 pass
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001297 elif c == pyparse.C_STRING_NEXT_LINES:
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001298 # inside a string which started before this line;
1299 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001300 text.insert("insert", indent)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001301 elif c == pyparse.C_BRACKET:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001302 # line up with the first (if any) element of the
1303 # last open bracket structure; else indent one
1304 # level beyond the indent of the line with the
1305 # last open bracket
1306 self.reindent_to(y.compute_bracket_indent())
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001307 elif c == pyparse.C_BACKSLASH:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001308 # if more than one line in this stmt already, just
1309 # mimic the current indent; else if initial line
1310 # has a start on an assignment stmt, indent to
1311 # beyond leftmost =; else to beyond first chunk of
1312 # non-whitespace on initial line
1313 if y.get_num_lines_in_stmt() > 1:
1314 text.insert("insert", indent)
1315 else:
1316 self.reindent_to(y.compute_backslash_indent())
1317 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001318 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001319 return "break"
1320
1321 # This line starts a brand new stmt; indent relative to
1322 # indentation of initial line of closest preceding
1323 # interesting stmt.
1324 indent = y.get_base_indent_string()
1325 text.insert("insert", indent)
1326 if y.is_block_opener():
1327 self.smart_indent_event(event)
1328 elif indent and y.is_block_closer():
1329 self.smart_backspace_event(event)
1330 return "break"
1331 finally:
1332 text.see("insert")
1333 text.undo_block_stop()
1334
Martin Panter7462b6492015-11-02 03:37:02 +00001335 # Our editwin provides an is_char_in_string function that works
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001336 # with a Tk text index, but PyParse only knows about offsets into
1337 # a string. This builds a function for PyParse that accepts an
1338 # offset.
1339
1340 def _build_char_in_string_func(self, startindex):
1341 def inner(offset, _startindex=startindex,
1342 _icis=self.is_char_in_string):
1343 return _icis(_startindex + "+%dc" % offset)
1344 return inner
1345
1346 def indent_region_event(self, event):
1347 head, tail, chars, lines = self.get_region()
1348 for pos in range(len(lines)):
1349 line = lines[pos]
1350 if line:
1351 raw, effective = classifyws(line, self.tabwidth)
1352 effective = effective + self.indentwidth
1353 lines[pos] = self._make_blanks(effective) + line[raw:]
1354 self.set_region(head, tail, chars, lines)
1355 return "break"
1356
1357 def dedent_region_event(self, event):
1358 head, tail, chars, lines = self.get_region()
1359 for pos in range(len(lines)):
1360 line = lines[pos]
1361 if line:
1362 raw, effective = classifyws(line, self.tabwidth)
1363 effective = max(effective - self.indentwidth, 0)
1364 lines[pos] = self._make_blanks(effective) + line[raw:]
1365 self.set_region(head, tail, chars, lines)
1366 return "break"
1367
1368 def comment_region_event(self, event):
1369 head, tail, chars, lines = self.get_region()
1370 for pos in range(len(lines) - 1):
1371 line = lines[pos]
1372 lines[pos] = '##' + line
1373 self.set_region(head, tail, chars, lines)
1374
1375 def uncomment_region_event(self, event):
1376 head, tail, chars, lines = self.get_region()
1377 for pos in range(len(lines)):
1378 line = lines[pos]
1379 if not line:
1380 continue
1381 if line[:2] == '##':
1382 line = line[2:]
1383 elif line[:1] == '#':
1384 line = line[1:]
1385 lines[pos] = line
1386 self.set_region(head, tail, chars, lines)
1387
1388 def tabify_region_event(self, event):
1389 head, tail, chars, lines = self.get_region()
1390 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001391 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001392 for pos in range(len(lines)):
1393 line = lines[pos]
1394 if line:
1395 raw, effective = classifyws(line, tabwidth)
1396 ntabs, nspaces = divmod(effective, tabwidth)
1397 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1398 self.set_region(head, tail, chars, lines)
1399
1400 def untabify_region_event(self, event):
1401 head, tail, chars, lines = self.get_region()
1402 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001403 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001404 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001405 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001406 self.set_region(head, tail, chars, lines)
1407
1408 def toggle_tabs_event(self, event):
1409 if self.askyesno(
1410 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001411 "Turn tabs " + ("on", "off")[self.usetabs] +
1412 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001413 ("will be", "remains at")[self.usetabs] + " 8." +
1414 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001415 parent=self.text):
1416 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001417 # Try to prevent inconsistent indentation.
1418 # User must change indent width manually after using tabs.
1419 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001420 return "break"
1421
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001422 # XXX this isn't bound to anything -- see tabwidth comments
1423## def change_tabwidth_event(self, event):
1424## new = self._asktabwidth()
1425## if new != self.tabwidth:
1426## self.tabwidth = new
1427## self.set_indentation_params(0, guess=0)
1428## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001429
1430 def change_indentwidth_event(self, event):
1431 new = self.askinteger(
1432 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001433 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001434 parent=self.text,
1435 initialvalue=self.indentwidth,
1436 minvalue=2,
1437 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001438 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001439 self.indentwidth = new
1440 return "break"
1441
1442 def get_region(self):
1443 text = self.text
1444 first, last = self.get_selection_indices()
1445 if first and last:
1446 head = text.index(first + " linestart")
1447 tail = text.index(last + "-1c lineend +1c")
1448 else:
1449 head = text.index("insert linestart")
1450 tail = text.index("insert lineend +1c")
1451 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001452 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001453 return head, tail, chars, lines
1454
1455 def set_region(self, head, tail, chars, lines):
1456 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001457 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001458 if newchars == chars:
1459 text.bell()
1460 return
1461 text.tag_remove("sel", "1.0", "end")
1462 text.mark_set("insert", head)
1463 text.undo_block_start()
1464 text.delete(head, tail)
1465 text.insert(head, newchars)
1466 text.undo_block_stop()
1467 text.tag_add("sel", head, "insert")
1468
1469 # Make string that displays as n leading blanks.
1470
1471 def _make_blanks(self, n):
1472 if self.usetabs:
1473 ntabs, nspaces = divmod(n, self.tabwidth)
1474 return '\t' * ntabs + ' ' * nspaces
1475 else:
1476 return ' ' * n
1477
1478 # Delete from beginning of line to insert point, then reinsert
1479 # column logical (meaning use tabs if appropriate) spaces.
1480
1481 def reindent_to(self, column):
1482 text = self.text
1483 text.undo_block_start()
1484 if text.compare("insert linestart", "!=", "insert"):
1485 text.delete("insert linestart", "insert")
1486 if column:
1487 text.insert("insert", self._make_blanks(column))
1488 text.undo_block_stop()
1489
1490 def _asktabwidth(self):
1491 return self.askinteger(
1492 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001493 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001494 parent=self.text,
1495 initialvalue=self.indentwidth,
1496 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001497 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001498
1499 # Guess indentwidth from text content.
1500 # Return guessed indentwidth. This should not be believed unless
1501 # it's in a reasonable range (e.g., it will be 0 if no indented
1502 # blocks are found).
1503
1504 def guess_indent(self):
1505 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1506 if opener and indented:
1507 raw, indentsmall = classifyws(opener, self.tabwidth)
1508 raw, indentlarge = classifyws(indented, self.tabwidth)
1509 else:
1510 indentsmall = indentlarge = 0
1511 return indentlarge - indentsmall
1512
1513# "line.col" -> line, as an int
1514def index2line(index):
1515 return int(float(index))
1516
1517# Look at the leading whitespace in s.
1518# Return pair (# of leading ws characters,
1519# effective # of leading blanks after expanding
1520# tabs to width tabwidth)
1521
1522def classifyws(s, tabwidth):
1523 raw = effective = 0
1524 for ch in s:
1525 if ch == ' ':
1526 raw = raw + 1
1527 effective = effective + 1
1528 elif ch == '\t':
1529 raw = raw + 1
1530 effective = (effective // tabwidth + 1) * tabwidth
1531 else:
1532 break
1533 return raw, effective
1534
1535import tokenize
1536_tokenize = tokenize
1537del tokenize
1538
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001539class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001540
1541 # .run() chews over the Text widget, looking for a block opener
1542 # and the stmt following it. Returns a pair,
1543 # (line containing block opener, line containing stmt)
1544 # Either or both may be None.
1545
1546 def __init__(self, text, tabwidth):
1547 self.text = text
1548 self.tabwidth = tabwidth
1549 self.i = self.finished = 0
1550 self.blkopenline = self.indentedline = None
1551
1552 def readline(self):
1553 if self.finished:
1554 return ""
1555 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001556 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001557 if self.text.compare(mark, ">=", "end"):
1558 return ""
1559 return self.text.get(mark, mark + " lineend+1c")
1560
1561 def tokeneater(self, type, token, start, end, line,
1562 INDENT=_tokenize.INDENT,
1563 NAME=_tokenize.NAME,
1564 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1565 if self.finished:
1566 pass
1567 elif type == NAME and token in OPENERS:
1568 self.blkopenline = line
1569 elif type == INDENT and self.blkopenline:
1570 self.indentedline = line
1571 self.finished = 1
1572
1573 def run(self):
1574 save_tabsize = _tokenize.tabsize
1575 _tokenize.tabsize = self.tabwidth
1576 try:
1577 try:
Trent Nelson428de652008-03-18 22:41:35 +00001578 tokens = _tokenize.generate_tokens(self.readline)
1579 for token in tokens:
1580 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001581 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001582 # since we cut off the tokenizer early, we can trigger
1583 # spurious errors
1584 pass
1585 finally:
1586 _tokenize.tabsize = save_tabsize
1587 return self.blkopenline, self.indentedline
1588
1589### end autoindent code ###
1590
David Scherer7aced172000-08-15 01:13:23 +00001591def prepstr(s):
1592 # Helper to extract the underscore from a string, e.g.
1593 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001594 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001595 if i >= 0:
1596 s = s[:i] + s[i+1:]
1597 return i, s
1598
1599
1600keynames = {
1601 'bracketleft': '[',
1602 'bracketright': ']',
1603 'slash': '/',
1604}
1605
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001606def get_accelerator(keydefs, eventname):
1607 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001608 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1609 # if not keylist:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001610 if (not keylist) or (macosx.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001611 "<<open-module>>",
1612 "<<goto-line>>",
1613 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001614 return ""
1615 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001616 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001617 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1618 s = re.sub("Key-", "", s)
1619 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1620 s = re.sub("Control-", "Ctrl-", s)
1621 s = re.sub("-", "+", s)
1622 s = re.sub("><", " ", s)
1623 s = re.sub("<", "", s)
1624 s = re.sub(">", "", s)
1625 return s
1626
1627
1628def fixwordbreaks(root):
1629 # Make sure that Tk's double-click and next/previous word
1630 # operations use our definition of a word (i.e. an identifier)
1631 tk = root.tk
1632 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1633 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1634 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1635
1636
Terry Jan Reedycd567362014-10-17 01:31:35 -04001637def _editor_window(parent): # htest #
1638 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001639 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001640 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001641 if sys.argv[1:]:
1642 filename = sys.argv[1]
1643 else:
1644 filename = None
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001645 macosx.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001646 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001647 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001648 # Does not stop error, neither does following
1649 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001650
1651if __name__ == '__main__':
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001652 from idlelib.idle_test.htest import run
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04001653 run(_editor_window)