blob: b214c6ab9b7af906992c1fd944d893252e10d921 [file] [log] [blame]
Brett Cannonaef82d32012-04-14 20:44:23 -04001import importlib
Brett Cannon50793b42013-06-07 13:17:48 -04002import importlib.abc
Eric Snow6029e082014-01-25 15:32:46 -07003import importlib.util
David Scherer7aced172000-08-15 01:13:23 +00004import os
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -04005import platform
David Scherer7aced172000-08-15 01:13:23 +00006import re
Guido van Rossum33d26892007-08-05 15:29:28 +00007import string
Brett Cannonaef82d32012-04-14 20:44:23 -04008import sys
Georg Brandl14fc4272008-05-17 18:39:55 +00009from tkinter import *
10import tkinter.simpledialog as tkSimpleDialog
11import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +000012import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000013import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000014
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040015from idlelib.multicall import MultiCallCreator
16from idlelib import windows
17from idlelib import search
18from idlelib import grep
19from idlelib import replace
20from idlelib import pyparse
21from idlelib.config import idleConf
22from idlelib import help_about, textview, configdialog
23from idlelib import macosx
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040024from idlelib import help
David Scherer7aced172000-08-15 01:13:23 +000025
26# The default tab setting for a Text widget, in average-width characters.
27TK_TABWIDTH_DEFAULT = 8
28
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -040029_py_version = ' (%s)' % platform.python_version()
30
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000031def _sphinx_version():
32 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
33 major, minor, micro, level, serial = sys.version_info
34 release = '%s%s' % (major, minor)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020035 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000036 if level == 'candidate':
37 release += 'rc%s' % (serial,)
38 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000039 release += '%s%s' % (level[0], serial)
40 return release
41
Terry Jan Reedye91e7632012-02-05 15:14:20 -050042
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000043class EditorWindow(object):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040044 from idlelib.percolator import Percolator
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -040045 from idlelib.colorizer import ColorDelegator, color_config
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040046 from idlelib.undo import UndoDelegator
47 from idlelib.iomenu import IOBinding, filesystemencoding, encoding
48 from idlelib import mainmenu
Guilherme Polo5424b0a2008-05-25 15:26:44 +000049 from tkinter import Toplevel
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040050 from idlelib.statusbar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000051
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000052 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000053
54 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000055 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +010056 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000057 if sys.platform.count('linux'):
58 # look for html docs in a couple of standard places
59 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
60 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
61 dochome = '/var/www/html/python/index.html'
62 else:
63 basepath = '/usr/share/doc/' # standard location
64 dochome = os.path.join(basepath, pyver,
65 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000066 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +010067 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000068 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +000069 if os.path.isfile(chmfile):
70 dochome = chmfile
Ned Deilyb7601672014-03-27 20:49:14 -070071 elif sys.platform == 'darwin':
72 # documentation may be stored inside a python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +010073 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +000074 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000075 dochome = os.path.normpath(dochome)
76 if os.path.isfile(dochome):
77 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +000078 if sys.platform == 'darwin':
79 # Safari requires real file:-URLs
80 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000081 else:
Terry Jan Reedyb6e17782014-09-19 22:54:15 -040082 EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
David Scherer7aced172000-08-15 01:13:23 +000083 self.flist = flist
84 root = root or flist.root
85 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +000086 try:
87 sys.ps1
88 except AttributeError:
89 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +000090 self.menubar = Menu(root)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040091 self.top = top = windows.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000092 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +000093 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +020094 #self.top.instance_dict makes flist.inversedict available to
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040095 #configdialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +000096 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +000097 else:
98 self.tkinter_vars = {} # keys: Tkinter event names
99 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000100 self.top.instance_dict = {}
101 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000102 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000103 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000104 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200105 self.width = idleConf.GetOption('main', 'EditorWindow',
106 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000107 text_options = {
108 'name': 'text',
109 'padx': 5,
110 'wrap': 'none',
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500111 'highlightthickness': 0,
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000112 'width': self.width,
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200113 'height': idleConf.GetOption('main', 'EditorWindow',
114 'height', type='int')}
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000115 if TkVersion >= 8.5:
116 # Starting with tk 8.5 we have to set the new tabstyle option
117 # to 'wordprocessor' to achieve the same display of tabs as in
118 # older tk versions.
119 text_options['tabstyle'] = 'wordprocessor'
120 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000121 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000122
123 self.createmenubar()
124 self.apply_bindings()
125
126 self.top.protocol("WM_DELETE_WINDOW", self.close)
127 self.top.bind("<<close-window>>", self.close_event)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400128 if macosx.isAquaTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000129 # Command-W on editorwindows doesn't work without this.
130 text.bind('<<close-window>>', self.close_event)
Terry Jan Reedy3c7eccd02015-09-22 21:10:27 -0400131 # Some OS X systems have only one mouse button, so use
132 # control-click for popup context menus there. For two
133 # buttons, AquaTk defines <2> as the right button, not <3>.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000134 text.bind("<Control-Button-1>",self.right_menu_event)
Terry Jan Reedy3c7eccd02015-09-22 21:10:27 -0400135 text.bind("<2>", self.right_menu_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000136 else:
Terry Jan Reedy3c7eccd02015-09-22 21:10:27 -0400137 # Elsewhere, use right-click for popup menus.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000138 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000139 text.bind("<<cut>>", self.cut)
140 text.bind("<<copy>>", self.copy)
141 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000142 text.bind("<<center-insert>>", self.center_insert_event)
143 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000144 text.bind("<<python-docs>>", self.python_docs)
145 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000146 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000147 text.bind("<<open-module>>", self.open_module)
148 text.bind("<<do-nothing>>", lambda event: "break")
149 text.bind("<<select-all>>", self.select_all)
150 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000151 text.bind("<<find>>", self.find_event)
152 text.bind("<<find-again>>", self.find_again_event)
153 text.bind("<<find-in-files>>", self.find_in_files_event)
154 text.bind("<<find-selection>>", self.find_selection_event)
155 text.bind("<<replace>>", self.replace_event)
156 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000157 text.bind("<<smart-backspace>>",self.smart_backspace_event)
158 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
159 text.bind("<<smart-indent>>",self.smart_indent_event)
160 text.bind("<<indent-region>>",self.indent_region_event)
161 text.bind("<<dedent-region>>",self.dedent_region_event)
162 text.bind("<<comment-region>>",self.comment_region_event)
163 text.bind("<<uncomment-region>>",self.uncomment_region_event)
164 text.bind("<<tabify-region>>",self.tabify_region_event)
165 text.bind("<<untabify-region>>",self.untabify_region_event)
166 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
167 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000168 text.bind("<Left>", self.move_at_edge_if_selection(0))
169 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000170 text.bind("<<del-word-left>>", self.del_word_left)
171 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000172 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000173
David Scherer7aced172000-08-15 01:13:23 +0000174 if flist:
175 flist.inversedict[self] = key
176 if key:
177 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000178 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000179 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
180 text.bind("<<open-class-browser>>", self.open_class_browser)
181 text.bind("<<open-path-browser>>", self.open_path_browser)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400182 text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
David Scherer7aced172000-08-15 01:13:23 +0000183
Steven M. Gava898a3652001-10-07 11:10:44 +0000184 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000185 vbar['command'] = text.yview
186 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000187 text['yscrollcommand'] = vbar.set
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400188 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000189 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
190 text.pack(side=TOP, fill=BOTH, expand=1)
191 text.focus_set()
192
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000193 # usetabs true -> literal tab characters are used by indent and
194 # dedent cmds, possibly mixed with spaces if
195 # indentwidth is not a multiple of tabwidth,
196 # which will cause Tabnanny to nag!
197 # false -> tab characters are converted to spaces by indent
198 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000199 # Although use-spaces=0 can be configured manually in config-main.def,
200 # configuration of tabs v. spaces is not supported in the configuration
201 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200202 usespaces = idleConf.GetOption('main', 'Indent',
203 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000204 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000205
206 # tabwidth is the display width of a literal tab character.
207 # CAUTION: telling Tk to use anything other than its default
208 # tab setting causes it to use an entirely different tabbing algorithm,
209 # treating tab stops as fixed distances from the left margin.
210 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000211 self.tabwidth = 8 # must remain 8 until Tk is fixed.
212
213 # indentwidth is the number of screen characters per indent level.
214 # The recommended Python indentation is four spaces.
215 self.indentwidth = self.tabwidth
216 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000217
218 # If context_use_ps1 is true, parsing searches back for a ps1 line;
219 # else searches for a popular (if, def, ...) Python stmt.
220 self.context_use_ps1 = False
221
222 # When searching backwards for a reliable place to begin parsing,
223 # first start num_context_lines[0] lines back, then
224 # num_context_lines[1] lines back if that didn't work, and so on.
225 # The last value should be huge (larger than the # of lines in a
226 # conceivable file).
227 # Making the initial values larger slows things down more often.
228 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000229 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000230 self.undo = undo = self.UndoDelegator()
231 per.insertfilter(undo)
232 text.undo_block_start = undo.undo_block_start
233 text.undo_block_stop = undo.undo_block_stop
234 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000235 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000236 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000237 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000238 self.good_load = False
239 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000240 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000241 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000242 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000243 if io.loadfile(filename):
244 self.good_load = True
245 is_py_src = self.ispythonsource(filename)
246 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000247 else:
248 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500249 self.good_load = True
250
Christian Heimesa156e092008-02-16 07:38:31 +0000251 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000252 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000253 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000254 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000255 menu = self.menudict.get('windows')
256 if menu:
257 end = menu.index("end")
258 if end is None:
259 end = -1
260 if end >= 0:
261 menu.add_separator()
262 end = end + 1
263 self.wmenu_end = end
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400264 windows.register_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000265
266 # Some abstractions so IDLE extensions are cross-IDE
267 self.askyesno = tkMessageBox.askyesno
268 self.askinteger = tkSimpleDialog.askinteger
269 self.showerror = tkMessageBox.showerror
270
Martin v. Löwis307021f2005-11-27 16:59:04 +0000271 def _filename_to_unicode(self, filename):
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400272 """Return filename as BMP unicode so diplayable in Tk."""
273 # Decode bytes to unicode.
274 if isinstance(filename, bytes):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000275 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400276 filename = filename.decode(self.filesystemencoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000277 except UnicodeDecodeError:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000278 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400279 filename = filename.decode(self.encoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000280 except UnicodeDecodeError:
281 # byte-to-byte conversion
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400282 filename = filename.decode('iso8859-1')
283 # Replace non-BMP char with diamond questionmark.
284 return re.sub('[\U00010000-\U0010FFFF]', '\ufffd', filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000285
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000286 def new_callback(self, event):
287 dirname, basename = self.io.defaultfilename()
288 self.flist.new(dirname)
289 return "break"
290
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000291 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400292 if (event.state & 4) != 0 and event.keysym == "Home":
293 # state&4==Control. If <Control-Home>, use the Tk binding.
294 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000295 if self.text.index("iomark") and \
296 self.text.compare("iomark", "<=", "insert lineend") and \
297 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400298 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000299 insertpt = int(self.text.index("iomark").split(".")[1])
300 else:
301 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000302 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000303 if line[insertpt] not in (' ','\t'):
304 break
305 else:
306 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000307 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000308 if insertpt == lineat:
309 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000310 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000311 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400312 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000313 self.text.tag_remove("sel", "1.0", "end")
314 else:
315 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200316 # there was no previous selection
317 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400318 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200319 if self.text.compare(self.text.index("sel.first"), "<",
320 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400321 self.text.mark_set("my_anchor", "sel.first") # extend back
322 else:
323 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000324 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400325 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000326 if self.text.compare(first,">",last):
327 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000328 self.text.tag_remove("sel", "1.0", "end")
329 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000330 self.text.mark_set("insert", dest)
331 self.text.see("insert")
332 return "break"
333
David Scherer7aced172000-08-15 01:13:23 +0000334 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000335 self.status_bar = self.MultiStatusBar(self.top)
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500336 sep = Frame(self.top, height=1, borderwidth=1, background='grey75')
Ned Deilyb7601672014-03-27 20:49:14 -0700337 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000338 # Insert some padding to avoid obscuring some of the statusbar
339 # by the resize widget.
340 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000341 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
342 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
343 self.status_bar.pack(side=BOTTOM, fill=X)
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500344 sep.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000345 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
346 self.text.event_add("<<set-line-and-column>>",
347 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000348 self.text.after_idle(self.set_line_and_column)
349
350 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000351 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000352 self.status_bar.set_label('column', 'Col: %s' % column)
353 self.status_bar.set_label('line', 'Ln: %s' % line)
354
David Scherer7aced172000-08-15 01:13:23 +0000355 menu_specs = [
356 ("file", "_File"),
357 ("edit", "_Edit"),
358 ("format", "F_ormat"),
359 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000360 ("options", "_Options"),
Ned Deilyccb416f2015-01-17 21:06:27 -0800361 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000362 ("help", "_Help"),
363 ]
364
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000365
David Scherer7aced172000-08-15 01:13:23 +0000366 def createmenubar(self):
367 mbar = self.menubar
368 self.menudict = menudict = {}
369 for name, label in self.menu_specs:
370 underline, label = prepstr(label)
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400371 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000372 mbar.add_cascade(label=label, menu=menu, underline=underline)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400373 if macosx.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000374 # Insert the application menu
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400375 menudict['application'] = menu = Menu(mbar, name='apple',
376 tearoff=0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000377 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000378 self.fill_menus()
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400379 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000380 self.menudict['file'].insert_cascade(3, label='Recent Files',
381 underline=0,
382 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000383 self.base_helpmenu_length = self.menudict['help'].index(END)
384 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000385
386 def postwindowsmenu(self):
387 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000388 menu = self.menudict['windows']
389 end = menu.index("end")
390 if end is None:
391 end = -1
392 if end > self.wmenu_end:
393 menu.delete(self.wmenu_end+1, end)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400394 windows.add_windows_to_menu(menu)
David Scherer7aced172000-08-15 01:13:23 +0000395
396 rmenu = None
397
398 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000399 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
400 if not self.rmenu:
401 self.make_rmenu()
402 rmenu = self.rmenu
403 self.event = event
404 iswin = sys.platform[:3] == 'win'
405 if iswin:
406 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200407
Roger Serwy6b2918a2013-04-07 12:15:52 -0500408 for item in self.rmenu_specs:
409 try:
410 label, eventname, verify_state = item
411 except ValueError: # see issue1207589
412 continue
413
Andrew Svetlovd1837672012-11-01 22:41:19 +0200414 if verify_state is None:
415 continue
416 state = getattr(self, verify_state)()
417 rmenu.entryconfigure(label, state=state)
418
419
David Scherer7aced172000-08-15 01:13:23 +0000420 rmenu.tk_popup(event.x_root, event.y_root)
421 if iswin:
422 self.text.config(cursor="ibeam")
423
424 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200425 # ("Label", "<<virtual-event>>", "statefuncname"), ...
426 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000427 ]
428
429 def make_rmenu(self):
430 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500431 for item in self.rmenu_specs:
432 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200433 if label is not None:
434 def command(text=self.text, eventname=eventname):
435 text.event_generate(eventname)
436 rmenu.add_command(label=label, command=command)
437 else:
438 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000439 self.rmenu = rmenu
440
Andrew Svetlovd1837672012-11-01 22:41:19 +0200441 def rmenu_check_cut(self):
442 return self.rmenu_check_copy()
443
444 def rmenu_check_copy(self):
445 try:
446 indx = self.text.index('sel.first')
447 except TclError:
448 return 'disabled'
449 else:
450 return 'normal' if indx else 'disabled'
451
452 def rmenu_check_paste(self):
453 try:
454 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
455 except TclError:
456 return 'disabled'
457 else:
458 return 'normal'
459
David Scherer7aced172000-08-15 01:13:23 +0000460 def about_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400461 "Handle Help 'About IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400462 # Synchronize with macosx.overrideRootMenu.about_dialog.
463 help_about.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000464
Steven M. Gava3b55a892001-11-21 05:56:26 +0000465 def config_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400466 "Handle Options 'Configure IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400467 # Synchronize with macosx.overrideRootMenu.config_dialog.
468 configdialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400469
David Scherer7aced172000-08-15 01:13:23 +0000470 def help_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400471 "Handle Help 'IDLE Help' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400472 # Synchronize with macosx.overrideRootMenu.help_dialog.
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500473 if self.root:
474 parent = self.root
475 else:
476 parent = self.top
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400477 help.show_idlehelp(parent)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000478
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000479 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000480 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000481 try:
482 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200483 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000484 tkMessageBox.showerror(title='Document Start Failure',
485 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000486 else:
487 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000488 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000489
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000490 def cut(self,event):
491 self.text.event_generate("<<Cut>>")
492 return "break"
493
494 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000495 if not self.text.tag_ranges("sel"):
496 # There is no selection, so do nothing and maybe interrupt.
497 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000498 self.text.event_generate("<<Copy>>")
499 return "break"
500
501 def paste(self,event):
502 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000503 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000504 return "break"
505
David Scherer7aced172000-08-15 01:13:23 +0000506 def select_all(self, event=None):
507 self.text.tag_add("sel", "1.0", "end-1c")
508 self.text.mark_set("insert", "1.0")
509 self.text.see("insert")
510 return "break"
511
512 def remove_selection(self, event=None):
513 self.text.tag_remove("sel", "1.0", "end")
514 self.text.see("insert")
515
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000516 def move_at_edge_if_selection(self, edge_index):
517 """Cursor move begins at start or end of selection
518
519 When a left/right cursor key is pressed create and return to Tkinter a
520 function which causes a cursor move from the associated edge of the
521 selection.
522
523 """
524 self_text_index = self.text.index
525 self_text_mark_set = self.text.mark_set
526 edges_table = ("sel.first+1c", "sel.last-1c")
527 def move_at_edge(event):
528 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
529 try:
530 self_text_index("sel.first")
531 self_text_mark_set("insert", edges_table[edge_index])
532 except TclError:
533 pass
534 return move_at_edge
535
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000536 def del_word_left(self, event):
537 self.text.event_generate('<Meta-Delete>')
538 return "break"
539
540 def del_word_right(self, event):
541 self.text.event_generate('<Meta-d>')
542 return "break"
543
Steven M. Gavac5976402002-01-04 03:06:08 +0000544 def find_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400545 search.find(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000546 return "break"
547
548 def find_again_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400549 search.find_again(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000550 return "break"
551
552 def find_selection_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400553 search.find_selection(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000554 return "break"
555
556 def find_in_files_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400557 grep.grep(self.text, self.io, self.flist)
Steven M. Gavac5976402002-01-04 03:06:08 +0000558 return "break"
559
560 def replace_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400561 replace.replace(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000562 return "break"
563
564 def goto_line_event(self, event):
565 text = self.text
566 lineno = tkSimpleDialog.askinteger("Goto",
567 "Go to line number:",parent=text)
568 if lineno is None:
569 return "break"
570 if lineno <= 0:
571 text.bell()
572 return "break"
573 text.mark_set("insert", "%d.0" % lineno)
574 text.see("insert")
575
David Scherer7aced172000-08-15 01:13:23 +0000576 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000577 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000578 try:
579 name = self.text.get("sel.first", "sel.last")
580 except TclError:
581 name = ""
582 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000583 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000584 name = tkSimpleDialog.askstring("Module",
585 "Enter the name of a Python module\n"
586 "to search on sys.path and open:",
587 parent=self.text, initialvalue=name)
588 if name:
589 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000590 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000591 return
David Scherer7aced172000-08-15 01:13:23 +0000592 # XXX Ought to insert current file's directory in front of path
593 try:
Eric Snow6029e082014-01-25 15:32:46 -0700594 spec = importlib.util.find_spec(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400595 except (ValueError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000596 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
597 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700598 if spec is None:
Brett Cannon50793b42013-06-07 13:17:48 -0400599 tkMessageBox.showerror("Import error", "module not found",
600 parent=self.text)
David Scherer7aced172000-08-15 01:13:23 +0000601 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700602 if not isinstance(spec.loader, importlib.abc.SourceLoader):
Brett Cannon50793b42013-06-07 13:17:48 -0400603 tkMessageBox.showerror("Import error", "not a source-based module",
604 parent=self.text)
605 return
606 try:
Eric Snow02b9f9d2014-01-06 20:42:59 -0700607 file_path = spec.loader.get_filename(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400608 except AttributeError:
609 tkMessageBox.showerror("Import error",
610 "loader does not support get_filename",
611 parent=self.text)
612 return
David Scherer7aced172000-08-15 01:13:23 +0000613 if self.flist:
Brett Cannon50793b42013-06-07 13:17:48 -0400614 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000615 else:
Brett Cannon50793b42013-06-07 13:17:48 -0400616 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400617 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000618
619 def open_class_browser(self, event=None):
620 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400621 if not (self.__class__.__name__ == 'PyShellEditorWindow'
622 and filename):
623 filename = self.open_module()
624 if filename is None:
625 return
David Scherer7aced172000-08-15 01:13:23 +0000626 head, tail = os.path.split(filename)
627 base, ext = os.path.splitext(tail)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400628 from idlelib import browser
629 browser.ClassBrowser(self.flist, base, [head])
David Scherer7aced172000-08-15 01:13:23 +0000630
631 def open_path_browser(self, event=None):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400632 from idlelib import pathbrowser
633 pathbrowser.PathBrowser(self.flist)
David Scherer7aced172000-08-15 01:13:23 +0000634
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400635 def open_turtle_demo(self, event = None):
636 import subprocess
637
638 cmd = [sys.executable,
639 '-c',
640 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400641 subprocess.Popen(cmd, shell=False)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400642
David Scherer7aced172000-08-15 01:13:23 +0000643 def gotoline(self, lineno):
644 if lineno is not None and lineno > 0:
645 self.text.mark_set("insert", "%d.0" % lineno)
646 self.text.tag_remove("sel", "1.0", "end")
647 self.text.tag_add("sel", "insert", "insert +1l")
648 self.center()
649
650 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000651 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000652 return True
David Scherer7aced172000-08-15 01:13:23 +0000653 base, ext = os.path.splitext(os.path.basename(filename))
654 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000655 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000656 line = self.text.get('1.0', '1.0 lineend')
657 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000658
659 def close_hook(self):
660 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000661 self.flist.unregister_maybe_terminate(self)
662 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000663
664 def set_close_hook(self, close_hook):
665 self.close_hook = close_hook
666
667 def filename_change_hook(self):
668 if self.flist:
669 self.flist.filename_changed_edit(self)
670 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000671 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000672 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000673
Christian Heimesa156e092008-02-16 07:38:31 +0000674 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000675 if self.color:
676 return
Christian Heimesa156e092008-02-16 07:38:31 +0000677 if self.ispythonsource(self.io.filename):
678 self.color = self.ColorDelegator()
679 # can add more colorizers here...
680 if self.color:
681 self.per.removefilter(self.undo)
682 self.per.insertfilter(self.color)
683 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000684
Christian Heimesa156e092008-02-16 07:38:31 +0000685 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000686 if not self.color:
687 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000688 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000689 self.per.removefilter(self.color)
690 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000691
Steven M. Gavab77d3432002-03-02 07:16:21 +0000692 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400693 "Update the color theme"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400694 # Called from self.filename_change_hook and from configdialog.py
Christian Heimesa156e092008-02-16 07:38:31 +0000695 self._rmcolorizer()
696 self._addcolorizer()
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -0400697 EditorWindow.color_config(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000698
Guido van Rossum33d26892007-08-05 15:29:28 +0000699 IDENTCHARS = string.ascii_letters + string.digits + "_"
700
701 def colorize_syntax_error(self, text, pos):
702 text.tag_add("ERROR", pos)
703 char = text.get(pos)
704 if char and char in self.IDENTCHARS:
705 text.tag_add("ERROR", pos + " wordstart", pos)
706 if '\n' == text.get(pos): # error at line end
707 text.mark_set("insert", pos)
708 else:
709 text.mark_set("insert", pos + "+1c")
710 text.see(pos)
711
Steven M. Gavab1585412002-03-12 00:21:56 +0000712 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000713 "Update the text widgets' font if it is changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400714 # Called from configdialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400715
716 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000717
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000718 def RemoveKeybindings(self):
719 "Remove the keybindings before they are changed."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400720 # Called from configdialog.py
721 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000722 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000723 self.text.event_delete(event, *keylist)
724 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000725 xkeydefs = idleConf.GetExtensionBindings(extensionName)
726 if xkeydefs:
727 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000728 self.text.event_delete(event, *keylist)
729
730 def ApplyKeybindings(self):
731 "Update the keybindings after they are changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400732 # Called from configdialog.py
733 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000734 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000735 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000736 xkeydefs = idleConf.GetExtensionBindings(extensionName)
737 if xkeydefs:
738 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000739 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000740 menuEventDict = {}
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400741 for menu in self.mainmenu.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000742 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000743 for item in menu[1]:
744 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000745 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000746 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000747 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700748 end = menu.index(END)
749 if end is None:
750 # Skip empty menus
751 continue
752 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000753 for index in range(0, end):
754 if menu.type(index) == 'command':
755 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000756 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000757 itemName = menu.entrycget(index, 'label')
758 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000759 if menubarItem in menuEventDict:
760 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000761 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000762 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000763 accel = get_accelerator(keydefs, event)
764 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000765
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000766 def set_notabs_indentwidth(self):
767 "Update the indentwidth if changed and not using tabs in this window"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400768 # Called from configdialog.py
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000769 if not self.usetabs:
770 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
771 type='int')
772
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000773 def reset_help_menu_entries(self):
774 "Update the additional help entries on the Help menu"
775 help_list = idleConf.GetAllExtraHelpSourcesList()
776 helpmenu = self.menudict['help']
777 # first delete the extra help entries, if any
778 helpmenu_length = helpmenu.index(END)
779 if helpmenu_length > self.base_helpmenu_length:
780 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
781 # then rebuild them
782 if help_list:
783 helpmenu.add_separator()
784 for entry in help_list:
785 cmd = self.__extra_help_callback(entry[1])
786 helpmenu.add_command(label=entry[0], command=cmd)
787 # and update the menu dictionary
788 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000789
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000790 def __extra_help_callback(self, helpfile):
791 "Create a callback with the helpfile value frozen at definition time"
792 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000793 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000794 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000795 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000796 try:
797 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200798 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000799 tkMessageBox.showerror(title='Document Start Failure',
800 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000801 else:
802 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000803 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000804
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000805 def update_recent_files_list(self, new_file=None):
806 "Load and update the recent files list and menus"
807 rf_list = []
808 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400809 with open(self.recent_files_path, 'r',
810 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000811 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000812 if new_file:
813 new_file = os.path.abspath(new_file) + '\n'
814 if new_file in rf_list:
815 rf_list.remove(new_file) # move to top
816 rf_list.insert(0, new_file)
817 # clean and save the recent files list
818 bad_paths = []
819 for path in rf_list:
820 if '\0' in path or not os.path.exists(path[0:-1]):
821 bad_paths.append(path)
822 rf_list = [path for path in rf_list if path not in bad_paths]
823 ulchars = "1234567890ABCDEFGHIJK"
824 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000825 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800826 with open(self.recent_files_path, 'w',
827 encoding='utf_8', errors='replace') as rf_file:
828 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200829 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800830 if not getattr(self.root, "recentfilelist_error_displayed", False):
831 self.root.recentfilelist_error_displayed = True
Terry Jan Reedy4c973e92015-10-27 03:38:02 -0400832 tkMessageBox.showwarning(title='IDLE Warning',
833 message="Cannot update File menu Recent Files list. "
834 "Your operating system says:\n%s\n"
835 "Select OK and IDLE will continue without updating."
836 % self._filename_to_unicode(str(err)),
Ned Deilyf505b742011-12-14 14:58:24 -0800837 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000838 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000839 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000840 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700841 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000842 for i, file_name in enumerate(rf_list):
843 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000844 # make unicode string to display non-ASCII chars correctly
845 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000846 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000847 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000848 command=callback,
849 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000850
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000851 def __recent_file_callback(self, file_name):
852 def open_recent_file(fn_closure=file_name):
853 self.io.open(editFile=fn_closure)
854 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000855
David Scherer7aced172000-08-15 01:13:23 +0000856 def saved_change_hook(self):
857 short = self.short_title()
858 long = self.long_title()
859 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400860 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000861 elif short:
862 title = short
863 elif long:
864 title = long
865 else:
866 title = "Untitled"
867 icon = short or long or title
868 if not self.get_saved():
869 title = "*%s*" % title
870 icon = "*%s" % icon
871 self.top.wm_title(title)
872 self.top.wm_iconname(icon)
873
874 def get_saved(self):
875 return self.undo.get_saved()
876
877 def set_saved(self, flag):
878 self.undo.set_saved(flag)
879
880 def reset_undo(self):
881 self.undo.reset_undo()
882
883 def short_title(self):
884 filename = self.io.filename
885 if filename:
886 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500887 else:
888 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000889 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400890 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000891
892 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000893 # return unicode string to display non-ASCII chars correctly
894 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000895
896 def center_insert_event(self, event):
897 self.center()
898
899 def center(self, mark="insert"):
900 text = self.text
901 top, bot = self.getwindowlines()
902 lineno = self.getlineno(mark)
903 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000904 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000905 text.yview(float(newtop))
906
907 def getwindowlines(self):
908 text = self.text
909 top = self.getlineno("@0,0")
910 bot = self.getlineno("@0,65535")
911 if top == bot and text.winfo_height() == 1:
912 # Geometry manager hasn't run yet
913 height = int(text['height'])
914 bot = top + height - 1
915 return top, bot
916
917 def getlineno(self, mark="insert"):
918 text = self.text
919 return int(float(text.index(mark)))
920
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000921 def get_geometry(self):
922 "Return (width, height, x, y)"
923 geom = self.top.wm_geometry()
924 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000925 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000926
David Scherer7aced172000-08-15 01:13:23 +0000927 def close_event(self, event):
928 self.close()
929
930 def maybesave(self):
931 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000932 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000933 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000934 self.top.deiconify()
935 self.top.lower()
936 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000937 return self.io.maybesave()
938
939 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000940 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000941 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000942 self._close()
943 return reply
944
945 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000946 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000947 self.update_recent_files_list(new_file=self.io.filename)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400948 windows.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000949 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000950 self.io.close()
951 self.io = None
952 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000953 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000954 self.color.close(False)
955 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000956 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000957 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000958 self.per.close()
959 self.per = None
960 self.top.destroy()
961 if self.close_hook:
962 # unless override: unregister from flist, terminate if last window
963 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000964
965 def load_extensions(self):
966 self.extensions = {}
967 self.load_standard_extensions()
968
969 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000970 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000971 if hasattr(ins, "close"):
972 ins.close()
973 self.extensions = {}
974
975 def load_standard_extensions(self):
976 for name in self.get_standard_extension_names():
977 try:
978 self.load_extension(name)
979 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000980 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000981 traceback.print_exc()
982
983 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000984 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000985
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400986 extfiles = { # map config-extension section names to new file names
987 'AutoComplete': 'autocomplete',
988 'AutoExpand': 'autoexpand',
989 'CallTips': 'calltips',
990 'CodeContext': 'codecontext',
991 'FormatParagraph': 'paragraph',
992 'ParenMatch': 'parenmatch',
993 'RstripExtension': 'rstrip',
994 'ScriptBinding': 'runscript',
995 'ZoomHeight': 'zoomheight',
996 }
997
David Scherer7aced172000-08-15 01:13:23 +0000998 def load_extension(self, name):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400999 fname = self.extfiles.get(name, name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001000 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001001 try:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001002 mod = importlib.import_module('.' + fname, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001003 except (ImportError, TypeError):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001004 mod = importlib.import_module(fname)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001005 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001006 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001007 raise
David Scherer7aced172000-08-15 01:13:23 +00001008 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001009 keydefs = idleConf.GetExtensionBindings(name)
1010 if hasattr(cls, "menudefs"):
1011 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001012 ins = cls(self)
1013 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001014 if keydefs:
1015 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001016 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001017 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001018 while methodname[:1] == '<':
1019 methodname = methodname[1:]
1020 while methodname[-1:] == '>':
1021 methodname = methodname[:-1]
1022 methodname = methodname + "_event"
1023 if hasattr(ins, methodname):
1024 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001025
1026 def apply_bindings(self, keydefs=None):
1027 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 text = self.text
1030 text.keydefs = keydefs
1031 for event, keylist in keydefs.items():
1032 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001033 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001034
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001035 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001036 """Add appropriate entries to the menus and submenus
1037
1038 Menus that are absent or None in self.menudict are ignored.
1039 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001040 if menudefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001041 menudefs = self.mainmenu.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001042 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001043 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001044 menudict = self.menudict
1045 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001046 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001047 menu = menudict.get(mname)
1048 if not menu:
1049 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001050 for entry in entrylist:
1051 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001052 menu.add_separator()
1053 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001054 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001055 checkbutton = (label[:1] == '!')
1056 if checkbutton:
1057 label = label[1:]
1058 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001059 accelerator = get_accelerator(keydefs, eventname)
1060 def command(text=text, eventname=eventname):
1061 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001062 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001063 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001064 menu.add_checkbutton(label=label, underline=underline,
1065 command=command, accelerator=accelerator,
1066 variable=var)
1067 else:
1068 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001069 command=command,
1070 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001071
1072 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001073 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001074 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001075 value = var.get()
1076 return value
1077 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001078 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001079
1080 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001081 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001082 if var:
1083 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001084 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001085 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001086
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001087 def get_var_obj(self, name, vartype=None):
1088 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001089 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001090 # create a Tkinter variable object with self.text as master:
1091 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001092 return var
1093
1094 # Tk implementations of "virtual text methods" -- each platform
1095 # reusing IDLE's support code needs to define these for its GUI's
1096 # flavor of widget.
1097
1098 # Is character at text_index in a Python string? Return 0 for
1099 # "guaranteed no", true for anything else. This info is expensive
1100 # to compute ab initio, but is probably already known by the
1101 # platform's colorizer.
1102
1103 def is_char_in_string(self, text_index):
1104 if self.color:
1105 # Return true iff colorizer hasn't (re)gotten this far
1106 # yet, or the character is tagged as being in a string
1107 return self.text.tag_prevrange("TODO", text_index) or \
1108 "STRING" in self.text.tag_names(text_index)
1109 else:
1110 # The colorizer is missing: assume the worst
1111 return 1
1112
1113 # If a selection is defined in the text widget, return (start,
1114 # end) as Tkinter text indices, otherwise return (None, None)
1115 def get_selection_indices(self):
1116 try:
1117 first = self.text.index("sel.first")
1118 last = self.text.index("sel.last")
1119 return first, last
1120 except TclError:
1121 return None, None
1122
1123 # Return the text widget's current view of what a tab stop means
1124 # (equivalent width in spaces).
1125
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001126 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001127 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1128 return int(current)
1129
1130 # Set the text widget's current view of what a tab stop means.
1131
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001132 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001133 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001134 if self.get_tk_tabwidth() != newtabwidth:
1135 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001136 pixels = text.tk.call("font", "measure", text["font"],
1137 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001138 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001139 text.configure(tabs=pixels)
1140
Guido van Rossum33d26892007-08-05 15:29:28 +00001141### begin autoindent code ### (configuration was moved to beginning of class)
1142
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001143 def set_indentation_params(self, is_py_src, guess=True):
1144 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001145 i = self.guess_indent()
1146 if 2 <= i <= 8:
1147 self.indentwidth = i
1148 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001149 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001150 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001151
1152 def smart_backspace_event(self, event):
1153 text = self.text
1154 first, last = self.get_selection_indices()
1155 if first and last:
1156 text.delete(first, last)
1157 text.mark_set("insert", first)
1158 return "break"
1159 # Delete whitespace left, until hitting a real char or closest
1160 # preceding virtual tab stop.
1161 chars = text.get("insert linestart", "insert")
1162 if chars == '':
1163 if text.compare("insert", ">", "1.0"):
1164 # easy: delete preceding newline
1165 text.delete("insert-1c")
1166 else:
1167 text.bell() # at start of buffer
1168 return "break"
1169 if chars[-1] not in " \t":
1170 # easy: delete preceding real char
1171 text.delete("insert-1c")
1172 return "break"
1173 # Ick. It may require *inserting* spaces if we back up over a
1174 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001175 tabwidth = self.tabwidth
1176 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001177 assert have > 0
1178 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001179 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001180 if self.context_use_ps1:
1181 last_line_of_prompt = sys.ps1.split('\n')[-1]
1182 else:
1183 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001184 ncharsdeleted = 0
1185 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001186 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001187 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001188 chars = chars[:-1]
1189 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001190 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001191 if have <= want or chars[-1] not in " \t":
1192 break
1193 text.undo_block_start()
1194 text.delete("insert-%dc" % ncharsdeleted, "insert")
1195 if have < want:
1196 text.insert("insert", ' ' * (want - have))
1197 text.undo_block_stop()
1198 return "break"
1199
1200 def smart_indent_event(self, event):
1201 # if intraline selection:
1202 # delete it
1203 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001204 # do indent-region
1205 # else:
1206 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001207 text = self.text
1208 first, last = self.get_selection_indices()
1209 text.undo_block_start()
1210 try:
1211 if first and last:
1212 if index2line(first) != index2line(last):
1213 return self.indent_region_event(event)
1214 text.delete(first, last)
1215 text.mark_set("insert", first)
1216 prefix = text.get("insert linestart", "insert")
1217 raw, effective = classifyws(prefix, self.tabwidth)
1218 if raw == len(prefix):
1219 # only whitespace to the left
1220 self.reindent_to(effective + self.indentwidth)
1221 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001222 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001223 if self.usetabs:
1224 pad = '\t'
1225 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001226 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001227 n = self.indentwidth
1228 pad = ' ' * (n - effective % n)
1229 text.insert("insert", pad)
1230 text.see("insert")
1231 return "break"
1232 finally:
1233 text.undo_block_stop()
1234
1235 def newline_and_indent_event(self, event):
1236 text = self.text
1237 first, last = self.get_selection_indices()
1238 text.undo_block_start()
1239 try:
1240 if first and last:
1241 text.delete(first, last)
1242 text.mark_set("insert", first)
1243 line = text.get("insert linestart", "insert")
1244 i, n = 0, len(line)
1245 while i < n and line[i] in " \t":
1246 i = i+1
1247 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001248 # the cursor is in or at leading indentation in a continuation
1249 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001250 text.insert("insert linestart", '\n')
1251 return "break"
1252 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001253 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001254 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001255 last_line_of_prompt = sys.ps1.split('\n')[-1]
1256 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001257 line = line[:-1]
1258 i = i+1
1259 if i:
1260 text.delete("insert - %d chars" % i, "insert")
1261 # strip whitespace after insert point
1262 while text.get("insert") in " \t":
1263 text.delete("insert")
1264 # start new line
1265 text.insert("insert", '\n')
1266
1267 # adjust indentation for continuations and block
1268 # open/close first need to find the last stmt
1269 lno = index2line(text.index('insert'))
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001270 y = pyparse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001271 if not self.context_use_ps1:
1272 for context in self.num_context_lines:
1273 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001274 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001275 rawtext = text.get(startatindex, "insert")
1276 y.set_str(rawtext)
1277 bod = y.find_good_parse_start(
1278 self.context_use_ps1,
1279 self._build_char_in_string_func(startatindex))
1280 if bod is not None or startat == 1:
1281 break
1282 y.set_lo(bod or 0)
1283 else:
1284 r = text.tag_prevrange("console", "insert")
1285 if r:
1286 startatindex = r[1]
1287 else:
1288 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001289 rawtext = text.get(startatindex, "insert")
1290 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001291 y.set_lo(0)
1292
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001293 c = y.get_continuation_type()
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001294 if c != pyparse.C_NONE:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001295 # The current stmt hasn't ended yet.
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001296 if c == pyparse.C_STRING_FIRST_LINE:
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001297 # after the first line of a string; do not indent at all
1298 pass
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001299 elif c == pyparse.C_STRING_NEXT_LINES:
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001300 # inside a string which started before this line;
1301 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001302 text.insert("insert", indent)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001303 elif c == pyparse.C_BRACKET:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001304 # line up with the first (if any) element of the
1305 # last open bracket structure; else indent one
1306 # level beyond the indent of the line with the
1307 # last open bracket
1308 self.reindent_to(y.compute_bracket_indent())
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001309 elif c == pyparse.C_BACKSLASH:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001310 # if more than one line in this stmt already, just
1311 # mimic the current indent; else if initial line
1312 # has a start on an assignment stmt, indent to
1313 # beyond leftmost =; else to beyond first chunk of
1314 # non-whitespace on initial line
1315 if y.get_num_lines_in_stmt() > 1:
1316 text.insert("insert", indent)
1317 else:
1318 self.reindent_to(y.compute_backslash_indent())
1319 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001320 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001321 return "break"
1322
1323 # This line starts a brand new stmt; indent relative to
1324 # indentation of initial line of closest preceding
1325 # interesting stmt.
1326 indent = y.get_base_indent_string()
1327 text.insert("insert", indent)
1328 if y.is_block_opener():
1329 self.smart_indent_event(event)
1330 elif indent and y.is_block_closer():
1331 self.smart_backspace_event(event)
1332 return "break"
1333 finally:
1334 text.see("insert")
1335 text.undo_block_stop()
1336
Martin Panter7462b6492015-11-02 03:37:02 +00001337 # Our editwin provides an is_char_in_string function that works
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001338 # with a Tk text index, but PyParse only knows about offsets into
1339 # a string. This builds a function for PyParse that accepts an
1340 # offset.
1341
1342 def _build_char_in_string_func(self, startindex):
1343 def inner(offset, _startindex=startindex,
1344 _icis=self.is_char_in_string):
1345 return _icis(_startindex + "+%dc" % offset)
1346 return inner
1347
1348 def indent_region_event(self, event):
1349 head, tail, chars, lines = self.get_region()
1350 for pos in range(len(lines)):
1351 line = lines[pos]
1352 if line:
1353 raw, effective = classifyws(line, self.tabwidth)
1354 effective = effective + self.indentwidth
1355 lines[pos] = self._make_blanks(effective) + line[raw:]
1356 self.set_region(head, tail, chars, lines)
1357 return "break"
1358
1359 def dedent_region_event(self, event):
1360 head, tail, chars, lines = self.get_region()
1361 for pos in range(len(lines)):
1362 line = lines[pos]
1363 if line:
1364 raw, effective = classifyws(line, self.tabwidth)
1365 effective = max(effective - self.indentwidth, 0)
1366 lines[pos] = self._make_blanks(effective) + line[raw:]
1367 self.set_region(head, tail, chars, lines)
1368 return "break"
1369
1370 def comment_region_event(self, event):
1371 head, tail, chars, lines = self.get_region()
1372 for pos in range(len(lines) - 1):
1373 line = lines[pos]
1374 lines[pos] = '##' + line
1375 self.set_region(head, tail, chars, lines)
1376
1377 def uncomment_region_event(self, event):
1378 head, tail, chars, lines = self.get_region()
1379 for pos in range(len(lines)):
1380 line = lines[pos]
1381 if not line:
1382 continue
1383 if line[:2] == '##':
1384 line = line[2:]
1385 elif line[:1] == '#':
1386 line = line[1:]
1387 lines[pos] = line
1388 self.set_region(head, tail, chars, lines)
1389
1390 def tabify_region_event(self, event):
1391 head, tail, chars, lines = self.get_region()
1392 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001393 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001394 for pos in range(len(lines)):
1395 line = lines[pos]
1396 if line:
1397 raw, effective = classifyws(line, tabwidth)
1398 ntabs, nspaces = divmod(effective, tabwidth)
1399 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1400 self.set_region(head, tail, chars, lines)
1401
1402 def untabify_region_event(self, event):
1403 head, tail, chars, lines = self.get_region()
1404 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001405 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001406 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001407 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001408 self.set_region(head, tail, chars, lines)
1409
1410 def toggle_tabs_event(self, event):
1411 if self.askyesno(
1412 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001413 "Turn tabs " + ("on", "off")[self.usetabs] +
1414 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001415 ("will be", "remains at")[self.usetabs] + " 8." +
1416 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001417 parent=self.text):
1418 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001419 # Try to prevent inconsistent indentation.
1420 # User must change indent width manually after using tabs.
1421 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001422 return "break"
1423
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001424 # XXX this isn't bound to anything -- see tabwidth comments
1425## def change_tabwidth_event(self, event):
1426## new = self._asktabwidth()
1427## if new != self.tabwidth:
1428## self.tabwidth = new
1429## self.set_indentation_params(0, guess=0)
1430## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001431
1432 def change_indentwidth_event(self, event):
1433 new = self.askinteger(
1434 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001435 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001436 parent=self.text,
1437 initialvalue=self.indentwidth,
1438 minvalue=2,
1439 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001440 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001441 self.indentwidth = new
1442 return "break"
1443
1444 def get_region(self):
1445 text = self.text
1446 first, last = self.get_selection_indices()
1447 if first and last:
1448 head = text.index(first + " linestart")
1449 tail = text.index(last + "-1c lineend +1c")
1450 else:
1451 head = text.index("insert linestart")
1452 tail = text.index("insert lineend +1c")
1453 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001454 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001455 return head, tail, chars, lines
1456
1457 def set_region(self, head, tail, chars, lines):
1458 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001459 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001460 if newchars == chars:
1461 text.bell()
1462 return
1463 text.tag_remove("sel", "1.0", "end")
1464 text.mark_set("insert", head)
1465 text.undo_block_start()
1466 text.delete(head, tail)
1467 text.insert(head, newchars)
1468 text.undo_block_stop()
1469 text.tag_add("sel", head, "insert")
1470
1471 # Make string that displays as n leading blanks.
1472
1473 def _make_blanks(self, n):
1474 if self.usetabs:
1475 ntabs, nspaces = divmod(n, self.tabwidth)
1476 return '\t' * ntabs + ' ' * nspaces
1477 else:
1478 return ' ' * n
1479
1480 # Delete from beginning of line to insert point, then reinsert
1481 # column logical (meaning use tabs if appropriate) spaces.
1482
1483 def reindent_to(self, column):
1484 text = self.text
1485 text.undo_block_start()
1486 if text.compare("insert linestart", "!=", "insert"):
1487 text.delete("insert linestart", "insert")
1488 if column:
1489 text.insert("insert", self._make_blanks(column))
1490 text.undo_block_stop()
1491
1492 def _asktabwidth(self):
1493 return self.askinteger(
1494 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001495 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001496 parent=self.text,
1497 initialvalue=self.indentwidth,
1498 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001499 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001500
1501 # Guess indentwidth from text content.
1502 # Return guessed indentwidth. This should not be believed unless
1503 # it's in a reasonable range (e.g., it will be 0 if no indented
1504 # blocks are found).
1505
1506 def guess_indent(self):
1507 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1508 if opener and indented:
1509 raw, indentsmall = classifyws(opener, self.tabwidth)
1510 raw, indentlarge = classifyws(indented, self.tabwidth)
1511 else:
1512 indentsmall = indentlarge = 0
1513 return indentlarge - indentsmall
1514
1515# "line.col" -> line, as an int
1516def index2line(index):
1517 return int(float(index))
1518
1519# Look at the leading whitespace in s.
1520# Return pair (# of leading ws characters,
1521# effective # of leading blanks after expanding
1522# tabs to width tabwidth)
1523
1524def classifyws(s, tabwidth):
1525 raw = effective = 0
1526 for ch in s:
1527 if ch == ' ':
1528 raw = raw + 1
1529 effective = effective + 1
1530 elif ch == '\t':
1531 raw = raw + 1
1532 effective = (effective // tabwidth + 1) * tabwidth
1533 else:
1534 break
1535 return raw, effective
1536
1537import tokenize
1538_tokenize = tokenize
1539del tokenize
1540
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001541class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001542
1543 # .run() chews over the Text widget, looking for a block opener
1544 # and the stmt following it. Returns a pair,
1545 # (line containing block opener, line containing stmt)
1546 # Either or both may be None.
1547
1548 def __init__(self, text, tabwidth):
1549 self.text = text
1550 self.tabwidth = tabwidth
1551 self.i = self.finished = 0
1552 self.blkopenline = self.indentedline = None
1553
1554 def readline(self):
1555 if self.finished:
1556 return ""
1557 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001558 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001559 if self.text.compare(mark, ">=", "end"):
1560 return ""
1561 return self.text.get(mark, mark + " lineend+1c")
1562
1563 def tokeneater(self, type, token, start, end, line,
1564 INDENT=_tokenize.INDENT,
1565 NAME=_tokenize.NAME,
1566 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1567 if self.finished:
1568 pass
1569 elif type == NAME and token in OPENERS:
1570 self.blkopenline = line
1571 elif type == INDENT and self.blkopenline:
1572 self.indentedline = line
1573 self.finished = 1
1574
1575 def run(self):
1576 save_tabsize = _tokenize.tabsize
1577 _tokenize.tabsize = self.tabwidth
1578 try:
1579 try:
Trent Nelson428de652008-03-18 22:41:35 +00001580 tokens = _tokenize.generate_tokens(self.readline)
1581 for token in tokens:
1582 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001583 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001584 # since we cut off the tokenizer early, we can trigger
1585 # spurious errors
1586 pass
1587 finally:
1588 _tokenize.tabsize = save_tabsize
1589 return self.blkopenline, self.indentedline
1590
1591### end autoindent code ###
1592
David Scherer7aced172000-08-15 01:13:23 +00001593def prepstr(s):
1594 # Helper to extract the underscore from a string, e.g.
1595 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001596 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001597 if i >= 0:
1598 s = s[:i] + s[i+1:]
1599 return i, s
1600
1601
1602keynames = {
1603 'bracketleft': '[',
1604 'bracketright': ']',
1605 'slash': '/',
1606}
1607
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001608def get_accelerator(keydefs, eventname):
1609 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001610 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1611 # if not keylist:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001612 if (not keylist) or (macosx.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001613 "<<open-module>>",
1614 "<<goto-line>>",
1615 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001616 return ""
1617 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001618 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001619 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1620 s = re.sub("Key-", "", s)
1621 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1622 s = re.sub("Control-", "Ctrl-", s)
1623 s = re.sub("-", "+", s)
1624 s = re.sub("><", " ", s)
1625 s = re.sub("<", "", s)
1626 s = re.sub(">", "", s)
1627 return s
1628
1629
1630def fixwordbreaks(root):
1631 # Make sure that Tk's double-click and next/previous word
1632 # operations use our definition of a word (i.e. an identifier)
1633 tk = root.tk
1634 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1635 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1636 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1637
1638
Terry Jan Reedycd567362014-10-17 01:31:35 -04001639def _editor_window(parent): # htest #
1640 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001641 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001642 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001643 if sys.argv[1:]:
1644 filename = sys.argv[1]
1645 else:
1646 filename = None
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001647 macosx.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001648 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001649 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001650 # Does not stop error, neither does following
1651 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001652
1653if __name__ == '__main__':
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001654 from idlelib.idle_test.htest import run
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04001655 run(_editor_window)