blob: d742dd64f16f79af24ceb199d620c2ad337426ef [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)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400194 text.bind("<<open-config-extensions-dialog>>",
195 self.config_extensions_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000196 text.bind("<<open-module>>", self.open_module)
197 text.bind("<<do-nothing>>", lambda event: "break")
198 text.bind("<<select-all>>", self.select_all)
199 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000200 text.bind("<<find>>", self.find_event)
201 text.bind("<<find-again>>", self.find_again_event)
202 text.bind("<<find-in-files>>", self.find_in_files_event)
203 text.bind("<<find-selection>>", self.find_selection_event)
204 text.bind("<<replace>>", self.replace_event)
205 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000206 text.bind("<<smart-backspace>>",self.smart_backspace_event)
207 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
208 text.bind("<<smart-indent>>",self.smart_indent_event)
209 text.bind("<<indent-region>>",self.indent_region_event)
210 text.bind("<<dedent-region>>",self.dedent_region_event)
211 text.bind("<<comment-region>>",self.comment_region_event)
212 text.bind("<<uncomment-region>>",self.uncomment_region_event)
213 text.bind("<<tabify-region>>",self.tabify_region_event)
214 text.bind("<<untabify-region>>",self.untabify_region_event)
215 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
216 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000217 text.bind("<Left>", self.move_at_edge_if_selection(0))
218 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000219 text.bind("<<del-word-left>>", self.del_word_left)
220 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000221 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000222
David Scherer7aced172000-08-15 01:13:23 +0000223 if flist:
224 flist.inversedict[self] = key
225 if key:
226 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000227 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000228 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
229 text.bind("<<open-class-browser>>", self.open_class_browser)
230 text.bind("<<open-path-browser>>", self.open_path_browser)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400231 text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
David Scherer7aced172000-08-15 01:13:23 +0000232
Steven M. Gava898a3652001-10-07 11:10:44 +0000233 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000234 vbar['command'] = text.yview
235 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000236 text['yscrollcommand'] = vbar.set
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400237 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000238 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
239 text.pack(side=TOP, fill=BOTH, expand=1)
240 text.focus_set()
241
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000242 # usetabs true -> literal tab characters are used by indent and
243 # dedent cmds, possibly mixed with spaces if
244 # indentwidth is not a multiple of tabwidth,
245 # which will cause Tabnanny to nag!
246 # false -> tab characters are converted to spaces by indent
247 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000248 # Although use-spaces=0 can be configured manually in config-main.def,
249 # configuration of tabs v. spaces is not supported in the configuration
250 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200251 usespaces = idleConf.GetOption('main', 'Indent',
252 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000253 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000254
255 # tabwidth is the display width of a literal tab character.
256 # CAUTION: telling Tk to use anything other than its default
257 # tab setting causes it to use an entirely different tabbing algorithm,
258 # treating tab stops as fixed distances from the left margin.
259 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000260 self.tabwidth = 8 # must remain 8 until Tk is fixed.
261
262 # indentwidth is the number of screen characters per indent level.
263 # The recommended Python indentation is four spaces.
264 self.indentwidth = self.tabwidth
265 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000266
267 # If context_use_ps1 is true, parsing searches back for a ps1 line;
268 # else searches for a popular (if, def, ...) Python stmt.
269 self.context_use_ps1 = False
270
271 # When searching backwards for a reliable place to begin parsing,
272 # first start num_context_lines[0] lines back, then
273 # num_context_lines[1] lines back if that didn't work, and so on.
274 # The last value should be huge (larger than the # of lines in a
275 # conceivable file).
276 # Making the initial values larger slows things down more often.
277 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000278 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000279 self.undo = undo = self.UndoDelegator()
280 per.insertfilter(undo)
281 text.undo_block_start = undo.undo_block_start
282 text.undo_block_stop = undo.undo_block_stop
283 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000284 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000285 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000286 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000287 self.good_load = False
288 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000289 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000290 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000291 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000292 if io.loadfile(filename):
293 self.good_load = True
294 is_py_src = self.ispythonsource(filename)
295 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000296 else:
297 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500298 self.good_load = True
299
Christian Heimesa156e092008-02-16 07:38:31 +0000300 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000301 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000302 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000303 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000304 menu = self.menudict.get('windows')
305 if menu:
306 end = menu.index("end")
307 if end is None:
308 end = -1
309 if end >= 0:
310 menu.add_separator()
311 end = end + 1
312 self.wmenu_end = end
313 WindowList.register_callback(self.postwindowsmenu)
314
315 # Some abstractions so IDLE extensions are cross-IDE
316 self.askyesno = tkMessageBox.askyesno
317 self.askinteger = tkSimpleDialog.askinteger
318 self.showerror = tkMessageBox.showerror
319
Martin v. Löwis307021f2005-11-27 16:59:04 +0000320 def _filename_to_unicode(self, filename):
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400321 """Return filename as BMP unicode so diplayable in Tk."""
322 # Decode bytes to unicode.
323 if isinstance(filename, bytes):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000324 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400325 filename = filename.decode(self.filesystemencoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000326 except UnicodeDecodeError:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000327 try:
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400328 filename = filename.decode(self.encoding)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000329 except UnicodeDecodeError:
330 # byte-to-byte conversion
Terry Jan Reedy5c28e9f2015-08-06 00:54:07 -0400331 filename = filename.decode('iso8859-1')
332 # Replace non-BMP char with diamond questionmark.
333 return re.sub('[\U00010000-\U0010FFFF]', '\ufffd', filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000334
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000335 def new_callback(self, event):
336 dirname, basename = self.io.defaultfilename()
337 self.flist.new(dirname)
338 return "break"
339
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000340 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400341 if (event.state & 4) != 0 and event.keysym == "Home":
342 # state&4==Control. If <Control-Home>, use the Tk binding.
343 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000344 if self.text.index("iomark") and \
345 self.text.compare("iomark", "<=", "insert lineend") and \
346 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400347 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000348 insertpt = int(self.text.index("iomark").split(".")[1])
349 else:
350 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000351 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000352 if line[insertpt] not in (' ','\t'):
353 break
354 else:
355 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000356 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000357 if insertpt == lineat:
358 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000359 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000360 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400361 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000362 self.text.tag_remove("sel", "1.0", "end")
363 else:
364 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200365 # there was no previous selection
366 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400367 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200368 if self.text.compare(self.text.index("sel.first"), "<",
369 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400370 self.text.mark_set("my_anchor", "sel.first") # extend back
371 else:
372 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000373 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400374 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000375 if self.text.compare(first,">",last):
376 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000377 self.text.tag_remove("sel", "1.0", "end")
378 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000379 self.text.mark_set("insert", dest)
380 self.text.see("insert")
381 return "break"
382
David Scherer7aced172000-08-15 01:13:23 +0000383 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000384 self.status_bar = self.MultiStatusBar(self.top)
Ned Deilyb7601672014-03-27 20:49:14 -0700385 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000386 # Insert some padding to avoid obscuring some of the statusbar
387 # by the resize widget.
388 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000389 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
390 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
391 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000392 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
393 self.text.event_add("<<set-line-and-column>>",
394 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000395 self.text.after_idle(self.set_line_and_column)
396
397 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000398 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000399 self.status_bar.set_label('column', 'Col: %s' % column)
400 self.status_bar.set_label('line', 'Ln: %s' % line)
401
David Scherer7aced172000-08-15 01:13:23 +0000402 menu_specs = [
403 ("file", "_File"),
404 ("edit", "_Edit"),
405 ("format", "F_ormat"),
406 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000407 ("options", "_Options"),
Ned Deilyccb416f2015-01-17 21:06:27 -0800408 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000409 ("help", "_Help"),
410 ]
411
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000412
David Scherer7aced172000-08-15 01:13:23 +0000413 def createmenubar(self):
414 mbar = self.menubar
415 self.menudict = menudict = {}
416 for name, label in self.menu_specs:
417 underline, label = prepstr(label)
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400418 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000419 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ned Deilyb7601672014-03-27 20:49:14 -0700420 if macosxSupport.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000421 # Insert the application menu
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400422 menudict['application'] = menu = Menu(mbar, name='apple',
423 tearoff=0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000424 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000425 self.fill_menus()
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400426 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000427 self.menudict['file'].insert_cascade(3, label='Recent Files',
428 underline=0,
429 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000430 self.base_helpmenu_length = self.menudict['help'].index(END)
431 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000432
433 def postwindowsmenu(self):
434 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000435 menu = self.menudict['windows']
436 end = menu.index("end")
437 if end is None:
438 end = -1
439 if end > self.wmenu_end:
440 menu.delete(self.wmenu_end+1, end)
441 WindowList.add_windows_to_menu(menu)
442
443 rmenu = None
444
445 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000446 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
447 if not self.rmenu:
448 self.make_rmenu()
449 rmenu = self.rmenu
450 self.event = event
451 iswin = sys.platform[:3] == 'win'
452 if iswin:
453 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200454
Roger Serwy6b2918a2013-04-07 12:15:52 -0500455 for item in self.rmenu_specs:
456 try:
457 label, eventname, verify_state = item
458 except ValueError: # see issue1207589
459 continue
460
Andrew Svetlovd1837672012-11-01 22:41:19 +0200461 if verify_state is None:
462 continue
463 state = getattr(self, verify_state)()
464 rmenu.entryconfigure(label, state=state)
465
466
David Scherer7aced172000-08-15 01:13:23 +0000467 rmenu.tk_popup(event.x_root, event.y_root)
468 if iswin:
469 self.text.config(cursor="ibeam")
470
471 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200472 # ("Label", "<<virtual-event>>", "statefuncname"), ...
473 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000474 ]
475
476 def make_rmenu(self):
477 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500478 for item in self.rmenu_specs:
479 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200480 if label is not None:
481 def command(text=self.text, eventname=eventname):
482 text.event_generate(eventname)
483 rmenu.add_command(label=label, command=command)
484 else:
485 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000486 self.rmenu = rmenu
487
Andrew Svetlovd1837672012-11-01 22:41:19 +0200488 def rmenu_check_cut(self):
489 return self.rmenu_check_copy()
490
491 def rmenu_check_copy(self):
492 try:
493 indx = self.text.index('sel.first')
494 except TclError:
495 return 'disabled'
496 else:
497 return 'normal' if indx else 'disabled'
498
499 def rmenu_check_paste(self):
500 try:
501 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
502 except TclError:
503 return 'disabled'
504 else:
505 return 'normal'
506
David Scherer7aced172000-08-15 01:13:23 +0000507 def about_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400508 "Handle Help 'About IDLE' event."
509 # Synchronize with macosxSupport.overrideRootMenu.about_dialog.
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000510 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000511
Steven M. Gava3b55a892001-11-21 05:56:26 +0000512 def config_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400513 "Handle Options 'Configure IDLE' event."
514 # Synchronize with macosxSupport.overrideRootMenu.config_dialog.
Steven M. Gava3b55a892001-11-21 05:56:26 +0000515 configDialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400516
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400517 def config_extensions_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400518 "Handle Options 'Configure Extensions' event."
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400519 configDialog.ConfigExtensionsDialog(self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000520
David Scherer7aced172000-08-15 01:13:23 +0000521 def help_dialog(self, event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400522 "Handle Help 'IDLE Help' event."
523 # Synchronize with macosxSupport.overrideRootMenu.help_dialog.
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500524 if self.root:
525 parent = self.root
526 else:
527 parent = self.top
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400528 help.show_idlehelp(parent)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000529
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000530 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000531 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000532 try:
533 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200534 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000535 tkMessageBox.showerror(title='Document Start Failure',
536 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000537 else:
538 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000539 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000540
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000541 def cut(self,event):
542 self.text.event_generate("<<Cut>>")
543 return "break"
544
545 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000546 if not self.text.tag_ranges("sel"):
547 # There is no selection, so do nothing and maybe interrupt.
548 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000549 self.text.event_generate("<<Copy>>")
550 return "break"
551
552 def paste(self,event):
553 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000554 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000555 return "break"
556
David Scherer7aced172000-08-15 01:13:23 +0000557 def select_all(self, event=None):
558 self.text.tag_add("sel", "1.0", "end-1c")
559 self.text.mark_set("insert", "1.0")
560 self.text.see("insert")
561 return "break"
562
563 def remove_selection(self, event=None):
564 self.text.tag_remove("sel", "1.0", "end")
565 self.text.see("insert")
566
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000567 def move_at_edge_if_selection(self, edge_index):
568 """Cursor move begins at start or end of selection
569
570 When a left/right cursor key is pressed create and return to Tkinter a
571 function which causes a cursor move from the associated edge of the
572 selection.
573
574 """
575 self_text_index = self.text.index
576 self_text_mark_set = self.text.mark_set
577 edges_table = ("sel.first+1c", "sel.last-1c")
578 def move_at_edge(event):
579 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
580 try:
581 self_text_index("sel.first")
582 self_text_mark_set("insert", edges_table[edge_index])
583 except TclError:
584 pass
585 return move_at_edge
586
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000587 def del_word_left(self, event):
588 self.text.event_generate('<Meta-Delete>')
589 return "break"
590
591 def del_word_right(self, event):
592 self.text.event_generate('<Meta-d>')
593 return "break"
594
Steven M. Gavac5976402002-01-04 03:06:08 +0000595 def find_event(self, event):
596 SearchDialog.find(self.text)
597 return "break"
598
599 def find_again_event(self, event):
600 SearchDialog.find_again(self.text)
601 return "break"
602
603 def find_selection_event(self, event):
604 SearchDialog.find_selection(self.text)
605 return "break"
606
607 def find_in_files_event(self, event):
608 GrepDialog.grep(self.text, self.io, self.flist)
609 return "break"
610
611 def replace_event(self, event):
612 ReplaceDialog.replace(self.text)
613 return "break"
614
615 def goto_line_event(self, event):
616 text = self.text
617 lineno = tkSimpleDialog.askinteger("Goto",
618 "Go to line number:",parent=text)
619 if lineno is None:
620 return "break"
621 if lineno <= 0:
622 text.bell()
623 return "break"
624 text.mark_set("insert", "%d.0" % lineno)
625 text.see("insert")
626
David Scherer7aced172000-08-15 01:13:23 +0000627 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000628 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000629 try:
630 name = self.text.get("sel.first", "sel.last")
631 except TclError:
632 name = ""
633 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000634 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000635 name = tkSimpleDialog.askstring("Module",
636 "Enter the name of a Python module\n"
637 "to search on sys.path and open:",
638 parent=self.text, initialvalue=name)
639 if name:
640 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000641 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000642 return
David Scherer7aced172000-08-15 01:13:23 +0000643 # XXX Ought to insert current file's directory in front of path
644 try:
Eric Snow6029e082014-01-25 15:32:46 -0700645 spec = importlib.util.find_spec(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400646 except (ValueError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000647 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
648 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700649 if spec is None:
Brett Cannon50793b42013-06-07 13:17:48 -0400650 tkMessageBox.showerror("Import error", "module not found",
651 parent=self.text)
David Scherer7aced172000-08-15 01:13:23 +0000652 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700653 if not isinstance(spec.loader, importlib.abc.SourceLoader):
Brett Cannon50793b42013-06-07 13:17:48 -0400654 tkMessageBox.showerror("Import error", "not a source-based module",
655 parent=self.text)
656 return
657 try:
Eric Snow02b9f9d2014-01-06 20:42:59 -0700658 file_path = spec.loader.get_filename(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400659 except AttributeError:
660 tkMessageBox.showerror("Import error",
661 "loader does not support get_filename",
662 parent=self.text)
663 return
David Scherer7aced172000-08-15 01:13:23 +0000664 if self.flist:
Brett Cannon50793b42013-06-07 13:17:48 -0400665 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000666 else:
Brett Cannon50793b42013-06-07 13:17:48 -0400667 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400668 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000669
670 def open_class_browser(self, event=None):
671 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400672 if not (self.__class__.__name__ == 'PyShellEditorWindow'
673 and filename):
674 filename = self.open_module()
675 if filename is None:
676 return
David Scherer7aced172000-08-15 01:13:23 +0000677 head, tail = os.path.split(filename)
678 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000679 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000680 ClassBrowser.ClassBrowser(self.flist, base, [head])
681
682 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000683 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000684 PathBrowser.PathBrowser(self.flist)
685
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400686 def open_turtle_demo(self, event = None):
687 import subprocess
688
689 cmd = [sys.executable,
690 '-c',
691 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400692 subprocess.Popen(cmd, shell=False)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400693
David Scherer7aced172000-08-15 01:13:23 +0000694 def gotoline(self, lineno):
695 if lineno is not None and lineno > 0:
696 self.text.mark_set("insert", "%d.0" % lineno)
697 self.text.tag_remove("sel", "1.0", "end")
698 self.text.tag_add("sel", "insert", "insert +1l")
699 self.center()
700
701 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000702 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000703 return True
David Scherer7aced172000-08-15 01:13:23 +0000704 base, ext = os.path.splitext(os.path.basename(filename))
705 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000706 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000707 line = self.text.get('1.0', '1.0 lineend')
708 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000709
710 def close_hook(self):
711 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000712 self.flist.unregister_maybe_terminate(self)
713 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000714
715 def set_close_hook(self, close_hook):
716 self.close_hook = close_hook
717
718 def filename_change_hook(self):
719 if self.flist:
720 self.flist.filename_changed_edit(self)
721 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000722 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000723 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000724
Christian Heimesa156e092008-02-16 07:38:31 +0000725 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000726 if self.color:
727 return
Christian Heimesa156e092008-02-16 07:38:31 +0000728 if self.ispythonsource(self.io.filename):
729 self.color = self.ColorDelegator()
730 # can add more colorizers here...
731 if self.color:
732 self.per.removefilter(self.undo)
733 self.per.insertfilter(self.color)
734 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000735
Christian Heimesa156e092008-02-16 07:38:31 +0000736 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000737 if not self.color:
738 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000739 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000740 self.per.removefilter(self.color)
741 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000742
Steven M. Gavab77d3432002-03-02 07:16:21 +0000743 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400744 "Update the color theme"
Christian Heimesa156e092008-02-16 07:38:31 +0000745 # Called from self.filename_change_hook and from configDialog.py
746 self._rmcolorizer()
747 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000748 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000749 normal_colors = idleConf.GetHighlight(theme, 'normal')
750 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
751 select_colors = idleConf.GetHighlight(theme, 'hilite')
752 self.text.config(
753 foreground=normal_colors['foreground'],
754 background=normal_colors['background'],
755 insertbackground=cursor_color,
756 selectforeground=select_colors['foreground'],
757 selectbackground=select_colors['background'],
Terry Jan Reedy507a5c42015-09-28 04:16:32 -0400758 inactiveselectbackground=select_colors['background'],
Christian Heimesa156e092008-02-16 07:38:31 +0000759 )
David Scherer7aced172000-08-15 01:13:23 +0000760
Guido van Rossum33d26892007-08-05 15:29:28 +0000761 IDENTCHARS = string.ascii_letters + string.digits + "_"
762
763 def colorize_syntax_error(self, text, pos):
764 text.tag_add("ERROR", pos)
765 char = text.get(pos)
766 if char and char in self.IDENTCHARS:
767 text.tag_add("ERROR", pos + " wordstart", pos)
768 if '\n' == text.get(pos): # error at line end
769 text.mark_set("insert", pos)
770 else:
771 text.mark_set("insert", pos + "+1c")
772 text.see(pos)
773
Steven M. Gavab1585412002-03-12 00:21:56 +0000774 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000775 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000776 # Called from configDialog.py
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400777
778 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000779
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000780 def RemoveKeybindings(self):
781 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000782 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000783 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000784 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000785 self.text.event_delete(event, *keylist)
786 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000787 xkeydefs = idleConf.GetExtensionBindings(extensionName)
788 if xkeydefs:
789 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000790 self.text.event_delete(event, *keylist)
791
792 def ApplyKeybindings(self):
793 "Update the keybindings after they are changed"
794 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000795 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000796 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000797 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000798 xkeydefs = idleConf.GetExtensionBindings(extensionName)
799 if xkeydefs:
800 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000801 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000802 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000803 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000804 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000805 for item in menu[1]:
806 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000807 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000808 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000809 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700810 end = menu.index(END)
811 if end is None:
812 # Skip empty menus
813 continue
814 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000815 for index in range(0, end):
816 if menu.type(index) == 'command':
817 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000818 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000819 itemName = menu.entrycget(index, 'label')
820 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000821 if menubarItem in menuEventDict:
822 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000823 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000824 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000825 accel = get_accelerator(keydefs, event)
826 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000827
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000828 def set_notabs_indentwidth(self):
829 "Update the indentwidth if changed and not using tabs in this window"
830 # Called from configDialog.py
831 if not self.usetabs:
832 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
833 type='int')
834
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000835 def reset_help_menu_entries(self):
836 "Update the additional help entries on the Help menu"
837 help_list = idleConf.GetAllExtraHelpSourcesList()
838 helpmenu = self.menudict['help']
839 # first delete the extra help entries, if any
840 helpmenu_length = helpmenu.index(END)
841 if helpmenu_length > self.base_helpmenu_length:
842 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
843 # then rebuild them
844 if help_list:
845 helpmenu.add_separator()
846 for entry in help_list:
847 cmd = self.__extra_help_callback(entry[1])
848 helpmenu.add_command(label=entry[0], command=cmd)
849 # and update the menu dictionary
850 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000851
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000852 def __extra_help_callback(self, helpfile):
853 "Create a callback with the helpfile value frozen at definition time"
854 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000855 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000856 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000857 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000858 try:
859 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200860 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000861 tkMessageBox.showerror(title='Document Start Failure',
862 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000863 else:
864 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000865 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000866
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000867 def update_recent_files_list(self, new_file=None):
868 "Load and update the recent files list and menus"
869 rf_list = []
870 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400871 with open(self.recent_files_path, 'r',
872 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000873 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000874 if new_file:
875 new_file = os.path.abspath(new_file) + '\n'
876 if new_file in rf_list:
877 rf_list.remove(new_file) # move to top
878 rf_list.insert(0, new_file)
879 # clean and save the recent files list
880 bad_paths = []
881 for path in rf_list:
882 if '\0' in path or not os.path.exists(path[0:-1]):
883 bad_paths.append(path)
884 rf_list = [path for path in rf_list if path not in bad_paths]
885 ulchars = "1234567890ABCDEFGHIJK"
886 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000887 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800888 with open(self.recent_files_path, 'w',
889 encoding='utf_8', errors='replace') as rf_file:
890 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200891 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800892 if not getattr(self.root, "recentfilelist_error_displayed", False):
893 self.root.recentfilelist_error_displayed = True
894 tkMessageBox.showerror(title='IDLE Error',
895 message='Unable to update Recent Files list:\n%s'
896 % str(err),
897 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000898 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000899 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000900 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700901 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000902 for i, file_name in enumerate(rf_list):
903 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000904 # make unicode string to display non-ASCII chars correctly
905 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000906 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000907 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000908 command=callback,
909 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000910
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000911 def __recent_file_callback(self, file_name):
912 def open_recent_file(fn_closure=file_name):
913 self.io.open(editFile=fn_closure)
914 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000915
David Scherer7aced172000-08-15 01:13:23 +0000916 def saved_change_hook(self):
917 short = self.short_title()
918 long = self.long_title()
919 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400920 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000921 elif short:
922 title = short
923 elif long:
924 title = long
925 else:
926 title = "Untitled"
927 icon = short or long or title
928 if not self.get_saved():
929 title = "*%s*" % title
930 icon = "*%s" % icon
931 self.top.wm_title(title)
932 self.top.wm_iconname(icon)
933
934 def get_saved(self):
935 return self.undo.get_saved()
936
937 def set_saved(self, flag):
938 self.undo.set_saved(flag)
939
940 def reset_undo(self):
941 self.undo.reset_undo()
942
943 def short_title(self):
944 filename = self.io.filename
945 if filename:
946 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500947 else:
948 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000949 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400950 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000951
952 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000953 # return unicode string to display non-ASCII chars correctly
954 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000955
956 def center_insert_event(self, event):
957 self.center()
958
959 def center(self, mark="insert"):
960 text = self.text
961 top, bot = self.getwindowlines()
962 lineno = self.getlineno(mark)
963 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000964 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000965 text.yview(float(newtop))
966
967 def getwindowlines(self):
968 text = self.text
969 top = self.getlineno("@0,0")
970 bot = self.getlineno("@0,65535")
971 if top == bot and text.winfo_height() == 1:
972 # Geometry manager hasn't run yet
973 height = int(text['height'])
974 bot = top + height - 1
975 return top, bot
976
977 def getlineno(self, mark="insert"):
978 text = self.text
979 return int(float(text.index(mark)))
980
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000981 def get_geometry(self):
982 "Return (width, height, x, y)"
983 geom = self.top.wm_geometry()
984 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000985 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000986
David Scherer7aced172000-08-15 01:13:23 +0000987 def close_event(self, event):
988 self.close()
989
990 def maybesave(self):
991 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000992 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000993 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000994 self.top.deiconify()
995 self.top.lower()
996 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000997 return self.io.maybesave()
998
999 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001000 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +00001001 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001002 self._close()
1003 return reply
1004
1005 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001006 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001007 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001008 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001009 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001010 self.io.close()
1011 self.io = None
1012 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001013 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001014 self.color.close(False)
1015 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001016 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001017 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001018 self.per.close()
1019 self.per = None
1020 self.top.destroy()
1021 if self.close_hook:
1022 # unless override: unregister from flist, terminate if last window
1023 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001024
1025 def load_extensions(self):
1026 self.extensions = {}
1027 self.load_standard_extensions()
1028
1029 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001030 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001031 if hasattr(ins, "close"):
1032 ins.close()
1033 self.extensions = {}
1034
1035 def load_standard_extensions(self):
1036 for name in self.get_standard_extension_names():
1037 try:
1038 self.load_extension(name)
1039 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001040 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001041 traceback.print_exc()
1042
1043 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001044 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001045
1046 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001047 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001048 try:
1049 mod = importlib.import_module('.' + name, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001050 except (ImportError, TypeError):
Brett Cannonaef82d32012-04-14 20:44:23 -04001051 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001052 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001053 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001054 raise
David Scherer7aced172000-08-15 01:13:23 +00001055 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001056 keydefs = idleConf.GetExtensionBindings(name)
1057 if hasattr(cls, "menudefs"):
1058 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001059 ins = cls(self)
1060 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001061 if keydefs:
1062 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001063 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001064 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001065 while methodname[:1] == '<':
1066 methodname = methodname[1:]
1067 while methodname[-1:] == '>':
1068 methodname = methodname[:-1]
1069 methodname = methodname + "_event"
1070 if hasattr(ins, methodname):
1071 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001072
1073 def apply_bindings(self, keydefs=None):
1074 if keydefs is None:
1075 keydefs = self.Bindings.default_keydefs
1076 text = self.text
1077 text.keydefs = keydefs
1078 for event, keylist in keydefs.items():
1079 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001080 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001081
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001082 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001083 """Add appropriate entries to the menus and submenus
1084
1085 Menus that are absent or None in self.menudict are ignored.
1086 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001087 if menudefs is None:
1088 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001089 if keydefs is None:
1090 keydefs = self.Bindings.default_keydefs
1091 menudict = self.menudict
1092 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001093 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001094 menu = menudict.get(mname)
1095 if not menu:
1096 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001097 for entry in entrylist:
1098 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001099 menu.add_separator()
1100 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001101 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001102 checkbutton = (label[:1] == '!')
1103 if checkbutton:
1104 label = label[1:]
1105 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001106 accelerator = get_accelerator(keydefs, eventname)
1107 def command(text=text, eventname=eventname):
1108 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001109 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001110 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001111 menu.add_checkbutton(label=label, underline=underline,
1112 command=command, accelerator=accelerator,
1113 variable=var)
1114 else:
1115 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001116 command=command,
1117 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001118
1119 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001120 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001121 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001122 value = var.get()
1123 return value
1124 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001125 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001126
1127 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001128 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001129 if var:
1130 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001131 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001132 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001133
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001134 def get_var_obj(self, name, vartype=None):
1135 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001136 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001137 # create a Tkinter variable object with self.text as master:
1138 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001139 return var
1140
1141 # Tk implementations of "virtual text methods" -- each platform
1142 # reusing IDLE's support code needs to define these for its GUI's
1143 # flavor of widget.
1144
1145 # Is character at text_index in a Python string? Return 0 for
1146 # "guaranteed no", true for anything else. This info is expensive
1147 # to compute ab initio, but is probably already known by the
1148 # platform's colorizer.
1149
1150 def is_char_in_string(self, text_index):
1151 if self.color:
1152 # Return true iff colorizer hasn't (re)gotten this far
1153 # yet, or the character is tagged as being in a string
1154 return self.text.tag_prevrange("TODO", text_index) or \
1155 "STRING" in self.text.tag_names(text_index)
1156 else:
1157 # The colorizer is missing: assume the worst
1158 return 1
1159
1160 # If a selection is defined in the text widget, return (start,
1161 # end) as Tkinter text indices, otherwise return (None, None)
1162 def get_selection_indices(self):
1163 try:
1164 first = self.text.index("sel.first")
1165 last = self.text.index("sel.last")
1166 return first, last
1167 except TclError:
1168 return None, None
1169
1170 # Return the text widget's current view of what a tab stop means
1171 # (equivalent width in spaces).
1172
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001173 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001174 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1175 return int(current)
1176
1177 # Set the text widget's current view of what a tab stop means.
1178
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001179 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001180 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001181 if self.get_tk_tabwidth() != newtabwidth:
1182 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001183 pixels = text.tk.call("font", "measure", text["font"],
1184 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001185 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001186 text.configure(tabs=pixels)
1187
Guido van Rossum33d26892007-08-05 15:29:28 +00001188### begin autoindent code ### (configuration was moved to beginning of class)
1189
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001190 def set_indentation_params(self, is_py_src, guess=True):
1191 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001192 i = self.guess_indent()
1193 if 2 <= i <= 8:
1194 self.indentwidth = i
1195 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001196 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001197 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001198
1199 def smart_backspace_event(self, event):
1200 text = self.text
1201 first, last = self.get_selection_indices()
1202 if first and last:
1203 text.delete(first, last)
1204 text.mark_set("insert", first)
1205 return "break"
1206 # Delete whitespace left, until hitting a real char or closest
1207 # preceding virtual tab stop.
1208 chars = text.get("insert linestart", "insert")
1209 if chars == '':
1210 if text.compare("insert", ">", "1.0"):
1211 # easy: delete preceding newline
1212 text.delete("insert-1c")
1213 else:
1214 text.bell() # at start of buffer
1215 return "break"
1216 if chars[-1] not in " \t":
1217 # easy: delete preceding real char
1218 text.delete("insert-1c")
1219 return "break"
1220 # Ick. It may require *inserting* spaces if we back up over a
1221 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001222 tabwidth = self.tabwidth
1223 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001224 assert have > 0
1225 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001226 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001227 if self.context_use_ps1:
1228 last_line_of_prompt = sys.ps1.split('\n')[-1]
1229 else:
1230 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001231 ncharsdeleted = 0
1232 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001233 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001234 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001235 chars = chars[:-1]
1236 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001237 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001238 if have <= want or chars[-1] not in " \t":
1239 break
1240 text.undo_block_start()
1241 text.delete("insert-%dc" % ncharsdeleted, "insert")
1242 if have < want:
1243 text.insert("insert", ' ' * (want - have))
1244 text.undo_block_stop()
1245 return "break"
1246
1247 def smart_indent_event(self, event):
1248 # if intraline selection:
1249 # delete it
1250 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001251 # do indent-region
1252 # else:
1253 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001254 text = self.text
1255 first, last = self.get_selection_indices()
1256 text.undo_block_start()
1257 try:
1258 if first and last:
1259 if index2line(first) != index2line(last):
1260 return self.indent_region_event(event)
1261 text.delete(first, last)
1262 text.mark_set("insert", first)
1263 prefix = text.get("insert linestart", "insert")
1264 raw, effective = classifyws(prefix, self.tabwidth)
1265 if raw == len(prefix):
1266 # only whitespace to the left
1267 self.reindent_to(effective + self.indentwidth)
1268 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001269 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001270 if self.usetabs:
1271 pad = '\t'
1272 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001273 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001274 n = self.indentwidth
1275 pad = ' ' * (n - effective % n)
1276 text.insert("insert", pad)
1277 text.see("insert")
1278 return "break"
1279 finally:
1280 text.undo_block_stop()
1281
1282 def newline_and_indent_event(self, event):
1283 text = self.text
1284 first, last = self.get_selection_indices()
1285 text.undo_block_start()
1286 try:
1287 if first and last:
1288 text.delete(first, last)
1289 text.mark_set("insert", first)
1290 line = text.get("insert linestart", "insert")
1291 i, n = 0, len(line)
1292 while i < n and line[i] in " \t":
1293 i = i+1
1294 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001295 # the cursor is in or at leading indentation in a continuation
1296 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001297 text.insert("insert linestart", '\n')
1298 return "break"
1299 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001300 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001301 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001302 last_line_of_prompt = sys.ps1.split('\n')[-1]
1303 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001304 line = line[:-1]
1305 i = i+1
1306 if i:
1307 text.delete("insert - %d chars" % i, "insert")
1308 # strip whitespace after insert point
1309 while text.get("insert") in " \t":
1310 text.delete("insert")
1311 # start new line
1312 text.insert("insert", '\n')
1313
1314 # adjust indentation for continuations and block
1315 # open/close first need to find the last stmt
1316 lno = index2line(text.index('insert'))
1317 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001318 if not self.context_use_ps1:
1319 for context in self.num_context_lines:
1320 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001321 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001322 rawtext = text.get(startatindex, "insert")
1323 y.set_str(rawtext)
1324 bod = y.find_good_parse_start(
1325 self.context_use_ps1,
1326 self._build_char_in_string_func(startatindex))
1327 if bod is not None or startat == 1:
1328 break
1329 y.set_lo(bod or 0)
1330 else:
1331 r = text.tag_prevrange("console", "insert")
1332 if r:
1333 startatindex = r[1]
1334 else:
1335 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001336 rawtext = text.get(startatindex, "insert")
1337 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001338 y.set_lo(0)
1339
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001340 c = y.get_continuation_type()
1341 if c != PyParse.C_NONE:
1342 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001343 if c == PyParse.C_STRING_FIRST_LINE:
1344 # after the first line of a string; do not indent at all
1345 pass
1346 elif c == PyParse.C_STRING_NEXT_LINES:
1347 # inside a string which started before this line;
1348 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001349 text.insert("insert", indent)
1350 elif c == PyParse.C_BRACKET:
1351 # line up with the first (if any) element of the
1352 # last open bracket structure; else indent one
1353 # level beyond the indent of the line with the
1354 # last open bracket
1355 self.reindent_to(y.compute_bracket_indent())
1356 elif c == PyParse.C_BACKSLASH:
1357 # if more than one line in this stmt already, just
1358 # mimic the current indent; else if initial line
1359 # has a start on an assignment stmt, indent to
1360 # beyond leftmost =; else to beyond first chunk of
1361 # non-whitespace on initial line
1362 if y.get_num_lines_in_stmt() > 1:
1363 text.insert("insert", indent)
1364 else:
1365 self.reindent_to(y.compute_backslash_indent())
1366 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001367 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001368 return "break"
1369
1370 # This line starts a brand new stmt; indent relative to
1371 # indentation of initial line of closest preceding
1372 # interesting stmt.
1373 indent = y.get_base_indent_string()
1374 text.insert("insert", indent)
1375 if y.is_block_opener():
1376 self.smart_indent_event(event)
1377 elif indent and y.is_block_closer():
1378 self.smart_backspace_event(event)
1379 return "break"
1380 finally:
1381 text.see("insert")
1382 text.undo_block_stop()
1383
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001384 # Our editwin provides a is_char_in_string function that works
1385 # with a Tk text index, but PyParse only knows about offsets into
1386 # a string. This builds a function for PyParse that accepts an
1387 # offset.
1388
1389 def _build_char_in_string_func(self, startindex):
1390 def inner(offset, _startindex=startindex,
1391 _icis=self.is_char_in_string):
1392 return _icis(_startindex + "+%dc" % offset)
1393 return inner
1394
1395 def indent_region_event(self, event):
1396 head, tail, chars, lines = self.get_region()
1397 for pos in range(len(lines)):
1398 line = lines[pos]
1399 if line:
1400 raw, effective = classifyws(line, self.tabwidth)
1401 effective = effective + self.indentwidth
1402 lines[pos] = self._make_blanks(effective) + line[raw:]
1403 self.set_region(head, tail, chars, lines)
1404 return "break"
1405
1406 def dedent_region_event(self, event):
1407 head, tail, chars, lines = self.get_region()
1408 for pos in range(len(lines)):
1409 line = lines[pos]
1410 if line:
1411 raw, effective = classifyws(line, self.tabwidth)
1412 effective = max(effective - self.indentwidth, 0)
1413 lines[pos] = self._make_blanks(effective) + line[raw:]
1414 self.set_region(head, tail, chars, lines)
1415 return "break"
1416
1417 def comment_region_event(self, event):
1418 head, tail, chars, lines = self.get_region()
1419 for pos in range(len(lines) - 1):
1420 line = lines[pos]
1421 lines[pos] = '##' + line
1422 self.set_region(head, tail, chars, lines)
1423
1424 def uncomment_region_event(self, event):
1425 head, tail, chars, lines = self.get_region()
1426 for pos in range(len(lines)):
1427 line = lines[pos]
1428 if not line:
1429 continue
1430 if line[:2] == '##':
1431 line = line[2:]
1432 elif line[:1] == '#':
1433 line = line[1:]
1434 lines[pos] = line
1435 self.set_region(head, tail, chars, lines)
1436
1437 def tabify_region_event(self, event):
1438 head, tail, chars, lines = self.get_region()
1439 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001440 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001441 for pos in range(len(lines)):
1442 line = lines[pos]
1443 if line:
1444 raw, effective = classifyws(line, tabwidth)
1445 ntabs, nspaces = divmod(effective, tabwidth)
1446 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1447 self.set_region(head, tail, chars, lines)
1448
1449 def untabify_region_event(self, event):
1450 head, tail, chars, lines = self.get_region()
1451 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001452 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001453 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001454 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001455 self.set_region(head, tail, chars, lines)
1456
1457 def toggle_tabs_event(self, event):
1458 if self.askyesno(
1459 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001460 "Turn tabs " + ("on", "off")[self.usetabs] +
1461 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001462 ("will be", "remains at")[self.usetabs] + " 8." +
1463 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001464 parent=self.text):
1465 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001466 # Try to prevent inconsistent indentation.
1467 # User must change indent width manually after using tabs.
1468 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001469 return "break"
1470
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001471 # XXX this isn't bound to anything -- see tabwidth comments
1472## def change_tabwidth_event(self, event):
1473## new = self._asktabwidth()
1474## if new != self.tabwidth:
1475## self.tabwidth = new
1476## self.set_indentation_params(0, guess=0)
1477## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001478
1479 def change_indentwidth_event(self, event):
1480 new = self.askinteger(
1481 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001482 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001483 parent=self.text,
1484 initialvalue=self.indentwidth,
1485 minvalue=2,
1486 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001487 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001488 self.indentwidth = new
1489 return "break"
1490
1491 def get_region(self):
1492 text = self.text
1493 first, last = self.get_selection_indices()
1494 if first and last:
1495 head = text.index(first + " linestart")
1496 tail = text.index(last + "-1c lineend +1c")
1497 else:
1498 head = text.index("insert linestart")
1499 tail = text.index("insert lineend +1c")
1500 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001501 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001502 return head, tail, chars, lines
1503
1504 def set_region(self, head, tail, chars, lines):
1505 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001506 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001507 if newchars == chars:
1508 text.bell()
1509 return
1510 text.tag_remove("sel", "1.0", "end")
1511 text.mark_set("insert", head)
1512 text.undo_block_start()
1513 text.delete(head, tail)
1514 text.insert(head, newchars)
1515 text.undo_block_stop()
1516 text.tag_add("sel", head, "insert")
1517
1518 # Make string that displays as n leading blanks.
1519
1520 def _make_blanks(self, n):
1521 if self.usetabs:
1522 ntabs, nspaces = divmod(n, self.tabwidth)
1523 return '\t' * ntabs + ' ' * nspaces
1524 else:
1525 return ' ' * n
1526
1527 # Delete from beginning of line to insert point, then reinsert
1528 # column logical (meaning use tabs if appropriate) spaces.
1529
1530 def reindent_to(self, column):
1531 text = self.text
1532 text.undo_block_start()
1533 if text.compare("insert linestart", "!=", "insert"):
1534 text.delete("insert linestart", "insert")
1535 if column:
1536 text.insert("insert", self._make_blanks(column))
1537 text.undo_block_stop()
1538
1539 def _asktabwidth(self):
1540 return self.askinteger(
1541 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001542 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001543 parent=self.text,
1544 initialvalue=self.indentwidth,
1545 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001546 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001547
1548 # Guess indentwidth from text content.
1549 # Return guessed indentwidth. This should not be believed unless
1550 # it's in a reasonable range (e.g., it will be 0 if no indented
1551 # blocks are found).
1552
1553 def guess_indent(self):
1554 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1555 if opener and indented:
1556 raw, indentsmall = classifyws(opener, self.tabwidth)
1557 raw, indentlarge = classifyws(indented, self.tabwidth)
1558 else:
1559 indentsmall = indentlarge = 0
1560 return indentlarge - indentsmall
1561
1562# "line.col" -> line, as an int
1563def index2line(index):
1564 return int(float(index))
1565
1566# Look at the leading whitespace in s.
1567# Return pair (# of leading ws characters,
1568# effective # of leading blanks after expanding
1569# tabs to width tabwidth)
1570
1571def classifyws(s, tabwidth):
1572 raw = effective = 0
1573 for ch in s:
1574 if ch == ' ':
1575 raw = raw + 1
1576 effective = effective + 1
1577 elif ch == '\t':
1578 raw = raw + 1
1579 effective = (effective // tabwidth + 1) * tabwidth
1580 else:
1581 break
1582 return raw, effective
1583
1584import tokenize
1585_tokenize = tokenize
1586del tokenize
1587
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001588class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001589
1590 # .run() chews over the Text widget, looking for a block opener
1591 # and the stmt following it. Returns a pair,
1592 # (line containing block opener, line containing stmt)
1593 # Either or both may be None.
1594
1595 def __init__(self, text, tabwidth):
1596 self.text = text
1597 self.tabwidth = tabwidth
1598 self.i = self.finished = 0
1599 self.blkopenline = self.indentedline = None
1600
1601 def readline(self):
1602 if self.finished:
1603 return ""
1604 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001605 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001606 if self.text.compare(mark, ">=", "end"):
1607 return ""
1608 return self.text.get(mark, mark + " lineend+1c")
1609
1610 def tokeneater(self, type, token, start, end, line,
1611 INDENT=_tokenize.INDENT,
1612 NAME=_tokenize.NAME,
1613 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1614 if self.finished:
1615 pass
1616 elif type == NAME and token in OPENERS:
1617 self.blkopenline = line
1618 elif type == INDENT and self.blkopenline:
1619 self.indentedline = line
1620 self.finished = 1
1621
1622 def run(self):
1623 save_tabsize = _tokenize.tabsize
1624 _tokenize.tabsize = self.tabwidth
1625 try:
1626 try:
Trent Nelson428de652008-03-18 22:41:35 +00001627 tokens = _tokenize.generate_tokens(self.readline)
1628 for token in tokens:
1629 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001630 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001631 # since we cut off the tokenizer early, we can trigger
1632 # spurious errors
1633 pass
1634 finally:
1635 _tokenize.tabsize = save_tabsize
1636 return self.blkopenline, self.indentedline
1637
1638### end autoindent code ###
1639
David Scherer7aced172000-08-15 01:13:23 +00001640def prepstr(s):
1641 # Helper to extract the underscore from a string, e.g.
1642 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001643 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001644 if i >= 0:
1645 s = s[:i] + s[i+1:]
1646 return i, s
1647
1648
1649keynames = {
1650 'bracketleft': '[',
1651 'bracketright': ']',
1652 'slash': '/',
1653}
1654
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001655def get_accelerator(keydefs, eventname):
1656 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001657 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1658 # if not keylist:
Ned Deilyb7601672014-03-27 20:49:14 -07001659 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001660 "<<open-module>>",
1661 "<<goto-line>>",
1662 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001663 return ""
1664 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001665 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001666 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1667 s = re.sub("Key-", "", s)
1668 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1669 s = re.sub("Control-", "Ctrl-", s)
1670 s = re.sub("-", "+", s)
1671 s = re.sub("><", " ", s)
1672 s = re.sub("<", "", s)
1673 s = re.sub(">", "", s)
1674 return s
1675
1676
1677def fixwordbreaks(root):
1678 # Make sure that Tk's double-click and next/previous word
1679 # operations use our definition of a word (i.e. an identifier)
1680 tk = root.tk
1681 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1682 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1683 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1684
1685
Terry Jan Reedycd567362014-10-17 01:31:35 -04001686def _editor_window(parent): # htest #
1687 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001688 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001689 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001690 if sys.argv[1:]:
1691 filename = sys.argv[1]
1692 else:
1693 filename = None
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001694 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001695 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001696 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001697 # Does not stop error, neither does following
1698 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001699
1700if __name__ == '__main__':
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001701 from idlelib.idle_test.htest import run
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04001702 run(_editor_window)