blob: b0f88b5463d1b6e52d66868e13101d9a032f9888 [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):
Terry Jan Reedy4ca060d2020-03-08 15:30:04 -0400502 self.text.tag_remove("sel", "1.0", "end")
David Scherer7aced172000-08-15 01:13:23 +0000503 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
504 if not self.rmenu:
505 self.make_rmenu()
506 rmenu = self.rmenu
507 self.event = event
508 iswin = sys.platform[:3] == 'win'
509 if iswin:
510 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200511
Roger Serwy6b2918a2013-04-07 12:15:52 -0500512 for item in self.rmenu_specs:
513 try:
514 label, eventname, verify_state = item
515 except ValueError: # see issue1207589
516 continue
517
Andrew Svetlovd1837672012-11-01 22:41:19 +0200518 if verify_state is None:
519 continue
520 state = getattr(self, verify_state)()
521 rmenu.entryconfigure(label, state=state)
522
523
David Scherer7aced172000-08-15 01:13:23 +0000524 rmenu.tk_popup(event.x_root, event.y_root)
525 if iswin:
526 self.text.config(cursor="ibeam")
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300527 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000528
529 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200530 # ("Label", "<<virtual-event>>", "statefuncname"), ...
531 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000532 ]
533
534 def make_rmenu(self):
535 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500536 for item in self.rmenu_specs:
537 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200538 if label is not None:
539 def command(text=self.text, eventname=eventname):
540 text.event_generate(eventname)
541 rmenu.add_command(label=label, command=command)
542 else:
543 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000544 self.rmenu = rmenu
545
Andrew Svetlovd1837672012-11-01 22:41:19 +0200546 def rmenu_check_cut(self):
547 return self.rmenu_check_copy()
548
549 def rmenu_check_copy(self):
550 try:
551 indx = self.text.index('sel.first')
552 except TclError:
553 return 'disabled'
554 else:
555 return 'normal' if indx else 'disabled'
556
557 def rmenu_check_paste(self):
558 try:
559 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
560 except TclError:
561 return 'disabled'
562 else:
563 return 'normal'
564
David Scherer7aced172000-08-15 01:13:23 +0000565 def about_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400566 "Handle Help 'About IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400567 # Synchronize with macosx.overrideRootMenu.about_dialog.
csabella18ede062017-06-23 20:00:58 -0400568 help_about.AboutDialog(self.top)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300569 return "break"
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000570
Steven M. Gava3b55a892001-11-21 05:56:26 +0000571 def config_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400572 "Handle Options 'Configure IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400573 # Synchronize with macosx.overrideRootMenu.config_dialog.
574 configdialog.ConfigDialog(self.top,'Settings')
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300575 return "break"
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400576
David Scherer7aced172000-08-15 01:13:23 +0000577 def help_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400578 "Handle Help 'IDLE Help' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400579 # Synchronize with macosx.overrideRootMenu.help_dialog.
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500580 if self.root:
581 parent = self.root
582 else:
583 parent = self.top
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400584 help.show_idlehelp(parent)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300585 return "break"
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000586
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000587 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000588 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000589 try:
590 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200591 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000592 tkMessageBox.showerror(title='Document Start Failure',
593 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000594 else:
595 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000596 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000597
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000598 def cut(self,event):
599 self.text.event_generate("<<Cut>>")
600 return "break"
601
602 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000603 if not self.text.tag_ranges("sel"):
604 # There is no selection, so do nothing and maybe interrupt.
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300605 return None
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000606 self.text.event_generate("<<Copy>>")
607 return "break"
608
609 def paste(self,event):
610 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000611 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000612 return "break"
613
David Scherer7aced172000-08-15 01:13:23 +0000614 def select_all(self, event=None):
615 self.text.tag_add("sel", "1.0", "end-1c")
616 self.text.mark_set("insert", "1.0")
617 self.text.see("insert")
618 return "break"
619
620 def remove_selection(self, event=None):
621 self.text.tag_remove("sel", "1.0", "end")
622 self.text.see("insert")
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300623 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000624
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000625 def move_at_edge_if_selection(self, edge_index):
626 """Cursor move begins at start or end of selection
627
628 When a left/right cursor key is pressed create and return to Tkinter a
629 function which causes a cursor move from the associated edge of the
630 selection.
631
632 """
633 self_text_index = self.text.index
634 self_text_mark_set = self.text.mark_set
635 edges_table = ("sel.first+1c", "sel.last-1c")
636 def move_at_edge(event):
637 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
638 try:
639 self_text_index("sel.first")
640 self_text_mark_set("insert", edges_table[edge_index])
641 except TclError:
642 pass
643 return move_at_edge
644
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000645 def del_word_left(self, event):
646 self.text.event_generate('<Meta-Delete>')
647 return "break"
648
649 def del_word_right(self, event):
650 self.text.event_generate('<Meta-d>')
651 return "break"
652
Steven M. Gavac5976402002-01-04 03:06:08 +0000653 def find_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400654 search.find(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000655 return "break"
656
657 def find_again_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400658 search.find_again(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000659 return "break"
660
661 def find_selection_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400662 search.find_selection(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000663 return "break"
664
665 def find_in_files_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400666 grep.grep(self.text, self.io, self.flist)
Steven M. Gavac5976402002-01-04 03:06:08 +0000667 return "break"
668
669 def replace_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400670 replace.replace(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000671 return "break"
672
673 def goto_line_event(self, event):
674 text = self.text
Terry Jan Reedy363fab82020-03-09 16:51:20 -0400675 lineno = query.Goto(
676 text, "Go To Line",
677 "Enter a positive integer\n"
678 "('big' = end of file):"
679 ).result
680 if lineno is not None:
681 text.tag_remove("sel", "1.0", "end")
682 text.mark_set("insert", f'{lineno}.0')
683 text.see("insert")
684 self.set_line_and_column()
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300685 return "break"
Steven M. Gavac5976402002-01-04 03:06:08 +0000686
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300687 def open_module(self):
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400688 """Get module name from user and open it.
689
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400690 Return module path or None for calls by open_module_browser
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400691 when latter is not invoked in named editor window.
692 """
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400693 # XXX This, open_module_browser, and open_path_browser
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400694 # would fit better in iomenu.IOBinding.
David Scherer7aced172000-08-15 01:13:23 +0000695 try:
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400696 name = self.text.get("sel.first", "sel.last").strip()
David Scherer7aced172000-08-15 01:13:23 +0000697 except TclError:
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400698 name = ''
699 file_path = query.ModuleName(
700 self.text, "Open Module",
701 "Enter the name of a Python module\n"
702 "to search on sys.path and open:",
703 name).result
704 if file_path is not None:
705 if self.flist:
706 self.flist.open(file_path)
707 else:
708 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400709 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000710
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300711 def open_module_event(self, event):
712 self.open_module()
713 return "break"
714
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400715 def open_module_browser(self, event=None):
David Scherer7aced172000-08-15 01:13:23 +0000716 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400717 if not (self.__class__.__name__ == 'PyShellEditorWindow'
718 and filename):
719 filename = self.open_module()
720 if filename is None:
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300721 return "break"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400722 from idlelib import browser
Terry Jan Reedyd6bb65f2017-09-30 19:54:28 -0400723 browser.ModuleBrowser(self.root, filename)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300724 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000725
726 def open_path_browser(self, event=None):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400727 from idlelib import pathbrowser
Cheryl Sabella20d48a42017-11-22 19:05:25 -0500728 pathbrowser.PathBrowser(self.root)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300729 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000730
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400731 def open_turtle_demo(self, event = None):
732 import subprocess
733
734 cmd = [sys.executable,
735 '-c',
736 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400737 subprocess.Popen(cmd, shell=False)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300738 return "break"
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400739
David Scherer7aced172000-08-15 01:13:23 +0000740 def gotoline(self, lineno):
741 if lineno is not None and lineno > 0:
742 self.text.mark_set("insert", "%d.0" % lineno)
743 self.text.tag_remove("sel", "1.0", "end")
744 self.text.tag_add("sel", "insert", "insert +1l")
745 self.center()
746
747 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000748 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000749 return True
David Scherer7aced172000-08-15 01:13:23 +0000750 base, ext = os.path.splitext(os.path.basename(filename))
751 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000752 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000753 line = self.text.get('1.0', '1.0 lineend')
754 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000755
756 def close_hook(self):
757 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000758 self.flist.unregister_maybe_terminate(self)
759 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000760
761 def set_close_hook(self, close_hook):
762 self.close_hook = close_hook
763
764 def filename_change_hook(self):
765 if self.flist:
766 self.flist.filename_changed_edit(self)
767 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000768 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000769 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000770
Christian Heimesa156e092008-02-16 07:38:31 +0000771 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000772 if self.color:
773 return
Christian Heimesa156e092008-02-16 07:38:31 +0000774 if self.ispythonsource(self.io.filename):
775 self.color = self.ColorDelegator()
776 # can add more colorizers here...
777 if self.color:
778 self.per.removefilter(self.undo)
779 self.per.insertfilter(self.color)
780 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000781
Christian Heimesa156e092008-02-16 07:38:31 +0000782 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000783 if not self.color:
784 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000785 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000786 self.per.removefilter(self.color)
787 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000788
Steven M. Gavab77d3432002-03-02 07:16:21 +0000789 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400790 "Update the color theme"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400791 # Called from self.filename_change_hook and from configdialog.py
Christian Heimesa156e092008-02-16 07:38:31 +0000792 self._rmcolorizer()
793 self._addcolorizer()
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -0400794 EditorWindow.color_config(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000795
Tal Einat7123ea02019-07-23 15:22:11 +0300796 if self.code_context is not None:
797 self.code_context.update_highlight_colors()
798
799 if self.line_numbers is not None:
800 self.line_numbers.update_colors()
Tal Einat7036e1d2019-07-17 11:15:53 +0300801
Guido van Rossum33d26892007-08-05 15:29:28 +0000802 IDENTCHARS = string.ascii_letters + string.digits + "_"
803
804 def colorize_syntax_error(self, text, pos):
805 text.tag_add("ERROR", pos)
806 char = text.get(pos)
807 if char and char in self.IDENTCHARS:
808 text.tag_add("ERROR", pos + " wordstart", pos)
809 if '\n' == text.get(pos): # error at line end
810 text.mark_set("insert", pos)
811 else:
812 text.mark_set("insert", pos + "+1c")
813 text.see(pos)
814
Zackery Spytz9c284492019-11-13 00:13:33 -0700815 def update_cursor_blink(self):
816 "Update the cursor blink configuration."
817 cursorblink = idleConf.GetOption(
818 'main', 'EditorWindow', 'cursor-blink', type='bool')
819 if not cursorblink:
820 self.text['insertofftime'] = 0
821 else:
822 # Restore the original value
823 self.text['insertofftime'] = idleConf.blink_off_time
824
Steven M. Gavab1585412002-03-12 00:21:56 +0000825 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000826 "Update the text widgets' font if it is changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400827 # Called from configdialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400828
Tal Einat7036e1d2019-07-17 11:15:53 +0300829 # Update the code context widget first, since its height affects
830 # the height of the text widget. This avoids double re-rendering.
Tal Einat7123ea02019-07-23 15:22:11 +0300831 if self.code_context is not None:
832 self.code_context.update_font()
833 # Next, update the line numbers widget, since its width affects
834 # the width of the text widget.
835 if self.line_numbers is not None:
836 self.line_numbers.update_font()
837 # Finally, update the main text widget.
838 new_font = idleConf.GetFont(self.root, 'main', 'EditorWindow')
Tal Einat7036e1d2019-07-17 11:15:53 +0300839 self.text['font'] = new_font
Tal Einatd4b4c002019-08-25 08:52:58 +0300840 self.set_width()
Steven M. Gavab1585412002-03-12 00:21:56 +0000841
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000842 def RemoveKeybindings(self):
843 "Remove the keybindings before they are changed."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400844 # Called from configdialog.py
845 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000846 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000847 self.text.event_delete(event, *keylist)
848 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000849 xkeydefs = idleConf.GetExtensionBindings(extensionName)
850 if xkeydefs:
851 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000852 self.text.event_delete(event, *keylist)
853
854 def ApplyKeybindings(self):
855 "Update the keybindings after they are changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400856 # Called from configdialog.py
857 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000858 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000859 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000860 xkeydefs = idleConf.GetExtensionBindings(extensionName)
861 if xkeydefs:
862 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000863 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000864 menuEventDict = {}
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400865 for menu in self.mainmenu.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000866 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000867 for item in menu[1]:
868 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000869 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000870 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000871 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700872 end = menu.index(END)
873 if end is None:
874 # Skip empty menus
875 continue
876 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000877 for index in range(0, end):
878 if menu.type(index) == 'command':
879 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000880 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000881 itemName = menu.entrycget(index, 'label')
882 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000883 if menubarItem in menuEventDict:
884 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000885 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000886 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000887 accel = get_accelerator(keydefs, event)
888 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000889
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000890 def set_notabs_indentwidth(self):
891 "Update the indentwidth if changed and not using tabs in this window"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400892 # Called from configdialog.py
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000893 if not self.usetabs:
894 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
895 type='int')
896
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000897 def reset_help_menu_entries(self):
898 "Update the additional help entries on the Help menu"
899 help_list = idleConf.GetAllExtraHelpSourcesList()
900 helpmenu = self.menudict['help']
901 # first delete the extra help entries, if any
902 helpmenu_length = helpmenu.index(END)
903 if helpmenu_length > self.base_helpmenu_length:
904 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
905 # then rebuild them
906 if help_list:
907 helpmenu.add_separator()
908 for entry in help_list:
909 cmd = self.__extra_help_callback(entry[1])
910 helpmenu.add_command(label=entry[0], command=cmd)
911 # and update the menu dictionary
912 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000913
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000914 def __extra_help_callback(self, helpfile):
915 "Create a callback with the helpfile value frozen at definition time"
916 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000917 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000918 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000919 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000920 try:
921 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200922 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000923 tkMessageBox.showerror(title='Document Start Failure',
924 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000925 else:
926 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000927 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000928
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000929 def update_recent_files_list(self, new_file=None):
930 "Load and update the recent files list and menus"
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400931 # TODO: move to iomenu.
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000932 rf_list = []
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400933 file_path = self.recent_files_path
934 if file_path and os.path.exists(file_path):
935 with open(file_path, 'r',
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400936 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000937 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000938 if new_file:
939 new_file = os.path.abspath(new_file) + '\n'
940 if new_file in rf_list:
941 rf_list.remove(new_file) # move to top
942 rf_list.insert(0, new_file)
943 # clean and save the recent files list
944 bad_paths = []
945 for path in rf_list:
946 if '\0' in path or not os.path.exists(path[0:-1]):
947 bad_paths.append(path)
948 rf_list = [path for path in rf_list if path not in bad_paths]
949 ulchars = "1234567890ABCDEFGHIJK"
950 rf_list = rf_list[0:len(ulchars)]
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400951 if file_path:
952 try:
953 with open(file_path, 'w',
954 encoding='utf_8', errors='replace') as rf_file:
955 rf_file.writelines(rf_list)
956 except OSError as err:
957 if not getattr(self.root, "recentfiles_message", False):
958 self.root.recentfiles_message = True
959 tkMessageBox.showwarning(title='IDLE Warning',
960 message="Cannot save Recent Files list to disk.\n"
961 f" {err}\n"
962 "Select OK to continue.",
963 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000964 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000965 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000966 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700967 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000968 for i, file_name in enumerate(rf_list):
969 file_name = file_name.rstrip() # zap \n
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000970 callback = instance.__recent_file_callback(file_name)
Serhiy Storchaka06cb94b2019-10-04 13:09:52 +0300971 menu.add_command(label=ulchars[i] + " " + file_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000972 command=callback,
973 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000974
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000975 def __recent_file_callback(self, file_name):
976 def open_recent_file(fn_closure=file_name):
977 self.io.open(editFile=fn_closure)
978 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000979
David Scherer7aced172000-08-15 01:13:23 +0000980 def saved_change_hook(self):
981 short = self.short_title()
982 long = self.long_title()
983 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400984 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000985 elif short:
986 title = short
987 elif long:
988 title = long
989 else:
Terry Jan Reedya9022392019-01-18 02:09:53 -0500990 title = "untitled"
David Scherer7aced172000-08-15 01:13:23 +0000991 icon = short or long or title
992 if not self.get_saved():
993 title = "*%s*" % title
994 icon = "*%s" % icon
995 self.top.wm_title(title)
996 self.top.wm_iconname(icon)
997
998 def get_saved(self):
999 return self.undo.get_saved()
1000
1001 def set_saved(self, flag):
1002 self.undo.set_saved(flag)
1003
1004 def reset_undo(self):
1005 self.undo.reset_undo()
1006
1007 def short_title(self):
1008 filename = self.io.filename
Serhiy Storchaka06cb94b2019-10-04 13:09:52 +03001009 return os.path.basename(filename) if filename else "untitled"
David Scherer7aced172000-08-15 01:13:23 +00001010
1011 def long_title(self):
Serhiy Storchaka06cb94b2019-10-04 13:09:52 +03001012 return self.io.filename or ""
David Scherer7aced172000-08-15 01:13:23 +00001013
1014 def center_insert_event(self, event):
1015 self.center()
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001016 return "break"
David Scherer7aced172000-08-15 01:13:23 +00001017
1018 def center(self, mark="insert"):
1019 text = self.text
1020 top, bot = self.getwindowlines()
1021 lineno = self.getlineno(mark)
1022 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001023 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +00001024 text.yview(float(newtop))
1025
1026 def getwindowlines(self):
1027 text = self.text
1028 top = self.getlineno("@0,0")
1029 bot = self.getlineno("@0,65535")
1030 if top == bot and text.winfo_height() == 1:
1031 # Geometry manager hasn't run yet
1032 height = int(text['height'])
1033 bot = top + height - 1
1034 return top, bot
1035
1036 def getlineno(self, mark="insert"):
1037 text = self.text
1038 return int(float(text.index(mark)))
1039
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001040 def get_geometry(self):
1041 "Return (width, height, x, y)"
1042 geom = self.top.wm_geometry()
1043 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +00001044 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001045
David Scherer7aced172000-08-15 01:13:23 +00001046 def close_event(self, event):
1047 self.close()
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001048 return "break"
David Scherer7aced172000-08-15 01:13:23 +00001049
1050 def maybesave(self):
1051 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001052 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001053 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001054 self.top.deiconify()
1055 self.top.lower()
1056 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001057 return self.io.maybesave()
1058
1059 def close(self):
Terry Jan Reedydfd34a92019-09-17 02:05:04 -04001060 try:
1061 reply = self.maybesave()
1062 if str(reply) != "cancel":
1063 self._close()
1064 return reply
1065 except AttributeError: # bpo-35379: close called twice
1066 pass
David Scherer7aced172000-08-15 01:13:23 +00001067
1068 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001069 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001070 self.update_recent_files_list(new_file=self.io.filename)
Terry Jan Reedya361e892018-06-20 21:25:59 -04001071 window.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001072 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001073 self.io.close()
1074 self.io = None
1075 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001076 if self.color:
Cheryl Sabellab9f03542019-03-01 05:19:40 -05001077 self.color.close()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001078 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001079 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001080 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001081 self.per.close()
1082 self.per = None
1083 self.top.destroy()
1084 if self.close_hook:
1085 # unless override: unregister from flist, terminate if last window
1086 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001087
1088 def load_extensions(self):
1089 self.extensions = {}
1090 self.load_standard_extensions()
1091
1092 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001093 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001094 if hasattr(ins, "close"):
1095 ins.close()
1096 self.extensions = {}
1097
1098 def load_standard_extensions(self):
1099 for name in self.get_standard_extension_names():
1100 try:
1101 self.load_extension(name)
1102 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001103 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001104 traceback.print_exc()
1105
1106 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001107 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001108
wohlganger58fc71c2017-09-10 16:19:47 -05001109 extfiles = { # Map built-in config-extension section names to file names.
1110 'ZzDummy': 'zzdummy',
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001111 }
1112
David Scherer7aced172000-08-15 01:13:23 +00001113 def load_extension(self, name):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001114 fname = self.extfiles.get(name, name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001115 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001116 try:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001117 mod = importlib.import_module('.' + fname, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001118 except (ImportError, TypeError):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001119 mod = importlib.import_module(fname)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001120 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001121 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001122 raise
David Scherer7aced172000-08-15 01:13:23 +00001123 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001124 keydefs = idleConf.GetExtensionBindings(name)
1125 if hasattr(cls, "menudefs"):
1126 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001127 ins = cls(self)
1128 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001129 if keydefs:
1130 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001131 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001132 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001133 while methodname[:1] == '<':
1134 methodname = methodname[1:]
1135 while methodname[-1:] == '>':
1136 methodname = methodname[:-1]
1137 methodname = methodname + "_event"
1138 if hasattr(ins, methodname):
1139 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001140
1141 def apply_bindings(self, keydefs=None):
1142 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001143 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001144 text = self.text
1145 text.keydefs = keydefs
1146 for event, keylist in keydefs.items():
1147 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001148 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001149
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001150 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001151 """Add appropriate entries to the menus and submenus
1152
1153 Menus that are absent or None in self.menudict are ignored.
1154 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001155 if menudefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001156 menudefs = self.mainmenu.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001157 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001158 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001159 menudict = self.menudict
1160 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001161 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001162 menu = menudict.get(mname)
1163 if not menu:
1164 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001165 for entry in entrylist:
1166 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001167 menu.add_separator()
1168 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001169 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001170 checkbutton = (label[:1] == '!')
1171 if checkbutton:
1172 label = label[1:]
1173 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001174 accelerator = get_accelerator(keydefs, eventname)
1175 def command(text=text, eventname=eventname):
1176 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001177 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001178 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001179 menu.add_checkbutton(label=label, underline=underline,
1180 command=command, accelerator=accelerator,
1181 variable=var)
1182 else:
1183 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001184 command=command,
1185 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001186
1187 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001188 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001189 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001190 value = var.get()
1191 return value
1192 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001193 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001194
1195 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001196 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001197 if var:
1198 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001199 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001200 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001201
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001202 def get_var_obj(self, name, vartype=None):
1203 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001204 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001205 # create a Tkinter variable object with self.text as master:
1206 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001207 return var
1208
1209 # Tk implementations of "virtual text methods" -- each platform
1210 # reusing IDLE's support code needs to define these for its GUI's
1211 # flavor of widget.
1212
1213 # Is character at text_index in a Python string? Return 0 for
1214 # "guaranteed no", true for anything else. This info is expensive
1215 # to compute ab initio, but is probably already known by the
1216 # platform's colorizer.
1217
1218 def is_char_in_string(self, text_index):
1219 if self.color:
1220 # Return true iff colorizer hasn't (re)gotten this far
1221 # yet, or the character is tagged as being in a string
1222 return self.text.tag_prevrange("TODO", text_index) or \
1223 "STRING" in self.text.tag_names(text_index)
1224 else:
1225 # The colorizer is missing: assume the worst
1226 return 1
1227
1228 # If a selection is defined in the text widget, return (start,
1229 # end) as Tkinter text indices, otherwise return (None, None)
1230 def get_selection_indices(self):
1231 try:
1232 first = self.text.index("sel.first")
1233 last = self.text.index("sel.last")
1234 return first, last
1235 except TclError:
1236 return None, None
1237
1238 # Return the text widget's current view of what a tab stop means
1239 # (equivalent width in spaces).
1240
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001241 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001242 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1243 return int(current)
1244
1245 # Set the text widget's current view of what a tab stop means.
1246
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001247 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001248 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001249 if self.get_tk_tabwidth() != newtabwidth:
1250 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001251 pixels = text.tk.call("font", "measure", text["font"],
1252 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001253 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001254 text.configure(tabs=pixels)
1255
Guido van Rossum33d26892007-08-05 15:29:28 +00001256### begin autoindent code ### (configuration was moved to beginning of class)
1257
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001258 def set_indentation_params(self, is_py_src, guess=True):
1259 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001260 i = self.guess_indent()
1261 if 2 <= i <= 8:
1262 self.indentwidth = i
1263 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001264 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001265 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001266
1267 def smart_backspace_event(self, event):
1268 text = self.text
1269 first, last = self.get_selection_indices()
1270 if first and last:
1271 text.delete(first, last)
1272 text.mark_set("insert", first)
1273 return "break"
1274 # Delete whitespace left, until hitting a real char or closest
1275 # preceding virtual tab stop.
1276 chars = text.get("insert linestart", "insert")
1277 if chars == '':
1278 if text.compare("insert", ">", "1.0"):
1279 # easy: delete preceding newline
1280 text.delete("insert-1c")
1281 else:
1282 text.bell() # at start of buffer
1283 return "break"
1284 if chars[-1] not in " \t":
1285 # easy: delete preceding real char
1286 text.delete("insert-1c")
1287 return "break"
1288 # Ick. It may require *inserting* spaces if we back up over a
1289 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001290 tabwidth = self.tabwidth
1291 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001292 assert have > 0
1293 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001294 # Debug prompt is multilined....
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001295 ncharsdeleted = 0
1296 while 1:
Terry Jan Reedye86172d2017-10-27 20:26:12 -04001297 if chars == self.prompt_last_line: # '' unless PyShell
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001298 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001299 chars = chars[:-1]
1300 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001301 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001302 if have <= want or chars[-1] not in " \t":
1303 break
1304 text.undo_block_start()
1305 text.delete("insert-%dc" % ncharsdeleted, "insert")
1306 if have < want:
1307 text.insert("insert", ' ' * (want - have))
1308 text.undo_block_stop()
1309 return "break"
1310
1311 def smart_indent_event(self, event):
1312 # if intraline selection:
1313 # delete it
1314 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001315 # do indent-region
1316 # else:
1317 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001318 text = self.text
1319 first, last = self.get_selection_indices()
1320 text.undo_block_start()
1321 try:
1322 if first and last:
1323 if index2line(first) != index2line(last):
Cheryl Sabella82494aa2019-07-17 09:44:44 -04001324 return self.fregion.indent_region_event(event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001325 text.delete(first, last)
1326 text.mark_set("insert", first)
1327 prefix = text.get("insert linestart", "insert")
Tal Einat9b5ce622019-07-11 17:20:14 +03001328 raw, effective = get_line_indent(prefix, self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001329 if raw == len(prefix):
1330 # only whitespace to the left
1331 self.reindent_to(effective + self.indentwidth)
1332 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001333 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001334 if self.usetabs:
1335 pad = '\t'
1336 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001337 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001338 n = self.indentwidth
1339 pad = ' ' * (n - effective % n)
1340 text.insert("insert", pad)
1341 text.see("insert")
1342 return "break"
1343 finally:
1344 text.undo_block_stop()
1345
1346 def newline_and_indent_event(self, event):
Cheryl Sabellaec646402020-01-21 05:11:26 -05001347 """Insert a newline and indentation after Enter keypress event.
1348
1349 Properly position the cursor on the new line based on information
1350 from the current line. This takes into account if the current line
1351 is a shell prompt, is empty, has selected text, contains a block
1352 opener, contains a block closer, is a continuation line, or
1353 is inside a string.
1354 """
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001355 text = self.text
1356 first, last = self.get_selection_indices()
1357 text.undo_block_start()
Cheryl Sabellaec646402020-01-21 05:11:26 -05001358 try: # Close undo block and expose new line in finally clause.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001359 if first and last:
1360 text.delete(first, last)
1361 text.mark_set("insert", first)
1362 line = text.get("insert linestart", "insert")
Cheryl Sabellaec646402020-01-21 05:11:26 -05001363
1364 # Count leading whitespace for indent size.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001365 i, n = 0, len(line)
1366 while i < n and line[i] in " \t":
Cheryl Sabellaec646402020-01-21 05:11:26 -05001367 i += 1
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001368 if i == n:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001369 # The cursor is in or at leading indentation in a continuation
1370 # line; just inject an empty line at the start.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001371 text.insert("insert linestart", '\n')
1372 return "break"
1373 indent = line[:i]
Cheryl Sabellaec646402020-01-21 05:11:26 -05001374
1375 # Strip whitespace before insert point unless it's in the prompt.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001376 i = 0
Terry Jan Reedye86172d2017-10-27 20:26:12 -04001377 while line and line[-1] in " \t" and line != self.prompt_last_line:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001378 line = line[:-1]
Cheryl Sabellaec646402020-01-21 05:11:26 -05001379 i += 1
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001380 if i:
1381 text.delete("insert - %d chars" % i, "insert")
Cheryl Sabellaec646402020-01-21 05:11:26 -05001382
1383 # Strip whitespace after insert point.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001384 while text.get("insert") in " \t":
1385 text.delete("insert")
Cheryl Sabellaec646402020-01-21 05:11:26 -05001386
1387 # Insert new line.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001388 text.insert("insert", '\n')
1389
Cheryl Sabellaec646402020-01-21 05:11:26 -05001390 # Adjust indentation for continuations and block open/close.
1391 # First need to find the last statement.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001392 lno = index2line(text.index('insert'))
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001393 y = pyparse.Parser(self.indentwidth, self.tabwidth)
Cheryl Sabella6bdc4de2019-06-02 14:56:47 -04001394 if not self.prompt_last_line:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001395 for context in self.num_context_lines:
1396 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001397 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001398 rawtext = text.get(startatindex, "insert")
Cheryl Sabellac29c03a2018-02-23 21:35:27 -05001399 y.set_code(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001400 bod = y.find_good_parse_start(
Cheryl Sabellaec646402020-01-21 05:11:26 -05001401 self._build_char_in_string_func(startatindex))
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001402 if bod is not None or startat == 1:
1403 break
1404 y.set_lo(bod or 0)
1405 else:
1406 r = text.tag_prevrange("console", "insert")
1407 if r:
1408 startatindex = r[1]
1409 else:
1410 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001411 rawtext = text.get(startatindex, "insert")
Cheryl Sabellac29c03a2018-02-23 21:35:27 -05001412 y.set_code(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001413 y.set_lo(0)
1414
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001415 c = y.get_continuation_type()
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001416 if c != pyparse.C_NONE:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001417 # The current statement hasn't ended yet.
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001418 if c == pyparse.C_STRING_FIRST_LINE:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001419 # After the first line of a string do not indent at all.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001420 pass
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001421 elif c == pyparse.C_STRING_NEXT_LINES:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001422 # Inside a string which started before this line;
1423 # just mimic the current indent.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001424 text.insert("insert", indent)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001425 elif c == pyparse.C_BRACKET:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001426 # Line up with the first (if any) element of the
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001427 # last open bracket structure; else indent one
1428 # level beyond the indent of the line with the
Cheryl Sabellaec646402020-01-21 05:11:26 -05001429 # last open bracket.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001430 self.reindent_to(y.compute_bracket_indent())
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001431 elif c == pyparse.C_BACKSLASH:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001432 # If more than one line in this statement already, just
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001433 # mimic the current indent; else if initial line
1434 # has a start on an assignment stmt, indent to
1435 # beyond leftmost =; else to beyond first chunk of
Cheryl Sabellaec646402020-01-21 05:11:26 -05001436 # non-whitespace on initial line.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001437 if y.get_num_lines_in_stmt() > 1:
1438 text.insert("insert", indent)
1439 else:
1440 self.reindent_to(y.compute_backslash_indent())
1441 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001442 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001443 return "break"
1444
Cheryl Sabellaec646402020-01-21 05:11:26 -05001445 # This line starts a brand new statement; indent relative to
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001446 # indentation of initial line of closest preceding
Cheryl Sabellaec646402020-01-21 05:11:26 -05001447 # interesting statement.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001448 indent = y.get_base_indent_string()
1449 text.insert("insert", indent)
1450 if y.is_block_opener():
1451 self.smart_indent_event(event)
1452 elif indent and y.is_block_closer():
1453 self.smart_backspace_event(event)
1454 return "break"
1455 finally:
1456 text.see("insert")
1457 text.undo_block_stop()
1458
Martin Panter7462b6492015-11-02 03:37:02 +00001459 # Our editwin provides an is_char_in_string function that works
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001460 # with a Tk text index, but PyParse only knows about offsets into
1461 # a string. This builds a function for PyParse that accepts an
1462 # offset.
1463
1464 def _build_char_in_string_func(self, startindex):
1465 def inner(offset, _startindex=startindex,
1466 _icis=self.is_char_in_string):
1467 return _icis(_startindex + "+%dc" % offset)
1468 return inner
1469
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001470 # XXX this isn't bound to anything -- see tabwidth comments
1471## def change_tabwidth_event(self, event):
1472## new = self._asktabwidth()
1473## if new != self.tabwidth:
1474## self.tabwidth = new
1475## self.set_indentation_params(0, guess=0)
1476## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001477
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001478 # Make string that displays as n leading blanks.
1479
1480 def _make_blanks(self, n):
1481 if self.usetabs:
1482 ntabs, nspaces = divmod(n, self.tabwidth)
1483 return '\t' * ntabs + ' ' * nspaces
1484 else:
1485 return ' ' * n
1486
1487 # Delete from beginning of line to insert point, then reinsert
1488 # column logical (meaning use tabs if appropriate) spaces.
1489
1490 def reindent_to(self, column):
1491 text = self.text
1492 text.undo_block_start()
1493 if text.compare("insert linestart", "!=", "insert"):
1494 text.delete("insert linestart", "insert")
1495 if column:
1496 text.insert("insert", self._make_blanks(column))
1497 text.undo_block_stop()
1498
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001499 # Guess indentwidth from text content.
1500 # Return guessed indentwidth. This should not be believed unless
1501 # it's in a reasonable range (e.g., it will be 0 if no indented
1502 # blocks are found).
1503
1504 def guess_indent(self):
1505 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1506 if opener and indented:
Tal Einat9b5ce622019-07-11 17:20:14 +03001507 raw, indentsmall = get_line_indent(opener, self.tabwidth)
1508 raw, indentlarge = get_line_indent(indented, self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001509 else:
1510 indentsmall = indentlarge = 0
1511 return indentlarge - indentsmall
1512
Tal Einat7123ea02019-07-23 15:22:11 +03001513 def toggle_line_numbers_event(self, event=None):
1514 if self.line_numbers is None:
1515 return
1516
1517 if self.line_numbers.is_shown:
1518 self.line_numbers.hide_sidebar()
1519 menu_label = "Show"
1520 else:
1521 self.line_numbers.show_sidebar()
1522 menu_label = "Hide"
1523 self.update_menu_label(menu='options', index='*Line Numbers',
1524 label=f'{menu_label} Line Numbers')
1525
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001526# "line.col" -> line, as an int
1527def index2line(index):
1528 return int(float(index))
1529
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001530
Tal Einat9b5ce622019-07-11 17:20:14 +03001531_line_indent_re = re.compile(r'[ \t]*')
1532def get_line_indent(line, tabwidth):
1533 """Return a line's indentation as (# chars, effective # of spaces).
1534
1535 The effective # of spaces is the length after properly "expanding"
1536 the tabs into spaces, as done by str.expandtabs(tabwidth).
1537 """
1538 m = _line_indent_re.match(line)
1539 return m.end(), len(m.group().expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001540
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001541
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001542class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001543
1544 # .run() chews over the Text widget, looking for a block opener
1545 # and the stmt following it. Returns a pair,
1546 # (line containing block opener, line containing stmt)
1547 # Either or both may be None.
1548
1549 def __init__(self, text, tabwidth):
1550 self.text = text
1551 self.tabwidth = tabwidth
1552 self.i = self.finished = 0
1553 self.blkopenline = self.indentedline = None
1554
1555 def readline(self):
1556 if self.finished:
1557 return ""
1558 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001559 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001560 if self.text.compare(mark, ">=", "end"):
1561 return ""
1562 return self.text.get(mark, mark + " lineend+1c")
1563
1564 def tokeneater(self, type, token, start, end, line,
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001565 INDENT=tokenize.INDENT,
1566 NAME=tokenize.NAME,
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001567 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1568 if self.finished:
1569 pass
1570 elif type == NAME and token in OPENERS:
1571 self.blkopenline = line
1572 elif type == INDENT and self.blkopenline:
1573 self.indentedline = line
1574 self.finished = 1
1575
1576 def run(self):
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001577 save_tabsize = tokenize.tabsize
1578 tokenize.tabsize = self.tabwidth
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001579 try:
1580 try:
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001581 tokens = tokenize.generate_tokens(self.readline)
Trent Nelson428de652008-03-18 22:41:35 +00001582 for token in tokens:
1583 self.tokeneater(*token)
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001584 except (tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001585 # since we cut off the tokenizer early, we can trigger
1586 # spurious errors
1587 pass
1588 finally:
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001589 tokenize.tabsize = save_tabsize
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001590 return self.blkopenline, self.indentedline
1591
1592### end autoindent code ###
1593
David Scherer7aced172000-08-15 01:13:23 +00001594def prepstr(s):
1595 # Helper to extract the underscore from a string, e.g.
1596 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001597 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001598 if i >= 0:
1599 s = s[:i] + s[i+1:]
1600 return i, s
1601
1602
1603keynames = {
1604 'bracketleft': '[',
1605 'bracketright': ']',
1606 'slash': '/',
1607}
1608
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001609def get_accelerator(keydefs, eventname):
1610 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001611 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1612 # if not keylist:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001613 if (not keylist) or (macosx.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001614 "<<open-module>>",
1615 "<<goto-line>>",
1616 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001617 return ""
1618 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001619 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001620 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1621 s = re.sub("Key-", "", s)
1622 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1623 s = re.sub("Control-", "Ctrl-", s)
1624 s = re.sub("-", "+", s)
1625 s = re.sub("><", " ", s)
1626 s = re.sub("<", "", s)
1627 s = re.sub(">", "", s)
1628 return s
1629
1630
1631def fixwordbreaks(root):
Terry Jan Reedy5ff3a162018-04-30 03:08:01 -04001632 # On Windows, tcl/tk breaks 'words' only on spaces, as in Command Prompt.
1633 # We want Motif style everywhere. See #21474, msg218992 and followup.
David Scherer7aced172000-08-15 01:13:23 +00001634 tk = root.tk
1635 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
Terry Jan Reedy5ff3a162018-04-30 03:08:01 -04001636 tk.call('set', 'tcl_wordchars', r'\w')
1637 tk.call('set', 'tcl_nonwordchars', r'\W')
David Scherer7aced172000-08-15 01:13:23 +00001638
1639
Terry Jan Reedycd567362014-10-17 01:31:35 -04001640def _editor_window(parent): # htest #
1641 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001642 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001643 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001644 if sys.argv[1:]:
1645 filename = sys.argv[1]
1646 else:
1647 filename = None
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001648 macosx.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001649 edit = EditorWindow(root=root, filename=filename)
Tal Einat077059e2018-08-10 09:02:08 +03001650 text = edit.text
1651 text['height'] = 10
1652 for i in range(20):
1653 text.insert('insert', ' '*i + str(i) + '\n')
1654 # text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001655 # Does not stop error, neither does following
1656 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001657
1658if __name__ == '__main__':
Terry Jan Reedyee5ef302018-06-15 18:20:55 -04001659 from unittest import main
1660 main('idlelib.idle_test.test_editor', verbosity=2, exit=False)
Terry Jan Reedy7c153412016-06-26 17:48:02 -04001661
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001662 from idlelib.idle_test.htest import run
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04001663 run(_editor_window)