blob: b51c45c97e50f2a3d3102416589e61674bc9d156 [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
David Scherer7aced172000-08-15 01:13:23 +0000102 self.menubar = Menu(root)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400103 self.top = top = windows.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000104 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000105 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200106 #self.top.instance_dict makes flist.inversedict available to
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400107 #configdialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000108 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000109 else:
110 self.tkinter_vars = {} # keys: Tkinter event names
111 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000112 self.top.instance_dict = {}
terryjreedy223c7e72017-07-07 22:28:06 -0400113 self.recent_files_path = os.path.join(
114 idleConf.userdir, 'recent-files.lst')
Terry Jan Reedye86172d2017-10-27 20:26:12 -0400115
116 self.prompt_last_line = '' # Override in PyShell
David Scherer7aced172000-08-15 01:13:23 +0000117 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000118 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200119 self.width = idleConf.GetOption('main', 'EditorWindow',
120 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000121 text_options = {
122 'name': 'text',
123 'padx': 5,
124 'wrap': 'none',
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500125 'highlightthickness': 0,
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000126 'width': self.width,
Terry Jan Reedy1080d132016-06-09 21:09:15 -0400127 'tabstyle': 'wordprocessor', # new in 8.5
128 'height': idleConf.GetOption(
129 'main', 'EditorWindow', 'height', type='int'),
130 }
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000131 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000132 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000133
134 self.createmenubar()
135 self.apply_bindings()
136
137 self.top.protocol("WM_DELETE_WINDOW", self.close)
138 self.top.bind("<<close-window>>", self.close_event)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400139 if macosx.isAquaTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000140 # Command-W on editorwindows doesn't work without this.
141 text.bind('<<close-window>>', self.close_event)
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400142 # Some OS X systems have only one mouse button, so use
143 # control-click for popup context menus there. For two
144 # buttons, AquaTk defines <2> as the right button, not <3>.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000145 text.bind("<Control-Button-1>",self.right_menu_event)
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400146 text.bind("<2>", self.right_menu_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000147 else:
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400148 # Elsewhere, use right-click for popup menus.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000149 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000150 text.bind("<<cut>>", self.cut)
151 text.bind("<<copy>>", self.copy)
152 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000153 text.bind("<<center-insert>>", self.center_insert_event)
154 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000155 text.bind("<<python-docs>>", self.python_docs)
156 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000157 text.bind("<<open-config-dialog>>", self.config_dialog)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300158 text.bind("<<open-module>>", self.open_module_event)
David Scherer7aced172000-08-15 01:13:23 +0000159 text.bind("<<do-nothing>>", lambda event: "break")
160 text.bind("<<select-all>>", self.select_all)
161 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000162 text.bind("<<find>>", self.find_event)
163 text.bind("<<find-again>>", self.find_again_event)
164 text.bind("<<find-in-files>>", self.find_in_files_event)
165 text.bind("<<find-selection>>", self.find_selection_event)
166 text.bind("<<replace>>", self.replace_event)
167 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000168 text.bind("<<smart-backspace>>",self.smart_backspace_event)
169 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
170 text.bind("<<smart-indent>>",self.smart_indent_event)
171 text.bind("<<indent-region>>",self.indent_region_event)
172 text.bind("<<dedent-region>>",self.dedent_region_event)
173 text.bind("<<comment-region>>",self.comment_region_event)
174 text.bind("<<uncomment-region>>",self.uncomment_region_event)
175 text.bind("<<tabify-region>>",self.tabify_region_event)
176 text.bind("<<untabify-region>>",self.untabify_region_event)
177 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
178 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000179 text.bind("<Left>", self.move_at_edge_if_selection(0))
180 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000181 text.bind("<<del-word-left>>", self.del_word_left)
182 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000183 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000184
David Scherer7aced172000-08-15 01:13:23 +0000185 if flist:
186 flist.inversedict[self] = key
187 if key:
188 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000189 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000190 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400191 text.bind("<<open-class-browser>>", self.open_module_browser)
David Scherer7aced172000-08-15 01:13:23 +0000192 text.bind("<<open-path-browser>>", self.open_path_browser)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400193 text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
David Scherer7aced172000-08-15 01:13:23 +0000194
Steven M. Gava898a3652001-10-07 11:10:44 +0000195 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000196 vbar['command'] = text.yview
197 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000198 text['yscrollcommand'] = vbar.set
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400199 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000200 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
201 text.pack(side=TOP, fill=BOTH, expand=1)
202 text.focus_set()
203
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000204 # usetabs true -> literal tab characters are used by indent and
205 # dedent cmds, possibly mixed with spaces if
206 # indentwidth is not a multiple of tabwidth,
207 # which will cause Tabnanny to nag!
208 # false -> tab characters are converted to spaces by indent
209 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000210 # Although use-spaces=0 can be configured manually in config-main.def,
211 # configuration of tabs v. spaces is not supported in the configuration
212 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200213 usespaces = idleConf.GetOption('main', 'Indent',
214 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000215 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000216
217 # tabwidth is the display width of a literal tab character.
218 # CAUTION: telling Tk to use anything other than its default
219 # tab setting causes it to use an entirely different tabbing algorithm,
220 # treating tab stops as fixed distances from the left margin.
221 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000222 self.tabwidth = 8 # must remain 8 until Tk is fixed.
223
224 # indentwidth is the number of screen characters per indent level.
225 # The recommended Python indentation is four spaces.
226 self.indentwidth = self.tabwidth
227 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000228
229 # If context_use_ps1 is true, parsing searches back for a ps1 line;
230 # else searches for a popular (if, def, ...) Python stmt.
231 self.context_use_ps1 = False
232
233 # When searching backwards for a reliable place to begin parsing,
234 # first start num_context_lines[0] lines back, then
235 # num_context_lines[1] lines back if that didn't work, and so on.
236 # The last value should be huge (larger than the # of lines in a
237 # conceivable file).
238 # Making the initial values larger slows things down more often.
239 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000240 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000241 self.undo = undo = self.UndoDelegator()
242 per.insertfilter(undo)
243 text.undo_block_start = undo.undo_block_start
244 text.undo_block_stop = undo.undo_block_stop
245 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000246 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000247 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000248 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000249 self.good_load = False
250 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000251 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000252 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000253 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000254 if io.loadfile(filename):
255 self.good_load = True
256 is_py_src = self.ispythonsource(filename)
257 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000258 else:
259 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500260 self.good_load = True
261
Christian Heimesa156e092008-02-16 07:38:31 +0000262 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000263 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000264 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000265 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000266 menu = self.menudict.get('windows')
267 if menu:
268 end = menu.index("end")
269 if end is None:
270 end = -1
271 if end >= 0:
272 menu.add_separator()
273 end = end + 1
274 self.wmenu_end = end
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400275 windows.register_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000276
277 # Some abstractions so IDLE extensions are cross-IDE
278 self.askyesno = tkMessageBox.askyesno
279 self.askinteger = tkSimpleDialog.askinteger
280 self.showerror = tkMessageBox.showerror
281
wohlganger58fc71c2017-09-10 16:19:47 -0500282 # Add pseudoevents for former extension fixed keys.
283 # (This probably needs to be done once in the process.)
284 text.event_add('<<autocomplete>>', '<Key-Tab>')
285 text.event_add('<<try-open-completions>>', '<KeyRelease-period>',
286 '<KeyRelease-slash>', '<KeyRelease-backslash>')
287 text.event_add('<<try-open-calltip>>', '<KeyRelease-parenleft>')
288 text.event_add('<<refresh-calltip>>', '<KeyRelease-parenright>')
289 text.event_add('<<paren-closed>>', '<KeyRelease-parenright>',
290 '<KeyRelease-bracketright>', '<KeyRelease-braceright>')
291
292 # Former extension bindings depends on frame.text being packed
293 # (called from self.ResetColorizer()).
294 autocomplete = self.AutoComplete(self)
295 text.bind("<<autocomplete>>", autocomplete.autocomplete_event)
296 text.bind("<<try-open-completions>>",
297 autocomplete.try_open_completions_event)
298 text.bind("<<force-open-completions>>",
299 autocomplete.force_open_completions_event)
300 text.bind("<<expand-word>>", self.AutoExpand(self).expand_word_event)
301 text.bind("<<format-paragraph>>",
302 self.FormatParagraph(self).format_paragraph_event)
303 parenmatch = self.ParenMatch(self)
304 text.bind("<<flash-paren>>", parenmatch.flash_paren_event)
305 text.bind("<<paren-closed>>", parenmatch.paren_closed_event)
306 scriptbinding = ScriptBinding(self)
307 text.bind("<<check-module>>", scriptbinding.check_module_event)
308 text.bind("<<run-module>>", scriptbinding.run_module_event)
309 text.bind("<<do-rstrip>>", self.RstripExtension(self).do_rstrip)
310 calltips = self.CallTips(self)
311 text.bind("<<try-open-calltip>>", calltips.try_open_calltip_event)
312 #refresh-calltips must come after paren-closed to work right
313 text.bind("<<refresh-calltip>>", calltips.refresh_calltip_event)
314 text.bind("<<force-open-calltip>>", calltips.force_open_calltip_event)
315 text.bind("<<zoom-height>>", self.ZoomHeight(self).zoom_height_event)
316 text.bind("<<toggle-code-context>>",
317 self.CodeContext(self).toggle_code_context_event)
318
Martin v. Löwis307021f2005-11-27 16:59:04 +0000319 def _filename_to_unicode(self, filename):
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400320 """Return filename as BMP unicode so diplayable in Tk."""
321 # Decode bytes to unicode.
322 if isinstance(filename, bytes):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000323 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400324 filename = filename.decode(self.filesystemencoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000325 except UnicodeDecodeError:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000326 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400327 filename = filename.decode(self.encoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000328 except UnicodeDecodeError:
329 # byte-to-byte conversion
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400330 filename = filename.decode('iso8859-1')
331 # Replace non-BMP char with diamond questionmark.
332 return re.sub('[\U00010000-\U0010FFFF]', '\ufffd', filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000333
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000334 def new_callback(self, event):
335 dirname, basename = self.io.defaultfilename()
336 self.flist.new(dirname)
337 return "break"
338
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000339 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400340 if (event.state & 4) != 0 and event.keysym == "Home":
341 # state&4==Control. If <Control-Home>, use the Tk binding.
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300342 return None
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000343 if self.text.index("iomark") and \
344 self.text.compare("iomark", "<=", "insert lineend") and \
345 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400346 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000347 insertpt = int(self.text.index("iomark").split(".")[1])
348 else:
349 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000350 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000351 if line[insertpt] not in (' ','\t'):
352 break
353 else:
354 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000355 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000356 if insertpt == lineat:
357 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000358 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000359 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400360 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000361 self.text.tag_remove("sel", "1.0", "end")
362 else:
363 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200364 # there was no previous selection
365 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400366 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200367 if self.text.compare(self.text.index("sel.first"), "<",
368 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400369 self.text.mark_set("my_anchor", "sel.first") # extend back
370 else:
371 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000372 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400373 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000374 if self.text.compare(first,">",last):
375 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000376 self.text.tag_remove("sel", "1.0", "end")
377 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000378 self.text.mark_set("insert", dest)
379 self.text.see("insert")
380 return "break"
381
David Scherer7aced172000-08-15 01:13:23 +0000382 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000383 self.status_bar = self.MultiStatusBar(self.top)
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500384 sep = Frame(self.top, height=1, borderwidth=1, background='grey75')
Ned Deilyb7601672014-03-27 20:49:14 -0700385 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000386 # Insert some padding to avoid obscuring some of the statusbar
387 # by the resize widget.
388 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000389 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
390 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
391 self.status_bar.pack(side=BOTTOM, fill=X)
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500392 sep.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000393 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
394 self.text.event_add("<<set-line-and-column>>",
395 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000396 self.text.after_idle(self.set_line_and_column)
397
398 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000399 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000400 self.status_bar.set_label('column', 'Col: %s' % column)
401 self.status_bar.set_label('line', 'Ln: %s' % line)
402
David Scherer7aced172000-08-15 01:13:23 +0000403 menu_specs = [
404 ("file", "_File"),
405 ("edit", "_Edit"),
406 ("format", "F_ormat"),
407 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000408 ("options", "_Options"),
Ned Deilyccb416f2015-01-17 21:06:27 -0800409 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000410 ("help", "_Help"),
411 ]
412
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000413
David Scherer7aced172000-08-15 01:13:23 +0000414 def createmenubar(self):
415 mbar = self.menubar
416 self.menudict = menudict = {}
417 for name, label in self.menu_specs:
418 underline, label = prepstr(label)
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400419 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000420 mbar.add_cascade(label=label, menu=menu, underline=underline)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400421 if macosx.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000422 # Insert the application menu
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400423 menudict['application'] = menu = Menu(mbar, name='apple',
424 tearoff=0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000425 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000426 self.fill_menus()
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400427 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000428 self.menudict['file'].insert_cascade(3, label='Recent Files',
429 underline=0,
430 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000431 self.base_helpmenu_length = self.menudict['help'].index(END)
432 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000433
434 def postwindowsmenu(self):
435 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000436 menu = self.menudict['windows']
437 end = menu.index("end")
438 if end is None:
439 end = -1
440 if end > self.wmenu_end:
441 menu.delete(self.wmenu_end+1, end)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400442 windows.add_windows_to_menu(menu)
David Scherer7aced172000-08-15 01:13:23 +0000443
444 rmenu = None
445
446 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000447 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
448 if not self.rmenu:
449 self.make_rmenu()
450 rmenu = self.rmenu
451 self.event = event
452 iswin = sys.platform[:3] == 'win'
453 if iswin:
454 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200455
Roger Serwy6b2918a2013-04-07 12:15:52 -0500456 for item in self.rmenu_specs:
457 try:
458 label, eventname, verify_state = item
459 except ValueError: # see issue1207589
460 continue
461
Andrew Svetlovd1837672012-11-01 22:41:19 +0200462 if verify_state is None:
463 continue
464 state = getattr(self, verify_state)()
465 rmenu.entryconfigure(label, state=state)
466
467
David Scherer7aced172000-08-15 01:13:23 +0000468 rmenu.tk_popup(event.x_root, event.y_root)
469 if iswin:
470 self.text.config(cursor="ibeam")
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300471 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000472
473 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200474 # ("Label", "<<virtual-event>>", "statefuncname"), ...
475 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000476 ]
477
478 def make_rmenu(self):
479 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500480 for item in self.rmenu_specs:
481 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200482 if label is not None:
483 def command(text=self.text, eventname=eventname):
484 text.event_generate(eventname)
485 rmenu.add_command(label=label, command=command)
486 else:
487 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000488 self.rmenu = rmenu
489
Andrew Svetlovd1837672012-11-01 22:41:19 +0200490 def rmenu_check_cut(self):
491 return self.rmenu_check_copy()
492
493 def rmenu_check_copy(self):
494 try:
495 indx = self.text.index('sel.first')
496 except TclError:
497 return 'disabled'
498 else:
499 return 'normal' if indx else 'disabled'
500
501 def rmenu_check_paste(self):
502 try:
503 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
504 except TclError:
505 return 'disabled'
506 else:
507 return 'normal'
508
David Scherer7aced172000-08-15 01:13:23 +0000509 def about_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400510 "Handle Help 'About IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400511 # Synchronize with macosx.overrideRootMenu.about_dialog.
csabella18ede062017-06-23 20:00:58 -0400512 help_about.AboutDialog(self.top)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300513 return "break"
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000514
Steven M. Gava3b55a892001-11-21 05:56:26 +0000515 def config_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400516 "Handle Options 'Configure IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400517 # Synchronize with macosx.overrideRootMenu.config_dialog.
518 configdialog.ConfigDialog(self.top,'Settings')
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300519 return "break"
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400520
David Scherer7aced172000-08-15 01:13:23 +0000521 def help_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400522 "Handle Help 'IDLE Help' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400523 # Synchronize with macosx.overrideRootMenu.help_dialog.
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500524 if self.root:
525 parent = self.root
526 else:
527 parent = self.top
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400528 help.show_idlehelp(parent)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300529 return "break"
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000530
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000531 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000532 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000533 try:
534 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200535 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000536 tkMessageBox.showerror(title='Document Start Failure',
537 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000538 else:
539 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000540 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000541
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000542 def cut(self,event):
543 self.text.event_generate("<<Cut>>")
544 return "break"
545
546 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000547 if not self.text.tag_ranges("sel"):
548 # There is no selection, so do nothing and maybe interrupt.
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300549 return None
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000550 self.text.event_generate("<<Copy>>")
551 return "break"
552
553 def paste(self,event):
554 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000555 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000556 return "break"
557
David Scherer7aced172000-08-15 01:13:23 +0000558 def select_all(self, event=None):
559 self.text.tag_add("sel", "1.0", "end-1c")
560 self.text.mark_set("insert", "1.0")
561 self.text.see("insert")
562 return "break"
563
564 def remove_selection(self, event=None):
565 self.text.tag_remove("sel", "1.0", "end")
566 self.text.see("insert")
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300567 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000568
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000569 def move_at_edge_if_selection(self, edge_index):
570 """Cursor move begins at start or end of selection
571
572 When a left/right cursor key is pressed create and return to Tkinter a
573 function which causes a cursor move from the associated edge of the
574 selection.
575
576 """
577 self_text_index = self.text.index
578 self_text_mark_set = self.text.mark_set
579 edges_table = ("sel.first+1c", "sel.last-1c")
580 def move_at_edge(event):
581 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
582 try:
583 self_text_index("sel.first")
584 self_text_mark_set("insert", edges_table[edge_index])
585 except TclError:
586 pass
587 return move_at_edge
588
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000589 def del_word_left(self, event):
590 self.text.event_generate('<Meta-Delete>')
591 return "break"
592
593 def del_word_right(self, event):
594 self.text.event_generate('<Meta-d>')
595 return "break"
596
Steven M. Gavac5976402002-01-04 03:06:08 +0000597 def find_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400598 search.find(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000599 return "break"
600
601 def find_again_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400602 search.find_again(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000603 return "break"
604
605 def find_selection_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400606 search.find_selection(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000607 return "break"
608
609 def find_in_files_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400610 grep.grep(self.text, self.io, self.flist)
Steven M. Gavac5976402002-01-04 03:06:08 +0000611 return "break"
612
613 def replace_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400614 replace.replace(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000615 return "break"
616
617 def goto_line_event(self, event):
618 text = self.text
619 lineno = tkSimpleDialog.askinteger("Goto",
620 "Go to line number:",parent=text)
621 if lineno is None:
622 return "break"
623 if lineno <= 0:
624 text.bell()
625 return "break"
626 text.mark_set("insert", "%d.0" % lineno)
627 text.see("insert")
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300628 return "break"
Steven M. Gavac5976402002-01-04 03:06:08 +0000629
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300630 def open_module(self):
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400631 """Get module name from user and open it.
632
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400633 Return module path or None for calls by open_module_browser
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400634 when latter is not invoked in named editor window.
635 """
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400636 # XXX This, open_module_browser, and open_path_browser
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400637 # would fit better in iomenu.IOBinding.
David Scherer7aced172000-08-15 01:13:23 +0000638 try:
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400639 name = self.text.get("sel.first", "sel.last").strip()
David Scherer7aced172000-08-15 01:13:23 +0000640 except TclError:
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400641 name = ''
642 file_path = query.ModuleName(
643 self.text, "Open Module",
644 "Enter the name of a Python module\n"
645 "to search on sys.path and open:",
646 name).result
647 if file_path is not None:
648 if self.flist:
649 self.flist.open(file_path)
650 else:
651 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400652 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000653
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300654 def open_module_event(self, event):
655 self.open_module()
656 return "break"
657
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400658 def open_module_browser(self, event=None):
David Scherer7aced172000-08-15 01:13:23 +0000659 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400660 if not (self.__class__.__name__ == 'PyShellEditorWindow'
661 and filename):
662 filename = self.open_module()
663 if filename is None:
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300664 return "break"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400665 from idlelib import browser
Terry Jan Reedyd6bb65f2017-09-30 19:54:28 -0400666 browser.ModuleBrowser(self.root, filename)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300667 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000668
669 def open_path_browser(self, event=None):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400670 from idlelib import pathbrowser
Cheryl Sabella20d48a42017-11-22 19:05:25 -0500671 pathbrowser.PathBrowser(self.root)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300672 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000673
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400674 def open_turtle_demo(self, event = None):
675 import subprocess
676
677 cmd = [sys.executable,
678 '-c',
679 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400680 subprocess.Popen(cmd, shell=False)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300681 return "break"
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400682
David Scherer7aced172000-08-15 01:13:23 +0000683 def gotoline(self, lineno):
684 if lineno is not None and lineno > 0:
685 self.text.mark_set("insert", "%d.0" % lineno)
686 self.text.tag_remove("sel", "1.0", "end")
687 self.text.tag_add("sel", "insert", "insert +1l")
688 self.center()
689
690 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000691 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000692 return True
David Scherer7aced172000-08-15 01:13:23 +0000693 base, ext = os.path.splitext(os.path.basename(filename))
694 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000695 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000696 line = self.text.get('1.0', '1.0 lineend')
697 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000698
699 def close_hook(self):
700 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000701 self.flist.unregister_maybe_terminate(self)
702 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000703
704 def set_close_hook(self, close_hook):
705 self.close_hook = close_hook
706
707 def filename_change_hook(self):
708 if self.flist:
709 self.flist.filename_changed_edit(self)
710 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000711 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000712 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000713
Christian Heimesa156e092008-02-16 07:38:31 +0000714 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000715 if self.color:
716 return
Christian Heimesa156e092008-02-16 07:38:31 +0000717 if self.ispythonsource(self.io.filename):
718 self.color = self.ColorDelegator()
719 # can add more colorizers here...
720 if self.color:
721 self.per.removefilter(self.undo)
722 self.per.insertfilter(self.color)
723 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000724
Christian Heimesa156e092008-02-16 07:38:31 +0000725 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000726 if not self.color:
727 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000728 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000729 self.per.removefilter(self.color)
730 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000731
Steven M. Gavab77d3432002-03-02 07:16:21 +0000732 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400733 "Update the color theme"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400734 # Called from self.filename_change_hook and from configdialog.py
Christian Heimesa156e092008-02-16 07:38:31 +0000735 self._rmcolorizer()
736 self._addcolorizer()
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -0400737 EditorWindow.color_config(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000738
Guido van Rossum33d26892007-08-05 15:29:28 +0000739 IDENTCHARS = string.ascii_letters + string.digits + "_"
740
741 def colorize_syntax_error(self, text, pos):
742 text.tag_add("ERROR", pos)
743 char = text.get(pos)
744 if char and char in self.IDENTCHARS:
745 text.tag_add("ERROR", pos + " wordstart", pos)
746 if '\n' == text.get(pos): # error at line end
747 text.mark_set("insert", pos)
748 else:
749 text.mark_set("insert", pos + "+1c")
750 text.see(pos)
751
Steven M. Gavab1585412002-03-12 00:21:56 +0000752 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000753 "Update the text widgets' font if it is changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400754 # Called from configdialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400755
756 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000757
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000758 def RemoveKeybindings(self):
759 "Remove the keybindings before they are changed."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400760 # Called from configdialog.py
761 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000762 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000763 self.text.event_delete(event, *keylist)
764 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000765 xkeydefs = idleConf.GetExtensionBindings(extensionName)
766 if xkeydefs:
767 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000768 self.text.event_delete(event, *keylist)
769
770 def ApplyKeybindings(self):
771 "Update the keybindings after they are changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400772 # Called from configdialog.py
773 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000774 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000775 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000776 xkeydefs = idleConf.GetExtensionBindings(extensionName)
777 if xkeydefs:
778 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000779 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000780 menuEventDict = {}
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400781 for menu in self.mainmenu.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000782 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000783 for item in menu[1]:
784 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000785 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000786 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000787 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700788 end = menu.index(END)
789 if end is None:
790 # Skip empty menus
791 continue
792 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000793 for index in range(0, end):
794 if menu.type(index) == 'command':
795 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000796 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000797 itemName = menu.entrycget(index, 'label')
798 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000799 if menubarItem in menuEventDict:
800 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000801 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000802 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000803 accel = get_accelerator(keydefs, event)
804 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000805
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000806 def set_notabs_indentwidth(self):
807 "Update the indentwidth if changed and not using tabs in this window"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400808 # Called from configdialog.py
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000809 if not self.usetabs:
810 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
811 type='int')
812
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000813 def reset_help_menu_entries(self):
814 "Update the additional help entries on the Help menu"
815 help_list = idleConf.GetAllExtraHelpSourcesList()
816 helpmenu = self.menudict['help']
817 # first delete the extra help entries, if any
818 helpmenu_length = helpmenu.index(END)
819 if helpmenu_length > self.base_helpmenu_length:
820 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
821 # then rebuild them
822 if help_list:
823 helpmenu.add_separator()
824 for entry in help_list:
825 cmd = self.__extra_help_callback(entry[1])
826 helpmenu.add_command(label=entry[0], command=cmd)
827 # and update the menu dictionary
828 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000829
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000830 def __extra_help_callback(self, helpfile):
831 "Create a callback with the helpfile value frozen at definition time"
832 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000833 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000834 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000835 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000836 try:
837 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200838 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000839 tkMessageBox.showerror(title='Document Start Failure',
840 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000841 else:
842 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000843 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000844
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000845 def update_recent_files_list(self, new_file=None):
846 "Load and update the recent files list and menus"
847 rf_list = []
848 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400849 with open(self.recent_files_path, 'r',
850 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000851 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000852 if new_file:
853 new_file = os.path.abspath(new_file) + '\n'
854 if new_file in rf_list:
855 rf_list.remove(new_file) # move to top
856 rf_list.insert(0, new_file)
857 # clean and save the recent files list
858 bad_paths = []
859 for path in rf_list:
860 if '\0' in path or not os.path.exists(path[0:-1]):
861 bad_paths.append(path)
862 rf_list = [path for path in rf_list if path not in bad_paths]
863 ulchars = "1234567890ABCDEFGHIJK"
864 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000865 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800866 with open(self.recent_files_path, 'w',
867 encoding='utf_8', errors='replace') as rf_file:
868 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200869 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800870 if not getattr(self.root, "recentfilelist_error_displayed", False):
871 self.root.recentfilelist_error_displayed = True
Terry Jan Reedy4c973e92015-10-27 03:38:02 -0400872 tkMessageBox.showwarning(title='IDLE Warning',
873 message="Cannot update File menu Recent Files list. "
874 "Your operating system says:\n%s\n"
875 "Select OK and IDLE will continue without updating."
876 % self._filename_to_unicode(str(err)),
Ned Deilyf505b742011-12-14 14:58:24 -0800877 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000878 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000879 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000880 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700881 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000882 for i, file_name in enumerate(rf_list):
883 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000884 # make unicode string to display non-ASCII chars correctly
885 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000886 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000887 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000888 command=callback,
889 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000890
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000891 def __recent_file_callback(self, file_name):
892 def open_recent_file(fn_closure=file_name):
893 self.io.open(editFile=fn_closure)
894 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000895
David Scherer7aced172000-08-15 01:13:23 +0000896 def saved_change_hook(self):
897 short = self.short_title()
898 long = self.long_title()
899 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400900 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000901 elif short:
902 title = short
903 elif long:
904 title = long
905 else:
906 title = "Untitled"
907 icon = short or long or title
908 if not self.get_saved():
909 title = "*%s*" % title
910 icon = "*%s" % icon
911 self.top.wm_title(title)
912 self.top.wm_iconname(icon)
913
914 def get_saved(self):
915 return self.undo.get_saved()
916
917 def set_saved(self, flag):
918 self.undo.set_saved(flag)
919
920 def reset_undo(self):
921 self.undo.reset_undo()
922
923 def short_title(self):
924 filename = self.io.filename
925 if filename:
926 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500927 else:
928 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000929 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400930 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000931
932 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000933 # return unicode string to display non-ASCII chars correctly
934 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000935
936 def center_insert_event(self, event):
937 self.center()
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300938 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000939
940 def center(self, mark="insert"):
941 text = self.text
942 top, bot = self.getwindowlines()
943 lineno = self.getlineno(mark)
944 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000945 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000946 text.yview(float(newtop))
947
948 def getwindowlines(self):
949 text = self.text
950 top = self.getlineno("@0,0")
951 bot = self.getlineno("@0,65535")
952 if top == bot and text.winfo_height() == 1:
953 # Geometry manager hasn't run yet
954 height = int(text['height'])
955 bot = top + height - 1
956 return top, bot
957
958 def getlineno(self, mark="insert"):
959 text = self.text
960 return int(float(text.index(mark)))
961
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000962 def get_geometry(self):
963 "Return (width, height, x, y)"
964 geom = self.top.wm_geometry()
965 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000966 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000967
David Scherer7aced172000-08-15 01:13:23 +0000968 def close_event(self, event):
969 self.close()
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300970 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000971
972 def maybesave(self):
973 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000974 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000975 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000976 self.top.deiconify()
977 self.top.lower()
978 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000979 return self.io.maybesave()
980
981 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000982 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000983 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000984 self._close()
985 return reply
986
987 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000988 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000989 self.update_recent_files_list(new_file=self.io.filename)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400990 windows.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000991 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000992 self.io.close()
993 self.io = None
994 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000995 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000996 self.color.close(False)
997 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000998 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000999 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001000 self.per.close()
1001 self.per = None
1002 self.top.destroy()
1003 if self.close_hook:
1004 # unless override: unregister from flist, terminate if last window
1005 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001006
1007 def load_extensions(self):
1008 self.extensions = {}
1009 self.load_standard_extensions()
1010
1011 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001012 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001013 if hasattr(ins, "close"):
1014 ins.close()
1015 self.extensions = {}
1016
1017 def load_standard_extensions(self):
1018 for name in self.get_standard_extension_names():
1019 try:
1020 self.load_extension(name)
1021 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001022 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001023 traceback.print_exc()
1024
1025 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001026 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001027
wohlganger58fc71c2017-09-10 16:19:47 -05001028 extfiles = { # Map built-in config-extension section names to file names.
1029 'ZzDummy': 'zzdummy',
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001030 }
1031
David Scherer7aced172000-08-15 01:13:23 +00001032 def load_extension(self, name):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001033 fname = self.extfiles.get(name, name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001034 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001035 try:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001036 mod = importlib.import_module('.' + fname, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001037 except (ImportError, TypeError):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001038 mod = importlib.import_module(fname)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001039 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001040 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001041 raise
David Scherer7aced172000-08-15 01:13:23 +00001042 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001043 keydefs = idleConf.GetExtensionBindings(name)
1044 if hasattr(cls, "menudefs"):
1045 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001046 ins = cls(self)
1047 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001048 if keydefs:
1049 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001050 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001051 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001052 while methodname[:1] == '<':
1053 methodname = methodname[1:]
1054 while methodname[-1:] == '>':
1055 methodname = methodname[:-1]
1056 methodname = methodname + "_event"
1057 if hasattr(ins, methodname):
1058 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001059
1060 def apply_bindings(self, keydefs=None):
1061 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001062 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001063 text = self.text
1064 text.keydefs = keydefs
1065 for event, keylist in keydefs.items():
1066 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001067 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001068
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001069 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001070 """Add appropriate entries to the menus and submenus
1071
1072 Menus that are absent or None in self.menudict are ignored.
1073 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001074 if menudefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001075 menudefs = self.mainmenu.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001076 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001077 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001078 menudict = self.menudict
1079 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001080 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001081 menu = menudict.get(mname)
1082 if not menu:
1083 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001084 for entry in entrylist:
1085 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001086 menu.add_separator()
1087 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001088 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001089 checkbutton = (label[:1] == '!')
1090 if checkbutton:
1091 label = label[1:]
1092 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001093 accelerator = get_accelerator(keydefs, eventname)
1094 def command(text=text, eventname=eventname):
1095 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001096 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001097 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001098 menu.add_checkbutton(label=label, underline=underline,
1099 command=command, accelerator=accelerator,
1100 variable=var)
1101 else:
1102 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001103 command=command,
1104 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001105
1106 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001107 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001108 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001109 value = var.get()
1110 return value
1111 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001112 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001113
1114 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001115 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001116 if var:
1117 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001118 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001119 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001120
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001121 def get_var_obj(self, name, vartype=None):
1122 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001123 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001124 # create a Tkinter variable object with self.text as master:
1125 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001126 return var
1127
1128 # Tk implementations of "virtual text methods" -- each platform
1129 # reusing IDLE's support code needs to define these for its GUI's
1130 # flavor of widget.
1131
1132 # Is character at text_index in a Python string? Return 0 for
1133 # "guaranteed no", true for anything else. This info is expensive
1134 # to compute ab initio, but is probably already known by the
1135 # platform's colorizer.
1136
1137 def is_char_in_string(self, text_index):
1138 if self.color:
1139 # Return true iff colorizer hasn't (re)gotten this far
1140 # yet, or the character is tagged as being in a string
1141 return self.text.tag_prevrange("TODO", text_index) or \
1142 "STRING" in self.text.tag_names(text_index)
1143 else:
1144 # The colorizer is missing: assume the worst
1145 return 1
1146
1147 # If a selection is defined in the text widget, return (start,
1148 # end) as Tkinter text indices, otherwise return (None, None)
1149 def get_selection_indices(self):
1150 try:
1151 first = self.text.index("sel.first")
1152 last = self.text.index("sel.last")
1153 return first, last
1154 except TclError:
1155 return None, None
1156
1157 # Return the text widget's current view of what a tab stop means
1158 # (equivalent width in spaces).
1159
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001160 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001161 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1162 return int(current)
1163
1164 # Set the text widget's current view of what a tab stop means.
1165
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001166 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001167 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001168 if self.get_tk_tabwidth() != newtabwidth:
1169 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001170 pixels = text.tk.call("font", "measure", text["font"],
1171 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001172 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001173 text.configure(tabs=pixels)
1174
Guido van Rossum33d26892007-08-05 15:29:28 +00001175### begin autoindent code ### (configuration was moved to beginning of class)
1176
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001177 def set_indentation_params(self, is_py_src, guess=True):
1178 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001179 i = self.guess_indent()
1180 if 2 <= i <= 8:
1181 self.indentwidth = i
1182 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001183 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001184 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001185
1186 def smart_backspace_event(self, event):
1187 text = self.text
1188 first, last = self.get_selection_indices()
1189 if first and last:
1190 text.delete(first, last)
1191 text.mark_set("insert", first)
1192 return "break"
1193 # Delete whitespace left, until hitting a real char or closest
1194 # preceding virtual tab stop.
1195 chars = text.get("insert linestart", "insert")
1196 if chars == '':
1197 if text.compare("insert", ">", "1.0"):
1198 # easy: delete preceding newline
1199 text.delete("insert-1c")
1200 else:
1201 text.bell() # at start of buffer
1202 return "break"
1203 if chars[-1] not in " \t":
1204 # easy: delete preceding real char
1205 text.delete("insert-1c")
1206 return "break"
1207 # Ick. It may require *inserting* spaces if we back up over a
1208 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001209 tabwidth = self.tabwidth
1210 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001211 assert have > 0
1212 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001213 # Debug prompt is multilined....
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001214 ncharsdeleted = 0
1215 while 1:
Terry Jan Reedye86172d2017-10-27 20:26:12 -04001216 if chars == self.prompt_last_line: # '' unless PyShell
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001217 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001218 chars = chars[:-1]
1219 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001220 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001221 if have <= want or chars[-1] not in " \t":
1222 break
1223 text.undo_block_start()
1224 text.delete("insert-%dc" % ncharsdeleted, "insert")
1225 if have < want:
1226 text.insert("insert", ' ' * (want - have))
1227 text.undo_block_stop()
1228 return "break"
1229
1230 def smart_indent_event(self, event):
1231 # if intraline selection:
1232 # delete it
1233 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001234 # do indent-region
1235 # else:
1236 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001237 text = self.text
1238 first, last = self.get_selection_indices()
1239 text.undo_block_start()
1240 try:
1241 if first and last:
1242 if index2line(first) != index2line(last):
1243 return self.indent_region_event(event)
1244 text.delete(first, last)
1245 text.mark_set("insert", first)
1246 prefix = text.get("insert linestart", "insert")
1247 raw, effective = classifyws(prefix, self.tabwidth)
1248 if raw == len(prefix):
1249 # only whitespace to the left
1250 self.reindent_to(effective + self.indentwidth)
1251 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001252 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001253 if self.usetabs:
1254 pad = '\t'
1255 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001256 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001257 n = self.indentwidth
1258 pad = ' ' * (n - effective % n)
1259 text.insert("insert", pad)
1260 text.see("insert")
1261 return "break"
1262 finally:
1263 text.undo_block_stop()
1264
1265 def newline_and_indent_event(self, event):
1266 text = self.text
1267 first, last = self.get_selection_indices()
1268 text.undo_block_start()
1269 try:
1270 if first and last:
1271 text.delete(first, last)
1272 text.mark_set("insert", first)
1273 line = text.get("insert linestart", "insert")
1274 i, n = 0, len(line)
1275 while i < n and line[i] in " \t":
1276 i = i+1
1277 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001278 # the cursor is in or at leading indentation in a continuation
1279 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001280 text.insert("insert linestart", '\n')
1281 return "break"
1282 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001283 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001284 i = 0
Terry Jan Reedye86172d2017-10-27 20:26:12 -04001285 while line and line[-1] in " \t" and line != self.prompt_last_line:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001286 line = line[:-1]
1287 i = i+1
1288 if i:
1289 text.delete("insert - %d chars" % i, "insert")
1290 # strip whitespace after insert point
1291 while text.get("insert") in " \t":
1292 text.delete("insert")
1293 # start new line
1294 text.insert("insert", '\n')
1295
1296 # adjust indentation for continuations and block
1297 # open/close first need to find the last stmt
1298 lno = index2line(text.index('insert'))
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001299 y = pyparse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001300 if not self.context_use_ps1:
1301 for context in self.num_context_lines:
1302 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001303 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001304 rawtext = text.get(startatindex, "insert")
1305 y.set_str(rawtext)
1306 bod = y.find_good_parse_start(
1307 self.context_use_ps1,
1308 self._build_char_in_string_func(startatindex))
1309 if bod is not None or startat == 1:
1310 break
1311 y.set_lo(bod or 0)
1312 else:
1313 r = text.tag_prevrange("console", "insert")
1314 if r:
1315 startatindex = r[1]
1316 else:
1317 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001318 rawtext = text.get(startatindex, "insert")
1319 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001320 y.set_lo(0)
1321
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001322 c = y.get_continuation_type()
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001323 if c != pyparse.C_NONE:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001324 # The current stmt hasn't ended yet.
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001325 if c == pyparse.C_STRING_FIRST_LINE:
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001326 # after the first line of a string; do not indent at all
1327 pass
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001328 elif c == pyparse.C_STRING_NEXT_LINES:
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001329 # inside a string which started before this line;
1330 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001331 text.insert("insert", indent)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001332 elif c == pyparse.C_BRACKET:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001333 # line up with the first (if any) element of the
1334 # last open bracket structure; else indent one
1335 # level beyond the indent of the line with the
1336 # last open bracket
1337 self.reindent_to(y.compute_bracket_indent())
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001338 elif c == pyparse.C_BACKSLASH:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001339 # if more than one line in this stmt already, just
1340 # mimic the current indent; else if initial line
1341 # has a start on an assignment stmt, indent to
1342 # beyond leftmost =; else to beyond first chunk of
1343 # non-whitespace on initial line
1344 if y.get_num_lines_in_stmt() > 1:
1345 text.insert("insert", indent)
1346 else:
1347 self.reindent_to(y.compute_backslash_indent())
1348 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001349 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001350 return "break"
1351
1352 # This line starts a brand new stmt; indent relative to
1353 # indentation of initial line of closest preceding
1354 # interesting stmt.
1355 indent = y.get_base_indent_string()
1356 text.insert("insert", indent)
1357 if y.is_block_opener():
1358 self.smart_indent_event(event)
1359 elif indent and y.is_block_closer():
1360 self.smart_backspace_event(event)
1361 return "break"
1362 finally:
1363 text.see("insert")
1364 text.undo_block_stop()
1365
Martin Panter7462b6492015-11-02 03:37:02 +00001366 # Our editwin provides an is_char_in_string function that works
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001367 # with a Tk text index, but PyParse only knows about offsets into
1368 # a string. This builds a function for PyParse that accepts an
1369 # offset.
1370
1371 def _build_char_in_string_func(self, startindex):
1372 def inner(offset, _startindex=startindex,
1373 _icis=self.is_char_in_string):
1374 return _icis(_startindex + "+%dc" % offset)
1375 return inner
1376
1377 def indent_region_event(self, event):
1378 head, tail, chars, lines = self.get_region()
1379 for pos in range(len(lines)):
1380 line = lines[pos]
1381 if line:
1382 raw, effective = classifyws(line, self.tabwidth)
1383 effective = effective + self.indentwidth
1384 lines[pos] = self._make_blanks(effective) + line[raw:]
1385 self.set_region(head, tail, chars, lines)
1386 return "break"
1387
1388 def dedent_region_event(self, event):
1389 head, tail, chars, lines = self.get_region()
1390 for pos in range(len(lines)):
1391 line = lines[pos]
1392 if line:
1393 raw, effective = classifyws(line, self.tabwidth)
1394 effective = max(effective - self.indentwidth, 0)
1395 lines[pos] = self._make_blanks(effective) + line[raw:]
1396 self.set_region(head, tail, chars, lines)
1397 return "break"
1398
1399 def comment_region_event(self, event):
1400 head, tail, chars, lines = self.get_region()
1401 for pos in range(len(lines) - 1):
1402 line = lines[pos]
1403 lines[pos] = '##' + line
1404 self.set_region(head, tail, chars, lines)
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001405 return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001406
1407 def uncomment_region_event(self, event):
1408 head, tail, chars, lines = self.get_region()
1409 for pos in range(len(lines)):
1410 line = lines[pos]
1411 if not line:
1412 continue
1413 if line[:2] == '##':
1414 line = line[2:]
1415 elif line[:1] == '#':
1416 line = line[1:]
1417 lines[pos] = line
1418 self.set_region(head, tail, chars, lines)
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001419 return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001420
1421 def tabify_region_event(self, event):
1422 head, tail, chars, lines = self.get_region()
1423 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001424 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001425 for pos in range(len(lines)):
1426 line = lines[pos]
1427 if line:
1428 raw, effective = classifyws(line, tabwidth)
1429 ntabs, nspaces = divmod(effective, tabwidth)
1430 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1431 self.set_region(head, tail, chars, lines)
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001432 return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001433
1434 def untabify_region_event(self, event):
1435 head, tail, chars, lines = self.get_region()
1436 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001437 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001438 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001439 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001440 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 toggle_tabs_event(self, event):
1444 if self.askyesno(
1445 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001446 "Turn tabs " + ("on", "off")[self.usetabs] +
1447 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001448 ("will be", "remains at")[self.usetabs] + " 8." +
1449 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001450 parent=self.text):
1451 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001452 # Try to prevent inconsistent indentation.
1453 # User must change indent width manually after using tabs.
1454 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001455 return "break"
1456
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001457 # XXX this isn't bound to anything -- see tabwidth comments
1458## def change_tabwidth_event(self, event):
1459## new = self._asktabwidth()
1460## if new != self.tabwidth:
1461## self.tabwidth = new
1462## self.set_indentation_params(0, guess=0)
1463## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001464
1465 def change_indentwidth_event(self, event):
1466 new = self.askinteger(
1467 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001468 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001469 parent=self.text,
1470 initialvalue=self.indentwidth,
1471 minvalue=2,
1472 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001473 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001474 self.indentwidth = new
1475 return "break"
1476
1477 def get_region(self):
1478 text = self.text
1479 first, last = self.get_selection_indices()
1480 if first and last:
1481 head = text.index(first + " linestart")
1482 tail = text.index(last + "-1c lineend +1c")
1483 else:
1484 head = text.index("insert linestart")
1485 tail = text.index("insert lineend +1c")
1486 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001487 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001488 return head, tail, chars, lines
1489
1490 def set_region(self, head, tail, chars, lines):
1491 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001492 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001493 if newchars == chars:
1494 text.bell()
1495 return
1496 text.tag_remove("sel", "1.0", "end")
1497 text.mark_set("insert", head)
1498 text.undo_block_start()
1499 text.delete(head, tail)
1500 text.insert(head, newchars)
1501 text.undo_block_stop()
1502 text.tag_add("sel", head, "insert")
1503
1504 # Make string that displays as n leading blanks.
1505
1506 def _make_blanks(self, n):
1507 if self.usetabs:
1508 ntabs, nspaces = divmod(n, self.tabwidth)
1509 return '\t' * ntabs + ' ' * nspaces
1510 else:
1511 return ' ' * n
1512
1513 # Delete from beginning of line to insert point, then reinsert
1514 # column logical (meaning use tabs if appropriate) spaces.
1515
1516 def reindent_to(self, column):
1517 text = self.text
1518 text.undo_block_start()
1519 if text.compare("insert linestart", "!=", "insert"):
1520 text.delete("insert linestart", "insert")
1521 if column:
1522 text.insert("insert", self._make_blanks(column))
1523 text.undo_block_stop()
1524
1525 def _asktabwidth(self):
1526 return self.askinteger(
1527 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001528 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001529 parent=self.text,
1530 initialvalue=self.indentwidth,
1531 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001532 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001533
1534 # Guess indentwidth from text content.
1535 # Return guessed indentwidth. This should not be believed unless
1536 # it's in a reasonable range (e.g., it will be 0 if no indented
1537 # blocks are found).
1538
1539 def guess_indent(self):
1540 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1541 if opener and indented:
1542 raw, indentsmall = classifyws(opener, self.tabwidth)
1543 raw, indentlarge = classifyws(indented, self.tabwidth)
1544 else:
1545 indentsmall = indentlarge = 0
1546 return indentlarge - indentsmall
1547
1548# "line.col" -> line, as an int
1549def index2line(index):
1550 return int(float(index))
1551
1552# Look at the leading whitespace in s.
1553# Return pair (# of leading ws characters,
1554# effective # of leading blanks after expanding
1555# tabs to width tabwidth)
1556
1557def classifyws(s, tabwidth):
1558 raw = effective = 0
1559 for ch in s:
1560 if ch == ' ':
1561 raw = raw + 1
1562 effective = effective + 1
1563 elif ch == '\t':
1564 raw = raw + 1
1565 effective = (effective // tabwidth + 1) * tabwidth
1566 else:
1567 break
1568 return raw, effective
1569
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001570
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001571class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001572
1573 # .run() chews over the Text widget, looking for a block opener
1574 # and the stmt following it. Returns a pair,
1575 # (line containing block opener, line containing stmt)
1576 # Either or both may be None.
1577
1578 def __init__(self, text, tabwidth):
1579 self.text = text
1580 self.tabwidth = tabwidth
1581 self.i = self.finished = 0
1582 self.blkopenline = self.indentedline = None
1583
1584 def readline(self):
1585 if self.finished:
1586 return ""
1587 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001588 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001589 if self.text.compare(mark, ">=", "end"):
1590 return ""
1591 return self.text.get(mark, mark + " lineend+1c")
1592
1593 def tokeneater(self, type, token, start, end, line,
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001594 INDENT=tokenize.INDENT,
1595 NAME=tokenize.NAME,
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001596 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1597 if self.finished:
1598 pass
1599 elif type == NAME and token in OPENERS:
1600 self.blkopenline = line
1601 elif type == INDENT and self.blkopenline:
1602 self.indentedline = line
1603 self.finished = 1
1604
1605 def run(self):
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001606 save_tabsize = tokenize.tabsize
1607 tokenize.tabsize = self.tabwidth
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001608 try:
1609 try:
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001610 tokens = tokenize.generate_tokens(self.readline)
Trent Nelson428de652008-03-18 22:41:35 +00001611 for token in tokens:
1612 self.tokeneater(*token)
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001613 except (tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001614 # since we cut off the tokenizer early, we can trigger
1615 # spurious errors
1616 pass
1617 finally:
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001618 tokenize.tabsize = save_tabsize
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001619 return self.blkopenline, self.indentedline
1620
1621### end autoindent code ###
1622
David Scherer7aced172000-08-15 01:13:23 +00001623def prepstr(s):
1624 # Helper to extract the underscore from a string, e.g.
1625 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001626 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001627 if i >= 0:
1628 s = s[:i] + s[i+1:]
1629 return i, s
1630
1631
1632keynames = {
1633 'bracketleft': '[',
1634 'bracketright': ']',
1635 'slash': '/',
1636}
1637
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001638def get_accelerator(keydefs, eventname):
1639 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001640 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1641 # if not keylist:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001642 if (not keylist) or (macosx.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001643 "<<open-module>>",
1644 "<<goto-line>>",
1645 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001646 return ""
1647 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001648 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001649 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1650 s = re.sub("Key-", "", s)
1651 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1652 s = re.sub("Control-", "Ctrl-", s)
1653 s = re.sub("-", "+", s)
1654 s = re.sub("><", " ", s)
1655 s = re.sub("<", "", s)
1656 s = re.sub(">", "", s)
1657 return s
1658
1659
1660def fixwordbreaks(root):
1661 # Make sure that Tk's double-click and next/previous word
1662 # operations use our definition of a word (i.e. an identifier)
1663 tk = root.tk
1664 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1665 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1666 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1667
1668
Terry Jan Reedycd567362014-10-17 01:31:35 -04001669def _editor_window(parent): # htest #
1670 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001671 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001672 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001673 if sys.argv[1:]:
1674 filename = sys.argv[1]
1675 else:
1676 filename = None
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001677 macosx.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001678 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001679 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001680 # Does not stop error, neither does following
1681 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001682
1683if __name__ == '__main__':
Terry Jan Reedy7c153412016-06-26 17:48:02 -04001684 import unittest
1685 unittest.main('idlelib.idle_test.test_editor', verbosity=2, exit=False)
1686
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001687 from idlelib.idle_test.htest import run
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04001688 run(_editor_window)