blob: b9cb50264ff06f22a36cf6cf405ba27aa1f4305d [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
Terry Jan Reedyc6fd6c82019-07-23 16:14:59 -04005import re
Guido van Rossum33d26892007-08-05 15:29:28 +00006import string
Terry Jan Reedyc6fd6c82019-07-23 16:14:59 -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 *
Tal Einatd4b4c002019-08-25 08:52:58 +030013from tkinter.font import Font
Terry Jan Reedy01e35752016-06-10 18:19:21 -040014from tkinter.ttk import Scrollbar
Terry Jan Reedy879986d2021-01-25 06:33:18 -050015from tkinter import simpledialog
16from tkinter import messagebox
Guido van Rossum36e0a922007-07-20 04:05:57 +000017
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040018from idlelib.config import idleConf
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040019from idlelib import configdialog
20from idlelib import grep
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040021from idlelib import help
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040022from idlelib import help_about
23from idlelib import macosx
24from idlelib.multicall import MultiCallCreator
25from idlelib import pyparse
26from idlelib import query
27from idlelib import replace
28from idlelib import search
GeeTransit2cd90252019-09-04 21:33:34 -040029from idlelib.tree import wheel_event
Terry Jan Reedya361e892018-06-20 21:25:59 -040030from idlelib import window
David Scherer7aced172000-08-15 01:13:23 +000031
32# The default tab setting for a Text widget, in average-width characters.
33TK_TABWIDTH_DEFAULT = 8
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -040034_py_version = ' (%s)' % platform.python_version()
Cheryl Sabellad49dbd92018-06-04 11:48:21 -040035darwin = sys.platform == 'darwin'
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -040036
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000037def _sphinx_version():
38 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
39 major, minor, micro, level, serial = sys.version_info
40 release = '%s%s' % (major, minor)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020041 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000042 if level == 'candidate':
43 release += 'rc%s' % (serial,)
44 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000045 release += '%s%s' % (level[0], serial)
46 return release
47
Terry Jan Reedye91e7632012-02-05 15:14:20 -050048
Terry Jan Reedy8dfe1562021-01-24 14:08:50 -050049class EditorWindow:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040050 from idlelib.percolator import Percolator
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -040051 from idlelib.colorizer import ColorDelegator, color_config
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040052 from idlelib.undo import UndoDelegator
Terry Jan Reedy7c153412016-06-26 17:48:02 -040053 from idlelib.iomenu import IOBinding, encoding
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040054 from idlelib import mainmenu
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040055 from idlelib.statusbar import MultiStatusBar
wohlganger58fc71c2017-09-10 16:19:47 -050056 from idlelib.autocomplete import AutoComplete
57 from idlelib.autoexpand import AutoExpand
Terry Jan Reedy06e20292018-06-19 23:00:35 -040058 from idlelib.calltip import Calltip
wohlganger58fc71c2017-09-10 16:19:47 -050059 from idlelib.codecontext import CodeContext
Tal Einat7123ea02019-07-23 15:22:11 +030060 from idlelib.sidebar import LineNumbers
Terry Jan Reedy1b389222019-07-17 20:48:36 -040061 from idlelib.format import FormatParagraph, FormatRegion, Indents, Rstrip
wohlganger58fc71c2017-09-10 16:19:47 -050062 from idlelib.parenmatch import ParenMatch
Tal Einat604e7b92018-09-25 15:10:14 +030063 from idlelib.squeezer import Squeezer
wohlganger58fc71c2017-09-10 16:19:47 -050064 from idlelib.zoomheight import ZoomHeight
David Scherer7aced172000-08-15 01:13:23 +000065
Terry Jan Reedy7c153412016-06-26 17:48:02 -040066 filesystemencoding = sys.getfilesystemencoding() # for file names
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000067 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000068
Tal Einat7123ea02019-07-23 15:22:11 +030069 allow_code_context = True
70 allow_line_numbers = True
Tal Einat7036e1d2019-07-17 11:15:53 +030071
David Scherer7aced172000-08-15 01:13:23 +000072 def __init__(self, flist=None, filename=None, key=None, root=None):
wohlganger58fc71c2017-09-10 16:19:47 -050073 # Delay import: runscript imports pyshell imports EditorWindow.
74 from idlelib.runscript import ScriptBinding
75
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000076 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +010077 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000078 if sys.platform.count('linux'):
79 # look for html docs in a couple of standard places
80 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
81 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
82 dochome = '/var/www/html/python/index.html'
83 else:
84 basepath = '/usr/share/doc/' # standard location
85 dochome = os.path.join(basepath, pyver,
86 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000087 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +010088 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000089 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +000090 if os.path.isfile(chmfile):
91 dochome = chmfile
Ned Deilyb7601672014-03-27 20:49:14 -070092 elif sys.platform == 'darwin':
93 # documentation may be stored inside a python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +010094 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +000095 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000096 dochome = os.path.normpath(dochome)
97 if os.path.isfile(dochome):
98 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +000099 if sys.platform == 'darwin':
100 # Safari requires real file:-URLs
101 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000102 else:
wohlganger58fc71c2017-09-10 16:19:47 -0500103 EditorWindow.help_url = ("https://docs.python.org/%d.%d/"
104 % sys.version_info[:2])
David Scherer7aced172000-08-15 01:13:23 +0000105 self.flist = flist
106 root = root or flist.root
107 self.root = root
David Scherer7aced172000-08-15 01:13:23 +0000108 self.menubar = Menu(root)
Terry Jan Reedya361e892018-06-20 21:25:59 -0400109 self.top = top = window.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000110 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000111 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200112 #self.top.instance_dict makes flist.inversedict available to
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400113 #configdialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000114 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000115 else:
116 self.tkinter_vars = {} # keys: Tkinter event names
117 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000118 self.top.instance_dict = {}
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400119 self.recent_files_path = idleConf.userdir and os.path.join(
terryjreedy223c7e72017-07-07 22:28:06 -0400120 idleConf.userdir, 'recent-files.lst')
Terry Jan Reedye86172d2017-10-27 20:26:12 -0400121
122 self.prompt_last_line = '' # Override in PyShell
David Scherer7aced172000-08-15 01:13:23 +0000123 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000124 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Tal Einatd4b4c002019-08-25 08:52:58 +0300125 width = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000126 text_options = {
127 'name': 'text',
128 'padx': 5,
129 'wrap': 'none',
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500130 'highlightthickness': 0,
Tal Einatd4b4c002019-08-25 08:52:58 +0300131 'width': width,
Terry Jan Reedy1080d132016-06-09 21:09:15 -0400132 'tabstyle': 'wordprocessor', # new in 8.5
133 'height': idleConf.GetOption(
134 'main', 'EditorWindow', 'height', type='int'),
135 }
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000136 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000137 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000138
139 self.createmenubar()
140 self.apply_bindings()
141
142 self.top.protocol("WM_DELETE_WINDOW", self.close)
143 self.top.bind("<<close-window>>", self.close_event)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400144 if macosx.isAquaTk():
Terry Jan Reedya361e892018-06-20 21:25:59 -0400145 # Command-W on editor windows doesn't work without this.
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000146 text.bind('<<close-window>>', self.close_event)
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400147 # Some OS X systems have only one mouse button, so use
148 # control-click for popup context menus there. For two
149 # buttons, AquaTk defines <2> as the right button, not <3>.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000150 text.bind("<Control-Button-1>",self.right_menu_event)
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400151 text.bind("<2>", self.right_menu_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000152 else:
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400153 # Elsewhere, use right-click for popup menus.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000154 text.bind("<3>",self.right_menu_event)
GeeTransit2cd90252019-09-04 21:33:34 -0400155
156 text.bind('<MouseWheel>', wheel_event)
157 text.bind('<Button-4>', wheel_event)
158 text.bind('<Button-5>', wheel_event)
Tal Einatd4b4c002019-08-25 08:52:58 +0300159 text.bind('<Configure>', self.handle_winconfig)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000160 text.bind("<<cut>>", self.cut)
161 text.bind("<<copy>>", self.copy)
162 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000163 text.bind("<<center-insert>>", self.center_insert_event)
164 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000165 text.bind("<<python-docs>>", self.python_docs)
166 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000167 text.bind("<<open-config-dialog>>", self.config_dialog)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300168 text.bind("<<open-module>>", self.open_module_event)
David Scherer7aced172000-08-15 01:13:23 +0000169 text.bind("<<do-nothing>>", lambda event: "break")
170 text.bind("<<select-all>>", self.select_all)
171 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000172 text.bind("<<find>>", self.find_event)
173 text.bind("<<find-again>>", self.find_again_event)
174 text.bind("<<find-in-files>>", self.find_in_files_event)
175 text.bind("<<find-selection>>", self.find_selection_event)
176 text.bind("<<replace>>", self.replace_event)
177 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000178 text.bind("<<smart-backspace>>",self.smart_backspace_event)
179 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
180 text.bind("<<smart-indent>>",self.smart_indent_event)
Cheryl Sabella82494aa2019-07-17 09:44:44 -0400181 self.fregion = fregion = self.FormatRegion(self)
Terry Jan Reedy1b389222019-07-17 20:48:36 -0400182 # self.fregion used in smart_indent_event to access indent_region.
Cheryl Sabella82494aa2019-07-17 09:44:44 -0400183 text.bind("<<indent-region>>", fregion.indent_region_event)
184 text.bind("<<dedent-region>>", fregion.dedent_region_event)
185 text.bind("<<comment-region>>", fregion.comment_region_event)
186 text.bind("<<uncomment-region>>", fregion.uncomment_region_event)
187 text.bind("<<tabify-region>>", fregion.tabify_region_event)
188 text.bind("<<untabify-region>>", fregion.untabify_region_event)
Terry Jan Reedyb8462472019-11-20 01:18:39 -0500189 indents = self.Indents(self)
190 text.bind("<<toggle-tabs>>", indents.toggle_tabs_event)
191 text.bind("<<change-indentwidth>>", indents.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000192 text.bind("<Left>", self.move_at_edge_if_selection(0))
193 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000194 text.bind("<<del-word-left>>", self.del_word_left)
195 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000196 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000197
David Scherer7aced172000-08-15 01:13:23 +0000198 if flist:
199 flist.inversedict[self] = key
200 if key:
201 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000202 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000203 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400204 text.bind("<<open-class-browser>>", self.open_module_browser)
David Scherer7aced172000-08-15 01:13:23 +0000205 text.bind("<<open-path-browser>>", self.open_path_browser)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400206 text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
David Scherer7aced172000-08-15 01:13:23 +0000207
Steven M. Gava898a3652001-10-07 11:10:44 +0000208 self.set_status_bar()
Tal Einat7123ea02019-07-23 15:22:11 +0300209 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
210 text_frame.rowconfigure(1, weight=1)
211 text_frame.columnconfigure(1, weight=1)
Cheryl Sabellad49dbd92018-06-04 11:48:21 -0400212 vbar['command'] = self.handle_yview
Tal Einat7123ea02019-07-23 15:22:11 +0300213 vbar.grid(row=1, column=2, sticky=NSEW)
David Scherer7aced172000-08-15 01:13:23 +0000214 text['yscrollcommand'] = vbar.set
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400215 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
Tal Einat7123ea02019-07-23 15:22:11 +0300216 text.grid(row=1, column=1, sticky=NSEW)
David Scherer7aced172000-08-15 01:13:23 +0000217 text.focus_set()
Tal Einatd4b4c002019-08-25 08:52:58 +0300218 self.set_width()
David Scherer7aced172000-08-15 01:13:23 +0000219
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000220 # usetabs true -> literal tab characters are used by indent and
221 # dedent cmds, possibly mixed with spaces if
222 # indentwidth is not a multiple of tabwidth,
223 # which will cause Tabnanny to nag!
224 # false -> tab characters are converted to spaces by indent
225 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000226 # Although use-spaces=0 can be configured manually in config-main.def,
227 # configuration of tabs v. spaces is not supported in the configuration
228 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200229 usespaces = idleConf.GetOption('main', 'Indent',
230 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000231 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000232
233 # tabwidth is the display width of a literal tab character.
234 # CAUTION: telling Tk to use anything other than its default
235 # tab setting causes it to use an entirely different tabbing algorithm,
236 # treating tab stops as fixed distances from the left margin.
237 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000238 self.tabwidth = 8 # must remain 8 until Tk is fixed.
239
240 # indentwidth is the number of screen characters per indent level.
241 # The recommended Python indentation is four spaces.
242 self.indentwidth = self.tabwidth
243 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000244
Zackery Spytz9c284492019-11-13 00:13:33 -0700245 # Store the current value of the insertofftime now so we can restore
246 # it if needed.
247 if not hasattr(idleConf, 'blink_off_time'):
248 idleConf.blink_off_time = self.text['insertofftime']
249 self.update_cursor_blink()
250
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000251 # When searching backwards for a reliable place to begin parsing,
252 # first start num_context_lines[0] lines back, then
253 # num_context_lines[1] lines back if that didn't work, and so on.
254 # The last value should be huge (larger than the # of lines in a
255 # conceivable file).
256 # Making the initial values larger slows things down more often.
257 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000258 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000259 self.undo = undo = self.UndoDelegator()
260 per.insertfilter(undo)
261 text.undo_block_start = undo.undo_block_start
262 text.undo_block_stop = undo.undo_block_stop
263 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000264 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000265 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000266 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000267 self.good_load = False
268 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000269 self.color = None # initialized below in self.ResetColorizer
Tal Einat7123ea02019-07-23 15:22:11 +0300270 self.code_context = None # optionally initialized later below
271 self.line_numbers = None # optionally initialized later below
David Scherer7aced172000-08-15 01:13:23 +0000272 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000273 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000274 if io.loadfile(filename):
275 self.good_load = True
276 is_py_src = self.ispythonsource(filename)
277 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000278 else:
279 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500280 self.good_load = True
281
Christian Heimesa156e092008-02-16 07:38:31 +0000282 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000283 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000284 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000285 self.load_extensions()
Terry Jan Reedy33c74202018-06-20 22:49:55 -0400286 menu = self.menudict.get('window')
David Scherer7aced172000-08-15 01:13:23 +0000287 if menu:
288 end = menu.index("end")
289 if end is None:
290 end = -1
291 if end >= 0:
292 menu.add_separator()
293 end = end + 1
294 self.wmenu_end = end
Terry Jan Reedya361e892018-06-20 21:25:59 -0400295 window.register_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000296
297 # Some abstractions so IDLE extensions are cross-IDE
Terry Jan Reedy879986d2021-01-25 06:33:18 -0500298 self.askinteger = simpledialog.askinteger
299 self.askyesno = messagebox.askyesno
300 self.showerror = messagebox.showerror
David Scherer7aced172000-08-15 01:13:23 +0000301
wohlganger58fc71c2017-09-10 16:19:47 -0500302 # Add pseudoevents for former extension fixed keys.
303 # (This probably needs to be done once in the process.)
304 text.event_add('<<autocomplete>>', '<Key-Tab>')
305 text.event_add('<<try-open-completions>>', '<KeyRelease-period>',
306 '<KeyRelease-slash>', '<KeyRelease-backslash>')
307 text.event_add('<<try-open-calltip>>', '<KeyRelease-parenleft>')
308 text.event_add('<<refresh-calltip>>', '<KeyRelease-parenright>')
309 text.event_add('<<paren-closed>>', '<KeyRelease-parenright>',
310 '<KeyRelease-bracketright>', '<KeyRelease-braceright>')
311
312 # Former extension bindings depends on frame.text being packed
313 # (called from self.ResetColorizer()).
314 autocomplete = self.AutoComplete(self)
315 text.bind("<<autocomplete>>", autocomplete.autocomplete_event)
316 text.bind("<<try-open-completions>>",
317 autocomplete.try_open_completions_event)
318 text.bind("<<force-open-completions>>",
319 autocomplete.force_open_completions_event)
320 text.bind("<<expand-word>>", self.AutoExpand(self).expand_word_event)
321 text.bind("<<format-paragraph>>",
322 self.FormatParagraph(self).format_paragraph_event)
323 parenmatch = self.ParenMatch(self)
324 text.bind("<<flash-paren>>", parenmatch.flash_paren_event)
325 text.bind("<<paren-closed>>", parenmatch.paren_closed_event)
326 scriptbinding = ScriptBinding(self)
327 text.bind("<<check-module>>", scriptbinding.check_module_event)
328 text.bind("<<run-module>>", scriptbinding.run_module_event)
Cheryl Sabella201bc2d2019-06-17 22:24:10 -0400329 text.bind("<<run-custom>>", scriptbinding.run_custom_event)
Srinivas Reddy Thatiparthy (శ్రీనివాస్ రెడ్డి తాటిపర్తి)9bb92232018-06-20 13:12:13 +0530330 text.bind("<<do-rstrip>>", self.Rstrip(self).do_rstrip)
Zackery Spytzbfdeaa32020-01-30 18:55:42 -0700331 self.ctip = ctip = self.Calltip(self)
Terry Jan Reedy06e20292018-06-19 23:00:35 -0400332 text.bind("<<try-open-calltip>>", ctip.try_open_calltip_event)
333 #refresh-calltip must come after paren-closed to work right
334 text.bind("<<refresh-calltip>>", ctip.refresh_calltip_event)
335 text.bind("<<force-open-calltip>>", ctip.force_open_calltip_event)
wohlganger58fc71c2017-09-10 16:19:47 -0500336 text.bind("<<zoom-height>>", self.ZoomHeight(self).zoom_height_event)
Tal Einat7123ea02019-07-23 15:22:11 +0300337 if self.allow_code_context:
338 self.code_context = self.CodeContext(self)
Tal Einat7036e1d2019-07-17 11:15:53 +0300339 text.bind("<<toggle-code-context>>",
Tal Einat7123ea02019-07-23 15:22:11 +0300340 self.code_context.toggle_code_context_event)
341 else:
Zackery Spytz23a567c2021-01-28 16:13:22 -0700342 self.update_menu_state('options', '*ode*ontext', 'disabled')
Tal Einat7123ea02019-07-23 15:22:11 +0300343 if self.allow_line_numbers:
344 self.line_numbers = self.LineNumbers(self)
345 if idleConf.GetOption('main', 'EditorWindow',
346 'line-numbers-default', type='bool'):
347 self.toggle_line_numbers_event()
348 text.bind("<<toggle-line-numbers>>", self.toggle_line_numbers_event)
349 else:
Zackery Spytz23a567c2021-01-28 16:13:22 -0700350 self.update_menu_state('options', '*ine*umbers', 'disabled')
wohlganger58fc71c2017-09-10 16:19:47 -0500351
Tal Einatd4b4c002019-08-25 08:52:58 +0300352 def handle_winconfig(self, event=None):
353 self.set_width()
354
355 def set_width(self):
356 text = self.text
357 inner_padding = sum(map(text.tk.getint, [text.cget('border'),
358 text.cget('padx')]))
359 pixel_width = text.winfo_width() - 2 * inner_padding
360
361 # Divide the width of the Text widget by the font width,
362 # which is taken to be the width of '0' (zero).
363 # http://www.tcl.tk/man/tcl8.6/TkCmd/text.htm#M21
364 zero_char_width = \
365 Font(text, font=text.cget('font')).measure('0')
366 self.width = pixel_width // zero_char_width
367
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000368 def new_callback(self, event):
369 dirname, basename = self.io.defaultfilename()
370 self.flist.new(dirname)
371 return "break"
372
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000373 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400374 if (event.state & 4) != 0 and event.keysym == "Home":
375 # state&4==Control. If <Control-Home>, use the Tk binding.
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300376 return None
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000377 if self.text.index("iomark") and \
378 self.text.compare("iomark", "<=", "insert lineend") and \
379 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400380 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000381 insertpt = int(self.text.index("iomark").split(".")[1])
382 else:
383 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000384 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000385 if line[insertpt] not in (' ','\t'):
386 break
387 else:
388 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000389 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000390 if insertpt == lineat:
391 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000392 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000393 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400394 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000395 self.text.tag_remove("sel", "1.0", "end")
396 else:
397 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200398 # there was no previous selection
399 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400400 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200401 if self.text.compare(self.text.index("sel.first"), "<",
402 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400403 self.text.mark_set("my_anchor", "sel.first") # extend back
404 else:
405 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000406 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400407 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000408 if self.text.compare(first,">",last):
409 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000410 self.text.tag_remove("sel", "1.0", "end")
411 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000412 self.text.mark_set("insert", dest)
413 self.text.see("insert")
414 return "break"
415
David Scherer7aced172000-08-15 01:13:23 +0000416 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000417 self.status_bar = self.MultiStatusBar(self.top)
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500418 sep = Frame(self.top, height=1, borderwidth=1, background='grey75')
Ned Deilyb7601672014-03-27 20:49:14 -0700419 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000420 # Insert some padding to avoid obscuring some of the statusbar
421 # by the resize widget.
422 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000423 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
424 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
425 self.status_bar.pack(side=BOTTOM, fill=X)
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500426 sep.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000427 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
428 self.text.event_add("<<set-line-and-column>>",
429 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000430 self.text.after_idle(self.set_line_and_column)
431
432 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000433 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000434 self.status_bar.set_label('column', 'Col: %s' % column)
435 self.status_bar.set_label('line', 'Ln: %s' % line)
436
David Scherer7aced172000-08-15 01:13:23 +0000437 menu_specs = [
438 ("file", "_File"),
439 ("edit", "_Edit"),
440 ("format", "F_ormat"),
441 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000442 ("options", "_Options"),
Terry Jan Reedy33c74202018-06-20 22:49:55 -0400443 ("window", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000444 ("help", "_Help"),
445 ]
446
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000447
David Scherer7aced172000-08-15 01:13:23 +0000448 def createmenubar(self):
449 mbar = self.menubar
450 self.menudict = menudict = {}
451 for name, label in self.menu_specs:
452 underline, label = prepstr(label)
Zackery Spytz23a567c2021-01-28 16:13:22 -0700453 postcommand = getattr(self, f'{name}_menu_postcommand', None)
454 menudict[name] = menu = Menu(mbar, name=name, tearoff=0,
455 postcommand=postcommand)
David Scherer7aced172000-08-15 01:13:23 +0000456 mbar.add_cascade(label=label, menu=menu, underline=underline)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400457 if macosx.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000458 # Insert the application menu
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400459 menudict['application'] = menu = Menu(mbar, name='apple',
460 tearoff=0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000461 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000462 self.fill_menus()
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400463 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000464 self.menudict['file'].insert_cascade(3, label='Recent Files',
465 underline=0,
466 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000467 self.base_helpmenu_length = self.menudict['help'].index(END)
468 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000469
470 def postwindowsmenu(self):
Terry Jan Reedya361e892018-06-20 21:25:59 -0400471 # Only called when Window menu exists
Terry Jan Reedy33c74202018-06-20 22:49:55 -0400472 menu = self.menudict['window']
David Scherer7aced172000-08-15 01:13:23 +0000473 end = menu.index("end")
474 if end is None:
475 end = -1
476 if end > self.wmenu_end:
477 menu.delete(self.wmenu_end+1, end)
Terry Jan Reedya361e892018-06-20 21:25:59 -0400478 window.add_windows_to_menu(menu)
David Scherer7aced172000-08-15 01:13:23 +0000479
Cheryl Sabellac1b4b0f2018-12-22 01:25:45 -0500480 def update_menu_label(self, menu, index, label):
Cheryl Sabella804a5d92018-12-28 13:15:30 -0500481 "Update label for menu item at index."
Cheryl Sabellac1b4b0f2018-12-22 01:25:45 -0500482 menuitem = self.menudict[menu]
483 menuitem.entryconfig(index, label=label)
484
Cheryl Sabella804a5d92018-12-28 13:15:30 -0500485 def update_menu_state(self, menu, index, state):
486 "Update state for menu item at index."
487 menuitem = self.menudict[menu]
488 menuitem.entryconfig(index, state=state)
489
Cheryl Sabellad49dbd92018-06-04 11:48:21 -0400490 def handle_yview(self, event, *args):
491 "Handle scrollbar."
492 if event == 'moveto':
493 fraction = float(args[0])
494 lines = (round(self.getlineno('end') * fraction) -
495 self.getlineno('@0,0'))
496 event = 'scroll'
497 args = (lines, 'units')
498 self.text.yview(event, *args)
499 return 'break'
500
David Scherer7aced172000-08-15 01:13:23 +0000501 rmenu = None
502
503 def right_menu_event(self, event):
Terry Jan Reedy97e4e0f2020-05-29 18:54:14 -0400504 text = self.text
505 newdex = text.index(f'@{event.x},{event.y}')
506 try:
507 in_selection = (text.compare('sel.first', '<=', newdex) and
508 text.compare(newdex, '<=', 'sel.last'))
509 except TclError:
510 in_selection = False
511 if not in_selection:
512 text.tag_remove("sel", "1.0", "end")
513 text.mark_set("insert", newdex)
David Scherer7aced172000-08-15 01:13:23 +0000514 if not self.rmenu:
515 self.make_rmenu()
516 rmenu = self.rmenu
517 self.event = event
518 iswin = sys.platform[:3] == 'win'
519 if iswin:
Terry Jan Reedy97e4e0f2020-05-29 18:54:14 -0400520 text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200521
Roger Serwy6b2918a2013-04-07 12:15:52 -0500522 for item in self.rmenu_specs:
523 try:
524 label, eventname, verify_state = item
525 except ValueError: # see issue1207589
526 continue
527
Andrew Svetlovd1837672012-11-01 22:41:19 +0200528 if verify_state is None:
529 continue
530 state = getattr(self, verify_state)()
531 rmenu.entryconfigure(label, state=state)
532
David Scherer7aced172000-08-15 01:13:23 +0000533 rmenu.tk_popup(event.x_root, event.y_root)
534 if iswin:
535 self.text.config(cursor="ibeam")
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300536 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000537
538 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200539 # ("Label", "<<virtual-event>>", "statefuncname"), ...
540 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000541 ]
542
543 def make_rmenu(self):
544 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500545 for item in self.rmenu_specs:
546 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200547 if label is not None:
548 def command(text=self.text, eventname=eventname):
549 text.event_generate(eventname)
550 rmenu.add_command(label=label, command=command)
551 else:
552 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000553 self.rmenu = rmenu
554
Andrew Svetlovd1837672012-11-01 22:41:19 +0200555 def rmenu_check_cut(self):
556 return self.rmenu_check_copy()
557
558 def rmenu_check_copy(self):
559 try:
560 indx = self.text.index('sel.first')
561 except TclError:
562 return 'disabled'
563 else:
564 return 'normal' if indx else 'disabled'
565
566 def rmenu_check_paste(self):
567 try:
568 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
569 except TclError:
570 return 'disabled'
571 else:
572 return 'normal'
573
David Scherer7aced172000-08-15 01:13:23 +0000574 def about_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400575 "Handle Help 'About IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400576 # Synchronize with macosx.overrideRootMenu.about_dialog.
csabella18ede062017-06-23 20:00:58 -0400577 help_about.AboutDialog(self.top)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300578 return "break"
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000579
Steven M. Gava3b55a892001-11-21 05:56:26 +0000580 def config_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400581 "Handle Options 'Configure IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400582 # Synchronize with macosx.overrideRootMenu.config_dialog.
583 configdialog.ConfigDialog(self.top,'Settings')
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300584 return "break"
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400585
David Scherer7aced172000-08-15 01:13:23 +0000586 def help_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400587 "Handle Help 'IDLE Help' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400588 # Synchronize with macosx.overrideRootMenu.help_dialog.
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500589 if self.root:
590 parent = self.root
591 else:
592 parent = self.top
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400593 help.show_idlehelp(parent)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300594 return "break"
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000595
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000596 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000597 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000598 try:
599 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200600 except OSError as why:
Terry Jan Reedy879986d2021-01-25 06:33:18 -0500601 messagebox.showerror(title='Document Start Failure',
Terry Reedy6739cc02011-01-01 02:25:36 +0000602 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000603 else:
604 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000605 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000606
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000607 def cut(self,event):
608 self.text.event_generate("<<Cut>>")
609 return "break"
610
611 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000612 if not self.text.tag_ranges("sel"):
613 # There is no selection, so do nothing and maybe interrupt.
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300614 return None
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000615 self.text.event_generate("<<Copy>>")
616 return "break"
617
618 def paste(self,event):
619 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000620 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000621 return "break"
622
David Scherer7aced172000-08-15 01:13:23 +0000623 def select_all(self, event=None):
624 self.text.tag_add("sel", "1.0", "end-1c")
625 self.text.mark_set("insert", "1.0")
626 self.text.see("insert")
627 return "break"
628
629 def remove_selection(self, event=None):
630 self.text.tag_remove("sel", "1.0", "end")
631 self.text.see("insert")
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300632 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000633
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000634 def move_at_edge_if_selection(self, edge_index):
635 """Cursor move begins at start or end of selection
636
637 When a left/right cursor key is pressed create and return to Tkinter a
638 function which causes a cursor move from the associated edge of the
639 selection.
640
641 """
642 self_text_index = self.text.index
643 self_text_mark_set = self.text.mark_set
644 edges_table = ("sel.first+1c", "sel.last-1c")
645 def move_at_edge(event):
646 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
647 try:
648 self_text_index("sel.first")
649 self_text_mark_set("insert", edges_table[edge_index])
650 except TclError:
651 pass
652 return move_at_edge
653
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000654 def del_word_left(self, event):
655 self.text.event_generate('<Meta-Delete>')
656 return "break"
657
658 def del_word_right(self, event):
659 self.text.event_generate('<Meta-d>')
660 return "break"
661
Steven M. Gavac5976402002-01-04 03:06:08 +0000662 def find_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400663 search.find(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000664 return "break"
665
666 def find_again_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400667 search.find_again(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000668 return "break"
669
670 def find_selection_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400671 search.find_selection(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000672 return "break"
673
674 def find_in_files_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400675 grep.grep(self.text, self.io, self.flist)
Steven M. Gavac5976402002-01-04 03:06:08 +0000676 return "break"
677
678 def replace_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400679 replace.replace(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000680 return "break"
681
682 def goto_line_event(self, event):
683 text = self.text
Terry Jan Reedy363fab82020-03-09 16:51:20 -0400684 lineno = query.Goto(
685 text, "Go To Line",
686 "Enter a positive integer\n"
687 "('big' = end of file):"
688 ).result
689 if lineno is not None:
690 text.tag_remove("sel", "1.0", "end")
691 text.mark_set("insert", f'{lineno}.0')
692 text.see("insert")
693 self.set_line_and_column()
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300694 return "break"
Steven M. Gavac5976402002-01-04 03:06:08 +0000695
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300696 def open_module(self):
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400697 """Get module name from user and open it.
698
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400699 Return module path or None for calls by open_module_browser
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400700 when latter is not invoked in named editor window.
701 """
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400702 # XXX This, open_module_browser, and open_path_browser
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400703 # would fit better in iomenu.IOBinding.
David Scherer7aced172000-08-15 01:13:23 +0000704 try:
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400705 name = self.text.get("sel.first", "sel.last").strip()
David Scherer7aced172000-08-15 01:13:23 +0000706 except TclError:
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400707 name = ''
708 file_path = query.ModuleName(
709 self.text, "Open Module",
710 "Enter the name of a Python module\n"
711 "to search on sys.path and open:",
712 name).result
713 if file_path is not None:
714 if self.flist:
715 self.flist.open(file_path)
716 else:
717 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400718 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000719
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300720 def open_module_event(self, event):
721 self.open_module()
722 return "break"
723
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400724 def open_module_browser(self, event=None):
David Scherer7aced172000-08-15 01:13:23 +0000725 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400726 if not (self.__class__.__name__ == 'PyShellEditorWindow'
727 and filename):
728 filename = self.open_module()
729 if filename is None:
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300730 return "break"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400731 from idlelib import browser
Terry Jan Reedyd6bb65f2017-09-30 19:54:28 -0400732 browser.ModuleBrowser(self.root, filename)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300733 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000734
735 def open_path_browser(self, event=None):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400736 from idlelib import pathbrowser
Cheryl Sabella20d48a42017-11-22 19:05:25 -0500737 pathbrowser.PathBrowser(self.root)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300738 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000739
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400740 def open_turtle_demo(self, event = None):
741 import subprocess
742
743 cmd = [sys.executable,
744 '-c',
745 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400746 subprocess.Popen(cmd, shell=False)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300747 return "break"
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400748
David Scherer7aced172000-08-15 01:13:23 +0000749 def gotoline(self, lineno):
750 if lineno is not None and lineno > 0:
751 self.text.mark_set("insert", "%d.0" % lineno)
752 self.text.tag_remove("sel", "1.0", "end")
753 self.text.tag_add("sel", "insert", "insert +1l")
754 self.center()
755
756 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000757 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000758 return True
David Scherer7aced172000-08-15 01:13:23 +0000759 base, ext = os.path.splitext(os.path.basename(filename))
760 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000761 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000762 line = self.text.get('1.0', '1.0 lineend')
763 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000764
765 def close_hook(self):
766 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000767 self.flist.unregister_maybe_terminate(self)
768 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000769
770 def set_close_hook(self, close_hook):
771 self.close_hook = close_hook
772
773 def filename_change_hook(self):
774 if self.flist:
775 self.flist.filename_changed_edit(self)
776 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000777 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000778 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000779
Christian Heimesa156e092008-02-16 07:38:31 +0000780 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000781 if self.color:
782 return
Christian Heimesa156e092008-02-16 07:38:31 +0000783 if self.ispythonsource(self.io.filename):
784 self.color = self.ColorDelegator()
785 # can add more colorizers here...
786 if self.color:
787 self.per.removefilter(self.undo)
788 self.per.insertfilter(self.color)
789 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000790
Christian Heimesa156e092008-02-16 07:38:31 +0000791 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000792 if not self.color:
793 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000794 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000795 self.per.removefilter(self.color)
796 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000797
Steven M. Gavab77d3432002-03-02 07:16:21 +0000798 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400799 "Update the color theme"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400800 # Called from self.filename_change_hook and from configdialog.py
Christian Heimesa156e092008-02-16 07:38:31 +0000801 self._rmcolorizer()
802 self._addcolorizer()
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -0400803 EditorWindow.color_config(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000804
Tal Einat7123ea02019-07-23 15:22:11 +0300805 if self.code_context is not None:
806 self.code_context.update_highlight_colors()
807
808 if self.line_numbers is not None:
809 self.line_numbers.update_colors()
Tal Einat7036e1d2019-07-17 11:15:53 +0300810
Guido van Rossum33d26892007-08-05 15:29:28 +0000811 IDENTCHARS = string.ascii_letters + string.digits + "_"
812
813 def colorize_syntax_error(self, text, pos):
814 text.tag_add("ERROR", pos)
815 char = text.get(pos)
816 if char and char in self.IDENTCHARS:
817 text.tag_add("ERROR", pos + " wordstart", pos)
818 if '\n' == text.get(pos): # error at line end
819 text.mark_set("insert", pos)
820 else:
821 text.mark_set("insert", pos + "+1c")
822 text.see(pos)
823
Zackery Spytz9c284492019-11-13 00:13:33 -0700824 def update_cursor_blink(self):
825 "Update the cursor blink configuration."
826 cursorblink = idleConf.GetOption(
827 'main', 'EditorWindow', 'cursor-blink', type='bool')
828 if not cursorblink:
829 self.text['insertofftime'] = 0
830 else:
831 # Restore the original value
832 self.text['insertofftime'] = idleConf.blink_off_time
833
Steven M. Gavab1585412002-03-12 00:21:56 +0000834 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000835 "Update the text widgets' font if it is changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400836 # Called from configdialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400837
Tal Einat7036e1d2019-07-17 11:15:53 +0300838 # Update the code context widget first, since its height affects
839 # the height of the text widget. This avoids double re-rendering.
Tal Einat7123ea02019-07-23 15:22:11 +0300840 if self.code_context is not None:
841 self.code_context.update_font()
842 # Next, update the line numbers widget, since its width affects
843 # the width of the text widget.
844 if self.line_numbers is not None:
845 self.line_numbers.update_font()
846 # Finally, update the main text widget.
847 new_font = idleConf.GetFont(self.root, 'main', 'EditorWindow')
Tal Einat7036e1d2019-07-17 11:15:53 +0300848 self.text['font'] = new_font
Tal Einatd4b4c002019-08-25 08:52:58 +0300849 self.set_width()
Steven M. Gavab1585412002-03-12 00:21:56 +0000850
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000851 def RemoveKeybindings(self):
852 "Remove the keybindings before they are changed."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400853 # Called from configdialog.py
854 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000855 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000856 self.text.event_delete(event, *keylist)
857 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000858 xkeydefs = idleConf.GetExtensionBindings(extensionName)
859 if xkeydefs:
860 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000861 self.text.event_delete(event, *keylist)
862
863 def ApplyKeybindings(self):
864 "Update the keybindings after they are changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400865 # Called from configdialog.py
866 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000867 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000868 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000869 xkeydefs = idleConf.GetExtensionBindings(extensionName)
870 if xkeydefs:
871 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000872 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000873 menuEventDict = {}
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400874 for menu in self.mainmenu.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000875 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000876 for item in menu[1]:
877 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000878 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000879 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000880 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700881 end = menu.index(END)
882 if end is None:
883 # Skip empty menus
884 continue
885 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000886 for index in range(0, end):
887 if menu.type(index) == 'command':
888 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000889 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000890 itemName = menu.entrycget(index, 'label')
891 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000892 if menubarItem in menuEventDict:
893 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000894 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000895 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000896 accel = get_accelerator(keydefs, event)
897 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000898
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000899 def set_notabs_indentwidth(self):
900 "Update the indentwidth if changed and not using tabs in this window"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400901 # Called from configdialog.py
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000902 if not self.usetabs:
903 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
904 type='int')
905
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000906 def reset_help_menu_entries(self):
907 "Update the additional help entries on the Help menu"
908 help_list = idleConf.GetAllExtraHelpSourcesList()
909 helpmenu = self.menudict['help']
910 # first delete the extra help entries, if any
911 helpmenu_length = helpmenu.index(END)
912 if helpmenu_length > self.base_helpmenu_length:
913 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
914 # then rebuild them
915 if help_list:
916 helpmenu.add_separator()
917 for entry in help_list:
918 cmd = self.__extra_help_callback(entry[1])
919 helpmenu.add_command(label=entry[0], command=cmd)
920 # and update the menu dictionary
921 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000922
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000923 def __extra_help_callback(self, helpfile):
924 "Create a callback with the helpfile value frozen at definition time"
925 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000926 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000927 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000928 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000929 try:
930 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200931 except OSError as why:
Terry Jan Reedy879986d2021-01-25 06:33:18 -0500932 messagebox.showerror(title='Document Start Failure',
Terry Reedy6739cc02011-01-01 02:25:36 +0000933 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000934 else:
935 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000936 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000937
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000938 def update_recent_files_list(self, new_file=None):
939 "Load and update the recent files list and menus"
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400940 # TODO: move to iomenu.
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000941 rf_list = []
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400942 file_path = self.recent_files_path
943 if file_path and os.path.exists(file_path):
944 with open(file_path, 'r',
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400945 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000946 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000947 if new_file:
948 new_file = os.path.abspath(new_file) + '\n'
949 if new_file in rf_list:
950 rf_list.remove(new_file) # move to top
951 rf_list.insert(0, new_file)
952 # clean and save the recent files list
953 bad_paths = []
954 for path in rf_list:
955 if '\0' in path or not os.path.exists(path[0:-1]):
956 bad_paths.append(path)
957 rf_list = [path for path in rf_list if path not in bad_paths]
958 ulchars = "1234567890ABCDEFGHIJK"
959 rf_list = rf_list[0:len(ulchars)]
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400960 if file_path:
961 try:
962 with open(file_path, 'w',
963 encoding='utf_8', errors='replace') as rf_file:
964 rf_file.writelines(rf_list)
965 except OSError as err:
966 if not getattr(self.root, "recentfiles_message", False):
967 self.root.recentfiles_message = True
Terry Jan Reedy879986d2021-01-25 06:33:18 -0500968 messagebox.showwarning(title='IDLE Warning',
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400969 message="Cannot save Recent Files list to disk.\n"
970 f" {err}\n"
971 "Select OK to continue.",
972 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000973 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000974 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000975 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700976 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000977 for i, file_name in enumerate(rf_list):
978 file_name = file_name.rstrip() # zap \n
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000979 callback = instance.__recent_file_callback(file_name)
Serhiy Storchaka06cb94b2019-10-04 13:09:52 +0300980 menu.add_command(label=ulchars[i] + " " + file_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000981 command=callback,
982 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000983
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000984 def __recent_file_callback(self, file_name):
985 def open_recent_file(fn_closure=file_name):
986 self.io.open(editFile=fn_closure)
987 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000988
David Scherer7aced172000-08-15 01:13:23 +0000989 def saved_change_hook(self):
990 short = self.short_title()
991 long = self.long_title()
992 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400993 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000994 elif short:
995 title = short
996 elif long:
997 title = long
998 else:
Terry Jan Reedya9022392019-01-18 02:09:53 -0500999 title = "untitled"
David Scherer7aced172000-08-15 01:13:23 +00001000 icon = short or long or title
1001 if not self.get_saved():
1002 title = "*%s*" % title
1003 icon = "*%s" % icon
1004 self.top.wm_title(title)
1005 self.top.wm_iconname(icon)
1006
1007 def get_saved(self):
1008 return self.undo.get_saved()
1009
1010 def set_saved(self, flag):
1011 self.undo.set_saved(flag)
1012
1013 def reset_undo(self):
1014 self.undo.reset_undo()
1015
1016 def short_title(self):
1017 filename = self.io.filename
Serhiy Storchaka06cb94b2019-10-04 13:09:52 +03001018 return os.path.basename(filename) if filename else "untitled"
David Scherer7aced172000-08-15 01:13:23 +00001019
1020 def long_title(self):
Serhiy Storchaka06cb94b2019-10-04 13:09:52 +03001021 return self.io.filename or ""
David Scherer7aced172000-08-15 01:13:23 +00001022
1023 def center_insert_event(self, event):
1024 self.center()
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001025 return "break"
David Scherer7aced172000-08-15 01:13:23 +00001026
1027 def center(self, mark="insert"):
1028 text = self.text
1029 top, bot = self.getwindowlines()
1030 lineno = self.getlineno(mark)
1031 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001032 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +00001033 text.yview(float(newtop))
1034
1035 def getwindowlines(self):
1036 text = self.text
1037 top = self.getlineno("@0,0")
1038 bot = self.getlineno("@0,65535")
1039 if top == bot and text.winfo_height() == 1:
1040 # Geometry manager hasn't run yet
1041 height = int(text['height'])
1042 bot = top + height - 1
1043 return top, bot
1044
1045 def getlineno(self, mark="insert"):
1046 text = self.text
1047 return int(float(text.index(mark)))
1048
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001049 def get_geometry(self):
1050 "Return (width, height, x, y)"
1051 geom = self.top.wm_geometry()
1052 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +00001053 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001054
David Scherer7aced172000-08-15 01:13:23 +00001055 def close_event(self, event):
1056 self.close()
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001057 return "break"
David Scherer7aced172000-08-15 01:13:23 +00001058
1059 def maybesave(self):
1060 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001061 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001062 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001063 self.top.deiconify()
1064 self.top.lower()
1065 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001066 return self.io.maybesave()
1067
1068 def close(self):
Terry Jan Reedydfd34a92019-09-17 02:05:04 -04001069 try:
1070 reply = self.maybesave()
1071 if str(reply) != "cancel":
1072 self._close()
1073 return reply
1074 except AttributeError: # bpo-35379: close called twice
1075 pass
David Scherer7aced172000-08-15 01:13:23 +00001076
1077 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001078 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001079 self.update_recent_files_list(new_file=self.io.filename)
Terry Jan Reedya361e892018-06-20 21:25:59 -04001080 window.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001081 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001082 self.io.close()
1083 self.io = None
1084 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001085 if self.color:
Cheryl Sabellab9f03542019-03-01 05:19:40 -05001086 self.color.close()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001087 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001088 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001089 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001090 self.per.close()
1091 self.per = None
1092 self.top.destroy()
1093 if self.close_hook:
1094 # unless override: unregister from flist, terminate if last window
1095 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001096
1097 def load_extensions(self):
1098 self.extensions = {}
1099 self.load_standard_extensions()
1100
1101 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001102 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001103 if hasattr(ins, "close"):
1104 ins.close()
1105 self.extensions = {}
1106
1107 def load_standard_extensions(self):
1108 for name in self.get_standard_extension_names():
1109 try:
1110 self.load_extension(name)
1111 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001112 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001113 traceback.print_exc()
1114
1115 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001116 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001117
wohlganger58fc71c2017-09-10 16:19:47 -05001118 extfiles = { # Map built-in config-extension section names to file names.
1119 'ZzDummy': 'zzdummy',
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001120 }
1121
David Scherer7aced172000-08-15 01:13:23 +00001122 def load_extension(self, name):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001123 fname = self.extfiles.get(name, name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001124 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001125 try:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001126 mod = importlib.import_module('.' + fname, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001127 except (ImportError, TypeError):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001128 mod = importlib.import_module(fname)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001129 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001130 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001131 raise
David Scherer7aced172000-08-15 01:13:23 +00001132 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001133 keydefs = idleConf.GetExtensionBindings(name)
1134 if hasattr(cls, "menudefs"):
1135 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001136 ins = cls(self)
1137 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001138 if keydefs:
1139 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001140 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001141 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001142 while methodname[:1] == '<':
1143 methodname = methodname[1:]
1144 while methodname[-1:] == '>':
1145 methodname = methodname[:-1]
1146 methodname = methodname + "_event"
1147 if hasattr(ins, methodname):
1148 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001149
1150 def apply_bindings(self, keydefs=None):
1151 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001152 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001153 text = self.text
1154 text.keydefs = keydefs
1155 for event, keylist in keydefs.items():
1156 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001157 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001158
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001159 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001160 """Add appropriate entries to the menus and submenus
1161
1162 Menus that are absent or None in self.menudict are ignored.
1163 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001164 if menudefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001165 menudefs = self.mainmenu.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001166 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001167 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001168 menudict = self.menudict
1169 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001170 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001171 menu = menudict.get(mname)
1172 if not menu:
1173 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001174 for entry in entrylist:
1175 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001176 menu.add_separator()
1177 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001178 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001179 checkbutton = (label[:1] == '!')
1180 if checkbutton:
1181 label = label[1:]
1182 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001183 accelerator = get_accelerator(keydefs, eventname)
1184 def command(text=text, eventname=eventname):
1185 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001186 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001187 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001188 menu.add_checkbutton(label=label, underline=underline,
1189 command=command, accelerator=accelerator,
1190 variable=var)
1191 else:
1192 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001193 command=command,
1194 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001195
1196 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001197 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001198 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001199 value = var.get()
1200 return value
1201 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001202 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001203
1204 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001205 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001206 if var:
1207 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001208 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001209 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001210
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001211 def get_var_obj(self, name, vartype=None):
1212 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001213 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001214 # create a Tkinter variable object with self.text as master:
1215 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001216 return var
1217
1218 # Tk implementations of "virtual text methods" -- each platform
1219 # reusing IDLE's support code needs to define these for its GUI's
1220 # flavor of widget.
1221
1222 # Is character at text_index in a Python string? Return 0 for
1223 # "guaranteed no", true for anything else. This info is expensive
1224 # to compute ab initio, but is probably already known by the
1225 # platform's colorizer.
1226
1227 def is_char_in_string(self, text_index):
1228 if self.color:
1229 # Return true iff colorizer hasn't (re)gotten this far
1230 # yet, or the character is tagged as being in a string
1231 return self.text.tag_prevrange("TODO", text_index) or \
1232 "STRING" in self.text.tag_names(text_index)
1233 else:
1234 # The colorizer is missing: assume the worst
1235 return 1
1236
1237 # If a selection is defined in the text widget, return (start,
1238 # end) as Tkinter text indices, otherwise return (None, None)
1239 def get_selection_indices(self):
1240 try:
1241 first = self.text.index("sel.first")
1242 last = self.text.index("sel.last")
1243 return first, last
1244 except TclError:
1245 return None, None
1246
1247 # Return the text widget's current view of what a tab stop means
1248 # (equivalent width in spaces).
1249
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001250 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001251 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1252 return int(current)
1253
1254 # Set the text widget's current view of what a tab stop means.
1255
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001256 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001257 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001258 if self.get_tk_tabwidth() != newtabwidth:
1259 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001260 pixels = text.tk.call("font", "measure", text["font"],
1261 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001262 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001263 text.configure(tabs=pixels)
1264
Guido van Rossum33d26892007-08-05 15:29:28 +00001265### begin autoindent code ### (configuration was moved to beginning of class)
1266
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001267 def set_indentation_params(self, is_py_src, guess=True):
1268 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001269 i = self.guess_indent()
1270 if 2 <= i <= 8:
1271 self.indentwidth = i
1272 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001273 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001274 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001275
1276 def smart_backspace_event(self, event):
1277 text = self.text
1278 first, last = self.get_selection_indices()
1279 if first and last:
1280 text.delete(first, last)
1281 text.mark_set("insert", first)
1282 return "break"
1283 # Delete whitespace left, until hitting a real char or closest
1284 # preceding virtual tab stop.
1285 chars = text.get("insert linestart", "insert")
1286 if chars == '':
1287 if text.compare("insert", ">", "1.0"):
1288 # easy: delete preceding newline
1289 text.delete("insert-1c")
1290 else:
1291 text.bell() # at start of buffer
1292 return "break"
1293 if chars[-1] not in " \t":
1294 # easy: delete preceding real char
1295 text.delete("insert-1c")
1296 return "break"
1297 # Ick. It may require *inserting* spaces if we back up over a
1298 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001299 tabwidth = self.tabwidth
1300 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001301 assert have > 0
1302 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001303 # Debug prompt is multilined....
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001304 ncharsdeleted = 0
1305 while 1:
Terry Jan Reedye86172d2017-10-27 20:26:12 -04001306 if chars == self.prompt_last_line: # '' unless PyShell
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001307 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001308 chars = chars[:-1]
1309 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001310 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001311 if have <= want or chars[-1] not in " \t":
1312 break
1313 text.undo_block_start()
1314 text.delete("insert-%dc" % ncharsdeleted, "insert")
1315 if have < want:
1316 text.insert("insert", ' ' * (want - have))
1317 text.undo_block_stop()
1318 return "break"
1319
1320 def smart_indent_event(self, event):
1321 # if intraline selection:
1322 # delete it
1323 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001324 # do indent-region
1325 # else:
1326 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001327 text = self.text
1328 first, last = self.get_selection_indices()
1329 text.undo_block_start()
1330 try:
1331 if first and last:
1332 if index2line(first) != index2line(last):
Cheryl Sabella82494aa2019-07-17 09:44:44 -04001333 return self.fregion.indent_region_event(event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001334 text.delete(first, last)
1335 text.mark_set("insert", first)
1336 prefix = text.get("insert linestart", "insert")
Tal Einat9b5ce622019-07-11 17:20:14 +03001337 raw, effective = get_line_indent(prefix, self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001338 if raw == len(prefix):
1339 # only whitespace to the left
1340 self.reindent_to(effective + self.indentwidth)
1341 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001342 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001343 if self.usetabs:
1344 pad = '\t'
1345 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001346 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001347 n = self.indentwidth
1348 pad = ' ' * (n - effective % n)
1349 text.insert("insert", pad)
1350 text.see("insert")
1351 return "break"
1352 finally:
1353 text.undo_block_stop()
1354
1355 def newline_and_indent_event(self, event):
Cheryl Sabellaec646402020-01-21 05:11:26 -05001356 """Insert a newline and indentation after Enter keypress event.
1357
1358 Properly position the cursor on the new line based on information
1359 from the current line. This takes into account if the current line
1360 is a shell prompt, is empty, has selected text, contains a block
1361 opener, contains a block closer, is a continuation line, or
1362 is inside a string.
1363 """
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001364 text = self.text
1365 first, last = self.get_selection_indices()
1366 text.undo_block_start()
Cheryl Sabellaec646402020-01-21 05:11:26 -05001367 try: # Close undo block and expose new line in finally clause.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001368 if first and last:
1369 text.delete(first, last)
1370 text.mark_set("insert", first)
1371 line = text.get("insert linestart", "insert")
Cheryl Sabellaec646402020-01-21 05:11:26 -05001372
1373 # Count leading whitespace for indent size.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001374 i, n = 0, len(line)
1375 while i < n and line[i] in " \t":
Cheryl Sabellaec646402020-01-21 05:11:26 -05001376 i += 1
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001377 if i == n:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001378 # The cursor is in or at leading indentation in a continuation
1379 # line; just inject an empty line at the start.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001380 text.insert("insert linestart", '\n')
1381 return "break"
1382 indent = line[:i]
Cheryl Sabellaec646402020-01-21 05:11:26 -05001383
1384 # Strip whitespace before insert point unless it's in the prompt.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001385 i = 0
Terry Jan Reedye86172d2017-10-27 20:26:12 -04001386 while line and line[-1] in " \t" and line != self.prompt_last_line:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001387 line = line[:-1]
Cheryl Sabellaec646402020-01-21 05:11:26 -05001388 i += 1
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001389 if i:
1390 text.delete("insert - %d chars" % i, "insert")
Cheryl Sabellaec646402020-01-21 05:11:26 -05001391
1392 # Strip whitespace after insert point.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001393 while text.get("insert") in " \t":
1394 text.delete("insert")
Cheryl Sabellaec646402020-01-21 05:11:26 -05001395
1396 # Insert new line.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001397 text.insert("insert", '\n')
1398
Cheryl Sabellaec646402020-01-21 05:11:26 -05001399 # Adjust indentation for continuations and block open/close.
1400 # First need to find the last statement.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001401 lno = index2line(text.index('insert'))
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001402 y = pyparse.Parser(self.indentwidth, self.tabwidth)
Cheryl Sabella6bdc4de2019-06-02 14:56:47 -04001403 if not self.prompt_last_line:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001404 for context in self.num_context_lines:
1405 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001406 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001407 rawtext = text.get(startatindex, "insert")
Cheryl Sabellac29c03a2018-02-23 21:35:27 -05001408 y.set_code(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001409 bod = y.find_good_parse_start(
Cheryl Sabellaec646402020-01-21 05:11:26 -05001410 self._build_char_in_string_func(startatindex))
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001411 if bod is not None or startat == 1:
1412 break
1413 y.set_lo(bod or 0)
1414 else:
1415 r = text.tag_prevrange("console", "insert")
1416 if r:
1417 startatindex = r[1]
1418 else:
1419 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001420 rawtext = text.get(startatindex, "insert")
Cheryl Sabellac29c03a2018-02-23 21:35:27 -05001421 y.set_code(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001422 y.set_lo(0)
1423
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001424 c = y.get_continuation_type()
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001425 if c != pyparse.C_NONE:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001426 # The current statement hasn't ended yet.
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001427 if c == pyparse.C_STRING_FIRST_LINE:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001428 # After the first line of a string do not indent at all.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001429 pass
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001430 elif c == pyparse.C_STRING_NEXT_LINES:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001431 # Inside a string which started before this line;
1432 # just mimic the current indent.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001433 text.insert("insert", indent)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001434 elif c == pyparse.C_BRACKET:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001435 # Line up with the first (if any) element of the
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001436 # last open bracket structure; else indent one
1437 # level beyond the indent of the line with the
Cheryl Sabellaec646402020-01-21 05:11:26 -05001438 # last open bracket.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001439 self.reindent_to(y.compute_bracket_indent())
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001440 elif c == pyparse.C_BACKSLASH:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001441 # If more than one line in this statement already, just
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001442 # mimic the current indent; else if initial line
1443 # has a start on an assignment stmt, indent to
1444 # beyond leftmost =; else to beyond first chunk of
Cheryl Sabellaec646402020-01-21 05:11:26 -05001445 # non-whitespace on initial line.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001446 if y.get_num_lines_in_stmt() > 1:
1447 text.insert("insert", indent)
1448 else:
1449 self.reindent_to(y.compute_backslash_indent())
1450 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001451 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001452 return "break"
1453
Cheryl Sabellaec646402020-01-21 05:11:26 -05001454 # This line starts a brand new statement; indent relative to
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001455 # indentation of initial line of closest preceding
Cheryl Sabellaec646402020-01-21 05:11:26 -05001456 # interesting statement.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001457 indent = y.get_base_indent_string()
1458 text.insert("insert", indent)
1459 if y.is_block_opener():
1460 self.smart_indent_event(event)
1461 elif indent and y.is_block_closer():
1462 self.smart_backspace_event(event)
1463 return "break"
1464 finally:
1465 text.see("insert")
1466 text.undo_block_stop()
1467
Martin Panter7462b6492015-11-02 03:37:02 +00001468 # Our editwin provides an is_char_in_string function that works
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001469 # with a Tk text index, but PyParse only knows about offsets into
1470 # a string. This builds a function for PyParse that accepts an
1471 # offset.
1472
1473 def _build_char_in_string_func(self, startindex):
1474 def inner(offset, _startindex=startindex,
1475 _icis=self.is_char_in_string):
1476 return _icis(_startindex + "+%dc" % offset)
1477 return inner
1478
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001479 # XXX this isn't bound to anything -- see tabwidth comments
1480## def change_tabwidth_event(self, event):
1481## new = self._asktabwidth()
1482## if new != self.tabwidth:
1483## self.tabwidth = new
1484## self.set_indentation_params(0, guess=0)
1485## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001486
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001487 # Make string that displays as n leading blanks.
1488
1489 def _make_blanks(self, n):
1490 if self.usetabs:
1491 ntabs, nspaces = divmod(n, self.tabwidth)
1492 return '\t' * ntabs + ' ' * nspaces
1493 else:
1494 return ' ' * n
1495
1496 # Delete from beginning of line to insert point, then reinsert
1497 # column logical (meaning use tabs if appropriate) spaces.
1498
1499 def reindent_to(self, column):
1500 text = self.text
1501 text.undo_block_start()
1502 if text.compare("insert linestart", "!=", "insert"):
1503 text.delete("insert linestart", "insert")
1504 if column:
1505 text.insert("insert", self._make_blanks(column))
1506 text.undo_block_stop()
1507
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001508 # Guess indentwidth from text content.
1509 # Return guessed indentwidth. This should not be believed unless
1510 # it's in a reasonable range (e.g., it will be 0 if no indented
1511 # blocks are found).
1512
1513 def guess_indent(self):
1514 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1515 if opener and indented:
Tal Einat9b5ce622019-07-11 17:20:14 +03001516 raw, indentsmall = get_line_indent(opener, self.tabwidth)
1517 raw, indentlarge = get_line_indent(indented, self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001518 else:
1519 indentsmall = indentlarge = 0
1520 return indentlarge - indentsmall
1521
Tal Einat7123ea02019-07-23 15:22:11 +03001522 def toggle_line_numbers_event(self, event=None):
1523 if self.line_numbers is None:
1524 return
1525
1526 if self.line_numbers.is_shown:
1527 self.line_numbers.hide_sidebar()
1528 menu_label = "Show"
1529 else:
1530 self.line_numbers.show_sidebar()
1531 menu_label = "Hide"
Zackery Spytz23a567c2021-01-28 16:13:22 -07001532 self.update_menu_label(menu='options', index='*ine*umbers',
Tal Einat7123ea02019-07-23 15:22:11 +03001533 label=f'{menu_label} Line Numbers')
1534
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001535# "line.col" -> line, as an int
1536def index2line(index):
1537 return int(float(index))
1538
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001539
Tal Einat9b5ce622019-07-11 17:20:14 +03001540_line_indent_re = re.compile(r'[ \t]*')
1541def get_line_indent(line, tabwidth):
1542 """Return a line's indentation as (# chars, effective # of spaces).
1543
1544 The effective # of spaces is the length after properly "expanding"
1545 the tabs into spaces, as done by str.expandtabs(tabwidth).
1546 """
1547 m = _line_indent_re.match(line)
1548 return m.end(), len(m.group().expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001549
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001550
Terry Jan Reedy8dfe1562021-01-24 14:08:50 -05001551class IndentSearcher:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001552
1553 # .run() chews over the Text widget, looking for a block opener
1554 # and the stmt following it. Returns a pair,
1555 # (line containing block opener, line containing stmt)
1556 # Either or both may be None.
1557
1558 def __init__(self, text, tabwidth):
1559 self.text = text
1560 self.tabwidth = tabwidth
1561 self.i = self.finished = 0
1562 self.blkopenline = self.indentedline = None
1563
1564 def readline(self):
1565 if self.finished:
1566 return ""
1567 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001568 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001569 if self.text.compare(mark, ">=", "end"):
1570 return ""
1571 return self.text.get(mark, mark + " lineend+1c")
1572
1573 def tokeneater(self, type, token, start, end, line,
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001574 INDENT=tokenize.INDENT,
1575 NAME=tokenize.NAME,
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001576 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1577 if self.finished:
1578 pass
1579 elif type == NAME and token in OPENERS:
1580 self.blkopenline = line
1581 elif type == INDENT and self.blkopenline:
1582 self.indentedline = line
1583 self.finished = 1
1584
1585 def run(self):
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001586 save_tabsize = tokenize.tabsize
1587 tokenize.tabsize = self.tabwidth
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001588 try:
1589 try:
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001590 tokens = tokenize.generate_tokens(self.readline)
Trent Nelson428de652008-03-18 22:41:35 +00001591 for token in tokens:
1592 self.tokeneater(*token)
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001593 except (tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001594 # since we cut off the tokenizer early, we can trigger
1595 # spurious errors
1596 pass
1597 finally:
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001598 tokenize.tabsize = save_tabsize
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001599 return self.blkopenline, self.indentedline
1600
1601### end autoindent code ###
1602
David Scherer7aced172000-08-15 01:13:23 +00001603def prepstr(s):
1604 # Helper to extract the underscore from a string, e.g.
1605 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001606 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001607 if i >= 0:
1608 s = s[:i] + s[i+1:]
1609 return i, s
1610
1611
1612keynames = {
1613 'bracketleft': '[',
1614 'bracketright': ']',
1615 'slash': '/',
1616}
1617
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001618def get_accelerator(keydefs, eventname):
1619 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001620 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1621 # if not keylist:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001622 if (not keylist) or (macosx.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001623 "<<open-module>>",
1624 "<<goto-line>>",
1625 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001626 return ""
1627 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001628 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001629 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1630 s = re.sub("Key-", "", s)
1631 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1632 s = re.sub("Control-", "Ctrl-", s)
1633 s = re.sub("-", "+", s)
1634 s = re.sub("><", " ", s)
1635 s = re.sub("<", "", s)
1636 s = re.sub(">", "", s)
1637 return s
1638
1639
1640def fixwordbreaks(root):
Terry Jan Reedy5ff3a162018-04-30 03:08:01 -04001641 # On Windows, tcl/tk breaks 'words' only on spaces, as in Command Prompt.
1642 # We want Motif style everywhere. See #21474, msg218992 and followup.
David Scherer7aced172000-08-15 01:13:23 +00001643 tk = root.tk
1644 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
Terry Jan Reedy5ff3a162018-04-30 03:08:01 -04001645 tk.call('set', 'tcl_wordchars', r'\w')
1646 tk.call('set', 'tcl_nonwordchars', r'\W')
David Scherer7aced172000-08-15 01:13:23 +00001647
1648
Terry Jan Reedycd567362014-10-17 01:31:35 -04001649def _editor_window(parent): # htest #
1650 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001651 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001652 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001653 if sys.argv[1:]:
1654 filename = sys.argv[1]
1655 else:
1656 filename = None
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001657 macosx.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001658 edit = EditorWindow(root=root, filename=filename)
Tal Einat077059e2018-08-10 09:02:08 +03001659 text = edit.text
1660 text['height'] = 10
1661 for i in range(20):
1662 text.insert('insert', ' '*i + str(i) + '\n')
1663 # text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001664 # Does not stop error, neither does following
1665 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001666
1667if __name__ == '__main__':
Terry Jan Reedyee5ef302018-06-15 18:20:55 -04001668 from unittest import main
1669 main('idlelib.idle_test.test_editor', verbosity=2, exit=False)
Terry Jan Reedy7c153412016-06-26 17:48:02 -04001670
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001671 from idlelib.idle_test.htest import run
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04001672 run(_editor_window)