blob: c9f1a1625ca5ea18823ce7ab1a62364b7ec1af1f [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)
Terry Jan Reedy06e20292018-06-19 23:00:35 -0400331 ctip = self.Calltip(self)
332 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"
681 text.mark_set("insert", "%d.0" % lineno)
682 text.see("insert")
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300683 return "break"
Steven M. Gavac5976402002-01-04 03:06:08 +0000684
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300685 def open_module(self):
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400686 """Get module name from user and open it.
687
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400688 Return module path or None for calls by open_module_browser
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400689 when latter is not invoked in named editor window.
690 """
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400691 # XXX This, open_module_browser, and open_path_browser
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400692 # would fit better in iomenu.IOBinding.
David Scherer7aced172000-08-15 01:13:23 +0000693 try:
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400694 name = self.text.get("sel.first", "sel.last").strip()
David Scherer7aced172000-08-15 01:13:23 +0000695 except TclError:
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400696 name = ''
697 file_path = query.ModuleName(
698 self.text, "Open Module",
699 "Enter the name of a Python module\n"
700 "to search on sys.path and open:",
701 name).result
702 if file_path is not None:
703 if self.flist:
704 self.flist.open(file_path)
705 else:
706 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400707 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000708
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300709 def open_module_event(self, event):
710 self.open_module()
711 return "break"
712
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400713 def open_module_browser(self, event=None):
David Scherer7aced172000-08-15 01:13:23 +0000714 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400715 if not (self.__class__.__name__ == 'PyShellEditorWindow'
716 and filename):
717 filename = self.open_module()
718 if filename is None:
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300719 return "break"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400720 from idlelib import browser
Terry Jan Reedyd6bb65f2017-09-30 19:54:28 -0400721 browser.ModuleBrowser(self.root, filename)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300722 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000723
724 def open_path_browser(self, event=None):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400725 from idlelib import pathbrowser
Cheryl Sabella20d48a42017-11-22 19:05:25 -0500726 pathbrowser.PathBrowser(self.root)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300727 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000728
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400729 def open_turtle_demo(self, event = None):
730 import subprocess
731
732 cmd = [sys.executable,
733 '-c',
734 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400735 subprocess.Popen(cmd, shell=False)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300736 return "break"
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400737
David Scherer7aced172000-08-15 01:13:23 +0000738 def gotoline(self, lineno):
739 if lineno is not None and lineno > 0:
740 self.text.mark_set("insert", "%d.0" % lineno)
741 self.text.tag_remove("sel", "1.0", "end")
742 self.text.tag_add("sel", "insert", "insert +1l")
743 self.center()
744
745 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000746 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000747 return True
David Scherer7aced172000-08-15 01:13:23 +0000748 base, ext = os.path.splitext(os.path.basename(filename))
749 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000750 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000751 line = self.text.get('1.0', '1.0 lineend')
752 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000753
754 def close_hook(self):
755 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000756 self.flist.unregister_maybe_terminate(self)
757 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000758
759 def set_close_hook(self, close_hook):
760 self.close_hook = close_hook
761
762 def filename_change_hook(self):
763 if self.flist:
764 self.flist.filename_changed_edit(self)
765 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000766 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000767 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000768
Christian Heimesa156e092008-02-16 07:38:31 +0000769 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000770 if self.color:
771 return
Christian Heimesa156e092008-02-16 07:38:31 +0000772 if self.ispythonsource(self.io.filename):
773 self.color = self.ColorDelegator()
774 # can add more colorizers here...
775 if self.color:
776 self.per.removefilter(self.undo)
777 self.per.insertfilter(self.color)
778 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000779
Christian Heimesa156e092008-02-16 07:38:31 +0000780 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000781 if not self.color:
782 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000783 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000784 self.per.removefilter(self.color)
785 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000786
Steven M. Gavab77d3432002-03-02 07:16:21 +0000787 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400788 "Update the color theme"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400789 # Called from self.filename_change_hook and from configdialog.py
Christian Heimesa156e092008-02-16 07:38:31 +0000790 self._rmcolorizer()
791 self._addcolorizer()
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -0400792 EditorWindow.color_config(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000793
Tal Einat7123ea02019-07-23 15:22:11 +0300794 if self.code_context is not None:
795 self.code_context.update_highlight_colors()
796
797 if self.line_numbers is not None:
798 self.line_numbers.update_colors()
Tal Einat7036e1d2019-07-17 11:15:53 +0300799
Guido van Rossum33d26892007-08-05 15:29:28 +0000800 IDENTCHARS = string.ascii_letters + string.digits + "_"
801
802 def colorize_syntax_error(self, text, pos):
803 text.tag_add("ERROR", pos)
804 char = text.get(pos)
805 if char and char in self.IDENTCHARS:
806 text.tag_add("ERROR", pos + " wordstart", pos)
807 if '\n' == text.get(pos): # error at line end
808 text.mark_set("insert", pos)
809 else:
810 text.mark_set("insert", pos + "+1c")
811 text.see(pos)
812
Zackery Spytz9c284492019-11-13 00:13:33 -0700813 def update_cursor_blink(self):
814 "Update the cursor blink configuration."
815 cursorblink = idleConf.GetOption(
816 'main', 'EditorWindow', 'cursor-blink', type='bool')
817 if not cursorblink:
818 self.text['insertofftime'] = 0
819 else:
820 # Restore the original value
821 self.text['insertofftime'] = idleConf.blink_off_time
822
Steven M. Gavab1585412002-03-12 00:21:56 +0000823 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000824 "Update the text widgets' font if it is changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400825 # Called from configdialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400826
Tal Einat7036e1d2019-07-17 11:15:53 +0300827 # Update the code context widget first, since its height affects
828 # the height of the text widget. This avoids double re-rendering.
Tal Einat7123ea02019-07-23 15:22:11 +0300829 if self.code_context is not None:
830 self.code_context.update_font()
831 # Next, update the line numbers widget, since its width affects
832 # the width of the text widget.
833 if self.line_numbers is not None:
834 self.line_numbers.update_font()
835 # Finally, update the main text widget.
836 new_font = idleConf.GetFont(self.root, 'main', 'EditorWindow')
Tal Einat7036e1d2019-07-17 11:15:53 +0300837 self.text['font'] = new_font
Tal Einatd4b4c002019-08-25 08:52:58 +0300838 self.set_width()
Steven M. Gavab1585412002-03-12 00:21:56 +0000839
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000840 def RemoveKeybindings(self):
841 "Remove the keybindings before they are changed."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400842 # Called from configdialog.py
843 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000844 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000845 self.text.event_delete(event, *keylist)
846 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000847 xkeydefs = idleConf.GetExtensionBindings(extensionName)
848 if xkeydefs:
849 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000850 self.text.event_delete(event, *keylist)
851
852 def ApplyKeybindings(self):
853 "Update the keybindings after they are changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400854 # Called from configdialog.py
855 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000856 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000857 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000858 xkeydefs = idleConf.GetExtensionBindings(extensionName)
859 if xkeydefs:
860 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000861 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000862 menuEventDict = {}
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400863 for menu in self.mainmenu.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000864 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000865 for item in menu[1]:
866 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000867 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000868 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000869 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700870 end = menu.index(END)
871 if end is None:
872 # Skip empty menus
873 continue
874 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000875 for index in range(0, end):
876 if menu.type(index) == 'command':
877 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000878 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000879 itemName = menu.entrycget(index, 'label')
880 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000881 if menubarItem in menuEventDict:
882 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000883 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000884 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000885 accel = get_accelerator(keydefs, event)
886 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000887
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000888 def set_notabs_indentwidth(self):
889 "Update the indentwidth if changed and not using tabs in this window"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400890 # Called from configdialog.py
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000891 if not self.usetabs:
892 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
893 type='int')
894
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000895 def reset_help_menu_entries(self):
896 "Update the additional help entries on the Help menu"
897 help_list = idleConf.GetAllExtraHelpSourcesList()
898 helpmenu = self.menudict['help']
899 # first delete the extra help entries, if any
900 helpmenu_length = helpmenu.index(END)
901 if helpmenu_length > self.base_helpmenu_length:
902 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
903 # then rebuild them
904 if help_list:
905 helpmenu.add_separator()
906 for entry in help_list:
907 cmd = self.__extra_help_callback(entry[1])
908 helpmenu.add_command(label=entry[0], command=cmd)
909 # and update the menu dictionary
910 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000911
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000912 def __extra_help_callback(self, helpfile):
913 "Create a callback with the helpfile value frozen at definition time"
914 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000915 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000916 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000917 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000918 try:
919 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200920 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000921 tkMessageBox.showerror(title='Document Start Failure',
922 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000923 else:
924 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000925 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000926
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000927 def update_recent_files_list(self, new_file=None):
928 "Load and update the recent files list and menus"
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400929 # TODO: move to iomenu.
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000930 rf_list = []
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400931 file_path = self.recent_files_path
932 if file_path and os.path.exists(file_path):
933 with open(file_path, 'r',
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400934 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000935 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000936 if new_file:
937 new_file = os.path.abspath(new_file) + '\n'
938 if new_file in rf_list:
939 rf_list.remove(new_file) # move to top
940 rf_list.insert(0, new_file)
941 # clean and save the recent files list
942 bad_paths = []
943 for path in rf_list:
944 if '\0' in path or not os.path.exists(path[0:-1]):
945 bad_paths.append(path)
946 rf_list = [path for path in rf_list if path not in bad_paths]
947 ulchars = "1234567890ABCDEFGHIJK"
948 rf_list = rf_list[0:len(ulchars)]
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400949 if file_path:
950 try:
951 with open(file_path, 'w',
952 encoding='utf_8', errors='replace') as rf_file:
953 rf_file.writelines(rf_list)
954 except OSError as err:
955 if not getattr(self.root, "recentfiles_message", False):
956 self.root.recentfiles_message = True
957 tkMessageBox.showwarning(title='IDLE Warning',
958 message="Cannot save Recent Files list to disk.\n"
959 f" {err}\n"
960 "Select OK to continue.",
961 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000962 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000963 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000964 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700965 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000966 for i, file_name in enumerate(rf_list):
967 file_name = file_name.rstrip() # zap \n
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000968 callback = instance.__recent_file_callback(file_name)
Serhiy Storchaka06cb94b2019-10-04 13:09:52 +0300969 menu.add_command(label=ulchars[i] + " " + file_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000970 command=callback,
971 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000972
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000973 def __recent_file_callback(self, file_name):
974 def open_recent_file(fn_closure=file_name):
975 self.io.open(editFile=fn_closure)
976 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000977
David Scherer7aced172000-08-15 01:13:23 +0000978 def saved_change_hook(self):
979 short = self.short_title()
980 long = self.long_title()
981 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400982 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000983 elif short:
984 title = short
985 elif long:
986 title = long
987 else:
Terry Jan Reedya9022392019-01-18 02:09:53 -0500988 title = "untitled"
David Scherer7aced172000-08-15 01:13:23 +0000989 icon = short or long or title
990 if not self.get_saved():
991 title = "*%s*" % title
992 icon = "*%s" % icon
993 self.top.wm_title(title)
994 self.top.wm_iconname(icon)
995
996 def get_saved(self):
997 return self.undo.get_saved()
998
999 def set_saved(self, flag):
1000 self.undo.set_saved(flag)
1001
1002 def reset_undo(self):
1003 self.undo.reset_undo()
1004
1005 def short_title(self):
1006 filename = self.io.filename
Serhiy Storchaka06cb94b2019-10-04 13:09:52 +03001007 return os.path.basename(filename) if filename else "untitled"
David Scherer7aced172000-08-15 01:13:23 +00001008
1009 def long_title(self):
Serhiy Storchaka06cb94b2019-10-04 13:09:52 +03001010 return self.io.filename or ""
David Scherer7aced172000-08-15 01:13:23 +00001011
1012 def center_insert_event(self, event):
1013 self.center()
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001014 return "break"
David Scherer7aced172000-08-15 01:13:23 +00001015
1016 def center(self, mark="insert"):
1017 text = self.text
1018 top, bot = self.getwindowlines()
1019 lineno = self.getlineno(mark)
1020 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001021 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +00001022 text.yview(float(newtop))
1023
1024 def getwindowlines(self):
1025 text = self.text
1026 top = self.getlineno("@0,0")
1027 bot = self.getlineno("@0,65535")
1028 if top == bot and text.winfo_height() == 1:
1029 # Geometry manager hasn't run yet
1030 height = int(text['height'])
1031 bot = top + height - 1
1032 return top, bot
1033
1034 def getlineno(self, mark="insert"):
1035 text = self.text
1036 return int(float(text.index(mark)))
1037
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001038 def get_geometry(self):
1039 "Return (width, height, x, y)"
1040 geom = self.top.wm_geometry()
1041 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +00001042 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001043
David Scherer7aced172000-08-15 01:13:23 +00001044 def close_event(self, event):
1045 self.close()
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001046 return "break"
David Scherer7aced172000-08-15 01:13:23 +00001047
1048 def maybesave(self):
1049 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001050 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001051 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001052 self.top.deiconify()
1053 self.top.lower()
1054 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001055 return self.io.maybesave()
1056
1057 def close(self):
Terry Jan Reedydfd34a92019-09-17 02:05:04 -04001058 try:
1059 reply = self.maybesave()
1060 if str(reply) != "cancel":
1061 self._close()
1062 return reply
1063 except AttributeError: # bpo-35379: close called twice
1064 pass
David Scherer7aced172000-08-15 01:13:23 +00001065
1066 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001067 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001068 self.update_recent_files_list(new_file=self.io.filename)
Terry Jan Reedya361e892018-06-20 21:25:59 -04001069 window.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001070 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001071 self.io.close()
1072 self.io = None
1073 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001074 if self.color:
Cheryl Sabellab9f03542019-03-01 05:19:40 -05001075 self.color.close()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001076 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001077 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001078 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001079 self.per.close()
1080 self.per = None
1081 self.top.destroy()
1082 if self.close_hook:
1083 # unless override: unregister from flist, terminate if last window
1084 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001085
1086 def load_extensions(self):
1087 self.extensions = {}
1088 self.load_standard_extensions()
1089
1090 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001091 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001092 if hasattr(ins, "close"):
1093 ins.close()
1094 self.extensions = {}
1095
1096 def load_standard_extensions(self):
1097 for name in self.get_standard_extension_names():
1098 try:
1099 self.load_extension(name)
1100 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001101 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001102 traceback.print_exc()
1103
1104 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001105 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001106
wohlganger58fc71c2017-09-10 16:19:47 -05001107 extfiles = { # Map built-in config-extension section names to file names.
1108 'ZzDummy': 'zzdummy',
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001109 }
1110
David Scherer7aced172000-08-15 01:13:23 +00001111 def load_extension(self, name):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001112 fname = self.extfiles.get(name, name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001113 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001114 try:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001115 mod = importlib.import_module('.' + fname, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001116 except (ImportError, TypeError):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001117 mod = importlib.import_module(fname)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001118 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001119 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001120 raise
David Scherer7aced172000-08-15 01:13:23 +00001121 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001122 keydefs = idleConf.GetExtensionBindings(name)
1123 if hasattr(cls, "menudefs"):
1124 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001125 ins = cls(self)
1126 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001127 if keydefs:
1128 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001129 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001130 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001131 while methodname[:1] == '<':
1132 methodname = methodname[1:]
1133 while methodname[-1:] == '>':
1134 methodname = methodname[:-1]
1135 methodname = methodname + "_event"
1136 if hasattr(ins, methodname):
1137 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001138
1139 def apply_bindings(self, keydefs=None):
1140 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001141 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001142 text = self.text
1143 text.keydefs = keydefs
1144 for event, keylist in keydefs.items():
1145 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001146 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001147
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001148 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001149 """Add appropriate entries to the menus and submenus
1150
1151 Menus that are absent or None in self.menudict are ignored.
1152 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001153 if menudefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001154 menudefs = self.mainmenu.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001155 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001156 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001157 menudict = self.menudict
1158 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001159 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001160 menu = menudict.get(mname)
1161 if not menu:
1162 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001163 for entry in entrylist:
1164 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001165 menu.add_separator()
1166 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001167 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001168 checkbutton = (label[:1] == '!')
1169 if checkbutton:
1170 label = label[1:]
1171 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001172 accelerator = get_accelerator(keydefs, eventname)
1173 def command(text=text, eventname=eventname):
1174 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001175 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001176 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001177 menu.add_checkbutton(label=label, underline=underline,
1178 command=command, accelerator=accelerator,
1179 variable=var)
1180 else:
1181 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001182 command=command,
1183 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001184
1185 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001186 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001187 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001188 value = var.get()
1189 return value
1190 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001191 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001192
1193 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001194 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001195 if var:
1196 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001197 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001198 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001199
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001200 def get_var_obj(self, name, vartype=None):
1201 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001202 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001203 # create a Tkinter variable object with self.text as master:
1204 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001205 return var
1206
1207 # Tk implementations of "virtual text methods" -- each platform
1208 # reusing IDLE's support code needs to define these for its GUI's
1209 # flavor of widget.
1210
1211 # Is character at text_index in a Python string? Return 0 for
1212 # "guaranteed no", true for anything else. This info is expensive
1213 # to compute ab initio, but is probably already known by the
1214 # platform's colorizer.
1215
1216 def is_char_in_string(self, text_index):
1217 if self.color:
1218 # Return true iff colorizer hasn't (re)gotten this far
1219 # yet, or the character is tagged as being in a string
1220 return self.text.tag_prevrange("TODO", text_index) or \
1221 "STRING" in self.text.tag_names(text_index)
1222 else:
1223 # The colorizer is missing: assume the worst
1224 return 1
1225
1226 # If a selection is defined in the text widget, return (start,
1227 # end) as Tkinter text indices, otherwise return (None, None)
1228 def get_selection_indices(self):
1229 try:
1230 first = self.text.index("sel.first")
1231 last = self.text.index("sel.last")
1232 return first, last
1233 except TclError:
1234 return None, None
1235
1236 # Return the text widget's current view of what a tab stop means
1237 # (equivalent width in spaces).
1238
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001239 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001240 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1241 return int(current)
1242
1243 # Set the text widget's current view of what a tab stop means.
1244
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001245 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001246 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001247 if self.get_tk_tabwidth() != newtabwidth:
1248 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001249 pixels = text.tk.call("font", "measure", text["font"],
1250 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001251 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001252 text.configure(tabs=pixels)
1253
Guido van Rossum33d26892007-08-05 15:29:28 +00001254### begin autoindent code ### (configuration was moved to beginning of class)
1255
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001256 def set_indentation_params(self, is_py_src, guess=True):
1257 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001258 i = self.guess_indent()
1259 if 2 <= i <= 8:
1260 self.indentwidth = i
1261 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001262 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001263 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001264
1265 def smart_backspace_event(self, event):
1266 text = self.text
1267 first, last = self.get_selection_indices()
1268 if first and last:
1269 text.delete(first, last)
1270 text.mark_set("insert", first)
1271 return "break"
1272 # Delete whitespace left, until hitting a real char or closest
1273 # preceding virtual tab stop.
1274 chars = text.get("insert linestart", "insert")
1275 if chars == '':
1276 if text.compare("insert", ">", "1.0"):
1277 # easy: delete preceding newline
1278 text.delete("insert-1c")
1279 else:
1280 text.bell() # at start of buffer
1281 return "break"
1282 if chars[-1] not in " \t":
1283 # easy: delete preceding real char
1284 text.delete("insert-1c")
1285 return "break"
1286 # Ick. It may require *inserting* spaces if we back up over a
1287 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001288 tabwidth = self.tabwidth
1289 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001290 assert have > 0
1291 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001292 # Debug prompt is multilined....
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001293 ncharsdeleted = 0
1294 while 1:
Terry Jan Reedye86172d2017-10-27 20:26:12 -04001295 if chars == self.prompt_last_line: # '' unless PyShell
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001296 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001297 chars = chars[:-1]
1298 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001299 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001300 if have <= want or chars[-1] not in " \t":
1301 break
1302 text.undo_block_start()
1303 text.delete("insert-%dc" % ncharsdeleted, "insert")
1304 if have < want:
1305 text.insert("insert", ' ' * (want - have))
1306 text.undo_block_stop()
1307 return "break"
1308
1309 def smart_indent_event(self, event):
1310 # if intraline selection:
1311 # delete it
1312 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001313 # do indent-region
1314 # else:
1315 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001316 text = self.text
1317 first, last = self.get_selection_indices()
1318 text.undo_block_start()
1319 try:
1320 if first and last:
1321 if index2line(first) != index2line(last):
Cheryl Sabella82494aa2019-07-17 09:44:44 -04001322 return self.fregion.indent_region_event(event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001323 text.delete(first, last)
1324 text.mark_set("insert", first)
1325 prefix = text.get("insert linestart", "insert")
Tal Einat9b5ce622019-07-11 17:20:14 +03001326 raw, effective = get_line_indent(prefix, self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001327 if raw == len(prefix):
1328 # only whitespace to the left
1329 self.reindent_to(effective + self.indentwidth)
1330 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001331 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001332 if self.usetabs:
1333 pad = '\t'
1334 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001335 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001336 n = self.indentwidth
1337 pad = ' ' * (n - effective % n)
1338 text.insert("insert", pad)
1339 text.see("insert")
1340 return "break"
1341 finally:
1342 text.undo_block_stop()
1343
1344 def newline_and_indent_event(self, event):
Cheryl Sabellaec646402020-01-21 05:11:26 -05001345 """Insert a newline and indentation after Enter keypress event.
1346
1347 Properly position the cursor on the new line based on information
1348 from the current line. This takes into account if the current line
1349 is a shell prompt, is empty, has selected text, contains a block
1350 opener, contains a block closer, is a continuation line, or
1351 is inside a string.
1352 """
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001353 text = self.text
1354 first, last = self.get_selection_indices()
1355 text.undo_block_start()
Cheryl Sabellaec646402020-01-21 05:11:26 -05001356 try: # Close undo block and expose new line in finally clause.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001357 if first and last:
1358 text.delete(first, last)
1359 text.mark_set("insert", first)
1360 line = text.get("insert linestart", "insert")
Cheryl Sabellaec646402020-01-21 05:11:26 -05001361
1362 # Count leading whitespace for indent size.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001363 i, n = 0, len(line)
1364 while i < n and line[i] in " \t":
Cheryl Sabellaec646402020-01-21 05:11:26 -05001365 i += 1
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001366 if i == n:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001367 # The cursor is in or at leading indentation in a continuation
1368 # line; just inject an empty line at the start.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001369 text.insert("insert linestart", '\n')
1370 return "break"
1371 indent = line[:i]
Cheryl Sabellaec646402020-01-21 05:11:26 -05001372
1373 # Strip whitespace before insert point unless it's in the prompt.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001374 i = 0
Terry Jan Reedye86172d2017-10-27 20:26:12 -04001375 while line and line[-1] in " \t" and line != self.prompt_last_line:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001376 line = line[:-1]
Cheryl Sabellaec646402020-01-21 05:11:26 -05001377 i += 1
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001378 if i:
1379 text.delete("insert - %d chars" % i, "insert")
Cheryl Sabellaec646402020-01-21 05:11:26 -05001380
1381 # Strip whitespace after insert point.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001382 while text.get("insert") in " \t":
1383 text.delete("insert")
Cheryl Sabellaec646402020-01-21 05:11:26 -05001384
1385 # Insert new line.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001386 text.insert("insert", '\n')
1387
Cheryl Sabellaec646402020-01-21 05:11:26 -05001388 # Adjust indentation for continuations and block open/close.
1389 # First need to find the last statement.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001390 lno = index2line(text.index('insert'))
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001391 y = pyparse.Parser(self.indentwidth, self.tabwidth)
Cheryl Sabella6bdc4de2019-06-02 14:56:47 -04001392 if not self.prompt_last_line:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001393 for context in self.num_context_lines:
1394 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001395 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001396 rawtext = text.get(startatindex, "insert")
Cheryl Sabellac29c03a2018-02-23 21:35:27 -05001397 y.set_code(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001398 bod = y.find_good_parse_start(
Cheryl Sabellaec646402020-01-21 05:11:26 -05001399 self._build_char_in_string_func(startatindex))
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001400 if bod is not None or startat == 1:
1401 break
1402 y.set_lo(bod or 0)
1403 else:
1404 r = text.tag_prevrange("console", "insert")
1405 if r:
1406 startatindex = r[1]
1407 else:
1408 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001409 rawtext = text.get(startatindex, "insert")
Cheryl Sabellac29c03a2018-02-23 21:35:27 -05001410 y.set_code(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001411 y.set_lo(0)
1412
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001413 c = y.get_continuation_type()
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001414 if c != pyparse.C_NONE:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001415 # The current statement hasn't ended yet.
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001416 if c == pyparse.C_STRING_FIRST_LINE:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001417 # After the first line of a string do not indent at all.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001418 pass
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001419 elif c == pyparse.C_STRING_NEXT_LINES:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001420 # Inside a string which started before this line;
1421 # just mimic the current indent.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001422 text.insert("insert", indent)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001423 elif c == pyparse.C_BRACKET:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001424 # Line up with the first (if any) element of the
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001425 # last open bracket structure; else indent one
1426 # level beyond the indent of the line with the
Cheryl Sabellaec646402020-01-21 05:11:26 -05001427 # last open bracket.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001428 self.reindent_to(y.compute_bracket_indent())
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001429 elif c == pyparse.C_BACKSLASH:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001430 # If more than one line in this statement already, just
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001431 # mimic the current indent; else if initial line
1432 # has a start on an assignment stmt, indent to
1433 # beyond leftmost =; else to beyond first chunk of
Cheryl Sabellaec646402020-01-21 05:11:26 -05001434 # non-whitespace on initial line.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001435 if y.get_num_lines_in_stmt() > 1:
1436 text.insert("insert", indent)
1437 else:
1438 self.reindent_to(y.compute_backslash_indent())
1439 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001440 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001441 return "break"
1442
Cheryl Sabellaec646402020-01-21 05:11:26 -05001443 # This line starts a brand new statement; indent relative to
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001444 # indentation of initial line of closest preceding
Cheryl Sabellaec646402020-01-21 05:11:26 -05001445 # interesting statement.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001446 indent = y.get_base_indent_string()
1447 text.insert("insert", indent)
1448 if y.is_block_opener():
1449 self.smart_indent_event(event)
1450 elif indent and y.is_block_closer():
1451 self.smart_backspace_event(event)
1452 return "break"
1453 finally:
1454 text.see("insert")
1455 text.undo_block_stop()
1456
Martin Panter7462b6492015-11-02 03:37:02 +00001457 # Our editwin provides an is_char_in_string function that works
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001458 # with a Tk text index, but PyParse only knows about offsets into
1459 # a string. This builds a function for PyParse that accepts an
1460 # offset.
1461
1462 def _build_char_in_string_func(self, startindex):
1463 def inner(offset, _startindex=startindex,
1464 _icis=self.is_char_in_string):
1465 return _icis(_startindex + "+%dc" % offset)
1466 return inner
1467
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001468 # XXX this isn't bound to anything -- see tabwidth comments
1469## def change_tabwidth_event(self, event):
1470## new = self._asktabwidth()
1471## if new != self.tabwidth:
1472## self.tabwidth = new
1473## self.set_indentation_params(0, guess=0)
1474## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001475
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001476 # Make string that displays as n leading blanks.
1477
1478 def _make_blanks(self, n):
1479 if self.usetabs:
1480 ntabs, nspaces = divmod(n, self.tabwidth)
1481 return '\t' * ntabs + ' ' * nspaces
1482 else:
1483 return ' ' * n
1484
1485 # Delete from beginning of line to insert point, then reinsert
1486 # column logical (meaning use tabs if appropriate) spaces.
1487
1488 def reindent_to(self, column):
1489 text = self.text
1490 text.undo_block_start()
1491 if text.compare("insert linestart", "!=", "insert"):
1492 text.delete("insert linestart", "insert")
1493 if column:
1494 text.insert("insert", self._make_blanks(column))
1495 text.undo_block_stop()
1496
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001497 # Guess indentwidth from text content.
1498 # Return guessed indentwidth. This should not be believed unless
1499 # it's in a reasonable range (e.g., it will be 0 if no indented
1500 # blocks are found).
1501
1502 def guess_indent(self):
1503 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1504 if opener and indented:
Tal Einat9b5ce622019-07-11 17:20:14 +03001505 raw, indentsmall = get_line_indent(opener, self.tabwidth)
1506 raw, indentlarge = get_line_indent(indented, self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001507 else:
1508 indentsmall = indentlarge = 0
1509 return indentlarge - indentsmall
1510
Tal Einat7123ea02019-07-23 15:22:11 +03001511 def toggle_line_numbers_event(self, event=None):
1512 if self.line_numbers is None:
1513 return
1514
1515 if self.line_numbers.is_shown:
1516 self.line_numbers.hide_sidebar()
1517 menu_label = "Show"
1518 else:
1519 self.line_numbers.show_sidebar()
1520 menu_label = "Hide"
1521 self.update_menu_label(menu='options', index='*Line Numbers',
1522 label=f'{menu_label} Line Numbers')
1523
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001524# "line.col" -> line, as an int
1525def index2line(index):
1526 return int(float(index))
1527
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001528
Tal Einat9b5ce622019-07-11 17:20:14 +03001529_line_indent_re = re.compile(r'[ \t]*')
1530def get_line_indent(line, tabwidth):
1531 """Return a line's indentation as (# chars, effective # of spaces).
1532
1533 The effective # of spaces is the length after properly "expanding"
1534 the tabs into spaces, as done by str.expandtabs(tabwidth).
1535 """
1536 m = _line_indent_re.match(line)
1537 return m.end(), len(m.group().expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001538
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001539
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001540class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001541
1542 # .run() chews over the Text widget, looking for a block opener
1543 # and the stmt following it. Returns a pair,
1544 # (line containing block opener, line containing stmt)
1545 # Either or both may be None.
1546
1547 def __init__(self, text, tabwidth):
1548 self.text = text
1549 self.tabwidth = tabwidth
1550 self.i = self.finished = 0
1551 self.blkopenline = self.indentedline = None
1552
1553 def readline(self):
1554 if self.finished:
1555 return ""
1556 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001557 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001558 if self.text.compare(mark, ">=", "end"):
1559 return ""
1560 return self.text.get(mark, mark + " lineend+1c")
1561
1562 def tokeneater(self, type, token, start, end, line,
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001563 INDENT=tokenize.INDENT,
1564 NAME=tokenize.NAME,
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001565 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1566 if self.finished:
1567 pass
1568 elif type == NAME and token in OPENERS:
1569 self.blkopenline = line
1570 elif type == INDENT and self.blkopenline:
1571 self.indentedline = line
1572 self.finished = 1
1573
1574 def run(self):
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001575 save_tabsize = tokenize.tabsize
1576 tokenize.tabsize = self.tabwidth
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001577 try:
1578 try:
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001579 tokens = tokenize.generate_tokens(self.readline)
Trent Nelson428de652008-03-18 22:41:35 +00001580 for token in tokens:
1581 self.tokeneater(*token)
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001582 except (tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001583 # since we cut off the tokenizer early, we can trigger
1584 # spurious errors
1585 pass
1586 finally:
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001587 tokenize.tabsize = save_tabsize
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001588 return self.blkopenline, self.indentedline
1589
1590### end autoindent code ###
1591
David Scherer7aced172000-08-15 01:13:23 +00001592def prepstr(s):
1593 # Helper to extract the underscore from a string, e.g.
1594 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001595 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001596 if i >= 0:
1597 s = s[:i] + s[i+1:]
1598 return i, s
1599
1600
1601keynames = {
1602 'bracketleft': '[',
1603 'bracketright': ']',
1604 'slash': '/',
1605}
1606
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001607def get_accelerator(keydefs, eventname):
1608 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001609 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1610 # if not keylist:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001611 if (not keylist) or (macosx.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001612 "<<open-module>>",
1613 "<<goto-line>>",
1614 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001615 return ""
1616 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001617 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001618 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1619 s = re.sub("Key-", "", s)
1620 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1621 s = re.sub("Control-", "Ctrl-", s)
1622 s = re.sub("-", "+", s)
1623 s = re.sub("><", " ", s)
1624 s = re.sub("<", "", s)
1625 s = re.sub(">", "", s)
1626 return s
1627
1628
1629def fixwordbreaks(root):
Terry Jan Reedy5ff3a162018-04-30 03:08:01 -04001630 # On Windows, tcl/tk breaks 'words' only on spaces, as in Command Prompt.
1631 # We want Motif style everywhere. See #21474, msg218992 and followup.
David Scherer7aced172000-08-15 01:13:23 +00001632 tk = root.tk
1633 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
Terry Jan Reedy5ff3a162018-04-30 03:08:01 -04001634 tk.call('set', 'tcl_wordchars', r'\w')
1635 tk.call('set', 'tcl_nonwordchars', r'\W')
David Scherer7aced172000-08-15 01:13:23 +00001636
1637
Terry Jan Reedycd567362014-10-17 01:31:35 -04001638def _editor_window(parent): # htest #
1639 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001640 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001641 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001642 if sys.argv[1:]:
1643 filename = sys.argv[1]
1644 else:
1645 filename = None
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001646 macosx.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001647 edit = EditorWindow(root=root, filename=filename)
Tal Einat077059e2018-08-10 09:02:08 +03001648 text = edit.text
1649 text['height'] = 10
1650 for i in range(20):
1651 text.insert('insert', ' '*i + str(i) + '\n')
1652 # text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001653 # Does not stop error, neither does following
1654 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001655
1656if __name__ == '__main__':
Terry Jan Reedyee5ef302018-06-15 18:20:55 -04001657 from unittest import main
1658 main('idlelib.idle_test.test_editor', verbosity=2, exit=False)
Terry Jan Reedy7c153412016-06-26 17:48:02 -04001659
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001660 from idlelib.idle_test.htest import run
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04001661 run(_editor_window)