blob: 855d375055653acd101970515c853243706c63af [file] [log] [blame]
Brett Cannon50793b42013-06-07 13:17:48 -04001import importlib.abc
Eric Snow6029e082014-01-25 15:32:46 -07002import importlib.util
David Scherer7aced172000-08-15 01:13:23 +00003import os
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -04004import platform
David Scherer7aced172000-08-15 01:13:23 +00005import re
Guido van Rossum33d26892007-08-05 15:29:28 +00006import string
Brett Cannonaef82d32012-04-14 20:44:23 -04007import sys
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04008import tokenize
9import traceback
10import webbrowser
11
Georg Brandl14fc4272008-05-17 18:39:55 +000012from tkinter import *
Terry Jan Reedy01e35752016-06-10 18:19:21 -040013from tkinter.ttk import Scrollbar
Georg Brandl14fc4272008-05-17 18:39:55 +000014import tkinter.simpledialog as tkSimpleDialog
15import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +000016
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040017from idlelib.config import idleConf
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040018from idlelib import configdialog
19from idlelib import grep
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040020from idlelib import help
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040021from idlelib import help_about
22from idlelib import macosx
23from idlelib.multicall import MultiCallCreator
24from idlelib import pyparse
25from idlelib import query
26from idlelib import replace
27from idlelib import search
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040028from idlelib import windows
David Scherer7aced172000-08-15 01:13:23 +000029
30# The default tab setting for a Text widget, in average-width characters.
31TK_TABWIDTH_DEFAULT = 8
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -040032_py_version = ' (%s)' % platform.python_version()
33
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000034def _sphinx_version():
35 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
36 major, minor, micro, level, serial = sys.version_info
37 release = '%s%s' % (major, minor)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020038 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000039 if level == 'candidate':
40 release += 'rc%s' % (serial,)
41 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000042 release += '%s%s' % (level[0], serial)
43 return release
44
Terry Jan Reedye91e7632012-02-05 15:14:20 -050045
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000046class EditorWindow(object):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040047 from idlelib.percolator import Percolator
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -040048 from idlelib.colorizer import ColorDelegator, color_config
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040049 from idlelib.undo import UndoDelegator
Terry Jan Reedy7c153412016-06-26 17:48:02 -040050 from idlelib.iomenu import IOBinding, encoding
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040051 from idlelib import mainmenu
Guilherme Polo5424b0a2008-05-25 15:26:44 +000052 from tkinter import Toplevel
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040053 from idlelib.statusbar import MultiStatusBar
wohlganger58fc71c2017-09-10 16:19:47 -050054 from idlelib.autocomplete import AutoComplete
55 from idlelib.autoexpand import AutoExpand
56 from idlelib.calltips import CallTips
57 from idlelib.codecontext import CodeContext
58 from idlelib.paragraph import FormatParagraph
59 from idlelib.parenmatch import ParenMatch
60 from idlelib.rstrip import RstripExtension
61 from idlelib.zoomheight import ZoomHeight
David Scherer7aced172000-08-15 01:13:23 +000062
Terry Jan Reedy7c153412016-06-26 17:48:02 -040063 filesystemencoding = sys.getfilesystemencoding() # for file names
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000064 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000065
66 def __init__(self, flist=None, filename=None, key=None, root=None):
wohlganger58fc71c2017-09-10 16:19:47 -050067 # Delay import: runscript imports pyshell imports EditorWindow.
68 from idlelib.runscript import ScriptBinding
69
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000070 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +010071 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000072 if sys.platform.count('linux'):
73 # look for html docs in a couple of standard places
74 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
75 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
76 dochome = '/var/www/html/python/index.html'
77 else:
78 basepath = '/usr/share/doc/' # standard location
79 dochome = os.path.join(basepath, pyver,
80 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000081 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +010082 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000083 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +000084 if os.path.isfile(chmfile):
85 dochome = chmfile
Ned Deilyb7601672014-03-27 20:49:14 -070086 elif sys.platform == 'darwin':
87 # documentation may be stored inside a python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +010088 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +000089 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000090 dochome = os.path.normpath(dochome)
91 if os.path.isfile(dochome):
92 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +000093 if sys.platform == 'darwin':
94 # Safari requires real file:-URLs
95 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000096 else:
wohlganger58fc71c2017-09-10 16:19:47 -050097 EditorWindow.help_url = ("https://docs.python.org/%d.%d/"
98 % sys.version_info[:2])
David Scherer7aced172000-08-15 01:13:23 +000099 self.flist = flist
100 root = root or flist.root
101 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000102 try:
103 sys.ps1
104 except AttributeError:
105 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000106 self.menubar = Menu(root)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400107 self.top = top = windows.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000108 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000109 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200110 #self.top.instance_dict makes flist.inversedict available to
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400111 #configdialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000112 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000113 else:
114 self.tkinter_vars = {} # keys: Tkinter event names
115 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000116 self.top.instance_dict = {}
terryjreedy223c7e72017-07-07 22:28:06 -0400117 self.recent_files_path = os.path.join(
118 idleConf.userdir, 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000119 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000120 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200121 self.width = idleConf.GetOption('main', 'EditorWindow',
122 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000123 text_options = {
124 'name': 'text',
125 'padx': 5,
126 'wrap': 'none',
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500127 'highlightthickness': 0,
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000128 'width': self.width,
Terry Jan Reedy1080d132016-06-09 21:09:15 -0400129 'tabstyle': 'wordprocessor', # new in 8.5
130 'height': idleConf.GetOption(
131 'main', 'EditorWindow', 'height', type='int'),
132 }
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000133 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000134 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000135
136 self.createmenubar()
137 self.apply_bindings()
138
139 self.top.protocol("WM_DELETE_WINDOW", self.close)
140 self.top.bind("<<close-window>>", self.close_event)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400141 if macosx.isAquaTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000142 # Command-W on editorwindows doesn't work without this.
143 text.bind('<<close-window>>', self.close_event)
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400144 # Some OS X systems have only one mouse button, so use
145 # control-click for popup context menus there. For two
146 # buttons, AquaTk defines <2> as the right button, not <3>.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000147 text.bind("<Control-Button-1>",self.right_menu_event)
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400148 text.bind("<2>", self.right_menu_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000149 else:
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400150 # Elsewhere, use right-click for popup menus.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000151 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000152 text.bind("<<cut>>", self.cut)
153 text.bind("<<copy>>", self.copy)
154 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000155 text.bind("<<center-insert>>", self.center_insert_event)
156 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000157 text.bind("<<python-docs>>", self.python_docs)
158 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000159 text.bind("<<open-config-dialog>>", self.config_dialog)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300160 text.bind("<<open-module>>", self.open_module_event)
David Scherer7aced172000-08-15 01:13:23 +0000161 text.bind("<<do-nothing>>", lambda event: "break")
162 text.bind("<<select-all>>", self.select_all)
163 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000164 text.bind("<<find>>", self.find_event)
165 text.bind("<<find-again>>", self.find_again_event)
166 text.bind("<<find-in-files>>", self.find_in_files_event)
167 text.bind("<<find-selection>>", self.find_selection_event)
168 text.bind("<<replace>>", self.replace_event)
169 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000170 text.bind("<<smart-backspace>>",self.smart_backspace_event)
171 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
172 text.bind("<<smart-indent>>",self.smart_indent_event)
173 text.bind("<<indent-region>>",self.indent_region_event)
174 text.bind("<<dedent-region>>",self.dedent_region_event)
175 text.bind("<<comment-region>>",self.comment_region_event)
176 text.bind("<<uncomment-region>>",self.uncomment_region_event)
177 text.bind("<<tabify-region>>",self.tabify_region_event)
178 text.bind("<<untabify-region>>",self.untabify_region_event)
179 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
180 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000181 text.bind("<Left>", self.move_at_edge_if_selection(0))
182 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000183 text.bind("<<del-word-left>>", self.del_word_left)
184 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000185 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000186
David Scherer7aced172000-08-15 01:13:23 +0000187 if flist:
188 flist.inversedict[self] = key
189 if key:
190 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000191 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000192 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
193 text.bind("<<open-class-browser>>", self.open_class_browser)
194 text.bind("<<open-path-browser>>", self.open_path_browser)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400195 text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
David Scherer7aced172000-08-15 01:13:23 +0000196
Steven M. Gava898a3652001-10-07 11:10:44 +0000197 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000198 vbar['command'] = text.yview
199 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000200 text['yscrollcommand'] = vbar.set
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400201 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000202 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
203 text.pack(side=TOP, fill=BOTH, expand=1)
204 text.focus_set()
205
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000206 # usetabs true -> literal tab characters are used by indent and
207 # dedent cmds, possibly mixed with spaces if
208 # indentwidth is not a multiple of tabwidth,
209 # which will cause Tabnanny to nag!
210 # false -> tab characters are converted to spaces by indent
211 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000212 # Although use-spaces=0 can be configured manually in config-main.def,
213 # configuration of tabs v. spaces is not supported in the configuration
214 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200215 usespaces = idleConf.GetOption('main', 'Indent',
216 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000217 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000218
219 # tabwidth is the display width of a literal tab character.
220 # CAUTION: telling Tk to use anything other than its default
221 # tab setting causes it to use an entirely different tabbing algorithm,
222 # treating tab stops as fixed distances from the left margin.
223 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000224 self.tabwidth = 8 # must remain 8 until Tk is fixed.
225
226 # indentwidth is the number of screen characters per indent level.
227 # The recommended Python indentation is four spaces.
228 self.indentwidth = self.tabwidth
229 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000230
231 # If context_use_ps1 is true, parsing searches back for a ps1 line;
232 # else searches for a popular (if, def, ...) Python stmt.
233 self.context_use_ps1 = False
234
235 # When searching backwards for a reliable place to begin parsing,
236 # first start num_context_lines[0] lines back, then
237 # num_context_lines[1] lines back if that didn't work, and so on.
238 # The last value should be huge (larger than the # of lines in a
239 # conceivable file).
240 # Making the initial values larger slows things down more often.
241 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000242 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000243 self.undo = undo = self.UndoDelegator()
244 per.insertfilter(undo)
245 text.undo_block_start = undo.undo_block_start
246 text.undo_block_stop = undo.undo_block_stop
247 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000248 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000249 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000250 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000251 self.good_load = False
252 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000253 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000254 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000255 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000256 if io.loadfile(filename):
257 self.good_load = True
258 is_py_src = self.ispythonsource(filename)
259 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000260 else:
261 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500262 self.good_load = True
263
Christian Heimesa156e092008-02-16 07:38:31 +0000264 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000265 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000266 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000267 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000268 menu = self.menudict.get('windows')
269 if menu:
270 end = menu.index("end")
271 if end is None:
272 end = -1
273 if end >= 0:
274 menu.add_separator()
275 end = end + 1
276 self.wmenu_end = end
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400277 windows.register_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000278
279 # Some abstractions so IDLE extensions are cross-IDE
280 self.askyesno = tkMessageBox.askyesno
281 self.askinteger = tkSimpleDialog.askinteger
282 self.showerror = tkMessageBox.showerror
283
wohlganger58fc71c2017-09-10 16:19:47 -0500284 # Add pseudoevents for former extension fixed keys.
285 # (This probably needs to be done once in the process.)
286 text.event_add('<<autocomplete>>', '<Key-Tab>')
287 text.event_add('<<try-open-completions>>', '<KeyRelease-period>',
288 '<KeyRelease-slash>', '<KeyRelease-backslash>')
289 text.event_add('<<try-open-calltip>>', '<KeyRelease-parenleft>')
290 text.event_add('<<refresh-calltip>>', '<KeyRelease-parenright>')
291 text.event_add('<<paren-closed>>', '<KeyRelease-parenright>',
292 '<KeyRelease-bracketright>', '<KeyRelease-braceright>')
293
294 # Former extension bindings depends on frame.text being packed
295 # (called from self.ResetColorizer()).
296 autocomplete = self.AutoComplete(self)
297 text.bind("<<autocomplete>>", autocomplete.autocomplete_event)
298 text.bind("<<try-open-completions>>",
299 autocomplete.try_open_completions_event)
300 text.bind("<<force-open-completions>>",
301 autocomplete.force_open_completions_event)
302 text.bind("<<expand-word>>", self.AutoExpand(self).expand_word_event)
303 text.bind("<<format-paragraph>>",
304 self.FormatParagraph(self).format_paragraph_event)
305 parenmatch = self.ParenMatch(self)
306 text.bind("<<flash-paren>>", parenmatch.flash_paren_event)
307 text.bind("<<paren-closed>>", parenmatch.paren_closed_event)
308 scriptbinding = ScriptBinding(self)
309 text.bind("<<check-module>>", scriptbinding.check_module_event)
310 text.bind("<<run-module>>", scriptbinding.run_module_event)
311 text.bind("<<do-rstrip>>", self.RstripExtension(self).do_rstrip)
312 calltips = self.CallTips(self)
313 text.bind("<<try-open-calltip>>", calltips.try_open_calltip_event)
314 #refresh-calltips must come after paren-closed to work right
315 text.bind("<<refresh-calltip>>", calltips.refresh_calltip_event)
316 text.bind("<<force-open-calltip>>", calltips.force_open_calltip_event)
317 text.bind("<<zoom-height>>", self.ZoomHeight(self).zoom_height_event)
318 text.bind("<<toggle-code-context>>",
319 self.CodeContext(self).toggle_code_context_event)
320
Martin v. Löwis307021f2005-11-27 16:59:04 +0000321 def _filename_to_unicode(self, filename):
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400322 """Return filename as BMP unicode so diplayable in Tk."""
323 # Decode bytes to unicode.
324 if isinstance(filename, bytes):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000325 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400326 filename = filename.decode(self.filesystemencoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000327 except UnicodeDecodeError:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000328 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400329 filename = filename.decode(self.encoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000330 except UnicodeDecodeError:
331 # byte-to-byte conversion
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400332 filename = filename.decode('iso8859-1')
333 # Replace non-BMP char with diamond questionmark.
334 return re.sub('[\U00010000-\U0010FFFF]', '\ufffd', filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000335
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000336 def new_callback(self, event):
337 dirname, basename = self.io.defaultfilename()
338 self.flist.new(dirname)
339 return "break"
340
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000341 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400342 if (event.state & 4) != 0 and event.keysym == "Home":
343 # state&4==Control. If <Control-Home>, use the Tk binding.
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300344 return None
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000345 if self.text.index("iomark") and \
346 self.text.compare("iomark", "<=", "insert lineend") and \
347 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400348 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000349 insertpt = int(self.text.index("iomark").split(".")[1])
350 else:
351 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000352 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000353 if line[insertpt] not in (' ','\t'):
354 break
355 else:
356 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000357 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000358 if insertpt == lineat:
359 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000360 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000361 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400362 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000363 self.text.tag_remove("sel", "1.0", "end")
364 else:
365 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200366 # there was no previous selection
367 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400368 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200369 if self.text.compare(self.text.index("sel.first"), "<",
370 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400371 self.text.mark_set("my_anchor", "sel.first") # extend back
372 else:
373 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000374 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400375 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000376 if self.text.compare(first,">",last):
377 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000378 self.text.tag_remove("sel", "1.0", "end")
379 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000380 self.text.mark_set("insert", dest)
381 self.text.see("insert")
382 return "break"
383
David Scherer7aced172000-08-15 01:13:23 +0000384 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000385 self.status_bar = self.MultiStatusBar(self.top)
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500386 sep = Frame(self.top, height=1, borderwidth=1, background='grey75')
Ned Deilyb7601672014-03-27 20:49:14 -0700387 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000388 # Insert some padding to avoid obscuring some of the statusbar
389 # by the resize widget.
390 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000391 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
392 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
393 self.status_bar.pack(side=BOTTOM, fill=X)
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500394 sep.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000395 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
396 self.text.event_add("<<set-line-and-column>>",
397 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000398 self.text.after_idle(self.set_line_and_column)
399
400 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000401 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000402 self.status_bar.set_label('column', 'Col: %s' % column)
403 self.status_bar.set_label('line', 'Ln: %s' % line)
404
David Scherer7aced172000-08-15 01:13:23 +0000405 menu_specs = [
406 ("file", "_File"),
407 ("edit", "_Edit"),
408 ("format", "F_ormat"),
409 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000410 ("options", "_Options"),
Ned Deilyccb416f2015-01-17 21:06:27 -0800411 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000412 ("help", "_Help"),
413 ]
414
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000415
David Scherer7aced172000-08-15 01:13:23 +0000416 def createmenubar(self):
417 mbar = self.menubar
418 self.menudict = menudict = {}
419 for name, label in self.menu_specs:
420 underline, label = prepstr(label)
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400421 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000422 mbar.add_cascade(label=label, menu=menu, underline=underline)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400423 if macosx.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000424 # Insert the application menu
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400425 menudict['application'] = menu = Menu(mbar, name='apple',
426 tearoff=0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000427 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000428 self.fill_menus()
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400429 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000430 self.menudict['file'].insert_cascade(3, label='Recent Files',
431 underline=0,
432 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000433 self.base_helpmenu_length = self.menudict['help'].index(END)
434 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000435
436 def postwindowsmenu(self):
437 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000438 menu = self.menudict['windows']
439 end = menu.index("end")
440 if end is None:
441 end = -1
442 if end > self.wmenu_end:
443 menu.delete(self.wmenu_end+1, end)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400444 windows.add_windows_to_menu(menu)
David Scherer7aced172000-08-15 01:13:23 +0000445
446 rmenu = None
447
448 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000449 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
450 if not self.rmenu:
451 self.make_rmenu()
452 rmenu = self.rmenu
453 self.event = event
454 iswin = sys.platform[:3] == 'win'
455 if iswin:
456 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200457
Roger Serwy6b2918a2013-04-07 12:15:52 -0500458 for item in self.rmenu_specs:
459 try:
460 label, eventname, verify_state = item
461 except ValueError: # see issue1207589
462 continue
463
Andrew Svetlovd1837672012-11-01 22:41:19 +0200464 if verify_state is None:
465 continue
466 state = getattr(self, verify_state)()
467 rmenu.entryconfigure(label, state=state)
468
469
David Scherer7aced172000-08-15 01:13:23 +0000470 rmenu.tk_popup(event.x_root, event.y_root)
471 if iswin:
472 self.text.config(cursor="ibeam")
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300473 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000474
475 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200476 # ("Label", "<<virtual-event>>", "statefuncname"), ...
477 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000478 ]
479
480 def make_rmenu(self):
481 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500482 for item in self.rmenu_specs:
483 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200484 if label is not None:
485 def command(text=self.text, eventname=eventname):
486 text.event_generate(eventname)
487 rmenu.add_command(label=label, command=command)
488 else:
489 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000490 self.rmenu = rmenu
491
Andrew Svetlovd1837672012-11-01 22:41:19 +0200492 def rmenu_check_cut(self):
493 return self.rmenu_check_copy()
494
495 def rmenu_check_copy(self):
496 try:
497 indx = self.text.index('sel.first')
498 except TclError:
499 return 'disabled'
500 else:
501 return 'normal' if indx else 'disabled'
502
503 def rmenu_check_paste(self):
504 try:
505 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
506 except TclError:
507 return 'disabled'
508 else:
509 return 'normal'
510
David Scherer7aced172000-08-15 01:13:23 +0000511 def about_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400512 "Handle Help 'About IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400513 # Synchronize with macosx.overrideRootMenu.about_dialog.
csabella18ede062017-06-23 20:00:58 -0400514 help_about.AboutDialog(self.top)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300515 return "break"
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000516
Steven M. Gava3b55a892001-11-21 05:56:26 +0000517 def config_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400518 "Handle Options 'Configure IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400519 # Synchronize with macosx.overrideRootMenu.config_dialog.
520 configdialog.ConfigDialog(self.top,'Settings')
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300521 return "break"
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400522
David Scherer7aced172000-08-15 01:13:23 +0000523 def help_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400524 "Handle Help 'IDLE Help' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400525 # Synchronize with macosx.overrideRootMenu.help_dialog.
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500526 if self.root:
527 parent = self.root
528 else:
529 parent = self.top
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400530 help.show_idlehelp(parent)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300531 return "break"
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000532
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000533 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000534 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000535 try:
536 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200537 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000538 tkMessageBox.showerror(title='Document Start Failure',
539 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000540 else:
541 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000542 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000543
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000544 def cut(self,event):
545 self.text.event_generate("<<Cut>>")
546 return "break"
547
548 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000549 if not self.text.tag_ranges("sel"):
550 # There is no selection, so do nothing and maybe interrupt.
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300551 return None
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000552 self.text.event_generate("<<Copy>>")
553 return "break"
554
555 def paste(self,event):
556 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000557 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000558 return "break"
559
David Scherer7aced172000-08-15 01:13:23 +0000560 def select_all(self, event=None):
561 self.text.tag_add("sel", "1.0", "end-1c")
562 self.text.mark_set("insert", "1.0")
563 self.text.see("insert")
564 return "break"
565
566 def remove_selection(self, event=None):
567 self.text.tag_remove("sel", "1.0", "end")
568 self.text.see("insert")
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300569 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000570
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000571 def move_at_edge_if_selection(self, edge_index):
572 """Cursor move begins at start or end of selection
573
574 When a left/right cursor key is pressed create and return to Tkinter a
575 function which causes a cursor move from the associated edge of the
576 selection.
577
578 """
579 self_text_index = self.text.index
580 self_text_mark_set = self.text.mark_set
581 edges_table = ("sel.first+1c", "sel.last-1c")
582 def move_at_edge(event):
583 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
584 try:
585 self_text_index("sel.first")
586 self_text_mark_set("insert", edges_table[edge_index])
587 except TclError:
588 pass
589 return move_at_edge
590
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000591 def del_word_left(self, event):
592 self.text.event_generate('<Meta-Delete>')
593 return "break"
594
595 def del_word_right(self, event):
596 self.text.event_generate('<Meta-d>')
597 return "break"
598
Steven M. Gavac5976402002-01-04 03:06:08 +0000599 def find_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400600 search.find(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000601 return "break"
602
603 def find_again_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400604 search.find_again(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000605 return "break"
606
607 def find_selection_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400608 search.find_selection(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000609 return "break"
610
611 def find_in_files_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400612 grep.grep(self.text, self.io, self.flist)
Steven M. Gavac5976402002-01-04 03:06:08 +0000613 return "break"
614
615 def replace_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400616 replace.replace(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000617 return "break"
618
619 def goto_line_event(self, event):
620 text = self.text
621 lineno = tkSimpleDialog.askinteger("Goto",
622 "Go to line number:",parent=text)
623 if lineno is None:
624 return "break"
625 if lineno <= 0:
626 text.bell()
627 return "break"
628 text.mark_set("insert", "%d.0" % lineno)
629 text.see("insert")
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300630 return "break"
Steven M. Gavac5976402002-01-04 03:06:08 +0000631
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300632 def open_module(self):
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400633 """Get module name from user and open it.
634
635 Return module path or None for calls by open_class_browser
636 when latter is not invoked in named editor window.
637 """
638 # XXX This, open_class_browser, and open_path_browser
639 # would fit better in iomenu.IOBinding.
David Scherer7aced172000-08-15 01:13:23 +0000640 try:
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400641 name = self.text.get("sel.first", "sel.last").strip()
David Scherer7aced172000-08-15 01:13:23 +0000642 except TclError:
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400643 name = ''
644 file_path = query.ModuleName(
645 self.text, "Open Module",
646 "Enter the name of a Python module\n"
647 "to search on sys.path and open:",
648 name).result
649 if file_path is not None:
650 if self.flist:
651 self.flist.open(file_path)
652 else:
653 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400654 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000655
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300656 def open_module_event(self, event):
657 self.open_module()
658 return "break"
659
David Scherer7aced172000-08-15 01:13:23 +0000660 def open_class_browser(self, event=None):
661 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400662 if not (self.__class__.__name__ == 'PyShellEditorWindow'
663 and filename):
664 filename = self.open_module()
665 if filename is None:
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300666 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000667 head, tail = os.path.split(filename)
668 base, ext = os.path.splitext(tail)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400669 from idlelib import browser
670 browser.ClassBrowser(self.flist, base, [head])
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300671 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000672
673 def open_path_browser(self, event=None):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400674 from idlelib import pathbrowser
675 pathbrowser.PathBrowser(self.flist)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300676 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000677
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400678 def open_turtle_demo(self, event = None):
679 import subprocess
680
681 cmd = [sys.executable,
682 '-c',
683 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400684 subprocess.Popen(cmd, shell=False)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300685 return "break"
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400686
David Scherer7aced172000-08-15 01:13:23 +0000687 def gotoline(self, lineno):
688 if lineno is not None and lineno > 0:
689 self.text.mark_set("insert", "%d.0" % lineno)
690 self.text.tag_remove("sel", "1.0", "end")
691 self.text.tag_add("sel", "insert", "insert +1l")
692 self.center()
693
694 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000695 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000696 return True
David Scherer7aced172000-08-15 01:13:23 +0000697 base, ext = os.path.splitext(os.path.basename(filename))
698 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000699 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000700 line = self.text.get('1.0', '1.0 lineend')
701 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000702
703 def close_hook(self):
704 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000705 self.flist.unregister_maybe_terminate(self)
706 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000707
708 def set_close_hook(self, close_hook):
709 self.close_hook = close_hook
710
711 def filename_change_hook(self):
712 if self.flist:
713 self.flist.filename_changed_edit(self)
714 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000715 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000716 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000717
Christian Heimesa156e092008-02-16 07:38:31 +0000718 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000719 if self.color:
720 return
Christian Heimesa156e092008-02-16 07:38:31 +0000721 if self.ispythonsource(self.io.filename):
722 self.color = self.ColorDelegator()
723 # can add more colorizers here...
724 if self.color:
725 self.per.removefilter(self.undo)
726 self.per.insertfilter(self.color)
727 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000728
Christian Heimesa156e092008-02-16 07:38:31 +0000729 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000730 if not self.color:
731 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000732 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000733 self.per.removefilter(self.color)
734 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000735
Steven M. Gavab77d3432002-03-02 07:16:21 +0000736 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400737 "Update the color theme"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400738 # Called from self.filename_change_hook and from configdialog.py
Christian Heimesa156e092008-02-16 07:38:31 +0000739 self._rmcolorizer()
740 self._addcolorizer()
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -0400741 EditorWindow.color_config(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000742
Guido van Rossum33d26892007-08-05 15:29:28 +0000743 IDENTCHARS = string.ascii_letters + string.digits + "_"
744
745 def colorize_syntax_error(self, text, pos):
746 text.tag_add("ERROR", pos)
747 char = text.get(pos)
748 if char and char in self.IDENTCHARS:
749 text.tag_add("ERROR", pos + " wordstart", pos)
750 if '\n' == text.get(pos): # error at line end
751 text.mark_set("insert", pos)
752 else:
753 text.mark_set("insert", pos + "+1c")
754 text.see(pos)
755
Steven M. Gavab1585412002-03-12 00:21:56 +0000756 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000757 "Update the text widgets' font if it is changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400758 # Called from configdialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400759
760 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000761
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000762 def RemoveKeybindings(self):
763 "Remove the keybindings before they are changed."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400764 # Called from configdialog.py
765 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000766 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000767 self.text.event_delete(event, *keylist)
768 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000769 xkeydefs = idleConf.GetExtensionBindings(extensionName)
770 if xkeydefs:
771 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000772 self.text.event_delete(event, *keylist)
773
774 def ApplyKeybindings(self):
775 "Update the keybindings after they are changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400776 # Called from configdialog.py
777 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000778 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000779 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000780 xkeydefs = idleConf.GetExtensionBindings(extensionName)
781 if xkeydefs:
782 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000783 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000784 menuEventDict = {}
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400785 for menu in self.mainmenu.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000786 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000787 for item in menu[1]:
788 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000789 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000790 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000791 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700792 end = menu.index(END)
793 if end is None:
794 # Skip empty menus
795 continue
796 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000797 for index in range(0, end):
798 if menu.type(index) == 'command':
799 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000800 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000801 itemName = menu.entrycget(index, 'label')
802 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000803 if menubarItem in menuEventDict:
804 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000805 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000806 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000807 accel = get_accelerator(keydefs, event)
808 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000809
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000810 def set_notabs_indentwidth(self):
811 "Update the indentwidth if changed and not using tabs in this window"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400812 # Called from configdialog.py
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000813 if not self.usetabs:
814 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
815 type='int')
816
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000817 def reset_help_menu_entries(self):
818 "Update the additional help entries on the Help menu"
819 help_list = idleConf.GetAllExtraHelpSourcesList()
820 helpmenu = self.menudict['help']
821 # first delete the extra help entries, if any
822 helpmenu_length = helpmenu.index(END)
823 if helpmenu_length > self.base_helpmenu_length:
824 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
825 # then rebuild them
826 if help_list:
827 helpmenu.add_separator()
828 for entry in help_list:
829 cmd = self.__extra_help_callback(entry[1])
830 helpmenu.add_command(label=entry[0], command=cmd)
831 # and update the menu dictionary
832 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000833
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000834 def __extra_help_callback(self, helpfile):
835 "Create a callback with the helpfile value frozen at definition time"
836 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000837 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000838 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000839 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000840 try:
841 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200842 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000843 tkMessageBox.showerror(title='Document Start Failure',
844 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000845 else:
846 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000847 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000848
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000849 def update_recent_files_list(self, new_file=None):
850 "Load and update the recent files list and menus"
851 rf_list = []
852 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400853 with open(self.recent_files_path, 'r',
854 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000855 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000856 if new_file:
857 new_file = os.path.abspath(new_file) + '\n'
858 if new_file in rf_list:
859 rf_list.remove(new_file) # move to top
860 rf_list.insert(0, new_file)
861 # clean and save the recent files list
862 bad_paths = []
863 for path in rf_list:
864 if '\0' in path or not os.path.exists(path[0:-1]):
865 bad_paths.append(path)
866 rf_list = [path for path in rf_list if path not in bad_paths]
867 ulchars = "1234567890ABCDEFGHIJK"
868 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000869 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800870 with open(self.recent_files_path, 'w',
871 encoding='utf_8', errors='replace') as rf_file:
872 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200873 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800874 if not getattr(self.root, "recentfilelist_error_displayed", False):
875 self.root.recentfilelist_error_displayed = True
Terry Jan Reedy4c973e92015-10-27 03:38:02 -0400876 tkMessageBox.showwarning(title='IDLE Warning',
877 message="Cannot update File menu Recent Files list. "
878 "Your operating system says:\n%s\n"
879 "Select OK and IDLE will continue without updating."
880 % self._filename_to_unicode(str(err)),
Ned Deilyf505b742011-12-14 14:58:24 -0800881 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000882 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000883 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000884 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700885 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000886 for i, file_name in enumerate(rf_list):
887 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000888 # make unicode string to display non-ASCII chars correctly
889 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000890 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000891 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000892 command=callback,
893 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000894
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000895 def __recent_file_callback(self, file_name):
896 def open_recent_file(fn_closure=file_name):
897 self.io.open(editFile=fn_closure)
898 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000899
David Scherer7aced172000-08-15 01:13:23 +0000900 def saved_change_hook(self):
901 short = self.short_title()
902 long = self.long_title()
903 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400904 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000905 elif short:
906 title = short
907 elif long:
908 title = long
909 else:
910 title = "Untitled"
911 icon = short or long or title
912 if not self.get_saved():
913 title = "*%s*" % title
914 icon = "*%s" % icon
915 self.top.wm_title(title)
916 self.top.wm_iconname(icon)
917
918 def get_saved(self):
919 return self.undo.get_saved()
920
921 def set_saved(self, flag):
922 self.undo.set_saved(flag)
923
924 def reset_undo(self):
925 self.undo.reset_undo()
926
927 def short_title(self):
928 filename = self.io.filename
929 if filename:
930 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500931 else:
932 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000933 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400934 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000935
936 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000937 # return unicode string to display non-ASCII chars correctly
938 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000939
940 def center_insert_event(self, event):
941 self.center()
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300942 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000943
944 def center(self, mark="insert"):
945 text = self.text
946 top, bot = self.getwindowlines()
947 lineno = self.getlineno(mark)
948 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000949 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000950 text.yview(float(newtop))
951
952 def getwindowlines(self):
953 text = self.text
954 top = self.getlineno("@0,0")
955 bot = self.getlineno("@0,65535")
956 if top == bot and text.winfo_height() == 1:
957 # Geometry manager hasn't run yet
958 height = int(text['height'])
959 bot = top + height - 1
960 return top, bot
961
962 def getlineno(self, mark="insert"):
963 text = self.text
964 return int(float(text.index(mark)))
965
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000966 def get_geometry(self):
967 "Return (width, height, x, y)"
968 geom = self.top.wm_geometry()
969 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000970 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000971
David Scherer7aced172000-08-15 01:13:23 +0000972 def close_event(self, event):
973 self.close()
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300974 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000975
976 def maybesave(self):
977 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000978 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000979 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000980 self.top.deiconify()
981 self.top.lower()
982 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000983 return self.io.maybesave()
984
985 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000986 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000987 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000988 self._close()
989 return reply
990
991 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000992 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000993 self.update_recent_files_list(new_file=self.io.filename)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400994 windows.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000995 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000996 self.io.close()
997 self.io = None
998 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000999 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001000 self.color.close(False)
1001 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001002 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001003 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001004 self.per.close()
1005 self.per = None
1006 self.top.destroy()
1007 if self.close_hook:
1008 # unless override: unregister from flist, terminate if last window
1009 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001010
1011 def load_extensions(self):
1012 self.extensions = {}
1013 self.load_standard_extensions()
1014
1015 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001016 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001017 if hasattr(ins, "close"):
1018 ins.close()
1019 self.extensions = {}
1020
1021 def load_standard_extensions(self):
1022 for name in self.get_standard_extension_names():
1023 try:
1024 self.load_extension(name)
1025 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001026 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001027 traceback.print_exc()
1028
1029 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001030 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001031
wohlganger58fc71c2017-09-10 16:19:47 -05001032 extfiles = { # Map built-in config-extension section names to file names.
1033 'ZzDummy': 'zzdummy',
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001034 }
1035
David Scherer7aced172000-08-15 01:13:23 +00001036 def load_extension(self, name):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001037 fname = self.extfiles.get(name, name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001038 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001039 try:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001040 mod = importlib.import_module('.' + fname, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001041 except (ImportError, TypeError):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001042 mod = importlib.import_module(fname)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001043 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001044 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001045 raise
David Scherer7aced172000-08-15 01:13:23 +00001046 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001047 keydefs = idleConf.GetExtensionBindings(name)
1048 if hasattr(cls, "menudefs"):
1049 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001050 ins = cls(self)
1051 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001052 if keydefs:
1053 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001054 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001055 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001056 while methodname[:1] == '<':
1057 methodname = methodname[1:]
1058 while methodname[-1:] == '>':
1059 methodname = methodname[:-1]
1060 methodname = methodname + "_event"
1061 if hasattr(ins, methodname):
1062 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001063
1064 def apply_bindings(self, keydefs=None):
1065 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001066 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001067 text = self.text
1068 text.keydefs = keydefs
1069 for event, keylist in keydefs.items():
1070 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001071 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001072
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001073 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001074 """Add appropriate entries to the menus and submenus
1075
1076 Menus that are absent or None in self.menudict are ignored.
1077 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001078 if menudefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001079 menudefs = self.mainmenu.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001080 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001081 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001082 menudict = self.menudict
1083 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001084 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001085 menu = menudict.get(mname)
1086 if not menu:
1087 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001088 for entry in entrylist:
1089 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001090 menu.add_separator()
1091 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001092 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001093 checkbutton = (label[:1] == '!')
1094 if checkbutton:
1095 label = label[1:]
1096 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001097 accelerator = get_accelerator(keydefs, eventname)
1098 def command(text=text, eventname=eventname):
1099 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001100 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001101 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001102 menu.add_checkbutton(label=label, underline=underline,
1103 command=command, accelerator=accelerator,
1104 variable=var)
1105 else:
1106 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001107 command=command,
1108 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001109
1110 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001111 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001112 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001113 value = var.get()
1114 return value
1115 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001116 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001117
1118 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001119 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001120 if var:
1121 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001122 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001123 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001124
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001125 def get_var_obj(self, name, vartype=None):
1126 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001127 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001128 # create a Tkinter variable object with self.text as master:
1129 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001130 return var
1131
1132 # Tk implementations of "virtual text methods" -- each platform
1133 # reusing IDLE's support code needs to define these for its GUI's
1134 # flavor of widget.
1135
1136 # Is character at text_index in a Python string? Return 0 for
1137 # "guaranteed no", true for anything else. This info is expensive
1138 # to compute ab initio, but is probably already known by the
1139 # platform's colorizer.
1140
1141 def is_char_in_string(self, text_index):
1142 if self.color:
1143 # Return true iff colorizer hasn't (re)gotten this far
1144 # yet, or the character is tagged as being in a string
1145 return self.text.tag_prevrange("TODO", text_index) or \
1146 "STRING" in self.text.tag_names(text_index)
1147 else:
1148 # The colorizer is missing: assume the worst
1149 return 1
1150
1151 # If a selection is defined in the text widget, return (start,
1152 # end) as Tkinter text indices, otherwise return (None, None)
1153 def get_selection_indices(self):
1154 try:
1155 first = self.text.index("sel.first")
1156 last = self.text.index("sel.last")
1157 return first, last
1158 except TclError:
1159 return None, None
1160
1161 # Return the text widget's current view of what a tab stop means
1162 # (equivalent width in spaces).
1163
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001164 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001165 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1166 return int(current)
1167
1168 # Set the text widget's current view of what a tab stop means.
1169
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001170 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001171 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001172 if self.get_tk_tabwidth() != newtabwidth:
1173 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001174 pixels = text.tk.call("font", "measure", text["font"],
1175 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001176 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001177 text.configure(tabs=pixels)
1178
Guido van Rossum33d26892007-08-05 15:29:28 +00001179### begin autoindent code ### (configuration was moved to beginning of class)
1180
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001181 def set_indentation_params(self, is_py_src, guess=True):
1182 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001183 i = self.guess_indent()
1184 if 2 <= i <= 8:
1185 self.indentwidth = i
1186 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001187 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001188 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001189
1190 def smart_backspace_event(self, event):
1191 text = self.text
1192 first, last = self.get_selection_indices()
1193 if first and last:
1194 text.delete(first, last)
1195 text.mark_set("insert", first)
1196 return "break"
1197 # Delete whitespace left, until hitting a real char or closest
1198 # preceding virtual tab stop.
1199 chars = text.get("insert linestart", "insert")
1200 if chars == '':
1201 if text.compare("insert", ">", "1.0"):
1202 # easy: delete preceding newline
1203 text.delete("insert-1c")
1204 else:
1205 text.bell() # at start of buffer
1206 return "break"
1207 if chars[-1] not in " \t":
1208 # easy: delete preceding real char
1209 text.delete("insert-1c")
1210 return "break"
1211 # Ick. It may require *inserting* spaces if we back up over a
1212 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001213 tabwidth = self.tabwidth
1214 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001215 assert have > 0
1216 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001217 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001218 if self.context_use_ps1:
1219 last_line_of_prompt = sys.ps1.split('\n')[-1]
1220 else:
1221 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001222 ncharsdeleted = 0
1223 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001224 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001225 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001226 chars = chars[:-1]
1227 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001228 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001229 if have <= want or chars[-1] not in " \t":
1230 break
1231 text.undo_block_start()
1232 text.delete("insert-%dc" % ncharsdeleted, "insert")
1233 if have < want:
1234 text.insert("insert", ' ' * (want - have))
1235 text.undo_block_stop()
1236 return "break"
1237
1238 def smart_indent_event(self, event):
1239 # if intraline selection:
1240 # delete it
1241 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001242 # do indent-region
1243 # else:
1244 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001245 text = self.text
1246 first, last = self.get_selection_indices()
1247 text.undo_block_start()
1248 try:
1249 if first and last:
1250 if index2line(first) != index2line(last):
1251 return self.indent_region_event(event)
1252 text.delete(first, last)
1253 text.mark_set("insert", first)
1254 prefix = text.get("insert linestart", "insert")
1255 raw, effective = classifyws(prefix, self.tabwidth)
1256 if raw == len(prefix):
1257 # only whitespace to the left
1258 self.reindent_to(effective + self.indentwidth)
1259 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001260 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001261 if self.usetabs:
1262 pad = '\t'
1263 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001264 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001265 n = self.indentwidth
1266 pad = ' ' * (n - effective % n)
1267 text.insert("insert", pad)
1268 text.see("insert")
1269 return "break"
1270 finally:
1271 text.undo_block_stop()
1272
1273 def newline_and_indent_event(self, event):
1274 text = self.text
1275 first, last = self.get_selection_indices()
1276 text.undo_block_start()
1277 try:
1278 if first and last:
1279 text.delete(first, last)
1280 text.mark_set("insert", first)
1281 line = text.get("insert linestart", "insert")
1282 i, n = 0, len(line)
1283 while i < n and line[i] in " \t":
1284 i = i+1
1285 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001286 # the cursor is in or at leading indentation in a continuation
1287 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001288 text.insert("insert linestart", '\n')
1289 return "break"
1290 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001291 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001292 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001293 last_line_of_prompt = sys.ps1.split('\n')[-1]
1294 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001295 line = line[:-1]
1296 i = i+1
1297 if i:
1298 text.delete("insert - %d chars" % i, "insert")
1299 # strip whitespace after insert point
1300 while text.get("insert") in " \t":
1301 text.delete("insert")
1302 # start new line
1303 text.insert("insert", '\n')
1304
1305 # adjust indentation for continuations and block
1306 # open/close first need to find the last stmt
1307 lno = index2line(text.index('insert'))
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001308 y = pyparse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001309 if not self.context_use_ps1:
1310 for context in self.num_context_lines:
1311 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001312 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001313 rawtext = text.get(startatindex, "insert")
1314 y.set_str(rawtext)
1315 bod = y.find_good_parse_start(
1316 self.context_use_ps1,
1317 self._build_char_in_string_func(startatindex))
1318 if bod is not None or startat == 1:
1319 break
1320 y.set_lo(bod or 0)
1321 else:
1322 r = text.tag_prevrange("console", "insert")
1323 if r:
1324 startatindex = r[1]
1325 else:
1326 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001327 rawtext = text.get(startatindex, "insert")
1328 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001329 y.set_lo(0)
1330
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001331 c = y.get_continuation_type()
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001332 if c != pyparse.C_NONE:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001333 # The current stmt hasn't ended yet.
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001334 if c == pyparse.C_STRING_FIRST_LINE:
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001335 # after the first line of a string; do not indent at all
1336 pass
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001337 elif c == pyparse.C_STRING_NEXT_LINES:
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001338 # inside a string which started before this line;
1339 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001340 text.insert("insert", indent)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001341 elif c == pyparse.C_BRACKET:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001342 # line up with the first (if any) element of the
1343 # last open bracket structure; else indent one
1344 # level beyond the indent of the line with the
1345 # last open bracket
1346 self.reindent_to(y.compute_bracket_indent())
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001347 elif c == pyparse.C_BACKSLASH:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001348 # if more than one line in this stmt already, just
1349 # mimic the current indent; else if initial line
1350 # has a start on an assignment stmt, indent to
1351 # beyond leftmost =; else to beyond first chunk of
1352 # non-whitespace on initial line
1353 if y.get_num_lines_in_stmt() > 1:
1354 text.insert("insert", indent)
1355 else:
1356 self.reindent_to(y.compute_backslash_indent())
1357 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001358 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001359 return "break"
1360
1361 # This line starts a brand new stmt; indent relative to
1362 # indentation of initial line of closest preceding
1363 # interesting stmt.
1364 indent = y.get_base_indent_string()
1365 text.insert("insert", indent)
1366 if y.is_block_opener():
1367 self.smart_indent_event(event)
1368 elif indent and y.is_block_closer():
1369 self.smart_backspace_event(event)
1370 return "break"
1371 finally:
1372 text.see("insert")
1373 text.undo_block_stop()
1374
Martin Panter7462b6492015-11-02 03:37:02 +00001375 # Our editwin provides an is_char_in_string function that works
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001376 # with a Tk text index, but PyParse only knows about offsets into
1377 # a string. This builds a function for PyParse that accepts an
1378 # offset.
1379
1380 def _build_char_in_string_func(self, startindex):
1381 def inner(offset, _startindex=startindex,
1382 _icis=self.is_char_in_string):
1383 return _icis(_startindex + "+%dc" % offset)
1384 return inner
1385
1386 def indent_region_event(self, event):
1387 head, tail, chars, lines = self.get_region()
1388 for pos in range(len(lines)):
1389 line = lines[pos]
1390 if line:
1391 raw, effective = classifyws(line, self.tabwidth)
1392 effective = effective + self.indentwidth
1393 lines[pos] = self._make_blanks(effective) + line[raw:]
1394 self.set_region(head, tail, chars, lines)
1395 return "break"
1396
1397 def dedent_region_event(self, event):
1398 head, tail, chars, lines = self.get_region()
1399 for pos in range(len(lines)):
1400 line = lines[pos]
1401 if line:
1402 raw, effective = classifyws(line, self.tabwidth)
1403 effective = max(effective - self.indentwidth, 0)
1404 lines[pos] = self._make_blanks(effective) + line[raw:]
1405 self.set_region(head, tail, chars, lines)
1406 return "break"
1407
1408 def comment_region_event(self, event):
1409 head, tail, chars, lines = self.get_region()
1410 for pos in range(len(lines) - 1):
1411 line = lines[pos]
1412 lines[pos] = '##' + line
1413 self.set_region(head, tail, chars, lines)
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001414 return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001415
1416 def uncomment_region_event(self, event):
1417 head, tail, chars, lines = self.get_region()
1418 for pos in range(len(lines)):
1419 line = lines[pos]
1420 if not line:
1421 continue
1422 if line[:2] == '##':
1423 line = line[2:]
1424 elif line[:1] == '#':
1425 line = line[1:]
1426 lines[pos] = line
1427 self.set_region(head, tail, chars, lines)
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001428 return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001429
1430 def tabify_region_event(self, event):
1431 head, tail, chars, lines = self.get_region()
1432 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001433 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001434 for pos in range(len(lines)):
1435 line = lines[pos]
1436 if line:
1437 raw, effective = classifyws(line, tabwidth)
1438 ntabs, nspaces = divmod(effective, tabwidth)
1439 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1440 self.set_region(head, tail, chars, lines)
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001441 return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001442
1443 def untabify_region_event(self, event):
1444 head, tail, chars, lines = self.get_region()
1445 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001446 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001447 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001448 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001449 self.set_region(head, tail, chars, lines)
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001450 return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001451
1452 def toggle_tabs_event(self, event):
1453 if self.askyesno(
1454 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001455 "Turn tabs " + ("on", "off")[self.usetabs] +
1456 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001457 ("will be", "remains at")[self.usetabs] + " 8." +
1458 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001459 parent=self.text):
1460 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001461 # Try to prevent inconsistent indentation.
1462 # User must change indent width manually after using tabs.
1463 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001464 return "break"
1465
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001466 # XXX this isn't bound to anything -- see tabwidth comments
1467## def change_tabwidth_event(self, event):
1468## new = self._asktabwidth()
1469## if new != self.tabwidth:
1470## self.tabwidth = new
1471## self.set_indentation_params(0, guess=0)
1472## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001473
1474 def change_indentwidth_event(self, event):
1475 new = self.askinteger(
1476 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001477 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001478 parent=self.text,
1479 initialvalue=self.indentwidth,
1480 minvalue=2,
1481 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001482 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001483 self.indentwidth = new
1484 return "break"
1485
1486 def get_region(self):
1487 text = self.text
1488 first, last = self.get_selection_indices()
1489 if first and last:
1490 head = text.index(first + " linestart")
1491 tail = text.index(last + "-1c lineend +1c")
1492 else:
1493 head = text.index("insert linestart")
1494 tail = text.index("insert lineend +1c")
1495 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001496 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001497 return head, tail, chars, lines
1498
1499 def set_region(self, head, tail, chars, lines):
1500 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001501 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001502 if newchars == chars:
1503 text.bell()
1504 return
1505 text.tag_remove("sel", "1.0", "end")
1506 text.mark_set("insert", head)
1507 text.undo_block_start()
1508 text.delete(head, tail)
1509 text.insert(head, newchars)
1510 text.undo_block_stop()
1511 text.tag_add("sel", head, "insert")
1512
1513 # Make string that displays as n leading blanks.
1514
1515 def _make_blanks(self, n):
1516 if self.usetabs:
1517 ntabs, nspaces = divmod(n, self.tabwidth)
1518 return '\t' * ntabs + ' ' * nspaces
1519 else:
1520 return ' ' * n
1521
1522 # Delete from beginning of line to insert point, then reinsert
1523 # column logical (meaning use tabs if appropriate) spaces.
1524
1525 def reindent_to(self, column):
1526 text = self.text
1527 text.undo_block_start()
1528 if text.compare("insert linestart", "!=", "insert"):
1529 text.delete("insert linestart", "insert")
1530 if column:
1531 text.insert("insert", self._make_blanks(column))
1532 text.undo_block_stop()
1533
1534 def _asktabwidth(self):
1535 return self.askinteger(
1536 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001537 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001538 parent=self.text,
1539 initialvalue=self.indentwidth,
1540 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001541 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001542
1543 # Guess indentwidth from text content.
1544 # Return guessed indentwidth. This should not be believed unless
1545 # it's in a reasonable range (e.g., it will be 0 if no indented
1546 # blocks are found).
1547
1548 def guess_indent(self):
1549 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1550 if opener and indented:
1551 raw, indentsmall = classifyws(opener, self.tabwidth)
1552 raw, indentlarge = classifyws(indented, self.tabwidth)
1553 else:
1554 indentsmall = indentlarge = 0
1555 return indentlarge - indentsmall
1556
1557# "line.col" -> line, as an int
1558def index2line(index):
1559 return int(float(index))
1560
1561# Look at the leading whitespace in s.
1562# Return pair (# of leading ws characters,
1563# effective # of leading blanks after expanding
1564# tabs to width tabwidth)
1565
1566def classifyws(s, tabwidth):
1567 raw = effective = 0
1568 for ch in s:
1569 if ch == ' ':
1570 raw = raw + 1
1571 effective = effective + 1
1572 elif ch == '\t':
1573 raw = raw + 1
1574 effective = (effective // tabwidth + 1) * tabwidth
1575 else:
1576 break
1577 return raw, effective
1578
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001579
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001580class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001581
1582 # .run() chews over the Text widget, looking for a block opener
1583 # and the stmt following it. Returns a pair,
1584 # (line containing block opener, line containing stmt)
1585 # Either or both may be None.
1586
1587 def __init__(self, text, tabwidth):
1588 self.text = text
1589 self.tabwidth = tabwidth
1590 self.i = self.finished = 0
1591 self.blkopenline = self.indentedline = None
1592
1593 def readline(self):
1594 if self.finished:
1595 return ""
1596 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001597 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001598 if self.text.compare(mark, ">=", "end"):
1599 return ""
1600 return self.text.get(mark, mark + " lineend+1c")
1601
1602 def tokeneater(self, type, token, start, end, line,
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001603 INDENT=tokenize.INDENT,
1604 NAME=tokenize.NAME,
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001605 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1606 if self.finished:
1607 pass
1608 elif type == NAME and token in OPENERS:
1609 self.blkopenline = line
1610 elif type == INDENT and self.blkopenline:
1611 self.indentedline = line
1612 self.finished = 1
1613
1614 def run(self):
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001615 save_tabsize = tokenize.tabsize
1616 tokenize.tabsize = self.tabwidth
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001617 try:
1618 try:
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001619 tokens = tokenize.generate_tokens(self.readline)
Trent Nelson428de652008-03-18 22:41:35 +00001620 for token in tokens:
1621 self.tokeneater(*token)
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001622 except (tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001623 # since we cut off the tokenizer early, we can trigger
1624 # spurious errors
1625 pass
1626 finally:
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001627 tokenize.tabsize = save_tabsize
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001628 return self.blkopenline, self.indentedline
1629
1630### end autoindent code ###
1631
David Scherer7aced172000-08-15 01:13:23 +00001632def prepstr(s):
1633 # Helper to extract the underscore from a string, e.g.
1634 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001635 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001636 if i >= 0:
1637 s = s[:i] + s[i+1:]
1638 return i, s
1639
1640
1641keynames = {
1642 'bracketleft': '[',
1643 'bracketright': ']',
1644 'slash': '/',
1645}
1646
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001647def get_accelerator(keydefs, eventname):
1648 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001649 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1650 # if not keylist:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001651 if (not keylist) or (macosx.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001652 "<<open-module>>",
1653 "<<goto-line>>",
1654 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001655 return ""
1656 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001657 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001658 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1659 s = re.sub("Key-", "", s)
1660 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1661 s = re.sub("Control-", "Ctrl-", s)
1662 s = re.sub("-", "+", s)
1663 s = re.sub("><", " ", s)
1664 s = re.sub("<", "", s)
1665 s = re.sub(">", "", s)
1666 return s
1667
1668
1669def fixwordbreaks(root):
1670 # Make sure that Tk's double-click and next/previous word
1671 # operations use our definition of a word (i.e. an identifier)
1672 tk = root.tk
1673 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1674 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1675 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1676
1677
Terry Jan Reedycd567362014-10-17 01:31:35 -04001678def _editor_window(parent): # htest #
1679 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001680 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001681 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001682 if sys.argv[1:]:
1683 filename = sys.argv[1]
1684 else:
1685 filename = None
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001686 macosx.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001687 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001688 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001689 # Does not stop error, neither does following
1690 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001691
1692if __name__ == '__main__':
Terry Jan Reedy7c153412016-06-26 17:48:02 -04001693 import unittest
1694 unittest.main('idlelib.idle_test.test_editor', verbosity=2, exit=False)
1695
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001696 from idlelib.idle_test.htest import run
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04001697 run(_editor_window)