blob: 5b81b52f9196c490ad66d795614cd92a4ff06468 [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
Georg Brandl14fc4272008-05-17 18:39:55 +000015import tkinter.simpledialog as tkSimpleDialog
16import tkinter.messagebox as tkMessageBox
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
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000049class EditorWindow(object):
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 Reedy3c7eccd02015-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 Reedy3c7eccd02015-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 Reedy3c7eccd02015-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
298 self.askyesno = tkMessageBox.askyesno
299 self.askinteger = tkSimpleDialog.askinteger
300 self.showerror = tkMessageBox.showerror
301
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:
342 self.update_menu_state('options', '*Code Context', 'disabled')
343 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:
350 self.update_menu_state('options', '*Line Numbers', '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)
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400453 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000454 mbar.add_cascade(label=label, menu=menu, underline=underline)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400455 if macosx.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000456 # Insert the application menu
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400457 menudict['application'] = menu = Menu(mbar, name='apple',
458 tearoff=0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000459 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000460 self.fill_menus()
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400461 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000462 self.menudict['file'].insert_cascade(3, label='Recent Files',
463 underline=0,
464 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000465 self.base_helpmenu_length = self.menudict['help'].index(END)
466 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000467
468 def postwindowsmenu(self):
Terry Jan Reedya361e892018-06-20 21:25:59 -0400469 # Only called when Window menu exists
Terry Jan Reedy33c74202018-06-20 22:49:55 -0400470 menu = self.menudict['window']
David Scherer7aced172000-08-15 01:13:23 +0000471 end = menu.index("end")
472 if end is None:
473 end = -1
474 if end > self.wmenu_end:
475 menu.delete(self.wmenu_end+1, end)
Terry Jan Reedya361e892018-06-20 21:25:59 -0400476 window.add_windows_to_menu(menu)
David Scherer7aced172000-08-15 01:13:23 +0000477
Cheryl Sabellac1b4b0f2018-12-22 01:25:45 -0500478 def update_menu_label(self, menu, index, label):
Cheryl Sabella804a5d92018-12-28 13:15:30 -0500479 "Update label for menu item at index."
Cheryl Sabellac1b4b0f2018-12-22 01:25:45 -0500480 menuitem = self.menudict[menu]
481 menuitem.entryconfig(index, label=label)
482
Cheryl Sabella804a5d92018-12-28 13:15:30 -0500483 def update_menu_state(self, menu, index, state):
484 "Update state for menu item at index."
485 menuitem = self.menudict[menu]
486 menuitem.entryconfig(index, state=state)
487
Cheryl Sabellad49dbd92018-06-04 11:48:21 -0400488 def handle_yview(self, event, *args):
489 "Handle scrollbar."
490 if event == 'moveto':
491 fraction = float(args[0])
492 lines = (round(self.getlineno('end') * fraction) -
493 self.getlineno('@0,0'))
494 event = 'scroll'
495 args = (lines, 'units')
496 self.text.yview(event, *args)
497 return 'break'
498
David Scherer7aced172000-08-15 01:13:23 +0000499 rmenu = None
500
501 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000502 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
503 if not self.rmenu:
504 self.make_rmenu()
505 rmenu = self.rmenu
506 self.event = event
507 iswin = sys.platform[:3] == 'win'
508 if iswin:
509 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200510
Roger Serwy6b2918a2013-04-07 12:15:52 -0500511 for item in self.rmenu_specs:
512 try:
513 label, eventname, verify_state = item
514 except ValueError: # see issue1207589
515 continue
516
Andrew Svetlovd1837672012-11-01 22:41:19 +0200517 if verify_state is None:
518 continue
519 state = getattr(self, verify_state)()
520 rmenu.entryconfigure(label, state=state)
521
522
David Scherer7aced172000-08-15 01:13:23 +0000523 rmenu.tk_popup(event.x_root, event.y_root)
524 if iswin:
525 self.text.config(cursor="ibeam")
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300526 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000527
528 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200529 # ("Label", "<<virtual-event>>", "statefuncname"), ...
530 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000531 ]
532
533 def make_rmenu(self):
534 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500535 for item in self.rmenu_specs:
536 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200537 if label is not None:
538 def command(text=self.text, eventname=eventname):
539 text.event_generate(eventname)
540 rmenu.add_command(label=label, command=command)
541 else:
542 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000543 self.rmenu = rmenu
544
Andrew Svetlovd1837672012-11-01 22:41:19 +0200545 def rmenu_check_cut(self):
546 return self.rmenu_check_copy()
547
548 def rmenu_check_copy(self):
549 try:
550 indx = self.text.index('sel.first')
551 except TclError:
552 return 'disabled'
553 else:
554 return 'normal' if indx else 'disabled'
555
556 def rmenu_check_paste(self):
557 try:
558 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
559 except TclError:
560 return 'disabled'
561 else:
562 return 'normal'
563
David Scherer7aced172000-08-15 01:13:23 +0000564 def about_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400565 "Handle Help 'About IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400566 # Synchronize with macosx.overrideRootMenu.about_dialog.
csabella18ede062017-06-23 20:00:58 -0400567 help_about.AboutDialog(self.top)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300568 return "break"
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000569
Steven M. Gava3b55a892001-11-21 05:56:26 +0000570 def config_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400571 "Handle Options 'Configure IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400572 # Synchronize with macosx.overrideRootMenu.config_dialog.
573 configdialog.ConfigDialog(self.top,'Settings')
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300574 return "break"
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400575
David Scherer7aced172000-08-15 01:13:23 +0000576 def help_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400577 "Handle Help 'IDLE Help' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400578 # Synchronize with macosx.overrideRootMenu.help_dialog.
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500579 if self.root:
580 parent = self.root
581 else:
582 parent = self.top
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400583 help.show_idlehelp(parent)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300584 return "break"
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000585
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000586 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000587 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000588 try:
589 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200590 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000591 tkMessageBox.showerror(title='Document Start Failure',
592 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000593 else:
594 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000595 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000596
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000597 def cut(self,event):
598 self.text.event_generate("<<Cut>>")
599 return "break"
600
601 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000602 if not self.text.tag_ranges("sel"):
603 # There is no selection, so do nothing and maybe interrupt.
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300604 return None
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000605 self.text.event_generate("<<Copy>>")
606 return "break"
607
608 def paste(self,event):
609 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000610 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000611 return "break"
612
David Scherer7aced172000-08-15 01:13:23 +0000613 def select_all(self, event=None):
614 self.text.tag_add("sel", "1.0", "end-1c")
615 self.text.mark_set("insert", "1.0")
616 self.text.see("insert")
617 return "break"
618
619 def remove_selection(self, event=None):
620 self.text.tag_remove("sel", "1.0", "end")
621 self.text.see("insert")
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300622 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000623
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000624 def move_at_edge_if_selection(self, edge_index):
625 """Cursor move begins at start or end of selection
626
627 When a left/right cursor key is pressed create and return to Tkinter a
628 function which causes a cursor move from the associated edge of the
629 selection.
630
631 """
632 self_text_index = self.text.index
633 self_text_mark_set = self.text.mark_set
634 edges_table = ("sel.first+1c", "sel.last-1c")
635 def move_at_edge(event):
636 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
637 try:
638 self_text_index("sel.first")
639 self_text_mark_set("insert", edges_table[edge_index])
640 except TclError:
641 pass
642 return move_at_edge
643
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000644 def del_word_left(self, event):
645 self.text.event_generate('<Meta-Delete>')
646 return "break"
647
648 def del_word_right(self, event):
649 self.text.event_generate('<Meta-d>')
650 return "break"
651
Steven M. Gavac5976402002-01-04 03:06:08 +0000652 def find_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400653 search.find(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000654 return "break"
655
656 def find_again_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400657 search.find_again(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000658 return "break"
659
660 def find_selection_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400661 search.find_selection(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000662 return "break"
663
664 def find_in_files_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400665 grep.grep(self.text, self.io, self.flist)
Steven M. Gavac5976402002-01-04 03:06:08 +0000666 return "break"
667
668 def replace_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400669 replace.replace(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000670 return "break"
671
672 def goto_line_event(self, event):
673 text = self.text
674 lineno = tkSimpleDialog.askinteger("Goto",
675 "Go to line number:",parent=text)
676 if lineno is None:
677 return "break"
678 if lineno <= 0:
679 text.bell()
680 return "break"
Terry Jan Reedy2522db12020-03-08 14:32:42 -0400681
682 text.tag_remove("sel", "1.0", "end")
683 text.mark_set("insert", f'{lineno}.0')
Steven M. Gavac5976402002-01-04 03:06:08 +0000684 text.see("insert")
Terry Jan Reedy2522db12020-03-08 14:32:42 -0400685 self.set_line_and_column()
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300686 return "break"
Steven M. Gavac5976402002-01-04 03:06:08 +0000687
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300688 def open_module(self):
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400689 """Get module name from user and open it.
690
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400691 Return module path or None for calls by open_module_browser
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400692 when latter is not invoked in named editor window.
693 """
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400694 # XXX This, open_module_browser, and open_path_browser
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400695 # would fit better in iomenu.IOBinding.
David Scherer7aced172000-08-15 01:13:23 +0000696 try:
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400697 name = self.text.get("sel.first", "sel.last").strip()
David Scherer7aced172000-08-15 01:13:23 +0000698 except TclError:
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400699 name = ''
700 file_path = query.ModuleName(
701 self.text, "Open Module",
702 "Enter the name of a Python module\n"
703 "to search on sys.path and open:",
704 name).result
705 if file_path is not None:
706 if self.flist:
707 self.flist.open(file_path)
708 else:
709 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400710 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000711
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300712 def open_module_event(self, event):
713 self.open_module()
714 return "break"
715
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400716 def open_module_browser(self, event=None):
David Scherer7aced172000-08-15 01:13:23 +0000717 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400718 if not (self.__class__.__name__ == 'PyShellEditorWindow'
719 and filename):
720 filename = self.open_module()
721 if filename is None:
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300722 return "break"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400723 from idlelib import browser
Terry Jan Reedyd6bb65f2017-09-30 19:54:28 -0400724 browser.ModuleBrowser(self.root, filename)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300725 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000726
727 def open_path_browser(self, event=None):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400728 from idlelib import pathbrowser
Cheryl Sabella20d48a42017-11-22 19:05:25 -0500729 pathbrowser.PathBrowser(self.root)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300730 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000731
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400732 def open_turtle_demo(self, event = None):
733 import subprocess
734
735 cmd = [sys.executable,
736 '-c',
737 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400738 subprocess.Popen(cmd, shell=False)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300739 return "break"
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400740
David Scherer7aced172000-08-15 01:13:23 +0000741 def gotoline(self, lineno):
742 if lineno is not None and lineno > 0:
743 self.text.mark_set("insert", "%d.0" % lineno)
744 self.text.tag_remove("sel", "1.0", "end")
745 self.text.tag_add("sel", "insert", "insert +1l")
746 self.center()
747
748 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000749 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000750 return True
David Scherer7aced172000-08-15 01:13:23 +0000751 base, ext = os.path.splitext(os.path.basename(filename))
752 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000753 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000754 line = self.text.get('1.0', '1.0 lineend')
755 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000756
757 def close_hook(self):
758 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000759 self.flist.unregister_maybe_terminate(self)
760 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000761
762 def set_close_hook(self, close_hook):
763 self.close_hook = close_hook
764
765 def filename_change_hook(self):
766 if self.flist:
767 self.flist.filename_changed_edit(self)
768 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000769 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000770 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000771
Christian Heimesa156e092008-02-16 07:38:31 +0000772 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000773 if self.color:
774 return
Christian Heimesa156e092008-02-16 07:38:31 +0000775 if self.ispythonsource(self.io.filename):
776 self.color = self.ColorDelegator()
777 # can add more colorizers here...
778 if self.color:
779 self.per.removefilter(self.undo)
780 self.per.insertfilter(self.color)
781 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000782
Christian Heimesa156e092008-02-16 07:38:31 +0000783 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000784 if not self.color:
785 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000786 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000787 self.per.removefilter(self.color)
788 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000789
Steven M. Gavab77d3432002-03-02 07:16:21 +0000790 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400791 "Update the color theme"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400792 # Called from self.filename_change_hook and from configdialog.py
Christian Heimesa156e092008-02-16 07:38:31 +0000793 self._rmcolorizer()
794 self._addcolorizer()
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -0400795 EditorWindow.color_config(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000796
Tal Einat7123ea02019-07-23 15:22:11 +0300797 if self.code_context is not None:
798 self.code_context.update_highlight_colors()
799
800 if self.line_numbers is not None:
801 self.line_numbers.update_colors()
Tal Einat7036e1d2019-07-17 11:15:53 +0300802
Guido van Rossum33d26892007-08-05 15:29:28 +0000803 IDENTCHARS = string.ascii_letters + string.digits + "_"
804
805 def colorize_syntax_error(self, text, pos):
806 text.tag_add("ERROR", pos)
807 char = text.get(pos)
808 if char and char in self.IDENTCHARS:
809 text.tag_add("ERROR", pos + " wordstart", pos)
810 if '\n' == text.get(pos): # error at line end
811 text.mark_set("insert", pos)
812 else:
813 text.mark_set("insert", pos + "+1c")
814 text.see(pos)
815
Zackery Spytz9c284492019-11-13 00:13:33 -0700816 def update_cursor_blink(self):
817 "Update the cursor blink configuration."
818 cursorblink = idleConf.GetOption(
819 'main', 'EditorWindow', 'cursor-blink', type='bool')
820 if not cursorblink:
821 self.text['insertofftime'] = 0
822 else:
823 # Restore the original value
824 self.text['insertofftime'] = idleConf.blink_off_time
825
Steven M. Gavab1585412002-03-12 00:21:56 +0000826 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000827 "Update the text widgets' font if it is changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400828 # Called from configdialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400829
Tal Einat7036e1d2019-07-17 11:15:53 +0300830 # Update the code context widget first, since its height affects
831 # the height of the text widget. This avoids double re-rendering.
Tal Einat7123ea02019-07-23 15:22:11 +0300832 if self.code_context is not None:
833 self.code_context.update_font()
834 # Next, update the line numbers widget, since its width affects
835 # the width of the text widget.
836 if self.line_numbers is not None:
837 self.line_numbers.update_font()
838 # Finally, update the main text widget.
839 new_font = idleConf.GetFont(self.root, 'main', 'EditorWindow')
Tal Einat7036e1d2019-07-17 11:15:53 +0300840 self.text['font'] = new_font
Tal Einatd4b4c002019-08-25 08:52:58 +0300841 self.set_width()
Steven M. Gavab1585412002-03-12 00:21:56 +0000842
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000843 def RemoveKeybindings(self):
844 "Remove the keybindings before they are changed."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400845 # Called from configdialog.py
846 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000847 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000848 self.text.event_delete(event, *keylist)
849 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000850 xkeydefs = idleConf.GetExtensionBindings(extensionName)
851 if xkeydefs:
852 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000853 self.text.event_delete(event, *keylist)
854
855 def ApplyKeybindings(self):
856 "Update the keybindings after they are changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400857 # Called from configdialog.py
858 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000859 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000860 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000861 xkeydefs = idleConf.GetExtensionBindings(extensionName)
862 if xkeydefs:
863 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000864 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000865 menuEventDict = {}
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400866 for menu in self.mainmenu.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000867 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000868 for item in menu[1]:
869 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000870 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000871 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000872 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700873 end = menu.index(END)
874 if end is None:
875 # Skip empty menus
876 continue
877 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000878 for index in range(0, end):
879 if menu.type(index) == 'command':
880 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000881 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000882 itemName = menu.entrycget(index, 'label')
883 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000884 if menubarItem in menuEventDict:
885 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000886 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000887 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000888 accel = get_accelerator(keydefs, event)
889 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000890
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000891 def set_notabs_indentwidth(self):
892 "Update the indentwidth if changed and not using tabs in this window"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400893 # Called from configdialog.py
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000894 if not self.usetabs:
895 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
896 type='int')
897
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000898 def reset_help_menu_entries(self):
899 "Update the additional help entries on the Help menu"
900 help_list = idleConf.GetAllExtraHelpSourcesList()
901 helpmenu = self.menudict['help']
902 # first delete the extra help entries, if any
903 helpmenu_length = helpmenu.index(END)
904 if helpmenu_length > self.base_helpmenu_length:
905 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
906 # then rebuild them
907 if help_list:
908 helpmenu.add_separator()
909 for entry in help_list:
910 cmd = self.__extra_help_callback(entry[1])
911 helpmenu.add_command(label=entry[0], command=cmd)
912 # and update the menu dictionary
913 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000914
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000915 def __extra_help_callback(self, helpfile):
916 "Create a callback with the helpfile value frozen at definition time"
917 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000918 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000919 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000920 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000921 try:
922 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200923 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000924 tkMessageBox.showerror(title='Document Start Failure',
925 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000926 else:
927 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000928 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000929
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000930 def update_recent_files_list(self, new_file=None):
931 "Load and update the recent files list and menus"
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400932 # TODO: move to iomenu.
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000933 rf_list = []
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400934 file_path = self.recent_files_path
935 if file_path and os.path.exists(file_path):
936 with open(file_path, 'r',
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400937 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000938 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000939 if new_file:
940 new_file = os.path.abspath(new_file) + '\n'
941 if new_file in rf_list:
942 rf_list.remove(new_file) # move to top
943 rf_list.insert(0, new_file)
944 # clean and save the recent files list
945 bad_paths = []
946 for path in rf_list:
947 if '\0' in path or not os.path.exists(path[0:-1]):
948 bad_paths.append(path)
949 rf_list = [path for path in rf_list if path not in bad_paths]
950 ulchars = "1234567890ABCDEFGHIJK"
951 rf_list = rf_list[0:len(ulchars)]
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400952 if file_path:
953 try:
954 with open(file_path, 'w',
955 encoding='utf_8', errors='replace') as rf_file:
956 rf_file.writelines(rf_list)
957 except OSError as err:
958 if not getattr(self.root, "recentfiles_message", False):
959 self.root.recentfiles_message = True
960 tkMessageBox.showwarning(title='IDLE Warning',
961 message="Cannot save Recent Files list to disk.\n"
962 f" {err}\n"
963 "Select OK to continue.",
964 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000965 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000966 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000967 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700968 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000969 for i, file_name in enumerate(rf_list):
970 file_name = file_name.rstrip() # zap \n
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000971 callback = instance.__recent_file_callback(file_name)
Serhiy Storchaka06cb94b2019-10-04 13:09:52 +0300972 menu.add_command(label=ulchars[i] + " " + file_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000973 command=callback,
974 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000975
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000976 def __recent_file_callback(self, file_name):
977 def open_recent_file(fn_closure=file_name):
978 self.io.open(editFile=fn_closure)
979 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000980
David Scherer7aced172000-08-15 01:13:23 +0000981 def saved_change_hook(self):
982 short = self.short_title()
983 long = self.long_title()
984 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400985 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000986 elif short:
987 title = short
988 elif long:
989 title = long
990 else:
Terry Jan Reedya9022392019-01-18 02:09:53 -0500991 title = "untitled"
David Scherer7aced172000-08-15 01:13:23 +0000992 icon = short or long or title
993 if not self.get_saved():
994 title = "*%s*" % title
995 icon = "*%s" % icon
996 self.top.wm_title(title)
997 self.top.wm_iconname(icon)
998
999 def get_saved(self):
1000 return self.undo.get_saved()
1001
1002 def set_saved(self, flag):
1003 self.undo.set_saved(flag)
1004
1005 def reset_undo(self):
1006 self.undo.reset_undo()
1007
1008 def short_title(self):
1009 filename = self.io.filename
Serhiy Storchaka06cb94b2019-10-04 13:09:52 +03001010 return os.path.basename(filename) if filename else "untitled"
David Scherer7aced172000-08-15 01:13:23 +00001011
1012 def long_title(self):
Serhiy Storchaka06cb94b2019-10-04 13:09:52 +03001013 return self.io.filename or ""
David Scherer7aced172000-08-15 01:13:23 +00001014
1015 def center_insert_event(self, event):
1016 self.center()
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001017 return "break"
David Scherer7aced172000-08-15 01:13:23 +00001018
1019 def center(self, mark="insert"):
1020 text = self.text
1021 top, bot = self.getwindowlines()
1022 lineno = self.getlineno(mark)
1023 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001024 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +00001025 text.yview(float(newtop))
1026
1027 def getwindowlines(self):
1028 text = self.text
1029 top = self.getlineno("@0,0")
1030 bot = self.getlineno("@0,65535")
1031 if top == bot and text.winfo_height() == 1:
1032 # Geometry manager hasn't run yet
1033 height = int(text['height'])
1034 bot = top + height - 1
1035 return top, bot
1036
1037 def getlineno(self, mark="insert"):
1038 text = self.text
1039 return int(float(text.index(mark)))
1040
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001041 def get_geometry(self):
1042 "Return (width, height, x, y)"
1043 geom = self.top.wm_geometry()
1044 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +00001045 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001046
David Scherer7aced172000-08-15 01:13:23 +00001047 def close_event(self, event):
1048 self.close()
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001049 return "break"
David Scherer7aced172000-08-15 01:13:23 +00001050
1051 def maybesave(self):
1052 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001053 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001054 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001055 self.top.deiconify()
1056 self.top.lower()
1057 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001058 return self.io.maybesave()
1059
1060 def close(self):
Terry Jan Reedydfd34a92019-09-17 02:05:04 -04001061 try:
1062 reply = self.maybesave()
1063 if str(reply) != "cancel":
1064 self._close()
1065 return reply
1066 except AttributeError: # bpo-35379: close called twice
1067 pass
David Scherer7aced172000-08-15 01:13:23 +00001068
1069 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001070 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001071 self.update_recent_files_list(new_file=self.io.filename)
Terry Jan Reedya361e892018-06-20 21:25:59 -04001072 window.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001073 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001074 self.io.close()
1075 self.io = None
1076 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001077 if self.color:
Cheryl Sabellab9f03542019-03-01 05:19:40 -05001078 self.color.close()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001079 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001080 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001081 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001082 self.per.close()
1083 self.per = None
1084 self.top.destroy()
1085 if self.close_hook:
1086 # unless override: unregister from flist, terminate if last window
1087 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001088
1089 def load_extensions(self):
1090 self.extensions = {}
1091 self.load_standard_extensions()
1092
1093 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001094 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001095 if hasattr(ins, "close"):
1096 ins.close()
1097 self.extensions = {}
1098
1099 def load_standard_extensions(self):
1100 for name in self.get_standard_extension_names():
1101 try:
1102 self.load_extension(name)
1103 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001104 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001105 traceback.print_exc()
1106
1107 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001108 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001109
wohlganger58fc71c2017-09-10 16:19:47 -05001110 extfiles = { # Map built-in config-extension section names to file names.
1111 'ZzDummy': 'zzdummy',
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001112 }
1113
David Scherer7aced172000-08-15 01:13:23 +00001114 def load_extension(self, name):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001115 fname = self.extfiles.get(name, name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001116 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001117 try:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001118 mod = importlib.import_module('.' + fname, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001119 except (ImportError, TypeError):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001120 mod = importlib.import_module(fname)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001121 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001122 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001123 raise
David Scherer7aced172000-08-15 01:13:23 +00001124 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001125 keydefs = idleConf.GetExtensionBindings(name)
1126 if hasattr(cls, "menudefs"):
1127 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001128 ins = cls(self)
1129 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001130 if keydefs:
1131 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001132 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001133 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001134 while methodname[:1] == '<':
1135 methodname = methodname[1:]
1136 while methodname[-1:] == '>':
1137 methodname = methodname[:-1]
1138 methodname = methodname + "_event"
1139 if hasattr(ins, methodname):
1140 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001141
1142 def apply_bindings(self, keydefs=None):
1143 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001144 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001145 text = self.text
1146 text.keydefs = keydefs
1147 for event, keylist in keydefs.items():
1148 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001149 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001150
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001151 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001152 """Add appropriate entries to the menus and submenus
1153
1154 Menus that are absent or None in self.menudict are ignored.
1155 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001156 if menudefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001157 menudefs = self.mainmenu.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001158 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001159 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001160 menudict = self.menudict
1161 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001162 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001163 menu = menudict.get(mname)
1164 if not menu:
1165 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001166 for entry in entrylist:
1167 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001168 menu.add_separator()
1169 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001170 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001171 checkbutton = (label[:1] == '!')
1172 if checkbutton:
1173 label = label[1:]
1174 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001175 accelerator = get_accelerator(keydefs, eventname)
1176 def command(text=text, eventname=eventname):
1177 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001178 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001179 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001180 menu.add_checkbutton(label=label, underline=underline,
1181 command=command, accelerator=accelerator,
1182 variable=var)
1183 else:
1184 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001185 command=command,
1186 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001187
1188 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001189 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001190 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001191 value = var.get()
1192 return value
1193 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001194 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001195
1196 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001197 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001198 if var:
1199 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001200 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001201 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001202
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001203 def get_var_obj(self, name, vartype=None):
1204 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001205 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001206 # create a Tkinter variable object with self.text as master:
1207 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001208 return var
1209
1210 # Tk implementations of "virtual text methods" -- each platform
1211 # reusing IDLE's support code needs to define these for its GUI's
1212 # flavor of widget.
1213
1214 # Is character at text_index in a Python string? Return 0 for
1215 # "guaranteed no", true for anything else. This info is expensive
1216 # to compute ab initio, but is probably already known by the
1217 # platform's colorizer.
1218
1219 def is_char_in_string(self, text_index):
1220 if self.color:
1221 # Return true iff colorizer hasn't (re)gotten this far
1222 # yet, or the character is tagged as being in a string
1223 return self.text.tag_prevrange("TODO", text_index) or \
1224 "STRING" in self.text.tag_names(text_index)
1225 else:
1226 # The colorizer is missing: assume the worst
1227 return 1
1228
1229 # If a selection is defined in the text widget, return (start,
1230 # end) as Tkinter text indices, otherwise return (None, None)
1231 def get_selection_indices(self):
1232 try:
1233 first = self.text.index("sel.first")
1234 last = self.text.index("sel.last")
1235 return first, last
1236 except TclError:
1237 return None, None
1238
1239 # Return the text widget's current view of what a tab stop means
1240 # (equivalent width in spaces).
1241
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001242 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001243 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1244 return int(current)
1245
1246 # Set the text widget's current view of what a tab stop means.
1247
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001248 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001249 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001250 if self.get_tk_tabwidth() != newtabwidth:
1251 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001252 pixels = text.tk.call("font", "measure", text["font"],
1253 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001254 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001255 text.configure(tabs=pixels)
1256
Guido van Rossum33d26892007-08-05 15:29:28 +00001257### begin autoindent code ### (configuration was moved to beginning of class)
1258
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001259 def set_indentation_params(self, is_py_src, guess=True):
1260 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001261 i = self.guess_indent()
1262 if 2 <= i <= 8:
1263 self.indentwidth = i
1264 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001265 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001266 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001267
1268 def smart_backspace_event(self, event):
1269 text = self.text
1270 first, last = self.get_selection_indices()
1271 if first and last:
1272 text.delete(first, last)
1273 text.mark_set("insert", first)
1274 return "break"
1275 # Delete whitespace left, until hitting a real char or closest
1276 # preceding virtual tab stop.
1277 chars = text.get("insert linestart", "insert")
1278 if chars == '':
1279 if text.compare("insert", ">", "1.0"):
1280 # easy: delete preceding newline
1281 text.delete("insert-1c")
1282 else:
1283 text.bell() # at start of buffer
1284 return "break"
1285 if chars[-1] not in " \t":
1286 # easy: delete preceding real char
1287 text.delete("insert-1c")
1288 return "break"
1289 # Ick. It may require *inserting* spaces if we back up over a
1290 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001291 tabwidth = self.tabwidth
1292 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001293 assert have > 0
1294 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001295 # Debug prompt is multilined....
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001296 ncharsdeleted = 0
1297 while 1:
Terry Jan Reedye86172d2017-10-27 20:26:12 -04001298 if chars == self.prompt_last_line: # '' unless PyShell
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001299 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001300 chars = chars[:-1]
1301 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001302 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001303 if have <= want or chars[-1] not in " \t":
1304 break
1305 text.undo_block_start()
1306 text.delete("insert-%dc" % ncharsdeleted, "insert")
1307 if have < want:
1308 text.insert("insert", ' ' * (want - have))
1309 text.undo_block_stop()
1310 return "break"
1311
1312 def smart_indent_event(self, event):
1313 # if intraline selection:
1314 # delete it
1315 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001316 # do indent-region
1317 # else:
1318 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001319 text = self.text
1320 first, last = self.get_selection_indices()
1321 text.undo_block_start()
1322 try:
1323 if first and last:
1324 if index2line(first) != index2line(last):
Cheryl Sabella82494aa2019-07-17 09:44:44 -04001325 return self.fregion.indent_region_event(event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001326 text.delete(first, last)
1327 text.mark_set("insert", first)
1328 prefix = text.get("insert linestart", "insert")
Tal Einat9b5ce622019-07-11 17:20:14 +03001329 raw, effective = get_line_indent(prefix, self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001330 if raw == len(prefix):
1331 # only whitespace to the left
1332 self.reindent_to(effective + self.indentwidth)
1333 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001334 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001335 if self.usetabs:
1336 pad = '\t'
1337 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001338 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001339 n = self.indentwidth
1340 pad = ' ' * (n - effective % n)
1341 text.insert("insert", pad)
1342 text.see("insert")
1343 return "break"
1344 finally:
1345 text.undo_block_stop()
1346
1347 def newline_and_indent_event(self, event):
Cheryl Sabellaec646402020-01-21 05:11:26 -05001348 """Insert a newline and indentation after Enter keypress event.
1349
1350 Properly position the cursor on the new line based on information
1351 from the current line. This takes into account if the current line
1352 is a shell prompt, is empty, has selected text, contains a block
1353 opener, contains a block closer, is a continuation line, or
1354 is inside a string.
1355 """
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001356 text = self.text
1357 first, last = self.get_selection_indices()
1358 text.undo_block_start()
Cheryl Sabellaec646402020-01-21 05:11:26 -05001359 try: # Close undo block and expose new line in finally clause.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001360 if first and last:
1361 text.delete(first, last)
1362 text.mark_set("insert", first)
1363 line = text.get("insert linestart", "insert")
Cheryl Sabellaec646402020-01-21 05:11:26 -05001364
1365 # Count leading whitespace for indent size.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001366 i, n = 0, len(line)
1367 while i < n and line[i] in " \t":
Cheryl Sabellaec646402020-01-21 05:11:26 -05001368 i += 1
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001369 if i == n:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001370 # The cursor is in or at leading indentation in a continuation
1371 # line; just inject an empty line at the start.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001372 text.insert("insert linestart", '\n')
1373 return "break"
1374 indent = line[:i]
Cheryl Sabellaec646402020-01-21 05:11:26 -05001375
1376 # Strip whitespace before insert point unless it's in the prompt.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001377 i = 0
Terry Jan Reedye86172d2017-10-27 20:26:12 -04001378 while line and line[-1] in " \t" and line != self.prompt_last_line:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001379 line = line[:-1]
Cheryl Sabellaec646402020-01-21 05:11:26 -05001380 i += 1
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001381 if i:
1382 text.delete("insert - %d chars" % i, "insert")
Cheryl Sabellaec646402020-01-21 05:11:26 -05001383
1384 # Strip whitespace after insert point.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001385 while text.get("insert") in " \t":
1386 text.delete("insert")
Cheryl Sabellaec646402020-01-21 05:11:26 -05001387
1388 # Insert new line.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001389 text.insert("insert", '\n')
1390
Cheryl Sabellaec646402020-01-21 05:11:26 -05001391 # Adjust indentation for continuations and block open/close.
1392 # First need to find the last statement.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001393 lno = index2line(text.index('insert'))
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001394 y = pyparse.Parser(self.indentwidth, self.tabwidth)
Cheryl Sabella6bdc4de2019-06-02 14:56:47 -04001395 if not self.prompt_last_line:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001396 for context in self.num_context_lines:
1397 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001398 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001399 rawtext = text.get(startatindex, "insert")
Cheryl Sabellac29c03a2018-02-23 21:35:27 -05001400 y.set_code(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001401 bod = y.find_good_parse_start(
Cheryl Sabellaec646402020-01-21 05:11:26 -05001402 self._build_char_in_string_func(startatindex))
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001403 if bod is not None or startat == 1:
1404 break
1405 y.set_lo(bod or 0)
1406 else:
1407 r = text.tag_prevrange("console", "insert")
1408 if r:
1409 startatindex = r[1]
1410 else:
1411 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001412 rawtext = text.get(startatindex, "insert")
Cheryl Sabellac29c03a2018-02-23 21:35:27 -05001413 y.set_code(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001414 y.set_lo(0)
1415
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001416 c = y.get_continuation_type()
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001417 if c != pyparse.C_NONE:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001418 # The current statement hasn't ended yet.
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001419 if c == pyparse.C_STRING_FIRST_LINE:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001420 # After the first line of a string do not indent at all.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001421 pass
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001422 elif c == pyparse.C_STRING_NEXT_LINES:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001423 # Inside a string which started before this line;
1424 # just mimic the current indent.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001425 text.insert("insert", indent)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001426 elif c == pyparse.C_BRACKET:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001427 # Line up with the first (if any) element of the
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001428 # last open bracket structure; else indent one
1429 # level beyond the indent of the line with the
Cheryl Sabellaec646402020-01-21 05:11:26 -05001430 # last open bracket.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001431 self.reindent_to(y.compute_bracket_indent())
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001432 elif c == pyparse.C_BACKSLASH:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001433 # If more than one line in this statement already, just
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001434 # mimic the current indent; else if initial line
1435 # has a start on an assignment stmt, indent to
1436 # beyond leftmost =; else to beyond first chunk of
Cheryl Sabellaec646402020-01-21 05:11:26 -05001437 # non-whitespace on initial line.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001438 if y.get_num_lines_in_stmt() > 1:
1439 text.insert("insert", indent)
1440 else:
1441 self.reindent_to(y.compute_backslash_indent())
1442 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001443 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001444 return "break"
1445
Cheryl Sabellaec646402020-01-21 05:11:26 -05001446 # This line starts a brand new statement; indent relative to
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001447 # indentation of initial line of closest preceding
Cheryl Sabellaec646402020-01-21 05:11:26 -05001448 # interesting statement.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001449 indent = y.get_base_indent_string()
1450 text.insert("insert", indent)
1451 if y.is_block_opener():
1452 self.smart_indent_event(event)
1453 elif indent and y.is_block_closer():
1454 self.smart_backspace_event(event)
1455 return "break"
1456 finally:
1457 text.see("insert")
1458 text.undo_block_stop()
1459
Martin Panter7462b6492015-11-02 03:37:02 +00001460 # Our editwin provides an is_char_in_string function that works
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001461 # with a Tk text index, but PyParse only knows about offsets into
1462 # a string. This builds a function for PyParse that accepts an
1463 # offset.
1464
1465 def _build_char_in_string_func(self, startindex):
1466 def inner(offset, _startindex=startindex,
1467 _icis=self.is_char_in_string):
1468 return _icis(_startindex + "+%dc" % offset)
1469 return inner
1470
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001471 # XXX this isn't bound to anything -- see tabwidth comments
1472## def change_tabwidth_event(self, event):
1473## new = self._asktabwidth()
1474## if new != self.tabwidth:
1475## self.tabwidth = new
1476## self.set_indentation_params(0, guess=0)
1477## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001478
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001479 # Make string that displays as n leading blanks.
1480
1481 def _make_blanks(self, n):
1482 if self.usetabs:
1483 ntabs, nspaces = divmod(n, self.tabwidth)
1484 return '\t' * ntabs + ' ' * nspaces
1485 else:
1486 return ' ' * n
1487
1488 # Delete from beginning of line to insert point, then reinsert
1489 # column logical (meaning use tabs if appropriate) spaces.
1490
1491 def reindent_to(self, column):
1492 text = self.text
1493 text.undo_block_start()
1494 if text.compare("insert linestart", "!=", "insert"):
1495 text.delete("insert linestart", "insert")
1496 if column:
1497 text.insert("insert", self._make_blanks(column))
1498 text.undo_block_stop()
1499
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001500 # Guess indentwidth from text content.
1501 # Return guessed indentwidth. This should not be believed unless
1502 # it's in a reasonable range (e.g., it will be 0 if no indented
1503 # blocks are found).
1504
1505 def guess_indent(self):
1506 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1507 if opener and indented:
Tal Einat9b5ce622019-07-11 17:20:14 +03001508 raw, indentsmall = get_line_indent(opener, self.tabwidth)
1509 raw, indentlarge = get_line_indent(indented, self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001510 else:
1511 indentsmall = indentlarge = 0
1512 return indentlarge - indentsmall
1513
Tal Einat7123ea02019-07-23 15:22:11 +03001514 def toggle_line_numbers_event(self, event=None):
1515 if self.line_numbers is None:
1516 return
1517
1518 if self.line_numbers.is_shown:
1519 self.line_numbers.hide_sidebar()
1520 menu_label = "Show"
1521 else:
1522 self.line_numbers.show_sidebar()
1523 menu_label = "Hide"
1524 self.update_menu_label(menu='options', index='*Line Numbers',
1525 label=f'{menu_label} Line Numbers')
1526
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001527# "line.col" -> line, as an int
1528def index2line(index):
1529 return int(float(index))
1530
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001531
Tal Einat9b5ce622019-07-11 17:20:14 +03001532_line_indent_re = re.compile(r'[ \t]*')
1533def get_line_indent(line, tabwidth):
1534 """Return a line's indentation as (# chars, effective # of spaces).
1535
1536 The effective # of spaces is the length after properly "expanding"
1537 the tabs into spaces, as done by str.expandtabs(tabwidth).
1538 """
1539 m = _line_indent_re.match(line)
1540 return m.end(), len(m.group().expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001541
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001542
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001543class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001544
1545 # .run() chews over the Text widget, looking for a block opener
1546 # and the stmt following it. Returns a pair,
1547 # (line containing block opener, line containing stmt)
1548 # Either or both may be None.
1549
1550 def __init__(self, text, tabwidth):
1551 self.text = text
1552 self.tabwidth = tabwidth
1553 self.i = self.finished = 0
1554 self.blkopenline = self.indentedline = None
1555
1556 def readline(self):
1557 if self.finished:
1558 return ""
1559 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001560 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001561 if self.text.compare(mark, ">=", "end"):
1562 return ""
1563 return self.text.get(mark, mark + " lineend+1c")
1564
1565 def tokeneater(self, type, token, start, end, line,
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001566 INDENT=tokenize.INDENT,
1567 NAME=tokenize.NAME,
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001568 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1569 if self.finished:
1570 pass
1571 elif type == NAME and token in OPENERS:
1572 self.blkopenline = line
1573 elif type == INDENT and self.blkopenline:
1574 self.indentedline = line
1575 self.finished = 1
1576
1577 def run(self):
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001578 save_tabsize = tokenize.tabsize
1579 tokenize.tabsize = self.tabwidth
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001580 try:
1581 try:
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001582 tokens = tokenize.generate_tokens(self.readline)
Trent Nelson428de652008-03-18 22:41:35 +00001583 for token in tokens:
1584 self.tokeneater(*token)
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001585 except (tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001586 # since we cut off the tokenizer early, we can trigger
1587 # spurious errors
1588 pass
1589 finally:
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001590 tokenize.tabsize = save_tabsize
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001591 return self.blkopenline, self.indentedline
1592
1593### end autoindent code ###
1594
David Scherer7aced172000-08-15 01:13:23 +00001595def prepstr(s):
1596 # Helper to extract the underscore from a string, e.g.
1597 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001598 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001599 if i >= 0:
1600 s = s[:i] + s[i+1:]
1601 return i, s
1602
1603
1604keynames = {
1605 'bracketleft': '[',
1606 'bracketright': ']',
1607 'slash': '/',
1608}
1609
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001610def get_accelerator(keydefs, eventname):
1611 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001612 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1613 # if not keylist:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001614 if (not keylist) or (macosx.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001615 "<<open-module>>",
1616 "<<goto-line>>",
1617 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001618 return ""
1619 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001620 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001621 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1622 s = re.sub("Key-", "", s)
1623 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1624 s = re.sub("Control-", "Ctrl-", s)
1625 s = re.sub("-", "+", s)
1626 s = re.sub("><", " ", s)
1627 s = re.sub("<", "", s)
1628 s = re.sub(">", "", s)
1629 return s
1630
1631
1632def fixwordbreaks(root):
Terry Jan Reedy5ff3a162018-04-30 03:08:01 -04001633 # On Windows, tcl/tk breaks 'words' only on spaces, as in Command Prompt.
1634 # We want Motif style everywhere. See #21474, msg218992 and followup.
David Scherer7aced172000-08-15 01:13:23 +00001635 tk = root.tk
1636 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
Terry Jan Reedy5ff3a162018-04-30 03:08:01 -04001637 tk.call('set', 'tcl_wordchars', r'\w')
1638 tk.call('set', 'tcl_nonwordchars', r'\W')
David Scherer7aced172000-08-15 01:13:23 +00001639
1640
Terry Jan Reedycd567362014-10-17 01:31:35 -04001641def _editor_window(parent): # htest #
1642 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001643 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001644 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001645 if sys.argv[1:]:
1646 filename = sys.argv[1]
1647 else:
1648 filename = None
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001649 macosx.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001650 edit = EditorWindow(root=root, filename=filename)
Tal Einat077059e2018-08-10 09:02:08 +03001651 text = edit.text
1652 text['height'] = 10
1653 for i in range(20):
1654 text.insert('insert', ' '*i + str(i) + '\n')
1655 # text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001656 # Does not stop error, neither does following
1657 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001658
1659if __name__ == '__main__':
Terry Jan Reedyee5ef302018-06-15 18:20:55 -04001660 from unittest import main
1661 main('idlelib.idle_test.test_editor', verbosity=2, exit=False)
Terry Jan Reedy7c153412016-06-26 17:48:02 -04001662
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001663 from idlelib.idle_test.htest import run
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04001664 run(_editor_window)