blob: d04fc08e473e56f9ecdc4e347adba6cbbeb2f51d [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,
Terry Jan Reedy1080d132016-06-09 21:09:15 -0400113 'tabstyle': 'wordprocessor', # new in 8.5
114 'height': idleConf.GetOption(
115 'main', 'EditorWindow', 'height', type='int'),
116 }
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000117 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000118 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000119
120 self.createmenubar()
121 self.apply_bindings()
122
123 self.top.protocol("WM_DELETE_WINDOW", self.close)
124 self.top.bind("<<close-window>>", self.close_event)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400125 if macosx.isAquaTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000126 # Command-W on editorwindows doesn't work without this.
127 text.bind('<<close-window>>', self.close_event)
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400128 # Some OS X systems have only one mouse button, so use
129 # control-click for popup context menus there. For two
130 # buttons, AquaTk defines <2> as the right button, not <3>.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000131 text.bind("<Control-Button-1>",self.right_menu_event)
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400132 text.bind("<2>", self.right_menu_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000133 else:
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400134 # Elsewhere, use right-click for popup menus.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000135 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000136 text.bind("<<cut>>", self.cut)
137 text.bind("<<copy>>", self.copy)
138 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000139 text.bind("<<center-insert>>", self.center_insert_event)
140 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000141 text.bind("<<python-docs>>", self.python_docs)
142 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000143 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000144 text.bind("<<open-module>>", self.open_module)
145 text.bind("<<do-nothing>>", lambda event: "break")
146 text.bind("<<select-all>>", self.select_all)
147 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000148 text.bind("<<find>>", self.find_event)
149 text.bind("<<find-again>>", self.find_again_event)
150 text.bind("<<find-in-files>>", self.find_in_files_event)
151 text.bind("<<find-selection>>", self.find_selection_event)
152 text.bind("<<replace>>", self.replace_event)
153 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000154 text.bind("<<smart-backspace>>",self.smart_backspace_event)
155 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
156 text.bind("<<smart-indent>>",self.smart_indent_event)
157 text.bind("<<indent-region>>",self.indent_region_event)
158 text.bind("<<dedent-region>>",self.dedent_region_event)
159 text.bind("<<comment-region>>",self.comment_region_event)
160 text.bind("<<uncomment-region>>",self.uncomment_region_event)
161 text.bind("<<tabify-region>>",self.tabify_region_event)
162 text.bind("<<untabify-region>>",self.untabify_region_event)
163 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
164 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000165 text.bind("<Left>", self.move_at_edge_if_selection(0))
166 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000167 text.bind("<<del-word-left>>", self.del_word_left)
168 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000169 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000170
David Scherer7aced172000-08-15 01:13:23 +0000171 if flist:
172 flist.inversedict[self] = key
173 if key:
174 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000175 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000176 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
177 text.bind("<<open-class-browser>>", self.open_class_browser)
178 text.bind("<<open-path-browser>>", self.open_path_browser)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400179 text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
David Scherer7aced172000-08-15 01:13:23 +0000180
Steven M. Gava898a3652001-10-07 11:10:44 +0000181 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000182 vbar['command'] = text.yview
183 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000184 text['yscrollcommand'] = vbar.set
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400185 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000186 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
187 text.pack(side=TOP, fill=BOTH, expand=1)
188 text.focus_set()
189
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000190 # usetabs true -> literal tab characters are used by indent and
191 # dedent cmds, possibly mixed with spaces if
192 # indentwidth is not a multiple of tabwidth,
193 # which will cause Tabnanny to nag!
194 # false -> tab characters are converted to spaces by indent
195 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000196 # Although use-spaces=0 can be configured manually in config-main.def,
197 # configuration of tabs v. spaces is not supported in the configuration
198 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200199 usespaces = idleConf.GetOption('main', 'Indent',
200 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000201 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000202
203 # tabwidth is the display width of a literal tab character.
204 # CAUTION: telling Tk to use anything other than its default
205 # tab setting causes it to use an entirely different tabbing algorithm,
206 # treating tab stops as fixed distances from the left margin.
207 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000208 self.tabwidth = 8 # must remain 8 until Tk is fixed.
209
210 # indentwidth is the number of screen characters per indent level.
211 # The recommended Python indentation is four spaces.
212 self.indentwidth = self.tabwidth
213 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000214
215 # If context_use_ps1 is true, parsing searches back for a ps1 line;
216 # else searches for a popular (if, def, ...) Python stmt.
217 self.context_use_ps1 = False
218
219 # When searching backwards for a reliable place to begin parsing,
220 # first start num_context_lines[0] lines back, then
221 # num_context_lines[1] lines back if that didn't work, and so on.
222 # The last value should be huge (larger than the # of lines in a
223 # conceivable file).
224 # Making the initial values larger slows things down more often.
225 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000226 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000227 self.undo = undo = self.UndoDelegator()
228 per.insertfilter(undo)
229 text.undo_block_start = undo.undo_block_start
230 text.undo_block_stop = undo.undo_block_stop
231 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000232 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000233 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000234 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000235 self.good_load = False
236 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000237 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000238 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000239 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000240 if io.loadfile(filename):
241 self.good_load = True
242 is_py_src = self.ispythonsource(filename)
243 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000244 else:
245 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500246 self.good_load = True
247
Christian Heimesa156e092008-02-16 07:38:31 +0000248 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000249 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000250 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000251 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000252 menu = self.menudict.get('windows')
253 if menu:
254 end = menu.index("end")
255 if end is None:
256 end = -1
257 if end >= 0:
258 menu.add_separator()
259 end = end + 1
260 self.wmenu_end = end
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400261 windows.register_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000262
263 # Some abstractions so IDLE extensions are cross-IDE
264 self.askyesno = tkMessageBox.askyesno
265 self.askinteger = tkSimpleDialog.askinteger
266 self.showerror = tkMessageBox.showerror
267
Martin v. Löwis307021f2005-11-27 16:59:04 +0000268 def _filename_to_unicode(self, filename):
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400269 """Return filename as BMP unicode so diplayable in Tk."""
270 # Decode bytes to unicode.
271 if isinstance(filename, bytes):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000272 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400273 filename = filename.decode(self.filesystemencoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000274 except UnicodeDecodeError:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000275 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400276 filename = filename.decode(self.encoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000277 except UnicodeDecodeError:
278 # byte-to-byte conversion
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400279 filename = filename.decode('iso8859-1')
280 # Replace non-BMP char with diamond questionmark.
281 return re.sub('[\U00010000-\U0010FFFF]', '\ufffd', filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000282
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000283 def new_callback(self, event):
284 dirname, basename = self.io.defaultfilename()
285 self.flist.new(dirname)
286 return "break"
287
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000288 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400289 if (event.state & 4) != 0 and event.keysym == "Home":
290 # state&4==Control. If <Control-Home>, use the Tk binding.
291 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000292 if self.text.index("iomark") and \
293 self.text.compare("iomark", "<=", "insert lineend") and \
294 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400295 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000296 insertpt = int(self.text.index("iomark").split(".")[1])
297 else:
298 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000299 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000300 if line[insertpt] not in (' ','\t'):
301 break
302 else:
303 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000304 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000305 if insertpt == lineat:
306 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000307 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000308 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400309 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000310 self.text.tag_remove("sel", "1.0", "end")
311 else:
312 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200313 # there was no previous selection
314 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400315 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200316 if self.text.compare(self.text.index("sel.first"), "<",
317 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400318 self.text.mark_set("my_anchor", "sel.first") # extend back
319 else:
320 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000321 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400322 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000323 if self.text.compare(first,">",last):
324 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000325 self.text.tag_remove("sel", "1.0", "end")
326 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000327 self.text.mark_set("insert", dest)
328 self.text.see("insert")
329 return "break"
330
David Scherer7aced172000-08-15 01:13:23 +0000331 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000332 self.status_bar = self.MultiStatusBar(self.top)
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500333 sep = Frame(self.top, height=1, borderwidth=1, background='grey75')
Ned Deilyb7601672014-03-27 20:49:14 -0700334 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000335 # Insert some padding to avoid obscuring some of the statusbar
336 # by the resize widget.
337 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000338 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
339 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
340 self.status_bar.pack(side=BOTTOM, fill=X)
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500341 sep.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000342 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
343 self.text.event_add("<<set-line-and-column>>",
344 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000345 self.text.after_idle(self.set_line_and_column)
346
347 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000348 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000349 self.status_bar.set_label('column', 'Col: %s' % column)
350 self.status_bar.set_label('line', 'Ln: %s' % line)
351
David Scherer7aced172000-08-15 01:13:23 +0000352 menu_specs = [
353 ("file", "_File"),
354 ("edit", "_Edit"),
355 ("format", "F_ormat"),
356 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000357 ("options", "_Options"),
Ned Deilyccb416f2015-01-17 21:06:27 -0800358 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000359 ("help", "_Help"),
360 ]
361
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000362
David Scherer7aced172000-08-15 01:13:23 +0000363 def createmenubar(self):
364 mbar = self.menubar
365 self.menudict = menudict = {}
366 for name, label in self.menu_specs:
367 underline, label = prepstr(label)
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400368 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000369 mbar.add_cascade(label=label, menu=menu, underline=underline)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400370 if macosx.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000371 # Insert the application menu
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400372 menudict['application'] = menu = Menu(mbar, name='apple',
373 tearoff=0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000374 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000375 self.fill_menus()
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400376 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000377 self.menudict['file'].insert_cascade(3, label='Recent Files',
378 underline=0,
379 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000380 self.base_helpmenu_length = self.menudict['help'].index(END)
381 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000382
383 def postwindowsmenu(self):
384 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000385 menu = self.menudict['windows']
386 end = menu.index("end")
387 if end is None:
388 end = -1
389 if end > self.wmenu_end:
390 menu.delete(self.wmenu_end+1, end)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400391 windows.add_windows_to_menu(menu)
David Scherer7aced172000-08-15 01:13:23 +0000392
393 rmenu = None
394
395 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000396 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
397 if not self.rmenu:
398 self.make_rmenu()
399 rmenu = self.rmenu
400 self.event = event
401 iswin = sys.platform[:3] == 'win'
402 if iswin:
403 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200404
Roger Serwy6b2918a2013-04-07 12:15:52 -0500405 for item in self.rmenu_specs:
406 try:
407 label, eventname, verify_state = item
408 except ValueError: # see issue1207589
409 continue
410
Andrew Svetlovd1837672012-11-01 22:41:19 +0200411 if verify_state is None:
412 continue
413 state = getattr(self, verify_state)()
414 rmenu.entryconfigure(label, state=state)
415
416
David Scherer7aced172000-08-15 01:13:23 +0000417 rmenu.tk_popup(event.x_root, event.y_root)
418 if iswin:
419 self.text.config(cursor="ibeam")
420
421 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200422 # ("Label", "<<virtual-event>>", "statefuncname"), ...
423 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000424 ]
425
426 def make_rmenu(self):
427 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500428 for item in self.rmenu_specs:
429 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200430 if label is not None:
431 def command(text=self.text, eventname=eventname):
432 text.event_generate(eventname)
433 rmenu.add_command(label=label, command=command)
434 else:
435 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000436 self.rmenu = rmenu
437
Andrew Svetlovd1837672012-11-01 22:41:19 +0200438 def rmenu_check_cut(self):
439 return self.rmenu_check_copy()
440
441 def rmenu_check_copy(self):
442 try:
443 indx = self.text.index('sel.first')
444 except TclError:
445 return 'disabled'
446 else:
447 return 'normal' if indx else 'disabled'
448
449 def rmenu_check_paste(self):
450 try:
451 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
452 except TclError:
453 return 'disabled'
454 else:
455 return 'normal'
456
David Scherer7aced172000-08-15 01:13:23 +0000457 def about_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400458 "Handle Help 'About IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400459 # Synchronize with macosx.overrideRootMenu.about_dialog.
460 help_about.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000461
Steven M. Gava3b55a892001-11-21 05:56:26 +0000462 def config_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400463 "Handle Options 'Configure IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400464 # Synchronize with macosx.overrideRootMenu.config_dialog.
465 configdialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400466
David Scherer7aced172000-08-15 01:13:23 +0000467 def help_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400468 "Handle Help 'IDLE Help' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400469 # Synchronize with macosx.overrideRootMenu.help_dialog.
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500470 if self.root:
471 parent = self.root
472 else:
473 parent = self.top
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400474 help.show_idlehelp(parent)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000475
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000476 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000477 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000478 try:
479 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200480 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000481 tkMessageBox.showerror(title='Document Start Failure',
482 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000483 else:
484 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000485 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000486
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000487 def cut(self,event):
488 self.text.event_generate("<<Cut>>")
489 return "break"
490
491 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000492 if not self.text.tag_ranges("sel"):
493 # There is no selection, so do nothing and maybe interrupt.
494 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000495 self.text.event_generate("<<Copy>>")
496 return "break"
497
498 def paste(self,event):
499 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000500 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000501 return "break"
502
David Scherer7aced172000-08-15 01:13:23 +0000503 def select_all(self, event=None):
504 self.text.tag_add("sel", "1.0", "end-1c")
505 self.text.mark_set("insert", "1.0")
506 self.text.see("insert")
507 return "break"
508
509 def remove_selection(self, event=None):
510 self.text.tag_remove("sel", "1.0", "end")
511 self.text.see("insert")
512
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000513 def move_at_edge_if_selection(self, edge_index):
514 """Cursor move begins at start or end of selection
515
516 When a left/right cursor key is pressed create and return to Tkinter a
517 function which causes a cursor move from the associated edge of the
518 selection.
519
520 """
521 self_text_index = self.text.index
522 self_text_mark_set = self.text.mark_set
523 edges_table = ("sel.first+1c", "sel.last-1c")
524 def move_at_edge(event):
525 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
526 try:
527 self_text_index("sel.first")
528 self_text_mark_set("insert", edges_table[edge_index])
529 except TclError:
530 pass
531 return move_at_edge
532
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000533 def del_word_left(self, event):
534 self.text.event_generate('<Meta-Delete>')
535 return "break"
536
537 def del_word_right(self, event):
538 self.text.event_generate('<Meta-d>')
539 return "break"
540
Steven M. Gavac5976402002-01-04 03:06:08 +0000541 def find_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400542 search.find(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000543 return "break"
544
545 def find_again_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400546 search.find_again(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000547 return "break"
548
549 def find_selection_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400550 search.find_selection(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000551 return "break"
552
553 def find_in_files_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400554 grep.grep(self.text, self.io, self.flist)
Steven M. Gavac5976402002-01-04 03:06:08 +0000555 return "break"
556
557 def replace_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400558 replace.replace(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000559 return "break"
560
561 def goto_line_event(self, event):
562 text = self.text
563 lineno = tkSimpleDialog.askinteger("Goto",
564 "Go to line number:",parent=text)
565 if lineno is None:
566 return "break"
567 if lineno <= 0:
568 text.bell()
569 return "break"
570 text.mark_set("insert", "%d.0" % lineno)
571 text.see("insert")
572
David Scherer7aced172000-08-15 01:13:23 +0000573 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000574 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000575 try:
576 name = self.text.get("sel.first", "sel.last")
577 except TclError:
578 name = ""
579 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000580 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000581 name = tkSimpleDialog.askstring("Module",
582 "Enter the name of a Python module\n"
583 "to search on sys.path and open:",
584 parent=self.text, initialvalue=name)
585 if name:
586 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000587 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000588 return
David Scherer7aced172000-08-15 01:13:23 +0000589 # XXX Ought to insert current file's directory in front of path
590 try:
Eric Snow6029e082014-01-25 15:32:46 -0700591 spec = importlib.util.find_spec(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400592 except (ValueError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000593 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
594 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700595 if spec is None:
Brett Cannon50793b42013-06-07 13:17:48 -0400596 tkMessageBox.showerror("Import error", "module not found",
597 parent=self.text)
David Scherer7aced172000-08-15 01:13:23 +0000598 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700599 if not isinstance(spec.loader, importlib.abc.SourceLoader):
Brett Cannon50793b42013-06-07 13:17:48 -0400600 tkMessageBox.showerror("Import error", "not a source-based module",
601 parent=self.text)
602 return
603 try:
Eric Snow02b9f9d2014-01-06 20:42:59 -0700604 file_path = spec.loader.get_filename(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400605 except AttributeError:
606 tkMessageBox.showerror("Import error",
607 "loader does not support get_filename",
608 parent=self.text)
609 return
David Scherer7aced172000-08-15 01:13:23 +0000610 if self.flist:
Brett Cannon50793b42013-06-07 13:17:48 -0400611 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000612 else:
Brett Cannon50793b42013-06-07 13:17:48 -0400613 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400614 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000615
616 def open_class_browser(self, event=None):
617 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400618 if not (self.__class__.__name__ == 'PyShellEditorWindow'
619 and filename):
620 filename = self.open_module()
621 if filename is None:
622 return
David Scherer7aced172000-08-15 01:13:23 +0000623 head, tail = os.path.split(filename)
624 base, ext = os.path.splitext(tail)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400625 from idlelib import browser
626 browser.ClassBrowser(self.flist, base, [head])
David Scherer7aced172000-08-15 01:13:23 +0000627
628 def open_path_browser(self, event=None):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400629 from idlelib import pathbrowser
630 pathbrowser.PathBrowser(self.flist)
David Scherer7aced172000-08-15 01:13:23 +0000631
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400632 def open_turtle_demo(self, event = None):
633 import subprocess
634
635 cmd = [sys.executable,
636 '-c',
637 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400638 subprocess.Popen(cmd, shell=False)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400639
David Scherer7aced172000-08-15 01:13:23 +0000640 def gotoline(self, lineno):
641 if lineno is not None and lineno > 0:
642 self.text.mark_set("insert", "%d.0" % lineno)
643 self.text.tag_remove("sel", "1.0", "end")
644 self.text.tag_add("sel", "insert", "insert +1l")
645 self.center()
646
647 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000648 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000649 return True
David Scherer7aced172000-08-15 01:13:23 +0000650 base, ext = os.path.splitext(os.path.basename(filename))
651 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000652 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000653 line = self.text.get('1.0', '1.0 lineend')
654 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000655
656 def close_hook(self):
657 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000658 self.flist.unregister_maybe_terminate(self)
659 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000660
661 def set_close_hook(self, close_hook):
662 self.close_hook = close_hook
663
664 def filename_change_hook(self):
665 if self.flist:
666 self.flist.filename_changed_edit(self)
667 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000668 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000669 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000670
Christian Heimesa156e092008-02-16 07:38:31 +0000671 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000672 if self.color:
673 return
Christian Heimesa156e092008-02-16 07:38:31 +0000674 if self.ispythonsource(self.io.filename):
675 self.color = self.ColorDelegator()
676 # can add more colorizers here...
677 if self.color:
678 self.per.removefilter(self.undo)
679 self.per.insertfilter(self.color)
680 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000681
Christian Heimesa156e092008-02-16 07:38:31 +0000682 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000683 if not self.color:
684 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000685 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000686 self.per.removefilter(self.color)
687 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000688
Steven M. Gavab77d3432002-03-02 07:16:21 +0000689 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400690 "Update the color theme"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400691 # Called from self.filename_change_hook and from configdialog.py
Christian Heimesa156e092008-02-16 07:38:31 +0000692 self._rmcolorizer()
693 self._addcolorizer()
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -0400694 EditorWindow.color_config(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000695
Guido van Rossum33d26892007-08-05 15:29:28 +0000696 IDENTCHARS = string.ascii_letters + string.digits + "_"
697
698 def colorize_syntax_error(self, text, pos):
699 text.tag_add("ERROR", pos)
700 char = text.get(pos)
701 if char and char in self.IDENTCHARS:
702 text.tag_add("ERROR", pos + " wordstart", pos)
703 if '\n' == text.get(pos): # error at line end
704 text.mark_set("insert", pos)
705 else:
706 text.mark_set("insert", pos + "+1c")
707 text.see(pos)
708
Steven M. Gavab1585412002-03-12 00:21:56 +0000709 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000710 "Update the text widgets' font if it is changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400711 # Called from configdialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400712
713 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000714
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000715 def RemoveKeybindings(self):
716 "Remove the keybindings before they are changed."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400717 # Called from configdialog.py
718 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000719 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000720 self.text.event_delete(event, *keylist)
721 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000722 xkeydefs = idleConf.GetExtensionBindings(extensionName)
723 if xkeydefs:
724 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000725 self.text.event_delete(event, *keylist)
726
727 def ApplyKeybindings(self):
728 "Update the keybindings after they are changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400729 # Called from configdialog.py
730 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000731 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000732 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000733 xkeydefs = idleConf.GetExtensionBindings(extensionName)
734 if xkeydefs:
735 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000736 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000737 menuEventDict = {}
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400738 for menu in self.mainmenu.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000739 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000740 for item in menu[1]:
741 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000742 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000743 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000744 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700745 end = menu.index(END)
746 if end is None:
747 # Skip empty menus
748 continue
749 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000750 for index in range(0, end):
751 if menu.type(index) == 'command':
752 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000753 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000754 itemName = menu.entrycget(index, 'label')
755 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000756 if menubarItem in menuEventDict:
757 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000758 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000759 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000760 accel = get_accelerator(keydefs, event)
761 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000762
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000763 def set_notabs_indentwidth(self):
764 "Update the indentwidth if changed and not using tabs in this window"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400765 # Called from configdialog.py
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000766 if not self.usetabs:
767 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
768 type='int')
769
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000770 def reset_help_menu_entries(self):
771 "Update the additional help entries on the Help menu"
772 help_list = idleConf.GetAllExtraHelpSourcesList()
773 helpmenu = self.menudict['help']
774 # first delete the extra help entries, if any
775 helpmenu_length = helpmenu.index(END)
776 if helpmenu_length > self.base_helpmenu_length:
777 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
778 # then rebuild them
779 if help_list:
780 helpmenu.add_separator()
781 for entry in help_list:
782 cmd = self.__extra_help_callback(entry[1])
783 helpmenu.add_command(label=entry[0], command=cmd)
784 # and update the menu dictionary
785 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000786
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000787 def __extra_help_callback(self, helpfile):
788 "Create a callback with the helpfile value frozen at definition time"
789 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000790 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000791 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000792 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000793 try:
794 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200795 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000796 tkMessageBox.showerror(title='Document Start Failure',
797 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000798 else:
799 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000800 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000801
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000802 def update_recent_files_list(self, new_file=None):
803 "Load and update the recent files list and menus"
804 rf_list = []
805 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400806 with open(self.recent_files_path, 'r',
807 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000808 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000809 if new_file:
810 new_file = os.path.abspath(new_file) + '\n'
811 if new_file in rf_list:
812 rf_list.remove(new_file) # move to top
813 rf_list.insert(0, new_file)
814 # clean and save the recent files list
815 bad_paths = []
816 for path in rf_list:
817 if '\0' in path or not os.path.exists(path[0:-1]):
818 bad_paths.append(path)
819 rf_list = [path for path in rf_list if path not in bad_paths]
820 ulchars = "1234567890ABCDEFGHIJK"
821 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000822 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800823 with open(self.recent_files_path, 'w',
824 encoding='utf_8', errors='replace') as rf_file:
825 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200826 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800827 if not getattr(self.root, "recentfilelist_error_displayed", False):
828 self.root.recentfilelist_error_displayed = True
Terry Jan Reedy4c973e92015-10-27 03:38:02 -0400829 tkMessageBox.showwarning(title='IDLE Warning',
830 message="Cannot update File menu Recent Files list. "
831 "Your operating system says:\n%s\n"
832 "Select OK and IDLE will continue without updating."
833 % self._filename_to_unicode(str(err)),
Ned Deilyf505b742011-12-14 14:58:24 -0800834 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000835 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000836 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000837 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700838 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000839 for i, file_name in enumerate(rf_list):
840 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000841 # make unicode string to display non-ASCII chars correctly
842 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000843 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000844 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000845 command=callback,
846 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000847
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000848 def __recent_file_callback(self, file_name):
849 def open_recent_file(fn_closure=file_name):
850 self.io.open(editFile=fn_closure)
851 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000852
David Scherer7aced172000-08-15 01:13:23 +0000853 def saved_change_hook(self):
854 short = self.short_title()
855 long = self.long_title()
856 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400857 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000858 elif short:
859 title = short
860 elif long:
861 title = long
862 else:
863 title = "Untitled"
864 icon = short or long or title
865 if not self.get_saved():
866 title = "*%s*" % title
867 icon = "*%s" % icon
868 self.top.wm_title(title)
869 self.top.wm_iconname(icon)
870
871 def get_saved(self):
872 return self.undo.get_saved()
873
874 def set_saved(self, flag):
875 self.undo.set_saved(flag)
876
877 def reset_undo(self):
878 self.undo.reset_undo()
879
880 def short_title(self):
881 filename = self.io.filename
882 if filename:
883 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500884 else:
885 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000886 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400887 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000888
889 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000890 # return unicode string to display non-ASCII chars correctly
891 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000892
893 def center_insert_event(self, event):
894 self.center()
895
896 def center(self, mark="insert"):
897 text = self.text
898 top, bot = self.getwindowlines()
899 lineno = self.getlineno(mark)
900 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000901 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000902 text.yview(float(newtop))
903
904 def getwindowlines(self):
905 text = self.text
906 top = self.getlineno("@0,0")
907 bot = self.getlineno("@0,65535")
908 if top == bot and text.winfo_height() == 1:
909 # Geometry manager hasn't run yet
910 height = int(text['height'])
911 bot = top + height - 1
912 return top, bot
913
914 def getlineno(self, mark="insert"):
915 text = self.text
916 return int(float(text.index(mark)))
917
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000918 def get_geometry(self):
919 "Return (width, height, x, y)"
920 geom = self.top.wm_geometry()
921 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000922 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000923
David Scherer7aced172000-08-15 01:13:23 +0000924 def close_event(self, event):
925 self.close()
926
927 def maybesave(self):
928 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000929 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000930 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000931 self.top.deiconify()
932 self.top.lower()
933 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000934 return self.io.maybesave()
935
936 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000937 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000938 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000939 self._close()
940 return reply
941
942 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000943 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000944 self.update_recent_files_list(new_file=self.io.filename)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400945 windows.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000946 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000947 self.io.close()
948 self.io = None
949 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000950 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000951 self.color.close(False)
952 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000953 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000954 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000955 self.per.close()
956 self.per = None
957 self.top.destroy()
958 if self.close_hook:
959 # unless override: unregister from flist, terminate if last window
960 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000961
962 def load_extensions(self):
963 self.extensions = {}
964 self.load_standard_extensions()
965
966 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000967 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000968 if hasattr(ins, "close"):
969 ins.close()
970 self.extensions = {}
971
972 def load_standard_extensions(self):
973 for name in self.get_standard_extension_names():
974 try:
975 self.load_extension(name)
976 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000977 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000978 traceback.print_exc()
979
980 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000981 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000982
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400983 extfiles = { # map config-extension section names to new file names
984 'AutoComplete': 'autocomplete',
985 'AutoExpand': 'autoexpand',
986 'CallTips': 'calltips',
987 'CodeContext': 'codecontext',
988 'FormatParagraph': 'paragraph',
989 'ParenMatch': 'parenmatch',
990 'RstripExtension': 'rstrip',
991 'ScriptBinding': 'runscript',
992 'ZoomHeight': 'zoomheight',
993 }
994
David Scherer7aced172000-08-15 01:13:23 +0000995 def load_extension(self, name):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400996 fname = self.extfiles.get(name, name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000997 try:
Brett Cannonaef82d32012-04-14 20:44:23 -0400998 try:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400999 mod = importlib.import_module('.' + fname, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001000 except (ImportError, TypeError):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001001 mod = importlib.import_module(fname)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001002 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001003 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001004 raise
David Scherer7aced172000-08-15 01:13:23 +00001005 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001006 keydefs = idleConf.GetExtensionBindings(name)
1007 if hasattr(cls, "menudefs"):
1008 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001009 ins = cls(self)
1010 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001011 if keydefs:
1012 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001013 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001014 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001015 while methodname[:1] == '<':
1016 methodname = methodname[1:]
1017 while methodname[-1:] == '>':
1018 methodname = methodname[:-1]
1019 methodname = methodname + "_event"
1020 if hasattr(ins, methodname):
1021 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001022
1023 def apply_bindings(self, keydefs=None):
1024 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001025 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001026 text = self.text
1027 text.keydefs = keydefs
1028 for event, keylist in keydefs.items():
1029 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001030 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001031
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001032 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001033 """Add appropriate entries to the menus and submenus
1034
1035 Menus that are absent or None in self.menudict are ignored.
1036 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001037 if menudefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001038 menudefs = self.mainmenu.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001039 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001040 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001041 menudict = self.menudict
1042 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001043 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001044 menu = menudict.get(mname)
1045 if not menu:
1046 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001047 for entry in entrylist:
1048 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001049 menu.add_separator()
1050 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001051 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001052 checkbutton = (label[:1] == '!')
1053 if checkbutton:
1054 label = label[1:]
1055 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001056 accelerator = get_accelerator(keydefs, eventname)
1057 def command(text=text, eventname=eventname):
1058 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001059 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001060 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001061 menu.add_checkbutton(label=label, underline=underline,
1062 command=command, accelerator=accelerator,
1063 variable=var)
1064 else:
1065 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001066 command=command,
1067 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001068
1069 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001070 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001071 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001072 value = var.get()
1073 return value
1074 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001075 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001076
1077 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001078 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001079 if var:
1080 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001081 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001082 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001083
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001084 def get_var_obj(self, name, vartype=None):
1085 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001086 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001087 # create a Tkinter variable object with self.text as master:
1088 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001089 return var
1090
1091 # Tk implementations of "virtual text methods" -- each platform
1092 # reusing IDLE's support code needs to define these for its GUI's
1093 # flavor of widget.
1094
1095 # Is character at text_index in a Python string? Return 0 for
1096 # "guaranteed no", true for anything else. This info is expensive
1097 # to compute ab initio, but is probably already known by the
1098 # platform's colorizer.
1099
1100 def is_char_in_string(self, text_index):
1101 if self.color:
1102 # Return true iff colorizer hasn't (re)gotten this far
1103 # yet, or the character is tagged as being in a string
1104 return self.text.tag_prevrange("TODO", text_index) or \
1105 "STRING" in self.text.tag_names(text_index)
1106 else:
1107 # The colorizer is missing: assume the worst
1108 return 1
1109
1110 # If a selection is defined in the text widget, return (start,
1111 # end) as Tkinter text indices, otherwise return (None, None)
1112 def get_selection_indices(self):
1113 try:
1114 first = self.text.index("sel.first")
1115 last = self.text.index("sel.last")
1116 return first, last
1117 except TclError:
1118 return None, None
1119
1120 # Return the text widget's current view of what a tab stop means
1121 # (equivalent width in spaces).
1122
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001123 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001124 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1125 return int(current)
1126
1127 # Set the text widget's current view of what a tab stop means.
1128
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001129 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001130 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001131 if self.get_tk_tabwidth() != newtabwidth:
1132 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001133 pixels = text.tk.call("font", "measure", text["font"],
1134 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001135 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001136 text.configure(tabs=pixels)
1137
Guido van Rossum33d26892007-08-05 15:29:28 +00001138### begin autoindent code ### (configuration was moved to beginning of class)
1139
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001140 def set_indentation_params(self, is_py_src, guess=True):
1141 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001142 i = self.guess_indent()
1143 if 2 <= i <= 8:
1144 self.indentwidth = i
1145 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001146 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001147 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001148
1149 def smart_backspace_event(self, event):
1150 text = self.text
1151 first, last = self.get_selection_indices()
1152 if first and last:
1153 text.delete(first, last)
1154 text.mark_set("insert", first)
1155 return "break"
1156 # Delete whitespace left, until hitting a real char or closest
1157 # preceding virtual tab stop.
1158 chars = text.get("insert linestart", "insert")
1159 if chars == '':
1160 if text.compare("insert", ">", "1.0"):
1161 # easy: delete preceding newline
1162 text.delete("insert-1c")
1163 else:
1164 text.bell() # at start of buffer
1165 return "break"
1166 if chars[-1] not in " \t":
1167 # easy: delete preceding real char
1168 text.delete("insert-1c")
1169 return "break"
1170 # Ick. It may require *inserting* spaces if we back up over a
1171 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001172 tabwidth = self.tabwidth
1173 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001174 assert have > 0
1175 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001176 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001177 if self.context_use_ps1:
1178 last_line_of_prompt = sys.ps1.split('\n')[-1]
1179 else:
1180 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001181 ncharsdeleted = 0
1182 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001183 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001184 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001185 chars = chars[:-1]
1186 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001187 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001188 if have <= want or chars[-1] not in " \t":
1189 break
1190 text.undo_block_start()
1191 text.delete("insert-%dc" % ncharsdeleted, "insert")
1192 if have < want:
1193 text.insert("insert", ' ' * (want - have))
1194 text.undo_block_stop()
1195 return "break"
1196
1197 def smart_indent_event(self, event):
1198 # if intraline selection:
1199 # delete it
1200 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001201 # do indent-region
1202 # else:
1203 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001204 text = self.text
1205 first, last = self.get_selection_indices()
1206 text.undo_block_start()
1207 try:
1208 if first and last:
1209 if index2line(first) != index2line(last):
1210 return self.indent_region_event(event)
1211 text.delete(first, last)
1212 text.mark_set("insert", first)
1213 prefix = text.get("insert linestart", "insert")
1214 raw, effective = classifyws(prefix, self.tabwidth)
1215 if raw == len(prefix):
1216 # only whitespace to the left
1217 self.reindent_to(effective + self.indentwidth)
1218 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001219 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001220 if self.usetabs:
1221 pad = '\t'
1222 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001223 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001224 n = self.indentwidth
1225 pad = ' ' * (n - effective % n)
1226 text.insert("insert", pad)
1227 text.see("insert")
1228 return "break"
1229 finally:
1230 text.undo_block_stop()
1231
1232 def newline_and_indent_event(self, event):
1233 text = self.text
1234 first, last = self.get_selection_indices()
1235 text.undo_block_start()
1236 try:
1237 if first and last:
1238 text.delete(first, last)
1239 text.mark_set("insert", first)
1240 line = text.get("insert linestart", "insert")
1241 i, n = 0, len(line)
1242 while i < n and line[i] in " \t":
1243 i = i+1
1244 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001245 # the cursor is in or at leading indentation in a continuation
1246 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001247 text.insert("insert linestart", '\n')
1248 return "break"
1249 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001250 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001251 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001252 last_line_of_prompt = sys.ps1.split('\n')[-1]
1253 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001254 line = line[:-1]
1255 i = i+1
1256 if i:
1257 text.delete("insert - %d chars" % i, "insert")
1258 # strip whitespace after insert point
1259 while text.get("insert") in " \t":
1260 text.delete("insert")
1261 # start new line
1262 text.insert("insert", '\n')
1263
1264 # adjust indentation for continuations and block
1265 # open/close first need to find the last stmt
1266 lno = index2line(text.index('insert'))
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001267 y = pyparse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001268 if not self.context_use_ps1:
1269 for context in self.num_context_lines:
1270 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001271 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001272 rawtext = text.get(startatindex, "insert")
1273 y.set_str(rawtext)
1274 bod = y.find_good_parse_start(
1275 self.context_use_ps1,
1276 self._build_char_in_string_func(startatindex))
1277 if bod is not None or startat == 1:
1278 break
1279 y.set_lo(bod or 0)
1280 else:
1281 r = text.tag_prevrange("console", "insert")
1282 if r:
1283 startatindex = r[1]
1284 else:
1285 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001286 rawtext = text.get(startatindex, "insert")
1287 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001288 y.set_lo(0)
1289
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001290 c = y.get_continuation_type()
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001291 if c != pyparse.C_NONE:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001292 # The current stmt hasn't ended yet.
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001293 if c == pyparse.C_STRING_FIRST_LINE:
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001294 # after the first line of a string; do not indent at all
1295 pass
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001296 elif c == pyparse.C_STRING_NEXT_LINES:
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001297 # inside a string which started before this line;
1298 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001299 text.insert("insert", indent)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001300 elif c == pyparse.C_BRACKET:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001301 # line up with the first (if any) element of the
1302 # last open bracket structure; else indent one
1303 # level beyond the indent of the line with the
1304 # last open bracket
1305 self.reindent_to(y.compute_bracket_indent())
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001306 elif c == pyparse.C_BACKSLASH:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001307 # if more than one line in this stmt already, just
1308 # mimic the current indent; else if initial line
1309 # has a start on an assignment stmt, indent to
1310 # beyond leftmost =; else to beyond first chunk of
1311 # non-whitespace on initial line
1312 if y.get_num_lines_in_stmt() > 1:
1313 text.insert("insert", indent)
1314 else:
1315 self.reindent_to(y.compute_backslash_indent())
1316 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001317 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001318 return "break"
1319
1320 # This line starts a brand new stmt; indent relative to
1321 # indentation of initial line of closest preceding
1322 # interesting stmt.
1323 indent = y.get_base_indent_string()
1324 text.insert("insert", indent)
1325 if y.is_block_opener():
1326 self.smart_indent_event(event)
1327 elif indent and y.is_block_closer():
1328 self.smart_backspace_event(event)
1329 return "break"
1330 finally:
1331 text.see("insert")
1332 text.undo_block_stop()
1333
Martin Panter7462b6492015-11-02 03:37:02 +00001334 # Our editwin provides an is_char_in_string function that works
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001335 # with a Tk text index, but PyParse only knows about offsets into
1336 # a string. This builds a function for PyParse that accepts an
1337 # offset.
1338
1339 def _build_char_in_string_func(self, startindex):
1340 def inner(offset, _startindex=startindex,
1341 _icis=self.is_char_in_string):
1342 return _icis(_startindex + "+%dc" % offset)
1343 return inner
1344
1345 def indent_region_event(self, event):
1346 head, tail, chars, lines = self.get_region()
1347 for pos in range(len(lines)):
1348 line = lines[pos]
1349 if line:
1350 raw, effective = classifyws(line, self.tabwidth)
1351 effective = effective + self.indentwidth
1352 lines[pos] = self._make_blanks(effective) + line[raw:]
1353 self.set_region(head, tail, chars, lines)
1354 return "break"
1355
1356 def dedent_region_event(self, event):
1357 head, tail, chars, lines = self.get_region()
1358 for pos in range(len(lines)):
1359 line = lines[pos]
1360 if line:
1361 raw, effective = classifyws(line, self.tabwidth)
1362 effective = max(effective - self.indentwidth, 0)
1363 lines[pos] = self._make_blanks(effective) + line[raw:]
1364 self.set_region(head, tail, chars, lines)
1365 return "break"
1366
1367 def comment_region_event(self, event):
1368 head, tail, chars, lines = self.get_region()
1369 for pos in range(len(lines) - 1):
1370 line = lines[pos]
1371 lines[pos] = '##' + line
1372 self.set_region(head, tail, chars, lines)
1373
1374 def uncomment_region_event(self, event):
1375 head, tail, chars, lines = self.get_region()
1376 for pos in range(len(lines)):
1377 line = lines[pos]
1378 if not line:
1379 continue
1380 if line[:2] == '##':
1381 line = line[2:]
1382 elif line[:1] == '#':
1383 line = line[1:]
1384 lines[pos] = line
1385 self.set_region(head, tail, chars, lines)
1386
1387 def tabify_region_event(self, event):
1388 head, tail, chars, lines = self.get_region()
1389 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001390 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001391 for pos in range(len(lines)):
1392 line = lines[pos]
1393 if line:
1394 raw, effective = classifyws(line, tabwidth)
1395 ntabs, nspaces = divmod(effective, tabwidth)
1396 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1397 self.set_region(head, tail, chars, lines)
1398
1399 def untabify_region_event(self, event):
1400 head, tail, chars, lines = self.get_region()
1401 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001402 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001403 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001404 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001405 self.set_region(head, tail, chars, lines)
1406
1407 def toggle_tabs_event(self, event):
1408 if self.askyesno(
1409 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001410 "Turn tabs " + ("on", "off")[self.usetabs] +
1411 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001412 ("will be", "remains at")[self.usetabs] + " 8." +
1413 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001414 parent=self.text):
1415 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001416 # Try to prevent inconsistent indentation.
1417 # User must change indent width manually after using tabs.
1418 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001419 return "break"
1420
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001421 # XXX this isn't bound to anything -- see tabwidth comments
1422## def change_tabwidth_event(self, event):
1423## new = self._asktabwidth()
1424## if new != self.tabwidth:
1425## self.tabwidth = new
1426## self.set_indentation_params(0, guess=0)
1427## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001428
1429 def change_indentwidth_event(self, event):
1430 new = self.askinteger(
1431 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001432 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001433 parent=self.text,
1434 initialvalue=self.indentwidth,
1435 minvalue=2,
1436 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001437 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001438 self.indentwidth = new
1439 return "break"
1440
1441 def get_region(self):
1442 text = self.text
1443 first, last = self.get_selection_indices()
1444 if first and last:
1445 head = text.index(first + " linestart")
1446 tail = text.index(last + "-1c lineend +1c")
1447 else:
1448 head = text.index("insert linestart")
1449 tail = text.index("insert lineend +1c")
1450 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001451 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001452 return head, tail, chars, lines
1453
1454 def set_region(self, head, tail, chars, lines):
1455 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001456 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001457 if newchars == chars:
1458 text.bell()
1459 return
1460 text.tag_remove("sel", "1.0", "end")
1461 text.mark_set("insert", head)
1462 text.undo_block_start()
1463 text.delete(head, tail)
1464 text.insert(head, newchars)
1465 text.undo_block_stop()
1466 text.tag_add("sel", head, "insert")
1467
1468 # Make string that displays as n leading blanks.
1469
1470 def _make_blanks(self, n):
1471 if self.usetabs:
1472 ntabs, nspaces = divmod(n, self.tabwidth)
1473 return '\t' * ntabs + ' ' * nspaces
1474 else:
1475 return ' ' * n
1476
1477 # Delete from beginning of line to insert point, then reinsert
1478 # column logical (meaning use tabs if appropriate) spaces.
1479
1480 def reindent_to(self, column):
1481 text = self.text
1482 text.undo_block_start()
1483 if text.compare("insert linestart", "!=", "insert"):
1484 text.delete("insert linestart", "insert")
1485 if column:
1486 text.insert("insert", self._make_blanks(column))
1487 text.undo_block_stop()
1488
1489 def _asktabwidth(self):
1490 return self.askinteger(
1491 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001492 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001493 parent=self.text,
1494 initialvalue=self.indentwidth,
1495 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001496 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001497
1498 # Guess indentwidth from text content.
1499 # Return guessed indentwidth. This should not be believed unless
1500 # it's in a reasonable range (e.g., it will be 0 if no indented
1501 # blocks are found).
1502
1503 def guess_indent(self):
1504 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1505 if opener and indented:
1506 raw, indentsmall = classifyws(opener, self.tabwidth)
1507 raw, indentlarge = classifyws(indented, self.tabwidth)
1508 else:
1509 indentsmall = indentlarge = 0
1510 return indentlarge - indentsmall
1511
1512# "line.col" -> line, as an int
1513def index2line(index):
1514 return int(float(index))
1515
1516# Look at the leading whitespace in s.
1517# Return pair (# of leading ws characters,
1518# effective # of leading blanks after expanding
1519# tabs to width tabwidth)
1520
1521def classifyws(s, tabwidth):
1522 raw = effective = 0
1523 for ch in s:
1524 if ch == ' ':
1525 raw = raw + 1
1526 effective = effective + 1
1527 elif ch == '\t':
1528 raw = raw + 1
1529 effective = (effective // tabwidth + 1) * tabwidth
1530 else:
1531 break
1532 return raw, effective
1533
1534import tokenize
1535_tokenize = tokenize
1536del tokenize
1537
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001538class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001539
1540 # .run() chews over the Text widget, looking for a block opener
1541 # and the stmt following it. Returns a pair,
1542 # (line containing block opener, line containing stmt)
1543 # Either or both may be None.
1544
1545 def __init__(self, text, tabwidth):
1546 self.text = text
1547 self.tabwidth = tabwidth
1548 self.i = self.finished = 0
1549 self.blkopenline = self.indentedline = None
1550
1551 def readline(self):
1552 if self.finished:
1553 return ""
1554 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001555 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001556 if self.text.compare(mark, ">=", "end"):
1557 return ""
1558 return self.text.get(mark, mark + " lineend+1c")
1559
1560 def tokeneater(self, type, token, start, end, line,
1561 INDENT=_tokenize.INDENT,
1562 NAME=_tokenize.NAME,
1563 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1564 if self.finished:
1565 pass
1566 elif type == NAME and token in OPENERS:
1567 self.blkopenline = line
1568 elif type == INDENT and self.blkopenline:
1569 self.indentedline = line
1570 self.finished = 1
1571
1572 def run(self):
1573 save_tabsize = _tokenize.tabsize
1574 _tokenize.tabsize = self.tabwidth
1575 try:
1576 try:
Trent Nelson428de652008-03-18 22:41:35 +00001577 tokens = _tokenize.generate_tokens(self.readline)
1578 for token in tokens:
1579 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001580 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001581 # since we cut off the tokenizer early, we can trigger
1582 # spurious errors
1583 pass
1584 finally:
1585 _tokenize.tabsize = save_tabsize
1586 return self.blkopenline, self.indentedline
1587
1588### end autoindent code ###
1589
David Scherer7aced172000-08-15 01:13:23 +00001590def prepstr(s):
1591 # Helper to extract the underscore from a string, e.g.
1592 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001593 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001594 if i >= 0:
1595 s = s[:i] + s[i+1:]
1596 return i, s
1597
1598
1599keynames = {
1600 'bracketleft': '[',
1601 'bracketright': ']',
1602 'slash': '/',
1603}
1604
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001605def get_accelerator(keydefs, eventname):
1606 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001607 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1608 # if not keylist:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001609 if (not keylist) or (macosx.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001610 "<<open-module>>",
1611 "<<goto-line>>",
1612 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001613 return ""
1614 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001615 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001616 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1617 s = re.sub("Key-", "", s)
1618 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1619 s = re.sub("Control-", "Ctrl-", s)
1620 s = re.sub("-", "+", s)
1621 s = re.sub("><", " ", s)
1622 s = re.sub("<", "", s)
1623 s = re.sub(">", "", s)
1624 return s
1625
1626
1627def fixwordbreaks(root):
1628 # Make sure that Tk's double-click and next/previous word
1629 # operations use our definition of a word (i.e. an identifier)
1630 tk = root.tk
1631 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1632 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1633 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1634
1635
Terry Jan Reedycd567362014-10-17 01:31:35 -04001636def _editor_window(parent): # htest #
1637 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001638 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001639 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001640 if sys.argv[1:]:
1641 filename = sys.argv[1]
1642 else:
1643 filename = None
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001644 macosx.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001645 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001646 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001647 # Does not stop error, neither does following
1648 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001649
1650if __name__ == '__main__':
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001651 from idlelib.idle_test.htest import run
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04001652 run(_editor_window)