blob: 51941900d5c741466874f6a30f4f775378f7a78d [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 Reedy3c7eccd2015-09-22 21:10:27 -0400147 # Some OS X systems have only one mouse button, so use
148 # control-click for popup context menus there. For two
149 # buttons, AquaTk defines <2> as the right button, not <3>.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000150 text.bind("<Control-Button-1>",self.right_menu_event)
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400151 text.bind("<2>", self.right_menu_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000152 else:
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400153 # Elsewhere, use right-click for popup menus.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000154 text.bind("<3>",self.right_menu_event)
GeeTransit2cd90252019-09-04 21:33:34 -0400155
156 text.bind('<MouseWheel>', wheel_event)
157 text.bind('<Button-4>', wheel_event)
158 text.bind('<Button-5>', wheel_event)
Tal Einatd4b4c002019-08-25 08:52:58 +0300159 text.bind('<Configure>', self.handle_winconfig)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000160 text.bind("<<cut>>", self.cut)
161 text.bind("<<copy>>", self.copy)
162 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000163 text.bind("<<center-insert>>", self.center_insert_event)
164 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000165 text.bind("<<python-docs>>", self.python_docs)
166 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000167 text.bind("<<open-config-dialog>>", self.config_dialog)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300168 text.bind("<<open-module>>", self.open_module_event)
David Scherer7aced172000-08-15 01:13:23 +0000169 text.bind("<<do-nothing>>", lambda event: "break")
170 text.bind("<<select-all>>", self.select_all)
171 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000172 text.bind("<<find>>", self.find_event)
173 text.bind("<<find-again>>", self.find_again_event)
174 text.bind("<<find-in-files>>", self.find_in_files_event)
175 text.bind("<<find-selection>>", self.find_selection_event)
176 text.bind("<<replace>>", self.replace_event)
177 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000178 text.bind("<<smart-backspace>>",self.smart_backspace_event)
179 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
180 text.bind("<<smart-indent>>",self.smart_indent_event)
Cheryl Sabella82494aa2019-07-17 09:44:44 -0400181 self.fregion = fregion = self.FormatRegion(self)
Terry Jan Reedy1b389222019-07-17 20:48:36 -0400182 # self.fregion used in smart_indent_event to access indent_region.
Cheryl Sabella82494aa2019-07-17 09:44:44 -0400183 text.bind("<<indent-region>>", fregion.indent_region_event)
184 text.bind("<<dedent-region>>", fregion.dedent_region_event)
185 text.bind("<<comment-region>>", fregion.comment_region_event)
186 text.bind("<<uncomment-region>>", fregion.uncomment_region_event)
187 text.bind("<<tabify-region>>", fregion.tabify_region_event)
188 text.bind("<<untabify-region>>", fregion.untabify_region_event)
Terry Jan Reedyb8462472019-11-20 01:18:39 -0500189 indents = self.Indents(self)
190 text.bind("<<toggle-tabs>>", indents.toggle_tabs_event)
191 text.bind("<<change-indentwidth>>", indents.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000192 text.bind("<Left>", self.move_at_edge_if_selection(0))
193 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000194 text.bind("<<del-word-left>>", self.del_word_left)
195 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000196 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000197
David Scherer7aced172000-08-15 01:13:23 +0000198 if flist:
199 flist.inversedict[self] = key
200 if key:
201 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000202 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000203 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400204 text.bind("<<open-class-browser>>", self.open_module_browser)
David Scherer7aced172000-08-15 01:13:23 +0000205 text.bind("<<open-path-browser>>", self.open_path_browser)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400206 text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
David Scherer7aced172000-08-15 01:13:23 +0000207
Steven M. Gava898a3652001-10-07 11:10:44 +0000208 self.set_status_bar()
Tal Einat7123ea02019-07-23 15:22:11 +0300209 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
210 text_frame.rowconfigure(1, weight=1)
211 text_frame.columnconfigure(1, weight=1)
Cheryl Sabellad49dbd92018-06-04 11:48:21 -0400212 vbar['command'] = self.handle_yview
Tal Einat7123ea02019-07-23 15:22:11 +0300213 vbar.grid(row=1, column=2, sticky=NSEW)
David Scherer7aced172000-08-15 01:13:23 +0000214 text['yscrollcommand'] = vbar.set
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400215 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
Tal Einat7123ea02019-07-23 15:22:11 +0300216 text.grid(row=1, column=1, sticky=NSEW)
David Scherer7aced172000-08-15 01:13:23 +0000217 text.focus_set()
Tal Einatd4b4c002019-08-25 08:52:58 +0300218 self.set_width()
David Scherer7aced172000-08-15 01:13:23 +0000219
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000220 # usetabs true -> literal tab characters are used by indent and
221 # dedent cmds, possibly mixed with spaces if
222 # indentwidth is not a multiple of tabwidth,
223 # which will cause Tabnanny to nag!
224 # false -> tab characters are converted to spaces by indent
225 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000226 # Although use-spaces=0 can be configured manually in config-main.def,
227 # configuration of tabs v. spaces is not supported in the configuration
228 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200229 usespaces = idleConf.GetOption('main', 'Indent',
230 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000231 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000232
233 # tabwidth is the display width of a literal tab character.
234 # CAUTION: telling Tk to use anything other than its default
235 # tab setting causes it to use an entirely different tabbing algorithm,
236 # treating tab stops as fixed distances from the left margin.
237 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000238 self.tabwidth = 8 # must remain 8 until Tk is fixed.
239
240 # indentwidth is the number of screen characters per indent level.
241 # The recommended Python indentation is four spaces.
242 self.indentwidth = self.tabwidth
243 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000244
Zackery Spytz9c284492019-11-13 00:13:33 -0700245 # Store the current value of the insertofftime now so we can restore
246 # it if needed.
247 if not hasattr(idleConf, 'blink_off_time'):
248 idleConf.blink_off_time = self.text['insertofftime']
249 self.update_cursor_blink()
250
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000251 # When searching backwards for a reliable place to begin parsing,
252 # first start num_context_lines[0] lines back, then
253 # num_context_lines[1] lines back if that didn't work, and so on.
254 # The last value should be huge (larger than the # of lines in a
255 # conceivable file).
256 # Making the initial values larger slows things down more often.
257 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000258 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000259 self.undo = undo = self.UndoDelegator()
260 per.insertfilter(undo)
261 text.undo_block_start = undo.undo_block_start
262 text.undo_block_stop = undo.undo_block_stop
263 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000264 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000265 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000266 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000267 self.good_load = False
268 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000269 self.color = None # initialized below in self.ResetColorizer
Tal Einat7123ea02019-07-23 15:22:11 +0300270 self.code_context = None # optionally initialized later below
271 self.line_numbers = None # optionally initialized later below
David Scherer7aced172000-08-15 01:13:23 +0000272 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000273 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000274 if io.loadfile(filename):
275 self.good_load = True
276 is_py_src = self.ispythonsource(filename)
277 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000278 else:
279 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500280 self.good_load = True
281
Christian Heimesa156e092008-02-16 07:38:31 +0000282 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000283 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000284 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000285 self.load_extensions()
Terry Jan Reedy33c74202018-06-20 22:49:55 -0400286 menu = self.menudict.get('window')
David Scherer7aced172000-08-15 01:13:23 +0000287 if menu:
288 end = menu.index("end")
289 if end is None:
290 end = -1
291 if end >= 0:
292 menu.add_separator()
293 end = end + 1
294 self.wmenu_end = end
Terry Jan Reedya361e892018-06-20 21:25:59 -0400295 window.register_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000296
297 # Some abstractions so IDLE extensions are cross-IDE
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
675 lineno = tkSimpleDialog.askinteger("Goto",
676 "Go to line number:",parent=text)
677 if lineno is None:
678 return "break"
679 if lineno <= 0:
680 text.bell()
681 return "break"
Terry Jan Reedy2522db12020-03-08 14:32:42 -0400682
683 text.tag_remove("sel", "1.0", "end")
684 text.mark_set("insert", f'{lineno}.0')
Steven M. Gavac5976402002-01-04 03:06:08 +0000685 text.see("insert")
Terry Jan Reedy2522db12020-03-08 14:32:42 -0400686 self.set_line_and_column()
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300687 return "break"
Steven M. Gavac5976402002-01-04 03:06:08 +0000688
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300689 def open_module(self):
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400690 """Get module name from user and open it.
691
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400692 Return module path or None for calls by open_module_browser
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400693 when latter is not invoked in named editor window.
694 """
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400695 # XXX This, open_module_browser, and open_path_browser
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400696 # would fit better in iomenu.IOBinding.
David Scherer7aced172000-08-15 01:13:23 +0000697 try:
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400698 name = self.text.get("sel.first", "sel.last").strip()
David Scherer7aced172000-08-15 01:13:23 +0000699 except TclError:
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400700 name = ''
701 file_path = query.ModuleName(
702 self.text, "Open Module",
703 "Enter the name of a Python module\n"
704 "to search on sys.path and open:",
705 name).result
706 if file_path is not None:
707 if self.flist:
708 self.flist.open(file_path)
709 else:
710 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400711 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000712
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300713 def open_module_event(self, event):
714 self.open_module()
715 return "break"
716
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400717 def open_module_browser(self, event=None):
David Scherer7aced172000-08-15 01:13:23 +0000718 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400719 if not (self.__class__.__name__ == 'PyShellEditorWindow'
720 and filename):
721 filename = self.open_module()
722 if filename is None:
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300723 return "break"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400724 from idlelib import browser
Terry Jan Reedyd6bb65f2017-09-30 19:54:28 -0400725 browser.ModuleBrowser(self.root, filename)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300726 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000727
728 def open_path_browser(self, event=None):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400729 from idlelib import pathbrowser
Cheryl Sabella20d48a42017-11-22 19:05:25 -0500730 pathbrowser.PathBrowser(self.root)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300731 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000732
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400733 def open_turtle_demo(self, event = None):
734 import subprocess
735
736 cmd = [sys.executable,
737 '-c',
738 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400739 subprocess.Popen(cmd, shell=False)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300740 return "break"
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400741
David Scherer7aced172000-08-15 01:13:23 +0000742 def gotoline(self, lineno):
743 if lineno is not None and lineno > 0:
744 self.text.mark_set("insert", "%d.0" % lineno)
745 self.text.tag_remove("sel", "1.0", "end")
746 self.text.tag_add("sel", "insert", "insert +1l")
747 self.center()
748
749 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000750 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000751 return True
David Scherer7aced172000-08-15 01:13:23 +0000752 base, ext = os.path.splitext(os.path.basename(filename))
753 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000754 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000755 line = self.text.get('1.0', '1.0 lineend')
756 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000757
758 def close_hook(self):
759 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000760 self.flist.unregister_maybe_terminate(self)
761 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000762
763 def set_close_hook(self, close_hook):
764 self.close_hook = close_hook
765
766 def filename_change_hook(self):
767 if self.flist:
768 self.flist.filename_changed_edit(self)
769 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000770 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000771 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000772
Christian Heimesa156e092008-02-16 07:38:31 +0000773 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000774 if self.color:
775 return
Christian Heimesa156e092008-02-16 07:38:31 +0000776 if self.ispythonsource(self.io.filename):
777 self.color = self.ColorDelegator()
778 # can add more colorizers here...
779 if self.color:
780 self.per.removefilter(self.undo)
781 self.per.insertfilter(self.color)
782 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000783
Christian Heimesa156e092008-02-16 07:38:31 +0000784 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000785 if not self.color:
786 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000787 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000788 self.per.removefilter(self.color)
789 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000790
Steven M. Gavab77d3432002-03-02 07:16:21 +0000791 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400792 "Update the color theme"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400793 # Called from self.filename_change_hook and from configdialog.py
Christian Heimesa156e092008-02-16 07:38:31 +0000794 self._rmcolorizer()
795 self._addcolorizer()
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -0400796 EditorWindow.color_config(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000797
Tal Einat7123ea02019-07-23 15:22:11 +0300798 if self.code_context is not None:
799 self.code_context.update_highlight_colors()
800
801 if self.line_numbers is not None:
802 self.line_numbers.update_colors()
Tal Einat7036e1d2019-07-17 11:15:53 +0300803
Guido van Rossum33d26892007-08-05 15:29:28 +0000804 IDENTCHARS = string.ascii_letters + string.digits + "_"
805
806 def colorize_syntax_error(self, text, pos):
807 text.tag_add("ERROR", pos)
808 char = text.get(pos)
809 if char and char in self.IDENTCHARS:
810 text.tag_add("ERROR", pos + " wordstart", pos)
811 if '\n' == text.get(pos): # error at line end
812 text.mark_set("insert", pos)
813 else:
814 text.mark_set("insert", pos + "+1c")
815 text.see(pos)
816
Zackery Spytz9c284492019-11-13 00:13:33 -0700817 def update_cursor_blink(self):
818 "Update the cursor blink configuration."
819 cursorblink = idleConf.GetOption(
820 'main', 'EditorWindow', 'cursor-blink', type='bool')
821 if not cursorblink:
822 self.text['insertofftime'] = 0
823 else:
824 # Restore the original value
825 self.text['insertofftime'] = idleConf.blink_off_time
826
Steven M. Gavab1585412002-03-12 00:21:56 +0000827 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000828 "Update the text widgets' font if it is changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400829 # Called from configdialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400830
Tal Einat7036e1d2019-07-17 11:15:53 +0300831 # Update the code context widget first, since its height affects
832 # the height of the text widget. This avoids double re-rendering.
Tal Einat7123ea02019-07-23 15:22:11 +0300833 if self.code_context is not None:
834 self.code_context.update_font()
835 # Next, update the line numbers widget, since its width affects
836 # the width of the text widget.
837 if self.line_numbers is not None:
838 self.line_numbers.update_font()
839 # Finally, update the main text widget.
840 new_font = idleConf.GetFont(self.root, 'main', 'EditorWindow')
Tal Einat7036e1d2019-07-17 11:15:53 +0300841 self.text['font'] = new_font
Tal Einatd4b4c002019-08-25 08:52:58 +0300842 self.set_width()
Steven M. Gavab1585412002-03-12 00:21:56 +0000843
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000844 def RemoveKeybindings(self):
845 "Remove the keybindings before they are changed."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400846 # Called from configdialog.py
847 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000848 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000849 self.text.event_delete(event, *keylist)
850 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000851 xkeydefs = idleConf.GetExtensionBindings(extensionName)
852 if xkeydefs:
853 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000854 self.text.event_delete(event, *keylist)
855
856 def ApplyKeybindings(self):
857 "Update the keybindings after they are changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400858 # Called from configdialog.py
859 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000860 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000861 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000862 xkeydefs = idleConf.GetExtensionBindings(extensionName)
863 if xkeydefs:
864 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000865 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000866 menuEventDict = {}
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400867 for menu in self.mainmenu.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000868 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000869 for item in menu[1]:
870 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000871 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000872 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000873 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700874 end = menu.index(END)
875 if end is None:
876 # Skip empty menus
877 continue
878 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000879 for index in range(0, end):
880 if menu.type(index) == 'command':
881 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000882 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000883 itemName = menu.entrycget(index, 'label')
884 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000885 if menubarItem in menuEventDict:
886 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000887 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000888 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000889 accel = get_accelerator(keydefs, event)
890 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000891
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000892 def set_notabs_indentwidth(self):
893 "Update the indentwidth if changed and not using tabs in this window"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400894 # Called from configdialog.py
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000895 if not self.usetabs:
896 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
897 type='int')
898
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000899 def reset_help_menu_entries(self):
900 "Update the additional help entries on the Help menu"
901 help_list = idleConf.GetAllExtraHelpSourcesList()
902 helpmenu = self.menudict['help']
903 # first delete the extra help entries, if any
904 helpmenu_length = helpmenu.index(END)
905 if helpmenu_length > self.base_helpmenu_length:
906 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
907 # then rebuild them
908 if help_list:
909 helpmenu.add_separator()
910 for entry in help_list:
911 cmd = self.__extra_help_callback(entry[1])
912 helpmenu.add_command(label=entry[0], command=cmd)
913 # and update the menu dictionary
914 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000915
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000916 def __extra_help_callback(self, helpfile):
917 "Create a callback with the helpfile value frozen at definition time"
918 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000919 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000920 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000921 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000922 try:
923 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200924 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000925 tkMessageBox.showerror(title='Document Start Failure',
926 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000927 else:
928 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000929 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000930
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000931 def update_recent_files_list(self, new_file=None):
932 "Load and update the recent files list and menus"
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400933 # TODO: move to iomenu.
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000934 rf_list = []
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400935 file_path = self.recent_files_path
936 if file_path and os.path.exists(file_path):
937 with open(file_path, 'r',
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400938 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000939 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000940 if new_file:
941 new_file = os.path.abspath(new_file) + '\n'
942 if new_file in rf_list:
943 rf_list.remove(new_file) # move to top
944 rf_list.insert(0, new_file)
945 # clean and save the recent files list
946 bad_paths = []
947 for path in rf_list:
948 if '\0' in path or not os.path.exists(path[0:-1]):
949 bad_paths.append(path)
950 rf_list = [path for path in rf_list if path not in bad_paths]
951 ulchars = "1234567890ABCDEFGHIJK"
952 rf_list = rf_list[0:len(ulchars)]
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400953 if file_path:
954 try:
955 with open(file_path, 'w',
956 encoding='utf_8', errors='replace') as rf_file:
957 rf_file.writelines(rf_list)
958 except OSError as err:
959 if not getattr(self.root, "recentfiles_message", False):
960 self.root.recentfiles_message = True
961 tkMessageBox.showwarning(title='IDLE Warning',
962 message="Cannot save Recent Files list to disk.\n"
963 f" {err}\n"
964 "Select OK to continue.",
965 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000966 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000967 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000968 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700969 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000970 for i, file_name in enumerate(rf_list):
971 file_name = file_name.rstrip() # zap \n
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000972 callback = instance.__recent_file_callback(file_name)
Serhiy Storchaka06cb94b2019-10-04 13:09:52 +0300973 menu.add_command(label=ulchars[i] + " " + file_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000974 command=callback,
975 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000976
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000977 def __recent_file_callback(self, file_name):
978 def open_recent_file(fn_closure=file_name):
979 self.io.open(editFile=fn_closure)
980 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000981
David Scherer7aced172000-08-15 01:13:23 +0000982 def saved_change_hook(self):
983 short = self.short_title()
984 long = self.long_title()
985 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400986 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000987 elif short:
988 title = short
989 elif long:
990 title = long
991 else:
Terry Jan Reedya9022392019-01-18 02:09:53 -0500992 title = "untitled"
David Scherer7aced172000-08-15 01:13:23 +0000993 icon = short or long or title
994 if not self.get_saved():
995 title = "*%s*" % title
996 icon = "*%s" % icon
997 self.top.wm_title(title)
998 self.top.wm_iconname(icon)
999
1000 def get_saved(self):
1001 return self.undo.get_saved()
1002
1003 def set_saved(self, flag):
1004 self.undo.set_saved(flag)
1005
1006 def reset_undo(self):
1007 self.undo.reset_undo()
1008
1009 def short_title(self):
1010 filename = self.io.filename
Serhiy Storchaka06cb94b2019-10-04 13:09:52 +03001011 return os.path.basename(filename) if filename else "untitled"
David Scherer7aced172000-08-15 01:13:23 +00001012
1013 def long_title(self):
Serhiy Storchaka06cb94b2019-10-04 13:09:52 +03001014 return self.io.filename or ""
David Scherer7aced172000-08-15 01:13:23 +00001015
1016 def center_insert_event(self, event):
1017 self.center()
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001018 return "break"
David Scherer7aced172000-08-15 01:13:23 +00001019
1020 def center(self, mark="insert"):
1021 text = self.text
1022 top, bot = self.getwindowlines()
1023 lineno = self.getlineno(mark)
1024 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001025 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +00001026 text.yview(float(newtop))
1027
1028 def getwindowlines(self):
1029 text = self.text
1030 top = self.getlineno("@0,0")
1031 bot = self.getlineno("@0,65535")
1032 if top == bot and text.winfo_height() == 1:
1033 # Geometry manager hasn't run yet
1034 height = int(text['height'])
1035 bot = top + height - 1
1036 return top, bot
1037
1038 def getlineno(self, mark="insert"):
1039 text = self.text
1040 return int(float(text.index(mark)))
1041
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001042 def get_geometry(self):
1043 "Return (width, height, x, y)"
1044 geom = self.top.wm_geometry()
1045 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +00001046 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001047
David Scherer7aced172000-08-15 01:13:23 +00001048 def close_event(self, event):
1049 self.close()
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001050 return "break"
David Scherer7aced172000-08-15 01:13:23 +00001051
1052 def maybesave(self):
1053 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001054 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001055 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001056 self.top.deiconify()
1057 self.top.lower()
1058 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001059 return self.io.maybesave()
1060
1061 def close(self):
Terry Jan Reedydfd34a92019-09-17 02:05:04 -04001062 try:
1063 reply = self.maybesave()
1064 if str(reply) != "cancel":
1065 self._close()
1066 return reply
1067 except AttributeError: # bpo-35379: close called twice
1068 pass
David Scherer7aced172000-08-15 01:13:23 +00001069
1070 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001071 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001072 self.update_recent_files_list(new_file=self.io.filename)
Terry Jan Reedya361e892018-06-20 21:25:59 -04001073 window.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001074 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001075 self.io.close()
1076 self.io = None
1077 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001078 if self.color:
Cheryl Sabellab9f03542019-03-01 05:19:40 -05001079 self.color.close()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001080 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001081 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001082 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001083 self.per.close()
1084 self.per = None
1085 self.top.destroy()
1086 if self.close_hook:
1087 # unless override: unregister from flist, terminate if last window
1088 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001089
1090 def load_extensions(self):
1091 self.extensions = {}
1092 self.load_standard_extensions()
1093
1094 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001095 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001096 if hasattr(ins, "close"):
1097 ins.close()
1098 self.extensions = {}
1099
1100 def load_standard_extensions(self):
1101 for name in self.get_standard_extension_names():
1102 try:
1103 self.load_extension(name)
1104 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001105 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001106 traceback.print_exc()
1107
1108 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001109 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001110
wohlganger58fc71c2017-09-10 16:19:47 -05001111 extfiles = { # Map built-in config-extension section names to file names.
1112 'ZzDummy': 'zzdummy',
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001113 }
1114
David Scherer7aced172000-08-15 01:13:23 +00001115 def load_extension(self, name):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001116 fname = self.extfiles.get(name, name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001117 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001118 try:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001119 mod = importlib.import_module('.' + fname, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001120 except (ImportError, TypeError):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001121 mod = importlib.import_module(fname)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001122 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001123 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001124 raise
David Scherer7aced172000-08-15 01:13:23 +00001125 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001126 keydefs = idleConf.GetExtensionBindings(name)
1127 if hasattr(cls, "menudefs"):
1128 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001129 ins = cls(self)
1130 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001131 if keydefs:
1132 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001133 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001134 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001135 while methodname[:1] == '<':
1136 methodname = methodname[1:]
1137 while methodname[-1:] == '>':
1138 methodname = methodname[:-1]
1139 methodname = methodname + "_event"
1140 if hasattr(ins, methodname):
1141 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001142
1143 def apply_bindings(self, keydefs=None):
1144 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001145 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001146 text = self.text
1147 text.keydefs = keydefs
1148 for event, keylist in keydefs.items():
1149 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001150 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001151
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001152 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001153 """Add appropriate entries to the menus and submenus
1154
1155 Menus that are absent or None in self.menudict are ignored.
1156 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001157 if menudefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001158 menudefs = self.mainmenu.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001159 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001160 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001161 menudict = self.menudict
1162 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001163 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001164 menu = menudict.get(mname)
1165 if not menu:
1166 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001167 for entry in entrylist:
1168 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001169 menu.add_separator()
1170 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001171 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001172 checkbutton = (label[:1] == '!')
1173 if checkbutton:
1174 label = label[1:]
1175 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001176 accelerator = get_accelerator(keydefs, eventname)
1177 def command(text=text, eventname=eventname):
1178 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001179 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001180 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001181 menu.add_checkbutton(label=label, underline=underline,
1182 command=command, accelerator=accelerator,
1183 variable=var)
1184 else:
1185 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001186 command=command,
1187 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001188
1189 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001190 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001191 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001192 value = var.get()
1193 return value
1194 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001195 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001196
1197 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001198 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001199 if var:
1200 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001201 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001202 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001203
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001204 def get_var_obj(self, name, vartype=None):
1205 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001206 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001207 # create a Tkinter variable object with self.text as master:
1208 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001209 return var
1210
1211 # Tk implementations of "virtual text methods" -- each platform
1212 # reusing IDLE's support code needs to define these for its GUI's
1213 # flavor of widget.
1214
1215 # Is character at text_index in a Python string? Return 0 for
1216 # "guaranteed no", true for anything else. This info is expensive
1217 # to compute ab initio, but is probably already known by the
1218 # platform's colorizer.
1219
1220 def is_char_in_string(self, text_index):
1221 if self.color:
1222 # Return true iff colorizer hasn't (re)gotten this far
1223 # yet, or the character is tagged as being in a string
1224 return self.text.tag_prevrange("TODO", text_index) or \
1225 "STRING" in self.text.tag_names(text_index)
1226 else:
1227 # The colorizer is missing: assume the worst
1228 return 1
1229
1230 # If a selection is defined in the text widget, return (start,
1231 # end) as Tkinter text indices, otherwise return (None, None)
1232 def get_selection_indices(self):
1233 try:
1234 first = self.text.index("sel.first")
1235 last = self.text.index("sel.last")
1236 return first, last
1237 except TclError:
1238 return None, None
1239
1240 # Return the text widget's current view of what a tab stop means
1241 # (equivalent width in spaces).
1242
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001243 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001244 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1245 return int(current)
1246
1247 # Set the text widget's current view of what a tab stop means.
1248
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001249 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001250 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001251 if self.get_tk_tabwidth() != newtabwidth:
1252 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001253 pixels = text.tk.call("font", "measure", text["font"],
1254 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001255 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001256 text.configure(tabs=pixels)
1257
Guido van Rossum33d26892007-08-05 15:29:28 +00001258### begin autoindent code ### (configuration was moved to beginning of class)
1259
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001260 def set_indentation_params(self, is_py_src, guess=True):
1261 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001262 i = self.guess_indent()
1263 if 2 <= i <= 8:
1264 self.indentwidth = i
1265 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001266 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001267 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001268
1269 def smart_backspace_event(self, event):
1270 text = self.text
1271 first, last = self.get_selection_indices()
1272 if first and last:
1273 text.delete(first, last)
1274 text.mark_set("insert", first)
1275 return "break"
1276 # Delete whitespace left, until hitting a real char or closest
1277 # preceding virtual tab stop.
1278 chars = text.get("insert linestart", "insert")
1279 if chars == '':
1280 if text.compare("insert", ">", "1.0"):
1281 # easy: delete preceding newline
1282 text.delete("insert-1c")
1283 else:
1284 text.bell() # at start of buffer
1285 return "break"
1286 if chars[-1] not in " \t":
1287 # easy: delete preceding real char
1288 text.delete("insert-1c")
1289 return "break"
1290 # Ick. It may require *inserting* spaces if we back up over a
1291 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001292 tabwidth = self.tabwidth
1293 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001294 assert have > 0
1295 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001296 # Debug prompt is multilined....
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001297 ncharsdeleted = 0
1298 while 1:
Terry Jan Reedye86172d2017-10-27 20:26:12 -04001299 if chars == self.prompt_last_line: # '' unless PyShell
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001300 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001301 chars = chars[:-1]
1302 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001303 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001304 if have <= want or chars[-1] not in " \t":
1305 break
1306 text.undo_block_start()
1307 text.delete("insert-%dc" % ncharsdeleted, "insert")
1308 if have < want:
1309 text.insert("insert", ' ' * (want - have))
1310 text.undo_block_stop()
1311 return "break"
1312
1313 def smart_indent_event(self, event):
1314 # if intraline selection:
1315 # delete it
1316 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001317 # do indent-region
1318 # else:
1319 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001320 text = self.text
1321 first, last = self.get_selection_indices()
1322 text.undo_block_start()
1323 try:
1324 if first and last:
1325 if index2line(first) != index2line(last):
Cheryl Sabella82494aa2019-07-17 09:44:44 -04001326 return self.fregion.indent_region_event(event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001327 text.delete(first, last)
1328 text.mark_set("insert", first)
1329 prefix = text.get("insert linestart", "insert")
Tal Einat9b5ce622019-07-11 17:20:14 +03001330 raw, effective = get_line_indent(prefix, self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001331 if raw == len(prefix):
1332 # only whitespace to the left
1333 self.reindent_to(effective + self.indentwidth)
1334 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001335 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001336 if self.usetabs:
1337 pad = '\t'
1338 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001339 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001340 n = self.indentwidth
1341 pad = ' ' * (n - effective % n)
1342 text.insert("insert", pad)
1343 text.see("insert")
1344 return "break"
1345 finally:
1346 text.undo_block_stop()
1347
1348 def newline_and_indent_event(self, event):
Cheryl Sabellaec646402020-01-21 05:11:26 -05001349 """Insert a newline and indentation after Enter keypress event.
1350
1351 Properly position the cursor on the new line based on information
1352 from the current line. This takes into account if the current line
1353 is a shell prompt, is empty, has selected text, contains a block
1354 opener, contains a block closer, is a continuation line, or
1355 is inside a string.
1356 """
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001357 text = self.text
1358 first, last = self.get_selection_indices()
1359 text.undo_block_start()
Cheryl Sabellaec646402020-01-21 05:11:26 -05001360 try: # Close undo block and expose new line in finally clause.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001361 if first and last:
1362 text.delete(first, last)
1363 text.mark_set("insert", first)
1364 line = text.get("insert linestart", "insert")
Cheryl Sabellaec646402020-01-21 05:11:26 -05001365
1366 # Count leading whitespace for indent size.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001367 i, n = 0, len(line)
1368 while i < n and line[i] in " \t":
Cheryl Sabellaec646402020-01-21 05:11:26 -05001369 i += 1
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001370 if i == n:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001371 # The cursor is in or at leading indentation in a continuation
1372 # line; just inject an empty line at the start.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001373 text.insert("insert linestart", '\n')
1374 return "break"
1375 indent = line[:i]
Cheryl Sabellaec646402020-01-21 05:11:26 -05001376
1377 # Strip whitespace before insert point unless it's in the prompt.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001378 i = 0
Terry Jan Reedye86172d2017-10-27 20:26:12 -04001379 while line and line[-1] in " \t" and line != self.prompt_last_line:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001380 line = line[:-1]
Cheryl Sabellaec646402020-01-21 05:11:26 -05001381 i += 1
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001382 if i:
1383 text.delete("insert - %d chars" % i, "insert")
Cheryl Sabellaec646402020-01-21 05:11:26 -05001384
1385 # Strip whitespace after insert point.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001386 while text.get("insert") in " \t":
1387 text.delete("insert")
Cheryl Sabellaec646402020-01-21 05:11:26 -05001388
1389 # Insert new line.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001390 text.insert("insert", '\n')
1391
Cheryl Sabellaec646402020-01-21 05:11:26 -05001392 # Adjust indentation for continuations and block open/close.
1393 # First need to find the last statement.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001394 lno = index2line(text.index('insert'))
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001395 y = pyparse.Parser(self.indentwidth, self.tabwidth)
Cheryl Sabella6bdc4de2019-06-02 14:56:47 -04001396 if not self.prompt_last_line:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001397 for context in self.num_context_lines:
1398 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001399 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001400 rawtext = text.get(startatindex, "insert")
Cheryl Sabellac29c03a2018-02-23 21:35:27 -05001401 y.set_code(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001402 bod = y.find_good_parse_start(
Cheryl Sabellaec646402020-01-21 05:11:26 -05001403 self._build_char_in_string_func(startatindex))
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001404 if bod is not None or startat == 1:
1405 break
1406 y.set_lo(bod or 0)
1407 else:
1408 r = text.tag_prevrange("console", "insert")
1409 if r:
1410 startatindex = r[1]
1411 else:
1412 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001413 rawtext = text.get(startatindex, "insert")
Cheryl Sabellac29c03a2018-02-23 21:35:27 -05001414 y.set_code(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001415 y.set_lo(0)
1416
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001417 c = y.get_continuation_type()
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001418 if c != pyparse.C_NONE:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001419 # The current statement hasn't ended yet.
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001420 if c == pyparse.C_STRING_FIRST_LINE:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001421 # After the first line of a string do not indent at all.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001422 pass
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001423 elif c == pyparse.C_STRING_NEXT_LINES:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001424 # Inside a string which started before this line;
1425 # just mimic the current indent.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001426 text.insert("insert", indent)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001427 elif c == pyparse.C_BRACKET:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001428 # Line up with the first (if any) element of the
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001429 # last open bracket structure; else indent one
1430 # level beyond the indent of the line with the
Cheryl Sabellaec646402020-01-21 05:11:26 -05001431 # last open bracket.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001432 self.reindent_to(y.compute_bracket_indent())
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001433 elif c == pyparse.C_BACKSLASH:
Cheryl Sabellaec646402020-01-21 05:11:26 -05001434 # If more than one line in this statement already, just
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001435 # mimic the current indent; else if initial line
1436 # has a start on an assignment stmt, indent to
1437 # beyond leftmost =; else to beyond first chunk of
Cheryl Sabellaec646402020-01-21 05:11:26 -05001438 # non-whitespace on initial line.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001439 if y.get_num_lines_in_stmt() > 1:
1440 text.insert("insert", indent)
1441 else:
1442 self.reindent_to(y.compute_backslash_indent())
1443 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001444 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001445 return "break"
1446
Cheryl Sabellaec646402020-01-21 05:11:26 -05001447 # This line starts a brand new statement; indent relative to
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001448 # indentation of initial line of closest preceding
Cheryl Sabellaec646402020-01-21 05:11:26 -05001449 # interesting statement.
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001450 indent = y.get_base_indent_string()
1451 text.insert("insert", indent)
1452 if y.is_block_opener():
1453 self.smart_indent_event(event)
1454 elif indent and y.is_block_closer():
1455 self.smart_backspace_event(event)
1456 return "break"
1457 finally:
1458 text.see("insert")
1459 text.undo_block_stop()
1460
Martin Panter7462b6492015-11-02 03:37:02 +00001461 # Our editwin provides an is_char_in_string function that works
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001462 # with a Tk text index, but PyParse only knows about offsets into
1463 # a string. This builds a function for PyParse that accepts an
1464 # offset.
1465
1466 def _build_char_in_string_func(self, startindex):
1467 def inner(offset, _startindex=startindex,
1468 _icis=self.is_char_in_string):
1469 return _icis(_startindex + "+%dc" % offset)
1470 return inner
1471
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001472 # XXX this isn't bound to anything -- see tabwidth comments
1473## def change_tabwidth_event(self, event):
1474## new = self._asktabwidth()
1475## if new != self.tabwidth:
1476## self.tabwidth = new
1477## self.set_indentation_params(0, guess=0)
1478## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001479
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001480 # Make string that displays as n leading blanks.
1481
1482 def _make_blanks(self, n):
1483 if self.usetabs:
1484 ntabs, nspaces = divmod(n, self.tabwidth)
1485 return '\t' * ntabs + ' ' * nspaces
1486 else:
1487 return ' ' * n
1488
1489 # Delete from beginning of line to insert point, then reinsert
1490 # column logical (meaning use tabs if appropriate) spaces.
1491
1492 def reindent_to(self, column):
1493 text = self.text
1494 text.undo_block_start()
1495 if text.compare("insert linestart", "!=", "insert"):
1496 text.delete("insert linestart", "insert")
1497 if column:
1498 text.insert("insert", self._make_blanks(column))
1499 text.undo_block_stop()
1500
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001501 # Guess indentwidth from text content.
1502 # Return guessed indentwidth. This should not be believed unless
1503 # it's in a reasonable range (e.g., it will be 0 if no indented
1504 # blocks are found).
1505
1506 def guess_indent(self):
1507 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1508 if opener and indented:
Tal Einat9b5ce622019-07-11 17:20:14 +03001509 raw, indentsmall = get_line_indent(opener, self.tabwidth)
1510 raw, indentlarge = get_line_indent(indented, self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001511 else:
1512 indentsmall = indentlarge = 0
1513 return indentlarge - indentsmall
1514
Tal Einat7123ea02019-07-23 15:22:11 +03001515 def toggle_line_numbers_event(self, event=None):
1516 if self.line_numbers is None:
1517 return
1518
1519 if self.line_numbers.is_shown:
1520 self.line_numbers.hide_sidebar()
1521 menu_label = "Show"
1522 else:
1523 self.line_numbers.show_sidebar()
1524 menu_label = "Hide"
1525 self.update_menu_label(menu='options', index='*Line Numbers',
1526 label=f'{menu_label} Line Numbers')
1527
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001528# "line.col" -> line, as an int
1529def index2line(index):
1530 return int(float(index))
1531
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001532
Tal Einat9b5ce622019-07-11 17:20:14 +03001533_line_indent_re = re.compile(r'[ \t]*')
1534def get_line_indent(line, tabwidth):
1535 """Return a line's indentation as (# chars, effective # of spaces).
1536
1537 The effective # of spaces is the length after properly "expanding"
1538 the tabs into spaces, as done by str.expandtabs(tabwidth).
1539 """
1540 m = _line_indent_re.match(line)
1541 return m.end(), len(m.group().expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001542
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001543
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001544class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001545
1546 # .run() chews over the Text widget, looking for a block opener
1547 # and the stmt following it. Returns a pair,
1548 # (line containing block opener, line containing stmt)
1549 # Either or both may be None.
1550
1551 def __init__(self, text, tabwidth):
1552 self.text = text
1553 self.tabwidth = tabwidth
1554 self.i = self.finished = 0
1555 self.blkopenline = self.indentedline = None
1556
1557 def readline(self):
1558 if self.finished:
1559 return ""
1560 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001561 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001562 if self.text.compare(mark, ">=", "end"):
1563 return ""
1564 return self.text.get(mark, mark + " lineend+1c")
1565
1566 def tokeneater(self, type, token, start, end, line,
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001567 INDENT=tokenize.INDENT,
1568 NAME=tokenize.NAME,
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001569 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1570 if self.finished:
1571 pass
1572 elif type == NAME and token in OPENERS:
1573 self.blkopenline = line
1574 elif type == INDENT and self.blkopenline:
1575 self.indentedline = line
1576 self.finished = 1
1577
1578 def run(self):
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001579 save_tabsize = tokenize.tabsize
1580 tokenize.tabsize = self.tabwidth
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001581 try:
1582 try:
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001583 tokens = tokenize.generate_tokens(self.readline)
Trent Nelson428de652008-03-18 22:41:35 +00001584 for token in tokens:
1585 self.tokeneater(*token)
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001586 except (tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001587 # since we cut off the tokenizer early, we can trigger
1588 # spurious errors
1589 pass
1590 finally:
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001591 tokenize.tabsize = save_tabsize
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001592 return self.blkopenline, self.indentedline
1593
1594### end autoindent code ###
1595
David Scherer7aced172000-08-15 01:13:23 +00001596def prepstr(s):
1597 # Helper to extract the underscore from a string, e.g.
1598 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001599 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001600 if i >= 0:
1601 s = s[:i] + s[i+1:]
1602 return i, s
1603
1604
1605keynames = {
1606 'bracketleft': '[',
1607 'bracketright': ']',
1608 'slash': '/',
1609}
1610
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001611def get_accelerator(keydefs, eventname):
1612 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001613 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1614 # if not keylist:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001615 if (not keylist) or (macosx.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001616 "<<open-module>>",
1617 "<<goto-line>>",
1618 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001619 return ""
1620 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001621 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001622 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1623 s = re.sub("Key-", "", s)
1624 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1625 s = re.sub("Control-", "Ctrl-", s)
1626 s = re.sub("-", "+", s)
1627 s = re.sub("><", " ", s)
1628 s = re.sub("<", "", s)
1629 s = re.sub(">", "", s)
1630 return s
1631
1632
1633def fixwordbreaks(root):
Terry Jan Reedy5ff3a162018-04-30 03:08:01 -04001634 # On Windows, tcl/tk breaks 'words' only on spaces, as in Command Prompt.
1635 # We want Motif style everywhere. See #21474, msg218992 and followup.
David Scherer7aced172000-08-15 01:13:23 +00001636 tk = root.tk
1637 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
Terry Jan Reedy5ff3a162018-04-30 03:08:01 -04001638 tk.call('set', 'tcl_wordchars', r'\w')
1639 tk.call('set', 'tcl_nonwordchars', r'\W')
David Scherer7aced172000-08-15 01:13:23 +00001640
1641
Terry Jan Reedycd567362014-10-17 01:31:35 -04001642def _editor_window(parent): # htest #
1643 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001644 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001645 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001646 if sys.argv[1:]:
1647 filename = sys.argv[1]
1648 else:
1649 filename = None
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001650 macosx.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001651 edit = EditorWindow(root=root, filename=filename)
Tal Einat077059e2018-08-10 09:02:08 +03001652 text = edit.text
1653 text['height'] = 10
1654 for i in range(20):
1655 text.insert('insert', ' '*i + str(i) + '\n')
1656 # text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001657 # Does not stop error, neither does following
1658 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001659
1660if __name__ == '__main__':
Terry Jan Reedyee5ef302018-06-15 18:20:55 -04001661 from unittest import main
1662 main('idlelib.idle_test.test_editor', verbosity=2, exit=False)
Terry Jan Reedy7c153412016-06-26 17:48:02 -04001663
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001664 from idlelib.idle_test.htest import run
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04001665 run(_editor_window)