blob: afa79f05cda694abae439ef1130dd82e0d57b4a0 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
Terry Jan Reedyc11633e2014-08-14 21:54:38 -04003import platform
David Scherer7aced172000-08-15 01:13:23 +00004import re
5import imp
Georg Brandl6634bf22008-05-20 07:13:37 +00006from Tkinter import *
7import tkSimpleDialog
8import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00009import webbrowser
Florent Xiclunad630c042010-04-02 07:24:52 +000010
11from idlelib.MultiCall import MultiCallCreator
Florent Xiclunad630c042010-04-02 07:24:52 +000012from idlelib import WindowList
13from idlelib import SearchDialog
14from idlelib import GrepDialog
15from idlelib import ReplaceDialog
16from idlelib import PyParse
17from idlelib.configHandler import idleConf
18from idlelib import aboutDialog, textView, configDialog
19from idlelib import macosxSupport
Terry Jan Reedy70e763c2015-09-20 19:55:44 -040020from idlelib import help
David Scherer7aced172000-08-15 01:13:23 +000021
22# The default tab setting for a Text widget, in average-width characters.
23TK_TABWIDTH_DEFAULT = 8
24
Terry Jan Reedyc11633e2014-08-14 21:54:38 -040025_py_version = ' (%s)' % platform.python_version()
26
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000027def _sphinx_version():
28 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
29 major, minor, micro, level, serial = sys.version_info
30 release = '%s%s' % (major, minor)
31 if micro:
Benjamin Petersonfb234632009-06-13 15:42:23 +000032 release += '%s' % (micro,)
33 if level == 'candidate':
34 release += 'rc%s' % (serial,)
35 elif level != 'final':
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000036 release += '%s%s' % (level[0], serial)
37 return release
38
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000039def _find_module(fullname, path=None):
40 """Version of imp.find_module() that handles hierarchical module names"""
41
42 file = None
43 for tgt in fullname.split('.'):
44 if file is not None:
45 file.close() # close intermediate files
46 (file, filename, descr) = imp.find_module(tgt, path)
47 if descr[2] == imp.PY_SOURCE:
48 break # find but not load the source file
49 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000050 try:
51 path = module.__path__
52 except AttributeError:
53 raise ImportError, 'No source for module ' + module.__name__
Raymond Hettinger179816d2011-04-12 18:54:46 -070054 if descr[2] != imp.PY_SOURCE:
55 # If all of the above fails and didn't raise an exception,fallback
56 # to a straight import which can find __init__.py in a package.
57 m = __import__(fullname)
58 try:
59 filename = m.__file__
60 except AttributeError:
61 pass
62 else:
63 file = None
64 base, ext = os.path.splitext(filename)
65 if ext == '.pyc':
66 ext = '.py'
67 filename = base + ext
68 descr = filename, None, imp.PY_SOURCE
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000069 return file, filename, descr
70
Terry Jan Reedyadb87e22012-02-05 15:10:55 -050071
72class HelpDialog(object):
73
74 def __init__(self):
Terry Jan Reedy70e763c2015-09-20 19:55:44 -040075 import warnings as w
76 w.warn("EditorWindow.HelpDialog is no longer used by Idle.\n"
77 "It will be removed in 3.6 or later.\n"
78 "It has been replaced by private help.HelpWindow\n",
79 DeprecationWarning, stacklevel=2)
Terry Jan Reedyadb87e22012-02-05 15:10:55 -050080 self.parent = None # parent of help window
81 self.dlg = None # the help window iteself
82
83 def display(self, parent, near=None):
84 """ Display the help dialog.
85
86 parent - parent widget for the help window
87
88 near - a Toplevel widget (e.g. EditorWindow or PyShell)
89 to use as a reference for placing the help window
90 """
91 if self.dlg is None:
92 self.show_dialog(parent)
93 if near:
94 self.nearwindow(near)
95
96 def show_dialog(self, parent):
97 self.parent = parent
98 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
99 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
100 dlg.bind('<Destroy>', self.destroy, '+')
101
102 def nearwindow(self, near):
103 # Place the help dialog near the window specified by parent.
104 # Note - this may not reposition the window in Metacity
105 # if "/apps/metacity/general/disable_workarounds" is enabled
106 dlg = self.dlg
107 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
108 dlg.withdraw()
109 dlg.geometry("=+%d+%d" % geom)
110 dlg.deiconify()
111 dlg.lift()
112
113 def destroy(self, ev=None):
114 self.dlg = None
115 self.parent = None
116
117helpDialog = HelpDialog() # singleton instance
Terry Jan Reedy43458462014-05-19 00:12:00 -0400118def _help_dialog(parent): # wrapper for htest
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -0400119 helpDialog.show_dialog(parent)
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500120
121
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +0000122class EditorWindow(object):
Florent Xiclunad630c042010-04-02 07:24:52 +0000123 from idlelib.Percolator import Percolator
124 from idlelib.ColorDelegator import ColorDelegator
125 from idlelib.UndoDelegator import UndoDelegator
126 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
127 from idlelib import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +0000128 from Tkinter import Toplevel
Florent Xiclunad630c042010-04-02 07:24:52 +0000129 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +0000130
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000131 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000132
133 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000134 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +0000135 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000136 if sys.platform.count('linux'):
137 # look for html docs in a couple of standard places
138 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
139 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
140 dochome = '/var/www/html/python/index.html'
141 else:
142 basepath = '/usr/share/doc/' # standard location
143 dochome = os.path.join(basepath, pyver,
144 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000145 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +0000146 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +0000147 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000148 if os.path.isfile(chmfile):
149 dochome = chmfile
Ned Deily57847df2014-03-27 20:47:04 -0700150 elif sys.platform == 'darwin':
151 # documentation may be stored inside a python framework
Ronald Oussoren19302d92006-06-11 14:33:36 +0000152 dochome = os.path.join(sys.prefix,
153 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000154 dochome = os.path.normpath(dochome)
155 if os.path.isfile(dochome):
156 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +0000157 if sys.platform == 'darwin':
158 # Safari requires real file:-URLs
159 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000160 else:
Terry Jan Reedyb17c1e02014-09-19 22:54:09 -0400161 EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
David Scherer7aced172000-08-15 01:13:23 +0000162 self.flist = flist
163 root = root or flist.root
164 self.root = root
Kurt B. Kaiserb3c4d162006-07-24 17:13:23 +0000165 try:
166 sys.ps1
167 except AttributeError:
168 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000169 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000170 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000171 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000172 self.tkinter_vars = flist.vars
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200173 #self.top.instance_dict makes flist.inversedict available to
174 #configDialog.py so it can access all EditorWindow instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000175 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000176 else:
177 self.tkinter_vars = {} # keys: Tkinter event names
178 # values: Tkinter variable instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000179 self.top.instance_dict = {}
180 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000181 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000182 self.text_frame = text_frame = Frame(top)
Martin v. Löwis4ebbefe2006-11-22 08:50:02 +0000183 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200184 self.width = idleConf.GetOption('main','EditorWindow','width', type='int')
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000185 text_options = {
186 'name': 'text',
187 'padx': 5,
188 'wrap': 'none',
189 'width': self.width,
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200190 'height': idleConf.GetOption('main', 'EditorWindow', 'height', type='int')}
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000191 if TkVersion >= 8.5:
192 # Starting with tk 8.5 we have to set the new tabstyle option
193 # to 'wordprocessor' to achieve the same display of tabs as in
194 # older tk versions.
195 text_options['tabstyle'] = 'wordprocessor'
196 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000197 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000198
199 self.createmenubar()
200 self.apply_bindings()
201
202 self.top.protocol("WM_DELETE_WINDOW", self.close)
203 self.top.bind("<<close-window>>", self.close_event)
Ned Deily57847df2014-03-27 20:47:04 -0700204 if macosxSupport.isAquaTk():
Ronald Oussoren17db4952006-07-23 09:41:09 +0000205 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000206 text.bind('<<close-window>>', self.close_event)
R. David Murray3f752ab2010-12-18 17:22:18 +0000207 # Some OS X systems have only one mouse button,
208 # so use control-click for pulldown menus there.
209 # (Note, AquaTk defines <2> as the right button if
210 # present and the Tk Text widget already binds <2>.)
211 text.bind("<Control-Button-1>",self.right_menu_event)
212 else:
213 # Elsewhere, use right-click for pulldown menus.
214 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000215 text.bind("<<cut>>", self.cut)
216 text.bind("<<copy>>", self.copy)
217 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000218 text.bind("<<center-insert>>", self.center_insert_event)
219 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000220 text.bind("<<python-docs>>", self.python_docs)
221 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000222 text.bind("<<open-config-dialog>>", self.config_dialog)
Terry Jan Reedy7a162072014-10-22 20:15:12 -0400223 text.bind("<<open-config-extensions-dialog>>",
224 self.config_extensions_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000225 text.bind("<<open-module>>", self.open_module)
226 text.bind("<<do-nothing>>", lambda event: "break")
227 text.bind("<<select-all>>", self.select_all)
228 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000229 text.bind("<<find>>", self.find_event)
230 text.bind("<<find-again>>", self.find_again_event)
231 text.bind("<<find-in-files>>", self.find_in_files_event)
232 text.bind("<<find-selection>>", self.find_selection_event)
233 text.bind("<<replace>>", self.replace_event)
234 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000235 text.bind("<<smart-backspace>>",self.smart_backspace_event)
236 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
237 text.bind("<<smart-indent>>",self.smart_indent_event)
238 text.bind("<<indent-region>>",self.indent_region_event)
239 text.bind("<<dedent-region>>",self.dedent_region_event)
240 text.bind("<<comment-region>>",self.comment_region_event)
241 text.bind("<<uncomment-region>>",self.uncomment_region_event)
242 text.bind("<<tabify-region>>",self.tabify_region_event)
243 text.bind("<<untabify-region>>",self.untabify_region_event)
244 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
245 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000246 text.bind("<Left>", self.move_at_edge_if_selection(0))
247 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000248 text.bind("<<del-word-left>>", self.del_word_left)
249 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000250 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000251
David Scherer7aced172000-08-15 01:13:23 +0000252 if flist:
253 flist.inversedict[self] = key
254 if key:
255 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000256 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000257 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
258 text.bind("<<open-class-browser>>", self.open_class_browser)
259 text.bind("<<open-path-browser>>", self.open_path_browser)
260
Steven M. Gava898a3652001-10-07 11:10:44 +0000261 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000262 vbar['command'] = text.yview
263 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000264 text['yscrollcommand'] = vbar.set
Terry Jan Reedy12352142015-08-01 18:57:27 -0400265 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000266 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
267 text.pack(side=TOP, fill=BOTH, expand=1)
268 text.focus_set()
269
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000270 # usetabs true -> literal tab characters are used by indent and
271 # dedent cmds, possibly mixed with spaces if
272 # indentwidth is not a multiple of tabwidth,
273 # which will cause Tabnanny to nag!
274 # false -> tab characters are converted to spaces by indent
275 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000276 # Although use-spaces=0 can be configured manually in config-main.def,
277 # configuration of tabs v. spaces is not supported in the configuration
278 # dialog. IDLE promotes the preferred Python indentation: use spaces!
279 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
280 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000281
282 # tabwidth is the display width of a literal tab character.
283 # CAUTION: telling Tk to use anything other than its default
284 # tab setting causes it to use an entirely different tabbing algorithm,
285 # treating tab stops as fixed distances from the left margin.
286 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000287 self.tabwidth = 8 # must remain 8 until Tk is fixed.
288
289 # indentwidth is the number of screen characters per indent level.
290 # The recommended Python indentation is four spaces.
291 self.indentwidth = self.tabwidth
292 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000293
294 # If context_use_ps1 is true, parsing searches back for a ps1 line;
295 # else searches for a popular (if, def, ...) Python stmt.
296 self.context_use_ps1 = False
297
298 # When searching backwards for a reliable place to begin parsing,
299 # first start num_context_lines[0] lines back, then
300 # num_context_lines[1] lines back if that didn't work, and so on.
301 # The last value should be huge (larger than the # of lines in a
302 # conceivable file).
303 # Making the initial values larger slows things down more often.
304 self.num_context_lines = 50, 500, 5000000
305
David Scherer7aced172000-08-15 01:13:23 +0000306 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000307
308 self.undo = undo = self.UndoDelegator()
309 per.insertfilter(undo)
310 text.undo_block_start = undo.undo_block_start
311 text.undo_block_stop = undo.undo_block_stop
312 undo.set_saved_change_hook(self.saved_change_hook)
313
314 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000315 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000316 io.set_filename_change_hook(self.filename_change_hook)
317
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000318 # Create the recent files submenu
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400319 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000320 self.menudict['file'].insert_cascade(3, label='Recent Files',
321 underline=0,
322 menu=self.recent_files_menu)
323 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000324
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000325 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000326 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000327 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000328 io.loadfile(filename)
329 else:
330 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000331 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000332 self.saved_change_hook()
333
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000334 self.set_indentation_params(self.ispythonsource(filename))
335
David Scherer7aced172000-08-15 01:13:23 +0000336 self.load_extensions()
337
338 menu = self.menudict.get('windows')
339 if menu:
340 end = menu.index("end")
341 if end is None:
342 end = -1
343 if end >= 0:
344 menu.add_separator()
345 end = end + 1
346 self.wmenu_end = end
347 WindowList.register_callback(self.postwindowsmenu)
348
349 # Some abstractions so IDLE extensions are cross-IDE
350 self.askyesno = tkMessageBox.askyesno
351 self.askinteger = tkSimpleDialog.askinteger
352 self.showerror = tkMessageBox.showerror
353
Roger Serwy02c0ed02013-05-20 22:13:39 -0500354 self._highlight_workaround() # Fix selection tags on Windows
355
356 def _highlight_workaround(self):
357 # On Windows, Tk removes painting of the selection
358 # tags which is different behavior than on Linux and Mac.
359 # See issue14146 for more information.
360 if not sys.platform.startswith('win'):
361 return
362
363 text = self.text
364 text.event_add("<<Highlight-FocusOut>>", "<FocusOut>")
365 text.event_add("<<Highlight-FocusIn>>", "<FocusIn>")
366 def highlight_fix(focus):
367 sel_range = text.tag_ranges("sel")
368 if sel_range:
369 if focus == 'out':
370 HILITE_CONFIG = idleConf.GetHighlight(
371 idleConf.CurrentTheme(), 'hilite')
372 text.tag_config("sel_fix", HILITE_CONFIG)
373 text.tag_raise("sel_fix")
374 text.tag_add("sel_fix", *sel_range)
375 elif focus == 'in':
376 text.tag_remove("sel_fix", "1.0", "end")
377
378 text.bind("<<Highlight-FocusOut>>",
379 lambda ev: highlight_fix("out"))
380 text.bind("<<Highlight-FocusIn>>",
381 lambda ev: highlight_fix("in"))
382
383
Martin v. Löwis307021f2005-11-27 16:59:04 +0000384 def _filename_to_unicode(self, filename):
385 """convert filename to unicode in order to display it in Tk"""
386 if isinstance(filename, unicode) or not filename:
387 return filename
388 else:
389 try:
390 return filename.decode(self.filesystemencoding)
391 except UnicodeDecodeError:
392 # XXX
393 try:
394 return filename.decode(self.encoding)
395 except UnicodeDecodeError:
396 # byte-to-byte conversion
397 return filename.decode('iso8859-1')
398
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000399 def new_callback(self, event):
400 dirname, basename = self.io.defaultfilename()
401 self.flist.new(dirname)
402 return "break"
403
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000404 def home_callback(self, event):
Kurt B. Kaiser020d3d92011-03-17 00:05:38 -0400405 if (event.state & 4) != 0 and event.keysym == "Home":
406 # state&4==Control. If <Control-Home>, use the Tk binding.
407 return
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000408 if self.text.index("iomark") and \
409 self.text.compare("iomark", "<=", "insert lineend") and \
410 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400411 # In Shell on input line, go to just after prompt
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000412 insertpt = int(self.text.index("iomark").split(".")[1])
413 else:
414 line = self.text.get("insert linestart", "insert lineend")
415 for insertpt in xrange(len(line)):
416 if line[insertpt] not in (' ','\t'):
417 break
418 else:
419 insertpt=len(line)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000420 lineat = int(self.text.index("insert").split('.')[1])
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000421 if insertpt == lineat:
422 insertpt = 0
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000423 dest = "insert linestart+"+str(insertpt)+"c"
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000424 if (event.state&1) == 0:
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400425 # shift was not pressed
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000426 self.text.tag_remove("sel", "1.0", "end")
427 else:
428 if not self.text.index("sel.first"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400429 self.text.mark_set("my_anchor", "insert") # there was no previous selection
430 else:
431 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
432 self.text.mark_set("my_anchor", "sel.first") # extend back
433 else:
434 self.text.mark_set("my_anchor", "sel.last") # extend forward
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000435 first = self.text.index(dest)
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400436 last = self.text.index("my_anchor")
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000437 if self.text.compare(first,">",last):
438 first,last = last,first
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000439 self.text.tag_remove("sel", "1.0", "end")
440 self.text.tag_add("sel", first, last)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000441 self.text.mark_set("insert", dest)
442 self.text.see("insert")
443 return "break"
444
David Scherer7aced172000-08-15 01:13:23 +0000445 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000446 self.status_bar = self.MultiStatusBar(self.top)
Ned Deily57847df2014-03-27 20:47:04 -0700447 if sys.platform == "darwin":
Ronald Oussoren19302d92006-06-11 14:33:36 +0000448 # Insert some padding to avoid obscuring some of the statusbar
449 # by the resize widget.
450 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000451 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
452 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
453 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000454 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
455 self.text.event_add("<<set-line-and-column>>",
456 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000457 self.text.after_idle(self.set_line_and_column)
458
459 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000460 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000461 self.status_bar.set_label('column', 'Col: %s' % column)
462 self.status_bar.set_label('line', 'Ln: %s' % line)
463
David Scherer7aced172000-08-15 01:13:23 +0000464 menu_specs = [
465 ("file", "_File"),
466 ("edit", "_Edit"),
467 ("format", "F_ormat"),
468 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000469 ("options", "_Options"),
Ned Deilyb5daa3d2015-01-17 21:03:41 -0800470 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000471 ("help", "_Help"),
472 ]
473
Ronald Oussoren19302d92006-06-11 14:33:36 +0000474
David Scherer7aced172000-08-15 01:13:23 +0000475 def createmenubar(self):
476 mbar = self.menubar
477 self.menudict = menudict = {}
478 for name, label in self.menu_specs:
479 underline, label = prepstr(label)
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400480 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000481 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000482
Ned Deily57847df2014-03-27 20:47:04 -0700483 if macosxSupport.isCarbonTk():
Ronald Oussoren19302d92006-06-11 14:33:36 +0000484 # Insert the application menu
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400485 menudict['application'] = menu = Menu(mbar, name='apple',
486 tearoff=0)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000487 mbar.add_cascade(label='IDLE', menu=menu)
488
David Scherer7aced172000-08-15 01:13:23 +0000489 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000490 self.base_helpmenu_length = self.menudict['help'].index(END)
491 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000492
493 def postwindowsmenu(self):
494 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000495 menu = self.menudict['windows']
496 end = menu.index("end")
497 if end is None:
498 end = -1
499 if end > self.wmenu_end:
500 menu.delete(self.wmenu_end+1, end)
501 WindowList.add_windows_to_menu(menu)
502
503 rmenu = None
504
505 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000506 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
507 if not self.rmenu:
508 self.make_rmenu()
509 rmenu = self.rmenu
510 self.event = event
511 iswin = sys.platform[:3] == 'win'
512 if iswin:
513 self.text.config(cursor="arrow")
Andrew Svetlov5018db72012-11-01 22:39:14 +0200514
Roger Serwy231a8fd2013-04-07 12:15:52 -0500515 for item in self.rmenu_specs:
516 try:
517 label, eventname, verify_state = item
518 except ValueError: # see issue1207589
519 continue
520
Andrew Svetlov5018db72012-11-01 22:39:14 +0200521 if verify_state is None:
522 continue
523 state = getattr(self, verify_state)()
524 rmenu.entryconfigure(label, state=state)
525
David Scherer7aced172000-08-15 01:13:23 +0000526 rmenu.tk_popup(event.x_root, event.y_root)
527 if iswin:
528 self.text.config(cursor="ibeam")
529
530 rmenu_specs = [
Andrew Svetlov5018db72012-11-01 22:39:14 +0200531 # ("Label", "<<virtual-event>>", "statefuncname"), ...
532 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000533 ]
534
535 def make_rmenu(self):
536 rmenu = Menu(self.text, tearoff=0)
Roger Serwy231a8fd2013-04-07 12:15:52 -0500537 for item in self.rmenu_specs:
538 label, eventname = item[0], item[1]
Andrew Svetlov5018db72012-11-01 22:39:14 +0200539 if label is not None:
540 def command(text=self.text, eventname=eventname):
541 text.event_generate(eventname)
542 rmenu.add_command(label=label, command=command)
543 else:
544 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000545 self.rmenu = rmenu
546
Andrew Svetlov5018db72012-11-01 22:39:14 +0200547 def rmenu_check_cut(self):
548 return self.rmenu_check_copy()
549
550 def rmenu_check_copy(self):
551 try:
552 indx = self.text.index('sel.first')
553 except TclError:
554 return 'disabled'
555 else:
556 return 'normal' if indx else 'disabled'
557
558 def rmenu_check_paste(self):
559 try:
560 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
561 except TclError:
562 return 'disabled'
563 else:
564 return 'normal'
565
David Scherer7aced172000-08-15 01:13:23 +0000566 def about_dialog(self, event=None):
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400567 "Handle Help 'About IDLE' event."
568 # Synchronize with macosxSupport.overrideRootMenu.about_dialog.
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000569 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000570
Steven M. Gava3b55a892001-11-21 05:56:26 +0000571 def config_dialog(self, event=None):
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400572 "Handle Options 'Configure IDLE' event."
573 # Synchronize with macosxSupport.overrideRootMenu.config_dialog.
Steven M. Gava3b55a892001-11-21 05:56:26 +0000574 configDialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400575
Terry Jan Reedy7a162072014-10-22 20:15:12 -0400576 def config_extensions_dialog(self, event=None):
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400577 "Handle Options 'Configure Extensions' event."
Terry Jan Reedy7a162072014-10-22 20:15:12 -0400578 configDialog.ConfigExtensionsDialog(self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000579
David Scherer7aced172000-08-15 01:13:23 +0000580 def help_dialog(self, event=None):
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400581 "Handle Help 'IDLE Help' event."
582 # Synchronize with macosxSupport.overrideRootMenu.help_dialog.
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500583 if self.root:
584 parent = self.root
585 else:
586 parent = self.top
Terry Jan Reedy70e763c2015-09-20 19:55:44 -0400587 help.show_idlehelp(parent)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000588
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000589 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000590 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000591 try:
592 os.startfile(self.help_url)
593 except WindowsError as why:
594 tkMessageBox.showerror(title='Document Start Failure',
595 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000596 else:
597 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000598 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000599
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000600 def cut(self,event):
601 self.text.event_generate("<<Cut>>")
602 return "break"
603
604 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000605 if not self.text.tag_ranges("sel"):
606 # There is no selection, so do nothing and maybe interrupt.
607 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000608 self.text.event_generate("<<Copy>>")
609 return "break"
610
611 def paste(self,event):
612 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000613 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000614 return "break"
615
David Scherer7aced172000-08-15 01:13:23 +0000616 def select_all(self, event=None):
617 self.text.tag_add("sel", "1.0", "end-1c")
618 self.text.mark_set("insert", "1.0")
619 self.text.see("insert")
620 return "break"
621
622 def remove_selection(self, event=None):
623 self.text.tag_remove("sel", "1.0", "end")
624 self.text.see("insert")
625
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000626 def move_at_edge_if_selection(self, edge_index):
627 """Cursor move begins at start or end of selection
628
629 When a left/right cursor key is pressed create and return to Tkinter a
630 function which causes a cursor move from the associated edge of the
631 selection.
632
633 """
634 self_text_index = self.text.index
635 self_text_mark_set = self.text.mark_set
636 edges_table = ("sel.first+1c", "sel.last-1c")
637 def move_at_edge(event):
638 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
639 try:
640 self_text_index("sel.first")
641 self_text_mark_set("insert", edges_table[edge_index])
642 except TclError:
643 pass
644 return move_at_edge
645
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000646 def del_word_left(self, event):
647 self.text.event_generate('<Meta-Delete>')
648 return "break"
649
650 def del_word_right(self, event):
651 self.text.event_generate('<Meta-d>')
652 return "break"
653
Steven M. Gavac5976402002-01-04 03:06:08 +0000654 def find_event(self, event):
655 SearchDialog.find(self.text)
656 return "break"
657
658 def find_again_event(self, event):
659 SearchDialog.find_again(self.text)
660 return "break"
661
662 def find_selection_event(self, event):
663 SearchDialog.find_selection(self.text)
664 return "break"
665
666 def find_in_files_event(self, event):
667 GrepDialog.grep(self.text, self.io, self.flist)
668 return "break"
669
670 def replace_event(self, event):
671 ReplaceDialog.replace(self.text)
672 return "break"
673
674 def goto_line_event(self, event):
675 text = self.text
676 lineno = tkSimpleDialog.askinteger("Goto",
677 "Go to line number:",parent=text)
678 if lineno is None:
679 return "break"
680 if lineno <= 0:
681 text.bell()
682 return "break"
683 text.mark_set("insert", "%d.0" % lineno)
684 text.see("insert")
685
David Scherer7aced172000-08-15 01:13:23 +0000686 def open_module(self, event=None):
687 # XXX Shouldn't this be in IOBinding or in FileList?
688 try:
689 name = self.text.get("sel.first", "sel.last")
690 except TclError:
691 name = ""
692 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000693 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000694 name = tkSimpleDialog.askstring("Module",
695 "Enter the name of a Python module\n"
696 "to search on sys.path and open:",
697 parent=self.text, initialvalue=name)
698 if name:
699 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000700 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000701 return
David Scherer7aced172000-08-15 01:13:23 +0000702 # XXX Ought to insert current file's directory in front of path
703 try:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400704 (f, file_path, (suffix, mode, mtype)) = _find_module(name)
Terry Jan Reedy2b149862013-06-29 00:59:34 -0400705 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000706 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
707 return
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400708 if mtype != imp.PY_SOURCE:
David Scherer7aced172000-08-15 01:13:23 +0000709 tkMessageBox.showerror("Unsupported type",
710 "%s is not a source module" % name, parent=self.text)
711 return
712 if f:
713 f.close()
714 if self.flist:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400715 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000716 else:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400717 self.io.loadfile(file_path)
718 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000719
720 def open_class_browser(self, event=None):
721 filename = self.io.filename
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400722 if not (self.__class__.__name__ == 'PyShellEditorWindow'
723 and filename):
724 filename = self.open_module()
725 if filename is None:
726 return
David Scherer7aced172000-08-15 01:13:23 +0000727 head, tail = os.path.split(filename)
728 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000729 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000730 ClassBrowser.ClassBrowser(self.flist, base, [head])
731
732 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000733 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000734 PathBrowser.PathBrowser(self.flist)
735
736 def gotoline(self, lineno):
737 if lineno is not None and lineno > 0:
738 self.text.mark_set("insert", "%d.0" % lineno)
739 self.text.tag_remove("sel", "1.0", "end")
740 self.text.tag_add("sel", "insert", "insert +1l")
741 self.center()
742
743 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000744 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000745 return True
David Scherer7aced172000-08-15 01:13:23 +0000746 base, ext = os.path.splitext(os.path.basename(filename))
747 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000748 return True
David Scherer7aced172000-08-15 01:13:23 +0000749 try:
750 f = open(filename)
751 line = f.readline()
752 f.close()
753 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000754 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000755 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000756
757 def close_hook(self):
758 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000759 self.flist.unregister_maybe_terminate(self)
760 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000761
762 def set_close_hook(self, close_hook):
763 self.close_hook = close_hook
764
765 def filename_change_hook(self):
766 if self.flist:
767 self.flist.filename_changed_edit(self)
768 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000769 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000770 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000771
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000772 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000773 if self.color:
774 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000775 if self.ispythonsource(self.io.filename):
776 self.color = self.ColorDelegator()
777 # can add more colorizers here...
778 if self.color:
779 self.per.removefilter(self.undo)
780 self.per.insertfilter(self.color)
781 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000782
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000783 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000784 if not self.color:
785 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000786 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000787 self.per.removefilter(self.color)
788 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000789
Steven M. Gavab77d3432002-03-02 07:16:21 +0000790 def ResetColorizer(self):
Terry Jan Reedy13755382014-10-09 18:44:26 -0400791 "Update the color theme"
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000792 # Called from self.filename_change_hook and from configDialog.py
793 self._rmcolorizer()
794 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000795 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000796 normal_colors = idleConf.GetHighlight(theme, 'normal')
797 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
798 select_colors = idleConf.GetHighlight(theme, 'hilite')
799 self.text.config(
800 foreground=normal_colors['foreground'],
801 background=normal_colors['background'],
802 insertbackground=cursor_color,
803 selectforeground=select_colors['foreground'],
804 selectbackground=select_colors['background'],
805 )
David Scherer7aced172000-08-15 01:13:23 +0000806
Steven M. Gavab1585412002-03-12 00:21:56 +0000807 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000808 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000809 # Called from configDialog.py
Terry Jan Reedy12352142015-08-01 18:57:27 -0400810
811 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000812
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000813 def RemoveKeybindings(self):
814 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000815 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000816 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000817 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000818 self.text.event_delete(event, *keylist)
819 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000820 xkeydefs = idleConf.GetExtensionBindings(extensionName)
821 if xkeydefs:
822 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000823 self.text.event_delete(event, *keylist)
824
825 def ApplyKeybindings(self):
826 "Update the keybindings after they are changed"
827 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000828 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000829 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000830 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000831 xkeydefs = idleConf.GetExtensionBindings(extensionName)
832 if xkeydefs:
833 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000834 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000835 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000836 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000837 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000838 for item in menu[1]:
839 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000840 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000841 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000842 menu = self.menudict[menubarItem]
Ned Deily14ef0c82013-07-20 14:38:24 -0700843 end = menu.index(END)
844 if end is None:
845 # Skip empty menus
846 continue
847 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000848 for index in range(0, end):
849 if menu.type(index) == 'command':
850 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000851 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000852 itemName = menu.entrycget(index, 'label')
853 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000854 if menubarItem in menuEventDict:
855 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000856 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000857 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000858 accel = get_accelerator(keydefs, event)
859 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000860
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000861 def set_notabs_indentwidth(self):
862 "Update the indentwidth if changed and not using tabs in this window"
863 # Called from configDialog.py
864 if not self.usetabs:
865 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
866 type='int')
867
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000868 def reset_help_menu_entries(self):
869 "Update the additional help entries on the Help menu"
870 help_list = idleConf.GetAllExtraHelpSourcesList()
871 helpmenu = self.menudict['help']
872 # first delete the extra help entries, if any
873 helpmenu_length = helpmenu.index(END)
874 if helpmenu_length > self.base_helpmenu_length:
875 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
876 # then rebuild them
877 if help_list:
878 helpmenu.add_separator()
879 for entry in help_list:
880 cmd = self.__extra_help_callback(entry[1])
881 helpmenu.add_command(label=entry[0], command=cmd)
882 # and update the menu dictionary
883 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000884
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000885 def __extra_help_callback(self, helpfile):
886 "Create a callback with the helpfile value frozen at definition time"
887 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000888 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000889 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000890 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000891 try:
892 os.startfile(helpfile)
893 except WindowsError as why:
894 tkMessageBox.showerror(title='Document Start Failure',
895 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000896 else:
897 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000898 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000899
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000900 def update_recent_files_list(self, new_file=None):
901 "Load and update the recent files list and menus"
902 rf_list = []
903 if os.path.exists(self.recent_files_path):
Terry Jan Reedyf9489432013-08-04 15:39:56 -0400904 with open(self.recent_files_path, 'r') 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 Deily40ad0412011-12-14 14:57:43 -0800920 with open(self.recent_files_path, 'w') as rf_file:
921 rf_file.writelines(rf_list)
922 except IOError as err:
923 if not getattr(self.root, "recentfilelist_error_displayed", False):
924 self.root.recentfilelist_error_displayed = True
925 tkMessageBox.showerror(title='IDLE Error',
926 message='Unable to update Recent Files list:\n%s'
927 % str(err),
928 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000929 # for each edit window instance, construct the recent files menu
930 for instance in self.top.instance_dict.keys():
931 menu = instance.recent_files_menu
Ned Deily71489842012-05-29 10:42:34 -0700932 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000933 for i, file_name in enumerate(rf_list):
934 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000935 # make unicode string to display non-ASCII chars correctly
936 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000937 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000938 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000939 command=callback,
940 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000941
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000942 def __recent_file_callback(self, file_name):
943 def open_recent_file(fn_closure=file_name):
944 self.io.open(editFile=fn_closure)
945 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000946
David Scherer7aced172000-08-15 01:13:23 +0000947 def saved_change_hook(self):
948 short = self.short_title()
949 long = self.long_title()
950 if short and long:
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400951 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000952 elif short:
953 title = short
954 elif long:
955 title = long
956 else:
957 title = "Untitled"
958 icon = short or long or title
959 if not self.get_saved():
960 title = "*%s*" % title
961 icon = "*%s" % icon
962 self.top.wm_title(title)
963 self.top.wm_iconname(icon)
964
965 def get_saved(self):
966 return self.undo.get_saved()
967
968 def set_saved(self, flag):
969 self.undo.set_saved(flag)
970
971 def reset_undo(self):
972 self.undo.reset_undo()
973
974 def short_title(self):
975 filename = self.io.filename
976 if filename:
977 filename = os.path.basename(filename)
Terry Jan Reedy59243652014-01-23 00:36:37 -0500978 else:
979 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000980 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400981 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000982
983 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000984 # return unicode string to display non-ASCII chars correctly
985 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000986
987 def center_insert_event(self, event):
988 self.center()
989
990 def center(self, mark="insert"):
991 text = self.text
992 top, bot = self.getwindowlines()
993 lineno = self.getlineno(mark)
994 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000995 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000996 text.yview(float(newtop))
997
998 def getwindowlines(self):
999 text = self.text
1000 top = self.getlineno("@0,0")
1001 bot = self.getlineno("@0,65535")
1002 if top == bot and text.winfo_height() == 1:
1003 # Geometry manager hasn't run yet
1004 height = int(text['height'])
1005 bot = top + height - 1
1006 return top, bot
1007
1008 def getlineno(self, mark="insert"):
1009 text = self.text
1010 return int(float(text.index(mark)))
1011
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001012 def get_geometry(self):
1013 "Return (width, height, x, y)"
1014 geom = self.top.wm_geometry()
1015 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
1016 tuple = (map(int, m.groups()))
1017 return tuple
1018
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()
Matthias Klosea398e2d2007-01-11 11:44:04 +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()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001042 self.io.close()
1043 self.io = None
1044 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001045 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +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
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +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):
1062 for ins in self.extensions.values():
1063 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:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001072 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +00001073 import traceback
1074 traceback.print_exc()
1075
1076 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001077 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001078
1079 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001080 try:
1081 mod = __import__(name, globals(), locals(), [])
1082 except ImportError:
1083 print "\nFailed to import extension: ", name
1084 return
David Scherer7aced172000-08-15 01:13:23 +00001085 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001086 keydefs = idleConf.GetExtensionBindings(name)
1087 if hasattr(cls, "menudefs"):
1088 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001089 ins = cls(self)
1090 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001091 if keydefs:
1092 self.apply_bindings(keydefs)
1093 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001094 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001095 while methodname[:1] == '<':
1096 methodname = methodname[1:]
1097 while methodname[-1:] == '>':
1098 methodname = methodname[:-1]
1099 methodname = methodname + "_event"
1100 if hasattr(ins, methodname):
1101 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001102
1103 def apply_bindings(self, keydefs=None):
1104 if keydefs is None:
1105 keydefs = self.Bindings.default_keydefs
1106 text = self.text
1107 text.keydefs = keydefs
1108 for event, keylist in keydefs.items():
1109 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001110 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001111
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001112 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001113 """Add appropriate entries to the menus and submenus
1114
1115 Menus that are absent or None in self.menudict are ignored.
1116 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001117 if menudefs is None:
1118 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001119 if keydefs is None:
1120 keydefs = self.Bindings.default_keydefs
1121 menudict = self.menudict
1122 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001123 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001124 menu = menudict.get(mname)
1125 if not menu:
1126 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001127 for entry in entrylist:
1128 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001129 menu.add_separator()
1130 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001131 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001132 checkbutton = (label[:1] == '!')
1133 if checkbutton:
1134 label = label[1:]
1135 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001136 accelerator = get_accelerator(keydefs, eventname)
1137 def command(text=text, eventname=eventname):
1138 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001139 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001140 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001141 menu.add_checkbutton(label=label, underline=underline,
1142 command=command, accelerator=accelerator,
1143 variable=var)
1144 else:
1145 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001146 command=command,
1147 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001148
1149 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001150 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001151 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001152 value = var.get()
1153 return value
1154 else:
1155 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001156
1157 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001158 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001159 if var:
1160 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001161 else:
1162 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001163
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001164 def get_var_obj(self, name, vartype=None):
1165 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001166 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001167 # create a Tkinter variable object with self.text as master:
1168 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001169 return var
1170
1171 # Tk implementations of "virtual text methods" -- each platform
1172 # reusing IDLE's support code needs to define these for its GUI's
1173 # flavor of widget.
1174
1175 # Is character at text_index in a Python string? Return 0 for
1176 # "guaranteed no", true for anything else. This info is expensive
1177 # to compute ab initio, but is probably already known by the
1178 # platform's colorizer.
1179
1180 def is_char_in_string(self, text_index):
1181 if self.color:
1182 # Return true iff colorizer hasn't (re)gotten this far
1183 # yet, or the character is tagged as being in a string
1184 return self.text.tag_prevrange("TODO", text_index) or \
1185 "STRING" in self.text.tag_names(text_index)
1186 else:
1187 # The colorizer is missing: assume the worst
1188 return 1
1189
1190 # If a selection is defined in the text widget, return (start,
1191 # end) as Tkinter text indices, otherwise return (None, None)
1192 def get_selection_indices(self):
1193 try:
1194 first = self.text.index("sel.first")
1195 last = self.text.index("sel.last")
1196 return first, last
1197 except TclError:
1198 return None, None
1199
1200 # Return the text widget's current view of what a tab stop means
1201 # (equivalent width in spaces).
1202
1203 def get_tabwidth(self):
1204 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1205 return int(current)
1206
1207 # Set the text widget's current view of what a tab stop means.
1208
1209 def set_tabwidth(self, newtabwidth):
1210 text = self.text
1211 if self.get_tabwidth() != newtabwidth:
1212 pixels = text.tk.call("font", "measure", text["font"],
1213 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001214 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001215 text.configure(tabs=pixels)
1216
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001217 # If ispythonsource and guess are true, guess a good value for
1218 # indentwidth based on file content (if possible), and if
1219 # indentwidth != tabwidth set usetabs false.
1220 # In any case, adjust the Text widget's view of what a tab
1221 # character means.
1222
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001223 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001224 if guess and ispythonsource:
1225 i = self.guess_indent()
1226 if 2 <= i <= 8:
1227 self.indentwidth = i
1228 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001229 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001230 self.set_tabwidth(self.tabwidth)
1231
1232 def smart_backspace_event(self, event):
1233 text = self.text
1234 first, last = self.get_selection_indices()
1235 if first and last:
1236 text.delete(first, last)
1237 text.mark_set("insert", first)
1238 return "break"
1239 # Delete whitespace left, until hitting a real char or closest
1240 # preceding virtual tab stop.
1241 chars = text.get("insert linestart", "insert")
1242 if chars == '':
1243 if text.compare("insert", ">", "1.0"):
1244 # easy: delete preceding newline
1245 text.delete("insert-1c")
1246 else:
1247 text.bell() # at start of buffer
1248 return "break"
1249 if chars[-1] not in " \t":
1250 # easy: delete preceding real char
1251 text.delete("insert-1c")
1252 return "break"
1253 # Ick. It may require *inserting* spaces if we back up over a
1254 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001255 tabwidth = self.tabwidth
1256 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001257 assert have > 0
1258 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001259 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001260 if self.context_use_ps1:
1261 last_line_of_prompt = sys.ps1.split('\n')[-1]
1262 else:
1263 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001264 ncharsdeleted = 0
1265 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001266 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001267 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001268 chars = chars[:-1]
1269 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001270 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001271 if have <= want or chars[-1] not in " \t":
1272 break
1273 text.undo_block_start()
1274 text.delete("insert-%dc" % ncharsdeleted, "insert")
1275 if have < want:
1276 text.insert("insert", ' ' * (want - have))
1277 text.undo_block_stop()
1278 return "break"
1279
1280 def smart_indent_event(self, event):
1281 # if intraline selection:
1282 # delete it
1283 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001284 # do indent-region
1285 # else:
1286 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001287 text = self.text
1288 first, last = self.get_selection_indices()
1289 text.undo_block_start()
1290 try:
1291 if first and last:
1292 if index2line(first) != index2line(last):
1293 return self.indent_region_event(event)
1294 text.delete(first, last)
1295 text.mark_set("insert", first)
1296 prefix = text.get("insert linestart", "insert")
1297 raw, effective = classifyws(prefix, self.tabwidth)
1298 if raw == len(prefix):
1299 # only whitespace to the left
1300 self.reindent_to(effective + self.indentwidth)
1301 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001302 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001303 if self.usetabs:
1304 pad = '\t'
1305 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001306 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001307 n = self.indentwidth
1308 pad = ' ' * (n - effective % n)
1309 text.insert("insert", pad)
1310 text.see("insert")
1311 return "break"
1312 finally:
1313 text.undo_block_stop()
1314
1315 def newline_and_indent_event(self, event):
1316 text = self.text
1317 first, last = self.get_selection_indices()
1318 text.undo_block_start()
1319 try:
1320 if first and last:
1321 text.delete(first, last)
1322 text.mark_set("insert", first)
1323 line = text.get("insert linestart", "insert")
1324 i, n = 0, len(line)
1325 while i < n and line[i] in " \t":
1326 i = i+1
1327 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001328 # the cursor is in or at leading indentation in a continuation
1329 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001330 text.insert("insert linestart", '\n')
1331 return "break"
1332 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001333 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001334 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001335 last_line_of_prompt = sys.ps1.split('\n')[-1]
1336 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001337 line = line[:-1]
1338 i = i+1
1339 if i:
1340 text.delete("insert - %d chars" % i, "insert")
1341 # strip whitespace after insert point
1342 while text.get("insert") in " \t":
1343 text.delete("insert")
1344 # start new line
1345 text.insert("insert", '\n')
1346
1347 # adjust indentation for continuations and block
1348 # open/close first need to find the last stmt
1349 lno = index2line(text.index('insert'))
1350 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001351 if not self.context_use_ps1:
1352 for context in self.num_context_lines:
1353 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001354 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001355 rawtext = text.get(startatindex, "insert")
1356 y.set_str(rawtext)
1357 bod = y.find_good_parse_start(
1358 self.context_use_ps1,
1359 self._build_char_in_string_func(startatindex))
1360 if bod is not None or startat == 1:
1361 break
1362 y.set_lo(bod or 0)
1363 else:
1364 r = text.tag_prevrange("console", "insert")
1365 if r:
1366 startatindex = r[1]
1367 else:
1368 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001369 rawtext = text.get(startatindex, "insert")
1370 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001371 y.set_lo(0)
1372
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001373 c = y.get_continuation_type()
1374 if c != PyParse.C_NONE:
1375 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001376 if c == PyParse.C_STRING_FIRST_LINE:
1377 # after the first line of a string; do not indent at all
1378 pass
1379 elif c == PyParse.C_STRING_NEXT_LINES:
1380 # inside a string which started before this line;
1381 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001382 text.insert("insert", indent)
1383 elif c == PyParse.C_BRACKET:
1384 # line up with the first (if any) element of the
1385 # last open bracket structure; else indent one
1386 # level beyond the indent of the line with the
1387 # last open bracket
1388 self.reindent_to(y.compute_bracket_indent())
1389 elif c == PyParse.C_BACKSLASH:
1390 # if more than one line in this stmt already, just
1391 # mimic the current indent; else if initial line
1392 # has a start on an assignment stmt, indent to
1393 # beyond leftmost =; else to beyond first chunk of
1394 # non-whitespace on initial line
1395 if y.get_num_lines_in_stmt() > 1:
1396 text.insert("insert", indent)
1397 else:
1398 self.reindent_to(y.compute_backslash_indent())
1399 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001400 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001401 return "break"
1402
1403 # This line starts a brand new stmt; indent relative to
1404 # indentation of initial line of closest preceding
1405 # interesting stmt.
1406 indent = y.get_base_indent_string()
1407 text.insert("insert", indent)
1408 if y.is_block_opener():
1409 self.smart_indent_event(event)
1410 elif indent and y.is_block_closer():
1411 self.smart_backspace_event(event)
1412 return "break"
1413 finally:
1414 text.see("insert")
1415 text.undo_block_stop()
1416
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001417 # Our editwin provides a is_char_in_string function that works
1418 # with a Tk text index, but PyParse only knows about offsets into
1419 # a string. This builds a function for PyParse that accepts an
1420 # offset.
1421
1422 def _build_char_in_string_func(self, startindex):
1423 def inner(offset, _startindex=startindex,
1424 _icis=self.is_char_in_string):
1425 return _icis(_startindex + "+%dc" % offset)
1426 return inner
1427
1428 def indent_region_event(self, event):
1429 head, tail, chars, lines = self.get_region()
1430 for pos in range(len(lines)):
1431 line = lines[pos]
1432 if line:
1433 raw, effective = classifyws(line, self.tabwidth)
1434 effective = effective + self.indentwidth
1435 lines[pos] = self._make_blanks(effective) + line[raw:]
1436 self.set_region(head, tail, chars, lines)
1437 return "break"
1438
1439 def dedent_region_event(self, event):
1440 head, tail, chars, lines = self.get_region()
1441 for pos in range(len(lines)):
1442 line = lines[pos]
1443 if line:
1444 raw, effective = classifyws(line, self.tabwidth)
1445 effective = max(effective - self.indentwidth, 0)
1446 lines[pos] = self._make_blanks(effective) + line[raw:]
1447 self.set_region(head, tail, chars, lines)
1448 return "break"
1449
1450 def comment_region_event(self, event):
1451 head, tail, chars, lines = self.get_region()
1452 for pos in range(len(lines) - 1):
1453 line = lines[pos]
1454 lines[pos] = '##' + line
1455 self.set_region(head, tail, chars, lines)
1456
1457 def uncomment_region_event(self, event):
1458 head, tail, chars, lines = self.get_region()
1459 for pos in range(len(lines)):
1460 line = lines[pos]
1461 if not line:
1462 continue
1463 if line[:2] == '##':
1464 line = line[2:]
1465 elif line[:1] == '#':
1466 line = line[1:]
1467 lines[pos] = line
1468 self.set_region(head, tail, chars, lines)
1469
1470 def tabify_region_event(self, event):
1471 head, tail, chars, lines = self.get_region()
1472 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001473 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001474 for pos in range(len(lines)):
1475 line = lines[pos]
1476 if line:
1477 raw, effective = classifyws(line, tabwidth)
1478 ntabs, nspaces = divmod(effective, tabwidth)
1479 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1480 self.set_region(head, tail, chars, lines)
1481
1482 def untabify_region_event(self, event):
1483 head, tail, chars, lines = self.get_region()
1484 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001485 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001486 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001487 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001488 self.set_region(head, tail, chars, lines)
1489
1490 def toggle_tabs_event(self, event):
1491 if self.askyesno(
1492 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001493 "Turn tabs " + ("on", "off")[self.usetabs] +
1494 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001495 ("will be", "remains at")[self.usetabs] + " 8." +
1496 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001497 parent=self.text):
1498 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001499 # Try to prevent inconsistent indentation.
1500 # User must change indent width manually after using tabs.
1501 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001502 return "break"
1503
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001504 # XXX this isn't bound to anything -- see tabwidth comments
1505## def change_tabwidth_event(self, event):
1506## new = self._asktabwidth()
1507## if new != self.tabwidth:
1508## self.tabwidth = new
1509## self.set_indentation_params(0, guess=0)
1510## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001511
1512 def change_indentwidth_event(self, event):
1513 new = self.askinteger(
1514 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001515 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001516 parent=self.text,
1517 initialvalue=self.indentwidth,
1518 minvalue=2,
1519 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001520 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001521 self.indentwidth = new
1522 return "break"
1523
1524 def get_region(self):
1525 text = self.text
1526 first, last = self.get_selection_indices()
1527 if first and last:
1528 head = text.index(first + " linestart")
1529 tail = text.index(last + "-1c lineend +1c")
1530 else:
1531 head = text.index("insert linestart")
1532 tail = text.index("insert lineend +1c")
1533 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001534 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001535 return head, tail, chars, lines
1536
1537 def set_region(self, head, tail, chars, lines):
1538 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001539 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001540 if newchars == chars:
1541 text.bell()
1542 return
1543 text.tag_remove("sel", "1.0", "end")
1544 text.mark_set("insert", head)
1545 text.undo_block_start()
1546 text.delete(head, tail)
1547 text.insert(head, newchars)
1548 text.undo_block_stop()
1549 text.tag_add("sel", head, "insert")
1550
1551 # Make string that displays as n leading blanks.
1552
1553 def _make_blanks(self, n):
1554 if self.usetabs:
1555 ntabs, nspaces = divmod(n, self.tabwidth)
1556 return '\t' * ntabs + ' ' * nspaces
1557 else:
1558 return ' ' * n
1559
1560 # Delete from beginning of line to insert point, then reinsert
1561 # column logical (meaning use tabs if appropriate) spaces.
1562
1563 def reindent_to(self, column):
1564 text = self.text
1565 text.undo_block_start()
1566 if text.compare("insert linestart", "!=", "insert"):
1567 text.delete("insert linestart", "insert")
1568 if column:
1569 text.insert("insert", self._make_blanks(column))
1570 text.undo_block_stop()
1571
1572 def _asktabwidth(self):
1573 return self.askinteger(
1574 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001575 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001576 parent=self.text,
1577 initialvalue=self.indentwidth,
1578 minvalue=2,
Roger Serwy75b249c2013-04-06 20:26:53 -05001579 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001580
1581 # Guess indentwidth from text content.
1582 # Return guessed indentwidth. This should not be believed unless
1583 # it's in a reasonable range (e.g., it will be 0 if no indented
1584 # blocks are found).
1585
1586 def guess_indent(self):
1587 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1588 if opener and indented:
1589 raw, indentsmall = classifyws(opener, self.tabwidth)
1590 raw, indentlarge = classifyws(indented, self.tabwidth)
1591 else:
1592 indentsmall = indentlarge = 0
1593 return indentlarge - indentsmall
1594
1595# "line.col" -> line, as an int
1596def index2line(index):
1597 return int(float(index))
1598
1599# Look at the leading whitespace in s.
1600# Return pair (# of leading ws characters,
1601# effective # of leading blanks after expanding
1602# tabs to width tabwidth)
1603
1604def classifyws(s, tabwidth):
1605 raw = effective = 0
1606 for ch in s:
1607 if ch == ' ':
1608 raw = raw + 1
1609 effective = effective + 1
1610 elif ch == '\t':
1611 raw = raw + 1
1612 effective = (effective // tabwidth + 1) * tabwidth
1613 else:
1614 break
1615 return raw, effective
1616
1617import tokenize
1618_tokenize = tokenize
1619del tokenize
1620
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001621class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001622
1623 # .run() chews over the Text widget, looking for a block opener
1624 # and the stmt following it. Returns a pair,
1625 # (line containing block opener, line containing stmt)
1626 # Either or both may be None.
1627
1628 def __init__(self, text, tabwidth):
1629 self.text = text
1630 self.tabwidth = tabwidth
1631 self.i = self.finished = 0
1632 self.blkopenline = self.indentedline = None
1633
1634 def readline(self):
1635 if self.finished:
1636 return ""
1637 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001638 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001639 if self.text.compare(mark, ">=", "end"):
1640 return ""
1641 return self.text.get(mark, mark + " lineend+1c")
1642
1643 def tokeneater(self, type, token, start, end, line,
1644 INDENT=_tokenize.INDENT,
1645 NAME=_tokenize.NAME,
1646 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1647 if self.finished:
1648 pass
1649 elif type == NAME and token in OPENERS:
1650 self.blkopenline = line
1651 elif type == INDENT and self.blkopenline:
1652 self.indentedline = line
1653 self.finished = 1
1654
1655 def run(self):
1656 save_tabsize = _tokenize.tabsize
1657 _tokenize.tabsize = self.tabwidth
1658 try:
1659 try:
1660 _tokenize.tokenize(self.readline, self.tokeneater)
Serhiy Storchaka61006a22012-12-27 21:34:23 +02001661 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001662 # since we cut off the tokenizer early, we can trigger
1663 # spurious errors
1664 pass
1665 finally:
1666 _tokenize.tabsize = save_tabsize
1667 return self.blkopenline, self.indentedline
1668
1669### end autoindent code ###
1670
David Scherer7aced172000-08-15 01:13:23 +00001671def prepstr(s):
1672 # Helper to extract the underscore from a string, e.g.
1673 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001674 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001675 if i >= 0:
1676 s = s[:i] + s[i+1:]
1677 return i, s
1678
1679
1680keynames = {
1681 'bracketleft': '[',
1682 'bracketright': ']',
1683 'slash': '/',
1684}
1685
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001686def get_accelerator(keydefs, eventname):
1687 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001688 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1689 # if not keylist:
Ned Deily57847df2014-03-27 20:47:04 -07001690 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily60651532011-01-31 00:52:49 +00001691 "<<open-module>>",
1692 "<<goto-line>>",
1693 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001694 return ""
1695 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001696 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001697 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1698 s = re.sub("Key-", "", s)
1699 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1700 s = re.sub("Control-", "Ctrl-", s)
1701 s = re.sub("-", "+", s)
1702 s = re.sub("><", " ", s)
1703 s = re.sub("<", "", s)
1704 s = re.sub(">", "", s)
1705 return s
1706
1707
1708def fixwordbreaks(root):
1709 # Make sure that Tk's double-click and next/previous word
1710 # operations use our definition of a word (i.e. an identifier)
1711 tk = root.tk
1712 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1713 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1714 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1715
1716
Terry Jan Reedycf834762014-10-17 01:31:29 -04001717def _editor_window(parent): # htest #
1718 # error if close master window first - timer event, after script
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001719 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001720 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001721 if sys.argv[1:]:
1722 filename = sys.argv[1]
1723 else:
1724 filename = None
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001725 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001726 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001727 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycf834762014-10-17 01:31:29 -04001728 # Does not stop error, neither does following
1729 # edit.text.bind("<<close-window>>", edit.close_event)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001730
David Scherer7aced172000-08-15 01:13:23 +00001731
1732if __name__ == '__main__':
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001733 from idlelib.idle_test.htest import run
Terry Jan Reedy70e763c2015-09-20 19:55:44 -04001734 run(_editor_window)