blob: ef35ffe466eef34a49debebb340fa48d1bba5243 [file] [log] [blame]
Brett Cannonaef82d32012-04-14 20:44:23 -04001import importlib
Brett Cannon50793b42013-06-07 13:17:48 -04002import importlib.abc
Eric Snow6029e082014-01-25 15:32:46 -07003import importlib.util
David Scherer7aced172000-08-15 01:13:23 +00004import os
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -04005import platform
David Scherer7aced172000-08-15 01:13:23 +00006import re
Guido van Rossum33d26892007-08-05 15:29:28 +00007import string
Brett Cannonaef82d32012-04-14 20:44:23 -04008import sys
Georg Brandl14fc4272008-05-17 18:39:55 +00009from tkinter import *
10import tkinter.simpledialog as tkSimpleDialog
11import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +000012import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000013import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000014
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000015from idlelib.MultiCall import MultiCallCreator
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000016from idlelib import WindowList
17from idlelib import SearchDialog
18from idlelib import GrepDialog
19from idlelib import ReplaceDialog
20from idlelib import PyParse
21from idlelib.configHandler import idleConf
22from idlelib import aboutDialog, textView, configDialog
23from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000024
25# The default tab setting for a Text widget, in average-width characters.
26TK_TABWIDTH_DEFAULT = 8
27
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -040028_py_version = ' (%s)' % platform.python_version()
29
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000030def _sphinx_version():
31 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
32 major, minor, micro, level, serial = sys.version_info
33 release = '%s%s' % (major, minor)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020034 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000035 if level == 'candidate':
36 release += 'rc%s' % (serial,)
37 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000038 release += '%s%s' % (level[0], serial)
39 return release
40
Terry Jan Reedye91e7632012-02-05 15:14:20 -050041
42class HelpDialog(object):
43
44 def __init__(self):
45 self.parent = None # parent of help window
46 self.dlg = None # the help window iteself
47
48 def display(self, parent, near=None):
49 """ Display the help dialog.
50
51 parent - parent widget for the help window
52
53 near - a Toplevel widget (e.g. EditorWindow or PyShell)
54 to use as a reference for placing the help window
55 """
56 if self.dlg is None:
57 self.show_dialog(parent)
58 if near:
59 self.nearwindow(near)
60
61 def show_dialog(self, parent):
62 self.parent = parent
63 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
64 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
65 dlg.bind('<Destroy>', self.destroy, '+')
66
67 def nearwindow(self, near):
68 # Place the help dialog near the window specified by parent.
69 # Note - this may not reposition the window in Metacity
70 # if "/apps/metacity/general/disable_workarounds" is enabled
71 dlg = self.dlg
72 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
73 dlg.withdraw()
74 dlg.geometry("=+%d+%d" % geom)
75 dlg.deiconify()
76 dlg.lift()
77
78 def destroy(self, ev=None):
79 self.dlg = None
80 self.parent = None
81
82helpDialog = HelpDialog() # singleton instance
Terry Jan Reedyab4fd442014-05-19 00:12:10 -040083def _help_dialog(parent): # wrapper for htest
Terry Jan Reedy06313b72014-05-11 23:32:32 -040084 helpDialog.show_dialog(parent)
Terry Jan Reedye91e7632012-02-05 15:14:20 -050085
86
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000087class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000088 from idlelib.Percolator import Percolator
89 from idlelib.ColorDelegator import ColorDelegator
90 from idlelib.UndoDelegator import UndoDelegator
91 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
92 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000093 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000094 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000095
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000096 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000097
98 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000099 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100100 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000101 if sys.platform.count('linux'):
102 # look for html docs in a couple of standard places
103 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
104 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
105 dochome = '/var/www/html/python/index.html'
106 else:
107 basepath = '/usr/share/doc/' # standard location
108 dochome = os.path.join(basepath, pyver,
109 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000110 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100111 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +0000112 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000113 if os.path.isfile(chmfile):
114 dochome = chmfile
Ned Deilyb7601672014-03-27 20:49:14 -0700115 elif sys.platform == 'darwin':
116 # documentation may be stored inside a python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100117 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000118 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000119 dochome = os.path.normpath(dochome)
120 if os.path.isfile(dochome):
121 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000122 if sys.platform == 'darwin':
123 # Safari requires real file:-URLs
124 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000125 else:
Terry Jan Reedyb6e17782014-09-19 22:54:15 -0400126 EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
David Scherer7aced172000-08-15 01:13:23 +0000127 self.flist = flist
128 root = root or flist.root
129 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000130 try:
131 sys.ps1
132 except AttributeError:
133 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000134 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000135 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000136 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000137 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200138 #self.top.instance_dict makes flist.inversedict available to
139 #configDialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000140 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000141 else:
142 self.tkinter_vars = {} # keys: Tkinter event names
143 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000144 self.top.instance_dict = {}
145 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000146 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000147 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000148 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200149 self.width = idleConf.GetOption('main', 'EditorWindow',
150 'width', type='int')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000151 text_options = {
152 'name': 'text',
153 'padx': 5,
154 'wrap': 'none',
155 'width': self.width,
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200156 'height': idleConf.GetOption('main', 'EditorWindow',
157 'height', type='int')}
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000158 if TkVersion >= 8.5:
159 # Starting with tk 8.5 we have to set the new tabstyle option
160 # to 'wordprocessor' to achieve the same display of tabs as in
161 # older tk versions.
162 text_options['tabstyle'] = 'wordprocessor'
163 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000164 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000165
166 self.createmenubar()
167 self.apply_bindings()
168
169 self.top.protocol("WM_DELETE_WINDOW", self.close)
170 self.top.bind("<<close-window>>", self.close_event)
Ned Deilyb7601672014-03-27 20:49:14 -0700171 if macosxSupport.isAquaTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000172 # Command-W on editorwindows doesn't work without this.
173 text.bind('<<close-window>>', self.close_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000174 # Some OS X systems have only one mouse button,
175 # so use control-click for pulldown menus there.
176 # (Note, AquaTk defines <2> as the right button if
177 # present and the Tk Text widget already binds <2>.)
178 text.bind("<Control-Button-1>",self.right_menu_event)
179 else:
180 # Elsewhere, use right-click for pulldown menus.
181 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000182 text.bind("<<cut>>", self.cut)
183 text.bind("<<copy>>", self.copy)
184 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000185 text.bind("<<center-insert>>", self.center_insert_event)
186 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000187 text.bind("<<python-docs>>", self.python_docs)
188 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000189 text.bind("<<open-config-dialog>>", self.config_dialog)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400190 text.bind("<<open-config-extensions-dialog>>",
191 self.config_extensions_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"),
Ned Deilyccb416f2015-01-17 21:06:27 -0800440 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000441 ("help", "_Help"),
442 ]
443
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000444
David Scherer7aced172000-08-15 01:13:23 +0000445 def createmenubar(self):
446 mbar = self.menubar
447 self.menudict = menudict = {}
448 for name, label in self.menu_specs:
449 underline, label = prepstr(label)
450 menudict[name] = menu = Menu(mbar, name=name)
451 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ned Deilyb7601672014-03-27 20:49:14 -0700452 if macosxSupport.isCarbonTk():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000453 # Insert the application menu
454 menudict['application'] = menu = Menu(mbar, name='apple')
455 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000456 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000457 self.recent_files_menu = Menu(self.menubar)
458 self.menudict['file'].insert_cascade(3, label='Recent Files',
459 underline=0,
460 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000461 self.base_helpmenu_length = self.menudict['help'].index(END)
462 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000463
464 def postwindowsmenu(self):
465 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000466 menu = self.menudict['windows']
467 end = menu.index("end")
468 if end is None:
469 end = -1
470 if end > self.wmenu_end:
471 menu.delete(self.wmenu_end+1, end)
472 WindowList.add_windows_to_menu(menu)
473
474 rmenu = None
475
476 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000477 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
478 if not self.rmenu:
479 self.make_rmenu()
480 rmenu = self.rmenu
481 self.event = event
482 iswin = sys.platform[:3] == 'win'
483 if iswin:
484 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200485
Roger Serwy6b2918a2013-04-07 12:15:52 -0500486 for item in self.rmenu_specs:
487 try:
488 label, eventname, verify_state = item
489 except ValueError: # see issue1207589
490 continue
491
Andrew Svetlovd1837672012-11-01 22:41:19 +0200492 if verify_state is None:
493 continue
494 state = getattr(self, verify_state)()
495 rmenu.entryconfigure(label, state=state)
496
497
David Scherer7aced172000-08-15 01:13:23 +0000498 rmenu.tk_popup(event.x_root, event.y_root)
499 if iswin:
500 self.text.config(cursor="ibeam")
501
502 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200503 # ("Label", "<<virtual-event>>", "statefuncname"), ...
504 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000505 ]
506
507 def make_rmenu(self):
508 rmenu = Menu(self.text, tearoff=0)
Roger Serwy6b2918a2013-04-07 12:15:52 -0500509 for item in self.rmenu_specs:
510 label, eventname = item[0], item[1]
Andrew Svetlovd1837672012-11-01 22:41:19 +0200511 if label is not None:
512 def command(text=self.text, eventname=eventname):
513 text.event_generate(eventname)
514 rmenu.add_command(label=label, command=command)
515 else:
516 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000517 self.rmenu = rmenu
518
Andrew Svetlovd1837672012-11-01 22:41:19 +0200519 def rmenu_check_cut(self):
520 return self.rmenu_check_copy()
521
522 def rmenu_check_copy(self):
523 try:
524 indx = self.text.index('sel.first')
525 except TclError:
526 return 'disabled'
527 else:
528 return 'normal' if indx else 'disabled'
529
530 def rmenu_check_paste(self):
531 try:
532 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
533 except TclError:
534 return 'disabled'
535 else:
536 return 'normal'
537
David Scherer7aced172000-08-15 01:13:23 +0000538 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000539 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000540
Steven M. Gava3b55a892001-11-21 05:56:26 +0000541 def config_dialog(self, event=None):
542 configDialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400543 def config_extensions_dialog(self, event=None):
544 configDialog.ConfigExtensionsDialog(self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000545
David Scherer7aced172000-08-15 01:13:23 +0000546 def help_dialog(self, event=None):
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500547 if self.root:
548 parent = self.root
549 else:
550 parent = self.top
551 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000552
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000553 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000554 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000555 try:
556 os.startfile(self.help_url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200557 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000558 tkMessageBox.showerror(title='Document Start Failure',
559 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000560 else:
561 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000562 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000563
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000564 def cut(self,event):
565 self.text.event_generate("<<Cut>>")
566 return "break"
567
568 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000569 if not self.text.tag_ranges("sel"):
570 # There is no selection, so do nothing and maybe interrupt.
571 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000572 self.text.event_generate("<<Copy>>")
573 return "break"
574
575 def paste(self,event):
576 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000577 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000578 return "break"
579
David Scherer7aced172000-08-15 01:13:23 +0000580 def select_all(self, event=None):
581 self.text.tag_add("sel", "1.0", "end-1c")
582 self.text.mark_set("insert", "1.0")
583 self.text.see("insert")
584 return "break"
585
586 def remove_selection(self, event=None):
587 self.text.tag_remove("sel", "1.0", "end")
588 self.text.see("insert")
589
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000590 def move_at_edge_if_selection(self, edge_index):
591 """Cursor move begins at start or end of selection
592
593 When a left/right cursor key is pressed create and return to Tkinter a
594 function which causes a cursor move from the associated edge of the
595 selection.
596
597 """
598 self_text_index = self.text.index
599 self_text_mark_set = self.text.mark_set
600 edges_table = ("sel.first+1c", "sel.last-1c")
601 def move_at_edge(event):
602 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
603 try:
604 self_text_index("sel.first")
605 self_text_mark_set("insert", edges_table[edge_index])
606 except TclError:
607 pass
608 return move_at_edge
609
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000610 def del_word_left(self, event):
611 self.text.event_generate('<Meta-Delete>')
612 return "break"
613
614 def del_word_right(self, event):
615 self.text.event_generate('<Meta-d>')
616 return "break"
617
Steven M. Gavac5976402002-01-04 03:06:08 +0000618 def find_event(self, event):
619 SearchDialog.find(self.text)
620 return "break"
621
622 def find_again_event(self, event):
623 SearchDialog.find_again(self.text)
624 return "break"
625
626 def find_selection_event(self, event):
627 SearchDialog.find_selection(self.text)
628 return "break"
629
630 def find_in_files_event(self, event):
631 GrepDialog.grep(self.text, self.io, self.flist)
632 return "break"
633
634 def replace_event(self, event):
635 ReplaceDialog.replace(self.text)
636 return "break"
637
638 def goto_line_event(self, event):
639 text = self.text
640 lineno = tkSimpleDialog.askinteger("Goto",
641 "Go to line number:",parent=text)
642 if lineno is None:
643 return "break"
644 if lineno <= 0:
645 text.bell()
646 return "break"
647 text.mark_set("insert", "%d.0" % lineno)
648 text.see("insert")
649
David Scherer7aced172000-08-15 01:13:23 +0000650 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000651 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000652 try:
653 name = self.text.get("sel.first", "sel.last")
654 except TclError:
655 name = ""
656 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000657 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000658 name = tkSimpleDialog.askstring("Module",
659 "Enter the name of a Python module\n"
660 "to search on sys.path and open:",
661 parent=self.text, initialvalue=name)
662 if name:
663 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000664 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000665 return
David Scherer7aced172000-08-15 01:13:23 +0000666 # XXX Ought to insert current file's directory in front of path
667 try:
Eric Snow6029e082014-01-25 15:32:46 -0700668 spec = importlib.util.find_spec(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400669 except (ValueError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000670 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
671 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700672 if spec is None:
Brett Cannon50793b42013-06-07 13:17:48 -0400673 tkMessageBox.showerror("Import error", "module not found",
674 parent=self.text)
David Scherer7aced172000-08-15 01:13:23 +0000675 return
Eric Snow02b9f9d2014-01-06 20:42:59 -0700676 if not isinstance(spec.loader, importlib.abc.SourceLoader):
Brett Cannon50793b42013-06-07 13:17:48 -0400677 tkMessageBox.showerror("Import error", "not a source-based module",
678 parent=self.text)
679 return
680 try:
Eric Snow02b9f9d2014-01-06 20:42:59 -0700681 file_path = spec.loader.get_filename(name)
Brett Cannon50793b42013-06-07 13:17:48 -0400682 except AttributeError:
683 tkMessageBox.showerror("Import error",
684 "loader does not support get_filename",
685 parent=self.text)
686 return
David Scherer7aced172000-08-15 01:13:23 +0000687 if self.flist:
Brett Cannon50793b42013-06-07 13:17:48 -0400688 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000689 else:
Brett Cannon50793b42013-06-07 13:17:48 -0400690 self.io.loadfile(file_path)
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400691 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000692
693 def open_class_browser(self, event=None):
694 filename = self.io.filename
Terry Jan Reedy380ec632014-10-15 22:01:31 -0400695 if not (self.__class__.__name__ == 'PyShellEditorWindow'
696 and filename):
697 filename = self.open_module()
698 if filename is None:
699 return
David Scherer7aced172000-08-15 01:13:23 +0000700 head, tail = os.path.split(filename)
701 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000702 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000703 ClassBrowser.ClassBrowser(self.flist, base, [head])
704
705 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000706 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000707 PathBrowser.PathBrowser(self.flist)
708
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400709 def open_turtle_demo(self, event = None):
710 import subprocess
711
712 cmd = [sys.executable,
713 '-c',
714 'from turtledemo.__main__ import main; main()']
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400715 subprocess.Popen(cmd, shell=False)
Terry Jan Reedy7e55db22014-07-28 22:23:59 -0400716
David Scherer7aced172000-08-15 01:13:23 +0000717 def gotoline(self, lineno):
718 if lineno is not None and lineno > 0:
719 self.text.mark_set("insert", "%d.0" % lineno)
720 self.text.tag_remove("sel", "1.0", "end")
721 self.text.tag_add("sel", "insert", "insert +1l")
722 self.center()
723
724 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000725 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000726 return True
David Scherer7aced172000-08-15 01:13:23 +0000727 base, ext = os.path.splitext(os.path.basename(filename))
728 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000729 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000730 line = self.text.get('1.0', '1.0 lineend')
731 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000732
733 def close_hook(self):
734 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000735 self.flist.unregister_maybe_terminate(self)
736 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000737
738 def set_close_hook(self, close_hook):
739 self.close_hook = close_hook
740
741 def filename_change_hook(self):
742 if self.flist:
743 self.flist.filename_changed_edit(self)
744 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000745 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000746 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000747
Christian Heimesa156e092008-02-16 07:38:31 +0000748 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000749 if self.color:
750 return
Christian Heimesa156e092008-02-16 07:38:31 +0000751 if self.ispythonsource(self.io.filename):
752 self.color = self.ColorDelegator()
753 # can add more colorizers here...
754 if self.color:
755 self.per.removefilter(self.undo)
756 self.per.insertfilter(self.color)
757 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000758
Christian Heimesa156e092008-02-16 07:38:31 +0000759 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000760 if not self.color:
761 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000762 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000763 self.per.removefilter(self.color)
764 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000765
Steven M. Gavab77d3432002-03-02 07:16:21 +0000766 def ResetColorizer(self):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400767 "Update the color theme"
Christian Heimesa156e092008-02-16 07:38:31 +0000768 # Called from self.filename_change_hook and from configDialog.py
769 self._rmcolorizer()
770 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000771 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000772 normal_colors = idleConf.GetHighlight(theme, 'normal')
773 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
774 select_colors = idleConf.GetHighlight(theme, 'hilite')
775 self.text.config(
776 foreground=normal_colors['foreground'],
777 background=normal_colors['background'],
778 insertbackground=cursor_color,
779 selectforeground=select_colors['foreground'],
780 selectbackground=select_colors['background'],
781 )
David Scherer7aced172000-08-15 01:13:23 +0000782
Guido van Rossum33d26892007-08-05 15:29:28 +0000783 IDENTCHARS = string.ascii_letters + string.digits + "_"
784
785 def colorize_syntax_error(self, text, pos):
786 text.tag_add("ERROR", pos)
787 char = text.get(pos)
788 if char and char in self.IDENTCHARS:
789 text.tag_add("ERROR", pos + " wordstart", pos)
790 if '\n' == text.get(pos): # error at line end
791 text.mark_set("insert", pos)
792 else:
793 text.mark_set("insert", pos + "+1c")
794 text.see(pos)
795
Steven M. Gavab1585412002-03-12 00:21:56 +0000796 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000797 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000798 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000799 fontWeight='normal'
800 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
801 fontWeight='bold'
802 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200803 idleConf.GetOption('main','EditorWindow','font-size',
804 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000805 fontWeight))
806
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000807 def RemoveKeybindings(self):
808 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000809 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000810 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000811 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000812 self.text.event_delete(event, *keylist)
813 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000814 xkeydefs = idleConf.GetExtensionBindings(extensionName)
815 if xkeydefs:
816 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000817 self.text.event_delete(event, *keylist)
818
819 def ApplyKeybindings(self):
820 "Update the keybindings after they are changed"
821 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000822 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000823 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000824 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000825 xkeydefs = idleConf.GetExtensionBindings(extensionName)
826 if xkeydefs:
827 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000828 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000829 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000830 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000831 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000832 for item in menu[1]:
833 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000834 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000835 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000836 menu = self.menudict[menubarItem]
Ned Deily8e8b9ba2013-07-20 15:06:26 -0700837 end = menu.index(END)
838 if end is None:
839 # Skip empty menus
840 continue
841 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000842 for index in range(0, end):
843 if menu.type(index) == 'command':
844 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000845 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000846 itemName = menu.entrycget(index, 'label')
847 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000848 if menubarItem in menuEventDict:
849 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000850 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000851 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000852 accel = get_accelerator(keydefs, event)
853 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000854
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000855 def set_notabs_indentwidth(self):
856 "Update the indentwidth if changed and not using tabs in this window"
857 # Called from configDialog.py
858 if not self.usetabs:
859 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
860 type='int')
861
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000862 def reset_help_menu_entries(self):
863 "Update the additional help entries on the Help menu"
864 help_list = idleConf.GetAllExtraHelpSourcesList()
865 helpmenu = self.menudict['help']
866 # first delete the extra help entries, if any
867 helpmenu_length = helpmenu.index(END)
868 if helpmenu_length > self.base_helpmenu_length:
869 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
870 # then rebuild them
871 if help_list:
872 helpmenu.add_separator()
873 for entry in help_list:
874 cmd = self.__extra_help_callback(entry[1])
875 helpmenu.add_command(label=entry[0], command=cmd)
876 # and update the menu dictionary
877 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000878
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000879 def __extra_help_callback(self, helpfile):
880 "Create a callback with the helpfile value frozen at definition time"
881 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000882 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000883 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000884 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000885 try:
886 os.startfile(helpfile)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200887 except OSError as why:
Terry Reedy6739cc02011-01-01 02:25:36 +0000888 tkMessageBox.showerror(title='Document Start Failure',
889 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000890 else:
891 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000892 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000893
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000894 def update_recent_files_list(self, new_file=None):
895 "Load and update the recent files list and menus"
896 rf_list = []
897 if os.path.exists(self.recent_files_path):
Terry Jan Reedy95f34ab2013-08-04 15:39:03 -0400898 with open(self.recent_files_path, 'r',
899 encoding='utf_8', errors='replace') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000900 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000901 if new_file:
902 new_file = os.path.abspath(new_file) + '\n'
903 if new_file in rf_list:
904 rf_list.remove(new_file) # move to top
905 rf_list.insert(0, new_file)
906 # clean and save the recent files list
907 bad_paths = []
908 for path in rf_list:
909 if '\0' in path or not os.path.exists(path[0:-1]):
910 bad_paths.append(path)
911 rf_list = [path for path in rf_list if path not in bad_paths]
912 ulchars = "1234567890ABCDEFGHIJK"
913 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000914 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800915 with open(self.recent_files_path, 'w',
916 encoding='utf_8', errors='replace') as rf_file:
917 rf_file.writelines(rf_list)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200918 except OSError as err:
Ned Deilyf505b742011-12-14 14:58:24 -0800919 if not getattr(self.root, "recentfilelist_error_displayed", False):
920 self.root.recentfilelist_error_displayed = True
921 tkMessageBox.showerror(title='IDLE Error',
922 message='Unable to update Recent Files list:\n%s'
923 % str(err),
924 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000925 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000926 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000927 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700928 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000929 for i, file_name in enumerate(rf_list):
930 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000931 # make unicode string to display non-ASCII chars correctly
932 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000933 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000934 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000935 command=callback,
936 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000937
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000938 def __recent_file_callback(self, file_name):
939 def open_recent_file(fn_closure=file_name):
940 self.io.open(editFile=fn_closure)
941 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000942
David Scherer7aced172000-08-15 01:13:23 +0000943 def saved_change_hook(self):
944 short = self.short_title()
945 long = self.long_title()
946 if short and long:
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400947 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000948 elif short:
949 title = short
950 elif long:
951 title = long
952 else:
953 title = "Untitled"
954 icon = short or long or title
955 if not self.get_saved():
956 title = "*%s*" % title
957 icon = "*%s" % icon
958 self.top.wm_title(title)
959 self.top.wm_iconname(icon)
960
961 def get_saved(self):
962 return self.undo.get_saved()
963
964 def set_saved(self, flag):
965 self.undo.set_saved(flag)
966
967 def reset_undo(self):
968 self.undo.reset_undo()
969
970 def short_title(self):
971 filename = self.io.filename
972 if filename:
973 filename = os.path.basename(filename)
Terry Jan Reedy94338de2014-01-23 00:36:46 -0500974 else:
975 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000976 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedy0726ddf2014-08-14 21:54:43 -0400977 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000978
979 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000980 # return unicode string to display non-ASCII chars correctly
981 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000982
983 def center_insert_event(self, event):
984 self.center()
985
986 def center(self, mark="insert"):
987 text = self.text
988 top, bot = self.getwindowlines()
989 lineno = self.getlineno(mark)
990 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000991 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000992 text.yview(float(newtop))
993
994 def getwindowlines(self):
995 text = self.text
996 top = self.getlineno("@0,0")
997 bot = self.getlineno("@0,65535")
998 if top == bot and text.winfo_height() == 1:
999 # Geometry manager hasn't run yet
1000 height = int(text['height'])
1001 bot = top + height - 1
1002 return top, bot
1003
1004 def getlineno(self, mark="insert"):
1005 text = self.text
1006 return int(float(text.index(mark)))
1007
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001008 def get_geometry(self):
1009 "Return (width, height, x, y)"
1010 geom = self.top.wm_geometry()
1011 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +00001012 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001013
David Scherer7aced172000-08-15 01:13:23 +00001014 def close_event(self, event):
1015 self.close()
1016
1017 def maybesave(self):
1018 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001019 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001020 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001021 self.top.deiconify()
1022 self.top.lower()
1023 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001024 return self.io.maybesave()
1025
1026 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001027 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +00001028 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001029 self._close()
1030 return reply
1031
1032 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001033 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001034 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001035 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001036 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001037 self.io.close()
1038 self.io = None
1039 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001040 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001041 self.color.close(False)
1042 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001043 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001044 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001045 self.per.close()
1046 self.per = None
1047 self.top.destroy()
1048 if self.close_hook:
1049 # unless override: unregister from flist, terminate if last window
1050 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001051
1052 def load_extensions(self):
1053 self.extensions = {}
1054 self.load_standard_extensions()
1055
1056 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001057 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001058 if hasattr(ins, "close"):
1059 ins.close()
1060 self.extensions = {}
1061
1062 def load_standard_extensions(self):
1063 for name in self.get_standard_extension_names():
1064 try:
1065 self.load_extension(name)
1066 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001067 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001068 traceback.print_exc()
1069
1070 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001071 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001072
1073 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001074 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001075 try:
1076 mod = importlib.import_module('.' + name, package=__package__)
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001077 except (ImportError, TypeError):
Brett Cannonaef82d32012-04-14 20:44:23 -04001078 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001079 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001080 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001081 raise
David Scherer7aced172000-08-15 01:13:23 +00001082 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001083 keydefs = idleConf.GetExtensionBindings(name)
1084 if hasattr(cls, "menudefs"):
1085 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001086 ins = cls(self)
1087 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001088 if keydefs:
1089 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001090 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001091 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001092 while methodname[:1] == '<':
1093 methodname = methodname[1:]
1094 while methodname[-1:] == '>':
1095 methodname = methodname[:-1]
1096 methodname = methodname + "_event"
1097 if hasattr(ins, methodname):
1098 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001099
1100 def apply_bindings(self, keydefs=None):
1101 if keydefs is None:
1102 keydefs = self.Bindings.default_keydefs
1103 text = self.text
1104 text.keydefs = keydefs
1105 for event, keylist in keydefs.items():
1106 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001107 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001108
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001109 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001110 """Add appropriate entries to the menus and submenus
1111
1112 Menus that are absent or None in self.menudict are ignored.
1113 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001114 if menudefs is None:
1115 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001116 if keydefs is None:
1117 keydefs = self.Bindings.default_keydefs
1118 menudict = self.menudict
1119 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001120 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001121 menu = menudict.get(mname)
1122 if not menu:
1123 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001124 for entry in entrylist:
1125 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001126 menu.add_separator()
1127 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001128 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001129 checkbutton = (label[:1] == '!')
1130 if checkbutton:
1131 label = label[1:]
1132 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001133 accelerator = get_accelerator(keydefs, eventname)
1134 def command(text=text, eventname=eventname):
1135 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001136 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001137 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001138 menu.add_checkbutton(label=label, underline=underline,
1139 command=command, accelerator=accelerator,
1140 variable=var)
1141 else:
1142 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001143 command=command,
1144 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001145
1146 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001147 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001148 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001149 value = var.get()
1150 return value
1151 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001152 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001153
1154 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001155 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001156 if var:
1157 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001158 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001159 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001160
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001161 def get_var_obj(self, name, vartype=None):
1162 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001163 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001164 # create a Tkinter variable object with self.text as master:
1165 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001166 return var
1167
1168 # Tk implementations of "virtual text methods" -- each platform
1169 # reusing IDLE's support code needs to define these for its GUI's
1170 # flavor of widget.
1171
1172 # Is character at text_index in a Python string? Return 0 for
1173 # "guaranteed no", true for anything else. This info is expensive
1174 # to compute ab initio, but is probably already known by the
1175 # platform's colorizer.
1176
1177 def is_char_in_string(self, text_index):
1178 if self.color:
1179 # Return true iff colorizer hasn't (re)gotten this far
1180 # yet, or the character is tagged as being in a string
1181 return self.text.tag_prevrange("TODO", text_index) or \
1182 "STRING" in self.text.tag_names(text_index)
1183 else:
1184 # The colorizer is missing: assume the worst
1185 return 1
1186
1187 # If a selection is defined in the text widget, return (start,
1188 # end) as Tkinter text indices, otherwise return (None, None)
1189 def get_selection_indices(self):
1190 try:
1191 first = self.text.index("sel.first")
1192 last = self.text.index("sel.last")
1193 return first, last
1194 except TclError:
1195 return None, None
1196
1197 # Return the text widget's current view of what a tab stop means
1198 # (equivalent width in spaces).
1199
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001200 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001201 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1202 return int(current)
1203
1204 # Set the text widget's current view of what a tab stop means.
1205
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001206 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001207 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001208 if self.get_tk_tabwidth() != newtabwidth:
1209 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001210 pixels = text.tk.call("font", "measure", text["font"],
1211 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001212 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001213 text.configure(tabs=pixels)
1214
Guido van Rossum33d26892007-08-05 15:29:28 +00001215### begin autoindent code ### (configuration was moved to beginning of class)
1216
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001217 def set_indentation_params(self, is_py_src, guess=True):
1218 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001219 i = self.guess_indent()
1220 if 2 <= i <= 8:
1221 self.indentwidth = i
1222 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001223 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001224 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001225
1226 def smart_backspace_event(self, event):
1227 text = self.text
1228 first, last = self.get_selection_indices()
1229 if first and last:
1230 text.delete(first, last)
1231 text.mark_set("insert", first)
1232 return "break"
1233 # Delete whitespace left, until hitting a real char or closest
1234 # preceding virtual tab stop.
1235 chars = text.get("insert linestart", "insert")
1236 if chars == '':
1237 if text.compare("insert", ">", "1.0"):
1238 # easy: delete preceding newline
1239 text.delete("insert-1c")
1240 else:
1241 text.bell() # at start of buffer
1242 return "break"
1243 if chars[-1] not in " \t":
1244 # easy: delete preceding real char
1245 text.delete("insert-1c")
1246 return "break"
1247 # Ick. It may require *inserting* spaces if we back up over a
1248 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001249 tabwidth = self.tabwidth
1250 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001251 assert have > 0
1252 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001253 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001254 if self.context_use_ps1:
1255 last_line_of_prompt = sys.ps1.split('\n')[-1]
1256 else:
1257 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001258 ncharsdeleted = 0
1259 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001260 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001261 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001262 chars = chars[:-1]
1263 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001264 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001265 if have <= want or chars[-1] not in " \t":
1266 break
1267 text.undo_block_start()
1268 text.delete("insert-%dc" % ncharsdeleted, "insert")
1269 if have < want:
1270 text.insert("insert", ' ' * (want - have))
1271 text.undo_block_stop()
1272 return "break"
1273
1274 def smart_indent_event(self, event):
1275 # if intraline selection:
1276 # delete it
1277 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001278 # do indent-region
1279 # else:
1280 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001281 text = self.text
1282 first, last = self.get_selection_indices()
1283 text.undo_block_start()
1284 try:
1285 if first and last:
1286 if index2line(first) != index2line(last):
1287 return self.indent_region_event(event)
1288 text.delete(first, last)
1289 text.mark_set("insert", first)
1290 prefix = text.get("insert linestart", "insert")
1291 raw, effective = classifyws(prefix, self.tabwidth)
1292 if raw == len(prefix):
1293 # only whitespace to the left
1294 self.reindent_to(effective + self.indentwidth)
1295 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001296 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001297 if self.usetabs:
1298 pad = '\t'
1299 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001300 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001301 n = self.indentwidth
1302 pad = ' ' * (n - effective % n)
1303 text.insert("insert", pad)
1304 text.see("insert")
1305 return "break"
1306 finally:
1307 text.undo_block_stop()
1308
1309 def newline_and_indent_event(self, event):
1310 text = self.text
1311 first, last = self.get_selection_indices()
1312 text.undo_block_start()
1313 try:
1314 if first and last:
1315 text.delete(first, last)
1316 text.mark_set("insert", first)
1317 line = text.get("insert linestart", "insert")
1318 i, n = 0, len(line)
1319 while i < n and line[i] in " \t":
1320 i = i+1
1321 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001322 # the cursor is in or at leading indentation in a continuation
1323 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001324 text.insert("insert linestart", '\n')
1325 return "break"
1326 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001327 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001328 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001329 last_line_of_prompt = sys.ps1.split('\n')[-1]
1330 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001331 line = line[:-1]
1332 i = i+1
1333 if i:
1334 text.delete("insert - %d chars" % i, "insert")
1335 # strip whitespace after insert point
1336 while text.get("insert") in " \t":
1337 text.delete("insert")
1338 # start new line
1339 text.insert("insert", '\n')
1340
1341 # adjust indentation for continuations and block
1342 # open/close first need to find the last stmt
1343 lno = index2line(text.index('insert'))
1344 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001345 if not self.context_use_ps1:
1346 for context in self.num_context_lines:
1347 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001348 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001349 rawtext = text.get(startatindex, "insert")
1350 y.set_str(rawtext)
1351 bod = y.find_good_parse_start(
1352 self.context_use_ps1,
1353 self._build_char_in_string_func(startatindex))
1354 if bod is not None or startat == 1:
1355 break
1356 y.set_lo(bod or 0)
1357 else:
1358 r = text.tag_prevrange("console", "insert")
1359 if r:
1360 startatindex = r[1]
1361 else:
1362 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001363 rawtext = text.get(startatindex, "insert")
1364 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001365 y.set_lo(0)
1366
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001367 c = y.get_continuation_type()
1368 if c != PyParse.C_NONE:
1369 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001370 if c == PyParse.C_STRING_FIRST_LINE:
1371 # after the first line of a string; do not indent at all
1372 pass
1373 elif c == PyParse.C_STRING_NEXT_LINES:
1374 # inside a string which started before this line;
1375 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001376 text.insert("insert", indent)
1377 elif c == PyParse.C_BRACKET:
1378 # line up with the first (if any) element of the
1379 # last open bracket structure; else indent one
1380 # level beyond the indent of the line with the
1381 # last open bracket
1382 self.reindent_to(y.compute_bracket_indent())
1383 elif c == PyParse.C_BACKSLASH:
1384 # if more than one line in this stmt already, just
1385 # mimic the current indent; else if initial line
1386 # has a start on an assignment stmt, indent to
1387 # beyond leftmost =; else to beyond first chunk of
1388 # non-whitespace on initial line
1389 if y.get_num_lines_in_stmt() > 1:
1390 text.insert("insert", indent)
1391 else:
1392 self.reindent_to(y.compute_backslash_indent())
1393 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001394 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001395 return "break"
1396
1397 # This line starts a brand new stmt; indent relative to
1398 # indentation of initial line of closest preceding
1399 # interesting stmt.
1400 indent = y.get_base_indent_string()
1401 text.insert("insert", indent)
1402 if y.is_block_opener():
1403 self.smart_indent_event(event)
1404 elif indent and y.is_block_closer():
1405 self.smart_backspace_event(event)
1406 return "break"
1407 finally:
1408 text.see("insert")
1409 text.undo_block_stop()
1410
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001411 # Our editwin provides a is_char_in_string function that works
1412 # with a Tk text index, but PyParse only knows about offsets into
1413 # a string. This builds a function for PyParse that accepts an
1414 # offset.
1415
1416 def _build_char_in_string_func(self, startindex):
1417 def inner(offset, _startindex=startindex,
1418 _icis=self.is_char_in_string):
1419 return _icis(_startindex + "+%dc" % offset)
1420 return inner
1421
1422 def indent_region_event(self, event):
1423 head, tail, chars, lines = self.get_region()
1424 for pos in range(len(lines)):
1425 line = lines[pos]
1426 if line:
1427 raw, effective = classifyws(line, self.tabwidth)
1428 effective = effective + self.indentwidth
1429 lines[pos] = self._make_blanks(effective) + line[raw:]
1430 self.set_region(head, tail, chars, lines)
1431 return "break"
1432
1433 def dedent_region_event(self, event):
1434 head, tail, chars, lines = self.get_region()
1435 for pos in range(len(lines)):
1436 line = lines[pos]
1437 if line:
1438 raw, effective = classifyws(line, self.tabwidth)
1439 effective = max(effective - self.indentwidth, 0)
1440 lines[pos] = self._make_blanks(effective) + line[raw:]
1441 self.set_region(head, tail, chars, lines)
1442 return "break"
1443
1444 def comment_region_event(self, event):
1445 head, tail, chars, lines = self.get_region()
1446 for pos in range(len(lines) - 1):
1447 line = lines[pos]
1448 lines[pos] = '##' + line
1449 self.set_region(head, tail, chars, lines)
1450
1451 def uncomment_region_event(self, event):
1452 head, tail, chars, lines = self.get_region()
1453 for pos in range(len(lines)):
1454 line = lines[pos]
1455 if not line:
1456 continue
1457 if line[:2] == '##':
1458 line = line[2:]
1459 elif line[:1] == '#':
1460 line = line[1:]
1461 lines[pos] = line
1462 self.set_region(head, tail, chars, lines)
1463
1464 def tabify_region_event(self, event):
1465 head, tail, chars, lines = self.get_region()
1466 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001467 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001468 for pos in range(len(lines)):
1469 line = lines[pos]
1470 if line:
1471 raw, effective = classifyws(line, tabwidth)
1472 ntabs, nspaces = divmod(effective, tabwidth)
1473 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1474 self.set_region(head, tail, chars, lines)
1475
1476 def untabify_region_event(self, event):
1477 head, tail, chars, lines = self.get_region()
1478 tabwidth = self._asktabwidth()
Roger Serwy0ef392c2013-04-06 20:26:53 -05001479 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001480 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001481 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001482 self.set_region(head, tail, chars, lines)
1483
1484 def toggle_tabs_event(self, event):
1485 if self.askyesno(
1486 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001487 "Turn tabs " + ("on", "off")[self.usetabs] +
1488 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001489 ("will be", "remains at")[self.usetabs] + " 8." +
1490 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001491 parent=self.text):
1492 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001493 # Try to prevent inconsistent indentation.
1494 # User must change indent width manually after using tabs.
1495 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001496 return "break"
1497
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001498 # XXX this isn't bound to anything -- see tabwidth comments
1499## def change_tabwidth_event(self, event):
1500## new = self._asktabwidth()
1501## if new != self.tabwidth:
1502## self.tabwidth = new
1503## self.set_indentation_params(0, guess=0)
1504## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001505
1506 def change_indentwidth_event(self, event):
1507 new = self.askinteger(
1508 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001509 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001510 parent=self.text,
1511 initialvalue=self.indentwidth,
1512 minvalue=2,
1513 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001514 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001515 self.indentwidth = new
1516 return "break"
1517
1518 def get_region(self):
1519 text = self.text
1520 first, last = self.get_selection_indices()
1521 if first and last:
1522 head = text.index(first + " linestart")
1523 tail = text.index(last + "-1c lineend +1c")
1524 else:
1525 head = text.index("insert linestart")
1526 tail = text.index("insert lineend +1c")
1527 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001528 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001529 return head, tail, chars, lines
1530
1531 def set_region(self, head, tail, chars, lines):
1532 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001533 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001534 if newchars == chars:
1535 text.bell()
1536 return
1537 text.tag_remove("sel", "1.0", "end")
1538 text.mark_set("insert", head)
1539 text.undo_block_start()
1540 text.delete(head, tail)
1541 text.insert(head, newchars)
1542 text.undo_block_stop()
1543 text.tag_add("sel", head, "insert")
1544
1545 # Make string that displays as n leading blanks.
1546
1547 def _make_blanks(self, n):
1548 if self.usetabs:
1549 ntabs, nspaces = divmod(n, self.tabwidth)
1550 return '\t' * ntabs + ' ' * nspaces
1551 else:
1552 return ' ' * n
1553
1554 # Delete from beginning of line to insert point, then reinsert
1555 # column logical (meaning use tabs if appropriate) spaces.
1556
1557 def reindent_to(self, column):
1558 text = self.text
1559 text.undo_block_start()
1560 if text.compare("insert linestart", "!=", "insert"):
1561 text.delete("insert linestart", "insert")
1562 if column:
1563 text.insert("insert", self._make_blanks(column))
1564 text.undo_block_stop()
1565
1566 def _asktabwidth(self):
1567 return self.askinteger(
1568 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001569 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001570 parent=self.text,
1571 initialvalue=self.indentwidth,
1572 minvalue=2,
Roger Serwy0ef392c2013-04-06 20:26:53 -05001573 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001574
1575 # Guess indentwidth from text content.
1576 # Return guessed indentwidth. This should not be believed unless
1577 # it's in a reasonable range (e.g., it will be 0 if no indented
1578 # blocks are found).
1579
1580 def guess_indent(self):
1581 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1582 if opener and indented:
1583 raw, indentsmall = classifyws(opener, self.tabwidth)
1584 raw, indentlarge = classifyws(indented, self.tabwidth)
1585 else:
1586 indentsmall = indentlarge = 0
1587 return indentlarge - indentsmall
1588
1589# "line.col" -> line, as an int
1590def index2line(index):
1591 return int(float(index))
1592
1593# Look at the leading whitespace in s.
1594# Return pair (# of leading ws characters,
1595# effective # of leading blanks after expanding
1596# tabs to width tabwidth)
1597
1598def classifyws(s, tabwidth):
1599 raw = effective = 0
1600 for ch in s:
1601 if ch == ' ':
1602 raw = raw + 1
1603 effective = effective + 1
1604 elif ch == '\t':
1605 raw = raw + 1
1606 effective = (effective // tabwidth + 1) * tabwidth
1607 else:
1608 break
1609 return raw, effective
1610
1611import tokenize
1612_tokenize = tokenize
1613del tokenize
1614
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001615class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001616
1617 # .run() chews over the Text widget, looking for a block opener
1618 # and the stmt following it. Returns a pair,
1619 # (line containing block opener, line containing stmt)
1620 # Either or both may be None.
1621
1622 def __init__(self, text, tabwidth):
1623 self.text = text
1624 self.tabwidth = tabwidth
1625 self.i = self.finished = 0
1626 self.blkopenline = self.indentedline = None
1627
1628 def readline(self):
1629 if self.finished:
1630 return ""
1631 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001632 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001633 if self.text.compare(mark, ">=", "end"):
1634 return ""
1635 return self.text.get(mark, mark + " lineend+1c")
1636
1637 def tokeneater(self, type, token, start, end, line,
1638 INDENT=_tokenize.INDENT,
1639 NAME=_tokenize.NAME,
1640 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1641 if self.finished:
1642 pass
1643 elif type == NAME and token in OPENERS:
1644 self.blkopenline = line
1645 elif type == INDENT and self.blkopenline:
1646 self.indentedline = line
1647 self.finished = 1
1648
1649 def run(self):
1650 save_tabsize = _tokenize.tabsize
1651 _tokenize.tabsize = self.tabwidth
1652 try:
1653 try:
Trent Nelson428de652008-03-18 22:41:35 +00001654 tokens = _tokenize.generate_tokens(self.readline)
1655 for token in tokens:
1656 self.tokeneater(*token)
Serhiy Storchaka07e0e062012-12-27 21:38:04 +02001657 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001658 # since we cut off the tokenizer early, we can trigger
1659 # spurious errors
1660 pass
1661 finally:
1662 _tokenize.tabsize = save_tabsize
1663 return self.blkopenline, self.indentedline
1664
1665### end autoindent code ###
1666
David Scherer7aced172000-08-15 01:13:23 +00001667def prepstr(s):
1668 # Helper to extract the underscore from a string, e.g.
1669 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001670 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001671 if i >= 0:
1672 s = s[:i] + s[i+1:]
1673 return i, s
1674
1675
1676keynames = {
1677 'bracketleft': '[',
1678 'bracketright': ']',
1679 'slash': '/',
1680}
1681
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001682def get_accelerator(keydefs, eventname):
1683 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001684 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1685 # if not keylist:
Ned Deilyb7601672014-03-27 20:49:14 -07001686 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily70063932011-01-29 18:29:01 +00001687 "<<open-module>>",
1688 "<<goto-line>>",
1689 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001690 return ""
1691 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001692 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001693 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1694 s = re.sub("Key-", "", s)
1695 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1696 s = re.sub("Control-", "Ctrl-", s)
1697 s = re.sub("-", "+", s)
1698 s = re.sub("><", " ", s)
1699 s = re.sub("<", "", s)
1700 s = re.sub(">", "", s)
1701 return s
1702
1703
1704def fixwordbreaks(root):
1705 # Make sure that Tk's double-click and next/previous word
1706 # operations use our definition of a word (i.e. an identifier)
1707 tk = root.tk
1708 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1709 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1710 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1711
1712
Terry Jan Reedycd567362014-10-17 01:31:35 -04001713def _editor_window(parent): # htest #
1714 # error if close master window first - timer event, after script
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001715 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001716 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001717 if sys.argv[1:]:
1718 filename = sys.argv[1]
1719 else:
1720 filename = None
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001721 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001722 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001723 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycd567362014-10-17 01:31:35 -04001724 # Does not stop error, neither does following
1725 # edit.text.bind("<<close-window>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001726
1727if __name__ == '__main__':
Terry Jan Reedy06313b72014-05-11 23:32:32 -04001728 from idlelib.idle_test.htest import run
Terry Jan Reedya2fc99e2014-05-25 18:44:05 -04001729 run(_help_dialog, _editor_window)