blob: 0196344d8dcd6a87c592834e557c16ff5eead2d2 [file] [log] [blame]
Brett Cannonaef82d32012-04-14 20:44:23 -04001import importlib
Brett Cannon50793b42013-06-07 13:17:48 -04002import importlib.abc
Eric Snow6029e082014-01-25 15:32:46 -07003import importlib.util
David Scherer7aced172000-08-15 01:13:23 +00004import os
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -04005import platform
David Scherer7aced172000-08-15 01:13:23 +00006import re
Guido van Rossum33d26892007-08-05 15:29:28 +00007import string
Brett Cannonaef82d32012-04-14 20:44:23 -04008import sys
Georg Brandl14fc4272008-05-17 18:39:55 +00009from tkinter import *
10import tkinter.simpledialog as tkSimpleDialog
11import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +000012import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000013import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000014
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000015from idlelib.MultiCall import MultiCallCreator
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000016from idlelib import WindowList
17from idlelib import SearchDialog
18from idlelib import GrepDialog
19from idlelib import ReplaceDialog
20from idlelib import PyParse
21from idlelib.configHandler import idleConf
22from idlelib import aboutDialog, textView, configDialog
23from idlelib import macosxSupport
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040024from idlelib import help
David Scherer7aced172000-08-15 01:13:23 +000025
26# The default tab setting for a Text widget, in average-width characters.
27TK_TABWIDTH_DEFAULT = 8
28
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -040029_py_version = ' (%s)' % platform.python_version()
30
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000031def _sphinx_version():
32 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
33 major, minor, micro, level, serial = sys.version_info
34 release = '%s%s' % (major, minor)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020035 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000036 if level == 'candidate':
37 release += 'rc%s' % (serial,)
38 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000039 release += '%s%s' % (level[0], serial)
40 return release
41
Terry Jan Reedye91e7632012-02-05 15:14:20 -050042
43class HelpDialog(object):
44
45 def __init__(self):
46 self.parent = None # parent of help window
47 self.dlg = None # the help window iteself
48
49 def display(self, parent, near=None):
50 """ Display the help dialog.
51
52 parent - parent widget for the help window
53
54 near - a Toplevel widget (e.g. EditorWindow or PyShell)
55 to use as a reference for placing the help window
56 """
Terry Jan Reedya0ae7892015-09-22 22:59:40 -040057 import warnings as w
58 w.warn("EditorWindow.HelpDialog is no longer used by Idle.\n"
59 "It will be removed in 3.6 or later.\n"
60 "It has been replaced by private help.HelpWindow\n",
61 DeprecationWarning, stacklevel=2)
Terry Jan Reedye91e7632012-02-05 15:14:20 -050062 if self.dlg is None:
63 self.show_dialog(parent)
64 if near:
65 self.nearwindow(near)
66
67 def show_dialog(self, parent):
68 self.parent = parent
69 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
70 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
71 dlg.bind('<Destroy>', self.destroy, '+')
72
73 def nearwindow(self, near):
74 # Place the help dialog near the window specified by parent.
75 # Note - this may not reposition the window in Metacity
76 # if "/apps/metacity/general/disable_workarounds" is enabled
77 dlg = self.dlg
78 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
79 dlg.withdraw()
80 dlg.geometry("=+%d+%d" % geom)
81 dlg.deiconify()
82 dlg.lift()
83
84 def destroy(self, ev=None):
85 self.dlg = None
86 self.parent = None
87
Terry Jan Reedy96f802a2015-09-20 23:05:25 -040088helpDialog = HelpDialog() # singleton instance, no longer used
Terry Jan Reedye91e7632012-02-05 15:14:20 -050089
90
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000091class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000092 from idlelib.Percolator import Percolator
93 from idlelib.ColorDelegator import ColorDelegator
94 from idlelib.UndoDelegator import UndoDelegator
95 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
96 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000097 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000098 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000099
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000100 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000101
102 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000103 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100104 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000105 if sys.platform.count('linux'):
106 # look for html docs in a couple of standard places
107 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
108 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
109 dochome = '/var/www/html/python/index.html'
110 else:
111 basepath = '/usr/share/doc/' # standard location
112 dochome = os.path.join(basepath, pyver,
113 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000114 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100115 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +0000116 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000117 if os.path.isfile(chmfile):
118 dochome = chmfile
Ned Deilyb7601672014-03-27 20:49:14 -0700119 elif sys.platform == 'darwin':
120 # documentation may be stored inside a python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100121 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000122 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000123 dochome = os.path.normpath(dochome)
124 if os.path.isfile(dochome):
125 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000126 if sys.platform == 'darwin':
127 # Safari requires real file:-URLs
128 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000129 else:
Terry Jan Reedyb6e17782014-09-19 22:54:15 -0400130 EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
David Scherer7aced172000-08-15 01:13:23 +0000131 self.flist = flist
132 root = root or flist.root
133 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000134 try:
135 sys.ps1
136 except AttributeError:
137 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000138 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000139 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000140 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000141 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200142 #self.top.instance_dict makes flist.inversedict available to
143 #configDialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000144 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000145 else:
146 self.tkinter_vars = {} # keys: Tkinter event names
147 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000148 self.top.instance_dict = {}
149 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000150 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000151 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000152 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200153 self.width = idleConf.GetOption('main', 'EditorWindow',
154 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000155 text_options = {
156 'name': 'text',
157 'padx': 5,
158 'wrap': 'none',
159 'width': self.width,
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200160 'height': idleConf.GetOption('main', 'EditorWindow',
161 'height', type='int')}
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000162 if TkVersion >= 8.5:
163 # Starting with tk 8.5 we have to set the new tabstyle option
164 # to 'wordprocessor' to achieve the same display of tabs as in
165 # older tk versions.
166 text_options['tabstyle'] = 'wordprocessor'
167 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000168 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000169
170 self.createmenubar()
171 self.apply_bindings()
172
173 self.top.protocol("WM_DELETE_WINDOW", self.close)
174 self.top.bind("<<close-window>>", self.close_event)
Ned Deilyb7601672014-03-27 20:49:14 -0700175 if macosxSupport.isAquaTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000176 # Command-W on editorwindows doesn't work without this.
177 text.bind('<<close-window>>', self.close_event)
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400178 # Some OS X systems have only one mouse button, so use
179 # control-click for popup context menus there. For two
180 # buttons, AquaTk defines <2> as the right button, not <3>.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000181 text.bind("<Control-Button-1>",self.right_menu_event)
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400182 text.bind("<2>", self.right_menu_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000183 else:
Terry Jan Reedy3c7eccd2015-09-22 21:10:27 -0400184 # Elsewhere, use right-click for popup menus.
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000185 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000186 text.bind("<<cut>>", self.cut)
187 text.bind("<<copy>>", self.copy)
188 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000189 text.bind("<<center-insert>>", self.center_insert_event)
190 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000191 text.bind("<<python-docs>>", self.python_docs)
192 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000193 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000194 text.bind("<<open-module>>", self.open_module)
195 text.bind("<<do-nothing>>", lambda event: "break")
196 text.bind("<<select-all>>", self.select_all)
197 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000198 text.bind("<<find>>", self.find_event)
199 text.bind("<<find-again>>", self.find_again_event)
200 text.bind("<<find-in-files>>", self.find_in_files_event)
201 text.bind("<<find-selection>>", self.find_selection_event)
202 text.bind("<<replace>>", self.replace_event)
203 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000204 text.bind("<<smart-backspace>>",self.smart_backspace_event)
205 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
206 text.bind("<<smart-indent>>",self.smart_indent_event)
207 text.bind("<<indent-region>>",self.indent_region_event)
208 text.bind("<<dedent-region>>",self.dedent_region_event)
209 text.bind("<<comment-region>>",self.comment_region_event)
210 text.bind("<<uncomment-region>>",self.uncomment_region_event)
211 text.bind("<<tabify-region>>",self.tabify_region_event)
212 text.bind("<<untabify-region>>",self.untabify_region_event)
213 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
214 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000215 text.bind("<Left>", self.move_at_edge_if_selection(0))
216 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000217 text.bind("<<del-word-left>>", self.del_word_left)
218 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000219 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000220
David Scherer7aced172000-08-15 01:13:23 +0000221 if flist:
222 flist.inversedict[self] = key
223 if key:
224 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000225 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000226 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
227 text.bind("<<open-class-browser>>", self.open_class_browser)
228 text.bind("<<open-path-browser>>", self.open_path_browser)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400229 text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
David Scherer7aced172000-08-15 01:13:23 +0000230
Steven M. Gava898a3652001-10-07 11:10:44 +0000231 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000232 vbar['command'] = text.yview
233 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000234 text['yscrollcommand'] = vbar.set
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400235 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000236 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
237 text.pack(side=TOP, fill=BOTH, expand=1)
238 text.focus_set()
239
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000240 # usetabs true -> literal tab characters are used by indent and
241 # dedent cmds, possibly mixed with spaces if
242 # indentwidth is not a multiple of tabwidth,
243 # which will cause Tabnanny to nag!
244 # false -> tab characters are converted to spaces by indent
245 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000246 # Although use-spaces=0 can be configured manually in config-main.def,
247 # configuration of tabs v. spaces is not supported in the configuration
248 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200249 usespaces = idleConf.GetOption('main', 'Indent',
250 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000251 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000252
253 # tabwidth is the display width of a literal tab character.
254 # CAUTION: telling Tk to use anything other than its default
255 # tab setting causes it to use an entirely different tabbing algorithm,
256 # treating tab stops as fixed distances from the left margin.
257 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000258 self.tabwidth = 8 # must remain 8 until Tk is fixed.
259
260 # indentwidth is the number of screen characters per indent level.
261 # The recommended Python indentation is four spaces.
262 self.indentwidth = self.tabwidth
263 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000264
265 # If context_use_ps1 is true, parsing searches back for a ps1 line;
266 # else searches for a popular (if, def, ...) Python stmt.
267 self.context_use_ps1 = False
268
269 # When searching backwards for a reliable place to begin parsing,
270 # first start num_context_lines[0] lines back, then
271 # num_context_lines[1] lines back if that didn't work, and so on.
272 # The last value should be huge (larger than the # of lines in a
273 # conceivable file).
274 # Making the initial values larger slows things down more often.
275 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000276 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000277 self.undo = undo = self.UndoDelegator()
278 per.insertfilter(undo)
279 text.undo_block_start = undo.undo_block_start
280 text.undo_block_stop = undo.undo_block_stop
281 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000282 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000283 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000284 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000285 self.good_load = False
286 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000287 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000288 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000289 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000290 if io.loadfile(filename):
291 self.good_load = True
292 is_py_src = self.ispythonsource(filename)
293 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000294 else:
295 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500296 self.good_load = True
297
Christian Heimesa156e092008-02-16 07:38:31 +0000298 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000299 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000300 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000301 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000302 menu = self.menudict.get('windows')
303 if menu:
304 end = menu.index("end")
305 if end is None:
306 end = -1
307 if end >= 0:
308 menu.add_separator()
309 end = end + 1
310 self.wmenu_end = end
311 WindowList.register_callback(self.postwindowsmenu)
312
313 # Some abstractions so IDLE extensions are cross-IDE
314 self.askyesno = tkMessageBox.askyesno
315 self.askinteger = tkSimpleDialog.askinteger
316 self.showerror = tkMessageBox.showerror
317
Martin v. Löwis307021f2005-11-27 16:59:04 +0000318 def _filename_to_unicode(self, filename):
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400319 """Return filename as BMP unicode so diplayable in Tk."""
320 # Decode bytes to unicode.
321 if isinstance(filename, bytes):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000322 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400323 filename = filename.decode(self.filesystemencoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000324 except UnicodeDecodeError:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000325 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400326 filename = filename.decode(self.encoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000327 except UnicodeDecodeError:
328 # byte-to-byte conversion
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400329 filename = filename.decode('iso8859-1')
330 # Replace non-BMP char with diamond questionmark.
331 return re.sub('[\U00010000-\U0010FFFF]', '\ufffd', filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000332
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000333 def new_callback(self, event):
334 dirname, basename = self.io.defaultfilename()
335 self.flist.new(dirname)
336 return "break"
337
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000338 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400339 if (event.state & 4) != 0 and event.keysym == "Home":
340 # state&4==Control. If <Control-Home>, use the Tk binding.
341 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000342 if self.text.index("iomark") and \
343 self.text.compare("iomark", "<=", "insert lineend") and \
344 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400345 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000346 insertpt = int(self.text.index("iomark").split(".")[1])
347 else:
348 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000349 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000350 if line[insertpt] not in (' ','\t'):
351 break
352 else:
353 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000354 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000355 if insertpt == lineat:
356 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000357 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000358 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400359 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000360 self.text.tag_remove("sel", "1.0", "end")
361 else:
362 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200363 # there was no previous selection
364 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400365 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200366 if self.text.compare(self.text.index("sel.first"), "<",
367 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400368 self.text.mark_set("my_anchor", "sel.first") # extend back
369 else:
370 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000371 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400372 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000373 if self.text.compare(first,">",last):
374 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000375 self.text.tag_remove("sel", "1.0", "end")
376 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000377 self.text.mark_set("insert", dest)
378 self.text.see("insert")
379 return "break"
380
David Scherer7aced172000-08-15 01:13:23 +0000381 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000382 self.status_bar = self.MultiStatusBar(self.top)
Ned Deilyb7601672014-03-27 20:49:14 -0700383 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000384 # Insert some padding to avoid obscuring some of the statusbar
385 # by the resize widget.
386 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000387 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
388 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
389 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000390 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
391 self.text.event_add("<<set-line-and-column>>",
392 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000393 self.text.after_idle(self.set_line_and_column)
394
395 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000396 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000397 self.status_bar.set_label('column', 'Col: %s' % column)
398 self.status_bar.set_label('line', 'Ln: %s' % line)
399
David Scherer7aced172000-08-15 01:13:23 +0000400 menu_specs = [
401 ("file", "_File"),
402 ("edit", "_Edit"),
403 ("format", "F_ormat"),
404 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000405 ("options", "_Options"),
Ned Deilyccb416f2015-01-17 21:06:27 -0800406 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000407 ("help", "_Help"),
408 ]
409
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000410
David Scherer7aced172000-08-15 01:13:23 +0000411 def createmenubar(self):
412 mbar = self.menubar
413 self.menudict = menudict = {}
414 for name, label in self.menu_specs:
415 underline, label = prepstr(label)
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400416 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000417 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ned Deilyb7601672014-03-27 20:49:14 -0700418 if macosxSupport.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000419 # Insert the application menu
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400420 menudict['application'] = menu = Menu(mbar, name='apple',
421 tearoff=0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000422 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000423 self.fill_menus()
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400424 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000425 self.menudict['file'].insert_cascade(3, label='Recent Files',
426 underline=0,
427 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000428 self.base_helpmenu_length = self.menudict['help'].index(END)
429 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000430
431 def postwindowsmenu(self):
432 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000433 menu = self.menudict['windows']
434 end = menu.index("end")
435 if end is None:
436 end = -1
437 if end > self.wmenu_end:
438 menu.delete(self.wmenu_end+1, end)
439 WindowList.add_windows_to_menu(menu)
440
441 rmenu = None
442
443 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000444 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
445 if not self.rmenu:
446 self.make_rmenu()
447 rmenu = self.rmenu
448 self.event = event
449 iswin = sys.platform[:3] == 'win'
450 if iswin:
451 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200452
Roger Serwy6b2918a2013-04-07 12:15:52 -0500453 for item in self.rmenu_specs:
454 try:
455 label, eventname, verify_state = item
456 except ValueError: # see issue1207589
457 continue
458
Andrew Svetlovd1837672012-11-01 22:41:19 +0200459 if verify_state is None:
460 continue
461 state = getattr(self, verify_state)()
462 rmenu.entryconfigure(label, state=state)
463
464
David Scherer7aced172000-08-15 01:13:23 +0000465 rmenu.tk_popup(event.x_root, event.y_root)
466 if iswin:
467 self.text.config(cursor="ibeam")
468
469 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200470 # ("Label", "<<virtual-event>>", "statefuncname"), ...
471 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000472 ]
473
474 def make_rmenu(self):
475 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500476 for item in self.rmenu_specs:
477 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200478 if label is not None:
479 def command(text=self.text, eventname=eventname):
480 text.event_generate(eventname)
481 rmenu.add_command(label=label, command=command)
482 else:
483 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000484 self.rmenu = rmenu
485
Andrew Svetlovd1837672012-11-01 22:41:19 +0200486 def rmenu_check_cut(self):
487 return self.rmenu_check_copy()
488
489 def rmenu_check_copy(self):
490 try:
491 indx = self.text.index('sel.first')
492 except TclError:
493 return 'disabled'
494 else:
495 return 'normal' if indx else 'disabled'
496
497 def rmenu_check_paste(self):
498 try:
499 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
500 except TclError:
501 return 'disabled'
502 else:
503 return 'normal'
504
David Scherer7aced172000-08-15 01:13:23 +0000505 def about_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400506 "Handle Help 'About IDLE' event."
507 # Synchronize with macosxSupport.overrideRootMenu.about_dialog.
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000508 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000509
Steven M. Gava3b55a892001-11-21 05:56:26 +0000510 def config_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400511 "Handle Options 'Configure IDLE' event."
512 # Synchronize with macosxSupport.overrideRootMenu.config_dialog.
Steven M. Gava3b55a892001-11-21 05:56:26 +0000513 configDialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400514
David Scherer7aced172000-08-15 01:13:23 +0000515 def help_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400516 "Handle Help 'IDLE Help' event."
517 # Synchronize with macosxSupport.overrideRootMenu.help_dialog.
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500518 if self.root:
519 parent = self.root
520 else:
521 parent = self.top
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400522 help.show_idlehelp(parent)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000523
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000524 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000525 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000526 try:
527 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200528 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000529 tkMessageBox.showerror(title='Document Start Failure',
530 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000531 else:
532 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000533 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000534
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000535 def cut(self,event):
536 self.text.event_generate("<<Cut>>")
537 return "break"
538
539 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000540 if not self.text.tag_ranges("sel"):
541 # There is no selection, so do nothing and maybe interrupt.
542 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000543 self.text.event_generate("<<Copy>>")
544 return "break"
545
546 def paste(self,event):
547 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000548 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000549 return "break"
550
David Scherer7aced172000-08-15 01:13:23 +0000551 def select_all(self, event=None):
552 self.text.tag_add("sel", "1.0", "end-1c")
553 self.text.mark_set("insert", "1.0")
554 self.text.see("insert")
555 return "break"
556
557 def remove_selection(self, event=None):
558 self.text.tag_remove("sel", "1.0", "end")
559 self.text.see("insert")
560
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000561 def move_at_edge_if_selection(self, edge_index):
562 """Cursor move begins at start or end of selection
563
564 When a left/right cursor key is pressed create and return to Tkinter a
565 function which causes a cursor move from the associated edge of the
566 selection.
567
568 """
569 self_text_index = self.text.index
570 self_text_mark_set = self.text.mark_set
571 edges_table = ("sel.first+1c", "sel.last-1c")
572 def move_at_edge(event):
573 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
574 try:
575 self_text_index("sel.first")
576 self_text_mark_set("insert", edges_table[edge_index])
577 except TclError:
578 pass
579 return move_at_edge
580
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000581 def del_word_left(self, event):
582 self.text.event_generate('<Meta-Delete>')
583 return "break"
584
585 def del_word_right(self, event):
586 self.text.event_generate('<Meta-d>')
587 return "break"
588
Steven M. Gavac5976402002-01-04 03:06:08 +0000589 def find_event(self, event):
590 SearchDialog.find(self.text)
591 return "break"
592
593 def find_again_event(self, event):
594 SearchDialog.find_again(self.text)
595 return "break"
596
597 def find_selection_event(self, event):
598 SearchDialog.find_selection(self.text)
599 return "break"
600
601 def find_in_files_event(self, event):
602 GrepDialog.grep(self.text, self.io, self.flist)
603 return "break"
604
605 def replace_event(self, event):
606 ReplaceDialog.replace(self.text)
607 return "break"
608
609 def goto_line_event(self, event):
610 text = self.text
611 lineno = tkSimpleDialog.askinteger("Goto",
612 "Go to line number:",parent=text)
613 if lineno is None:
614 return "break"
615 if lineno <= 0:
616 text.bell()
617 return "break"
618 text.mark_set("insert", "%d.0" % lineno)
619 text.see("insert")
620
David Scherer7aced172000-08-15 01:13:23 +0000621 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000622 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000623 try:
624 name = self.text.get("sel.first", "sel.last")
625 except TclError:
626 name = ""
627 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000628 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000629 name = tkSimpleDialog.askstring("Module",
630 "Enter the name of a Python module\n"
631 "to search on sys.path and open:",
632 parent=self.text, initialvalue=name)
633 if name:
634 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000635 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000636 return
David Scherer7aced172000-08-15 01:13:23 +0000637 # XXX Ought to insert current file's directory in front of path
638 try:
Eric Snow6029e082014-01-25 15:32:46 -0700639 spec = importlib.util.find_spec(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400640 except (ValueError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000641 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
642 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700643 if spec is None:
Brett Cannon50793b42013-06-07 13:17:48 -0400644 tkMessageBox.showerror("Import error", "module not found",
645 parent=self.text)
David Scherer7aced172000-08-15 01:13:23 +0000646 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700647 if not isinstance(spec.loader, importlib.abc.SourceLoader):
Brett Cannon50793b42013-06-07 13:17:48 -0400648 tkMessageBox.showerror("Import error", "not a source-based module",
649 parent=self.text)
650 return
651 try:
Eric Snow02b9f9d2014-01-06 20:42:59 -0700652 file_path = spec.loader.get_filename(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400653 except AttributeError:
654 tkMessageBox.showerror("Import error",
655 "loader does not support get_filename",
656 parent=self.text)
657 return
David Scherer7aced172000-08-15 01:13:23 +0000658 if self.flist:
Brett Cannon50793b42013-06-07 13:17:48 -0400659 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000660 else:
Brett Cannon50793b42013-06-07 13:17:48 -0400661 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400662 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000663
664 def open_class_browser(self, event=None):
665 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400666 if not (self.__class__.__name__ == 'PyShellEditorWindow'
667 and filename):
668 filename = self.open_module()
669 if filename is None:
670 return
David Scherer7aced172000-08-15 01:13:23 +0000671 head, tail = os.path.split(filename)
672 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000673 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000674 ClassBrowser.ClassBrowser(self.flist, base, [head])
675
676 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000677 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000678 PathBrowser.PathBrowser(self.flist)
679
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400680 def open_turtle_demo(self, event = None):
681 import subprocess
682
683 cmd = [sys.executable,
684 '-c',
685 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400686 subprocess.Popen(cmd, shell=False)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400687
David Scherer7aced172000-08-15 01:13:23 +0000688 def gotoline(self, lineno):
689 if lineno is not None and lineno > 0:
690 self.text.mark_set("insert", "%d.0" % lineno)
691 self.text.tag_remove("sel", "1.0", "end")
692 self.text.tag_add("sel", "insert", "insert +1l")
693 self.center()
694
695 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000696 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000697 return True
David Scherer7aced172000-08-15 01:13:23 +0000698 base, ext = os.path.splitext(os.path.basename(filename))
699 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000700 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000701 line = self.text.get('1.0', '1.0 lineend')
702 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000703
704 def close_hook(self):
705 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000706 self.flist.unregister_maybe_terminate(self)
707 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000708
709 def set_close_hook(self, close_hook):
710 self.close_hook = close_hook
711
712 def filename_change_hook(self):
713 if self.flist:
714 self.flist.filename_changed_edit(self)
715 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000716 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000717 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000718
Christian Heimesa156e092008-02-16 07:38:31 +0000719 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000720 if self.color:
721 return
Christian Heimesa156e092008-02-16 07:38:31 +0000722 if self.ispythonsource(self.io.filename):
723 self.color = self.ColorDelegator()
724 # can add more colorizers here...
725 if self.color:
726 self.per.removefilter(self.undo)
727 self.per.insertfilter(self.color)
728 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000729
Christian Heimesa156e092008-02-16 07:38:31 +0000730 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000731 if not self.color:
732 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000733 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000734 self.per.removefilter(self.color)
735 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000736
Steven M. Gavab77d3432002-03-02 07:16:21 +0000737 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400738 "Update the color theme"
Christian Heimesa156e092008-02-16 07:38:31 +0000739 # Called from self.filename_change_hook and from configDialog.py
740 self._rmcolorizer()
741 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000742 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000743 normal_colors = idleConf.GetHighlight(theme, 'normal')
744 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
745 select_colors = idleConf.GetHighlight(theme, 'hilite')
746 self.text.config(
747 foreground=normal_colors['foreground'],
748 background=normal_colors['background'],
749 insertbackground=cursor_color,
750 selectforeground=select_colors['foreground'],
751 selectbackground=select_colors['background'],
752 )
Terry Jan Reedyd3517062015-09-28 04:52:49 -0400753 if TkVersion >= 8.5:
754 self.text.config(
755 inactiveselectbackground=select_colors['background'])
David Scherer7aced172000-08-15 01:13:23 +0000756
Guido van Rossum33d26892007-08-05 15:29:28 +0000757 IDENTCHARS = string.ascii_letters + string.digits + "_"
758
759 def colorize_syntax_error(self, text, pos):
760 text.tag_add("ERROR", pos)
761 char = text.get(pos)
762 if char and char in self.IDENTCHARS:
763 text.tag_add("ERROR", pos + " wordstart", pos)
764 if '\n' == text.get(pos): # error at line end
765 text.mark_set("insert", pos)
766 else:
767 text.mark_set("insert", pos + "+1c")
768 text.see(pos)
769
Steven M. Gavab1585412002-03-12 00:21:56 +0000770 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000771 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000772 # Called from configDialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400773
774 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000775
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000776 def RemoveKeybindings(self):
777 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000778 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000779 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000780 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000781 self.text.event_delete(event, *keylist)
782 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000783 xkeydefs = idleConf.GetExtensionBindings(extensionName)
784 if xkeydefs:
785 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000786 self.text.event_delete(event, *keylist)
787
788 def ApplyKeybindings(self):
789 "Update the keybindings after they are changed"
790 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000791 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000792 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000793 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000794 xkeydefs = idleConf.GetExtensionBindings(extensionName)
795 if xkeydefs:
796 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000797 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000798 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000799 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000800 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000801 for item in menu[1]:
802 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000803 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000804 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000805 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700806 end = menu.index(END)
807 if end is None:
808 # Skip empty menus
809 continue
810 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000811 for index in range(0, end):
812 if menu.type(index) == 'command':
813 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000814 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000815 itemName = menu.entrycget(index, 'label')
816 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000817 if menubarItem in menuEventDict:
818 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000819 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000820 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000821 accel = get_accelerator(keydefs, event)
822 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000823
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000824 def set_notabs_indentwidth(self):
825 "Update the indentwidth if changed and not using tabs in this window"
826 # Called from configDialog.py
827 if not self.usetabs:
828 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
829 type='int')
830
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000831 def reset_help_menu_entries(self):
832 "Update the additional help entries on the Help menu"
833 help_list = idleConf.GetAllExtraHelpSourcesList()
834 helpmenu = self.menudict['help']
835 # first delete the extra help entries, if any
836 helpmenu_length = helpmenu.index(END)
837 if helpmenu_length > self.base_helpmenu_length:
838 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
839 # then rebuild them
840 if help_list:
841 helpmenu.add_separator()
842 for entry in help_list:
843 cmd = self.__extra_help_callback(entry[1])
844 helpmenu.add_command(label=entry[0], command=cmd)
845 # and update the menu dictionary
846 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000847
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000848 def __extra_help_callback(self, helpfile):
849 "Create a callback with the helpfile value frozen at definition time"
850 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000851 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000852 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000853 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000854 try:
855 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200856 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000857 tkMessageBox.showerror(title='Document Start Failure',
858 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000859 else:
860 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000861 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000862
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000863 def update_recent_files_list(self, new_file=None):
864 "Load and update the recent files list and menus"
865 rf_list = []
866 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400867 with open(self.recent_files_path, 'r',
868 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000869 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000870 if new_file:
871 new_file = os.path.abspath(new_file) + '\n'
872 if new_file in rf_list:
873 rf_list.remove(new_file) # move to top
874 rf_list.insert(0, new_file)
875 # clean and save the recent files list
876 bad_paths = []
877 for path in rf_list:
878 if '\0' in path or not os.path.exists(path[0:-1]):
879 bad_paths.append(path)
880 rf_list = [path for path in rf_list if path not in bad_paths]
881 ulchars = "1234567890ABCDEFGHIJK"
882 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000883 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800884 with open(self.recent_files_path, 'w',
885 encoding='utf_8', errors='replace') as rf_file:
886 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200887 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800888 if not getattr(self.root, "recentfilelist_error_displayed", False):
889 self.root.recentfilelist_error_displayed = True
890 tkMessageBox.showerror(title='IDLE Error',
891 message='Unable to update Recent Files list:\n%s'
892 % str(err),
893 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000894 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000895 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000896 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700897 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000898 for i, file_name in enumerate(rf_list):
899 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000900 # make unicode string to display non-ASCII chars correctly
901 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000902 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000903 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000904 command=callback,
905 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000906
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000907 def __recent_file_callback(self, file_name):
908 def open_recent_file(fn_closure=file_name):
909 self.io.open(editFile=fn_closure)
910 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000911
David Scherer7aced172000-08-15 01:13:23 +0000912 def saved_change_hook(self):
913 short = self.short_title()
914 long = self.long_title()
915 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400916 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000917 elif short:
918 title = short
919 elif long:
920 title = long
921 else:
922 title = "Untitled"
923 icon = short or long or title
924 if not self.get_saved():
925 title = "*%s*" % title
926 icon = "*%s" % icon
927 self.top.wm_title(title)
928 self.top.wm_iconname(icon)
929
930 def get_saved(self):
931 return self.undo.get_saved()
932
933 def set_saved(self, flag):
934 self.undo.set_saved(flag)
935
936 def reset_undo(self):
937 self.undo.reset_undo()
938
939 def short_title(self):
940 filename = self.io.filename
941 if filename:
942 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500943 else:
944 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000945 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400946 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000947
948 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000949 # return unicode string to display non-ASCII chars correctly
950 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000951
952 def center_insert_event(self, event):
953 self.center()
954
955 def center(self, mark="insert"):
956 text = self.text
957 top, bot = self.getwindowlines()
958 lineno = self.getlineno(mark)
959 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000960 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000961 text.yview(float(newtop))
962
963 def getwindowlines(self):
964 text = self.text
965 top = self.getlineno("@0,0")
966 bot = self.getlineno("@0,65535")
967 if top == bot and text.winfo_height() == 1:
968 # Geometry manager hasn't run yet
969 height = int(text['height'])
970 bot = top + height - 1
971 return top, bot
972
973 def getlineno(self, mark="insert"):
974 text = self.text
975 return int(float(text.index(mark)))
976
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000977 def get_geometry(self):
978 "Return (width, height, x, y)"
979 geom = self.top.wm_geometry()
980 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000981 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000982
David Scherer7aced172000-08-15 01:13:23 +0000983 def close_event(self, event):
984 self.close()
985
986 def maybesave(self):
987 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000988 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000989 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000990 self.top.deiconify()
991 self.top.lower()
992 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000993 return self.io.maybesave()
994
995 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000996 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000997 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000998 self._close()
999 return reply
1000
1001 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001002 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001003 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001004 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001005 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001006 self.io.close()
1007 self.io = None
1008 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001009 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001010 self.color.close(False)
1011 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001012 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001013 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001014 self.per.close()
1015 self.per = None
1016 self.top.destroy()
1017 if self.close_hook:
1018 # unless override: unregister from flist, terminate if last window
1019 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001020
1021 def load_extensions(self):
1022 self.extensions = {}
1023 self.load_standard_extensions()
1024
1025 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001026 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001027 if hasattr(ins, "close"):
1028 ins.close()
1029 self.extensions = {}
1030
1031 def load_standard_extensions(self):
1032 for name in self.get_standard_extension_names():
1033 try:
1034 self.load_extension(name)
1035 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001036 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001037 traceback.print_exc()
1038
1039 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001040 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001041
1042 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001043 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001044 try:
1045 mod = importlib.import_module('.' + name, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001046 except (ImportError, TypeError):
Brett Cannonaef82d32012-04-14 20:44:23 -04001047 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001048 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001049 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001050 raise
David Scherer7aced172000-08-15 01:13:23 +00001051 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001052 keydefs = idleConf.GetExtensionBindings(name)
1053 if hasattr(cls, "menudefs"):
1054 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001055 ins = cls(self)
1056 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001057 if keydefs:
1058 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001059 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001060 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001061 while methodname[:1] == '<':
1062 methodname = methodname[1:]
1063 while methodname[-1:] == '>':
1064 methodname = methodname[:-1]
1065 methodname = methodname + "_event"
1066 if hasattr(ins, methodname):
1067 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001068
1069 def apply_bindings(self, keydefs=None):
1070 if keydefs is None:
1071 keydefs = self.Bindings.default_keydefs
1072 text = self.text
1073 text.keydefs = keydefs
1074 for event, keylist in keydefs.items():
1075 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001076 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001077
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001078 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001079 """Add appropriate entries to the menus and submenus
1080
1081 Menus that are absent or None in self.menudict are ignored.
1082 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001083 if menudefs is None:
1084 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001085 if keydefs is None:
1086 keydefs = self.Bindings.default_keydefs
1087 menudict = self.menudict
1088 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001089 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001090 menu = menudict.get(mname)
1091 if not menu:
1092 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001093 for entry in entrylist:
1094 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001095 menu.add_separator()
1096 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001097 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001098 checkbutton = (label[:1] == '!')
1099 if checkbutton:
1100 label = label[1:]
1101 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001102 accelerator = get_accelerator(keydefs, eventname)
1103 def command(text=text, eventname=eventname):
1104 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001105 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001106 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001107 menu.add_checkbutton(label=label, underline=underline,
1108 command=command, accelerator=accelerator,
1109 variable=var)
1110 else:
1111 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001112 command=command,
1113 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001114
1115 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001116 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001117 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001118 value = var.get()
1119 return value
1120 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001121 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001122
1123 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001124 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001125 if var:
1126 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001127 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001128 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001129
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001130 def get_var_obj(self, name, vartype=None):
1131 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001132 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001133 # create a Tkinter variable object with self.text as master:
1134 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001135 return var
1136
1137 # Tk implementations of "virtual text methods" -- each platform
1138 # reusing IDLE's support code needs to define these for its GUI's
1139 # flavor of widget.
1140
1141 # Is character at text_index in a Python string? Return 0 for
1142 # "guaranteed no", true for anything else. This info is expensive
1143 # to compute ab initio, but is probably already known by the
1144 # platform's colorizer.
1145
1146 def is_char_in_string(self, text_index):
1147 if self.color:
1148 # Return true iff colorizer hasn't (re)gotten this far
1149 # yet, or the character is tagged as being in a string
1150 return self.text.tag_prevrange("TODO", text_index) or \
1151 "STRING" in self.text.tag_names(text_index)
1152 else:
1153 # The colorizer is missing: assume the worst
1154 return 1
1155
1156 # If a selection is defined in the text widget, return (start,
1157 # end) as Tkinter text indices, otherwise return (None, None)
1158 def get_selection_indices(self):
1159 try:
1160 first = self.text.index("sel.first")
1161 last = self.text.index("sel.last")
1162 return first, last
1163 except TclError:
1164 return None, None
1165
1166 # Return the text widget's current view of what a tab stop means
1167 # (equivalent width in spaces).
1168
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001169 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001170 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1171 return int(current)
1172
1173 # Set the text widget's current view of what a tab stop means.
1174
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001175 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001176 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001177 if self.get_tk_tabwidth() != newtabwidth:
1178 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001179 pixels = text.tk.call("font", "measure", text["font"],
1180 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001181 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001182 text.configure(tabs=pixels)
1183
Guido van Rossum33d26892007-08-05 15:29:28 +00001184### begin autoindent code ### (configuration was moved to beginning of class)
1185
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001186 def set_indentation_params(self, is_py_src, guess=True):
1187 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001188 i = self.guess_indent()
1189 if 2 <= i <= 8:
1190 self.indentwidth = i
1191 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001192 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001193 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001194
1195 def smart_backspace_event(self, event):
1196 text = self.text
1197 first, last = self.get_selection_indices()
1198 if first and last:
1199 text.delete(first, last)
1200 text.mark_set("insert", first)
1201 return "break"
1202 # Delete whitespace left, until hitting a real char or closest
1203 # preceding virtual tab stop.
1204 chars = text.get("insert linestart", "insert")
1205 if chars == '':
1206 if text.compare("insert", ">", "1.0"):
1207 # easy: delete preceding newline
1208 text.delete("insert-1c")
1209 else:
1210 text.bell() # at start of buffer
1211 return "break"
1212 if chars[-1] not in " \t":
1213 # easy: delete preceding real char
1214 text.delete("insert-1c")
1215 return "break"
1216 # Ick. It may require *inserting* spaces if we back up over a
1217 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001218 tabwidth = self.tabwidth
1219 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001220 assert have > 0
1221 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001222 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001223 if self.context_use_ps1:
1224 last_line_of_prompt = sys.ps1.split('\n')[-1]
1225 else:
1226 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001227 ncharsdeleted = 0
1228 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001229 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001230 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001231 chars = chars[:-1]
1232 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001233 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001234 if have <= want or chars[-1] not in " \t":
1235 break
1236 text.undo_block_start()
1237 text.delete("insert-%dc" % ncharsdeleted, "insert")
1238 if have < want:
1239 text.insert("insert", ' ' * (want - have))
1240 text.undo_block_stop()
1241 return "break"
1242
1243 def smart_indent_event(self, event):
1244 # if intraline selection:
1245 # delete it
1246 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001247 # do indent-region
1248 # else:
1249 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001250 text = self.text
1251 first, last = self.get_selection_indices()
1252 text.undo_block_start()
1253 try:
1254 if first and last:
1255 if index2line(first) != index2line(last):
1256 return self.indent_region_event(event)
1257 text.delete(first, last)
1258 text.mark_set("insert", first)
1259 prefix = text.get("insert linestart", "insert")
1260 raw, effective = classifyws(prefix, self.tabwidth)
1261 if raw == len(prefix):
1262 # only whitespace to the left
1263 self.reindent_to(effective + self.indentwidth)
1264 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001265 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001266 if self.usetabs:
1267 pad = '\t'
1268 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001269 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001270 n = self.indentwidth
1271 pad = ' ' * (n - effective % n)
1272 text.insert("insert", pad)
1273 text.see("insert")
1274 return "break"
1275 finally:
1276 text.undo_block_stop()
1277
1278 def newline_and_indent_event(self, event):
1279 text = self.text
1280 first, last = self.get_selection_indices()
1281 text.undo_block_start()
1282 try:
1283 if first and last:
1284 text.delete(first, last)
1285 text.mark_set("insert", first)
1286 line = text.get("insert linestart", "insert")
1287 i, n = 0, len(line)
1288 while i < n and line[i] in " \t":
1289 i = i+1
1290 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001291 # the cursor is in or at leading indentation in a continuation
1292 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001293 text.insert("insert linestart", '\n')
1294 return "break"
1295 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001296 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001297 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001298 last_line_of_prompt = sys.ps1.split('\n')[-1]
1299 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001300 line = line[:-1]
1301 i = i+1
1302 if i:
1303 text.delete("insert - %d chars" % i, "insert")
1304 # strip whitespace after insert point
1305 while text.get("insert") in " \t":
1306 text.delete("insert")
1307 # start new line
1308 text.insert("insert", '\n')
1309
1310 # adjust indentation for continuations and block
1311 # open/close first need to find the last stmt
1312 lno = index2line(text.index('insert'))
1313 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001314 if not self.context_use_ps1:
1315 for context in self.num_context_lines:
1316 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001317 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001318 rawtext = text.get(startatindex, "insert")
1319 y.set_str(rawtext)
1320 bod = y.find_good_parse_start(
1321 self.context_use_ps1,
1322 self._build_char_in_string_func(startatindex))
1323 if bod is not None or startat == 1:
1324 break
1325 y.set_lo(bod or 0)
1326 else:
1327 r = text.tag_prevrange("console", "insert")
1328 if r:
1329 startatindex = r[1]
1330 else:
1331 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001332 rawtext = text.get(startatindex, "insert")
1333 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001334 y.set_lo(0)
1335
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001336 c = y.get_continuation_type()
1337 if c != PyParse.C_NONE:
1338 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001339 if c == PyParse.C_STRING_FIRST_LINE:
1340 # after the first line of a string; do not indent at all
1341 pass
1342 elif c == PyParse.C_STRING_NEXT_LINES:
1343 # inside a string which started before this line;
1344 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001345 text.insert("insert", indent)
1346 elif c == PyParse.C_BRACKET:
1347 # line up with the first (if any) element of the
1348 # last open bracket structure; else indent one
1349 # level beyond the indent of the line with the
1350 # last open bracket
1351 self.reindent_to(y.compute_bracket_indent())
1352 elif c == PyParse.C_BACKSLASH:
1353 # if more than one line in this stmt already, just
1354 # mimic the current indent; else if initial line
1355 # has a start on an assignment stmt, indent to
1356 # beyond leftmost =; else to beyond first chunk of
1357 # non-whitespace on initial line
1358 if y.get_num_lines_in_stmt() > 1:
1359 text.insert("insert", indent)
1360 else:
1361 self.reindent_to(y.compute_backslash_indent())
1362 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001363 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001364 return "break"
1365
1366 # This line starts a brand new stmt; indent relative to
1367 # indentation of initial line of closest preceding
1368 # interesting stmt.
1369 indent = y.get_base_indent_string()
1370 text.insert("insert", indent)
1371 if y.is_block_opener():
1372 self.smart_indent_event(event)
1373 elif indent and y.is_block_closer():
1374 self.smart_backspace_event(event)
1375 return "break"
1376 finally:
1377 text.see("insert")
1378 text.undo_block_stop()
1379
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001380 # Our editwin provides a is_char_in_string function that works
1381 # with a Tk text index, but PyParse only knows about offsets into
1382 # a string. This builds a function for PyParse that accepts an
1383 # offset.
1384
1385 def _build_char_in_string_func(self, startindex):
1386 def inner(offset, _startindex=startindex,
1387 _icis=self.is_char_in_string):
1388 return _icis(_startindex + "+%dc" % offset)
1389 return inner
1390
1391 def indent_region_event(self, event):
1392 head, tail, chars, lines = self.get_region()
1393 for pos in range(len(lines)):
1394 line = lines[pos]
1395 if line:
1396 raw, effective = classifyws(line, self.tabwidth)
1397 effective = effective + self.indentwidth
1398 lines[pos] = self._make_blanks(effective) + line[raw:]
1399 self.set_region(head, tail, chars, lines)
1400 return "break"
1401
1402 def dedent_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 = max(effective - self.indentwidth, 0)
1409 lines[pos] = self._make_blanks(effective) + line[raw:]
1410 self.set_region(head, tail, chars, lines)
1411 return "break"
1412
1413 def comment_region_event(self, event):
1414 head, tail, chars, lines = self.get_region()
1415 for pos in range(len(lines) - 1):
1416 line = lines[pos]
1417 lines[pos] = '##' + line
1418 self.set_region(head, tail, chars, lines)
1419
1420 def uncomment_region_event(self, event):
1421 head, tail, chars, lines = self.get_region()
1422 for pos in range(len(lines)):
1423 line = lines[pos]
1424 if not line:
1425 continue
1426 if line[:2] == '##':
1427 line = line[2:]
1428 elif line[:1] == '#':
1429 line = line[1:]
1430 lines[pos] = line
1431 self.set_region(head, tail, chars, lines)
1432
1433 def tabify_region_event(self, event):
1434 head, tail, chars, lines = self.get_region()
1435 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001436 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001437 for pos in range(len(lines)):
1438 line = lines[pos]
1439 if line:
1440 raw, effective = classifyws(line, tabwidth)
1441 ntabs, nspaces = divmod(effective, tabwidth)
1442 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1443 self.set_region(head, tail, chars, lines)
1444
1445 def untabify_region_event(self, event):
1446 head, tail, chars, lines = self.get_region()
1447 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001448 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001449 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001450 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001451 self.set_region(head, tail, chars, lines)
1452
1453 def toggle_tabs_event(self, event):
1454 if self.askyesno(
1455 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001456 "Turn tabs " + ("on", "off")[self.usetabs] +
1457 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001458 ("will be", "remains at")[self.usetabs] + " 8." +
1459 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001460 parent=self.text):
1461 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001462 # Try to prevent inconsistent indentation.
1463 # User must change indent width manually after using tabs.
1464 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001465 return "break"
1466
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001467 # XXX this isn't bound to anything -- see tabwidth comments
1468## def change_tabwidth_event(self, event):
1469## new = self._asktabwidth()
1470## if new != self.tabwidth:
1471## self.tabwidth = new
1472## self.set_indentation_params(0, guess=0)
1473## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001474
1475 def change_indentwidth_event(self, event):
1476 new = self.askinteger(
1477 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001478 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001479 parent=self.text,
1480 initialvalue=self.indentwidth,
1481 minvalue=2,
1482 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001483 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001484 self.indentwidth = new
1485 return "break"
1486
1487 def get_region(self):
1488 text = self.text
1489 first, last = self.get_selection_indices()
1490 if first and last:
1491 head = text.index(first + " linestart")
1492 tail = text.index(last + "-1c lineend +1c")
1493 else:
1494 head = text.index("insert linestart")
1495 tail = text.index("insert lineend +1c")
1496 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001497 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001498 return head, tail, chars, lines
1499
1500 def set_region(self, head, tail, chars, lines):
1501 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001502 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001503 if newchars == chars:
1504 text.bell()
1505 return
1506 text.tag_remove("sel", "1.0", "end")
1507 text.mark_set("insert", head)
1508 text.undo_block_start()
1509 text.delete(head, tail)
1510 text.insert(head, newchars)
1511 text.undo_block_stop()
1512 text.tag_add("sel", head, "insert")
1513
1514 # Make string that displays as n leading blanks.
1515
1516 def _make_blanks(self, n):
1517 if self.usetabs:
1518 ntabs, nspaces = divmod(n, self.tabwidth)
1519 return '\t' * ntabs + ' ' * nspaces
1520 else:
1521 return ' ' * n
1522
1523 # Delete from beginning of line to insert point, then reinsert
1524 # column logical (meaning use tabs if appropriate) spaces.
1525
1526 def reindent_to(self, column):
1527 text = self.text
1528 text.undo_block_start()
1529 if text.compare("insert linestart", "!=", "insert"):
1530 text.delete("insert linestart", "insert")
1531 if column:
1532 text.insert("insert", self._make_blanks(column))
1533 text.undo_block_stop()
1534
1535 def _asktabwidth(self):
1536 return self.askinteger(
1537 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001538 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001539 parent=self.text,
1540 initialvalue=self.indentwidth,
1541 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001542 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001543
1544 # Guess indentwidth from text content.
1545 # Return guessed indentwidth. This should not be believed unless
1546 # it's in a reasonable range (e.g., it will be 0 if no indented
1547 # blocks are found).
1548
1549 def guess_indent(self):
1550 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1551 if opener and indented:
1552 raw, indentsmall = classifyws(opener, self.tabwidth)
1553 raw, indentlarge = classifyws(indented, self.tabwidth)
1554 else:
1555 indentsmall = indentlarge = 0
1556 return indentlarge - indentsmall
1557
1558# "line.col" -> line, as an int
1559def index2line(index):
1560 return int(float(index))
1561
1562# Look at the leading whitespace in s.
1563# Return pair (# of leading ws characters,
1564# effective # of leading blanks after expanding
1565# tabs to width tabwidth)
1566
1567def classifyws(s, tabwidth):
1568 raw = effective = 0
1569 for ch in s:
1570 if ch == ' ':
1571 raw = raw + 1
1572 effective = effective + 1
1573 elif ch == '\t':
1574 raw = raw + 1
1575 effective = (effective // tabwidth + 1) * tabwidth
1576 else:
1577 break
1578 return raw, effective
1579
1580import tokenize
1581_tokenize = tokenize
1582del tokenize
1583
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001584class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001585
1586 # .run() chews over the Text widget, looking for a block opener
1587 # and the stmt following it. Returns a pair,
1588 # (line containing block opener, line containing stmt)
1589 # Either or both may be None.
1590
1591 def __init__(self, text, tabwidth):
1592 self.text = text
1593 self.tabwidth = tabwidth
1594 self.i = self.finished = 0
1595 self.blkopenline = self.indentedline = None
1596
1597 def readline(self):
1598 if self.finished:
1599 return ""
1600 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001601 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001602 if self.text.compare(mark, ">=", "end"):
1603 return ""
1604 return self.text.get(mark, mark + " lineend+1c")
1605
1606 def tokeneater(self, type, token, start, end, line,
1607 INDENT=_tokenize.INDENT,
1608 NAME=_tokenize.NAME,
1609 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1610 if self.finished:
1611 pass
1612 elif type == NAME and token in OPENERS:
1613 self.blkopenline = line
1614 elif type == INDENT and self.blkopenline:
1615 self.indentedline = line
1616 self.finished = 1
1617
1618 def run(self):
1619 save_tabsize = _tokenize.tabsize
1620 _tokenize.tabsize = self.tabwidth
1621 try:
1622 try:
Trent Nelson428de652008-03-18 22:41:35 +00001623 tokens = _tokenize.generate_tokens(self.readline)
1624 for token in tokens:
1625 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001626 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001627 # since we cut off the tokenizer early, we can trigger
1628 # spurious errors
1629 pass
1630 finally:
1631 _tokenize.tabsize = save_tabsize
1632 return self.blkopenline, self.indentedline
1633
1634### end autoindent code ###
1635
David Scherer7aced172000-08-15 01:13:23 +00001636def prepstr(s):
1637 # Helper to extract the underscore from a string, e.g.
1638 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001639 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001640 if i >= 0:
1641 s = s[:i] + s[i+1:]
1642 return i, s
1643
1644
1645keynames = {
1646 'bracketleft': '[',
1647 'bracketright': ']',
1648 'slash': '/',
1649}
1650
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001651def get_accelerator(keydefs, eventname):
1652 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001653 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1654 # if not keylist:
Ned Deilyb7601672014-03-27 20:49:14 -07001655 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001656 "<<open-module>>",
1657 "<<goto-line>>",
1658 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001659 return ""
1660 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001661 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001662 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1663 s = re.sub("Key-", "", s)
1664 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1665 s = re.sub("Control-", "Ctrl-", s)
1666 s = re.sub("-", "+", s)
1667 s = re.sub("><", " ", s)
1668 s = re.sub("<", "", s)
1669 s = re.sub(">", "", s)
1670 return s
1671
1672
1673def fixwordbreaks(root):
1674 # Make sure that Tk's double-click and next/previous word
1675 # operations use our definition of a word (i.e. an identifier)
1676 tk = root.tk
1677 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1678 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1679 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1680
1681
Terry Jan Reedycd567362014-10-17 01:31:35 -04001682def _editor_window(parent): # htest #
1683 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001684 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001685 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001686 if sys.argv[1:]:
1687 filename = sys.argv[1]
1688 else:
1689 filename = None
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001690 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001691 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001692 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001693 # Does not stop error, neither does following
1694 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001695
1696if __name__ == '__main__':
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001697 from idlelib.idle_test.htest import run
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04001698 run(_editor_window)