blob: 7c3f215e9f96a8cc604f3c2d973cc8c79d94a185 [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
David Scherer7aced172000-08-15 01:13:23 +00005import re
Guido van Rossum33d26892007-08-05 15:29:28 +00006import string
Brett Cannonaef82d32012-04-14 20:44:23 -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 *
Terry Jan Reedy01e35752016-06-10 18:19:21 -040013from tkinter.ttk import Scrollbar
Georg Brandl14fc4272008-05-17 18:39:55 +000014import tkinter.simpledialog as tkSimpleDialog
15import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +000016
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040017from idlelib.config import idleConf
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040018from idlelib import configdialog
19from idlelib import grep
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040020from idlelib import help
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040021from idlelib import help_about
22from idlelib import macosx
23from idlelib.multicall import MultiCallCreator
24from idlelib import pyparse
25from idlelib import query
26from idlelib import replace
27from idlelib import search
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040028from idlelib import windows
David Scherer7aced172000-08-15 01:13:23 +000029
30# The default tab setting for a Text widget, in average-width characters.
31TK_TABWIDTH_DEFAULT = 8
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -040032_py_version = ' (%s)' % platform.python_version()
Miss Islington (bot)cfc12ec2018-06-04 09:05:24 -070033darwin = sys.platform == 'darwin'
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -040034
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000035def _sphinx_version():
36 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
37 major, minor, micro, level, serial = sys.version_info
38 release = '%s%s' % (major, minor)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020039 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000040 if level == 'candidate':
41 release += 'rc%s' % (serial,)
42 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000043 release += '%s%s' % (level[0], serial)
44 return release
45
Terry Jan Reedye91e7632012-02-05 15:14:20 -050046
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000047class EditorWindow(object):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040048 from idlelib.percolator import Percolator
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -040049 from idlelib.colorizer import ColorDelegator, color_config
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040050 from idlelib.undo import UndoDelegator
Terry Jan Reedy7c153412016-06-26 17:48:02 -040051 from idlelib.iomenu import IOBinding, encoding
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040052 from idlelib import mainmenu
Miss Islington (bot)cfc12ec2018-06-04 09:05:24 -070053 from tkinter import Toplevel, EventType
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040054 from idlelib.statusbar import MultiStatusBar
wohlganger58fc71c2017-09-10 16:19:47 -050055 from idlelib.autocomplete import AutoComplete
56 from idlelib.autoexpand import AutoExpand
57 from idlelib.calltips import CallTips
58 from idlelib.codecontext import CodeContext
59 from idlelib.paragraph import FormatParagraph
60 from idlelib.parenmatch import ParenMatch
61 from idlelib.rstrip import RstripExtension
62 from idlelib.zoomheight import ZoomHeight
David Scherer7aced172000-08-15 01:13:23 +000063
Terry Jan Reedy7c153412016-06-26 17:48:02 -040064 filesystemencoding = sys.getfilesystemencoding() # for file names
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000065 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000066
67 def __init__(self, flist=None, filename=None, key=None, root=None):
wohlganger58fc71c2017-09-10 16:19:47 -050068 # Delay import: runscript imports pyshell imports EditorWindow.
69 from idlelib.runscript import ScriptBinding
70
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000071 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +010072 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000073 if sys.platform.count('linux'):
74 # look for html docs in a couple of standard places
75 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
76 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
77 dochome = '/var/www/html/python/index.html'
78 else:
79 basepath = '/usr/share/doc/' # standard location
80 dochome = os.path.join(basepath, pyver,
81 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000082 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +010083 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000084 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +000085 if os.path.isfile(chmfile):
86 dochome = chmfile
Ned Deilyb7601672014-03-27 20:49:14 -070087 elif sys.platform == 'darwin':
88 # documentation may be stored inside a python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +010089 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +000090 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000091 dochome = os.path.normpath(dochome)
92 if os.path.isfile(dochome):
93 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +000094 if sys.platform == 'darwin':
95 # Safari requires real file:-URLs
96 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000097 else:
wohlganger58fc71c2017-09-10 16:19:47 -050098 EditorWindow.help_url = ("https://docs.python.org/%d.%d/"
99 % sys.version_info[:2])
David Scherer7aced172000-08-15 01:13:23 +0000100 self.flist = flist
101 root = root or flist.root
102 self.root = root
David Scherer7aced172000-08-15 01:13:23 +0000103 self.menubar = Menu(root)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400104 self.top = top = windows.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000105 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000106 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200107 #self.top.instance_dict makes flist.inversedict available to
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400108 #configdialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000109 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000110 else:
111 self.tkinter_vars = {} # keys: Tkinter event names
112 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000113 self.top.instance_dict = {}
terryjreedy223c7e72017-07-07 22:28:06 -0400114 self.recent_files_path = os.path.join(
115 idleConf.userdir, 'recent-files.lst')
Terry Jan Reedye86172d2017-10-27 20:26:12 -0400116
117 self.prompt_last_line = '' # Override in PyShell
David Scherer7aced172000-08-15 01:13:23 +0000118 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000119 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200120 self.width = idleConf.GetOption('main', 'EditorWindow',
121 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000122 text_options = {
123 'name': 'text',
124 'padx': 5,
125 'wrap': 'none',
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500126 'highlightthickness': 0,
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000127 'width': self.width,
Terry Jan Reedy1080d132016-06-09 21:09:15 -0400128 'tabstyle': 'wordprocessor', # new in 8.5
129 'height': idleConf.GetOption(
130 'main', 'EditorWindow', 'height', type='int'),
131 }
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000132 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000133 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000134
135 self.createmenubar()
136 self.apply_bindings()
137
138 self.top.protocol("WM_DELETE_WINDOW", self.close)
139 self.top.bind("<<close-window>>", self.close_event)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400140 if macosx.isAquaTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000141 # Command-W on editorwindows doesn't work without this.
142 text.bind('<<close-window>>', self.close_event)
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400143 # Some OS X systems have only one mouse button, so use
144 # control-click for popup context menus there. For two
145 # buttons, AquaTk defines <2> as the right button, not <3>.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000146 text.bind("<Control-Button-1>",self.right_menu_event)
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400147 text.bind("<2>", self.right_menu_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000148 else:
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400149 # Elsewhere, use right-click for popup menus.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000150 text.bind("<3>",self.right_menu_event)
Miss Islington (bot)cfc12ec2018-06-04 09:05:24 -0700151 text.bind('<MouseWheel>', self.mousescroll)
152 text.bind('<Button-4>', self.mousescroll)
153 text.bind('<Button-5>', self.mousescroll)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000154 text.bind("<<cut>>", self.cut)
155 text.bind("<<copy>>", self.copy)
156 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000157 text.bind("<<center-insert>>", self.center_insert_event)
158 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000159 text.bind("<<python-docs>>", self.python_docs)
160 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000161 text.bind("<<open-config-dialog>>", self.config_dialog)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300162 text.bind("<<open-module>>", self.open_module_event)
David Scherer7aced172000-08-15 01:13:23 +0000163 text.bind("<<do-nothing>>", lambda event: "break")
164 text.bind("<<select-all>>", self.select_all)
165 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000166 text.bind("<<find>>", self.find_event)
167 text.bind("<<find-again>>", self.find_again_event)
168 text.bind("<<find-in-files>>", self.find_in_files_event)
169 text.bind("<<find-selection>>", self.find_selection_event)
170 text.bind("<<replace>>", self.replace_event)
171 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000172 text.bind("<<smart-backspace>>",self.smart_backspace_event)
173 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
174 text.bind("<<smart-indent>>",self.smart_indent_event)
175 text.bind("<<indent-region>>",self.indent_region_event)
176 text.bind("<<dedent-region>>",self.dedent_region_event)
177 text.bind("<<comment-region>>",self.comment_region_event)
178 text.bind("<<uncomment-region>>",self.uncomment_region_event)
179 text.bind("<<tabify-region>>",self.tabify_region_event)
180 text.bind("<<untabify-region>>",self.untabify_region_event)
181 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
182 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000183 text.bind("<Left>", self.move_at_edge_if_selection(0))
184 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000185 text.bind("<<del-word-left>>", self.del_word_left)
186 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000187 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000188
David Scherer7aced172000-08-15 01:13:23 +0000189 if flist:
190 flist.inversedict[self] = key
191 if key:
192 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000193 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000194 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400195 text.bind("<<open-class-browser>>", self.open_module_browser)
David Scherer7aced172000-08-15 01:13:23 +0000196 text.bind("<<open-path-browser>>", self.open_path_browser)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400197 text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
David Scherer7aced172000-08-15 01:13:23 +0000198
Steven M. Gava898a3652001-10-07 11:10:44 +0000199 self.set_status_bar()
Miss Islington (bot)cfc12ec2018-06-04 09:05:24 -0700200 vbar['command'] = self.handle_yview
David Scherer7aced172000-08-15 01:13:23 +0000201 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000202 text['yscrollcommand'] = vbar.set
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400203 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000204 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
205 text.pack(side=TOP, fill=BOTH, expand=1)
206 text.focus_set()
207
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000208 # usetabs true -> literal tab characters are used by indent and
209 # dedent cmds, possibly mixed with spaces if
210 # indentwidth is not a multiple of tabwidth,
211 # which will cause Tabnanny to nag!
212 # false -> tab characters are converted to spaces by indent
213 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000214 # Although use-spaces=0 can be configured manually in config-main.def,
215 # configuration of tabs v. spaces is not supported in the configuration
216 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200217 usespaces = idleConf.GetOption('main', 'Indent',
218 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000219 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000220
221 # tabwidth is the display width of a literal tab character.
222 # CAUTION: telling Tk to use anything other than its default
223 # tab setting causes it to use an entirely different tabbing algorithm,
224 # treating tab stops as fixed distances from the left margin.
225 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000226 self.tabwidth = 8 # must remain 8 until Tk is fixed.
227
228 # indentwidth is the number of screen characters per indent level.
229 # The recommended Python indentation is four spaces.
230 self.indentwidth = self.tabwidth
231 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000232
233 # If context_use_ps1 is true, parsing searches back for a ps1 line;
234 # else searches for a popular (if, def, ...) Python stmt.
235 self.context_use_ps1 = False
236
237 # When searching backwards for a reliable place to begin parsing,
238 # first start num_context_lines[0] lines back, then
239 # num_context_lines[1] lines back if that didn't work, and so on.
240 # The last value should be huge (larger than the # of lines in a
241 # conceivable file).
242 # Making the initial values larger slows things down more often.
243 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000244 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000245 self.undo = undo = self.UndoDelegator()
246 per.insertfilter(undo)
247 text.undo_block_start = undo.undo_block_start
248 text.undo_block_stop = undo.undo_block_stop
249 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000250 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000251 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000252 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000253 self.good_load = False
254 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000255 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000256 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000257 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000258 if io.loadfile(filename):
259 self.good_load = True
260 is_py_src = self.ispythonsource(filename)
261 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000262 else:
263 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500264 self.good_load = True
265
Christian Heimesa156e092008-02-16 07:38:31 +0000266 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000267 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000268 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000269 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000270 menu = self.menudict.get('windows')
271 if menu:
272 end = menu.index("end")
273 if end is None:
274 end = -1
275 if end >= 0:
276 menu.add_separator()
277 end = end + 1
278 self.wmenu_end = end
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400279 windows.register_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000280
281 # Some abstractions so IDLE extensions are cross-IDE
282 self.askyesno = tkMessageBox.askyesno
283 self.askinteger = tkSimpleDialog.askinteger
284 self.showerror = tkMessageBox.showerror
285
wohlganger58fc71c2017-09-10 16:19:47 -0500286 # Add pseudoevents for former extension fixed keys.
287 # (This probably needs to be done once in the process.)
288 text.event_add('<<autocomplete>>', '<Key-Tab>')
289 text.event_add('<<try-open-completions>>', '<KeyRelease-period>',
290 '<KeyRelease-slash>', '<KeyRelease-backslash>')
291 text.event_add('<<try-open-calltip>>', '<KeyRelease-parenleft>')
292 text.event_add('<<refresh-calltip>>', '<KeyRelease-parenright>')
293 text.event_add('<<paren-closed>>', '<KeyRelease-parenright>',
294 '<KeyRelease-bracketright>', '<KeyRelease-braceright>')
295
296 # Former extension bindings depends on frame.text being packed
297 # (called from self.ResetColorizer()).
298 autocomplete = self.AutoComplete(self)
299 text.bind("<<autocomplete>>", autocomplete.autocomplete_event)
300 text.bind("<<try-open-completions>>",
301 autocomplete.try_open_completions_event)
302 text.bind("<<force-open-completions>>",
303 autocomplete.force_open_completions_event)
304 text.bind("<<expand-word>>", self.AutoExpand(self).expand_word_event)
305 text.bind("<<format-paragraph>>",
306 self.FormatParagraph(self).format_paragraph_event)
307 parenmatch = self.ParenMatch(self)
308 text.bind("<<flash-paren>>", parenmatch.flash_paren_event)
309 text.bind("<<paren-closed>>", parenmatch.paren_closed_event)
310 scriptbinding = ScriptBinding(self)
311 text.bind("<<check-module>>", scriptbinding.check_module_event)
312 text.bind("<<run-module>>", scriptbinding.run_module_event)
313 text.bind("<<do-rstrip>>", self.RstripExtension(self).do_rstrip)
314 calltips = self.CallTips(self)
315 text.bind("<<try-open-calltip>>", calltips.try_open_calltip_event)
316 #refresh-calltips must come after paren-closed to work right
317 text.bind("<<refresh-calltip>>", calltips.refresh_calltip_event)
318 text.bind("<<force-open-calltip>>", calltips.force_open_calltip_event)
319 text.bind("<<zoom-height>>", self.ZoomHeight(self).zoom_height_event)
320 text.bind("<<toggle-code-context>>",
321 self.CodeContext(self).toggle_code_context_event)
322
Martin v. Löwis307021f2005-11-27 16:59:04 +0000323 def _filename_to_unicode(self, filename):
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400324 """Return filename as BMP unicode so diplayable in Tk."""
325 # Decode bytes to unicode.
326 if isinstance(filename, bytes):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000327 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400328 filename = filename.decode(self.filesystemencoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000329 except UnicodeDecodeError:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000330 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400331 filename = filename.decode(self.encoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000332 except UnicodeDecodeError:
333 # byte-to-byte conversion
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400334 filename = filename.decode('iso8859-1')
335 # Replace non-BMP char with diamond questionmark.
336 return re.sub('[\U00010000-\U0010FFFF]', '\ufffd', filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000337
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000338 def new_callback(self, event):
339 dirname, basename = self.io.defaultfilename()
340 self.flist.new(dirname)
341 return "break"
342
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000343 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400344 if (event.state & 4) != 0 and event.keysym == "Home":
345 # state&4==Control. If <Control-Home>, use the Tk binding.
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300346 return None
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000347 if self.text.index("iomark") and \
348 self.text.compare("iomark", "<=", "insert lineend") and \
349 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400350 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000351 insertpt = int(self.text.index("iomark").split(".")[1])
352 else:
353 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000354 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000355 if line[insertpt] not in (' ','\t'):
356 break
357 else:
358 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000359 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000360 if insertpt == lineat:
361 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000362 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000363 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400364 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000365 self.text.tag_remove("sel", "1.0", "end")
366 else:
367 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200368 # there was no previous selection
369 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400370 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200371 if self.text.compare(self.text.index("sel.first"), "<",
372 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400373 self.text.mark_set("my_anchor", "sel.first") # extend back
374 else:
375 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000376 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400377 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000378 if self.text.compare(first,">",last):
379 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000380 self.text.tag_remove("sel", "1.0", "end")
381 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000382 self.text.mark_set("insert", dest)
383 self.text.see("insert")
384 return "break"
385
David Scherer7aced172000-08-15 01:13:23 +0000386 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000387 self.status_bar = self.MultiStatusBar(self.top)
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500388 sep = Frame(self.top, height=1, borderwidth=1, background='grey75')
Ned Deilyb7601672014-03-27 20:49:14 -0700389 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000390 # Insert some padding to avoid obscuring some of the statusbar
391 # by the resize widget.
392 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000393 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
394 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
395 self.status_bar.pack(side=BOTTOM, fill=X)
Terry Jan Reedyd36d8172015-11-16 07:32:26 -0500396 sep.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000397 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
398 self.text.event_add("<<set-line-and-column>>",
399 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000400 self.text.after_idle(self.set_line_and_column)
401
402 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000403 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000404 self.status_bar.set_label('column', 'Col: %s' % column)
405 self.status_bar.set_label('line', 'Ln: %s' % line)
406
David Scherer7aced172000-08-15 01:13:23 +0000407 menu_specs = [
408 ("file", "_File"),
409 ("edit", "_Edit"),
410 ("format", "F_ormat"),
411 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000412 ("options", "_Options"),
Ned Deilyccb416f2015-01-17 21:06:27 -0800413 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000414 ("help", "_Help"),
415 ]
416
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000417
David Scherer7aced172000-08-15 01:13:23 +0000418 def createmenubar(self):
419 mbar = self.menubar
420 self.menudict = menudict = {}
421 for name, label in self.menu_specs:
422 underline, label = prepstr(label)
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400423 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000424 mbar.add_cascade(label=label, menu=menu, underline=underline)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400425 if macosx.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000426 # Insert the application menu
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400427 menudict['application'] = menu = Menu(mbar, name='apple',
428 tearoff=0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000429 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000430 self.fill_menus()
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400431 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000432 self.menudict['file'].insert_cascade(3, label='Recent Files',
433 underline=0,
434 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000435 self.base_helpmenu_length = self.menudict['help'].index(END)
436 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000437
438 def postwindowsmenu(self):
439 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000440 menu = self.menudict['windows']
441 end = menu.index("end")
442 if end is None:
443 end = -1
444 if end > self.wmenu_end:
445 menu.delete(self.wmenu_end+1, end)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400446 windows.add_windows_to_menu(menu)
David Scherer7aced172000-08-15 01:13:23 +0000447
Miss Islington (bot)cfc12ec2018-06-04 09:05:24 -0700448 def handle_yview(self, event, *args):
449 "Handle scrollbar."
450 if event == 'moveto':
451 fraction = float(args[0])
452 lines = (round(self.getlineno('end') * fraction) -
453 self.getlineno('@0,0'))
454 event = 'scroll'
455 args = (lines, 'units')
456 self.text.yview(event, *args)
457 return 'break'
458
459 def mousescroll(self, event):
460 "Handle scroll wheel."
461 up = {EventType.MouseWheel: event.delta >= 0 == darwin,
462 EventType.Button: event.num == 4}
463 lines = 5
464 if up[event.type]:
465 lines = -lines
466 self.text.yview_scroll(lines, 'units')
467 return 'break'
468
David Scherer7aced172000-08-15 01:13:23 +0000469 rmenu = None
470
471 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000472 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
473 if not self.rmenu:
474 self.make_rmenu()
475 rmenu = self.rmenu
476 self.event = event
477 iswin = sys.platform[:3] == 'win'
478 if iswin:
479 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200480
Roger Serwy6b2918a2013-04-07 12:15:52 -0500481 for item in self.rmenu_specs:
482 try:
483 label, eventname, verify_state = item
484 except ValueError: # see issue1207589
485 continue
486
Andrew Svetlovd1837672012-11-01 22:41:19 +0200487 if verify_state is None:
488 continue
489 state = getattr(self, verify_state)()
490 rmenu.entryconfigure(label, state=state)
491
492
David Scherer7aced172000-08-15 01:13:23 +0000493 rmenu.tk_popup(event.x_root, event.y_root)
494 if iswin:
495 self.text.config(cursor="ibeam")
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300496 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000497
498 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200499 # ("Label", "<<virtual-event>>", "statefuncname"), ...
500 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000501 ]
502
503 def make_rmenu(self):
504 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500505 for item in self.rmenu_specs:
506 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200507 if label is not None:
508 def command(text=self.text, eventname=eventname):
509 text.event_generate(eventname)
510 rmenu.add_command(label=label, command=command)
511 else:
512 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000513 self.rmenu = rmenu
514
Andrew Svetlovd1837672012-11-01 22:41:19 +0200515 def rmenu_check_cut(self):
516 return self.rmenu_check_copy()
517
518 def rmenu_check_copy(self):
519 try:
520 indx = self.text.index('sel.first')
521 except TclError:
522 return 'disabled'
523 else:
524 return 'normal' if indx else 'disabled'
525
526 def rmenu_check_paste(self):
527 try:
528 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
529 except TclError:
530 return 'disabled'
531 else:
532 return 'normal'
533
David Scherer7aced172000-08-15 01:13:23 +0000534 def about_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400535 "Handle Help 'About IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400536 # Synchronize with macosx.overrideRootMenu.about_dialog.
csabella18ede062017-06-23 20:00:58 -0400537 help_about.AboutDialog(self.top)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300538 return "break"
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000539
Steven M. Gava3b55a892001-11-21 05:56:26 +0000540 def config_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400541 "Handle Options 'Configure IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400542 # Synchronize with macosx.overrideRootMenu.config_dialog.
543 configdialog.ConfigDialog(self.top,'Settings')
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300544 return "break"
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400545
David Scherer7aced172000-08-15 01:13:23 +0000546 def help_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400547 "Handle Help 'IDLE Help' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400548 # Synchronize with macosx.overrideRootMenu.help_dialog.
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500549 if self.root:
550 parent = self.root
551 else:
552 parent = self.top
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400553 help.show_idlehelp(parent)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300554 return "break"
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000555
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000556 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000557 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000558 try:
559 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200560 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000561 tkMessageBox.showerror(title='Document Start Failure',
562 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000563 else:
564 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000565 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000566
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000567 def cut(self,event):
568 self.text.event_generate("<<Cut>>")
569 return "break"
570
571 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000572 if not self.text.tag_ranges("sel"):
573 # There is no selection, so do nothing and maybe interrupt.
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300574 return None
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000575 self.text.event_generate("<<Copy>>")
576 return "break"
577
578 def paste(self,event):
579 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000580 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000581 return "break"
582
David Scherer7aced172000-08-15 01:13:23 +0000583 def select_all(self, event=None):
584 self.text.tag_add("sel", "1.0", "end-1c")
585 self.text.mark_set("insert", "1.0")
586 self.text.see("insert")
587 return "break"
588
589 def remove_selection(self, event=None):
590 self.text.tag_remove("sel", "1.0", "end")
591 self.text.see("insert")
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300592 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000593
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000594 def move_at_edge_if_selection(self, edge_index):
595 """Cursor move begins at start or end of selection
596
597 When a left/right cursor key is pressed create and return to Tkinter a
598 function which causes a cursor move from the associated edge of the
599 selection.
600
601 """
602 self_text_index = self.text.index
603 self_text_mark_set = self.text.mark_set
604 edges_table = ("sel.first+1c", "sel.last-1c")
605 def move_at_edge(event):
606 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
607 try:
608 self_text_index("sel.first")
609 self_text_mark_set("insert", edges_table[edge_index])
610 except TclError:
611 pass
612 return move_at_edge
613
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000614 def del_word_left(self, event):
615 self.text.event_generate('<Meta-Delete>')
616 return "break"
617
618 def del_word_right(self, event):
619 self.text.event_generate('<Meta-d>')
620 return "break"
621
Steven M. Gavac5976402002-01-04 03:06:08 +0000622 def find_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400623 search.find(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000624 return "break"
625
626 def find_again_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400627 search.find_again(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000628 return "break"
629
630 def find_selection_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400631 search.find_selection(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000632 return "break"
633
634 def find_in_files_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400635 grep.grep(self.text, self.io, self.flist)
Steven M. Gavac5976402002-01-04 03:06:08 +0000636 return "break"
637
638 def replace_event(self, event):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400639 replace.replace(self.text)
Steven M. Gavac5976402002-01-04 03:06:08 +0000640 return "break"
641
642 def goto_line_event(self, event):
643 text = self.text
644 lineno = tkSimpleDialog.askinteger("Goto",
645 "Go to line number:",parent=text)
646 if lineno is None:
647 return "break"
648 if lineno <= 0:
649 text.bell()
650 return "break"
651 text.mark_set("insert", "%d.0" % lineno)
652 text.see("insert")
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300653 return "break"
Steven M. Gavac5976402002-01-04 03:06:08 +0000654
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300655 def open_module(self):
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400656 """Get module name from user and open it.
657
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400658 Return module path or None for calls by open_module_browser
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400659 when latter is not invoked in named editor window.
660 """
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400661 # XXX This, open_module_browser, and open_path_browser
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400662 # would fit better in iomenu.IOBinding.
David Scherer7aced172000-08-15 01:13:23 +0000663 try:
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400664 name = self.text.get("sel.first", "sel.last").strip()
David Scherer7aced172000-08-15 01:13:23 +0000665 except TclError:
Terry Jan Reedy0cd6b972016-07-03 19:11:13 -0400666 name = ''
667 file_path = query.ModuleName(
668 self.text, "Open Module",
669 "Enter the name of a Python module\n"
670 "to search on sys.path and open:",
671 name).result
672 if file_path is not None:
673 if self.flist:
674 self.flist.open(file_path)
675 else:
676 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400677 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000678
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300679 def open_module_event(self, event):
680 self.open_module()
681 return "break"
682
Cheryl Sabellacd99e792017-09-23 16:46:01 -0400683 def open_module_browser(self, event=None):
David Scherer7aced172000-08-15 01:13:23 +0000684 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400685 if not (self.__class__.__name__ == 'PyShellEditorWindow'
686 and filename):
687 filename = self.open_module()
688 if filename is None:
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300689 return "break"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400690 from idlelib import browser
Terry Jan Reedyd6bb65f2017-09-30 19:54:28 -0400691 browser.ModuleBrowser(self.root, filename)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300692 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000693
694 def open_path_browser(self, event=None):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400695 from idlelib import pathbrowser
Cheryl Sabella20d48a42017-11-22 19:05:25 -0500696 pathbrowser.PathBrowser(self.root)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300697 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000698
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400699 def open_turtle_demo(self, event = None):
700 import subprocess
701
702 cmd = [sys.executable,
703 '-c',
704 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400705 subprocess.Popen(cmd, shell=False)
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300706 return "break"
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400707
David Scherer7aced172000-08-15 01:13:23 +0000708 def gotoline(self, lineno):
709 if lineno is not None and lineno > 0:
710 self.text.mark_set("insert", "%d.0" % lineno)
711 self.text.tag_remove("sel", "1.0", "end")
712 self.text.tag_add("sel", "insert", "insert +1l")
713 self.center()
714
715 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000716 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000717 return True
David Scherer7aced172000-08-15 01:13:23 +0000718 base, ext = os.path.splitext(os.path.basename(filename))
719 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000720 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000721 line = self.text.get('1.0', '1.0 lineend')
722 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000723
724 def close_hook(self):
725 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000726 self.flist.unregister_maybe_terminate(self)
727 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000728
729 def set_close_hook(self, close_hook):
730 self.close_hook = close_hook
731
732 def filename_change_hook(self):
733 if self.flist:
734 self.flist.filename_changed_edit(self)
735 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000736 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000737 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000738
Christian Heimesa156e092008-02-16 07:38:31 +0000739 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000740 if self.color:
741 return
Christian Heimesa156e092008-02-16 07:38:31 +0000742 if self.ispythonsource(self.io.filename):
743 self.color = self.ColorDelegator()
744 # can add more colorizers here...
745 if self.color:
746 self.per.removefilter(self.undo)
747 self.per.insertfilter(self.color)
748 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000749
Christian Heimesa156e092008-02-16 07:38:31 +0000750 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000751 if not self.color:
752 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000753 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000754 self.per.removefilter(self.color)
755 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000756
Steven M. Gavab77d3432002-03-02 07:16:21 +0000757 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400758 "Update the color theme"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400759 # Called from self.filename_change_hook and from configdialog.py
Christian Heimesa156e092008-02-16 07:38:31 +0000760 self._rmcolorizer()
761 self._addcolorizer()
Terry Jan Reedy2bac3b72016-05-29 01:40:22 -0400762 EditorWindow.color_config(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000763
Guido van Rossum33d26892007-08-05 15:29:28 +0000764 IDENTCHARS = string.ascii_letters + string.digits + "_"
765
766 def colorize_syntax_error(self, text, pos):
767 text.tag_add("ERROR", pos)
768 char = text.get(pos)
769 if char and char in self.IDENTCHARS:
770 text.tag_add("ERROR", pos + " wordstart", pos)
771 if '\n' == text.get(pos): # error at line end
772 text.mark_set("insert", pos)
773 else:
774 text.mark_set("insert", pos + "+1c")
775 text.see(pos)
776
Steven M. Gavab1585412002-03-12 00:21:56 +0000777 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000778 "Update the text widgets' font if it is changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400779 # Called from configdialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400780
781 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000782
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000783 def RemoveKeybindings(self):
784 "Remove the keybindings before they are changed."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400785 # Called from configdialog.py
786 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000787 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000788 self.text.event_delete(event, *keylist)
789 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000790 xkeydefs = idleConf.GetExtensionBindings(extensionName)
791 if xkeydefs:
792 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000793 self.text.event_delete(event, *keylist)
794
795 def ApplyKeybindings(self):
796 "Update the keybindings after they are changed"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400797 # Called from configdialog.py
798 self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000799 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000800 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000801 xkeydefs = idleConf.GetExtensionBindings(extensionName)
802 if xkeydefs:
803 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000804 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000805 menuEventDict = {}
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400806 for menu in self.mainmenu.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000807 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000808 for item in menu[1]:
809 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000810 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000811 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000812 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700813 end = menu.index(END)
814 if end is None:
815 # Skip empty menus
816 continue
817 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000818 for index in range(0, end):
819 if menu.type(index) == 'command':
820 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000821 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000822 itemName = menu.entrycget(index, 'label')
823 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000824 if menubarItem in menuEventDict:
825 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000826 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000827 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000828 accel = get_accelerator(keydefs, event)
829 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000830
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000831 def set_notabs_indentwidth(self):
832 "Update the indentwidth if changed and not using tabs in this window"
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400833 # Called from configdialog.py
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000834 if not self.usetabs:
835 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
836 type='int')
837
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000838 def reset_help_menu_entries(self):
839 "Update the additional help entries on the Help menu"
840 help_list = idleConf.GetAllExtraHelpSourcesList()
841 helpmenu = self.menudict['help']
842 # first delete the extra help entries, if any
843 helpmenu_length = helpmenu.index(END)
844 if helpmenu_length > self.base_helpmenu_length:
845 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
846 # then rebuild them
847 if help_list:
848 helpmenu.add_separator()
849 for entry in help_list:
850 cmd = self.__extra_help_callback(entry[1])
851 helpmenu.add_command(label=entry[0], command=cmd)
852 # and update the menu dictionary
853 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000854
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000855 def __extra_help_callback(self, helpfile):
856 "Create a callback with the helpfile value frozen at definition time"
857 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000858 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000859 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000860 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000861 try:
862 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200863 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000864 tkMessageBox.showerror(title='Document Start Failure',
865 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000866 else:
867 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000868 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000869
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000870 def update_recent_files_list(self, new_file=None):
871 "Load and update the recent files list and menus"
872 rf_list = []
873 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400874 with open(self.recent_files_path, 'r',
875 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000876 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000877 if new_file:
878 new_file = os.path.abspath(new_file) + '\n'
879 if new_file in rf_list:
880 rf_list.remove(new_file) # move to top
881 rf_list.insert(0, new_file)
882 # clean and save the recent files list
883 bad_paths = []
884 for path in rf_list:
885 if '\0' in path or not os.path.exists(path[0:-1]):
886 bad_paths.append(path)
887 rf_list = [path for path in rf_list if path not in bad_paths]
888 ulchars = "1234567890ABCDEFGHIJK"
889 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000890 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800891 with open(self.recent_files_path, 'w',
892 encoding='utf_8', errors='replace') as rf_file:
893 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200894 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800895 if not getattr(self.root, "recentfilelist_error_displayed", False):
896 self.root.recentfilelist_error_displayed = True
Terry Jan Reedy4c973e92015-10-27 03:38:02 -0400897 tkMessageBox.showwarning(title='IDLE Warning',
898 message="Cannot update File menu Recent Files list. "
899 "Your operating system says:\n%s\n"
900 "Select OK and IDLE will continue without updating."
901 % self._filename_to_unicode(str(err)),
Ned Deilyf505b742011-12-14 14:58:24 -0800902 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000903 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000904 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000905 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700906 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000907 for i, file_name in enumerate(rf_list):
908 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000909 # make unicode string to display non-ASCII chars correctly
910 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000911 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000912 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000913 command=callback,
914 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000915
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000916 def __recent_file_callback(self, file_name):
917 def open_recent_file(fn_closure=file_name):
918 self.io.open(editFile=fn_closure)
919 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000920
David Scherer7aced172000-08-15 01:13:23 +0000921 def saved_change_hook(self):
922 short = self.short_title()
923 long = self.long_title()
924 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400925 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000926 elif short:
927 title = short
928 elif long:
929 title = long
930 else:
931 title = "Untitled"
932 icon = short or long or title
933 if not self.get_saved():
934 title = "*%s*" % title
935 icon = "*%s" % icon
936 self.top.wm_title(title)
937 self.top.wm_iconname(icon)
938
939 def get_saved(self):
940 return self.undo.get_saved()
941
942 def set_saved(self, flag):
943 self.undo.set_saved(flag)
944
945 def reset_undo(self):
946 self.undo.reset_undo()
947
948 def short_title(self):
949 filename = self.io.filename
950 if filename:
951 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500952 else:
953 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000954 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400955 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000956
957 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000958 # return unicode string to display non-ASCII chars correctly
959 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000960
961 def center_insert_event(self, event):
962 self.center()
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300963 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000964
965 def center(self, mark="insert"):
966 text = self.text
967 top, bot = self.getwindowlines()
968 lineno = self.getlineno(mark)
969 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000970 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000971 text.yview(float(newtop))
972
973 def getwindowlines(self):
974 text = self.text
975 top = self.getlineno("@0,0")
976 bot = self.getlineno("@0,65535")
977 if top == bot and text.winfo_height() == 1:
978 # Geometry manager hasn't run yet
979 height = int(text['height'])
980 bot = top + height - 1
981 return top, bot
982
983 def getlineno(self, mark="insert"):
984 text = self.text
985 return int(float(text.index(mark)))
986
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000987 def get_geometry(self):
988 "Return (width, height, x, y)"
989 geom = self.top.wm_geometry()
990 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000991 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000992
David Scherer7aced172000-08-15 01:13:23 +0000993 def close_event(self, event):
994 self.close()
Serhiy Storchaka213ce122017-06-27 07:02:32 +0300995 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000996
997 def maybesave(self):
998 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000999 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001000 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001001 self.top.deiconify()
1002 self.top.lower()
1003 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001004 return self.io.maybesave()
1005
1006 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001007 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +00001008 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001009 self._close()
1010 return reply
1011
1012 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001013 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001014 self.update_recent_files_list(new_file=self.io.filename)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001015 windows.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001016 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001017 self.io.close()
1018 self.io = None
1019 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001020 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001021 self.color.close(False)
1022 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001023 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001024 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001025 self.per.close()
1026 self.per = None
1027 self.top.destroy()
1028 if self.close_hook:
1029 # unless override: unregister from flist, terminate if last window
1030 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001031
1032 def load_extensions(self):
1033 self.extensions = {}
1034 self.load_standard_extensions()
1035
1036 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001037 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001038 if hasattr(ins, "close"):
1039 ins.close()
1040 self.extensions = {}
1041
1042 def load_standard_extensions(self):
1043 for name in self.get_standard_extension_names():
1044 try:
1045 self.load_extension(name)
1046 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001047 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001048 traceback.print_exc()
1049
1050 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001051 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001052
wohlganger58fc71c2017-09-10 16:19:47 -05001053 extfiles = { # Map built-in config-extension section names to file names.
1054 'ZzDummy': 'zzdummy',
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001055 }
1056
David Scherer7aced172000-08-15 01:13:23 +00001057 def load_extension(self, name):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001058 fname = self.extfiles.get(name, name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001059 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001060 try:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001061 mod = importlib.import_module('.' + fname, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001062 except (ImportError, TypeError):
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001063 mod = importlib.import_module(fname)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001064 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001065 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001066 raise
David Scherer7aced172000-08-15 01:13:23 +00001067 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001068 keydefs = idleConf.GetExtensionBindings(name)
1069 if hasattr(cls, "menudefs"):
1070 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001071 ins = cls(self)
1072 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001073 if keydefs:
1074 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001075 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001076 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001077 while methodname[:1] == '<':
1078 methodname = methodname[1:]
1079 while methodname[-1:] == '>':
1080 methodname = methodname[:-1]
1081 methodname = methodname + "_event"
1082 if hasattr(ins, methodname):
1083 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001084
1085 def apply_bindings(self, keydefs=None):
1086 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001087 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001088 text = self.text
1089 text.keydefs = keydefs
1090 for event, keylist in keydefs.items():
1091 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001092 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001093
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001094 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001095 """Add appropriate entries to the menus and submenus
1096
1097 Menus that are absent or None in self.menudict are ignored.
1098 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001099 if menudefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001100 menudefs = self.mainmenu.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001101 if keydefs is None:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001102 keydefs = self.mainmenu.default_keydefs
David Scherer7aced172000-08-15 01:13:23 +00001103 menudict = self.menudict
1104 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001105 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001106 menu = menudict.get(mname)
1107 if not menu:
1108 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001109 for entry in entrylist:
1110 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001111 menu.add_separator()
1112 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001113 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001114 checkbutton = (label[:1] == '!')
1115 if checkbutton:
1116 label = label[1:]
1117 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001118 accelerator = get_accelerator(keydefs, eventname)
1119 def command(text=text, eventname=eventname):
1120 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001121 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001122 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001123 menu.add_checkbutton(label=label, underline=underline,
1124 command=command, accelerator=accelerator,
1125 variable=var)
1126 else:
1127 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001128 command=command,
1129 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001130
1131 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001132 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001133 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001134 value = var.get()
1135 return value
1136 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001137 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001138
1139 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001140 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001141 if var:
1142 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001143 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001144 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001145
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001146 def get_var_obj(self, name, vartype=None):
1147 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001148 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001149 # create a Tkinter variable object with self.text as master:
1150 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001151 return var
1152
1153 # Tk implementations of "virtual text methods" -- each platform
1154 # reusing IDLE's support code needs to define these for its GUI's
1155 # flavor of widget.
1156
1157 # Is character at text_index in a Python string? Return 0 for
1158 # "guaranteed no", true for anything else. This info is expensive
1159 # to compute ab initio, but is probably already known by the
1160 # platform's colorizer.
1161
1162 def is_char_in_string(self, text_index):
1163 if self.color:
1164 # Return true iff colorizer hasn't (re)gotten this far
1165 # yet, or the character is tagged as being in a string
1166 return self.text.tag_prevrange("TODO", text_index) or \
1167 "STRING" in self.text.tag_names(text_index)
1168 else:
1169 # The colorizer is missing: assume the worst
1170 return 1
1171
1172 # If a selection is defined in the text widget, return (start,
1173 # end) as Tkinter text indices, otherwise return (None, None)
1174 def get_selection_indices(self):
1175 try:
1176 first = self.text.index("sel.first")
1177 last = self.text.index("sel.last")
1178 return first, last
1179 except TclError:
1180 return None, None
1181
1182 # Return the text widget's current view of what a tab stop means
1183 # (equivalent width in spaces).
1184
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001185 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001186 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1187 return int(current)
1188
1189 # Set the text widget's current view of what a tab stop means.
1190
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001191 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001192 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001193 if self.get_tk_tabwidth() != newtabwidth:
1194 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001195 pixels = text.tk.call("font", "measure", text["font"],
1196 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001197 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001198 text.configure(tabs=pixels)
1199
Guido van Rossum33d26892007-08-05 15:29:28 +00001200### begin autoindent code ### (configuration was moved to beginning of class)
1201
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001202 def set_indentation_params(self, is_py_src, guess=True):
1203 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001204 i = self.guess_indent()
1205 if 2 <= i <= 8:
1206 self.indentwidth = i
1207 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001208 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001209 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001210
1211 def smart_backspace_event(self, event):
1212 text = self.text
1213 first, last = self.get_selection_indices()
1214 if first and last:
1215 text.delete(first, last)
1216 text.mark_set("insert", first)
1217 return "break"
1218 # Delete whitespace left, until hitting a real char or closest
1219 # preceding virtual tab stop.
1220 chars = text.get("insert linestart", "insert")
1221 if chars == '':
1222 if text.compare("insert", ">", "1.0"):
1223 # easy: delete preceding newline
1224 text.delete("insert-1c")
1225 else:
1226 text.bell() # at start of buffer
1227 return "break"
1228 if chars[-1] not in " \t":
1229 # easy: delete preceding real char
1230 text.delete("insert-1c")
1231 return "break"
1232 # Ick. It may require *inserting* spaces if we back up over a
1233 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001234 tabwidth = self.tabwidth
1235 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001236 assert have > 0
1237 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001238 # Debug prompt is multilined....
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001239 ncharsdeleted = 0
1240 while 1:
Terry Jan Reedye86172d2017-10-27 20:26:12 -04001241 if chars == self.prompt_last_line: # '' unless PyShell
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001242 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001243 chars = chars[:-1]
1244 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001245 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001246 if have <= want or chars[-1] not in " \t":
1247 break
1248 text.undo_block_start()
1249 text.delete("insert-%dc" % ncharsdeleted, "insert")
1250 if have < want:
1251 text.insert("insert", ' ' * (want - have))
1252 text.undo_block_stop()
1253 return "break"
1254
1255 def smart_indent_event(self, event):
1256 # if intraline selection:
1257 # delete it
1258 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001259 # do indent-region
1260 # else:
1261 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001262 text = self.text
1263 first, last = self.get_selection_indices()
1264 text.undo_block_start()
1265 try:
1266 if first and last:
1267 if index2line(first) != index2line(last):
1268 return self.indent_region_event(event)
1269 text.delete(first, last)
1270 text.mark_set("insert", first)
1271 prefix = text.get("insert linestart", "insert")
1272 raw, effective = classifyws(prefix, self.tabwidth)
1273 if raw == len(prefix):
1274 # only whitespace to the left
1275 self.reindent_to(effective + self.indentwidth)
1276 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001277 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001278 if self.usetabs:
1279 pad = '\t'
1280 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001281 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001282 n = self.indentwidth
1283 pad = ' ' * (n - effective % n)
1284 text.insert("insert", pad)
1285 text.see("insert")
1286 return "break"
1287 finally:
1288 text.undo_block_stop()
1289
1290 def newline_and_indent_event(self, event):
1291 text = self.text
1292 first, last = self.get_selection_indices()
1293 text.undo_block_start()
1294 try:
1295 if first and last:
1296 text.delete(first, last)
1297 text.mark_set("insert", first)
1298 line = text.get("insert linestart", "insert")
1299 i, n = 0, len(line)
1300 while i < n and line[i] in " \t":
1301 i = i+1
1302 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001303 # the cursor is in or at leading indentation in a continuation
1304 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001305 text.insert("insert linestart", '\n')
1306 return "break"
1307 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001308 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001309 i = 0
Terry Jan Reedye86172d2017-10-27 20:26:12 -04001310 while line and line[-1] in " \t" and line != self.prompt_last_line:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001311 line = line[:-1]
1312 i = i+1
1313 if i:
1314 text.delete("insert - %d chars" % i, "insert")
1315 # strip whitespace after insert point
1316 while text.get("insert") in " \t":
1317 text.delete("insert")
1318 # start new line
1319 text.insert("insert", '\n')
1320
1321 # adjust indentation for continuations and block
1322 # open/close first need to find the last stmt
1323 lno = index2line(text.index('insert'))
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001324 y = pyparse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001325 if not self.context_use_ps1:
1326 for context in self.num_context_lines:
1327 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001328 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001329 rawtext = text.get(startatindex, "insert")
Miss Islington (bot)f409c992018-02-23 18:59:53 -08001330 y.set_code(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001331 bod = y.find_good_parse_start(
1332 self.context_use_ps1,
1333 self._build_char_in_string_func(startatindex))
1334 if bod is not None or startat == 1:
1335 break
1336 y.set_lo(bod or 0)
1337 else:
1338 r = text.tag_prevrange("console", "insert")
1339 if r:
1340 startatindex = r[1]
1341 else:
1342 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001343 rawtext = text.get(startatindex, "insert")
Miss Islington (bot)f409c992018-02-23 18:59:53 -08001344 y.set_code(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001345 y.set_lo(0)
1346
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001347 c = y.get_continuation_type()
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001348 if c != pyparse.C_NONE:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001349 # The current stmt hasn't ended yet.
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001350 if c == pyparse.C_STRING_FIRST_LINE:
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001351 # after the first line of a string; do not indent at all
1352 pass
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001353 elif c == pyparse.C_STRING_NEXT_LINES:
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001354 # inside a string which started before this line;
1355 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001356 text.insert("insert", indent)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001357 elif c == pyparse.C_BRACKET:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001358 # line up with the first (if any) element of the
1359 # last open bracket structure; else indent one
1360 # level beyond the indent of the line with the
1361 # last open bracket
1362 self.reindent_to(y.compute_bracket_indent())
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001363 elif c == pyparse.C_BACKSLASH:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001364 # if more than one line in this stmt already, just
1365 # mimic the current indent; else if initial line
1366 # has a start on an assignment stmt, indent to
1367 # beyond leftmost =; else to beyond first chunk of
1368 # non-whitespace on initial line
1369 if y.get_num_lines_in_stmt() > 1:
1370 text.insert("insert", indent)
1371 else:
1372 self.reindent_to(y.compute_backslash_indent())
1373 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001374 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001375 return "break"
1376
1377 # This line starts a brand new stmt; indent relative to
1378 # indentation of initial line of closest preceding
1379 # interesting stmt.
1380 indent = y.get_base_indent_string()
1381 text.insert("insert", indent)
1382 if y.is_block_opener():
1383 self.smart_indent_event(event)
1384 elif indent and y.is_block_closer():
1385 self.smart_backspace_event(event)
1386 return "break"
1387 finally:
1388 text.see("insert")
1389 text.undo_block_stop()
1390
Martin Panter7462b6492015-11-02 03:37:02 +00001391 # Our editwin provides an is_char_in_string function that works
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001392 # with a Tk text index, but PyParse only knows about offsets into
1393 # a string. This builds a function for PyParse that accepts an
1394 # offset.
1395
1396 def _build_char_in_string_func(self, startindex):
1397 def inner(offset, _startindex=startindex,
1398 _icis=self.is_char_in_string):
1399 return _icis(_startindex + "+%dc" % offset)
1400 return inner
1401
1402 def indent_region_event(self, event):
1403 head, tail, chars, lines = self.get_region()
1404 for pos in range(len(lines)):
1405 line = lines[pos]
1406 if line:
1407 raw, effective = classifyws(line, self.tabwidth)
1408 effective = effective + self.indentwidth
1409 lines[pos] = self._make_blanks(effective) + line[raw:]
1410 self.set_region(head, tail, chars, lines)
1411 return "break"
1412
1413 def dedent_region_event(self, event):
1414 head, tail, chars, lines = self.get_region()
1415 for pos in range(len(lines)):
1416 line = lines[pos]
1417 if line:
1418 raw, effective = classifyws(line, self.tabwidth)
1419 effective = max(effective - self.indentwidth, 0)
1420 lines[pos] = self._make_blanks(effective) + line[raw:]
1421 self.set_region(head, tail, chars, lines)
1422 return "break"
1423
1424 def comment_region_event(self, event):
1425 head, tail, chars, lines = self.get_region()
1426 for pos in range(len(lines) - 1):
1427 line = lines[pos]
1428 lines[pos] = '##' + line
1429 self.set_region(head, tail, chars, lines)
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001430 return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001431
1432 def uncomment_region_event(self, event):
1433 head, tail, chars, lines = self.get_region()
1434 for pos in range(len(lines)):
1435 line = lines[pos]
1436 if not line:
1437 continue
1438 if line[:2] == '##':
1439 line = line[2:]
1440 elif line[:1] == '#':
1441 line = line[1:]
1442 lines[pos] = line
1443 self.set_region(head, tail, chars, lines)
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001444 return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001445
1446 def tabify_region_event(self, event):
1447 head, tail, chars, lines = self.get_region()
1448 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001449 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001450 for pos in range(len(lines)):
1451 line = lines[pos]
1452 if line:
1453 raw, effective = classifyws(line, tabwidth)
1454 ntabs, nspaces = divmod(effective, tabwidth)
1455 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1456 self.set_region(head, tail, chars, lines)
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001457 return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001458
1459 def untabify_region_event(self, event):
1460 head, tail, chars, lines = self.get_region()
1461 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001462 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001463 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001464 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001465 self.set_region(head, tail, chars, lines)
Serhiy Storchaka213ce122017-06-27 07:02:32 +03001466 return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001467
1468 def toggle_tabs_event(self, event):
1469 if self.askyesno(
1470 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001471 "Turn tabs " + ("on", "off")[self.usetabs] +
1472 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001473 ("will be", "remains at")[self.usetabs] + " 8." +
1474 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001475 parent=self.text):
1476 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001477 # Try to prevent inconsistent indentation.
1478 # User must change indent width manually after using tabs.
1479 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001480 return "break"
1481
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001482 # XXX this isn't bound to anything -- see tabwidth comments
1483## def change_tabwidth_event(self, event):
1484## new = self._asktabwidth()
1485## if new != self.tabwidth:
1486## self.tabwidth = new
1487## self.set_indentation_params(0, guess=0)
1488## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001489
1490 def change_indentwidth_event(self, event):
1491 new = self.askinteger(
1492 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001493 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001494 parent=self.text,
1495 initialvalue=self.indentwidth,
1496 minvalue=2,
1497 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001498 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001499 self.indentwidth = new
1500 return "break"
1501
1502 def get_region(self):
1503 text = self.text
1504 first, last = self.get_selection_indices()
1505 if first and last:
1506 head = text.index(first + " linestart")
1507 tail = text.index(last + "-1c lineend +1c")
1508 else:
1509 head = text.index("insert linestart")
1510 tail = text.index("insert lineend +1c")
1511 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001512 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001513 return head, tail, chars, lines
1514
1515 def set_region(self, head, tail, chars, lines):
1516 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001517 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001518 if newchars == chars:
1519 text.bell()
1520 return
1521 text.tag_remove("sel", "1.0", "end")
1522 text.mark_set("insert", head)
1523 text.undo_block_start()
1524 text.delete(head, tail)
1525 text.insert(head, newchars)
1526 text.undo_block_stop()
1527 text.tag_add("sel", head, "insert")
1528
1529 # Make string that displays as n leading blanks.
1530
1531 def _make_blanks(self, n):
1532 if self.usetabs:
1533 ntabs, nspaces = divmod(n, self.tabwidth)
1534 return '\t' * ntabs + ' ' * nspaces
1535 else:
1536 return ' ' * n
1537
1538 # Delete from beginning of line to insert point, then reinsert
1539 # column logical (meaning use tabs if appropriate) spaces.
1540
1541 def reindent_to(self, column):
1542 text = self.text
1543 text.undo_block_start()
1544 if text.compare("insert linestart", "!=", "insert"):
1545 text.delete("insert linestart", "insert")
1546 if column:
1547 text.insert("insert", self._make_blanks(column))
1548 text.undo_block_stop()
1549
1550 def _asktabwidth(self):
1551 return self.askinteger(
1552 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001553 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001554 parent=self.text,
1555 initialvalue=self.indentwidth,
1556 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001557 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001558
1559 # Guess indentwidth from text content.
1560 # Return guessed indentwidth. This should not be believed unless
1561 # it's in a reasonable range (e.g., it will be 0 if no indented
1562 # blocks are found).
1563
1564 def guess_indent(self):
1565 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1566 if opener and indented:
1567 raw, indentsmall = classifyws(opener, self.tabwidth)
1568 raw, indentlarge = classifyws(indented, self.tabwidth)
1569 else:
1570 indentsmall = indentlarge = 0
1571 return indentlarge - indentsmall
1572
1573# "line.col" -> line, as an int
1574def index2line(index):
1575 return int(float(index))
1576
1577# Look at the leading whitespace in s.
1578# Return pair (# of leading ws characters,
1579# effective # of leading blanks after expanding
1580# tabs to width tabwidth)
1581
1582def classifyws(s, tabwidth):
1583 raw = effective = 0
1584 for ch in s:
1585 if ch == ' ':
1586 raw = raw + 1
1587 effective = effective + 1
1588 elif ch == '\t':
1589 raw = raw + 1
1590 effective = (effective // tabwidth + 1) * tabwidth
1591 else:
1592 break
1593 return raw, effective
1594
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001595
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001596class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001597
1598 # .run() chews over the Text widget, looking for a block opener
1599 # and the stmt following it. Returns a pair,
1600 # (line containing block opener, line containing stmt)
1601 # Either or both may be None.
1602
1603 def __init__(self, text, tabwidth):
1604 self.text = text
1605 self.tabwidth = tabwidth
1606 self.i = self.finished = 0
1607 self.blkopenline = self.indentedline = None
1608
1609 def readline(self):
1610 if self.finished:
1611 return ""
1612 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001613 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001614 if self.text.compare(mark, ">=", "end"):
1615 return ""
1616 return self.text.get(mark, mark + " lineend+1c")
1617
1618 def tokeneater(self, type, token, start, end, line,
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001619 INDENT=tokenize.INDENT,
1620 NAME=tokenize.NAME,
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001621 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1622 if self.finished:
1623 pass
1624 elif type == NAME and token in OPENERS:
1625 self.blkopenline = line
1626 elif type == INDENT and self.blkopenline:
1627 self.indentedline = line
1628 self.finished = 1
1629
1630 def run(self):
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001631 save_tabsize = tokenize.tabsize
1632 tokenize.tabsize = self.tabwidth
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001633 try:
1634 try:
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001635 tokens = tokenize.generate_tokens(self.readline)
Trent Nelson428de652008-03-18 22:41:35 +00001636 for token in tokens:
1637 self.tokeneater(*token)
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001638 except (tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001639 # since we cut off the tokenizer early, we can trigger
1640 # spurious errors
1641 pass
1642 finally:
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04001643 tokenize.tabsize = save_tabsize
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001644 return self.blkopenline, self.indentedline
1645
1646### end autoindent code ###
1647
David Scherer7aced172000-08-15 01:13:23 +00001648def prepstr(s):
1649 # Helper to extract the underscore from a string, e.g.
1650 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001651 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001652 if i >= 0:
1653 s = s[:i] + s[i+1:]
1654 return i, s
1655
1656
1657keynames = {
1658 'bracketleft': '[',
1659 'bracketright': ']',
1660 'slash': '/',
1661}
1662
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001663def get_accelerator(keydefs, eventname):
1664 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001665 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1666 # if not keylist:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001667 if (not keylist) or (macosx.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001668 "<<open-module>>",
1669 "<<goto-line>>",
1670 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001671 return ""
1672 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001673 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001674 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1675 s = re.sub("Key-", "", s)
1676 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1677 s = re.sub("Control-", "Ctrl-", s)
1678 s = re.sub("-", "+", s)
1679 s = re.sub("><", " ", s)
1680 s = re.sub("<", "", s)
1681 s = re.sub(">", "", s)
1682 return s
1683
1684
1685def fixwordbreaks(root):
Miss Islington (bot)887b5f82018-04-30 00:27:50 -07001686 # On Windows, tcl/tk breaks 'words' only on spaces, as in Command Prompt.
1687 # We want Motif style everywhere. See #21474, msg218992 and followup.
David Scherer7aced172000-08-15 01:13:23 +00001688 tk = root.tk
1689 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
Miss Islington (bot)887b5f82018-04-30 00:27:50 -07001690 tk.call('set', 'tcl_wordchars', r'\w')
1691 tk.call('set', 'tcl_nonwordchars', r'\W')
David Scherer7aced172000-08-15 01:13:23 +00001692
1693
Terry Jan Reedycd567362014-10-17 01:31:35 -04001694def _editor_window(parent): # htest #
1695 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001696 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001697 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001698 if sys.argv[1:]:
1699 filename = sys.argv[1]
1700 else:
1701 filename = None
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04001702 macosx.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001703 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001704 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001705 # Does not stop error, neither does following
1706 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001707
1708if __name__ == '__main__':
Terry Jan Reedy7c153412016-06-26 17:48:02 -04001709 import unittest
1710 unittest.main('idlelib.idle_test.test_editor', verbosity=2, exit=False)
1711
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001712 from idlelib.idle_test.htest import run
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04001713 run(_editor_window)