blob: f9f53376abf5662a29e4256e0fe61a70d2d26ca0 [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
Terry Jan Reedy8b7122c2015-09-20 23:05:21 -0400117helpDialog = HelpDialog() # singleton instance, no longer used
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500118
119
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +0000120class EditorWindow(object):
Florent Xiclunad630c042010-04-02 07:24:52 +0000121 from idlelib.Percolator import Percolator
122 from idlelib.ColorDelegator import ColorDelegator
123 from idlelib.UndoDelegator import UndoDelegator
124 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
125 from idlelib import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +0000126 from Tkinter import Toplevel
Florent Xiclunad630c042010-04-02 07:24:52 +0000127 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +0000128
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000129 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000130
131 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000132 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +0000133 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000134 if sys.platform.count('linux'):
135 # look for html docs in a couple of standard places
136 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
137 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
138 dochome = '/var/www/html/python/index.html'
139 else:
140 basepath = '/usr/share/doc/' # standard location
141 dochome = os.path.join(basepath, pyver,
142 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000143 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +0000144 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +0000145 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000146 if os.path.isfile(chmfile):
147 dochome = chmfile
Ned Deily57847df2014-03-27 20:47:04 -0700148 elif sys.platform == 'darwin':
149 # documentation may be stored inside a python framework
Ronald Oussoren19302d92006-06-11 14:33:36 +0000150 dochome = os.path.join(sys.prefix,
151 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000152 dochome = os.path.normpath(dochome)
153 if os.path.isfile(dochome):
154 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +0000155 if sys.platform == 'darwin':
156 # Safari requires real file:-URLs
157 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000158 else:
Terry Jan Reedyb17c1e02014-09-19 22:54:09 -0400159 EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
David Scherer7aced172000-08-15 01:13:23 +0000160 self.flist = flist
161 root = root or flist.root
162 self.root = root
Kurt B. Kaiserb3c4d162006-07-24 17:13:23 +0000163 try:
164 sys.ps1
165 except AttributeError:
166 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000167 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000168 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000169 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000170 self.tkinter_vars = flist.vars
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200171 #self.top.instance_dict makes flist.inversedict available to
172 #configDialog.py so it can access all EditorWindow instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000173 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000174 else:
175 self.tkinter_vars = {} # keys: Tkinter event names
176 # values: Tkinter variable instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000177 self.top.instance_dict = {}
178 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000179 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000180 self.text_frame = text_frame = Frame(top)
Martin v. Löwis4ebbefe2006-11-22 08:50:02 +0000181 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200182 self.width = idleConf.GetOption('main','EditorWindow','width', type='int')
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000183 text_options = {
184 'name': 'text',
185 'padx': 5,
186 'wrap': 'none',
187 'width': self.width,
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200188 'height': idleConf.GetOption('main', 'EditorWindow', 'height', type='int')}
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000189 if TkVersion >= 8.5:
190 # Starting with tk 8.5 we have to set the new tabstyle option
191 # to 'wordprocessor' to achieve the same display of tabs as in
192 # older tk versions.
193 text_options['tabstyle'] = 'wordprocessor'
194 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000195 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000196
197 self.createmenubar()
198 self.apply_bindings()
199
200 self.top.protocol("WM_DELETE_WINDOW", self.close)
201 self.top.bind("<<close-window>>", self.close_event)
Ned Deily57847df2014-03-27 20:47:04 -0700202 if macosxSupport.isAquaTk():
Ronald Oussoren17db4952006-07-23 09:41:09 +0000203 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000204 text.bind('<<close-window>>', self.close_event)
Terry Jan Reedyca33d562015-09-22 21:10:22 -0400205 # Some OS X systems have only one mouse button, so use
206 # control-click for popup context menus there. For two
207 # buttons, AquaTk defines <2> as the right button, not <3>.
R. David Murray3f752ab2010-12-18 17:22:18 +0000208 text.bind("<Control-Button-1>",self.right_menu_event)
Terry Jan Reedyca33d562015-09-22 21:10:22 -0400209 text.bind("<2>", self.right_menu_event)
R. David Murray3f752ab2010-12-18 17:22:18 +0000210 else:
Terry Jan Reedyca33d562015-09-22 21:10:22 -0400211 # Elsewhere, use right-click for popup menus.
R. David Murray3f752ab2010-12-18 17:22:18 +0000212 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000213 text.bind("<<cut>>", self.cut)
214 text.bind("<<copy>>", self.copy)
215 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000216 text.bind("<<center-insert>>", self.center_insert_event)
217 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000218 text.bind("<<python-docs>>", self.python_docs)
219 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000220 text.bind("<<open-config-dialog>>", self.config_dialog)
Terry Jan Reedy7a162072014-10-22 20:15:12 -0400221 text.bind("<<open-config-extensions-dialog>>",
222 self.config_extensions_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000223 text.bind("<<open-module>>", self.open_module)
224 text.bind("<<do-nothing>>", lambda event: "break")
225 text.bind("<<select-all>>", self.select_all)
226 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000227 text.bind("<<find>>", self.find_event)
228 text.bind("<<find-again>>", self.find_again_event)
229 text.bind("<<find-in-files>>", self.find_in_files_event)
230 text.bind("<<find-selection>>", self.find_selection_event)
231 text.bind("<<replace>>", self.replace_event)
232 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000233 text.bind("<<smart-backspace>>",self.smart_backspace_event)
234 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
235 text.bind("<<smart-indent>>",self.smart_indent_event)
236 text.bind("<<indent-region>>",self.indent_region_event)
237 text.bind("<<dedent-region>>",self.dedent_region_event)
238 text.bind("<<comment-region>>",self.comment_region_event)
239 text.bind("<<uncomment-region>>",self.uncomment_region_event)
240 text.bind("<<tabify-region>>",self.tabify_region_event)
241 text.bind("<<untabify-region>>",self.untabify_region_event)
242 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
243 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000244 text.bind("<Left>", self.move_at_edge_if_selection(0))
245 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000246 text.bind("<<del-word-left>>", self.del_word_left)
247 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000248 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000249
David Scherer7aced172000-08-15 01:13:23 +0000250 if flist:
251 flist.inversedict[self] = key
252 if key:
253 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000254 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000255 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
256 text.bind("<<open-class-browser>>", self.open_class_browser)
257 text.bind("<<open-path-browser>>", self.open_path_browser)
258
Steven M. Gava898a3652001-10-07 11:10:44 +0000259 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000260 vbar['command'] = text.yview
261 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000262 text['yscrollcommand'] = vbar.set
Terry Jan Reedy12352142015-08-01 18:57:27 -0400263 text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
David Scherer7aced172000-08-15 01:13:23 +0000264 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
265 text.pack(side=TOP, fill=BOTH, expand=1)
266 text.focus_set()
267
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000268 # usetabs true -> literal tab characters are used by indent and
269 # dedent cmds, possibly mixed with spaces if
270 # indentwidth is not a multiple of tabwidth,
271 # which will cause Tabnanny to nag!
272 # false -> tab characters are converted to spaces by indent
273 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000274 # Although use-spaces=0 can be configured manually in config-main.def,
275 # configuration of tabs v. spaces is not supported in the configuration
276 # dialog. IDLE promotes the preferred Python indentation: use spaces!
277 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
278 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000279
280 # tabwidth is the display width of a literal tab character.
281 # CAUTION: telling Tk to use anything other than its default
282 # tab setting causes it to use an entirely different tabbing algorithm,
283 # treating tab stops as fixed distances from the left margin.
284 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000285 self.tabwidth = 8 # must remain 8 until Tk is fixed.
286
287 # indentwidth is the number of screen characters per indent level.
288 # The recommended Python indentation is four spaces.
289 self.indentwidth = self.tabwidth
290 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000291
292 # If context_use_ps1 is true, parsing searches back for a ps1 line;
293 # else searches for a popular (if, def, ...) Python stmt.
294 self.context_use_ps1 = False
295
296 # When searching backwards for a reliable place to begin parsing,
297 # first start num_context_lines[0] lines back, then
298 # num_context_lines[1] lines back if that didn't work, and so on.
299 # The last value should be huge (larger than the # of lines in a
300 # conceivable file).
301 # Making the initial values larger slows things down more often.
302 self.num_context_lines = 50, 500, 5000000
303
David Scherer7aced172000-08-15 01:13:23 +0000304 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000305
306 self.undo = undo = self.UndoDelegator()
307 per.insertfilter(undo)
308 text.undo_block_start = undo.undo_block_start
309 text.undo_block_stop = undo.undo_block_stop
310 undo.set_saved_change_hook(self.saved_change_hook)
311
312 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000313 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000314 io.set_filename_change_hook(self.filename_change_hook)
315
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000316 # Create the recent files submenu
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400317 self.recent_files_menu = Menu(self.menubar, tearoff=0)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000318 self.menudict['file'].insert_cascade(3, label='Recent Files',
319 underline=0,
320 menu=self.recent_files_menu)
321 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000322
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000323 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000324 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000325 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000326 io.loadfile(filename)
327 else:
328 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000329 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000330 self.saved_change_hook()
331
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000332 self.set_indentation_params(self.ispythonsource(filename))
333
David Scherer7aced172000-08-15 01:13:23 +0000334 self.load_extensions()
335
336 menu = self.menudict.get('windows')
337 if menu:
338 end = menu.index("end")
339 if end is None:
340 end = -1
341 if end >= 0:
342 menu.add_separator()
343 end = end + 1
344 self.wmenu_end = end
345 WindowList.register_callback(self.postwindowsmenu)
346
347 # Some abstractions so IDLE extensions are cross-IDE
348 self.askyesno = tkMessageBox.askyesno
349 self.askinteger = tkSimpleDialog.askinteger
350 self.showerror = tkMessageBox.showerror
351
Roger Serwy02c0ed02013-05-20 22:13:39 -0500352 self._highlight_workaround() # Fix selection tags on Windows
353
354 def _highlight_workaround(self):
355 # On Windows, Tk removes painting of the selection
356 # tags which is different behavior than on Linux and Mac.
357 # See issue14146 for more information.
358 if not sys.platform.startswith('win'):
359 return
360
361 text = self.text
362 text.event_add("<<Highlight-FocusOut>>", "<FocusOut>")
363 text.event_add("<<Highlight-FocusIn>>", "<FocusIn>")
364 def highlight_fix(focus):
365 sel_range = text.tag_ranges("sel")
366 if sel_range:
367 if focus == 'out':
368 HILITE_CONFIG = idleConf.GetHighlight(
369 idleConf.CurrentTheme(), 'hilite')
370 text.tag_config("sel_fix", HILITE_CONFIG)
371 text.tag_raise("sel_fix")
372 text.tag_add("sel_fix", *sel_range)
373 elif focus == 'in':
374 text.tag_remove("sel_fix", "1.0", "end")
375
376 text.bind("<<Highlight-FocusOut>>",
377 lambda ev: highlight_fix("out"))
378 text.bind("<<Highlight-FocusIn>>",
379 lambda ev: highlight_fix("in"))
380
381
Martin v. Löwis307021f2005-11-27 16:59:04 +0000382 def _filename_to_unicode(self, filename):
383 """convert filename to unicode in order to display it in Tk"""
384 if isinstance(filename, unicode) or not filename:
385 return filename
386 else:
387 try:
388 return filename.decode(self.filesystemencoding)
389 except UnicodeDecodeError:
390 # XXX
391 try:
392 return filename.decode(self.encoding)
393 except UnicodeDecodeError:
394 # byte-to-byte conversion
395 return filename.decode('iso8859-1')
396
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000397 def new_callback(self, event):
398 dirname, basename = self.io.defaultfilename()
399 self.flist.new(dirname)
400 return "break"
401
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000402 def home_callback(self, event):
Kurt B. Kaiser020d3d92011-03-17 00:05:38 -0400403 if (event.state & 4) != 0 and event.keysym == "Home":
404 # state&4==Control. If <Control-Home>, use the Tk binding.
405 return
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000406 if self.text.index("iomark") and \
407 self.text.compare("iomark", "<=", "insert lineend") and \
408 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400409 # In Shell on input line, go to just after prompt
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000410 insertpt = int(self.text.index("iomark").split(".")[1])
411 else:
412 line = self.text.get("insert linestart", "insert lineend")
413 for insertpt in xrange(len(line)):
414 if line[insertpt] not in (' ','\t'):
415 break
416 else:
417 insertpt=len(line)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000418 lineat = int(self.text.index("insert").split('.')[1])
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000419 if insertpt == lineat:
420 insertpt = 0
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000421 dest = "insert linestart+"+str(insertpt)+"c"
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000422 if (event.state&1) == 0:
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400423 # shift was not pressed
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000424 self.text.tag_remove("sel", "1.0", "end")
425 else:
426 if not self.text.index("sel.first"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400427 self.text.mark_set("my_anchor", "insert") # there was no previous selection
428 else:
429 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
430 self.text.mark_set("my_anchor", "sel.first") # extend back
431 else:
432 self.text.mark_set("my_anchor", "sel.last") # extend forward
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000433 first = self.text.index(dest)
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400434 last = self.text.index("my_anchor")
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000435 if self.text.compare(first,">",last):
436 first,last = last,first
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000437 self.text.tag_remove("sel", "1.0", "end")
438 self.text.tag_add("sel", first, last)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000439 self.text.mark_set("insert", dest)
440 self.text.see("insert")
441 return "break"
442
David Scherer7aced172000-08-15 01:13:23 +0000443 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000444 self.status_bar = self.MultiStatusBar(self.top)
Ned Deily57847df2014-03-27 20:47:04 -0700445 if sys.platform == "darwin":
Ronald Oussoren19302d92006-06-11 14:33:36 +0000446 # Insert some padding to avoid obscuring some of the statusbar
447 # by the resize widget.
448 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000449 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
450 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
451 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000452 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
453 self.text.event_add("<<set-line-and-column>>",
454 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000455 self.text.after_idle(self.set_line_and_column)
456
457 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000458 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000459 self.status_bar.set_label('column', 'Col: %s' % column)
460 self.status_bar.set_label('line', 'Ln: %s' % line)
461
David Scherer7aced172000-08-15 01:13:23 +0000462 menu_specs = [
463 ("file", "_File"),
464 ("edit", "_Edit"),
465 ("format", "F_ormat"),
466 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000467 ("options", "_Options"),
Ned Deilyb5daa3d2015-01-17 21:03:41 -0800468 ("windows", "_Window"),
David Scherer7aced172000-08-15 01:13:23 +0000469 ("help", "_Help"),
470 ]
471
Ronald Oussoren19302d92006-06-11 14:33:36 +0000472
David Scherer7aced172000-08-15 01:13:23 +0000473 def createmenubar(self):
474 mbar = self.menubar
475 self.menudict = menudict = {}
476 for name, label in self.menu_specs:
477 underline, label = prepstr(label)
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400478 menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
David Scherer7aced172000-08-15 01:13:23 +0000479 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000480
Ned Deily57847df2014-03-27 20:47:04 -0700481 if macosxSupport.isCarbonTk():
Ronald Oussoren19302d92006-06-11 14:33:36 +0000482 # Insert the application menu
Terry Jan Reedyfa002d42015-07-30 16:44:09 -0400483 menudict['application'] = menu = Menu(mbar, name='apple',
484 tearoff=0)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000485 mbar.add_cascade(label='IDLE', menu=menu)
486
David Scherer7aced172000-08-15 01:13:23 +0000487 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000488 self.base_helpmenu_length = self.menudict['help'].index(END)
489 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000490
491 def postwindowsmenu(self):
492 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000493 menu = self.menudict['windows']
494 end = menu.index("end")
495 if end is None:
496 end = -1
497 if end > self.wmenu_end:
498 menu.delete(self.wmenu_end+1, end)
499 WindowList.add_windows_to_menu(menu)
500
501 rmenu = None
502
503 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000504 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
505 if not self.rmenu:
506 self.make_rmenu()
507 rmenu = self.rmenu
508 self.event = event
509 iswin = sys.platform[:3] == 'win'
510 if iswin:
511 self.text.config(cursor="arrow")
Andrew Svetlov5018db72012-11-01 22:39:14 +0200512
Roger Serwy231a8fd2013-04-07 12:15:52 -0500513 for item in self.rmenu_specs:
514 try:
515 label, eventname, verify_state = item
516 except ValueError: # see issue1207589
517 continue
518
Andrew Svetlov5018db72012-11-01 22:39:14 +0200519 if verify_state is None:
520 continue
521 state = getattr(self, verify_state)()
522 rmenu.entryconfigure(label, state=state)
523
David Scherer7aced172000-08-15 01:13:23 +0000524 rmenu.tk_popup(event.x_root, event.y_root)
525 if iswin:
526 self.text.config(cursor="ibeam")
527
528 rmenu_specs = [
Andrew Svetlov5018db72012-11-01 22:39:14 +0200529 # ("Label", "<<virtual-event>>", "statefuncname"), ...
530 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000531 ]
532
533 def make_rmenu(self):
534 rmenu = Menu(self.text, tearoff=0)
Roger Serwy231a8fd2013-04-07 12:15:52 -0500535 for item in self.rmenu_specs:
536 label, eventname = item[0], item[1]
Andrew Svetlov5018db72012-11-01 22:39:14 +0200537 if label is not None:
538 def command(text=self.text, eventname=eventname):
539 text.event_generate(eventname)
540 rmenu.add_command(label=label, command=command)
541 else:
542 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000543 self.rmenu = rmenu
544
Andrew Svetlov5018db72012-11-01 22:39:14 +0200545 def rmenu_check_cut(self):
546 return self.rmenu_check_copy()
547
548 def rmenu_check_copy(self):
549 try:
550 indx = self.text.index('sel.first')
551 except TclError:
552 return 'disabled'
553 else:
554 return 'normal' if indx else 'disabled'
555
556 def rmenu_check_paste(self):
557 try:
558 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
559 except TclError:
560 return 'disabled'
561 else:
562 return 'normal'
563
David Scherer7aced172000-08-15 01:13:23 +0000564 def about_dialog(self, event=None):
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400565 "Handle Help 'About IDLE' event."
566 # Synchronize with macosxSupport.overrideRootMenu.about_dialog.
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000567 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000568
Steven M. Gava3b55a892001-11-21 05:56:26 +0000569 def config_dialog(self, event=None):
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400570 "Handle Options 'Configure IDLE' event."
571 # Synchronize with macosxSupport.overrideRootMenu.config_dialog.
Steven M. Gava3b55a892001-11-21 05:56:26 +0000572 configDialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400573
Terry Jan Reedy7a162072014-10-22 20:15:12 -0400574 def config_extensions_dialog(self, event=None):
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400575 "Handle Options 'Configure Extensions' event."
Terry Jan Reedy7a162072014-10-22 20:15:12 -0400576 configDialog.ConfigExtensionsDialog(self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000577
David Scherer7aced172000-08-15 01:13:23 +0000578 def help_dialog(self, event=None):
Terry Jan Reedy36443f12015-09-20 22:55:17 -0400579 "Handle Help 'IDLE Help' event."
580 # Synchronize with macosxSupport.overrideRootMenu.help_dialog.
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500581 if self.root:
582 parent = self.root
583 else:
584 parent = self.top
Terry Jan Reedy70e763c2015-09-20 19:55:44 -0400585 help.show_idlehelp(parent)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000586
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000587 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000588 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000589 try:
590 os.startfile(self.help_url)
591 except WindowsError as why:
592 tkMessageBox.showerror(title='Document Start Failure',
593 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000594 else:
595 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000596 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000597
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000598 def cut(self,event):
599 self.text.event_generate("<<Cut>>")
600 return "break"
601
602 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000603 if not self.text.tag_ranges("sel"):
604 # There is no selection, so do nothing and maybe interrupt.
605 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000606 self.text.event_generate("<<Copy>>")
607 return "break"
608
609 def paste(self,event):
610 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000611 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000612 return "break"
613
David Scherer7aced172000-08-15 01:13:23 +0000614 def select_all(self, event=None):
615 self.text.tag_add("sel", "1.0", "end-1c")
616 self.text.mark_set("insert", "1.0")
617 self.text.see("insert")
618 return "break"
619
620 def remove_selection(self, event=None):
621 self.text.tag_remove("sel", "1.0", "end")
622 self.text.see("insert")
623
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000624 def move_at_edge_if_selection(self, edge_index):
625 """Cursor move begins at start or end of selection
626
627 When a left/right cursor key is pressed create and return to Tkinter a
628 function which causes a cursor move from the associated edge of the
629 selection.
630
631 """
632 self_text_index = self.text.index
633 self_text_mark_set = self.text.mark_set
634 edges_table = ("sel.first+1c", "sel.last-1c")
635 def move_at_edge(event):
636 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
637 try:
638 self_text_index("sel.first")
639 self_text_mark_set("insert", edges_table[edge_index])
640 except TclError:
641 pass
642 return move_at_edge
643
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000644 def del_word_left(self, event):
645 self.text.event_generate('<Meta-Delete>')
646 return "break"
647
648 def del_word_right(self, event):
649 self.text.event_generate('<Meta-d>')
650 return "break"
651
Steven M. Gavac5976402002-01-04 03:06:08 +0000652 def find_event(self, event):
653 SearchDialog.find(self.text)
654 return "break"
655
656 def find_again_event(self, event):
657 SearchDialog.find_again(self.text)
658 return "break"
659
660 def find_selection_event(self, event):
661 SearchDialog.find_selection(self.text)
662 return "break"
663
664 def find_in_files_event(self, event):
665 GrepDialog.grep(self.text, self.io, self.flist)
666 return "break"
667
668 def replace_event(self, event):
669 ReplaceDialog.replace(self.text)
670 return "break"
671
672 def goto_line_event(self, event):
673 text = self.text
674 lineno = tkSimpleDialog.askinteger("Goto",
675 "Go to line number:",parent=text)
676 if lineno is None:
677 return "break"
678 if lineno <= 0:
679 text.bell()
680 return "break"
681 text.mark_set("insert", "%d.0" % lineno)
682 text.see("insert")
683
David Scherer7aced172000-08-15 01:13:23 +0000684 def open_module(self, event=None):
685 # XXX Shouldn't this be in IOBinding or in FileList?
686 try:
687 name = self.text.get("sel.first", "sel.last")
688 except TclError:
689 name = ""
690 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000691 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000692 name = tkSimpleDialog.askstring("Module",
693 "Enter the name of a Python module\n"
694 "to search on sys.path and open:",
695 parent=self.text, initialvalue=name)
696 if name:
697 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000698 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000699 return
David Scherer7aced172000-08-15 01:13:23 +0000700 # XXX Ought to insert current file's directory in front of path
701 try:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400702 (f, file_path, (suffix, mode, mtype)) = _find_module(name)
Terry Jan Reedy2b149862013-06-29 00:59:34 -0400703 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000704 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
705 return
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400706 if mtype != imp.PY_SOURCE:
David Scherer7aced172000-08-15 01:13:23 +0000707 tkMessageBox.showerror("Unsupported type",
708 "%s is not a source module" % name, parent=self.text)
709 return
710 if f:
711 f.close()
712 if self.flist:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400713 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000714 else:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400715 self.io.loadfile(file_path)
716 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000717
718 def open_class_browser(self, event=None):
719 filename = self.io.filename
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400720 if not (self.__class__.__name__ == 'PyShellEditorWindow'
721 and filename):
722 filename = self.open_module()
723 if filename is None:
724 return
David Scherer7aced172000-08-15 01:13:23 +0000725 head, tail = os.path.split(filename)
726 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000727 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000728 ClassBrowser.ClassBrowser(self.flist, base, [head])
729
730 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000731 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000732 PathBrowser.PathBrowser(self.flist)
733
734 def gotoline(self, lineno):
735 if lineno is not None and lineno > 0:
736 self.text.mark_set("insert", "%d.0" % lineno)
737 self.text.tag_remove("sel", "1.0", "end")
738 self.text.tag_add("sel", "insert", "insert +1l")
739 self.center()
740
741 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000742 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000743 return True
David Scherer7aced172000-08-15 01:13:23 +0000744 base, ext = os.path.splitext(os.path.basename(filename))
745 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000746 return True
David Scherer7aced172000-08-15 01:13:23 +0000747 try:
748 f = open(filename)
749 line = f.readline()
750 f.close()
751 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000752 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000753 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000754
755 def close_hook(self):
756 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000757 self.flist.unregister_maybe_terminate(self)
758 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000759
760 def set_close_hook(self, close_hook):
761 self.close_hook = close_hook
762
763 def filename_change_hook(self):
764 if self.flist:
765 self.flist.filename_changed_edit(self)
766 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000767 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000768 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000769
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000770 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000771 if self.color:
772 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000773 if self.ispythonsource(self.io.filename):
774 self.color = self.ColorDelegator()
775 # can add more colorizers here...
776 if self.color:
777 self.per.removefilter(self.undo)
778 self.per.insertfilter(self.color)
779 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000780
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000781 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000782 if not self.color:
783 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000784 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000785 self.per.removefilter(self.color)
786 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000787
Steven M. Gavab77d3432002-03-02 07:16:21 +0000788 def ResetColorizer(self):
Terry Jan Reedy13755382014-10-09 18:44:26 -0400789 "Update the color theme"
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000790 # Called from self.filename_change_hook and from configDialog.py
791 self._rmcolorizer()
792 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000793 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000794 normal_colors = idleConf.GetHighlight(theme, 'normal')
795 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
796 select_colors = idleConf.GetHighlight(theme, 'hilite')
797 self.text.config(
798 foreground=normal_colors['foreground'],
799 background=normal_colors['background'],
800 insertbackground=cursor_color,
801 selectforeground=select_colors['foreground'],
802 selectbackground=select_colors['background'],
803 )
David Scherer7aced172000-08-15 01:13:23 +0000804
Steven M. Gavab1585412002-03-12 00:21:56 +0000805 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000806 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000807 # Called from configDialog.py
Terry Jan Reedy12352142015-08-01 18:57:27 -0400808
809 self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
Steven M. Gavab1585412002-03-12 00:21:56 +0000810
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000811 def RemoveKeybindings(self):
812 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000813 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000814 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000815 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000816 self.text.event_delete(event, *keylist)
817 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000818 xkeydefs = idleConf.GetExtensionBindings(extensionName)
819 if xkeydefs:
820 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000821 self.text.event_delete(event, *keylist)
822
823 def ApplyKeybindings(self):
824 "Update the keybindings after they are changed"
825 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000826 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000827 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000828 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000829 xkeydefs = idleConf.GetExtensionBindings(extensionName)
830 if xkeydefs:
831 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000832 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000833 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000834 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000835 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000836 for item in menu[1]:
837 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000838 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000839 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000840 menu = self.menudict[menubarItem]
Ned Deily14ef0c82013-07-20 14:38:24 -0700841 end = menu.index(END)
842 if end is None:
843 # Skip empty menus
844 continue
845 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000846 for index in range(0, end):
847 if menu.type(index) == 'command':
848 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000849 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000850 itemName = menu.entrycget(index, 'label')
851 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000852 if menubarItem in menuEventDict:
853 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000854 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000855 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000856 accel = get_accelerator(keydefs, event)
857 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000858
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000859 def set_notabs_indentwidth(self):
860 "Update the indentwidth if changed and not using tabs in this window"
861 # Called from configDialog.py
862 if not self.usetabs:
863 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
864 type='int')
865
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000866 def reset_help_menu_entries(self):
867 "Update the additional help entries on the Help menu"
868 help_list = idleConf.GetAllExtraHelpSourcesList()
869 helpmenu = self.menudict['help']
870 # first delete the extra help entries, if any
871 helpmenu_length = helpmenu.index(END)
872 if helpmenu_length > self.base_helpmenu_length:
873 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
874 # then rebuild them
875 if help_list:
876 helpmenu.add_separator()
877 for entry in help_list:
878 cmd = self.__extra_help_callback(entry[1])
879 helpmenu.add_command(label=entry[0], command=cmd)
880 # and update the menu dictionary
881 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000882
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000883 def __extra_help_callback(self, helpfile):
884 "Create a callback with the helpfile value frozen at definition time"
885 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000886 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000887 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000888 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000889 try:
890 os.startfile(helpfile)
891 except WindowsError as why:
892 tkMessageBox.showerror(title='Document Start Failure',
893 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000894 else:
895 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000896 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000897
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000898 def update_recent_files_list(self, new_file=None):
899 "Load and update the recent files list and menus"
900 rf_list = []
901 if os.path.exists(self.recent_files_path):
Terry Jan Reedyf9489432013-08-04 15:39:56 -0400902 with open(self.recent_files_path, 'r') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000903 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000904 if new_file:
905 new_file = os.path.abspath(new_file) + '\n'
906 if new_file in rf_list:
907 rf_list.remove(new_file) # move to top
908 rf_list.insert(0, new_file)
909 # clean and save the recent files list
910 bad_paths = []
911 for path in rf_list:
912 if '\0' in path or not os.path.exists(path[0:-1]):
913 bad_paths.append(path)
914 rf_list = [path for path in rf_list if path not in bad_paths]
915 ulchars = "1234567890ABCDEFGHIJK"
916 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000917 try:
Ned Deily40ad0412011-12-14 14:57:43 -0800918 with open(self.recent_files_path, 'w') as rf_file:
919 rf_file.writelines(rf_list)
920 except IOError as err:
921 if not getattr(self.root, "recentfilelist_error_displayed", False):
922 self.root.recentfilelist_error_displayed = True
923 tkMessageBox.showerror(title='IDLE Error',
924 message='Unable to update Recent Files list:\n%s'
925 % str(err),
926 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000927 # for each edit window instance, construct the recent files menu
928 for instance in self.top.instance_dict.keys():
929 menu = instance.recent_files_menu
Ned Deily71489842012-05-29 10:42:34 -0700930 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000931 for i, file_name in enumerate(rf_list):
932 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000933 # make unicode string to display non-ASCII chars correctly
934 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000935 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000936 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000937 command=callback,
938 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000939
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000940 def __recent_file_callback(self, file_name):
941 def open_recent_file(fn_closure=file_name):
942 self.io.open(editFile=fn_closure)
943 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000944
David Scherer7aced172000-08-15 01:13:23 +0000945 def saved_change_hook(self):
946 short = self.short_title()
947 long = self.long_title()
948 if short and long:
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400949 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000950 elif short:
951 title = short
952 elif long:
953 title = long
954 else:
955 title = "Untitled"
956 icon = short or long or title
957 if not self.get_saved():
958 title = "*%s*" % title
959 icon = "*%s" % icon
960 self.top.wm_title(title)
961 self.top.wm_iconname(icon)
962
963 def get_saved(self):
964 return self.undo.get_saved()
965
966 def set_saved(self, flag):
967 self.undo.set_saved(flag)
968
969 def reset_undo(self):
970 self.undo.reset_undo()
971
972 def short_title(self):
973 filename = self.io.filename
974 if filename:
975 filename = os.path.basename(filename)
Terry Jan Reedy59243652014-01-23 00:36:37 -0500976 else:
977 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000978 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400979 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000980
981 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000982 # return unicode string to display non-ASCII chars correctly
983 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000984
985 def center_insert_event(self, event):
986 self.center()
987
988 def center(self, mark="insert"):
989 text = self.text
990 top, bot = self.getwindowlines()
991 lineno = self.getlineno(mark)
992 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000993 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000994 text.yview(float(newtop))
995
996 def getwindowlines(self):
997 text = self.text
998 top = self.getlineno("@0,0")
999 bot = self.getlineno("@0,65535")
1000 if top == bot and text.winfo_height() == 1:
1001 # Geometry manager hasn't run yet
1002 height = int(text['height'])
1003 bot = top + height - 1
1004 return top, bot
1005
1006 def getlineno(self, mark="insert"):
1007 text = self.text
1008 return int(float(text.index(mark)))
1009
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001010 def get_geometry(self):
1011 "Return (width, height, x, y)"
1012 geom = self.top.wm_geometry()
1013 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
1014 tuple = (map(int, m.groups()))
1015 return tuple
1016
David Scherer7aced172000-08-15 01:13:23 +00001017 def close_event(self, event):
1018 self.close()
1019
1020 def maybesave(self):
1021 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001022 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001023 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001024 self.top.deiconify()
1025 self.top.lower()
1026 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001027 return self.io.maybesave()
1028
1029 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001030 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +00001031 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001032 self._close()
1033 return reply
1034
1035 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001036 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001037 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001038 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001039 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001040 self.io.close()
1041 self.io = None
1042 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001043 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001044 self.color.close(False)
1045 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001046 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001047 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001048 self.per.close()
1049 self.per = None
1050 self.top.destroy()
1051 if self.close_hook:
1052 # unless override: unregister from flist, terminate if last window
1053 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001054
1055 def load_extensions(self):
1056 self.extensions = {}
1057 self.load_standard_extensions()
1058
1059 def unload_extensions(self):
1060 for ins in self.extensions.values():
1061 if hasattr(ins, "close"):
1062 ins.close()
1063 self.extensions = {}
1064
1065 def load_standard_extensions(self):
1066 for name in self.get_standard_extension_names():
1067 try:
1068 self.load_extension(name)
1069 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001070 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +00001071 import traceback
1072 traceback.print_exc()
1073
1074 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001075 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001076
1077 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001078 try:
1079 mod = __import__(name, globals(), locals(), [])
1080 except ImportError:
1081 print "\nFailed to import extension: ", name
1082 return
David Scherer7aced172000-08-15 01:13:23 +00001083 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001084 keydefs = idleConf.GetExtensionBindings(name)
1085 if hasattr(cls, "menudefs"):
1086 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001087 ins = cls(self)
1088 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001089 if keydefs:
1090 self.apply_bindings(keydefs)
1091 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001092 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001093 while methodname[:1] == '<':
1094 methodname = methodname[1:]
1095 while methodname[-1:] == '>':
1096 methodname = methodname[:-1]
1097 methodname = methodname + "_event"
1098 if hasattr(ins, methodname):
1099 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001100
1101 def apply_bindings(self, keydefs=None):
1102 if keydefs is None:
1103 keydefs = self.Bindings.default_keydefs
1104 text = self.text
1105 text.keydefs = keydefs
1106 for event, keylist in keydefs.items():
1107 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001108 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001109
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001110 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001111 """Add appropriate entries to the menus and submenus
1112
1113 Menus that are absent or None in self.menudict are ignored.
1114 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001115 if menudefs is None:
1116 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001117 if keydefs is None:
1118 keydefs = self.Bindings.default_keydefs
1119 menudict = self.menudict
1120 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001121 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001122 menu = menudict.get(mname)
1123 if not menu:
1124 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001125 for entry in entrylist:
1126 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001127 menu.add_separator()
1128 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001129 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001130 checkbutton = (label[:1] == '!')
1131 if checkbutton:
1132 label = label[1:]
1133 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001134 accelerator = get_accelerator(keydefs, eventname)
1135 def command(text=text, eventname=eventname):
1136 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001137 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001138 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001139 menu.add_checkbutton(label=label, underline=underline,
1140 command=command, accelerator=accelerator,
1141 variable=var)
1142 else:
1143 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001144 command=command,
1145 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001146
1147 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001148 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001149 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001150 value = var.get()
1151 return value
1152 else:
1153 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001154
1155 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001156 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001157 if var:
1158 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001159 else:
1160 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001161
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001162 def get_var_obj(self, name, vartype=None):
1163 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001164 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001165 # create a Tkinter variable object with self.text as master:
1166 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001167 return var
1168
1169 # Tk implementations of "virtual text methods" -- each platform
1170 # reusing IDLE's support code needs to define these for its GUI's
1171 # flavor of widget.
1172
1173 # Is character at text_index in a Python string? Return 0 for
1174 # "guaranteed no", true for anything else. This info is expensive
1175 # to compute ab initio, but is probably already known by the
1176 # platform's colorizer.
1177
1178 def is_char_in_string(self, text_index):
1179 if self.color:
1180 # Return true iff colorizer hasn't (re)gotten this far
1181 # yet, or the character is tagged as being in a string
1182 return self.text.tag_prevrange("TODO", text_index) or \
1183 "STRING" in self.text.tag_names(text_index)
1184 else:
1185 # The colorizer is missing: assume the worst
1186 return 1
1187
1188 # If a selection is defined in the text widget, return (start,
1189 # end) as Tkinter text indices, otherwise return (None, None)
1190 def get_selection_indices(self):
1191 try:
1192 first = self.text.index("sel.first")
1193 last = self.text.index("sel.last")
1194 return first, last
1195 except TclError:
1196 return None, None
1197
1198 # Return the text widget's current view of what a tab stop means
1199 # (equivalent width in spaces).
1200
1201 def get_tabwidth(self):
1202 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1203 return int(current)
1204
1205 # Set the text widget's current view of what a tab stop means.
1206
1207 def set_tabwidth(self, newtabwidth):
1208 text = self.text
1209 if self.get_tabwidth() != newtabwidth:
1210 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
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001215 # If ispythonsource and guess are true, guess a good value for
1216 # indentwidth based on file content (if possible), and if
1217 # indentwidth != tabwidth set usetabs false.
1218 # In any case, adjust the Text widget's view of what a tab
1219 # character means.
1220
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001221 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001222 if guess and ispythonsource:
1223 i = self.guess_indent()
1224 if 2 <= i <= 8:
1225 self.indentwidth = i
1226 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001227 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001228 self.set_tabwidth(self.tabwidth)
1229
1230 def smart_backspace_event(self, event):
1231 text = self.text
1232 first, last = self.get_selection_indices()
1233 if first and last:
1234 text.delete(first, last)
1235 text.mark_set("insert", first)
1236 return "break"
1237 # Delete whitespace left, until hitting a real char or closest
1238 # preceding virtual tab stop.
1239 chars = text.get("insert linestart", "insert")
1240 if chars == '':
1241 if text.compare("insert", ">", "1.0"):
1242 # easy: delete preceding newline
1243 text.delete("insert-1c")
1244 else:
1245 text.bell() # at start of buffer
1246 return "break"
1247 if chars[-1] not in " \t":
1248 # easy: delete preceding real char
1249 text.delete("insert-1c")
1250 return "break"
1251 # Ick. It may require *inserting* spaces if we back up over a
1252 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001253 tabwidth = self.tabwidth
1254 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001255 assert have > 0
1256 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001257 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001258 if self.context_use_ps1:
1259 last_line_of_prompt = sys.ps1.split('\n')[-1]
1260 else:
1261 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001262 ncharsdeleted = 0
1263 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001264 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001265 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001266 chars = chars[:-1]
1267 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001268 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001269 if have <= want or chars[-1] not in " \t":
1270 break
1271 text.undo_block_start()
1272 text.delete("insert-%dc" % ncharsdeleted, "insert")
1273 if have < want:
1274 text.insert("insert", ' ' * (want - have))
1275 text.undo_block_stop()
1276 return "break"
1277
1278 def smart_indent_event(self, event):
1279 # if intraline selection:
1280 # delete it
1281 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001282 # do indent-region
1283 # else:
1284 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001285 text = self.text
1286 first, last = self.get_selection_indices()
1287 text.undo_block_start()
1288 try:
1289 if first and last:
1290 if index2line(first) != index2line(last):
1291 return self.indent_region_event(event)
1292 text.delete(first, last)
1293 text.mark_set("insert", first)
1294 prefix = text.get("insert linestart", "insert")
1295 raw, effective = classifyws(prefix, self.tabwidth)
1296 if raw == len(prefix):
1297 # only whitespace to the left
1298 self.reindent_to(effective + self.indentwidth)
1299 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001300 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001301 if self.usetabs:
1302 pad = '\t'
1303 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001304 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001305 n = self.indentwidth
1306 pad = ' ' * (n - effective % n)
1307 text.insert("insert", pad)
1308 text.see("insert")
1309 return "break"
1310 finally:
1311 text.undo_block_stop()
1312
1313 def newline_and_indent_event(self, event):
1314 text = self.text
1315 first, last = self.get_selection_indices()
1316 text.undo_block_start()
1317 try:
1318 if first and last:
1319 text.delete(first, last)
1320 text.mark_set("insert", first)
1321 line = text.get("insert linestart", "insert")
1322 i, n = 0, len(line)
1323 while i < n and line[i] in " \t":
1324 i = i+1
1325 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001326 # the cursor is in or at leading indentation in a continuation
1327 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001328 text.insert("insert linestart", '\n')
1329 return "break"
1330 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001331 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001332 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001333 last_line_of_prompt = sys.ps1.split('\n')[-1]
1334 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001335 line = line[:-1]
1336 i = i+1
1337 if i:
1338 text.delete("insert - %d chars" % i, "insert")
1339 # strip whitespace after insert point
1340 while text.get("insert") in " \t":
1341 text.delete("insert")
1342 # start new line
1343 text.insert("insert", '\n')
1344
1345 # adjust indentation for continuations and block
1346 # open/close first need to find the last stmt
1347 lno = index2line(text.index('insert'))
1348 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001349 if not self.context_use_ps1:
1350 for context in self.num_context_lines:
1351 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001352 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001353 rawtext = text.get(startatindex, "insert")
1354 y.set_str(rawtext)
1355 bod = y.find_good_parse_start(
1356 self.context_use_ps1,
1357 self._build_char_in_string_func(startatindex))
1358 if bod is not None or startat == 1:
1359 break
1360 y.set_lo(bod or 0)
1361 else:
1362 r = text.tag_prevrange("console", "insert")
1363 if r:
1364 startatindex = r[1]
1365 else:
1366 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001367 rawtext = text.get(startatindex, "insert")
1368 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001369 y.set_lo(0)
1370
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001371 c = y.get_continuation_type()
1372 if c != PyParse.C_NONE:
1373 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001374 if c == PyParse.C_STRING_FIRST_LINE:
1375 # after the first line of a string; do not indent at all
1376 pass
1377 elif c == PyParse.C_STRING_NEXT_LINES:
1378 # inside a string which started before this line;
1379 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001380 text.insert("insert", indent)
1381 elif c == PyParse.C_BRACKET:
1382 # line up with the first (if any) element of the
1383 # last open bracket structure; else indent one
1384 # level beyond the indent of the line with the
1385 # last open bracket
1386 self.reindent_to(y.compute_bracket_indent())
1387 elif c == PyParse.C_BACKSLASH:
1388 # if more than one line in this stmt already, just
1389 # mimic the current indent; else if initial line
1390 # has a start on an assignment stmt, indent to
1391 # beyond leftmost =; else to beyond first chunk of
1392 # non-whitespace on initial line
1393 if y.get_num_lines_in_stmt() > 1:
1394 text.insert("insert", indent)
1395 else:
1396 self.reindent_to(y.compute_backslash_indent())
1397 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001398 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001399 return "break"
1400
1401 # This line starts a brand new stmt; indent relative to
1402 # indentation of initial line of closest preceding
1403 # interesting stmt.
1404 indent = y.get_base_indent_string()
1405 text.insert("insert", indent)
1406 if y.is_block_opener():
1407 self.smart_indent_event(event)
1408 elif indent and y.is_block_closer():
1409 self.smart_backspace_event(event)
1410 return "break"
1411 finally:
1412 text.see("insert")
1413 text.undo_block_stop()
1414
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001415 # Our editwin provides a is_char_in_string function that works
1416 # with a Tk text index, but PyParse only knows about offsets into
1417 # a string. This builds a function for PyParse that accepts an
1418 # offset.
1419
1420 def _build_char_in_string_func(self, startindex):
1421 def inner(offset, _startindex=startindex,
1422 _icis=self.is_char_in_string):
1423 return _icis(_startindex + "+%dc" % offset)
1424 return inner
1425
1426 def indent_region_event(self, event):
1427 head, tail, chars, lines = self.get_region()
1428 for pos in range(len(lines)):
1429 line = lines[pos]
1430 if line:
1431 raw, effective = classifyws(line, self.tabwidth)
1432 effective = effective + self.indentwidth
1433 lines[pos] = self._make_blanks(effective) + line[raw:]
1434 self.set_region(head, tail, chars, lines)
1435 return "break"
1436
1437 def dedent_region_event(self, event):
1438 head, tail, chars, lines = self.get_region()
1439 for pos in range(len(lines)):
1440 line = lines[pos]
1441 if line:
1442 raw, effective = classifyws(line, self.tabwidth)
1443 effective = max(effective - self.indentwidth, 0)
1444 lines[pos] = self._make_blanks(effective) + line[raw:]
1445 self.set_region(head, tail, chars, lines)
1446 return "break"
1447
1448 def comment_region_event(self, event):
1449 head, tail, chars, lines = self.get_region()
1450 for pos in range(len(lines) - 1):
1451 line = lines[pos]
1452 lines[pos] = '##' + line
1453 self.set_region(head, tail, chars, lines)
1454
1455 def uncomment_region_event(self, event):
1456 head, tail, chars, lines = self.get_region()
1457 for pos in range(len(lines)):
1458 line = lines[pos]
1459 if not line:
1460 continue
1461 if line[:2] == '##':
1462 line = line[2:]
1463 elif line[:1] == '#':
1464 line = line[1:]
1465 lines[pos] = line
1466 self.set_region(head, tail, chars, lines)
1467
1468 def tabify_region_event(self, event):
1469 head, tail, chars, lines = self.get_region()
1470 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001471 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001472 for pos in range(len(lines)):
1473 line = lines[pos]
1474 if line:
1475 raw, effective = classifyws(line, tabwidth)
1476 ntabs, nspaces = divmod(effective, tabwidth)
1477 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1478 self.set_region(head, tail, chars, lines)
1479
1480 def untabify_region_event(self, event):
1481 head, tail, chars, lines = self.get_region()
1482 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001483 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001484 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001485 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001486 self.set_region(head, tail, chars, lines)
1487
1488 def toggle_tabs_event(self, event):
1489 if self.askyesno(
1490 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001491 "Turn tabs " + ("on", "off")[self.usetabs] +
1492 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001493 ("will be", "remains at")[self.usetabs] + " 8." +
1494 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001495 parent=self.text):
1496 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001497 # Try to prevent inconsistent indentation.
1498 # User must change indent width manually after using tabs.
1499 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001500 return "break"
1501
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001502 # XXX this isn't bound to anything -- see tabwidth comments
1503## def change_tabwidth_event(self, event):
1504## new = self._asktabwidth()
1505## if new != self.tabwidth:
1506## self.tabwidth = new
1507## self.set_indentation_params(0, guess=0)
1508## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001509
1510 def change_indentwidth_event(self, event):
1511 new = self.askinteger(
1512 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001513 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001514 parent=self.text,
1515 initialvalue=self.indentwidth,
1516 minvalue=2,
1517 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001518 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001519 self.indentwidth = new
1520 return "break"
1521
1522 def get_region(self):
1523 text = self.text
1524 first, last = self.get_selection_indices()
1525 if first and last:
1526 head = text.index(first + " linestart")
1527 tail = text.index(last + "-1c lineend +1c")
1528 else:
1529 head = text.index("insert linestart")
1530 tail = text.index("insert lineend +1c")
1531 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001532 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001533 return head, tail, chars, lines
1534
1535 def set_region(self, head, tail, chars, lines):
1536 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001537 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001538 if newchars == chars:
1539 text.bell()
1540 return
1541 text.tag_remove("sel", "1.0", "end")
1542 text.mark_set("insert", head)
1543 text.undo_block_start()
1544 text.delete(head, tail)
1545 text.insert(head, newchars)
1546 text.undo_block_stop()
1547 text.tag_add("sel", head, "insert")
1548
1549 # Make string that displays as n leading blanks.
1550
1551 def _make_blanks(self, n):
1552 if self.usetabs:
1553 ntabs, nspaces = divmod(n, self.tabwidth)
1554 return '\t' * ntabs + ' ' * nspaces
1555 else:
1556 return ' ' * n
1557
1558 # Delete from beginning of line to insert point, then reinsert
1559 # column logical (meaning use tabs if appropriate) spaces.
1560
1561 def reindent_to(self, column):
1562 text = self.text
1563 text.undo_block_start()
1564 if text.compare("insert linestart", "!=", "insert"):
1565 text.delete("insert linestart", "insert")
1566 if column:
1567 text.insert("insert", self._make_blanks(column))
1568 text.undo_block_stop()
1569
1570 def _asktabwidth(self):
1571 return self.askinteger(
1572 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001573 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001574 parent=self.text,
1575 initialvalue=self.indentwidth,
1576 minvalue=2,
Roger Serwy75b249c2013-04-06 20:26:53 -05001577 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001578
1579 # Guess indentwidth from text content.
1580 # Return guessed indentwidth. This should not be believed unless
1581 # it's in a reasonable range (e.g., it will be 0 if no indented
1582 # blocks are found).
1583
1584 def guess_indent(self):
1585 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1586 if opener and indented:
1587 raw, indentsmall = classifyws(opener, self.tabwidth)
1588 raw, indentlarge = classifyws(indented, self.tabwidth)
1589 else:
1590 indentsmall = indentlarge = 0
1591 return indentlarge - indentsmall
1592
1593# "line.col" -> line, as an int
1594def index2line(index):
1595 return int(float(index))
1596
1597# Look at the leading whitespace in s.
1598# Return pair (# of leading ws characters,
1599# effective # of leading blanks after expanding
1600# tabs to width tabwidth)
1601
1602def classifyws(s, tabwidth):
1603 raw = effective = 0
1604 for ch in s:
1605 if ch == ' ':
1606 raw = raw + 1
1607 effective = effective + 1
1608 elif ch == '\t':
1609 raw = raw + 1
1610 effective = (effective // tabwidth + 1) * tabwidth
1611 else:
1612 break
1613 return raw, effective
1614
1615import tokenize
1616_tokenize = tokenize
1617del tokenize
1618
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001619class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001620
1621 # .run() chews over the Text widget, looking for a block opener
1622 # and the stmt following it. Returns a pair,
1623 # (line containing block opener, line containing stmt)
1624 # Either or both may be None.
1625
1626 def __init__(self, text, tabwidth):
1627 self.text = text
1628 self.tabwidth = tabwidth
1629 self.i = self.finished = 0
1630 self.blkopenline = self.indentedline = None
1631
1632 def readline(self):
1633 if self.finished:
1634 return ""
1635 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001636 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001637 if self.text.compare(mark, ">=", "end"):
1638 return ""
1639 return self.text.get(mark, mark + " lineend+1c")
1640
1641 def tokeneater(self, type, token, start, end, line,
1642 INDENT=_tokenize.INDENT,
1643 NAME=_tokenize.NAME,
1644 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1645 if self.finished:
1646 pass
1647 elif type == NAME and token in OPENERS:
1648 self.blkopenline = line
1649 elif type == INDENT and self.blkopenline:
1650 self.indentedline = line
1651 self.finished = 1
1652
1653 def run(self):
1654 save_tabsize = _tokenize.tabsize
1655 _tokenize.tabsize = self.tabwidth
1656 try:
1657 try:
1658 _tokenize.tokenize(self.readline, self.tokeneater)
Serhiy Storchaka61006a22012-12-27 21:34:23 +02001659 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001660 # since we cut off the tokenizer early, we can trigger
1661 # spurious errors
1662 pass
1663 finally:
1664 _tokenize.tabsize = save_tabsize
1665 return self.blkopenline, self.indentedline
1666
1667### end autoindent code ###
1668
David Scherer7aced172000-08-15 01:13:23 +00001669def prepstr(s):
1670 # Helper to extract the underscore from a string, e.g.
1671 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001672 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001673 if i >= 0:
1674 s = s[:i] + s[i+1:]
1675 return i, s
1676
1677
1678keynames = {
1679 'bracketleft': '[',
1680 'bracketright': ']',
1681 'slash': '/',
1682}
1683
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001684def get_accelerator(keydefs, eventname):
1685 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001686 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1687 # if not keylist:
Ned Deily57847df2014-03-27 20:47:04 -07001688 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily60651532011-01-31 00:52:49 +00001689 "<<open-module>>",
1690 "<<goto-line>>",
1691 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001692 return ""
1693 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001694 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001695 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1696 s = re.sub("Key-", "", s)
1697 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1698 s = re.sub("Control-", "Ctrl-", s)
1699 s = re.sub("-", "+", s)
1700 s = re.sub("><", " ", s)
1701 s = re.sub("<", "", s)
1702 s = re.sub(">", "", s)
1703 return s
1704
1705
1706def fixwordbreaks(root):
1707 # Make sure that Tk's double-click and next/previous word
1708 # operations use our definition of a word (i.e. an identifier)
1709 tk = root.tk
1710 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1711 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1712 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1713
1714
Terry Jan Reedycf834762014-10-17 01:31:29 -04001715def _editor_window(parent): # htest #
1716 # error if close master window first - timer event, after script
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001717 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001718 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001719 if sys.argv[1:]:
1720 filename = sys.argv[1]
1721 else:
1722 filename = None
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001723 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001724 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001725 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycf834762014-10-17 01:31:29 -04001726 # Does not stop error, neither does following
1727 # edit.text.bind("<<close-window>>", edit.close_event)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001728
David Scherer7aced172000-08-15 01:13:23 +00001729
1730if __name__ == '__main__':
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001731 from idlelib.idle_test.htest import run
Terry Jan Reedy70e763c2015-09-20 19:55:44 -04001732 run(_editor_window)