blob: bdf4a1a6f6ab3ac95bf057640821168484a1862a [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
16from idlelib import idlever
17from idlelib import WindowList
18from idlelib import SearchDialog
19from idlelib import GrepDialog
20from idlelib import ReplaceDialog
21from idlelib import PyParse
22from idlelib.configHandler import idleConf
23from idlelib import aboutDialog, textView, configDialog
24from idlelib import macosxSupport
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 """
57 if self.dlg is None:
58 self.show_dialog(parent)
59 if near:
60 self.nearwindow(near)
61
62 def show_dialog(self, parent):
63 self.parent = parent
64 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
65 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
66 dlg.bind('<Destroy>', self.destroy, '+')
67
68 def nearwindow(self, near):
69 # Place the help dialog near the window specified by parent.
70 # Note - this may not reposition the window in Metacity
71 # if "/apps/metacity/general/disable_workarounds" is enabled
72 dlg = self.dlg
73 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
74 dlg.withdraw()
75 dlg.geometry("=+%d+%d" % geom)
76 dlg.deiconify()
77 dlg.lift()
78
79 def destroy(self, ev=None):
80 self.dlg = None
81 self.parent = None
82
83helpDialog = HelpDialog() # singleton instance
Terry Jan Reedyab4fd442014-05-19 00:12:10 -040084def _help_dialog(parent): # wrapper for htest
Terry Jan Reedy06313b72014-05-11 23:32:32 -040085 helpDialog.show_dialog(parent)
Terry Jan Reedye91e7632012-02-05 15:14:20 -050086
87
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000088class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000089 from idlelib.Percolator import Percolator
90 from idlelib.ColorDelegator import ColorDelegator
91 from idlelib.UndoDelegator import UndoDelegator
92 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
93 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000094 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000095 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000096
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000097 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000098
99 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000100 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100101 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000102 if sys.platform.count('linux'):
103 # look for html docs in a couple of standard places
104 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
105 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
106 dochome = '/var/www/html/python/index.html'
107 else:
108 basepath = '/usr/share/doc/' # standard location
109 dochome = os.path.join(basepath, pyver,
110 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000111 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100112 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +0000113 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000114 if os.path.isfile(chmfile):
115 dochome = chmfile
Ned Deilyb7601672014-03-27 20:49:14 -0700116 elif sys.platform == 'darwin':
117 # documentation may be stored inside a python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100118 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000119 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000120 dochome = os.path.normpath(dochome)
121 if os.path.isfile(dochome):
122 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000123 if sys.platform == 'darwin':
124 # Safari requires real file:-URLs
125 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000126 else:
Terry Jan Reedyb6e17782014-09-19 22:54:15 -0400127 EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000128 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +0000129 self.flist = flist
130 root = root or flist.root
131 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000132 try:
133 sys.ps1
134 except AttributeError:
135 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000136 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000137 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000138 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000139 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200140 #self.top.instance_dict makes flist.inversedict available to
141 #configDialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000142 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000143 else:
144 self.tkinter_vars = {} # keys: Tkinter event names
145 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000146 self.top.instance_dict = {}
147 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000148 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000149 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000150 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200151 self.width = idleConf.GetOption('main', 'EditorWindow',
152 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000153 text_options = {
154 'name': 'text',
155 'padx': 5,
156 'wrap': 'none',
157 'width': self.width,
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200158 'height': idleConf.GetOption('main', 'EditorWindow',
159 'height', type='int')}
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000160 if TkVersion >= 8.5:
161 # Starting with tk 8.5 we have to set the new tabstyle option
162 # to 'wordprocessor' to achieve the same display of tabs as in
163 # older tk versions.
164 text_options['tabstyle'] = 'wordprocessor'
165 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000166 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000167
168 self.createmenubar()
169 self.apply_bindings()
170
171 self.top.protocol("WM_DELETE_WINDOW", self.close)
172 self.top.bind("<<close-window>>", self.close_event)
Ned Deilyb7601672014-03-27 20:49:14 -0700173 if macosxSupport.isAquaTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000174 # Command-W on editorwindows doesn't work without this.
175 text.bind('<<close-window>>', self.close_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000176 # Some OS X systems have only one mouse button,
177 # so use control-click for pulldown menus there.
178 # (Note, AquaTk defines <2> as the right button if
179 # present and the Tk Text widget already binds <2>.)
180 text.bind("<Control-Button-1>",self.right_menu_event)
181 else:
182 # Elsewhere, use right-click for pulldown menus.
183 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000184 text.bind("<<cut>>", self.cut)
185 text.bind("<<copy>>", self.copy)
186 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000187 text.bind("<<center-insert>>", self.center_insert_event)
188 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000189 text.bind("<<python-docs>>", self.python_docs)
190 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000191 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000192 text.bind("<<open-module>>", self.open_module)
193 text.bind("<<do-nothing>>", lambda event: "break")
194 text.bind("<<select-all>>", self.select_all)
195 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000196 text.bind("<<find>>", self.find_event)
197 text.bind("<<find-again>>", self.find_again_event)
198 text.bind("<<find-in-files>>", self.find_in_files_event)
199 text.bind("<<find-selection>>", self.find_selection_event)
200 text.bind("<<replace>>", self.replace_event)
201 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000202 text.bind("<<smart-backspace>>",self.smart_backspace_event)
203 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
204 text.bind("<<smart-indent>>",self.smart_indent_event)
205 text.bind("<<indent-region>>",self.indent_region_event)
206 text.bind("<<dedent-region>>",self.dedent_region_event)
207 text.bind("<<comment-region>>",self.comment_region_event)
208 text.bind("<<uncomment-region>>",self.uncomment_region_event)
209 text.bind("<<tabify-region>>",self.tabify_region_event)
210 text.bind("<<untabify-region>>",self.untabify_region_event)
211 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
212 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000213 text.bind("<Left>", self.move_at_edge_if_selection(0))
214 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000215 text.bind("<<del-word-left>>", self.del_word_left)
216 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000217 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000218
David Scherer7aced172000-08-15 01:13:23 +0000219 if flist:
220 flist.inversedict[self] = key
221 if key:
222 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000223 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000224 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
225 text.bind("<<open-class-browser>>", self.open_class_browser)
226 text.bind("<<open-path-browser>>", self.open_path_browser)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400227 text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
David Scherer7aced172000-08-15 01:13:23 +0000228
Steven M. Gava898a3652001-10-07 11:10:44 +0000229 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000230 vbar['command'] = text.yview
231 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000232 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000233 fontWeight = 'normal'
234 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000235 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000236 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200237 idleConf.GetOption('main', 'EditorWindow',
238 'font-size', type='int'),
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000239 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000240 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
241 text.pack(side=TOP, fill=BOTH, expand=1)
242 text.focus_set()
243
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000244 # usetabs true -> literal tab characters are used by indent and
245 # dedent cmds, possibly mixed with spaces if
246 # indentwidth is not a multiple of tabwidth,
247 # which will cause Tabnanny to nag!
248 # false -> tab characters are converted to spaces by indent
249 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000250 # Although use-spaces=0 can be configured manually in config-main.def,
251 # configuration of tabs v. spaces is not supported in the configuration
252 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200253 usespaces = idleConf.GetOption('main', 'Indent',
254 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000255 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000256
257 # tabwidth is the display width of a literal tab character.
258 # CAUTION: telling Tk to use anything other than its default
259 # tab setting causes it to use an entirely different tabbing algorithm,
260 # treating tab stops as fixed distances from the left margin.
261 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000262 self.tabwidth = 8 # must remain 8 until Tk is fixed.
263
264 # indentwidth is the number of screen characters per indent level.
265 # The recommended Python indentation is four spaces.
266 self.indentwidth = self.tabwidth
267 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000268
269 # If context_use_ps1 is true, parsing searches back for a ps1 line;
270 # else searches for a popular (if, def, ...) Python stmt.
271 self.context_use_ps1 = False
272
273 # When searching backwards for a reliable place to begin parsing,
274 # first start num_context_lines[0] lines back, then
275 # num_context_lines[1] lines back if that didn't work, and so on.
276 # The last value should be huge (larger than the # of lines in a
277 # conceivable file).
278 # Making the initial values larger slows things down more often.
279 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000280 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000281 self.undo = undo = self.UndoDelegator()
282 per.insertfilter(undo)
283 text.undo_block_start = undo.undo_block_start
284 text.undo_block_stop = undo.undo_block_stop
285 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000286 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000287 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000288 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000289 self.good_load = False
290 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000291 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000292 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000293 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000294 if io.loadfile(filename):
295 self.good_load = True
296 is_py_src = self.ispythonsource(filename)
297 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000298 else:
299 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500300 self.good_load = True
301
Christian Heimesa156e092008-02-16 07:38:31 +0000302 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000303 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000304 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000305 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000306 menu = self.menudict.get('windows')
307 if menu:
308 end = menu.index("end")
309 if end is None:
310 end = -1
311 if end >= 0:
312 menu.add_separator()
313 end = end + 1
314 self.wmenu_end = end
315 WindowList.register_callback(self.postwindowsmenu)
316
317 # Some abstractions so IDLE extensions are cross-IDE
318 self.askyesno = tkMessageBox.askyesno
319 self.askinteger = tkSimpleDialog.askinteger
320 self.showerror = tkMessageBox.showerror
321
Roger Serwycaf30242013-05-20 22:13:39 -0500322 self._highlight_workaround() # Fix selection tags on Windows
323
324 def _highlight_workaround(self):
325 # On Windows, Tk removes painting of the selection
326 # tags which is different behavior than on Linux and Mac.
327 # See issue14146 for more information.
328 if not sys.platform.startswith('win'):
329 return
330
331 text = self.text
332 text.event_add("<<Highlight-FocusOut>>", "<FocusOut>")
333 text.event_add("<<Highlight-FocusIn>>", "<FocusIn>")
334 def highlight_fix(focus):
335 sel_range = text.tag_ranges("sel")
336 if sel_range:
337 if focus == 'out':
338 HILITE_CONFIG = idleConf.GetHighlight(
339 idleConf.CurrentTheme(), 'hilite')
340 text.tag_config("sel_fix", HILITE_CONFIG)
341 text.tag_raise("sel_fix")
342 text.tag_add("sel_fix", *sel_range)
343 elif focus == 'in':
344 text.tag_remove("sel_fix", "1.0", "end")
345
346 text.bind("<<Highlight-FocusOut>>",
347 lambda ev: highlight_fix("out"))
348 text.bind("<<Highlight-FocusIn>>",
349 lambda ev: highlight_fix("in"))
350
351
Martin v. Löwis307021f2005-11-27 16:59:04 +0000352 def _filename_to_unicode(self, filename):
353 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000354 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000355 return filename
356 else:
357 try:
358 return filename.decode(self.filesystemencoding)
359 except UnicodeDecodeError:
360 # XXX
361 try:
362 return filename.decode(self.encoding)
363 except UnicodeDecodeError:
364 # byte-to-byte conversion
365 return filename.decode('iso8859-1')
366
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000367 def new_callback(self, event):
368 dirname, basename = self.io.defaultfilename()
369 self.flist.new(dirname)
370 return "break"
371
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000372 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400373 if (event.state & 4) != 0 and event.keysym == "Home":
374 # state&4==Control. If <Control-Home>, use the Tk binding.
375 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000376 if self.text.index("iomark") and \
377 self.text.compare("iomark", "<=", "insert lineend") and \
378 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400379 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000380 insertpt = int(self.text.index("iomark").split(".")[1])
381 else:
382 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000383 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000384 if line[insertpt] not in (' ','\t'):
385 break
386 else:
387 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000388 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000389 if insertpt == lineat:
390 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000391 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000392 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400393 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000394 self.text.tag_remove("sel", "1.0", "end")
395 else:
396 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200397 # there was no previous selection
398 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400399 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200400 if self.text.compare(self.text.index("sel.first"), "<",
401 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400402 self.text.mark_set("my_anchor", "sel.first") # extend back
403 else:
404 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000405 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400406 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000407 if self.text.compare(first,">",last):
408 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000409 self.text.tag_remove("sel", "1.0", "end")
410 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000411 self.text.mark_set("insert", dest)
412 self.text.see("insert")
413 return "break"
414
David Scherer7aced172000-08-15 01:13:23 +0000415 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000416 self.status_bar = self.MultiStatusBar(self.top)
Ned Deilyb7601672014-03-27 20:49:14 -0700417 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000418 # Insert some padding to avoid obscuring some of the statusbar
419 # by the resize widget.
420 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000421 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
422 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
423 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000424 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
425 self.text.event_add("<<set-line-and-column>>",
426 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000427 self.text.after_idle(self.set_line_and_column)
428
429 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000430 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000431 self.status_bar.set_label('column', 'Col: %s' % column)
432 self.status_bar.set_label('line', 'Ln: %s' % line)
433
David Scherer7aced172000-08-15 01:13:23 +0000434 menu_specs = [
435 ("file", "_File"),
436 ("edit", "_Edit"),
437 ("format", "F_ormat"),
438 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000439 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000440 ("windows", "_Windows"),
441 ("help", "_Help"),
442 ]
443
Ned Deilyb7601672014-03-27 20:49:14 -0700444 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000445 menu_specs[-2] = ("windows", "_Window")
446
447
David Scherer7aced172000-08-15 01:13:23 +0000448 def createmenubar(self):
449 mbar = self.menubar
450 self.menudict = menudict = {}
451 for name, label in self.menu_specs:
452 underline, label = prepstr(label)
453 menudict[name] = menu = Menu(mbar, name=name)
454 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ned Deilyb7601672014-03-27 20:49:14 -0700455 if macosxSupport.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000456 # Insert the application menu
457 menudict['application'] = menu = Menu(mbar, name='apple')
458 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000459 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000460 self.recent_files_menu = Menu(self.menubar)
461 self.menudict['file'].insert_cascade(3, label='Recent Files',
462 underline=0,
463 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000464 self.base_helpmenu_length = self.menudict['help'].index(END)
465 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000466
467 def postwindowsmenu(self):
468 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000469 menu = self.menudict['windows']
470 end = menu.index("end")
471 if end is None:
472 end = -1
473 if end > self.wmenu_end:
474 menu.delete(self.wmenu_end+1, end)
475 WindowList.add_windows_to_menu(menu)
476
477 rmenu = None
478
479 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000480 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
481 if not self.rmenu:
482 self.make_rmenu()
483 rmenu = self.rmenu
484 self.event = event
485 iswin = sys.platform[:3] == 'win'
486 if iswin:
487 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200488
Roger Serwy6b2918a2013-04-07 12:15:52 -0500489 for item in self.rmenu_specs:
490 try:
491 label, eventname, verify_state = item
492 except ValueError: # see issue1207589
493 continue
494
Andrew Svetlovd1837672012-11-01 22:41:19 +0200495 if verify_state is None:
496 continue
497 state = getattr(self, verify_state)()
498 rmenu.entryconfigure(label, state=state)
499
500
David Scherer7aced172000-08-15 01:13:23 +0000501 rmenu.tk_popup(event.x_root, event.y_root)
502 if iswin:
503 self.text.config(cursor="ibeam")
504
505 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200506 # ("Label", "<<virtual-event>>", "statefuncname"), ...
507 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000508 ]
509
510 def make_rmenu(self):
511 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500512 for item in self.rmenu_specs:
513 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200514 if label is not None:
515 def command(text=self.text, eventname=eventname):
516 text.event_generate(eventname)
517 rmenu.add_command(label=label, command=command)
518 else:
519 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000520 self.rmenu = rmenu
521
Andrew Svetlovd1837672012-11-01 22:41:19 +0200522 def rmenu_check_cut(self):
523 return self.rmenu_check_copy()
524
525 def rmenu_check_copy(self):
526 try:
527 indx = self.text.index('sel.first')
528 except TclError:
529 return 'disabled'
530 else:
531 return 'normal' if indx else 'disabled'
532
533 def rmenu_check_paste(self):
534 try:
535 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
536 except TclError:
537 return 'disabled'
538 else:
539 return 'normal'
540
David Scherer7aced172000-08-15 01:13:23 +0000541 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000542 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000543
Steven M. Gava3b55a892001-11-21 05:56:26 +0000544 def config_dialog(self, event=None):
545 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000546
David Scherer7aced172000-08-15 01:13:23 +0000547 def help_dialog(self, event=None):
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500548 if self.root:
549 parent = self.root
550 else:
551 parent = self.top
552 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000553
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000554 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000555 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000556 try:
557 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200558 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000559 tkMessageBox.showerror(title='Document Start Failure',
560 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000561 else:
562 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000563 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000564
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000565 def cut(self,event):
566 self.text.event_generate("<<Cut>>")
567 return "break"
568
569 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000570 if not self.text.tag_ranges("sel"):
571 # There is no selection, so do nothing and maybe interrupt.
572 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000573 self.text.event_generate("<<Copy>>")
574 return "break"
575
576 def paste(self,event):
577 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000578 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000579 return "break"
580
David Scherer7aced172000-08-15 01:13:23 +0000581 def select_all(self, event=None):
582 self.text.tag_add("sel", "1.0", "end-1c")
583 self.text.mark_set("insert", "1.0")
584 self.text.see("insert")
585 return "break"
586
587 def remove_selection(self, event=None):
588 self.text.tag_remove("sel", "1.0", "end")
589 self.text.see("insert")
590
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000591 def move_at_edge_if_selection(self, edge_index):
592 """Cursor move begins at start or end of selection
593
594 When a left/right cursor key is pressed create and return to Tkinter a
595 function which causes a cursor move from the associated edge of the
596 selection.
597
598 """
599 self_text_index = self.text.index
600 self_text_mark_set = self.text.mark_set
601 edges_table = ("sel.first+1c", "sel.last-1c")
602 def move_at_edge(event):
603 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
604 try:
605 self_text_index("sel.first")
606 self_text_mark_set("insert", edges_table[edge_index])
607 except TclError:
608 pass
609 return move_at_edge
610
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000611 def del_word_left(self, event):
612 self.text.event_generate('<Meta-Delete>')
613 return "break"
614
615 def del_word_right(self, event):
616 self.text.event_generate('<Meta-d>')
617 return "break"
618
Steven M. Gavac5976402002-01-04 03:06:08 +0000619 def find_event(self, event):
620 SearchDialog.find(self.text)
621 return "break"
622
623 def find_again_event(self, event):
624 SearchDialog.find_again(self.text)
625 return "break"
626
627 def find_selection_event(self, event):
628 SearchDialog.find_selection(self.text)
629 return "break"
630
631 def find_in_files_event(self, event):
632 GrepDialog.grep(self.text, self.io, self.flist)
633 return "break"
634
635 def replace_event(self, event):
636 ReplaceDialog.replace(self.text)
637 return "break"
638
639 def goto_line_event(self, event):
640 text = self.text
641 lineno = tkSimpleDialog.askinteger("Goto",
642 "Go to line number:",parent=text)
643 if lineno is None:
644 return "break"
645 if lineno <= 0:
646 text.bell()
647 return "break"
648 text.mark_set("insert", "%d.0" % lineno)
649 text.see("insert")
650
David Scherer7aced172000-08-15 01:13:23 +0000651 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000652 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000653 try:
654 name = self.text.get("sel.first", "sel.last")
655 except TclError:
656 name = ""
657 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000658 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000659 name = tkSimpleDialog.askstring("Module",
660 "Enter the name of a Python module\n"
661 "to search on sys.path and open:",
662 parent=self.text, initialvalue=name)
663 if name:
664 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000665 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000666 return
David Scherer7aced172000-08-15 01:13:23 +0000667 # XXX Ought to insert current file's directory in front of path
668 try:
Eric Snow6029e082014-01-25 15:32:46 -0700669 spec = importlib.util.find_spec(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400670 except (ValueError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000671 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
672 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700673 if spec is None:
Brett Cannon50793b42013-06-07 13:17:48 -0400674 tkMessageBox.showerror("Import error", "module not found",
675 parent=self.text)
David Scherer7aced172000-08-15 01:13:23 +0000676 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700677 if not isinstance(spec.loader, importlib.abc.SourceLoader):
Brett Cannon50793b42013-06-07 13:17:48 -0400678 tkMessageBox.showerror("Import error", "not a source-based module",
679 parent=self.text)
680 return
681 try:
Eric Snow02b9f9d2014-01-06 20:42:59 -0700682 file_path = spec.loader.get_filename(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400683 except AttributeError:
684 tkMessageBox.showerror("Import error",
685 "loader does not support get_filename",
686 parent=self.text)
687 return
David Scherer7aced172000-08-15 01:13:23 +0000688 if self.flist:
Brett Cannon50793b42013-06-07 13:17:48 -0400689 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000690 else:
Brett Cannon50793b42013-06-07 13:17:48 -0400691 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400692 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000693
694 def open_class_browser(self, event=None):
695 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400696 if not (self.__class__.__name__ == 'PyShellEditorWindow'
697 and filename):
698 filename = self.open_module()
699 if filename is None:
700 return
David Scherer7aced172000-08-15 01:13:23 +0000701 head, tail = os.path.split(filename)
702 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000703 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000704 ClassBrowser.ClassBrowser(self.flist, base, [head])
705
706 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000707 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000708 PathBrowser.PathBrowser(self.flist)
709
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400710 def open_turtle_demo(self, event = None):
711 import subprocess
712
713 cmd = [sys.executable,
714 '-c',
715 'from turtledemo.__main__ import main; main()']
716 p = subprocess.Popen(cmd, shell=False)
717
David Scherer7aced172000-08-15 01:13:23 +0000718 def gotoline(self, lineno):
719 if lineno is not None and lineno > 0:
720 self.text.mark_set("insert", "%d.0" % lineno)
721 self.text.tag_remove("sel", "1.0", "end")
722 self.text.tag_add("sel", "insert", "insert +1l")
723 self.center()
724
725 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000726 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000727 return True
David Scherer7aced172000-08-15 01:13:23 +0000728 base, ext = os.path.splitext(os.path.basename(filename))
729 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000730 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000731 line = self.text.get('1.0', '1.0 lineend')
732 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000733
734 def close_hook(self):
735 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000736 self.flist.unregister_maybe_terminate(self)
737 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000738
739 def set_close_hook(self, close_hook):
740 self.close_hook = close_hook
741
742 def filename_change_hook(self):
743 if self.flist:
744 self.flist.filename_changed_edit(self)
745 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000746 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000747 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000748
Christian Heimesa156e092008-02-16 07:38:31 +0000749 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000750 if self.color:
751 return
Christian Heimesa156e092008-02-16 07:38:31 +0000752 if self.ispythonsource(self.io.filename):
753 self.color = self.ColorDelegator()
754 # can add more colorizers here...
755 if self.color:
756 self.per.removefilter(self.undo)
757 self.per.insertfilter(self.color)
758 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000759
Christian Heimesa156e092008-02-16 07:38:31 +0000760 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000761 if not self.color:
762 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000763 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000764 self.per.removefilter(self.color)
765 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000766
Steven M. Gavab77d3432002-03-02 07:16:21 +0000767 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400768 "Update the color theme"
Christian Heimesa156e092008-02-16 07:38:31 +0000769 # Called from self.filename_change_hook and from configDialog.py
770 self._rmcolorizer()
771 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000772 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000773 normal_colors = idleConf.GetHighlight(theme, 'normal')
774 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
775 select_colors = idleConf.GetHighlight(theme, 'hilite')
776 self.text.config(
777 foreground=normal_colors['foreground'],
778 background=normal_colors['background'],
779 insertbackground=cursor_color,
780 selectforeground=select_colors['foreground'],
781 selectbackground=select_colors['background'],
782 )
David Scherer7aced172000-08-15 01:13:23 +0000783
Guido van Rossum33d26892007-08-05 15:29:28 +0000784 IDENTCHARS = string.ascii_letters + string.digits + "_"
785
786 def colorize_syntax_error(self, text, pos):
787 text.tag_add("ERROR", pos)
788 char = text.get(pos)
789 if char and char in self.IDENTCHARS:
790 text.tag_add("ERROR", pos + " wordstart", pos)
791 if '\n' == text.get(pos): # error at line end
792 text.mark_set("insert", pos)
793 else:
794 text.mark_set("insert", pos + "+1c")
795 text.see(pos)
796
Steven M. Gavab1585412002-03-12 00:21:56 +0000797 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000798 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000799 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000800 fontWeight='normal'
801 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
802 fontWeight='bold'
803 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200804 idleConf.GetOption('main','EditorWindow','font-size',
805 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000806 fontWeight))
807
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000808 def RemoveKeybindings(self):
809 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000810 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000811 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000812 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000813 self.text.event_delete(event, *keylist)
814 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000815 xkeydefs = idleConf.GetExtensionBindings(extensionName)
816 if xkeydefs:
817 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000818 self.text.event_delete(event, *keylist)
819
820 def ApplyKeybindings(self):
821 "Update the keybindings after they are changed"
822 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000823 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000824 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000825 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000826 xkeydefs = idleConf.GetExtensionBindings(extensionName)
827 if xkeydefs:
828 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000829 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000830 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000831 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000832 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000833 for item in menu[1]:
834 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000835 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000836 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000837 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700838 end = menu.index(END)
839 if end is None:
840 # Skip empty menus
841 continue
842 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000843 for index in range(0, end):
844 if menu.type(index) == 'command':
845 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000846 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000847 itemName = menu.entrycget(index, 'label')
848 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000849 if menubarItem in menuEventDict:
850 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000851 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000852 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000853 accel = get_accelerator(keydefs, event)
854 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000855
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000856 def set_notabs_indentwidth(self):
857 "Update the indentwidth if changed and not using tabs in this window"
858 # Called from configDialog.py
859 if not self.usetabs:
860 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
861 type='int')
862
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000863 def reset_help_menu_entries(self):
864 "Update the additional help entries on the Help menu"
865 help_list = idleConf.GetAllExtraHelpSourcesList()
866 helpmenu = self.menudict['help']
867 # first delete the extra help entries, if any
868 helpmenu_length = helpmenu.index(END)
869 if helpmenu_length > self.base_helpmenu_length:
870 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
871 # then rebuild them
872 if help_list:
873 helpmenu.add_separator()
874 for entry in help_list:
875 cmd = self.__extra_help_callback(entry[1])
876 helpmenu.add_command(label=entry[0], command=cmd)
877 # and update the menu dictionary
878 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000879
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000880 def __extra_help_callback(self, helpfile):
881 "Create a callback with the helpfile value frozen at definition time"
882 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000883 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000884 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000885 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000886 try:
887 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200888 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000889 tkMessageBox.showerror(title='Document Start Failure',
890 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000891 else:
892 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000893 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000894
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000895 def update_recent_files_list(self, new_file=None):
896 "Load and update the recent files list and menus"
897 rf_list = []
898 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400899 with open(self.recent_files_path, 'r',
900 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000901 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000902 if new_file:
903 new_file = os.path.abspath(new_file) + '\n'
904 if new_file in rf_list:
905 rf_list.remove(new_file) # move to top
906 rf_list.insert(0, new_file)
907 # clean and save the recent files list
908 bad_paths = []
909 for path in rf_list:
910 if '\0' in path or not os.path.exists(path[0:-1]):
911 bad_paths.append(path)
912 rf_list = [path for path in rf_list if path not in bad_paths]
913 ulchars = "1234567890ABCDEFGHIJK"
914 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000915 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800916 with open(self.recent_files_path, 'w',
917 encoding='utf_8', errors='replace') as rf_file:
918 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200919 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800920 if not getattr(self.root, "recentfilelist_error_displayed", False):
921 self.root.recentfilelist_error_displayed = True
922 tkMessageBox.showerror(title='IDLE Error',
923 message='Unable to update Recent Files list:\n%s'
924 % str(err),
925 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000926 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000927 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000928 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700929 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000930 for i, file_name in enumerate(rf_list):
931 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000932 # make unicode string to display non-ASCII chars correctly
933 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000934 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000935 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000936 command=callback,
937 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000938
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000939 def __recent_file_callback(self, file_name):
940 def open_recent_file(fn_closure=file_name):
941 self.io.open(editFile=fn_closure)
942 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000943
David Scherer7aced172000-08-15 01:13:23 +0000944 def saved_change_hook(self):
945 short = self.short_title()
946 long = self.long_title()
947 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400948 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000949 elif short:
950 title = short
951 elif long:
952 title = long
953 else:
954 title = "Untitled"
955 icon = short or long or title
956 if not self.get_saved():
957 title = "*%s*" % title
958 icon = "*%s" % icon
959 self.top.wm_title(title)
960 self.top.wm_iconname(icon)
961
962 def get_saved(self):
963 return self.undo.get_saved()
964
965 def set_saved(self, flag):
966 self.undo.set_saved(flag)
967
968 def reset_undo(self):
969 self.undo.reset_undo()
970
971 def short_title(self):
972 filename = self.io.filename
973 if filename:
974 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500975 else:
976 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000977 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400978 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000979
980 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000981 # return unicode string to display non-ASCII chars correctly
982 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000983
984 def center_insert_event(self, event):
985 self.center()
986
987 def center(self, mark="insert"):
988 text = self.text
989 top, bot = self.getwindowlines()
990 lineno = self.getlineno(mark)
991 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000992 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000993 text.yview(float(newtop))
994
995 def getwindowlines(self):
996 text = self.text
997 top = self.getlineno("@0,0")
998 bot = self.getlineno("@0,65535")
999 if top == bot and text.winfo_height() == 1:
1000 # Geometry manager hasn't run yet
1001 height = int(text['height'])
1002 bot = top + height - 1
1003 return top, bot
1004
1005 def getlineno(self, mark="insert"):
1006 text = self.text
1007 return int(float(text.index(mark)))
1008
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001009 def get_geometry(self):
1010 "Return (width, height, x, y)"
1011 geom = self.top.wm_geometry()
1012 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +00001013 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001014
David Scherer7aced172000-08-15 01:13:23 +00001015 def close_event(self, event):
1016 self.close()
1017
1018 def maybesave(self):
1019 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001020 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001021 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001022 self.top.deiconify()
1023 self.top.lower()
1024 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001025 return self.io.maybesave()
1026
1027 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001028 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +00001029 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001030 self._close()
1031 return reply
1032
1033 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001034 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001035 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001036 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001037 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001038 self.io.close()
1039 self.io = None
1040 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001041 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001042 self.color.close(False)
1043 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001044 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001045 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001046 self.per.close()
1047 self.per = None
1048 self.top.destroy()
1049 if self.close_hook:
1050 # unless override: unregister from flist, terminate if last window
1051 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001052
1053 def load_extensions(self):
1054 self.extensions = {}
1055 self.load_standard_extensions()
1056
1057 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001058 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001059 if hasattr(ins, "close"):
1060 ins.close()
1061 self.extensions = {}
1062
1063 def load_standard_extensions(self):
1064 for name in self.get_standard_extension_names():
1065 try:
1066 self.load_extension(name)
1067 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001068 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001069 traceback.print_exc()
1070
1071 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001072 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001073
1074 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001075 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001076 try:
1077 mod = importlib.import_module('.' + name, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001078 except (ImportError, TypeError):
Brett Cannonaef82d32012-04-14 20:44:23 -04001079 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001080 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001081 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001082 raise
David Scherer7aced172000-08-15 01:13:23 +00001083 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001084 keydefs = idleConf.GetExtensionBindings(name)
1085 if hasattr(cls, "menudefs"):
1086 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001087 ins = cls(self)
1088 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001089 if keydefs:
1090 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001091 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001092 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001093 while methodname[:1] == '<':
1094 methodname = methodname[1:]
1095 while methodname[-1:] == '>':
1096 methodname = methodname[:-1]
1097 methodname = methodname + "_event"
1098 if hasattr(ins, methodname):
1099 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001100
1101 def apply_bindings(self, keydefs=None):
1102 if keydefs is None:
1103 keydefs = self.Bindings.default_keydefs
1104 text = self.text
1105 text.keydefs = keydefs
1106 for event, keylist in keydefs.items():
1107 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001108 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001109
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001110 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001111 """Add appropriate entries to the menus and submenus
1112
1113 Menus that are absent or None in self.menudict are ignored.
1114 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001115 if menudefs is None:
1116 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001117 if keydefs is None:
1118 keydefs = self.Bindings.default_keydefs
1119 menudict = self.menudict
1120 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001121 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001122 menu = menudict.get(mname)
1123 if not menu:
1124 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001125 for entry in entrylist:
1126 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001127 menu.add_separator()
1128 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001129 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001130 checkbutton = (label[:1] == '!')
1131 if checkbutton:
1132 label = label[1:]
1133 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001134 accelerator = get_accelerator(keydefs, eventname)
1135 def command(text=text, eventname=eventname):
1136 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001137 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001138 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001139 menu.add_checkbutton(label=label, underline=underline,
1140 command=command, accelerator=accelerator,
1141 variable=var)
1142 else:
1143 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001144 command=command,
1145 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001146
1147 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001148 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001149 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001150 value = var.get()
1151 return value
1152 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001153 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001154
1155 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001156 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001157 if var:
1158 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001159 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001160 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001161
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001162 def get_var_obj(self, name, vartype=None):
1163 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001164 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001165 # create a Tkinter variable object with self.text as master:
1166 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001167 return var
1168
1169 # Tk implementations of "virtual text methods" -- each platform
1170 # reusing IDLE's support code needs to define these for its GUI's
1171 # flavor of widget.
1172
1173 # Is character at text_index in a Python string? Return 0 for
1174 # "guaranteed no", true for anything else. This info is expensive
1175 # to compute ab initio, but is probably already known by the
1176 # platform's colorizer.
1177
1178 def is_char_in_string(self, text_index):
1179 if self.color:
1180 # Return true iff colorizer hasn't (re)gotten this far
1181 # yet, or the character is tagged as being in a string
1182 return self.text.tag_prevrange("TODO", text_index) or \
1183 "STRING" in self.text.tag_names(text_index)
1184 else:
1185 # The colorizer is missing: assume the worst
1186 return 1
1187
1188 # If a selection is defined in the text widget, return (start,
1189 # end) as Tkinter text indices, otherwise return (None, None)
1190 def get_selection_indices(self):
1191 try:
1192 first = self.text.index("sel.first")
1193 last = self.text.index("sel.last")
1194 return first, last
1195 except TclError:
1196 return None, None
1197
1198 # Return the text widget's current view of what a tab stop means
1199 # (equivalent width in spaces).
1200
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001201 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001202 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1203 return int(current)
1204
1205 # Set the text widget's current view of what a tab stop means.
1206
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001207 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001208 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001209 if self.get_tk_tabwidth() != newtabwidth:
1210 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001211 pixels = text.tk.call("font", "measure", text["font"],
1212 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001213 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001214 text.configure(tabs=pixels)
1215
Guido van Rossum33d26892007-08-05 15:29:28 +00001216### begin autoindent code ### (configuration was moved to beginning of class)
1217
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001218 def set_indentation_params(self, is_py_src, guess=True):
1219 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001220 i = self.guess_indent()
1221 if 2 <= i <= 8:
1222 self.indentwidth = i
1223 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001224 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001225 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001226
1227 def smart_backspace_event(self, event):
1228 text = self.text
1229 first, last = self.get_selection_indices()
1230 if first and last:
1231 text.delete(first, last)
1232 text.mark_set("insert", first)
1233 return "break"
1234 # Delete whitespace left, until hitting a real char or closest
1235 # preceding virtual tab stop.
1236 chars = text.get("insert linestart", "insert")
1237 if chars == '':
1238 if text.compare("insert", ">", "1.0"):
1239 # easy: delete preceding newline
1240 text.delete("insert-1c")
1241 else:
1242 text.bell() # at start of buffer
1243 return "break"
1244 if chars[-1] not in " \t":
1245 # easy: delete preceding real char
1246 text.delete("insert-1c")
1247 return "break"
1248 # Ick. It may require *inserting* spaces if we back up over a
1249 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001250 tabwidth = self.tabwidth
1251 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001252 assert have > 0
1253 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001254 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001255 if self.context_use_ps1:
1256 last_line_of_prompt = sys.ps1.split('\n')[-1]
1257 else:
1258 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001259 ncharsdeleted = 0
1260 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001261 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001262 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001263 chars = chars[:-1]
1264 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001265 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001266 if have <= want or chars[-1] not in " \t":
1267 break
1268 text.undo_block_start()
1269 text.delete("insert-%dc" % ncharsdeleted, "insert")
1270 if have < want:
1271 text.insert("insert", ' ' * (want - have))
1272 text.undo_block_stop()
1273 return "break"
1274
1275 def smart_indent_event(self, event):
1276 # if intraline selection:
1277 # delete it
1278 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001279 # do indent-region
1280 # else:
1281 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001282 text = self.text
1283 first, last = self.get_selection_indices()
1284 text.undo_block_start()
1285 try:
1286 if first and last:
1287 if index2line(first) != index2line(last):
1288 return self.indent_region_event(event)
1289 text.delete(first, last)
1290 text.mark_set("insert", first)
1291 prefix = text.get("insert linestart", "insert")
1292 raw, effective = classifyws(prefix, self.tabwidth)
1293 if raw == len(prefix):
1294 # only whitespace to the left
1295 self.reindent_to(effective + self.indentwidth)
1296 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001297 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001298 if self.usetabs:
1299 pad = '\t'
1300 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001301 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001302 n = self.indentwidth
1303 pad = ' ' * (n - effective % n)
1304 text.insert("insert", pad)
1305 text.see("insert")
1306 return "break"
1307 finally:
1308 text.undo_block_stop()
1309
1310 def newline_and_indent_event(self, event):
1311 text = self.text
1312 first, last = self.get_selection_indices()
1313 text.undo_block_start()
1314 try:
1315 if first and last:
1316 text.delete(first, last)
1317 text.mark_set("insert", first)
1318 line = text.get("insert linestart", "insert")
1319 i, n = 0, len(line)
1320 while i < n and line[i] in " \t":
1321 i = i+1
1322 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001323 # the cursor is in or at leading indentation in a continuation
1324 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001325 text.insert("insert linestart", '\n')
1326 return "break"
1327 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001328 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001329 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001330 last_line_of_prompt = sys.ps1.split('\n')[-1]
1331 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001332 line = line[:-1]
1333 i = i+1
1334 if i:
1335 text.delete("insert - %d chars" % i, "insert")
1336 # strip whitespace after insert point
1337 while text.get("insert") in " \t":
1338 text.delete("insert")
1339 # start new line
1340 text.insert("insert", '\n')
1341
1342 # adjust indentation for continuations and block
1343 # open/close first need to find the last stmt
1344 lno = index2line(text.index('insert'))
1345 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001346 if not self.context_use_ps1:
1347 for context in self.num_context_lines:
1348 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001349 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001350 rawtext = text.get(startatindex, "insert")
1351 y.set_str(rawtext)
1352 bod = y.find_good_parse_start(
1353 self.context_use_ps1,
1354 self._build_char_in_string_func(startatindex))
1355 if bod is not None or startat == 1:
1356 break
1357 y.set_lo(bod or 0)
1358 else:
1359 r = text.tag_prevrange("console", "insert")
1360 if r:
1361 startatindex = r[1]
1362 else:
1363 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001364 rawtext = text.get(startatindex, "insert")
1365 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001366 y.set_lo(0)
1367
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001368 c = y.get_continuation_type()
1369 if c != PyParse.C_NONE:
1370 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001371 if c == PyParse.C_STRING_FIRST_LINE:
1372 # after the first line of a string; do not indent at all
1373 pass
1374 elif c == PyParse.C_STRING_NEXT_LINES:
1375 # inside a string which started before this line;
1376 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001377 text.insert("insert", indent)
1378 elif c == PyParse.C_BRACKET:
1379 # line up with the first (if any) element of the
1380 # last open bracket structure; else indent one
1381 # level beyond the indent of the line with the
1382 # last open bracket
1383 self.reindent_to(y.compute_bracket_indent())
1384 elif c == PyParse.C_BACKSLASH:
1385 # if more than one line in this stmt already, just
1386 # mimic the current indent; else if initial line
1387 # has a start on an assignment stmt, indent to
1388 # beyond leftmost =; else to beyond first chunk of
1389 # non-whitespace on initial line
1390 if y.get_num_lines_in_stmt() > 1:
1391 text.insert("insert", indent)
1392 else:
1393 self.reindent_to(y.compute_backslash_indent())
1394 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001395 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001396 return "break"
1397
1398 # This line starts a brand new stmt; indent relative to
1399 # indentation of initial line of closest preceding
1400 # interesting stmt.
1401 indent = y.get_base_indent_string()
1402 text.insert("insert", indent)
1403 if y.is_block_opener():
1404 self.smart_indent_event(event)
1405 elif indent and y.is_block_closer():
1406 self.smart_backspace_event(event)
1407 return "break"
1408 finally:
1409 text.see("insert")
1410 text.undo_block_stop()
1411
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001412 # Our editwin provides a is_char_in_string function that works
1413 # with a Tk text index, but PyParse only knows about offsets into
1414 # a string. This builds a function for PyParse that accepts an
1415 # offset.
1416
1417 def _build_char_in_string_func(self, startindex):
1418 def inner(offset, _startindex=startindex,
1419 _icis=self.is_char_in_string):
1420 return _icis(_startindex + "+%dc" % offset)
1421 return inner
1422
1423 def indent_region_event(self, event):
1424 head, tail, chars, lines = self.get_region()
1425 for pos in range(len(lines)):
1426 line = lines[pos]
1427 if line:
1428 raw, effective = classifyws(line, self.tabwidth)
1429 effective = effective + self.indentwidth
1430 lines[pos] = self._make_blanks(effective) + line[raw:]
1431 self.set_region(head, tail, chars, lines)
1432 return "break"
1433
1434 def dedent_region_event(self, event):
1435 head, tail, chars, lines = self.get_region()
1436 for pos in range(len(lines)):
1437 line = lines[pos]
1438 if line:
1439 raw, effective = classifyws(line, self.tabwidth)
1440 effective = max(effective - self.indentwidth, 0)
1441 lines[pos] = self._make_blanks(effective) + line[raw:]
1442 self.set_region(head, tail, chars, lines)
1443 return "break"
1444
1445 def comment_region_event(self, event):
1446 head, tail, chars, lines = self.get_region()
1447 for pos in range(len(lines) - 1):
1448 line = lines[pos]
1449 lines[pos] = '##' + line
1450 self.set_region(head, tail, chars, lines)
1451
1452 def uncomment_region_event(self, event):
1453 head, tail, chars, lines = self.get_region()
1454 for pos in range(len(lines)):
1455 line = lines[pos]
1456 if not line:
1457 continue
1458 if line[:2] == '##':
1459 line = line[2:]
1460 elif line[:1] == '#':
1461 line = line[1:]
1462 lines[pos] = line
1463 self.set_region(head, tail, chars, lines)
1464
1465 def tabify_region_event(self, event):
1466 head, tail, chars, lines = self.get_region()
1467 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001468 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001469 for pos in range(len(lines)):
1470 line = lines[pos]
1471 if line:
1472 raw, effective = classifyws(line, tabwidth)
1473 ntabs, nspaces = divmod(effective, tabwidth)
1474 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1475 self.set_region(head, tail, chars, lines)
1476
1477 def untabify_region_event(self, event):
1478 head, tail, chars, lines = self.get_region()
1479 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001480 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001481 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001482 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001483 self.set_region(head, tail, chars, lines)
1484
1485 def toggle_tabs_event(self, event):
1486 if self.askyesno(
1487 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001488 "Turn tabs " + ("on", "off")[self.usetabs] +
1489 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001490 ("will be", "remains at")[self.usetabs] + " 8." +
1491 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001492 parent=self.text):
1493 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001494 # Try to prevent inconsistent indentation.
1495 # User must change indent width manually after using tabs.
1496 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001497 return "break"
1498
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001499 # XXX this isn't bound to anything -- see tabwidth comments
1500## def change_tabwidth_event(self, event):
1501## new = self._asktabwidth()
1502## if new != self.tabwidth:
1503## self.tabwidth = new
1504## self.set_indentation_params(0, guess=0)
1505## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001506
1507 def change_indentwidth_event(self, event):
1508 new = self.askinteger(
1509 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001510 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001511 parent=self.text,
1512 initialvalue=self.indentwidth,
1513 minvalue=2,
1514 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001515 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001516 self.indentwidth = new
1517 return "break"
1518
1519 def get_region(self):
1520 text = self.text
1521 first, last = self.get_selection_indices()
1522 if first and last:
1523 head = text.index(first + " linestart")
1524 tail = text.index(last + "-1c lineend +1c")
1525 else:
1526 head = text.index("insert linestart")
1527 tail = text.index("insert lineend +1c")
1528 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001529 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001530 return head, tail, chars, lines
1531
1532 def set_region(self, head, tail, chars, lines):
1533 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001534 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001535 if newchars == chars:
1536 text.bell()
1537 return
1538 text.tag_remove("sel", "1.0", "end")
1539 text.mark_set("insert", head)
1540 text.undo_block_start()
1541 text.delete(head, tail)
1542 text.insert(head, newchars)
1543 text.undo_block_stop()
1544 text.tag_add("sel", head, "insert")
1545
1546 # Make string that displays as n leading blanks.
1547
1548 def _make_blanks(self, n):
1549 if self.usetabs:
1550 ntabs, nspaces = divmod(n, self.tabwidth)
1551 return '\t' * ntabs + ' ' * nspaces
1552 else:
1553 return ' ' * n
1554
1555 # Delete from beginning of line to insert point, then reinsert
1556 # column logical (meaning use tabs if appropriate) spaces.
1557
1558 def reindent_to(self, column):
1559 text = self.text
1560 text.undo_block_start()
1561 if text.compare("insert linestart", "!=", "insert"):
1562 text.delete("insert linestart", "insert")
1563 if column:
1564 text.insert("insert", self._make_blanks(column))
1565 text.undo_block_stop()
1566
1567 def _asktabwidth(self):
1568 return self.askinteger(
1569 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001570 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001571 parent=self.text,
1572 initialvalue=self.indentwidth,
1573 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001574 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001575
1576 # Guess indentwidth from text content.
1577 # Return guessed indentwidth. This should not be believed unless
1578 # it's in a reasonable range (e.g., it will be 0 if no indented
1579 # blocks are found).
1580
1581 def guess_indent(self):
1582 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1583 if opener and indented:
1584 raw, indentsmall = classifyws(opener, self.tabwidth)
1585 raw, indentlarge = classifyws(indented, self.tabwidth)
1586 else:
1587 indentsmall = indentlarge = 0
1588 return indentlarge - indentsmall
1589
1590# "line.col" -> line, as an int
1591def index2line(index):
1592 return int(float(index))
1593
1594# Look at the leading whitespace in s.
1595# Return pair (# of leading ws characters,
1596# effective # of leading blanks after expanding
1597# tabs to width tabwidth)
1598
1599def classifyws(s, tabwidth):
1600 raw = effective = 0
1601 for ch in s:
1602 if ch == ' ':
1603 raw = raw + 1
1604 effective = effective + 1
1605 elif ch == '\t':
1606 raw = raw + 1
1607 effective = (effective // tabwidth + 1) * tabwidth
1608 else:
1609 break
1610 return raw, effective
1611
1612import tokenize
1613_tokenize = tokenize
1614del tokenize
1615
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001616class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001617
1618 # .run() chews over the Text widget, looking for a block opener
1619 # and the stmt following it. Returns a pair,
1620 # (line containing block opener, line containing stmt)
1621 # Either or both may be None.
1622
1623 def __init__(self, text, tabwidth):
1624 self.text = text
1625 self.tabwidth = tabwidth
1626 self.i = self.finished = 0
1627 self.blkopenline = self.indentedline = None
1628
1629 def readline(self):
1630 if self.finished:
1631 return ""
1632 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001633 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001634 if self.text.compare(mark, ">=", "end"):
1635 return ""
1636 return self.text.get(mark, mark + " lineend+1c")
1637
1638 def tokeneater(self, type, token, start, end, line,
1639 INDENT=_tokenize.INDENT,
1640 NAME=_tokenize.NAME,
1641 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1642 if self.finished:
1643 pass
1644 elif type == NAME and token in OPENERS:
1645 self.blkopenline = line
1646 elif type == INDENT and self.blkopenline:
1647 self.indentedline = line
1648 self.finished = 1
1649
1650 def run(self):
1651 save_tabsize = _tokenize.tabsize
1652 _tokenize.tabsize = self.tabwidth
1653 try:
1654 try:
Trent Nelson428de652008-03-18 22:41:35 +00001655 tokens = _tokenize.generate_tokens(self.readline)
1656 for token in tokens:
1657 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001658 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001659 # since we cut off the tokenizer early, we can trigger
1660 # spurious errors
1661 pass
1662 finally:
1663 _tokenize.tabsize = save_tabsize
1664 return self.blkopenline, self.indentedline
1665
1666### end autoindent code ###
1667
David Scherer7aced172000-08-15 01:13:23 +00001668def prepstr(s):
1669 # Helper to extract the underscore from a string, e.g.
1670 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001671 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001672 if i >= 0:
1673 s = s[:i] + s[i+1:]
1674 return i, s
1675
1676
1677keynames = {
1678 'bracketleft': '[',
1679 'bracketright': ']',
1680 'slash': '/',
1681}
1682
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001683def get_accelerator(keydefs, eventname):
1684 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001685 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1686 # if not keylist:
Ned Deilyb7601672014-03-27 20:49:14 -07001687 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001688 "<<open-module>>",
1689 "<<goto-line>>",
1690 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001691 return ""
1692 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001693 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001694 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1695 s = re.sub("Key-", "", s)
1696 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1697 s = re.sub("Control-", "Ctrl-", s)
1698 s = re.sub("-", "+", s)
1699 s = re.sub("><", " ", s)
1700 s = re.sub("<", "", s)
1701 s = re.sub(">", "", s)
1702 return s
1703
1704
1705def fixwordbreaks(root):
1706 # Make sure that Tk's double-click and next/previous word
1707 # operations use our definition of a word (i.e. an identifier)
1708 tk = root.tk
1709 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1710 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1711 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1712
1713
Terry Jan Reedycd567362014-10-17 01:31:35 -04001714def _editor_window(parent): # htest #
1715 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001716 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001717 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001718 if sys.argv[1:]:
1719 filename = sys.argv[1]
1720 else:
1721 filename = None
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001722 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001723 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001724 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001725 # Does not stop error, neither does following
1726 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001727
1728if __name__ == '__main__':
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001729 from idlelib.idle_test.htest import run
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001730 run(_help_dialog, _editor_window)