blob: 7d2ea8f7596979b579f7c31b215b39351982acfc [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)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400192 text.bind("<<open-config-extensions-dialog>>",
193 self.config_extensions_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000194 text.bind("<<open-module>>", self.open_module)
195 text.bind("<<do-nothing>>", lambda event: "break")
196 text.bind("<<select-all>>", self.select_all)
197 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000198 text.bind("<<find>>", self.find_event)
199 text.bind("<<find-again>>", self.find_again_event)
200 text.bind("<<find-in-files>>", self.find_in_files_event)
201 text.bind("<<find-selection>>", self.find_selection_event)
202 text.bind("<<replace>>", self.replace_event)
203 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000204 text.bind("<<smart-backspace>>",self.smart_backspace_event)
205 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
206 text.bind("<<smart-indent>>",self.smart_indent_event)
207 text.bind("<<indent-region>>",self.indent_region_event)
208 text.bind("<<dedent-region>>",self.dedent_region_event)
209 text.bind("<<comment-region>>",self.comment_region_event)
210 text.bind("<<uncomment-region>>",self.uncomment_region_event)
211 text.bind("<<tabify-region>>",self.tabify_region_event)
212 text.bind("<<untabify-region>>",self.untabify_region_event)
213 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
214 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000215 text.bind("<Left>", self.move_at_edge_if_selection(0))
216 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000217 text.bind("<<del-word-left>>", self.del_word_left)
218 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000219 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000220
David Scherer7aced172000-08-15 01:13:23 +0000221 if flist:
222 flist.inversedict[self] = key
223 if key:
224 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000225 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000226 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
227 text.bind("<<open-class-browser>>", self.open_class_browser)
228 text.bind("<<open-path-browser>>", self.open_path_browser)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400229 text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
David Scherer7aced172000-08-15 01:13:23 +0000230
Steven M. Gava898a3652001-10-07 11:10:44 +0000231 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000232 vbar['command'] = text.yview
233 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000234 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000235 fontWeight = 'normal'
236 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000237 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000238 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200239 idleConf.GetOption('main', 'EditorWindow',
240 'font-size', type='int'),
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000241 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000242 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
243 text.pack(side=TOP, fill=BOTH, expand=1)
244 text.focus_set()
245
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000246 # usetabs true -> literal tab characters are used by indent and
247 # dedent cmds, possibly mixed with spaces if
248 # indentwidth is not a multiple of tabwidth,
249 # which will cause Tabnanny to nag!
250 # false -> tab characters are converted to spaces by indent
251 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000252 # Although use-spaces=0 can be configured manually in config-main.def,
253 # configuration of tabs v. spaces is not supported in the configuration
254 # dialog. IDLE promotes the preferred Python indentation: use spaces!
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200255 usespaces = idleConf.GetOption('main', 'Indent',
256 'use-spaces', type='bool')
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000257 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000258
259 # tabwidth is the display width of a literal tab character.
260 # CAUTION: telling Tk to use anything other than its default
261 # tab setting causes it to use an entirely different tabbing algorithm,
262 # treating tab stops as fixed distances from the left margin.
263 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000264 self.tabwidth = 8 # must remain 8 until Tk is fixed.
265
266 # indentwidth is the number of screen characters per indent level.
267 # The recommended Python indentation is four spaces.
268 self.indentwidth = self.tabwidth
269 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000270
271 # If context_use_ps1 is true, parsing searches back for a ps1 line;
272 # else searches for a popular (if, def, ...) Python stmt.
273 self.context_use_ps1 = False
274
275 # When searching backwards for a reliable place to begin parsing,
276 # first start num_context_lines[0] lines back, then
277 # num_context_lines[1] lines back if that didn't work, and so on.
278 # The last value should be huge (larger than the # of lines in a
279 # conceivable file).
280 # Making the initial values larger slows things down more often.
281 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000282 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000283 self.undo = undo = self.UndoDelegator()
284 per.insertfilter(undo)
285 text.undo_block_start = undo.undo_block_start
286 text.undo_block_stop = undo.undo_block_stop
287 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000288 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000289 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000290 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000291 self.good_load = False
292 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000293 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000294 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000295 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000296 if io.loadfile(filename):
297 self.good_load = True
298 is_py_src = self.ispythonsource(filename)
299 self.set_indentation_params(is_py_src)
David Scherer7aced172000-08-15 01:13:23 +0000300 else:
301 io.set_filename(filename)
Roger Serwy5b1ab242013-05-05 11:34:21 -0500302 self.good_load = True
303
Christian Heimesa156e092008-02-16 07:38:31 +0000304 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000305 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000306 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000307 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000308 menu = self.menudict.get('windows')
309 if menu:
310 end = menu.index("end")
311 if end is None:
312 end = -1
313 if end >= 0:
314 menu.add_separator()
315 end = end + 1
316 self.wmenu_end = end
317 WindowList.register_callback(self.postwindowsmenu)
318
319 # Some abstractions so IDLE extensions are cross-IDE
320 self.askyesno = tkMessageBox.askyesno
321 self.askinteger = tkSimpleDialog.askinteger
322 self.showerror = tkMessageBox.showerror
323
Roger Serwycaf30242013-05-20 22:13:39 -0500324 self._highlight_workaround() # Fix selection tags on Windows
325
326 def _highlight_workaround(self):
327 # On Windows, Tk removes painting of the selection
328 # tags which is different behavior than on Linux and Mac.
329 # See issue14146 for more information.
330 if not sys.platform.startswith('win'):
331 return
332
333 text = self.text
334 text.event_add("<<Highlight-FocusOut>>", "<FocusOut>")
335 text.event_add("<<Highlight-FocusIn>>", "<FocusIn>")
336 def highlight_fix(focus):
337 sel_range = text.tag_ranges("sel")
338 if sel_range:
339 if focus == 'out':
340 HILITE_CONFIG = idleConf.GetHighlight(
341 idleConf.CurrentTheme(), 'hilite')
342 text.tag_config("sel_fix", HILITE_CONFIG)
343 text.tag_raise("sel_fix")
344 text.tag_add("sel_fix", *sel_range)
345 elif focus == 'in':
346 text.tag_remove("sel_fix", "1.0", "end")
347
348 text.bind("<<Highlight-FocusOut>>",
349 lambda ev: highlight_fix("out"))
350 text.bind("<<Highlight-FocusIn>>",
351 lambda ev: highlight_fix("in"))
352
353
Martin v. Löwis307021f2005-11-27 16:59:04 +0000354 def _filename_to_unicode(self, filename):
355 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000356 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000357 return filename
358 else:
359 try:
360 return filename.decode(self.filesystemencoding)
361 except UnicodeDecodeError:
362 # XXX
363 try:
364 return filename.decode(self.encoding)
365 except UnicodeDecodeError:
366 # byte-to-byte conversion
367 return filename.decode('iso8859-1')
368
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000369 def new_callback(self, event):
370 dirname, basename = self.io.defaultfilename()
371 self.flist.new(dirname)
372 return "break"
373
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000374 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400375 if (event.state & 4) != 0 and event.keysym == "Home":
376 # state&4==Control. If <Control-Home>, use the Tk binding.
377 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000378 if self.text.index("iomark") and \
379 self.text.compare("iomark", "<=", "insert lineend") and \
380 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400381 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000382 insertpt = int(self.text.index("iomark").split(".")[1])
383 else:
384 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000385 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000386 if line[insertpt] not in (' ','\t'):
387 break
388 else:
389 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000390 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000391 if insertpt == lineat:
392 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000393 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000394 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400395 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000396 self.text.tag_remove("sel", "1.0", "end")
397 else:
398 if not self.text.index("sel.first"):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200399 # there was no previous selection
400 self.text.mark_set("my_anchor", "insert")
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400401 else:
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200402 if self.text.compare(self.text.index("sel.first"), "<",
403 self.text.index("insert")):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400404 self.text.mark_set("my_anchor", "sel.first") # extend back
405 else:
406 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000407 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400408 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000409 if self.text.compare(first,">",last):
410 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000411 self.text.tag_remove("sel", "1.0", "end")
412 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000413 self.text.mark_set("insert", dest)
414 self.text.see("insert")
415 return "break"
416
David Scherer7aced172000-08-15 01:13:23 +0000417 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000418 self.status_bar = self.MultiStatusBar(self.top)
Ned Deilyb7601672014-03-27 20:49:14 -0700419 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000420 # Insert some padding to avoid obscuring some of the statusbar
421 # by the resize widget.
422 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000423 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
424 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
425 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000426 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
427 self.text.event_add("<<set-line-and-column>>",
428 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000429 self.text.after_idle(self.set_line_and_column)
430
431 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000432 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000433 self.status_bar.set_label('column', 'Col: %s' % column)
434 self.status_bar.set_label('line', 'Ln: %s' % line)
435
David Scherer7aced172000-08-15 01:13:23 +0000436 menu_specs = [
437 ("file", "_File"),
438 ("edit", "_Edit"),
439 ("format", "F_ormat"),
440 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000441 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000442 ("windows", "_Windows"),
443 ("help", "_Help"),
444 ]
445
Ned Deilyb7601672014-03-27 20:49:14 -0700446 if sys.platform == "darwin":
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000447 menu_specs[-2] = ("windows", "_Window")
448
449
David Scherer7aced172000-08-15 01:13:23 +0000450 def createmenubar(self):
451 mbar = self.menubar
452 self.menudict = menudict = {}
453 for name, label in self.menu_specs:
454 underline, label = prepstr(label)
455 menudict[name] = menu = Menu(mbar, name=name)
456 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ned Deilyb7601672014-03-27 20:49:14 -0700457 if macosxSupport.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000458 # Insert the application menu
459 menudict['application'] = menu = Menu(mbar, name='apple')
460 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000461 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000462 self.recent_files_menu = Menu(self.menubar)
463 self.menudict['file'].insert_cascade(3, label='Recent Files',
464 underline=0,
465 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000466 self.base_helpmenu_length = self.menudict['help'].index(END)
467 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000468
469 def postwindowsmenu(self):
470 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000471 menu = self.menudict['windows']
472 end = menu.index("end")
473 if end is None:
474 end = -1
475 if end > self.wmenu_end:
476 menu.delete(self.wmenu_end+1, end)
477 WindowList.add_windows_to_menu(menu)
478
479 rmenu = None
480
481 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000482 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
483 if not self.rmenu:
484 self.make_rmenu()
485 rmenu = self.rmenu
486 self.event = event
487 iswin = sys.platform[:3] == 'win'
488 if iswin:
489 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200490
Roger Serwy6b2918a2013-04-07 12:15:52 -0500491 for item in self.rmenu_specs:
492 try:
493 label, eventname, verify_state = item
494 except ValueError: # see issue1207589
495 continue
496
Andrew Svetlovd1837672012-11-01 22:41:19 +0200497 if verify_state is None:
498 continue
499 state = getattr(self, verify_state)()
500 rmenu.entryconfigure(label, state=state)
501
502
David Scherer7aced172000-08-15 01:13:23 +0000503 rmenu.tk_popup(event.x_root, event.y_root)
504 if iswin:
505 self.text.config(cursor="ibeam")
506
507 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200508 # ("Label", "<<virtual-event>>", "statefuncname"), ...
509 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000510 ]
511
512 def make_rmenu(self):
513 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500514 for item in self.rmenu_specs:
515 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200516 if label is not None:
517 def command(text=self.text, eventname=eventname):
518 text.event_generate(eventname)
519 rmenu.add_command(label=label, command=command)
520 else:
521 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000522 self.rmenu = rmenu
523
Andrew Svetlovd1837672012-11-01 22:41:19 +0200524 def rmenu_check_cut(self):
525 return self.rmenu_check_copy()
526
527 def rmenu_check_copy(self):
528 try:
529 indx = self.text.index('sel.first')
530 except TclError:
531 return 'disabled'
532 else:
533 return 'normal' if indx else 'disabled'
534
535 def rmenu_check_paste(self):
536 try:
537 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
538 except TclError:
539 return 'disabled'
540 else:
541 return 'normal'
542
David Scherer7aced172000-08-15 01:13:23 +0000543 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000544 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000545
Steven M. Gava3b55a892001-11-21 05:56:26 +0000546 def config_dialog(self, event=None):
547 configDialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400548 def config_extensions_dialog(self, event=None):
549 configDialog.ConfigExtensionsDialog(self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000550
David Scherer7aced172000-08-15 01:13:23 +0000551 def help_dialog(self, event=None):
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500552 if self.root:
553 parent = self.root
554 else:
555 parent = self.top
556 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000557
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000558 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000559 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000560 try:
561 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200562 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000563 tkMessageBox.showerror(title='Document Start Failure',
564 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000565 else:
566 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000567 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000568
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000569 def cut(self,event):
570 self.text.event_generate("<<Cut>>")
571 return "break"
572
573 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000574 if not self.text.tag_ranges("sel"):
575 # There is no selection, so do nothing and maybe interrupt.
576 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000577 self.text.event_generate("<<Copy>>")
578 return "break"
579
580 def paste(self,event):
581 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000582 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000583 return "break"
584
David Scherer7aced172000-08-15 01:13:23 +0000585 def select_all(self, event=None):
586 self.text.tag_add("sel", "1.0", "end-1c")
587 self.text.mark_set("insert", "1.0")
588 self.text.see("insert")
589 return "break"
590
591 def remove_selection(self, event=None):
592 self.text.tag_remove("sel", "1.0", "end")
593 self.text.see("insert")
594
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000595 def move_at_edge_if_selection(self, edge_index):
596 """Cursor move begins at start or end of selection
597
598 When a left/right cursor key is pressed create and return to Tkinter a
599 function which causes a cursor move from the associated edge of the
600 selection.
601
602 """
603 self_text_index = self.text.index
604 self_text_mark_set = self.text.mark_set
605 edges_table = ("sel.first+1c", "sel.last-1c")
606 def move_at_edge(event):
607 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
608 try:
609 self_text_index("sel.first")
610 self_text_mark_set("insert", edges_table[edge_index])
611 except TclError:
612 pass
613 return move_at_edge
614
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000615 def del_word_left(self, event):
616 self.text.event_generate('<Meta-Delete>')
617 return "break"
618
619 def del_word_right(self, event):
620 self.text.event_generate('<Meta-d>')
621 return "break"
622
Steven M. Gavac5976402002-01-04 03:06:08 +0000623 def find_event(self, event):
624 SearchDialog.find(self.text)
625 return "break"
626
627 def find_again_event(self, event):
628 SearchDialog.find_again(self.text)
629 return "break"
630
631 def find_selection_event(self, event):
632 SearchDialog.find_selection(self.text)
633 return "break"
634
635 def find_in_files_event(self, event):
636 GrepDialog.grep(self.text, self.io, self.flist)
637 return "break"
638
639 def replace_event(self, event):
640 ReplaceDialog.replace(self.text)
641 return "break"
642
643 def goto_line_event(self, event):
644 text = self.text
645 lineno = tkSimpleDialog.askinteger("Goto",
646 "Go to line number:",parent=text)
647 if lineno is None:
648 return "break"
649 if lineno <= 0:
650 text.bell()
651 return "break"
652 text.mark_set("insert", "%d.0" % lineno)
653 text.see("insert")
654
David Scherer7aced172000-08-15 01:13:23 +0000655 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000656 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000657 try:
658 name = self.text.get("sel.first", "sel.last")
659 except TclError:
660 name = ""
661 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000662 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000663 name = tkSimpleDialog.askstring("Module",
664 "Enter the name of a Python module\n"
665 "to search on sys.path and open:",
666 parent=self.text, initialvalue=name)
667 if name:
668 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000669 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000670 return
David Scherer7aced172000-08-15 01:13:23 +0000671 # XXX Ought to insert current file's directory in front of path
672 try:
Eric Snow6029e082014-01-25 15:32:46 -0700673 spec = importlib.util.find_spec(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400674 except (ValueError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000675 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
676 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700677 if spec is None:
Brett Cannon50793b42013-06-07 13:17:48 -0400678 tkMessageBox.showerror("Import error", "module not found",
679 parent=self.text)
David Scherer7aced172000-08-15 01:13:23 +0000680 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700681 if not isinstance(spec.loader, importlib.abc.SourceLoader):
Brett Cannon50793b42013-06-07 13:17:48 -0400682 tkMessageBox.showerror("Import error", "not a source-based module",
683 parent=self.text)
684 return
685 try:
Eric Snow02b9f9d2014-01-06 20:42:59 -0700686 file_path = spec.loader.get_filename(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400687 except AttributeError:
688 tkMessageBox.showerror("Import error",
689 "loader does not support get_filename",
690 parent=self.text)
691 return
David Scherer7aced172000-08-15 01:13:23 +0000692 if self.flist:
Brett Cannon50793b42013-06-07 13:17:48 -0400693 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000694 else:
Brett Cannon50793b42013-06-07 13:17:48 -0400695 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400696 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000697
698 def open_class_browser(self, event=None):
699 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400700 if not (self.__class__.__name__ == 'PyShellEditorWindow'
701 and filename):
702 filename = self.open_module()
703 if filename is None:
704 return
David Scherer7aced172000-08-15 01:13:23 +0000705 head, tail = os.path.split(filename)
706 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000707 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000708 ClassBrowser.ClassBrowser(self.flist, base, [head])
709
710 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000711 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000712 PathBrowser.PathBrowser(self.flist)
713
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400714 def open_turtle_demo(self, event = None):
715 import subprocess
716
717 cmd = [sys.executable,
718 '-c',
719 'from turtledemo.__main__ import main; main()']
720 p = subprocess.Popen(cmd, shell=False)
721
David Scherer7aced172000-08-15 01:13:23 +0000722 def gotoline(self, lineno):
723 if lineno is not None and lineno > 0:
724 self.text.mark_set("insert", "%d.0" % lineno)
725 self.text.tag_remove("sel", "1.0", "end")
726 self.text.tag_add("sel", "insert", "insert +1l")
727 self.center()
728
729 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000730 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000731 return True
David Scherer7aced172000-08-15 01:13:23 +0000732 base, ext = os.path.splitext(os.path.basename(filename))
733 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000734 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000735 line = self.text.get('1.0', '1.0 lineend')
736 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000737
738 def close_hook(self):
739 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000740 self.flist.unregister_maybe_terminate(self)
741 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000742
743 def set_close_hook(self, close_hook):
744 self.close_hook = close_hook
745
746 def filename_change_hook(self):
747 if self.flist:
748 self.flist.filename_changed_edit(self)
749 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000750 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000751 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000752
Christian Heimesa156e092008-02-16 07:38:31 +0000753 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000754 if self.color:
755 return
Christian Heimesa156e092008-02-16 07:38:31 +0000756 if self.ispythonsource(self.io.filename):
757 self.color = self.ColorDelegator()
758 # can add more colorizers here...
759 if self.color:
760 self.per.removefilter(self.undo)
761 self.per.insertfilter(self.color)
762 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000763
Christian Heimesa156e092008-02-16 07:38:31 +0000764 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000765 if not self.color:
766 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000767 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000768 self.per.removefilter(self.color)
769 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000770
Steven M. Gavab77d3432002-03-02 07:16:21 +0000771 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400772 "Update the color theme"
Christian Heimesa156e092008-02-16 07:38:31 +0000773 # Called from self.filename_change_hook and from configDialog.py
774 self._rmcolorizer()
775 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000776 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000777 normal_colors = idleConf.GetHighlight(theme, 'normal')
778 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
779 select_colors = idleConf.GetHighlight(theme, 'hilite')
780 self.text.config(
781 foreground=normal_colors['foreground'],
782 background=normal_colors['background'],
783 insertbackground=cursor_color,
784 selectforeground=select_colors['foreground'],
785 selectbackground=select_colors['background'],
786 )
David Scherer7aced172000-08-15 01:13:23 +0000787
Guido van Rossum33d26892007-08-05 15:29:28 +0000788 IDENTCHARS = string.ascii_letters + string.digits + "_"
789
790 def colorize_syntax_error(self, text, pos):
791 text.tag_add("ERROR", pos)
792 char = text.get(pos)
793 if char and char in self.IDENTCHARS:
794 text.tag_add("ERROR", pos + " wordstart", pos)
795 if '\n' == text.get(pos): # error at line end
796 text.mark_set("insert", pos)
797 else:
798 text.mark_set("insert", pos + "+1c")
799 text.see(pos)
800
Steven M. Gavab1585412002-03-12 00:21:56 +0000801 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000802 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000803 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000804 fontWeight='normal'
805 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
806 fontWeight='bold'
807 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200808 idleConf.GetOption('main','EditorWindow','font-size',
809 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000810 fontWeight))
811
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000812 def RemoveKeybindings(self):
813 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000814 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000815 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000816 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000817 self.text.event_delete(event, *keylist)
818 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000819 xkeydefs = idleConf.GetExtensionBindings(extensionName)
820 if xkeydefs:
821 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000822 self.text.event_delete(event, *keylist)
823
824 def ApplyKeybindings(self):
825 "Update the keybindings after they are changed"
826 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000827 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000828 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000829 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000830 xkeydefs = idleConf.GetExtensionBindings(extensionName)
831 if xkeydefs:
832 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000833 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000834 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000835 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000836 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000837 for item in menu[1]:
838 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000839 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000840 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000841 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700842 end = menu.index(END)
843 if end is None:
844 # Skip empty menus
845 continue
846 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000847 for index in range(0, end):
848 if menu.type(index) == 'command':
849 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000850 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000851 itemName = menu.entrycget(index, 'label')
852 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000853 if menubarItem in menuEventDict:
854 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000855 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000856 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000857 accel = get_accelerator(keydefs, event)
858 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000859
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000860 def set_notabs_indentwidth(self):
861 "Update the indentwidth if changed and not using tabs in this window"
862 # Called from configDialog.py
863 if not self.usetabs:
864 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
865 type='int')
866
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000867 def reset_help_menu_entries(self):
868 "Update the additional help entries on the Help menu"
869 help_list = idleConf.GetAllExtraHelpSourcesList()
870 helpmenu = self.menudict['help']
871 # first delete the extra help entries, if any
872 helpmenu_length = helpmenu.index(END)
873 if helpmenu_length > self.base_helpmenu_length:
874 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
875 # then rebuild them
876 if help_list:
877 helpmenu.add_separator()
878 for entry in help_list:
879 cmd = self.__extra_help_callback(entry[1])
880 helpmenu.add_command(label=entry[0], command=cmd)
881 # and update the menu dictionary
882 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000883
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000884 def __extra_help_callback(self, helpfile):
885 "Create a callback with the helpfile value frozen at definition time"
886 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000887 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000888 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000889 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000890 try:
891 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200892 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000893 tkMessageBox.showerror(title='Document Start Failure',
894 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000895 else:
896 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000897 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000898
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000899 def update_recent_files_list(self, new_file=None):
900 "Load and update the recent files list and menus"
901 rf_list = []
902 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400903 with open(self.recent_files_path, 'r',
904 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000905 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000906 if new_file:
907 new_file = os.path.abspath(new_file) + '\n'
908 if new_file in rf_list:
909 rf_list.remove(new_file) # move to top
910 rf_list.insert(0, new_file)
911 # clean and save the recent files list
912 bad_paths = []
913 for path in rf_list:
914 if '\0' in path or not os.path.exists(path[0:-1]):
915 bad_paths.append(path)
916 rf_list = [path for path in rf_list if path not in bad_paths]
917 ulchars = "1234567890ABCDEFGHIJK"
918 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000919 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800920 with open(self.recent_files_path, 'w',
921 encoding='utf_8', errors='replace') as rf_file:
922 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200923 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800924 if not getattr(self.root, "recentfilelist_error_displayed", False):
925 self.root.recentfilelist_error_displayed = True
926 tkMessageBox.showerror(title='IDLE Error',
927 message='Unable to update Recent Files list:\n%s'
928 % str(err),
929 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000930 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000931 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000932 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700933 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000934 for i, file_name in enumerate(rf_list):
935 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000936 # make unicode string to display non-ASCII chars correctly
937 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000938 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000939 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000940 command=callback,
941 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000942
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000943 def __recent_file_callback(self, file_name):
944 def open_recent_file(fn_closure=file_name):
945 self.io.open(editFile=fn_closure)
946 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000947
David Scherer7aced172000-08-15 01:13:23 +0000948 def saved_change_hook(self):
949 short = self.short_title()
950 long = self.long_title()
951 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400952 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000953 elif short:
954 title = short
955 elif long:
956 title = long
957 else:
958 title = "Untitled"
959 icon = short or long or title
960 if not self.get_saved():
961 title = "*%s*" % title
962 icon = "*%s" % icon
963 self.top.wm_title(title)
964 self.top.wm_iconname(icon)
965
966 def get_saved(self):
967 return self.undo.get_saved()
968
969 def set_saved(self, flag):
970 self.undo.set_saved(flag)
971
972 def reset_undo(self):
973 self.undo.reset_undo()
974
975 def short_title(self):
976 filename = self.io.filename
977 if filename:
978 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500979 else:
980 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000981 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400982 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000983
984 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000985 # return unicode string to display non-ASCII chars correctly
986 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000987
988 def center_insert_event(self, event):
989 self.center()
990
991 def center(self, mark="insert"):
992 text = self.text
993 top, bot = self.getwindowlines()
994 lineno = self.getlineno(mark)
995 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000996 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000997 text.yview(float(newtop))
998
999 def getwindowlines(self):
1000 text = self.text
1001 top = self.getlineno("@0,0")
1002 bot = self.getlineno("@0,65535")
1003 if top == bot and text.winfo_height() == 1:
1004 # Geometry manager hasn't run yet
1005 height = int(text['height'])
1006 bot = top + height - 1
1007 return top, bot
1008
1009 def getlineno(self, mark="insert"):
1010 text = self.text
1011 return int(float(text.index(mark)))
1012
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001013 def get_geometry(self):
1014 "Return (width, height, x, y)"
1015 geom = self.top.wm_geometry()
1016 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +00001017 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001018
David Scherer7aced172000-08-15 01:13:23 +00001019 def close_event(self, event):
1020 self.close()
1021
1022 def maybesave(self):
1023 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001024 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001025 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001026 self.top.deiconify()
1027 self.top.lower()
1028 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001029 return self.io.maybesave()
1030
1031 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001032 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +00001033 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001034 self._close()
1035 return reply
1036
1037 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001038 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001039 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001040 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001041 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001042 self.io.close()
1043 self.io = None
1044 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001045 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001046 self.color.close(False)
1047 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001048 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001049 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001050 self.per.close()
1051 self.per = None
1052 self.top.destroy()
1053 if self.close_hook:
1054 # unless override: unregister from flist, terminate if last window
1055 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001056
1057 def load_extensions(self):
1058 self.extensions = {}
1059 self.load_standard_extensions()
1060
1061 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001062 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001063 if hasattr(ins, "close"):
1064 ins.close()
1065 self.extensions = {}
1066
1067 def load_standard_extensions(self):
1068 for name in self.get_standard_extension_names():
1069 try:
1070 self.load_extension(name)
1071 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001072 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001073 traceback.print_exc()
1074
1075 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001076 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001077
1078 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001079 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001080 try:
1081 mod = importlib.import_module('.' + name, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001082 except (ImportError, TypeError):
Brett Cannonaef82d32012-04-14 20:44:23 -04001083 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001084 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001085 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001086 raise
David Scherer7aced172000-08-15 01:13:23 +00001087 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001088 keydefs = idleConf.GetExtensionBindings(name)
1089 if hasattr(cls, "menudefs"):
1090 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001091 ins = cls(self)
1092 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001093 if keydefs:
1094 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001095 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001096 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001097 while methodname[:1] == '<':
1098 methodname = methodname[1:]
1099 while methodname[-1:] == '>':
1100 methodname = methodname[:-1]
1101 methodname = methodname + "_event"
1102 if hasattr(ins, methodname):
1103 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001104
1105 def apply_bindings(self, keydefs=None):
1106 if keydefs is None:
1107 keydefs = self.Bindings.default_keydefs
1108 text = self.text
1109 text.keydefs = keydefs
1110 for event, keylist in keydefs.items():
1111 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001112 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001113
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001114 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001115 """Add appropriate entries to the menus and submenus
1116
1117 Menus that are absent or None in self.menudict are ignored.
1118 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001119 if menudefs is None:
1120 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001121 if keydefs is None:
1122 keydefs = self.Bindings.default_keydefs
1123 menudict = self.menudict
1124 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001125 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001126 menu = menudict.get(mname)
1127 if not menu:
1128 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001129 for entry in entrylist:
1130 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001131 menu.add_separator()
1132 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001133 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001134 checkbutton = (label[:1] == '!')
1135 if checkbutton:
1136 label = label[1:]
1137 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001138 accelerator = get_accelerator(keydefs, eventname)
1139 def command(text=text, eventname=eventname):
1140 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001141 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001142 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001143 menu.add_checkbutton(label=label, underline=underline,
1144 command=command, accelerator=accelerator,
1145 variable=var)
1146 else:
1147 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001148 command=command,
1149 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001150
1151 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001152 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001153 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001154 value = var.get()
1155 return value
1156 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001157 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001158
1159 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001160 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001161 if var:
1162 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001163 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001164 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001165
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001166 def get_var_obj(self, name, vartype=None):
1167 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001168 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001169 # create a Tkinter variable object with self.text as master:
1170 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001171 return var
1172
1173 # Tk implementations of "virtual text methods" -- each platform
1174 # reusing IDLE's support code needs to define these for its GUI's
1175 # flavor of widget.
1176
1177 # Is character at text_index in a Python string? Return 0 for
1178 # "guaranteed no", true for anything else. This info is expensive
1179 # to compute ab initio, but is probably already known by the
1180 # platform's colorizer.
1181
1182 def is_char_in_string(self, text_index):
1183 if self.color:
1184 # Return true iff colorizer hasn't (re)gotten this far
1185 # yet, or the character is tagged as being in a string
1186 return self.text.tag_prevrange("TODO", text_index) or \
1187 "STRING" in self.text.tag_names(text_index)
1188 else:
1189 # The colorizer is missing: assume the worst
1190 return 1
1191
1192 # If a selection is defined in the text widget, return (start,
1193 # end) as Tkinter text indices, otherwise return (None, None)
1194 def get_selection_indices(self):
1195 try:
1196 first = self.text.index("sel.first")
1197 last = self.text.index("sel.last")
1198 return first, last
1199 except TclError:
1200 return None, None
1201
1202 # Return the text widget's current view of what a tab stop means
1203 # (equivalent width in spaces).
1204
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001205 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001206 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1207 return int(current)
1208
1209 # Set the text widget's current view of what a tab stop means.
1210
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001211 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001212 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001213 if self.get_tk_tabwidth() != newtabwidth:
1214 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001215 pixels = text.tk.call("font", "measure", text["font"],
1216 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001217 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001218 text.configure(tabs=pixels)
1219
Guido van Rossum33d26892007-08-05 15:29:28 +00001220### begin autoindent code ### (configuration was moved to beginning of class)
1221
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001222 def set_indentation_params(self, is_py_src, guess=True):
1223 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001224 i = self.guess_indent()
1225 if 2 <= i <= 8:
1226 self.indentwidth = i
1227 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001228 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001229 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001230
1231 def smart_backspace_event(self, event):
1232 text = self.text
1233 first, last = self.get_selection_indices()
1234 if first and last:
1235 text.delete(first, last)
1236 text.mark_set("insert", first)
1237 return "break"
1238 # Delete whitespace left, until hitting a real char or closest
1239 # preceding virtual tab stop.
1240 chars = text.get("insert linestart", "insert")
1241 if chars == '':
1242 if text.compare("insert", ">", "1.0"):
1243 # easy: delete preceding newline
1244 text.delete("insert-1c")
1245 else:
1246 text.bell() # at start of buffer
1247 return "break"
1248 if chars[-1] not in " \t":
1249 # easy: delete preceding real char
1250 text.delete("insert-1c")
1251 return "break"
1252 # Ick. It may require *inserting* spaces if we back up over a
1253 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001254 tabwidth = self.tabwidth
1255 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001256 assert have > 0
1257 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001258 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001259 if self.context_use_ps1:
1260 last_line_of_prompt = sys.ps1.split('\n')[-1]
1261 else:
1262 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001263 ncharsdeleted = 0
1264 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001265 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001266 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001267 chars = chars[:-1]
1268 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001269 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001270 if have <= want or chars[-1] not in " \t":
1271 break
1272 text.undo_block_start()
1273 text.delete("insert-%dc" % ncharsdeleted, "insert")
1274 if have < want:
1275 text.insert("insert", ' ' * (want - have))
1276 text.undo_block_stop()
1277 return "break"
1278
1279 def smart_indent_event(self, event):
1280 # if intraline selection:
1281 # delete it
1282 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001283 # do indent-region
1284 # else:
1285 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001286 text = self.text
1287 first, last = self.get_selection_indices()
1288 text.undo_block_start()
1289 try:
1290 if first and last:
1291 if index2line(first) != index2line(last):
1292 return self.indent_region_event(event)
1293 text.delete(first, last)
1294 text.mark_set("insert", first)
1295 prefix = text.get("insert linestart", "insert")
1296 raw, effective = classifyws(prefix, self.tabwidth)
1297 if raw == len(prefix):
1298 # only whitespace to the left
1299 self.reindent_to(effective + self.indentwidth)
1300 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001301 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001302 if self.usetabs:
1303 pad = '\t'
1304 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001305 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001306 n = self.indentwidth
1307 pad = ' ' * (n - effective % n)
1308 text.insert("insert", pad)
1309 text.see("insert")
1310 return "break"
1311 finally:
1312 text.undo_block_stop()
1313
1314 def newline_and_indent_event(self, event):
1315 text = self.text
1316 first, last = self.get_selection_indices()
1317 text.undo_block_start()
1318 try:
1319 if first and last:
1320 text.delete(first, last)
1321 text.mark_set("insert", first)
1322 line = text.get("insert linestart", "insert")
1323 i, n = 0, len(line)
1324 while i < n and line[i] in " \t":
1325 i = i+1
1326 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001327 # the cursor is in or at leading indentation in a continuation
1328 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001329 text.insert("insert linestart", '\n')
1330 return "break"
1331 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001332 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001333 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001334 last_line_of_prompt = sys.ps1.split('\n')[-1]
1335 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001336 line = line[:-1]
1337 i = i+1
1338 if i:
1339 text.delete("insert - %d chars" % i, "insert")
1340 # strip whitespace after insert point
1341 while text.get("insert") in " \t":
1342 text.delete("insert")
1343 # start new line
1344 text.insert("insert", '\n')
1345
1346 # adjust indentation for continuations and block
1347 # open/close first need to find the last stmt
1348 lno = index2line(text.index('insert'))
1349 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001350 if not self.context_use_ps1:
1351 for context in self.num_context_lines:
1352 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001353 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001354 rawtext = text.get(startatindex, "insert")
1355 y.set_str(rawtext)
1356 bod = y.find_good_parse_start(
1357 self.context_use_ps1,
1358 self._build_char_in_string_func(startatindex))
1359 if bod is not None or startat == 1:
1360 break
1361 y.set_lo(bod or 0)
1362 else:
1363 r = text.tag_prevrange("console", "insert")
1364 if r:
1365 startatindex = r[1]
1366 else:
1367 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001368 rawtext = text.get(startatindex, "insert")
1369 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001370 y.set_lo(0)
1371
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001372 c = y.get_continuation_type()
1373 if c != PyParse.C_NONE:
1374 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001375 if c == PyParse.C_STRING_FIRST_LINE:
1376 # after the first line of a string; do not indent at all
1377 pass
1378 elif c == PyParse.C_STRING_NEXT_LINES:
1379 # inside a string which started before this line;
1380 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001381 text.insert("insert", indent)
1382 elif c == PyParse.C_BRACKET:
1383 # line up with the first (if any) element of the
1384 # last open bracket structure; else indent one
1385 # level beyond the indent of the line with the
1386 # last open bracket
1387 self.reindent_to(y.compute_bracket_indent())
1388 elif c == PyParse.C_BACKSLASH:
1389 # if more than one line in this stmt already, just
1390 # mimic the current indent; else if initial line
1391 # has a start on an assignment stmt, indent to
1392 # beyond leftmost =; else to beyond first chunk of
1393 # non-whitespace on initial line
1394 if y.get_num_lines_in_stmt() > 1:
1395 text.insert("insert", indent)
1396 else:
1397 self.reindent_to(y.compute_backslash_indent())
1398 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001399 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001400 return "break"
1401
1402 # This line starts a brand new stmt; indent relative to
1403 # indentation of initial line of closest preceding
1404 # interesting stmt.
1405 indent = y.get_base_indent_string()
1406 text.insert("insert", indent)
1407 if y.is_block_opener():
1408 self.smart_indent_event(event)
1409 elif indent and y.is_block_closer():
1410 self.smart_backspace_event(event)
1411 return "break"
1412 finally:
1413 text.see("insert")
1414 text.undo_block_stop()
1415
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001416 # Our editwin provides a is_char_in_string function that works
1417 # with a Tk text index, but PyParse only knows about offsets into
1418 # a string. This builds a function for PyParse that accepts an
1419 # offset.
1420
1421 def _build_char_in_string_func(self, startindex):
1422 def inner(offset, _startindex=startindex,
1423 _icis=self.is_char_in_string):
1424 return _icis(_startindex + "+%dc" % offset)
1425 return inner
1426
1427 def indent_region_event(self, event):
1428 head, tail, chars, lines = self.get_region()
1429 for pos in range(len(lines)):
1430 line = lines[pos]
1431 if line:
1432 raw, effective = classifyws(line, self.tabwidth)
1433 effective = effective + self.indentwidth
1434 lines[pos] = self._make_blanks(effective) + line[raw:]
1435 self.set_region(head, tail, chars, lines)
1436 return "break"
1437
1438 def dedent_region_event(self, event):
1439 head, tail, chars, lines = self.get_region()
1440 for pos in range(len(lines)):
1441 line = lines[pos]
1442 if line:
1443 raw, effective = classifyws(line, self.tabwidth)
1444 effective = max(effective - self.indentwidth, 0)
1445 lines[pos] = self._make_blanks(effective) + line[raw:]
1446 self.set_region(head, tail, chars, lines)
1447 return "break"
1448
1449 def comment_region_event(self, event):
1450 head, tail, chars, lines = self.get_region()
1451 for pos in range(len(lines) - 1):
1452 line = lines[pos]
1453 lines[pos] = '##' + line
1454 self.set_region(head, tail, chars, lines)
1455
1456 def uncomment_region_event(self, event):
1457 head, tail, chars, lines = self.get_region()
1458 for pos in range(len(lines)):
1459 line = lines[pos]
1460 if not line:
1461 continue
1462 if line[:2] == '##':
1463 line = line[2:]
1464 elif line[:1] == '#':
1465 line = line[1:]
1466 lines[pos] = line
1467 self.set_region(head, tail, chars, lines)
1468
1469 def tabify_region_event(self, event):
1470 head, tail, chars, lines = self.get_region()
1471 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001472 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001473 for pos in range(len(lines)):
1474 line = lines[pos]
1475 if line:
1476 raw, effective = classifyws(line, tabwidth)
1477 ntabs, nspaces = divmod(effective, tabwidth)
1478 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1479 self.set_region(head, tail, chars, lines)
1480
1481 def untabify_region_event(self, event):
1482 head, tail, chars, lines = self.get_region()
1483 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001484 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001485 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001486 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001487 self.set_region(head, tail, chars, lines)
1488
1489 def toggle_tabs_event(self, event):
1490 if self.askyesno(
1491 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001492 "Turn tabs " + ("on", "off")[self.usetabs] +
1493 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001494 ("will be", "remains at")[self.usetabs] + " 8." +
1495 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001496 parent=self.text):
1497 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001498 # Try to prevent inconsistent indentation.
1499 # User must change indent width manually after using tabs.
1500 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001501 return "break"
1502
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001503 # XXX this isn't bound to anything -- see tabwidth comments
1504## def change_tabwidth_event(self, event):
1505## new = self._asktabwidth()
1506## if new != self.tabwidth:
1507## self.tabwidth = new
1508## self.set_indentation_params(0, guess=0)
1509## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001510
1511 def change_indentwidth_event(self, event):
1512 new = self.askinteger(
1513 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001514 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001515 parent=self.text,
1516 initialvalue=self.indentwidth,
1517 minvalue=2,
1518 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001519 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001520 self.indentwidth = new
1521 return "break"
1522
1523 def get_region(self):
1524 text = self.text
1525 first, last = self.get_selection_indices()
1526 if first and last:
1527 head = text.index(first + " linestart")
1528 tail = text.index(last + "-1c lineend +1c")
1529 else:
1530 head = text.index("insert linestart")
1531 tail = text.index("insert lineend +1c")
1532 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001533 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001534 return head, tail, chars, lines
1535
1536 def set_region(self, head, tail, chars, lines):
1537 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001538 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001539 if newchars == chars:
1540 text.bell()
1541 return
1542 text.tag_remove("sel", "1.0", "end")
1543 text.mark_set("insert", head)
1544 text.undo_block_start()
1545 text.delete(head, tail)
1546 text.insert(head, newchars)
1547 text.undo_block_stop()
1548 text.tag_add("sel", head, "insert")
1549
1550 # Make string that displays as n leading blanks.
1551
1552 def _make_blanks(self, n):
1553 if self.usetabs:
1554 ntabs, nspaces = divmod(n, self.tabwidth)
1555 return '\t' * ntabs + ' ' * nspaces
1556 else:
1557 return ' ' * n
1558
1559 # Delete from beginning of line to insert point, then reinsert
1560 # column logical (meaning use tabs if appropriate) spaces.
1561
1562 def reindent_to(self, column):
1563 text = self.text
1564 text.undo_block_start()
1565 if text.compare("insert linestart", "!=", "insert"):
1566 text.delete("insert linestart", "insert")
1567 if column:
1568 text.insert("insert", self._make_blanks(column))
1569 text.undo_block_stop()
1570
1571 def _asktabwidth(self):
1572 return self.askinteger(
1573 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001574 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001575 parent=self.text,
1576 initialvalue=self.indentwidth,
1577 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001578 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001579
1580 # Guess indentwidth from text content.
1581 # Return guessed indentwidth. This should not be believed unless
1582 # it's in a reasonable range (e.g., it will be 0 if no indented
1583 # blocks are found).
1584
1585 def guess_indent(self):
1586 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1587 if opener and indented:
1588 raw, indentsmall = classifyws(opener, self.tabwidth)
1589 raw, indentlarge = classifyws(indented, self.tabwidth)
1590 else:
1591 indentsmall = indentlarge = 0
1592 return indentlarge - indentsmall
1593
1594# "line.col" -> line, as an int
1595def index2line(index):
1596 return int(float(index))
1597
1598# Look at the leading whitespace in s.
1599# Return pair (# of leading ws characters,
1600# effective # of leading blanks after expanding
1601# tabs to width tabwidth)
1602
1603def classifyws(s, tabwidth):
1604 raw = effective = 0
1605 for ch in s:
1606 if ch == ' ':
1607 raw = raw + 1
1608 effective = effective + 1
1609 elif ch == '\t':
1610 raw = raw + 1
1611 effective = (effective // tabwidth + 1) * tabwidth
1612 else:
1613 break
1614 return raw, effective
1615
1616import tokenize
1617_tokenize = tokenize
1618del tokenize
1619
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001620class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001621
1622 # .run() chews over the Text widget, looking for a block opener
1623 # and the stmt following it. Returns a pair,
1624 # (line containing block opener, line containing stmt)
1625 # Either or both may be None.
1626
1627 def __init__(self, text, tabwidth):
1628 self.text = text
1629 self.tabwidth = tabwidth
1630 self.i = self.finished = 0
1631 self.blkopenline = self.indentedline = None
1632
1633 def readline(self):
1634 if self.finished:
1635 return ""
1636 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001637 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001638 if self.text.compare(mark, ">=", "end"):
1639 return ""
1640 return self.text.get(mark, mark + " lineend+1c")
1641
1642 def tokeneater(self, type, token, start, end, line,
1643 INDENT=_tokenize.INDENT,
1644 NAME=_tokenize.NAME,
1645 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1646 if self.finished:
1647 pass
1648 elif type == NAME and token in OPENERS:
1649 self.blkopenline = line
1650 elif type == INDENT and self.blkopenline:
1651 self.indentedline = line
1652 self.finished = 1
1653
1654 def run(self):
1655 save_tabsize = _tokenize.tabsize
1656 _tokenize.tabsize = self.tabwidth
1657 try:
1658 try:
Trent Nelson428de652008-03-18 22:41:35 +00001659 tokens = _tokenize.generate_tokens(self.readline)
1660 for token in tokens:
1661 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001662 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001663 # since we cut off the tokenizer early, we can trigger
1664 # spurious errors
1665 pass
1666 finally:
1667 _tokenize.tabsize = save_tabsize
1668 return self.blkopenline, self.indentedline
1669
1670### end autoindent code ###
1671
David Scherer7aced172000-08-15 01:13:23 +00001672def prepstr(s):
1673 # Helper to extract the underscore from a string, e.g.
1674 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001675 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001676 if i >= 0:
1677 s = s[:i] + s[i+1:]
1678 return i, s
1679
1680
1681keynames = {
1682 'bracketleft': '[',
1683 'bracketright': ']',
1684 'slash': '/',
1685}
1686
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001687def get_accelerator(keydefs, eventname):
1688 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001689 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1690 # if not keylist:
Ned Deilyb7601672014-03-27 20:49:14 -07001691 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001692 "<<open-module>>",
1693 "<<goto-line>>",
1694 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001695 return ""
1696 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001697 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001698 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1699 s = re.sub("Key-", "", s)
1700 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1701 s = re.sub("Control-", "Ctrl-", s)
1702 s = re.sub("-", "+", s)
1703 s = re.sub("><", " ", s)
1704 s = re.sub("<", "", s)
1705 s = re.sub(">", "", s)
1706 return s
1707
1708
1709def fixwordbreaks(root):
1710 # Make sure that Tk's double-click and next/previous word
1711 # operations use our definition of a word (i.e. an identifier)
1712 tk = root.tk
1713 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1714 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1715 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1716
1717
Terry Jan Reedycd567362014-10-17 01:31:35 -04001718def _editor_window(parent): # htest #
1719 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001720 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001721 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001722 if sys.argv[1:]:
1723 filename = sys.argv[1]
1724 else:
1725 filename = None
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001726 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001727 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001728 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001729 # Does not stop error, neither does following
1730 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001731
1732if __name__ == '__main__':
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001733 from idlelib.idle_test.htest import run
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001734 run(_help_dialog, _editor_window)