blob: fcc8a3f08ccfe33e1537f1c50678340b0d87eb10 [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
wohlganger58fc71c2017-09-10 16:19:47 -050063 from idlelib.zoomheight import ZoomHeight
David Scherer7aced172000-08-15 01:13:23 +000064
Terry Jan Reedy7c153412016-06-26 17:48:02 -040065 filesystemencoding = sys.getfilesystemencoding() # for file names
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000066 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000067
Tal Einat7123ea02019-07-23 15:22:11 +030068 allow_code_context = True
69 allow_line_numbers = True
Tal Einat15d38612021-04-29 01:27:55 +030070 user_input_insert_tags = None
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()).
Tal Einatb43cc312021-05-03 05:27:38 +0300314 autocomplete = self.AutoComplete(self, self.user_input_insert_tags)
wohlganger58fc71c2017-09-10 16:19:47 -0500315 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:
Tal Einat15d38612021-04-29 01:27:55 +0300787 self.per.insertfilterafter(filter=self.color, after=self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000788
Christian Heimesa156e092008-02-16 07:38:31 +0000789 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000790 if not self.color:
791 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000792 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000793 self.per.removefilter(self.color)
794 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000795
Steven M. Gavab77d3432002-03-02 07:16:21 +0000796 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400797 "Update the color theme"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400798 # Called from self.filename_change_hook and from configdialog.py
Christian Heimesa156e092008-02-16 07:38:31 +0000799 self._rmcolorizer()
800 self._addcolorizer()
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -0400801 EditorWindow.color_config(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000802
Tal Einat7123ea02019-07-23 15:22:11 +0300803 if self.code_context is not None:
804 self.code_context.update_highlight_colors()
805
806 if self.line_numbers is not None:
807 self.line_numbers.update_colors()
Tal Einat7036e1d2019-07-17 11:15:53 +0300808
Guido van Rossum33d26892007-08-05 15:29:28 +0000809 IDENTCHARS = string.ascii_letters + string.digits + "_"
810
811 def colorize_syntax_error(self, text, pos):
812 text.tag_add("ERROR", pos)
813 char = text.get(pos)
814 if char and char in self.IDENTCHARS:
815 text.tag_add("ERROR", pos + " wordstart", pos)
816 if '\n' == text.get(pos): # error at line end
817 text.mark_set("insert", pos)
818 else:
819 text.mark_set("insert", pos + "+1c")
820 text.see(pos)
821
Zackery Spytz9c284492019-11-13 00:13:33 -0700822 def update_cursor_blink(self):
823 "Update the cursor blink configuration."
824 cursorblink = idleConf.GetOption(
825 'main', 'EditorWindow', 'cursor-blink', type='bool')
826 if not cursorblink:
827 self.text['insertofftime'] = 0
828 else:
829 # Restore the original value
830 self.text['insertofftime'] = idleConf.blink_off_time
831
Steven M. Gavab1585412002-03-12 00:21:56 +0000832 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000833 "Update the text widgets' font if it is changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400834 # Called from configdialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400835
Tal Einat7036e1d2019-07-17 11:15:53 +0300836 # Update the code context widget first, since its height affects
837 # the height of the text widget. This avoids double re-rendering.
Tal Einat7123ea02019-07-23 15:22:11 +0300838 if self.code_context is not None:
839 self.code_context.update_font()
840 # Next, update the line numbers widget, since its width affects
841 # the width of the text widget.
842 if self.line_numbers is not None:
843 self.line_numbers.update_font()
844 # Finally, update the main text widget.
845 new_font = idleConf.GetFont(self.root, 'main', 'EditorWindow')
Tal Einat7036e1d2019-07-17 11:15:53 +0300846 self.text['font'] = new_font
Tal Einatd4b4c002019-08-25 08:52:58 +0300847 self.set_width()
Steven M. Gavab1585412002-03-12 00:21:56 +0000848
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000849 def RemoveKeybindings(self):
850 "Remove the keybindings before they are changed."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400851 # Called from configdialog.py
852 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000853 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000854 self.text.event_delete(event, *keylist)
855 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000856 xkeydefs = idleConf.GetExtensionBindings(extensionName)
857 if xkeydefs:
858 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000859 self.text.event_delete(event, *keylist)
860
861 def ApplyKeybindings(self):
862 "Update the keybindings after they are changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400863 # Called from configdialog.py
864 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000865 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000866 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000867 xkeydefs = idleConf.GetExtensionBindings(extensionName)
868 if xkeydefs:
869 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000870 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000871 menuEventDict = {}
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400872 for menu in self.mainmenu.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000873 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000874 for item in menu[1]:
875 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000876 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000877 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000878 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700879 end = menu.index(END)
880 if end is None:
881 # Skip empty menus
882 continue
883 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000884 for index in range(0, end):
885 if menu.type(index) == 'command':
886 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000887 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000888 itemName = menu.entrycget(index, 'label')
889 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000890 if menubarItem in menuEventDict:
891 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000892 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000893 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000894 accel = get_accelerator(keydefs, event)
895 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000896
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000897 def set_notabs_indentwidth(self):
898 "Update the indentwidth if changed and not using tabs in this window"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400899 # Called from configdialog.py
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000900 if not self.usetabs:
901 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
902 type='int')
903
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000904 def reset_help_menu_entries(self):
905 "Update the additional help entries on the Help menu"
906 help_list = idleConf.GetAllExtraHelpSourcesList()
907 helpmenu = self.menudict['help']
908 # first delete the extra help entries, if any
909 helpmenu_length = helpmenu.index(END)
910 if helpmenu_length > self.base_helpmenu_length:
911 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
912 # then rebuild them
913 if help_list:
914 helpmenu.add_separator()
915 for entry in help_list:
916 cmd = self.__extra_help_callback(entry[1])
917 helpmenu.add_command(label=entry[0], command=cmd)
918 # and update the menu dictionary
919 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000920
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000921 def __extra_help_callback(self, helpfile):
922 "Create a callback with the helpfile value frozen at definition time"
923 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000924 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000925 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000926 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000927 try:
928 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200929 except OSError as why:
Terry Jan Reedy879986d2021-01-25 06:33:18 -0500930 messagebox.showerror(title='Document Start Failure',
Terry Reedy6739cc02011-01-01 02:25:36 +0000931 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000932 else:
933 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000934 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000935
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000936 def update_recent_files_list(self, new_file=None):
937 "Load and update the recent files list and menus"
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400938 # TODO: move to iomenu.
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000939 rf_list = []
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400940 file_path = self.recent_files_path
941 if file_path and os.path.exists(file_path):
942 with open(file_path, 'r',
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400943 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000944 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000945 if new_file:
946 new_file = os.path.abspath(new_file) + '\n'
947 if new_file in rf_list:
948 rf_list.remove(new_file) # move to top
949 rf_list.insert(0, new_file)
950 # clean and save the recent files list
951 bad_paths = []
952 for path in rf_list:
953 if '\0' in path or not os.path.exists(path[0:-1]):
954 bad_paths.append(path)
955 rf_list = [path for path in rf_list if path not in bad_paths]
956 ulchars = "1234567890ABCDEFGHIJK"
957 rf_list = rf_list[0:len(ulchars)]
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400958 if file_path:
959 try:
960 with open(file_path, 'w',
961 encoding='utf_8', errors='replace') as rf_file:
962 rf_file.writelines(rf_list)
963 except OSError as err:
964 if not getattr(self.root, "recentfiles_message", False):
965 self.root.recentfiles_message = True
Terry Jan Reedy879986d2021-01-25 06:33:18 -0500966 messagebox.showwarning(title='IDLE Warning',
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400967 message="Cannot save Recent Files list to disk.\n"
968 f" {err}\n"
969 "Select OK to continue.",
970 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000971 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000972 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000973 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700974 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000975 for i, file_name in enumerate(rf_list):
976 file_name = file_name.rstrip() # zap \n
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000977 callback = instance.__recent_file_callback(file_name)
Serhiy Storchaka06cb94b2019-10-04 13:09:52 +0300978 menu.add_command(label=ulchars[i] + " " + file_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000979 command=callback,
980 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000981
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000982 def __recent_file_callback(self, file_name):
983 def open_recent_file(fn_closure=file_name):
984 self.io.open(editFile=fn_closure)
985 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000986
David Scherer7aced172000-08-15 01:13:23 +0000987 def saved_change_hook(self):
988 short = self.short_title()
989 long = self.long_title()
990 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400991 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000992 elif short:
993 title = short
994 elif long:
995 title = long
996 else:
Terry Jan Reedya9022392019-01-18 02:09:53 -0500997 title = "untitled"
David Scherer7aced172000-08-15 01:13:23 +0000998 icon = short or long or title
999 if not self.get_saved():
1000 title = "*%s*" % title
1001 icon = "*%s" % icon
1002 self.top.wm_title(title)
1003 self.top.wm_iconname(icon)
1004
1005 def get_saved(self):
1006 return self.undo.get_saved()
1007
1008 def set_saved(self, flag):
1009 self.undo.set_saved(flag)
1010
1011 def reset_undo(self):
1012 self.undo.reset_undo()
1013
1014 def short_title(self):
1015 filename = self.io.filename
Serhiy Storchaka06cb94b2019-10-04 13:09:52 +03001016 return os.path.basename(filename) if filename else "untitled"
David Scherer7aced172000-08-15 01:13:23 +00001017
1018 def long_title(self):
Serhiy Storchaka06cb94b2019-10-04 13:09:52 +03001019 return self.io.filename or ""
David Scherer7aced172000-08-15 01:13:23 +00001020
1021 def center_insert_event(self, event):
1022 self.center()
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001023 return "break"
David Scherer7aced172000-08-15 01:13:23 +00001024
1025 def center(self, mark="insert"):
1026 text = self.text
1027 top, bot = self.getwindowlines()
1028 lineno = self.getlineno(mark)
1029 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001030 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +00001031 text.yview(float(newtop))
1032
1033 def getwindowlines(self):
1034 text = self.text
1035 top = self.getlineno("@0,0")
1036 bot = self.getlineno("@0,65535")
1037 if top == bot and text.winfo_height() == 1:
1038 # Geometry manager hasn't run yet
1039 height = int(text['height'])
1040 bot = top + height - 1
1041 return top, bot
1042
1043 def getlineno(self, mark="insert"):
1044 text = self.text
1045 return int(float(text.index(mark)))
1046
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001047 def get_geometry(self):
1048 "Return (width, height, x, y)"
1049 geom = self.top.wm_geometry()
1050 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +00001051 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001052
David Scherer7aced172000-08-15 01:13:23 +00001053 def close_event(self, event):
1054 self.close()
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001055 return "break"
David Scherer7aced172000-08-15 01:13:23 +00001056
1057 def maybesave(self):
1058 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001059 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001060 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001061 self.top.deiconify()
1062 self.top.lower()
1063 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001064 return self.io.maybesave()
1065
1066 def close(self):
Terry Jan Reedydfd34a92019-09-17 02:05:04 -04001067 try:
1068 reply = self.maybesave()
1069 if str(reply) != "cancel":
1070 self._close()
1071 return reply
1072 except AttributeError: # bpo-35379: close called twice
1073 pass
David Scherer7aced172000-08-15 01:13:23 +00001074
1075 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001076 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001077 self.update_recent_files_list(new_file=self.io.filename)
Terry Jan Reedya361e892018-06-20 21:25:59 -04001078 window.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001079 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001080 self.io.close()
1081 self.io = None
1082 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001083 if self.color:
Cheryl Sabellab9f03542019-03-01 05:19:40 -05001084 self.color.close()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001085 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001086 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001087 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001088 self.per.close()
1089 self.per = None
1090 self.top.destroy()
1091 if self.close_hook:
1092 # unless override: unregister from flist, terminate if last window
1093 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001094
1095 def load_extensions(self):
1096 self.extensions = {}
1097 self.load_standard_extensions()
1098
1099 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001100 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001101 if hasattr(ins, "close"):
1102 ins.close()
1103 self.extensions = {}
1104
1105 def load_standard_extensions(self):
1106 for name in self.get_standard_extension_names():
1107 try:
1108 self.load_extension(name)
1109 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001110 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001111 traceback.print_exc()
1112
1113 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001114 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001115
wohlganger58fc71c2017-09-10 16:19:47 -05001116 extfiles = { # Map built-in config-extension section names to file names.
1117 'ZzDummy': 'zzdummy',
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001118 }
1119
David Scherer7aced172000-08-15 01:13:23 +00001120 def load_extension(self, name):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001121 fname = self.extfiles.get(name, name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001122 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001123 try:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001124 mod = importlib.import_module('.' + fname, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001125 except (ImportError, TypeError):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001126 mod = importlib.import_module(fname)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001127 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001128 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001129 raise
David Scherer7aced172000-08-15 01:13:23 +00001130 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001131 keydefs = idleConf.GetExtensionBindings(name)
1132 if hasattr(cls, "menudefs"):
1133 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001134 ins = cls(self)
1135 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001136 if keydefs:
1137 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001138 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001139 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001140 while methodname[:1] == '<':
1141 methodname = methodname[1:]
1142 while methodname[-1:] == '>':
1143 methodname = methodname[:-1]
1144 methodname = methodname + "_event"
1145 if hasattr(ins, methodname):
1146 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001147
1148 def apply_bindings(self, keydefs=None):
1149 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001150 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001151 text = self.text
1152 text.keydefs = keydefs
1153 for event, keylist in keydefs.items():
1154 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001155 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001156
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001157 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001158 """Add appropriate entries to the menus and submenus
1159
1160 Menus that are absent or None in self.menudict are ignored.
1161 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001162 if menudefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001163 menudefs = self.mainmenu.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001164 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001165 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001166 menudict = self.menudict
1167 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001168 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001169 menu = menudict.get(mname)
1170 if not menu:
1171 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001172 for entry in entrylist:
1173 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001174 menu.add_separator()
1175 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001176 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001177 checkbutton = (label[:1] == '!')
1178 if checkbutton:
1179 label = label[1:]
1180 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001181 accelerator = get_accelerator(keydefs, eventname)
1182 def command(text=text, eventname=eventname):
1183 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001184 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001185 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001186 menu.add_checkbutton(label=label, underline=underline,
1187 command=command, accelerator=accelerator,
1188 variable=var)
1189 else:
1190 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001191 command=command,
1192 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001193
1194 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001195 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001196 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001197 value = var.get()
1198 return value
1199 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001200 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001201
1202 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001203 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001204 if var:
1205 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001206 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001207 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001208
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001209 def get_var_obj(self, name, vartype=None):
1210 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001211 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001212 # create a Tkinter variable object with self.text as master:
1213 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001214 return var
1215
1216 # Tk implementations of "virtual text methods" -- each platform
1217 # reusing IDLE's support code needs to define these for its GUI's
1218 # flavor of widget.
1219
1220 # Is character at text_index in a Python string? Return 0 for
1221 # "guaranteed no", true for anything else. This info is expensive
1222 # to compute ab initio, but is probably already known by the
1223 # platform's colorizer.
1224
1225 def is_char_in_string(self, text_index):
1226 if self.color:
1227 # Return true iff colorizer hasn't (re)gotten this far
1228 # yet, or the character is tagged as being in a string
1229 return self.text.tag_prevrange("TODO", text_index) or \
1230 "STRING" in self.text.tag_names(text_index)
1231 else:
1232 # The colorizer is missing: assume the worst
1233 return 1
1234
1235 # If a selection is defined in the text widget, return (start,
1236 # end) as Tkinter text indices, otherwise return (None, None)
1237 def get_selection_indices(self):
1238 try:
1239 first = self.text.index("sel.first")
1240 last = self.text.index("sel.last")
1241 return first, last
1242 except TclError:
1243 return None, None
1244
1245 # Return the text widget's current view of what a tab stop means
1246 # (equivalent width in spaces).
1247
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001248 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001249 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1250 return int(current)
1251
1252 # Set the text widget's current view of what a tab stop means.
1253
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001254 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001255 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001256 if self.get_tk_tabwidth() != newtabwidth:
1257 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001258 pixels = text.tk.call("font", "measure", text["font"],
1259 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001260 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001261 text.configure(tabs=pixels)
1262
Guido van Rossum33d26892007-08-05 15:29:28 +00001263### begin autoindent code ### (configuration was moved to beginning of class)
1264
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001265 def set_indentation_params(self, is_py_src, guess=True):
1266 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001267 i = self.guess_indent()
1268 if 2 <= i <= 8:
1269 self.indentwidth = i
1270 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001271 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001272 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001273
1274 def smart_backspace_event(self, event):
1275 text = self.text
1276 first, last = self.get_selection_indices()
1277 if first and last:
1278 text.delete(first, last)
1279 text.mark_set("insert", first)
1280 return "break"
1281 # Delete whitespace left, until hitting a real char or closest
1282 # preceding virtual tab stop.
1283 chars = text.get("insert linestart", "insert")
1284 if chars == '':
1285 if text.compare("insert", ">", "1.0"):
1286 # easy: delete preceding newline
1287 text.delete("insert-1c")
1288 else:
1289 text.bell() # at start of buffer
1290 return "break"
1291 if chars[-1] not in " \t":
1292 # easy: delete preceding real char
1293 text.delete("insert-1c")
1294 return "break"
1295 # Ick. It may require *inserting* spaces if we back up over a
1296 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001297 tabwidth = self.tabwidth
1298 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001299 assert have > 0
1300 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001301 # Debug prompt is multilined....
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001302 ncharsdeleted = 0
1303 while 1:
1304 chars = chars[:-1]
1305 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001306 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001307 if have <= want or chars[-1] not in " \t":
1308 break
1309 text.undo_block_start()
1310 text.delete("insert-%dc" % ncharsdeleted, "insert")
1311 if have < want:
Tal Einat15d38612021-04-29 01:27:55 +03001312 text.insert("insert", ' ' * (want - have),
1313 self.user_input_insert_tags)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001314 text.undo_block_stop()
1315 return "break"
1316
1317 def smart_indent_event(self, event):
1318 # if intraline selection:
1319 # delete it
1320 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001321 # do indent-region
1322 # else:
1323 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001324 text = self.text
1325 first, last = self.get_selection_indices()
1326 text.undo_block_start()
1327 try:
1328 if first and last:
1329 if index2line(first) != index2line(last):
Cheryl Sabella82494aa2019-07-17 09:44:44 -04001330 return self.fregion.indent_region_event(event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001331 text.delete(first, last)
1332 text.mark_set("insert", first)
1333 prefix = text.get("insert linestart", "insert")
Tal Einat9b5ce622019-07-11 17:20:14 +03001334 raw, effective = get_line_indent(prefix, self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001335 if raw == len(prefix):
1336 # only whitespace to the left
1337 self.reindent_to(effective + self.indentwidth)
1338 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001339 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001340 if self.usetabs:
1341 pad = '\t'
1342 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001343 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001344 n = self.indentwidth
1345 pad = ' ' * (n - effective % n)
Tal Einat15d38612021-04-29 01:27:55 +03001346 text.insert("insert", pad, self.user_input_insert_tags)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001347 text.see("insert")
1348 return "break"
1349 finally:
1350 text.undo_block_stop()
1351
1352 def newline_and_indent_event(self, event):
Cheryl Sabellaec646402020-01-21 05:11:26 -05001353 """Insert a newline and indentation after Enter keypress event.
1354
1355 Properly position the cursor on the new line based on information
1356 from the current line. This takes into account if the current line
1357 is a shell prompt, is empty, has selected text, contains a block
1358 opener, contains a block closer, is a continuation line, or
1359 is inside a string.
1360 """
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001361 text = self.text
1362 first, last = self.get_selection_indices()
1363 text.undo_block_start()
Cheryl Sabellaec646402020-01-21 05:11:26 -05001364 try: # Close undo block and expose new line in finally clause.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001365 if first and last:
1366 text.delete(first, last)
1367 text.mark_set("insert", first)
1368 line = text.get("insert linestart", "insert")
Cheryl Sabellaec646402020-01-21 05:11:26 -05001369
1370 # Count leading whitespace for indent size.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001371 i, n = 0, len(line)
1372 while i < n and line[i] in " \t":
Cheryl Sabellaec646402020-01-21 05:11:26 -05001373 i += 1
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001374 if i == n:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001375 # The cursor is in or at leading indentation in a continuation
1376 # line; just inject an empty line at the start.
Tal Einat15d38612021-04-29 01:27:55 +03001377 text.insert("insert linestart", '\n',
1378 self.user_input_insert_tags)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001379 return "break"
1380 indent = line[:i]
Cheryl Sabellaec646402020-01-21 05:11:26 -05001381
1382 # Strip whitespace before insert point unless it's in the prompt.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001383 i = 0
Tal Einat15d38612021-04-29 01:27:55 +03001384 while line and line[-1] in " \t":
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001385 line = line[:-1]
Cheryl Sabellaec646402020-01-21 05:11:26 -05001386 i += 1
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001387 if i:
1388 text.delete("insert - %d chars" % i, "insert")
Cheryl Sabellaec646402020-01-21 05:11:26 -05001389
1390 # Strip whitespace after insert point.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001391 while text.get("insert") in " \t":
1392 text.delete("insert")
Cheryl Sabellaec646402020-01-21 05:11:26 -05001393
1394 # Insert new line.
Tal Einat15d38612021-04-29 01:27:55 +03001395 text.insert("insert", '\n', self.user_input_insert_tags)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001396
Cheryl Sabellaec646402020-01-21 05:11:26 -05001397 # Adjust indentation for continuations and block open/close.
1398 # First need to find the last statement.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001399 lno = index2line(text.index('insert'))
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001400 y = pyparse.Parser(self.indentwidth, self.tabwidth)
Cheryl Sabella6bdc4de2019-06-02 14:56:47 -04001401 if not self.prompt_last_line:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001402 for context in self.num_context_lines:
1403 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001404 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001405 rawtext = text.get(startatindex, "insert")
Cheryl Sabellac29c03a2018-02-23 21:35:27 -05001406 y.set_code(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001407 bod = y.find_good_parse_start(
Cheryl Sabellaec646402020-01-21 05:11:26 -05001408 self._build_char_in_string_func(startatindex))
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001409 if bod is not None or startat == 1:
1410 break
1411 y.set_lo(bod or 0)
1412 else:
1413 r = text.tag_prevrange("console", "insert")
1414 if r:
1415 startatindex = r[1]
1416 else:
1417 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001418 rawtext = text.get(startatindex, "insert")
Cheryl Sabellac29c03a2018-02-23 21:35:27 -05001419 y.set_code(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001420 y.set_lo(0)
1421
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001422 c = y.get_continuation_type()
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001423 if c != pyparse.C_NONE:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001424 # The current statement hasn't ended yet.
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001425 if c == pyparse.C_STRING_FIRST_LINE:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001426 # After the first line of a string do not indent at all.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001427 pass
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001428 elif c == pyparse.C_STRING_NEXT_LINES:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001429 # Inside a string which started before this line;
1430 # just mimic the current indent.
Tal Einat15d38612021-04-29 01:27:55 +03001431 text.insert("insert", indent, self.user_input_insert_tags)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001432 elif c == pyparse.C_BRACKET:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001433 # Line up with the first (if any) element of the
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001434 # last open bracket structure; else indent one
1435 # level beyond the indent of the line with the
Cheryl Sabellaec646402020-01-21 05:11:26 -05001436 # last open bracket.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001437 self.reindent_to(y.compute_bracket_indent())
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001438 elif c == pyparse.C_BACKSLASH:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001439 # If more than one line in this statement already, just
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001440 # mimic the current indent; else if initial line
1441 # has a start on an assignment stmt, indent to
1442 # beyond leftmost =; else to beyond first chunk of
Cheryl Sabellaec646402020-01-21 05:11:26 -05001443 # non-whitespace on initial line.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001444 if y.get_num_lines_in_stmt() > 1:
Tal Einat15d38612021-04-29 01:27:55 +03001445 text.insert("insert", indent,
1446 self.user_input_insert_tags)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001447 else:
1448 self.reindent_to(y.compute_backslash_indent())
1449 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001450 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001451 return "break"
1452
Cheryl Sabellaec646402020-01-21 05:11:26 -05001453 # This line starts a brand new statement; indent relative to
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001454 # indentation of initial line of closest preceding
Cheryl Sabellaec646402020-01-21 05:11:26 -05001455 # interesting statement.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001456 indent = y.get_base_indent_string()
Tal Einat15d38612021-04-29 01:27:55 +03001457 text.insert("insert", indent, self.user_input_insert_tags)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001458 if y.is_block_opener():
1459 self.smart_indent_event(event)
1460 elif indent and y.is_block_closer():
1461 self.smart_backspace_event(event)
1462 return "break"
1463 finally:
1464 text.see("insert")
1465 text.undo_block_stop()
1466
Martin Panter7462b6492015-11-02 03:37:02 +00001467 # Our editwin provides an is_char_in_string function that works
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001468 # with a Tk text index, but PyParse only knows about offsets into
1469 # a string. This builds a function for PyParse that accepts an
1470 # offset.
1471
1472 def _build_char_in_string_func(self, startindex):
1473 def inner(offset, _startindex=startindex,
1474 _icis=self.is_char_in_string):
1475 return _icis(_startindex + "+%dc" % offset)
1476 return inner
1477
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001478 # XXX this isn't bound to anything -- see tabwidth comments
1479## def change_tabwidth_event(self, event):
1480## new = self._asktabwidth()
1481## if new != self.tabwidth:
1482## self.tabwidth = new
1483## self.set_indentation_params(0, guess=0)
1484## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001485
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001486 # Make string that displays as n leading blanks.
1487
1488 def _make_blanks(self, n):
1489 if self.usetabs:
1490 ntabs, nspaces = divmod(n, self.tabwidth)
1491 return '\t' * ntabs + ' ' * nspaces
1492 else:
1493 return ' ' * n
1494
1495 # Delete from beginning of line to insert point, then reinsert
1496 # column logical (meaning use tabs if appropriate) spaces.
1497
1498 def reindent_to(self, column):
1499 text = self.text
1500 text.undo_block_start()
1501 if text.compare("insert linestart", "!=", "insert"):
1502 text.delete("insert linestart", "insert")
1503 if column:
Tal Einat15d38612021-04-29 01:27:55 +03001504 text.insert("insert", self._make_blanks(column),
1505 self.user_input_insert_tags)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001506 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)