blob: 0a95293ace70d2c244ed345c93987433d6e295a1 [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
David Scherer7aced172000-08-15 01:13:23 +000020
21# The default tab setting for a Text widget, in average-width characters.
22TK_TABWIDTH_DEFAULT = 8
23
Terry Jan Reedyc11633e2014-08-14 21:54:38 -040024_py_version = ' (%s)' % platform.python_version()
25
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000026def _sphinx_version():
27 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
28 major, minor, micro, level, serial = sys.version_info
29 release = '%s%s' % (major, minor)
30 if micro:
Benjamin Petersonfb234632009-06-13 15:42:23 +000031 release += '%s' % (micro,)
32 if level == 'candidate':
33 release += 'rc%s' % (serial,)
34 elif level != 'final':
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000035 release += '%s%s' % (level[0], serial)
36 return release
37
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000038def _find_module(fullname, path=None):
39 """Version of imp.find_module() that handles hierarchical module names"""
40
41 file = None
42 for tgt in fullname.split('.'):
43 if file is not None:
44 file.close() # close intermediate files
45 (file, filename, descr) = imp.find_module(tgt, path)
46 if descr[2] == imp.PY_SOURCE:
47 break # find but not load the source file
48 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000049 try:
50 path = module.__path__
51 except AttributeError:
52 raise ImportError, 'No source for module ' + module.__name__
Raymond Hettinger179816d2011-04-12 18:54:46 -070053 if descr[2] != imp.PY_SOURCE:
54 # If all of the above fails and didn't raise an exception,fallback
55 # to a straight import which can find __init__.py in a package.
56 m = __import__(fullname)
57 try:
58 filename = m.__file__
59 except AttributeError:
60 pass
61 else:
62 file = None
63 base, ext = os.path.splitext(filename)
64 if ext == '.pyc':
65 ext = '.py'
66 filename = base + ext
67 descr = filename, None, imp.PY_SOURCE
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000068 return file, filename, descr
69
Terry Jan Reedyadb87e22012-02-05 15:10:55 -050070
71class HelpDialog(object):
72
73 def __init__(self):
74 self.parent = None # parent of help window
75 self.dlg = None # the help window iteself
76
77 def display(self, parent, near=None):
78 """ Display the help dialog.
79
80 parent - parent widget for the help window
81
82 near - a Toplevel widget (e.g. EditorWindow or PyShell)
83 to use as a reference for placing the help window
84 """
85 if self.dlg is None:
86 self.show_dialog(parent)
87 if near:
88 self.nearwindow(near)
89
90 def show_dialog(self, parent):
91 self.parent = parent
92 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
93 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
94 dlg.bind('<Destroy>', self.destroy, '+')
95
96 def nearwindow(self, near):
97 # Place the help dialog near the window specified by parent.
98 # Note - this may not reposition the window in Metacity
99 # if "/apps/metacity/general/disable_workarounds" is enabled
100 dlg = self.dlg
101 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
102 dlg.withdraw()
103 dlg.geometry("=+%d+%d" % geom)
104 dlg.deiconify()
105 dlg.lift()
106
107 def destroy(self, ev=None):
108 self.dlg = None
109 self.parent = None
110
111helpDialog = HelpDialog() # singleton instance
Terry Jan Reedy43458462014-05-19 00:12:00 -0400112def _help_dialog(parent): # wrapper for htest
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -0400113 helpDialog.show_dialog(parent)
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500114
115
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +0000116class EditorWindow(object):
Florent Xiclunad630c042010-04-02 07:24:52 +0000117 from idlelib.Percolator import Percolator
118 from idlelib.ColorDelegator import ColorDelegator
119 from idlelib.UndoDelegator import UndoDelegator
120 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
121 from idlelib import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +0000122 from Tkinter import Toplevel
Florent Xiclunad630c042010-04-02 07:24:52 +0000123 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +0000124
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000125 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000126
127 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000128 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +0000129 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000130 if sys.platform.count('linux'):
131 # look for html docs in a couple of standard places
132 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
133 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
134 dochome = '/var/www/html/python/index.html'
135 else:
136 basepath = '/usr/share/doc/' # standard location
137 dochome = os.path.join(basepath, pyver,
138 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000139 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +0000140 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +0000141 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000142 if os.path.isfile(chmfile):
143 dochome = chmfile
Ned Deily57847df2014-03-27 20:47:04 -0700144 elif sys.platform == 'darwin':
145 # documentation may be stored inside a python framework
Ronald Oussoren19302d92006-06-11 14:33:36 +0000146 dochome = os.path.join(sys.prefix,
147 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000148 dochome = os.path.normpath(dochome)
149 if os.path.isfile(dochome):
150 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +0000151 if sys.platform == 'darwin':
152 # Safari requires real file:-URLs
153 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000154 else:
Terry Jan Reedyb17c1e02014-09-19 22:54:09 -0400155 EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
David Scherer7aced172000-08-15 01:13:23 +0000156 self.flist = flist
157 root = root or flist.root
158 self.root = root
Kurt B. Kaiserb3c4d162006-07-24 17:13:23 +0000159 try:
160 sys.ps1
161 except AttributeError:
162 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000163 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000164 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000165 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000166 self.tkinter_vars = flist.vars
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200167 #self.top.instance_dict makes flist.inversedict available to
168 #configDialog.py so it can access all EditorWindow instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000169 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000170 else:
171 self.tkinter_vars = {} # keys: Tkinter event names
172 # values: Tkinter variable instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000173 self.top.instance_dict = {}
174 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000175 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000176 self.text_frame = text_frame = Frame(top)
Martin v. Löwis4ebbefe2006-11-22 08:50:02 +0000177 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200178 self.width = idleConf.GetOption('main','EditorWindow','width', type='int')
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000179 text_options = {
180 'name': 'text',
181 'padx': 5,
182 'wrap': 'none',
183 'width': self.width,
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200184 'height': idleConf.GetOption('main', 'EditorWindow', 'height', type='int')}
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000185 if TkVersion >= 8.5:
186 # Starting with tk 8.5 we have to set the new tabstyle option
187 # to 'wordprocessor' to achieve the same display of tabs as in
188 # older tk versions.
189 text_options['tabstyle'] = 'wordprocessor'
190 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000191 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000192
193 self.createmenubar()
194 self.apply_bindings()
195
196 self.top.protocol("WM_DELETE_WINDOW", self.close)
197 self.top.bind("<<close-window>>", self.close_event)
Ned Deily57847df2014-03-27 20:47:04 -0700198 if macosxSupport.isAquaTk():
Ronald Oussoren17db4952006-07-23 09:41:09 +0000199 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000200 text.bind('<<close-window>>', self.close_event)
R. David Murray3f752ab2010-12-18 17:22:18 +0000201 # Some OS X systems have only one mouse button,
202 # so use control-click for pulldown menus there.
203 # (Note, AquaTk defines <2> as the right button if
204 # present and the Tk Text widget already binds <2>.)
205 text.bind("<Control-Button-1>",self.right_menu_event)
206 else:
207 # Elsewhere, use right-click for pulldown menus.
208 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000209 text.bind("<<cut>>", self.cut)
210 text.bind("<<copy>>", self.copy)
211 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000212 text.bind("<<center-insert>>", self.center_insert_event)
213 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000214 text.bind("<<python-docs>>", self.python_docs)
215 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000216 text.bind("<<open-config-dialog>>", self.config_dialog)
Terry Jan Reedy7a162072014-10-22 20:15:12 -0400217 text.bind("<<open-config-extensions-dialog>>",
218 self.config_extensions_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000219 text.bind("<<open-module>>", self.open_module)
220 text.bind("<<do-nothing>>", lambda event: "break")
221 text.bind("<<select-all>>", self.select_all)
222 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000223 text.bind("<<find>>", self.find_event)
224 text.bind("<<find-again>>", self.find_again_event)
225 text.bind("<<find-in-files>>", self.find_in_files_event)
226 text.bind("<<find-selection>>", self.find_selection_event)
227 text.bind("<<replace>>", self.replace_event)
228 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000229 text.bind("<<smart-backspace>>",self.smart_backspace_event)
230 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
231 text.bind("<<smart-indent>>",self.smart_indent_event)
232 text.bind("<<indent-region>>",self.indent_region_event)
233 text.bind("<<dedent-region>>",self.dedent_region_event)
234 text.bind("<<comment-region>>",self.comment_region_event)
235 text.bind("<<uncomment-region>>",self.uncomment_region_event)
236 text.bind("<<tabify-region>>",self.tabify_region_event)
237 text.bind("<<untabify-region>>",self.untabify_region_event)
238 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
239 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000240 text.bind("<Left>", self.move_at_edge_if_selection(0))
241 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000242 text.bind("<<del-word-left>>", self.del_word_left)
243 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000244 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000245
David Scherer7aced172000-08-15 01:13:23 +0000246 if flist:
247 flist.inversedict[self] = key
248 if key:
249 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000250 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000251 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
252 text.bind("<<open-class-browser>>", self.open_class_browser)
253 text.bind("<<open-path-browser>>", self.open_path_browser)
254
Steven M. Gava898a3652001-10-07 11:10:44 +0000255 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000256 vbar['command'] = text.yview
257 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000258 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000259 fontWeight = 'normal'
260 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000261 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000262 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200263 idleConf.GetOption('main', 'EditorWindow',
264 'font-size', type='int'),
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000265 fontWeight))
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
319 self.recent_files_menu = Menu(self.menubar)
320 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)
480 menudict[name] = menu = Menu(mbar, name=name)
481 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
485 menudict['application'] = menu = Menu(mbar, name='apple')
486 mbar.add_cascade(label='IDLE', menu=menu)
487
David Scherer7aced172000-08-15 01:13:23 +0000488 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000489 self.base_helpmenu_length = self.menudict['help'].index(END)
490 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000491
492 def postwindowsmenu(self):
493 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000494 menu = self.menudict['windows']
495 end = menu.index("end")
496 if end is None:
497 end = -1
498 if end > self.wmenu_end:
499 menu.delete(self.wmenu_end+1, end)
500 WindowList.add_windows_to_menu(menu)
501
502 rmenu = None
503
504 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000505 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
506 if not self.rmenu:
507 self.make_rmenu()
508 rmenu = self.rmenu
509 self.event = event
510 iswin = sys.platform[:3] == 'win'
511 if iswin:
512 self.text.config(cursor="arrow")
Andrew Svetlov5018db72012-11-01 22:39:14 +0200513
Roger Serwy231a8fd2013-04-07 12:15:52 -0500514 for item in self.rmenu_specs:
515 try:
516 label, eventname, verify_state = item
517 except ValueError: # see issue1207589
518 continue
519
Andrew Svetlov5018db72012-11-01 22:39:14 +0200520 if verify_state is None:
521 continue
522 state = getattr(self, verify_state)()
523 rmenu.entryconfigure(label, state=state)
524
David Scherer7aced172000-08-15 01:13:23 +0000525 rmenu.tk_popup(event.x_root, event.y_root)
526 if iswin:
527 self.text.config(cursor="ibeam")
528
529 rmenu_specs = [
Andrew Svetlov5018db72012-11-01 22:39:14 +0200530 # ("Label", "<<virtual-event>>", "statefuncname"), ...
531 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000532 ]
533
534 def make_rmenu(self):
535 rmenu = Menu(self.text, tearoff=0)
Roger Serwy231a8fd2013-04-07 12:15:52 -0500536 for item in self.rmenu_specs:
537 label, eventname = item[0], item[1]
Andrew Svetlov5018db72012-11-01 22:39:14 +0200538 if label is not None:
539 def command(text=self.text, eventname=eventname):
540 text.event_generate(eventname)
541 rmenu.add_command(label=label, command=command)
542 else:
543 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000544 self.rmenu = rmenu
545
Andrew Svetlov5018db72012-11-01 22:39:14 +0200546 def rmenu_check_cut(self):
547 return self.rmenu_check_copy()
548
549 def rmenu_check_copy(self):
550 try:
551 indx = self.text.index('sel.first')
552 except TclError:
553 return 'disabled'
554 else:
555 return 'normal' if indx else 'disabled'
556
557 def rmenu_check_paste(self):
558 try:
559 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
560 except TclError:
561 return 'disabled'
562 else:
563 return 'normal'
564
David Scherer7aced172000-08-15 01:13:23 +0000565 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000566 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000567
Steven M. Gava3b55a892001-11-21 05:56:26 +0000568 def config_dialog(self, event=None):
569 configDialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedy7a162072014-10-22 20:15:12 -0400570 def config_extensions_dialog(self, event=None):
571 configDialog.ConfigExtensionsDialog(self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000572
David Scherer7aced172000-08-15 01:13:23 +0000573 def help_dialog(self, event=None):
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500574 if self.root:
575 parent = self.root
576 else:
577 parent = self.top
578 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000579
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000580 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000581 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000582 try:
583 os.startfile(self.help_url)
584 except WindowsError as why:
585 tkMessageBox.showerror(title='Document Start Failure',
586 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000587 else:
588 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000589 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000590
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000591 def cut(self,event):
592 self.text.event_generate("<<Cut>>")
593 return "break"
594
595 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000596 if not self.text.tag_ranges("sel"):
597 # There is no selection, so do nothing and maybe interrupt.
598 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000599 self.text.event_generate("<<Copy>>")
600 return "break"
601
602 def paste(self,event):
603 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000604 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000605 return "break"
606
David Scherer7aced172000-08-15 01:13:23 +0000607 def select_all(self, event=None):
608 self.text.tag_add("sel", "1.0", "end-1c")
609 self.text.mark_set("insert", "1.0")
610 self.text.see("insert")
611 return "break"
612
613 def remove_selection(self, event=None):
614 self.text.tag_remove("sel", "1.0", "end")
615 self.text.see("insert")
616
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000617 def move_at_edge_if_selection(self, edge_index):
618 """Cursor move begins at start or end of selection
619
620 When a left/right cursor key is pressed create and return to Tkinter a
621 function which causes a cursor move from the associated edge of the
622 selection.
623
624 """
625 self_text_index = self.text.index
626 self_text_mark_set = self.text.mark_set
627 edges_table = ("sel.first+1c", "sel.last-1c")
628 def move_at_edge(event):
629 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
630 try:
631 self_text_index("sel.first")
632 self_text_mark_set("insert", edges_table[edge_index])
633 except TclError:
634 pass
635 return move_at_edge
636
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000637 def del_word_left(self, event):
638 self.text.event_generate('<Meta-Delete>')
639 return "break"
640
641 def del_word_right(self, event):
642 self.text.event_generate('<Meta-d>')
643 return "break"
644
Steven M. Gavac5976402002-01-04 03:06:08 +0000645 def find_event(self, event):
646 SearchDialog.find(self.text)
647 return "break"
648
649 def find_again_event(self, event):
650 SearchDialog.find_again(self.text)
651 return "break"
652
653 def find_selection_event(self, event):
654 SearchDialog.find_selection(self.text)
655 return "break"
656
657 def find_in_files_event(self, event):
658 GrepDialog.grep(self.text, self.io, self.flist)
659 return "break"
660
661 def replace_event(self, event):
662 ReplaceDialog.replace(self.text)
663 return "break"
664
665 def goto_line_event(self, event):
666 text = self.text
667 lineno = tkSimpleDialog.askinteger("Goto",
668 "Go to line number:",parent=text)
669 if lineno is None:
670 return "break"
671 if lineno <= 0:
672 text.bell()
673 return "break"
674 text.mark_set("insert", "%d.0" % lineno)
675 text.see("insert")
676
David Scherer7aced172000-08-15 01:13:23 +0000677 def open_module(self, event=None):
678 # XXX Shouldn't this be in IOBinding or in FileList?
679 try:
680 name = self.text.get("sel.first", "sel.last")
681 except TclError:
682 name = ""
683 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000684 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000685 name = tkSimpleDialog.askstring("Module",
686 "Enter the name of a Python module\n"
687 "to search on sys.path and open:",
688 parent=self.text, initialvalue=name)
689 if name:
690 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000691 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000692 return
David Scherer7aced172000-08-15 01:13:23 +0000693 # XXX Ought to insert current file's directory in front of path
694 try:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400695 (f, file_path, (suffix, mode, mtype)) = _find_module(name)
Terry Jan Reedy2b149862013-06-29 00:59:34 -0400696 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000697 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
698 return
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400699 if mtype != imp.PY_SOURCE:
David Scherer7aced172000-08-15 01:13:23 +0000700 tkMessageBox.showerror("Unsupported type",
701 "%s is not a source module" % name, parent=self.text)
702 return
703 if f:
704 f.close()
705 if self.flist:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400706 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000707 else:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400708 self.io.loadfile(file_path)
709 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000710
711 def open_class_browser(self, event=None):
712 filename = self.io.filename
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400713 if not (self.__class__.__name__ == 'PyShellEditorWindow'
714 and filename):
715 filename = self.open_module()
716 if filename is None:
717 return
David Scherer7aced172000-08-15 01:13:23 +0000718 head, tail = os.path.split(filename)
719 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000720 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000721 ClassBrowser.ClassBrowser(self.flist, base, [head])
722
723 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000724 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000725 PathBrowser.PathBrowser(self.flist)
726
727 def gotoline(self, lineno):
728 if lineno is not None and lineno > 0:
729 self.text.mark_set("insert", "%d.0" % lineno)
730 self.text.tag_remove("sel", "1.0", "end")
731 self.text.tag_add("sel", "insert", "insert +1l")
732 self.center()
733
734 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000735 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000736 return True
David Scherer7aced172000-08-15 01:13:23 +0000737 base, ext = os.path.splitext(os.path.basename(filename))
738 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000739 return True
David Scherer7aced172000-08-15 01:13:23 +0000740 try:
741 f = open(filename)
742 line = f.readline()
743 f.close()
744 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000745 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000746 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000747
748 def close_hook(self):
749 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000750 self.flist.unregister_maybe_terminate(self)
751 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000752
753 def set_close_hook(self, close_hook):
754 self.close_hook = close_hook
755
756 def filename_change_hook(self):
757 if self.flist:
758 self.flist.filename_changed_edit(self)
759 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000760 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000761 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000762
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000763 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000764 if self.color:
765 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000766 if self.ispythonsource(self.io.filename):
767 self.color = self.ColorDelegator()
768 # can add more colorizers here...
769 if self.color:
770 self.per.removefilter(self.undo)
771 self.per.insertfilter(self.color)
772 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000773
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000774 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000775 if not self.color:
776 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000777 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000778 self.per.removefilter(self.color)
779 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000780
Steven M. Gavab77d3432002-03-02 07:16:21 +0000781 def ResetColorizer(self):
Terry Jan Reedy13755382014-10-09 18:44:26 -0400782 "Update the color theme"
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000783 # Called from self.filename_change_hook and from configDialog.py
784 self._rmcolorizer()
785 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000786 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000787 normal_colors = idleConf.GetHighlight(theme, 'normal')
788 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
789 select_colors = idleConf.GetHighlight(theme, 'hilite')
790 self.text.config(
791 foreground=normal_colors['foreground'],
792 background=normal_colors['background'],
793 insertbackground=cursor_color,
794 selectforeground=select_colors['foreground'],
795 selectbackground=select_colors['background'],
796 )
David Scherer7aced172000-08-15 01:13:23 +0000797
Steven M. Gavab1585412002-03-12 00:21:56 +0000798 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000799 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000800 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000801 fontWeight='normal'
802 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
803 fontWeight='bold'
804 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200805 idleConf.GetOption('main','EditorWindow','font-size',
806 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000807 fontWeight))
808
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000809 def RemoveKeybindings(self):
810 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000811 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000812 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000813 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000814 self.text.event_delete(event, *keylist)
815 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000816 xkeydefs = idleConf.GetExtensionBindings(extensionName)
817 if xkeydefs:
818 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000819 self.text.event_delete(event, *keylist)
820
821 def ApplyKeybindings(self):
822 "Update the keybindings after they are changed"
823 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000824 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000825 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000826 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000827 xkeydefs = idleConf.GetExtensionBindings(extensionName)
828 if xkeydefs:
829 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000830 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000831 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000832 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000833 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000834 for item in menu[1]:
835 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000836 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000837 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000838 menu = self.menudict[menubarItem]
Ned Deily14ef0c82013-07-20 14:38:24 -0700839 end = menu.index(END)
840 if end is None:
841 # Skip empty menus
842 continue
843 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000844 for index in range(0, end):
845 if menu.type(index) == 'command':
846 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000847 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000848 itemName = menu.entrycget(index, 'label')
849 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000850 if menubarItem in menuEventDict:
851 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000852 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000853 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000854 accel = get_accelerator(keydefs, event)
855 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000856
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000857 def set_notabs_indentwidth(self):
858 "Update the indentwidth if changed and not using tabs in this window"
859 # Called from configDialog.py
860 if not self.usetabs:
861 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
862 type='int')
863
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000864 def reset_help_menu_entries(self):
865 "Update the additional help entries on the Help menu"
866 help_list = idleConf.GetAllExtraHelpSourcesList()
867 helpmenu = self.menudict['help']
868 # first delete the extra help entries, if any
869 helpmenu_length = helpmenu.index(END)
870 if helpmenu_length > self.base_helpmenu_length:
871 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
872 # then rebuild them
873 if help_list:
874 helpmenu.add_separator()
875 for entry in help_list:
876 cmd = self.__extra_help_callback(entry[1])
877 helpmenu.add_command(label=entry[0], command=cmd)
878 # and update the menu dictionary
879 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000880
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000881 def __extra_help_callback(self, helpfile):
882 "Create a callback with the helpfile value frozen at definition time"
883 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000884 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000885 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000886 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000887 try:
888 os.startfile(helpfile)
889 except WindowsError as why:
890 tkMessageBox.showerror(title='Document Start Failure',
891 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000892 else:
893 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000894 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000895
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000896 def update_recent_files_list(self, new_file=None):
897 "Load and update the recent files list and menus"
898 rf_list = []
899 if os.path.exists(self.recent_files_path):
Terry Jan Reedyf9489432013-08-04 15:39:56 -0400900 with open(self.recent_files_path, 'r') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000901 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000902 if new_file:
903 new_file = os.path.abspath(new_file) + '\n'
904 if new_file in rf_list:
905 rf_list.remove(new_file) # move to top
906 rf_list.insert(0, new_file)
907 # clean and save the recent files list
908 bad_paths = []
909 for path in rf_list:
910 if '\0' in path or not os.path.exists(path[0:-1]):
911 bad_paths.append(path)
912 rf_list = [path for path in rf_list if path not in bad_paths]
913 ulchars = "1234567890ABCDEFGHIJK"
914 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000915 try:
Ned Deily40ad0412011-12-14 14:57:43 -0800916 with open(self.recent_files_path, 'w') as rf_file:
917 rf_file.writelines(rf_list)
918 except IOError as err:
919 if not getattr(self.root, "recentfilelist_error_displayed", False):
920 self.root.recentfilelist_error_displayed = True
921 tkMessageBox.showerror(title='IDLE Error',
922 message='Unable to update Recent Files list:\n%s'
923 % str(err),
924 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000925 # for each edit window instance, construct the recent files menu
926 for instance in self.top.instance_dict.keys():
927 menu = instance.recent_files_menu
Ned Deily71489842012-05-29 10:42:34 -0700928 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000929 for i, file_name in enumerate(rf_list):
930 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000931 # make unicode string to display non-ASCII chars correctly
932 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000933 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000934 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000935 command=callback,
936 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000937
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000938 def __recent_file_callback(self, file_name):
939 def open_recent_file(fn_closure=file_name):
940 self.io.open(editFile=fn_closure)
941 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000942
David Scherer7aced172000-08-15 01:13:23 +0000943 def saved_change_hook(self):
944 short = self.short_title()
945 long = self.long_title()
946 if short and long:
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400947 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000948 elif short:
949 title = short
950 elif long:
951 title = long
952 else:
953 title = "Untitled"
954 icon = short or long or title
955 if not self.get_saved():
956 title = "*%s*" % title
957 icon = "*%s" % icon
958 self.top.wm_title(title)
959 self.top.wm_iconname(icon)
960
961 def get_saved(self):
962 return self.undo.get_saved()
963
964 def set_saved(self, flag):
965 self.undo.set_saved(flag)
966
967 def reset_undo(self):
968 self.undo.reset_undo()
969
970 def short_title(self):
971 filename = self.io.filename
972 if filename:
973 filename = os.path.basename(filename)
Terry Jan Reedy59243652014-01-23 00:36:37 -0500974 else:
975 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000976 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400977 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000978
979 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000980 # return unicode string to display non-ASCII chars correctly
981 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000982
983 def center_insert_event(self, event):
984 self.center()
985
986 def center(self, mark="insert"):
987 text = self.text
988 top, bot = self.getwindowlines()
989 lineno = self.getlineno(mark)
990 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000991 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000992 text.yview(float(newtop))
993
994 def getwindowlines(self):
995 text = self.text
996 top = self.getlineno("@0,0")
997 bot = self.getlineno("@0,65535")
998 if top == bot and text.winfo_height() == 1:
999 # Geometry manager hasn't run yet
1000 height = int(text['height'])
1001 bot = top + height - 1
1002 return top, bot
1003
1004 def getlineno(self, mark="insert"):
1005 text = self.text
1006 return int(float(text.index(mark)))
1007
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001008 def get_geometry(self):
1009 "Return (width, height, x, y)"
1010 geom = self.top.wm_geometry()
1011 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
1012 tuple = (map(int, m.groups()))
1013 return tuple
1014
David Scherer7aced172000-08-15 01:13:23 +00001015 def close_event(self, event):
1016 self.close()
1017
1018 def maybesave(self):
1019 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001020 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001021 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001022 self.top.deiconify()
1023 self.top.lower()
1024 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001025 return self.io.maybesave()
1026
1027 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001028 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +00001029 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001030 self._close()
1031 return reply
1032
1033 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001034 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001035 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001036 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001037 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001038 self.io.close()
1039 self.io = None
1040 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001041 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001042 self.color.close(False)
1043 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001044 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001045 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001046 self.per.close()
1047 self.per = None
1048 self.top.destroy()
1049 if self.close_hook:
1050 # unless override: unregister from flist, terminate if last window
1051 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001052
1053 def load_extensions(self):
1054 self.extensions = {}
1055 self.load_standard_extensions()
1056
1057 def unload_extensions(self):
1058 for ins in self.extensions.values():
1059 if hasattr(ins, "close"):
1060 ins.close()
1061 self.extensions = {}
1062
1063 def load_standard_extensions(self):
1064 for name in self.get_standard_extension_names():
1065 try:
1066 self.load_extension(name)
1067 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001068 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +00001069 import traceback
1070 traceback.print_exc()
1071
1072 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001073 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001074
1075 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001076 try:
1077 mod = __import__(name, globals(), locals(), [])
1078 except ImportError:
1079 print "\nFailed to import extension: ", name
1080 return
David Scherer7aced172000-08-15 01:13:23 +00001081 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001082 keydefs = idleConf.GetExtensionBindings(name)
1083 if hasattr(cls, "menudefs"):
1084 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001085 ins = cls(self)
1086 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001087 if keydefs:
1088 self.apply_bindings(keydefs)
1089 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001090 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001091 while methodname[:1] == '<':
1092 methodname = methodname[1:]
1093 while methodname[-1:] == '>':
1094 methodname = methodname[:-1]
1095 methodname = methodname + "_event"
1096 if hasattr(ins, methodname):
1097 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001098
1099 def apply_bindings(self, keydefs=None):
1100 if keydefs is None:
1101 keydefs = self.Bindings.default_keydefs
1102 text = self.text
1103 text.keydefs = keydefs
1104 for event, keylist in keydefs.items():
1105 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001106 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001107
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001108 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001109 """Add appropriate entries to the menus and submenus
1110
1111 Menus that are absent or None in self.menudict are ignored.
1112 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001113 if menudefs is None:
1114 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001115 if keydefs is None:
1116 keydefs = self.Bindings.default_keydefs
1117 menudict = self.menudict
1118 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001119 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001120 menu = menudict.get(mname)
1121 if not menu:
1122 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001123 for entry in entrylist:
1124 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001125 menu.add_separator()
1126 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001127 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001128 checkbutton = (label[:1] == '!')
1129 if checkbutton:
1130 label = label[1:]
1131 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001132 accelerator = get_accelerator(keydefs, eventname)
1133 def command(text=text, eventname=eventname):
1134 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001135 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001136 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001137 menu.add_checkbutton(label=label, underline=underline,
1138 command=command, accelerator=accelerator,
1139 variable=var)
1140 else:
1141 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001142 command=command,
1143 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001144
1145 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001146 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001147 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001148 value = var.get()
1149 return value
1150 else:
1151 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001152
1153 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001154 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001155 if var:
1156 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001157 else:
1158 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001159
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001160 def get_var_obj(self, name, vartype=None):
1161 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001162 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001163 # create a Tkinter variable object with self.text as master:
1164 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001165 return var
1166
1167 # Tk implementations of "virtual text methods" -- each platform
1168 # reusing IDLE's support code needs to define these for its GUI's
1169 # flavor of widget.
1170
1171 # Is character at text_index in a Python string? Return 0 for
1172 # "guaranteed no", true for anything else. This info is expensive
1173 # to compute ab initio, but is probably already known by the
1174 # platform's colorizer.
1175
1176 def is_char_in_string(self, text_index):
1177 if self.color:
1178 # Return true iff colorizer hasn't (re)gotten this far
1179 # yet, or the character is tagged as being in a string
1180 return self.text.tag_prevrange("TODO", text_index) or \
1181 "STRING" in self.text.tag_names(text_index)
1182 else:
1183 # The colorizer is missing: assume the worst
1184 return 1
1185
1186 # If a selection is defined in the text widget, return (start,
1187 # end) as Tkinter text indices, otherwise return (None, None)
1188 def get_selection_indices(self):
1189 try:
1190 first = self.text.index("sel.first")
1191 last = self.text.index("sel.last")
1192 return first, last
1193 except TclError:
1194 return None, None
1195
1196 # Return the text widget's current view of what a tab stop means
1197 # (equivalent width in spaces).
1198
1199 def get_tabwidth(self):
1200 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1201 return int(current)
1202
1203 # Set the text widget's current view of what a tab stop means.
1204
1205 def set_tabwidth(self, newtabwidth):
1206 text = self.text
1207 if self.get_tabwidth() != newtabwidth:
1208 pixels = text.tk.call("font", "measure", text["font"],
1209 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001210 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001211 text.configure(tabs=pixels)
1212
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001213 # If ispythonsource and guess are true, guess a good value for
1214 # indentwidth based on file content (if possible), and if
1215 # indentwidth != tabwidth set usetabs false.
1216 # In any case, adjust the Text widget's view of what a tab
1217 # character means.
1218
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001219 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001220 if guess and ispythonsource:
1221 i = self.guess_indent()
1222 if 2 <= i <= 8:
1223 self.indentwidth = i
1224 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001225 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001226 self.set_tabwidth(self.tabwidth)
1227
1228 def smart_backspace_event(self, event):
1229 text = self.text
1230 first, last = self.get_selection_indices()
1231 if first and last:
1232 text.delete(first, last)
1233 text.mark_set("insert", first)
1234 return "break"
1235 # Delete whitespace left, until hitting a real char or closest
1236 # preceding virtual tab stop.
1237 chars = text.get("insert linestart", "insert")
1238 if chars == '':
1239 if text.compare("insert", ">", "1.0"):
1240 # easy: delete preceding newline
1241 text.delete("insert-1c")
1242 else:
1243 text.bell() # at start of buffer
1244 return "break"
1245 if chars[-1] not in " \t":
1246 # easy: delete preceding real char
1247 text.delete("insert-1c")
1248 return "break"
1249 # Ick. It may require *inserting* spaces if we back up over a
1250 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001251 tabwidth = self.tabwidth
1252 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001253 assert have > 0
1254 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001255 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001256 if self.context_use_ps1:
1257 last_line_of_prompt = sys.ps1.split('\n')[-1]
1258 else:
1259 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001260 ncharsdeleted = 0
1261 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001262 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001263 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001264 chars = chars[:-1]
1265 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001266 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001267 if have <= want or chars[-1] not in " \t":
1268 break
1269 text.undo_block_start()
1270 text.delete("insert-%dc" % ncharsdeleted, "insert")
1271 if have < want:
1272 text.insert("insert", ' ' * (want - have))
1273 text.undo_block_stop()
1274 return "break"
1275
1276 def smart_indent_event(self, event):
1277 # if intraline selection:
1278 # delete it
1279 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001280 # do indent-region
1281 # else:
1282 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001283 text = self.text
1284 first, last = self.get_selection_indices()
1285 text.undo_block_start()
1286 try:
1287 if first and last:
1288 if index2line(first) != index2line(last):
1289 return self.indent_region_event(event)
1290 text.delete(first, last)
1291 text.mark_set("insert", first)
1292 prefix = text.get("insert linestart", "insert")
1293 raw, effective = classifyws(prefix, self.tabwidth)
1294 if raw == len(prefix):
1295 # only whitespace to the left
1296 self.reindent_to(effective + self.indentwidth)
1297 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001298 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001299 if self.usetabs:
1300 pad = '\t'
1301 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001302 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001303 n = self.indentwidth
1304 pad = ' ' * (n - effective % n)
1305 text.insert("insert", pad)
1306 text.see("insert")
1307 return "break"
1308 finally:
1309 text.undo_block_stop()
1310
1311 def newline_and_indent_event(self, event):
1312 text = self.text
1313 first, last = self.get_selection_indices()
1314 text.undo_block_start()
1315 try:
1316 if first and last:
1317 text.delete(first, last)
1318 text.mark_set("insert", first)
1319 line = text.get("insert linestart", "insert")
1320 i, n = 0, len(line)
1321 while i < n and line[i] in " \t":
1322 i = i+1
1323 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001324 # the cursor is in or at leading indentation in a continuation
1325 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001326 text.insert("insert linestart", '\n')
1327 return "break"
1328 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001329 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001330 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001331 last_line_of_prompt = sys.ps1.split('\n')[-1]
1332 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001333 line = line[:-1]
1334 i = i+1
1335 if i:
1336 text.delete("insert - %d chars" % i, "insert")
1337 # strip whitespace after insert point
1338 while text.get("insert") in " \t":
1339 text.delete("insert")
1340 # start new line
1341 text.insert("insert", '\n')
1342
1343 # adjust indentation for continuations and block
1344 # open/close first need to find the last stmt
1345 lno = index2line(text.index('insert'))
1346 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001347 if not self.context_use_ps1:
1348 for context in self.num_context_lines:
1349 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001350 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001351 rawtext = text.get(startatindex, "insert")
1352 y.set_str(rawtext)
1353 bod = y.find_good_parse_start(
1354 self.context_use_ps1,
1355 self._build_char_in_string_func(startatindex))
1356 if bod is not None or startat == 1:
1357 break
1358 y.set_lo(bod or 0)
1359 else:
1360 r = text.tag_prevrange("console", "insert")
1361 if r:
1362 startatindex = r[1]
1363 else:
1364 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001365 rawtext = text.get(startatindex, "insert")
1366 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001367 y.set_lo(0)
1368
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001369 c = y.get_continuation_type()
1370 if c != PyParse.C_NONE:
1371 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001372 if c == PyParse.C_STRING_FIRST_LINE:
1373 # after the first line of a string; do not indent at all
1374 pass
1375 elif c == PyParse.C_STRING_NEXT_LINES:
1376 # inside a string which started before this line;
1377 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001378 text.insert("insert", indent)
1379 elif c == PyParse.C_BRACKET:
1380 # line up with the first (if any) element of the
1381 # last open bracket structure; else indent one
1382 # level beyond the indent of the line with the
1383 # last open bracket
1384 self.reindent_to(y.compute_bracket_indent())
1385 elif c == PyParse.C_BACKSLASH:
1386 # if more than one line in this stmt already, just
1387 # mimic the current indent; else if initial line
1388 # has a start on an assignment stmt, indent to
1389 # beyond leftmost =; else to beyond first chunk of
1390 # non-whitespace on initial line
1391 if y.get_num_lines_in_stmt() > 1:
1392 text.insert("insert", indent)
1393 else:
1394 self.reindent_to(y.compute_backslash_indent())
1395 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001396 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001397 return "break"
1398
1399 # This line starts a brand new stmt; indent relative to
1400 # indentation of initial line of closest preceding
1401 # interesting stmt.
1402 indent = y.get_base_indent_string()
1403 text.insert("insert", indent)
1404 if y.is_block_opener():
1405 self.smart_indent_event(event)
1406 elif indent and y.is_block_closer():
1407 self.smart_backspace_event(event)
1408 return "break"
1409 finally:
1410 text.see("insert")
1411 text.undo_block_stop()
1412
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001413 # Our editwin provides a is_char_in_string function that works
1414 # with a Tk text index, but PyParse only knows about offsets into
1415 # a string. This builds a function for PyParse that accepts an
1416 # offset.
1417
1418 def _build_char_in_string_func(self, startindex):
1419 def inner(offset, _startindex=startindex,
1420 _icis=self.is_char_in_string):
1421 return _icis(_startindex + "+%dc" % offset)
1422 return inner
1423
1424 def indent_region_event(self, event):
1425 head, tail, chars, lines = self.get_region()
1426 for pos in range(len(lines)):
1427 line = lines[pos]
1428 if line:
1429 raw, effective = classifyws(line, self.tabwidth)
1430 effective = effective + self.indentwidth
1431 lines[pos] = self._make_blanks(effective) + line[raw:]
1432 self.set_region(head, tail, chars, lines)
1433 return "break"
1434
1435 def dedent_region_event(self, event):
1436 head, tail, chars, lines = self.get_region()
1437 for pos in range(len(lines)):
1438 line = lines[pos]
1439 if line:
1440 raw, effective = classifyws(line, self.tabwidth)
1441 effective = max(effective - self.indentwidth, 0)
1442 lines[pos] = self._make_blanks(effective) + line[raw:]
1443 self.set_region(head, tail, chars, lines)
1444 return "break"
1445
1446 def comment_region_event(self, event):
1447 head, tail, chars, lines = self.get_region()
1448 for pos in range(len(lines) - 1):
1449 line = lines[pos]
1450 lines[pos] = '##' + line
1451 self.set_region(head, tail, chars, lines)
1452
1453 def uncomment_region_event(self, event):
1454 head, tail, chars, lines = self.get_region()
1455 for pos in range(len(lines)):
1456 line = lines[pos]
1457 if not line:
1458 continue
1459 if line[:2] == '##':
1460 line = line[2:]
1461 elif line[:1] == '#':
1462 line = line[1:]
1463 lines[pos] = line
1464 self.set_region(head, tail, chars, lines)
1465
1466 def tabify_region_event(self, event):
1467 head, tail, chars, lines = self.get_region()
1468 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001469 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001470 for pos in range(len(lines)):
1471 line = lines[pos]
1472 if line:
1473 raw, effective = classifyws(line, tabwidth)
1474 ntabs, nspaces = divmod(effective, tabwidth)
1475 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1476 self.set_region(head, tail, chars, lines)
1477
1478 def untabify_region_event(self, event):
1479 head, tail, chars, lines = self.get_region()
1480 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001481 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001482 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001483 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001484 self.set_region(head, tail, chars, lines)
1485
1486 def toggle_tabs_event(self, event):
1487 if self.askyesno(
1488 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001489 "Turn tabs " + ("on", "off")[self.usetabs] +
1490 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001491 ("will be", "remains at")[self.usetabs] + " 8." +
1492 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001493 parent=self.text):
1494 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001495 # Try to prevent inconsistent indentation.
1496 # User must change indent width manually after using tabs.
1497 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001498 return "break"
1499
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001500 # XXX this isn't bound to anything -- see tabwidth comments
1501## def change_tabwidth_event(self, event):
1502## new = self._asktabwidth()
1503## if new != self.tabwidth:
1504## self.tabwidth = new
1505## self.set_indentation_params(0, guess=0)
1506## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001507
1508 def change_indentwidth_event(self, event):
1509 new = self.askinteger(
1510 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001511 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001512 parent=self.text,
1513 initialvalue=self.indentwidth,
1514 minvalue=2,
1515 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001516 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001517 self.indentwidth = new
1518 return "break"
1519
1520 def get_region(self):
1521 text = self.text
1522 first, last = self.get_selection_indices()
1523 if first and last:
1524 head = text.index(first + " linestart")
1525 tail = text.index(last + "-1c lineend +1c")
1526 else:
1527 head = text.index("insert linestart")
1528 tail = text.index("insert lineend +1c")
1529 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001530 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001531 return head, tail, chars, lines
1532
1533 def set_region(self, head, tail, chars, lines):
1534 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001535 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001536 if newchars == chars:
1537 text.bell()
1538 return
1539 text.tag_remove("sel", "1.0", "end")
1540 text.mark_set("insert", head)
1541 text.undo_block_start()
1542 text.delete(head, tail)
1543 text.insert(head, newchars)
1544 text.undo_block_stop()
1545 text.tag_add("sel", head, "insert")
1546
1547 # Make string that displays as n leading blanks.
1548
1549 def _make_blanks(self, n):
1550 if self.usetabs:
1551 ntabs, nspaces = divmod(n, self.tabwidth)
1552 return '\t' * ntabs + ' ' * nspaces
1553 else:
1554 return ' ' * n
1555
1556 # Delete from beginning of line to insert point, then reinsert
1557 # column logical (meaning use tabs if appropriate) spaces.
1558
1559 def reindent_to(self, column):
1560 text = self.text
1561 text.undo_block_start()
1562 if text.compare("insert linestart", "!=", "insert"):
1563 text.delete("insert linestart", "insert")
1564 if column:
1565 text.insert("insert", self._make_blanks(column))
1566 text.undo_block_stop()
1567
1568 def _asktabwidth(self):
1569 return self.askinteger(
1570 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001571 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001572 parent=self.text,
1573 initialvalue=self.indentwidth,
1574 minvalue=2,
Roger Serwy75b249c2013-04-06 20:26:53 -05001575 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001576
1577 # Guess indentwidth from text content.
1578 # Return guessed indentwidth. This should not be believed unless
1579 # it's in a reasonable range (e.g., it will be 0 if no indented
1580 # blocks are found).
1581
1582 def guess_indent(self):
1583 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1584 if opener and indented:
1585 raw, indentsmall = classifyws(opener, self.tabwidth)
1586 raw, indentlarge = classifyws(indented, self.tabwidth)
1587 else:
1588 indentsmall = indentlarge = 0
1589 return indentlarge - indentsmall
1590
1591# "line.col" -> line, as an int
1592def index2line(index):
1593 return int(float(index))
1594
1595# Look at the leading whitespace in s.
1596# Return pair (# of leading ws characters,
1597# effective # of leading blanks after expanding
1598# tabs to width tabwidth)
1599
1600def classifyws(s, tabwidth):
1601 raw = effective = 0
1602 for ch in s:
1603 if ch == ' ':
1604 raw = raw + 1
1605 effective = effective + 1
1606 elif ch == '\t':
1607 raw = raw + 1
1608 effective = (effective // tabwidth + 1) * tabwidth
1609 else:
1610 break
1611 return raw, effective
1612
1613import tokenize
1614_tokenize = tokenize
1615del tokenize
1616
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001617class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001618
1619 # .run() chews over the Text widget, looking for a block opener
1620 # and the stmt following it. Returns a pair,
1621 # (line containing block opener, line containing stmt)
1622 # Either or both may be None.
1623
1624 def __init__(self, text, tabwidth):
1625 self.text = text
1626 self.tabwidth = tabwidth
1627 self.i = self.finished = 0
1628 self.blkopenline = self.indentedline = None
1629
1630 def readline(self):
1631 if self.finished:
1632 return ""
1633 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001634 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001635 if self.text.compare(mark, ">=", "end"):
1636 return ""
1637 return self.text.get(mark, mark + " lineend+1c")
1638
1639 def tokeneater(self, type, token, start, end, line,
1640 INDENT=_tokenize.INDENT,
1641 NAME=_tokenize.NAME,
1642 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1643 if self.finished:
1644 pass
1645 elif type == NAME and token in OPENERS:
1646 self.blkopenline = line
1647 elif type == INDENT and self.blkopenline:
1648 self.indentedline = line
1649 self.finished = 1
1650
1651 def run(self):
1652 save_tabsize = _tokenize.tabsize
1653 _tokenize.tabsize = self.tabwidth
1654 try:
1655 try:
1656 _tokenize.tokenize(self.readline, self.tokeneater)
Serhiy Storchaka61006a22012-12-27 21:34:23 +02001657 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001658 # since we cut off the tokenizer early, we can trigger
1659 # spurious errors
1660 pass
1661 finally:
1662 _tokenize.tabsize = save_tabsize
1663 return self.blkopenline, self.indentedline
1664
1665### end autoindent code ###
1666
David Scherer7aced172000-08-15 01:13:23 +00001667def prepstr(s):
1668 # Helper to extract the underscore from a string, e.g.
1669 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001670 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001671 if i >= 0:
1672 s = s[:i] + s[i+1:]
1673 return i, s
1674
1675
1676keynames = {
1677 'bracketleft': '[',
1678 'bracketright': ']',
1679 'slash': '/',
1680}
1681
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001682def get_accelerator(keydefs, eventname):
1683 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001684 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1685 # if not keylist:
Ned Deily57847df2014-03-27 20:47:04 -07001686 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily60651532011-01-31 00:52:49 +00001687 "<<open-module>>",
1688 "<<goto-line>>",
1689 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001690 return ""
1691 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001692 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001693 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1694 s = re.sub("Key-", "", s)
1695 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1696 s = re.sub("Control-", "Ctrl-", s)
1697 s = re.sub("-", "+", s)
1698 s = re.sub("><", " ", s)
1699 s = re.sub("<", "", s)
1700 s = re.sub(">", "", s)
1701 return s
1702
1703
1704def fixwordbreaks(root):
1705 # Make sure that Tk's double-click and next/previous word
1706 # operations use our definition of a word (i.e. an identifier)
1707 tk = root.tk
1708 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1709 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1710 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1711
1712
Terry Jan Reedycf834762014-10-17 01:31:29 -04001713def _editor_window(parent): # htest #
1714 # error if close master window first - timer event, after script
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001715 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001716 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001717 if sys.argv[1:]:
1718 filename = sys.argv[1]
1719 else:
1720 filename = None
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001721 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001722 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001723 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycf834762014-10-17 01:31:29 -04001724 # Does not stop error, neither does following
1725 # edit.text.bind("<<close-window>>", edit.close_event)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001726
David Scherer7aced172000-08-15 01:13:23 +00001727
1728if __name__ == '__main__':
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001729 from idlelib.idle_test.htest import run
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001730 run(_help_dialog, _editor_window)