blob: 8ec0f3294dcc10c82dabad5524fd861dab41b75e [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
12from idlelib import idlever
13from idlelib import WindowList
14from idlelib import SearchDialog
15from idlelib import GrepDialog
16from idlelib import ReplaceDialog
17from idlelib import PyParse
18from idlelib.configHandler import idleConf
19from idlelib import aboutDialog, textView, configDialog
20from idlelib import macosxSupport
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):
75 self.parent = None # parent of help window
76 self.dlg = None # the help window iteself
77
78 def display(self, parent, near=None):
79 """ Display the help dialog.
80
81 parent - parent widget for the help window
82
83 near - a Toplevel widget (e.g. EditorWindow or PyShell)
84 to use as a reference for placing the help window
85 """
86 if self.dlg is None:
87 self.show_dialog(parent)
88 if near:
89 self.nearwindow(near)
90
91 def show_dialog(self, parent):
92 self.parent = parent
93 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
94 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
95 dlg.bind('<Destroy>', self.destroy, '+')
96
97 def nearwindow(self, near):
98 # Place the help dialog near the window specified by parent.
99 # Note - this may not reposition the window in Metacity
100 # if "/apps/metacity/general/disable_workarounds" is enabled
101 dlg = self.dlg
102 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
103 dlg.withdraw()
104 dlg.geometry("=+%d+%d" % geom)
105 dlg.deiconify()
106 dlg.lift()
107
108 def destroy(self, ev=None):
109 self.dlg = None
110 self.parent = None
111
112helpDialog = HelpDialog() # singleton instance
Terry Jan Reedy43458462014-05-19 00:12:00 -0400113def _help_dialog(parent): # wrapper for htest
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -0400114 helpDialog.show_dialog(parent)
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500115
116
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +0000117class EditorWindow(object):
Florent Xiclunad630c042010-04-02 07:24:52 +0000118 from idlelib.Percolator import Percolator
119 from idlelib.ColorDelegator import ColorDelegator
120 from idlelib.UndoDelegator import UndoDelegator
121 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
122 from idlelib import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +0000123 from Tkinter import Toplevel
Florent Xiclunad630c042010-04-02 07:24:52 +0000124 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +0000125
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000126 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000127
128 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000129 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +0000130 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000131 if sys.platform.count('linux'):
132 # look for html docs in a couple of standard places
133 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
134 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
135 dochome = '/var/www/html/python/index.html'
136 else:
137 basepath = '/usr/share/doc/' # standard location
138 dochome = os.path.join(basepath, pyver,
139 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000140 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +0000141 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +0000142 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000143 if os.path.isfile(chmfile):
144 dochome = chmfile
Ned Deily57847df2014-03-27 20:47:04 -0700145 elif sys.platform == 'darwin':
146 # documentation may be stored inside a python framework
Ronald Oussoren19302d92006-06-11 14:33:36 +0000147 dochome = os.path.join(sys.prefix,
148 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000149 dochome = os.path.normpath(dochome)
150 if os.path.isfile(dochome):
151 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +0000152 if sys.platform == 'darwin':
153 # Safari requires real file:-URLs
154 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000155 else:
Terry Jan Reedyb17c1e02014-09-19 22:54:09 -0400156 EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000157 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +0000158 self.flist = flist
159 root = root or flist.root
160 self.root = root
Kurt B. Kaiserb3c4d162006-07-24 17:13:23 +0000161 try:
162 sys.ps1
163 except AttributeError:
164 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000165 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000166 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000167 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000168 self.tkinter_vars = flist.vars
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200169 #self.top.instance_dict makes flist.inversedict available to
170 #configDialog.py so it can access all EditorWindow instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000171 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000172 else:
173 self.tkinter_vars = {} # keys: Tkinter event names
174 # values: Tkinter variable instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000175 self.top.instance_dict = {}
176 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000177 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000178 self.text_frame = text_frame = Frame(top)
Martin v. Löwis4ebbefe2006-11-22 08:50:02 +0000179 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200180 self.width = idleConf.GetOption('main','EditorWindow','width', type='int')
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000181 text_options = {
182 'name': 'text',
183 'padx': 5,
184 'wrap': 'none',
185 'width': self.width,
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200186 'height': idleConf.GetOption('main', 'EditorWindow', 'height', type='int')}
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000187 if TkVersion >= 8.5:
188 # Starting with tk 8.5 we have to set the new tabstyle option
189 # to 'wordprocessor' to achieve the same display of tabs as in
190 # older tk versions.
191 text_options['tabstyle'] = 'wordprocessor'
192 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000193 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000194
195 self.createmenubar()
196 self.apply_bindings()
197
198 self.top.protocol("WM_DELETE_WINDOW", self.close)
199 self.top.bind("<<close-window>>", self.close_event)
Ned Deily57847df2014-03-27 20:47:04 -0700200 if macosxSupport.isAquaTk():
Ronald Oussoren17db4952006-07-23 09:41:09 +0000201 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000202 text.bind('<<close-window>>', self.close_event)
R. David Murray3f752ab2010-12-18 17:22:18 +0000203 # Some OS X systems have only one mouse button,
204 # so use control-click for pulldown menus there.
205 # (Note, AquaTk defines <2> as the right button if
206 # present and the Tk Text widget already binds <2>.)
207 text.bind("<Control-Button-1>",self.right_menu_event)
208 else:
209 # Elsewhere, use right-click for pulldown menus.
210 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000211 text.bind("<<cut>>", self.cut)
212 text.bind("<<copy>>", self.copy)
213 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000214 text.bind("<<center-insert>>", self.center_insert_event)
215 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000216 text.bind("<<python-docs>>", self.python_docs)
217 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000218 text.bind("<<open-config-dialog>>", self.config_dialog)
Terry Jan Reedy7a162072014-10-22 20:15:12 -0400219 text.bind("<<open-config-extensions-dialog>>",
220 self.config_extensions_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000221 text.bind("<<open-module>>", self.open_module)
222 text.bind("<<do-nothing>>", lambda event: "break")
223 text.bind("<<select-all>>", self.select_all)
224 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000225 text.bind("<<find>>", self.find_event)
226 text.bind("<<find-again>>", self.find_again_event)
227 text.bind("<<find-in-files>>", self.find_in_files_event)
228 text.bind("<<find-selection>>", self.find_selection_event)
229 text.bind("<<replace>>", self.replace_event)
230 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000231 text.bind("<<smart-backspace>>",self.smart_backspace_event)
232 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
233 text.bind("<<smart-indent>>",self.smart_indent_event)
234 text.bind("<<indent-region>>",self.indent_region_event)
235 text.bind("<<dedent-region>>",self.dedent_region_event)
236 text.bind("<<comment-region>>",self.comment_region_event)
237 text.bind("<<uncomment-region>>",self.uncomment_region_event)
238 text.bind("<<tabify-region>>",self.tabify_region_event)
239 text.bind("<<untabify-region>>",self.untabify_region_event)
240 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
241 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000242 text.bind("<Left>", self.move_at_edge_if_selection(0))
243 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000244 text.bind("<<del-word-left>>", self.del_word_left)
245 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000246 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000247
David Scherer7aced172000-08-15 01:13:23 +0000248 if flist:
249 flist.inversedict[self] = key
250 if key:
251 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000252 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000253 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
254 text.bind("<<open-class-browser>>", self.open_class_browser)
255 text.bind("<<open-path-browser>>", self.open_path_browser)
256
Steven M. Gava898a3652001-10-07 11:10:44 +0000257 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000258 vbar['command'] = text.yview
259 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000260 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000261 fontWeight = 'normal'
262 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000263 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000264 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200265 idleConf.GetOption('main', 'EditorWindow',
266 'font-size', type='int'),
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000267 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000268 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
269 text.pack(side=TOP, fill=BOTH, expand=1)
270 text.focus_set()
271
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000272 # usetabs true -> literal tab characters are used by indent and
273 # dedent cmds, possibly mixed with spaces if
274 # indentwidth is not a multiple of tabwidth,
275 # which will cause Tabnanny to nag!
276 # false -> tab characters are converted to spaces by indent
277 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000278 # Although use-spaces=0 can be configured manually in config-main.def,
279 # configuration of tabs v. spaces is not supported in the configuration
280 # dialog. IDLE promotes the preferred Python indentation: use spaces!
281 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
282 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000283
284 # tabwidth is the display width of a literal tab character.
285 # CAUTION: telling Tk to use anything other than its default
286 # tab setting causes it to use an entirely different tabbing algorithm,
287 # treating tab stops as fixed distances from the left margin.
288 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000289 self.tabwidth = 8 # must remain 8 until Tk is fixed.
290
291 # indentwidth is the number of screen characters per indent level.
292 # The recommended Python indentation is four spaces.
293 self.indentwidth = self.tabwidth
294 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000295
296 # If context_use_ps1 is true, parsing searches back for a ps1 line;
297 # else searches for a popular (if, def, ...) Python stmt.
298 self.context_use_ps1 = False
299
300 # When searching backwards for a reliable place to begin parsing,
301 # first start num_context_lines[0] lines back, then
302 # num_context_lines[1] lines back if that didn't work, and so on.
303 # The last value should be huge (larger than the # of lines in a
304 # conceivable file).
305 # Making the initial values larger slows things down more often.
306 self.num_context_lines = 50, 500, 5000000
307
David Scherer7aced172000-08-15 01:13:23 +0000308 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000309
310 self.undo = undo = self.UndoDelegator()
311 per.insertfilter(undo)
312 text.undo_block_start = undo.undo_block_start
313 text.undo_block_stop = undo.undo_block_stop
314 undo.set_saved_change_hook(self.saved_change_hook)
315
316 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000317 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000318 io.set_filename_change_hook(self.filename_change_hook)
319
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000320 # Create the recent files submenu
321 self.recent_files_menu = Menu(self.menubar)
322 self.menudict['file'].insert_cascade(3, label='Recent Files',
323 underline=0,
324 menu=self.recent_files_menu)
325 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000326
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000327 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000328 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000329 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000330 io.loadfile(filename)
331 else:
332 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000333 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000334 self.saved_change_hook()
335
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000336 self.set_indentation_params(self.ispythonsource(filename))
337
David Scherer7aced172000-08-15 01:13:23 +0000338 self.load_extensions()
339
340 menu = self.menudict.get('windows')
341 if menu:
342 end = menu.index("end")
343 if end is None:
344 end = -1
345 if end >= 0:
346 menu.add_separator()
347 end = end + 1
348 self.wmenu_end = end
349 WindowList.register_callback(self.postwindowsmenu)
350
351 # Some abstractions so IDLE extensions are cross-IDE
352 self.askyesno = tkMessageBox.askyesno
353 self.askinteger = tkSimpleDialog.askinteger
354 self.showerror = tkMessageBox.showerror
355
Roger Serwy02c0ed02013-05-20 22:13:39 -0500356 self._highlight_workaround() # Fix selection tags on Windows
357
358 def _highlight_workaround(self):
359 # On Windows, Tk removes painting of the selection
360 # tags which is different behavior than on Linux and Mac.
361 # See issue14146 for more information.
362 if not sys.platform.startswith('win'):
363 return
364
365 text = self.text
366 text.event_add("<<Highlight-FocusOut>>", "<FocusOut>")
367 text.event_add("<<Highlight-FocusIn>>", "<FocusIn>")
368 def highlight_fix(focus):
369 sel_range = text.tag_ranges("sel")
370 if sel_range:
371 if focus == 'out':
372 HILITE_CONFIG = idleConf.GetHighlight(
373 idleConf.CurrentTheme(), 'hilite')
374 text.tag_config("sel_fix", HILITE_CONFIG)
375 text.tag_raise("sel_fix")
376 text.tag_add("sel_fix", *sel_range)
377 elif focus == 'in':
378 text.tag_remove("sel_fix", "1.0", "end")
379
380 text.bind("<<Highlight-FocusOut>>",
381 lambda ev: highlight_fix("out"))
382 text.bind("<<Highlight-FocusIn>>",
383 lambda ev: highlight_fix("in"))
384
385
Martin v. Löwis307021f2005-11-27 16:59:04 +0000386 def _filename_to_unicode(self, filename):
387 """convert filename to unicode in order to display it in Tk"""
388 if isinstance(filename, unicode) or not filename:
389 return filename
390 else:
391 try:
392 return filename.decode(self.filesystemencoding)
393 except UnicodeDecodeError:
394 # XXX
395 try:
396 return filename.decode(self.encoding)
397 except UnicodeDecodeError:
398 # byte-to-byte conversion
399 return filename.decode('iso8859-1')
400
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000401 def new_callback(self, event):
402 dirname, basename = self.io.defaultfilename()
403 self.flist.new(dirname)
404 return "break"
405
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000406 def home_callback(self, event):
Kurt B. Kaiser020d3d92011-03-17 00:05:38 -0400407 if (event.state & 4) != 0 and event.keysym == "Home":
408 # state&4==Control. If <Control-Home>, use the Tk binding.
409 return
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000410 if self.text.index("iomark") and \
411 self.text.compare("iomark", "<=", "insert lineend") and \
412 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400413 # In Shell on input line, go to just after prompt
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000414 insertpt = int(self.text.index("iomark").split(".")[1])
415 else:
416 line = self.text.get("insert linestart", "insert lineend")
417 for insertpt in xrange(len(line)):
418 if line[insertpt] not in (' ','\t'):
419 break
420 else:
421 insertpt=len(line)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000422 lineat = int(self.text.index("insert").split('.')[1])
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000423 if insertpt == lineat:
424 insertpt = 0
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000425 dest = "insert linestart+"+str(insertpt)+"c"
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000426 if (event.state&1) == 0:
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400427 # shift was not pressed
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000428 self.text.tag_remove("sel", "1.0", "end")
429 else:
430 if not self.text.index("sel.first"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400431 self.text.mark_set("my_anchor", "insert") # there was no previous selection
432 else:
433 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
434 self.text.mark_set("my_anchor", "sel.first") # extend back
435 else:
436 self.text.mark_set("my_anchor", "sel.last") # extend forward
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000437 first = self.text.index(dest)
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400438 last = self.text.index("my_anchor")
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000439 if self.text.compare(first,">",last):
440 first,last = last,first
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000441 self.text.tag_remove("sel", "1.0", "end")
442 self.text.tag_add("sel", first, last)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000443 self.text.mark_set("insert", dest)
444 self.text.see("insert")
445 return "break"
446
David Scherer7aced172000-08-15 01:13:23 +0000447 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000448 self.status_bar = self.MultiStatusBar(self.top)
Ned Deily57847df2014-03-27 20:47:04 -0700449 if sys.platform == "darwin":
Ronald Oussoren19302d92006-06-11 14:33:36 +0000450 # Insert some padding to avoid obscuring some of the statusbar
451 # by the resize widget.
452 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000453 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
454 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
455 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000456 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
457 self.text.event_add("<<set-line-and-column>>",
458 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000459 self.text.after_idle(self.set_line_and_column)
460
461 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000462 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000463 self.status_bar.set_label('column', 'Col: %s' % column)
464 self.status_bar.set_label('line', 'Ln: %s' % line)
465
David Scherer7aced172000-08-15 01:13:23 +0000466 menu_specs = [
467 ("file", "_File"),
468 ("edit", "_Edit"),
469 ("format", "F_ormat"),
470 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000471 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000472 ("windows", "_Windows"),
473 ("help", "_Help"),
474 ]
475
Ned Deily57847df2014-03-27 20:47:04 -0700476 if sys.platform == "darwin":
Ronald Oussoren19302d92006-06-11 14:33:36 +0000477 menu_specs[-2] = ("windows", "_Window")
478
479
David Scherer7aced172000-08-15 01:13:23 +0000480 def createmenubar(self):
481 mbar = self.menubar
482 self.menudict = menudict = {}
483 for name, label in self.menu_specs:
484 underline, label = prepstr(label)
485 menudict[name] = menu = Menu(mbar, name=name)
486 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000487
Ned Deily57847df2014-03-27 20:47:04 -0700488 if macosxSupport.isCarbonTk():
Ronald Oussoren19302d92006-06-11 14:33:36 +0000489 # Insert the application menu
490 menudict['application'] = menu = Menu(mbar, name='apple')
491 mbar.add_cascade(label='IDLE', menu=menu)
492
David Scherer7aced172000-08-15 01:13:23 +0000493 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000494 self.base_helpmenu_length = self.menudict['help'].index(END)
495 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000496
497 def postwindowsmenu(self):
498 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000499 menu = self.menudict['windows']
500 end = menu.index("end")
501 if end is None:
502 end = -1
503 if end > self.wmenu_end:
504 menu.delete(self.wmenu_end+1, end)
505 WindowList.add_windows_to_menu(menu)
506
507 rmenu = None
508
509 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000510 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
511 if not self.rmenu:
512 self.make_rmenu()
513 rmenu = self.rmenu
514 self.event = event
515 iswin = sys.platform[:3] == 'win'
516 if iswin:
517 self.text.config(cursor="arrow")
Andrew Svetlov5018db72012-11-01 22:39:14 +0200518
Roger Serwy231a8fd2013-04-07 12:15:52 -0500519 for item in self.rmenu_specs:
520 try:
521 label, eventname, verify_state = item
522 except ValueError: # see issue1207589
523 continue
524
Andrew Svetlov5018db72012-11-01 22:39:14 +0200525 if verify_state is None:
526 continue
527 state = getattr(self, verify_state)()
528 rmenu.entryconfigure(label, state=state)
529
David Scherer7aced172000-08-15 01:13:23 +0000530 rmenu.tk_popup(event.x_root, event.y_root)
531 if iswin:
532 self.text.config(cursor="ibeam")
533
534 rmenu_specs = [
Andrew Svetlov5018db72012-11-01 22:39:14 +0200535 # ("Label", "<<virtual-event>>", "statefuncname"), ...
536 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000537 ]
538
539 def make_rmenu(self):
540 rmenu = Menu(self.text, tearoff=0)
Roger Serwy231a8fd2013-04-07 12:15:52 -0500541 for item in self.rmenu_specs:
542 label, eventname = item[0], item[1]
Andrew Svetlov5018db72012-11-01 22:39:14 +0200543 if label is not None:
544 def command(text=self.text, eventname=eventname):
545 text.event_generate(eventname)
546 rmenu.add_command(label=label, command=command)
547 else:
548 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000549 self.rmenu = rmenu
550
Andrew Svetlov5018db72012-11-01 22:39:14 +0200551 def rmenu_check_cut(self):
552 return self.rmenu_check_copy()
553
554 def rmenu_check_copy(self):
555 try:
556 indx = self.text.index('sel.first')
557 except TclError:
558 return 'disabled'
559 else:
560 return 'normal' if indx else 'disabled'
561
562 def rmenu_check_paste(self):
563 try:
564 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
565 except TclError:
566 return 'disabled'
567 else:
568 return 'normal'
569
David Scherer7aced172000-08-15 01:13:23 +0000570 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000571 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000572
Steven M. Gava3b55a892001-11-21 05:56:26 +0000573 def config_dialog(self, event=None):
574 configDialog.ConfigDialog(self.top,'Settings')
Terry Jan Reedy7a162072014-10-22 20:15:12 -0400575 def config_extensions_dialog(self, event=None):
576 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 Reedyadb87e22012-02-05 15:10:55 -0500579 if self.root:
580 parent = self.root
581 else:
582 parent = self.top
583 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000584
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000585 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000586 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000587 try:
588 os.startfile(self.help_url)
589 except WindowsError as why:
590 tkMessageBox.showerror(title='Document Start Failure',
591 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000592 else:
593 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000594 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000595
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000596 def cut(self,event):
597 self.text.event_generate("<<Cut>>")
598 return "break"
599
600 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000601 if not self.text.tag_ranges("sel"):
602 # There is no selection, so do nothing and maybe interrupt.
603 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000604 self.text.event_generate("<<Copy>>")
605 return "break"
606
607 def paste(self,event):
608 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000609 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000610 return "break"
611
David Scherer7aced172000-08-15 01:13:23 +0000612 def select_all(self, event=None):
613 self.text.tag_add("sel", "1.0", "end-1c")
614 self.text.mark_set("insert", "1.0")
615 self.text.see("insert")
616 return "break"
617
618 def remove_selection(self, event=None):
619 self.text.tag_remove("sel", "1.0", "end")
620 self.text.see("insert")
621
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000622 def move_at_edge_if_selection(self, edge_index):
623 """Cursor move begins at start or end of selection
624
625 When a left/right cursor key is pressed create and return to Tkinter a
626 function which causes a cursor move from the associated edge of the
627 selection.
628
629 """
630 self_text_index = self.text.index
631 self_text_mark_set = self.text.mark_set
632 edges_table = ("sel.first+1c", "sel.last-1c")
633 def move_at_edge(event):
634 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
635 try:
636 self_text_index("sel.first")
637 self_text_mark_set("insert", edges_table[edge_index])
638 except TclError:
639 pass
640 return move_at_edge
641
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000642 def del_word_left(self, event):
643 self.text.event_generate('<Meta-Delete>')
644 return "break"
645
646 def del_word_right(self, event):
647 self.text.event_generate('<Meta-d>')
648 return "break"
649
Steven M. Gavac5976402002-01-04 03:06:08 +0000650 def find_event(self, event):
651 SearchDialog.find(self.text)
652 return "break"
653
654 def find_again_event(self, event):
655 SearchDialog.find_again(self.text)
656 return "break"
657
658 def find_selection_event(self, event):
659 SearchDialog.find_selection(self.text)
660 return "break"
661
662 def find_in_files_event(self, event):
663 GrepDialog.grep(self.text, self.io, self.flist)
664 return "break"
665
666 def replace_event(self, event):
667 ReplaceDialog.replace(self.text)
668 return "break"
669
670 def goto_line_event(self, event):
671 text = self.text
672 lineno = tkSimpleDialog.askinteger("Goto",
673 "Go to line number:",parent=text)
674 if lineno is None:
675 return "break"
676 if lineno <= 0:
677 text.bell()
678 return "break"
679 text.mark_set("insert", "%d.0" % lineno)
680 text.see("insert")
681
David Scherer7aced172000-08-15 01:13:23 +0000682 def open_module(self, event=None):
683 # XXX Shouldn't this be in IOBinding or in FileList?
684 try:
685 name = self.text.get("sel.first", "sel.last")
686 except TclError:
687 name = ""
688 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000689 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000690 name = tkSimpleDialog.askstring("Module",
691 "Enter the name of a Python module\n"
692 "to search on sys.path and open:",
693 parent=self.text, initialvalue=name)
694 if name:
695 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000696 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000697 return
David Scherer7aced172000-08-15 01:13:23 +0000698 # XXX Ought to insert current file's directory in front of path
699 try:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400700 (f, file_path, (suffix, mode, mtype)) = _find_module(name)
Terry Jan Reedy2b149862013-06-29 00:59:34 -0400701 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000702 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
703 return
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400704 if mtype != imp.PY_SOURCE:
David Scherer7aced172000-08-15 01:13:23 +0000705 tkMessageBox.showerror("Unsupported type",
706 "%s is not a source module" % name, parent=self.text)
707 return
708 if f:
709 f.close()
710 if self.flist:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400711 self.flist.open(file_path)
David Scherer7aced172000-08-15 01:13:23 +0000712 else:
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400713 self.io.loadfile(file_path)
714 return file_path
David Scherer7aced172000-08-15 01:13:23 +0000715
716 def open_class_browser(self, event=None):
717 filename = self.io.filename
Terry Jan Reedy0234fd12014-10-15 22:01:23 -0400718 if not (self.__class__.__name__ == 'PyShellEditorWindow'
719 and filename):
720 filename = self.open_module()
721 if filename is None:
722 return
David Scherer7aced172000-08-15 01:13:23 +0000723 head, tail = os.path.split(filename)
724 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000725 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000726 ClassBrowser.ClassBrowser(self.flist, base, [head])
727
728 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000729 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000730 PathBrowser.PathBrowser(self.flist)
731
732 def gotoline(self, lineno):
733 if lineno is not None and lineno > 0:
734 self.text.mark_set("insert", "%d.0" % lineno)
735 self.text.tag_remove("sel", "1.0", "end")
736 self.text.tag_add("sel", "insert", "insert +1l")
737 self.center()
738
739 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000740 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000741 return True
David Scherer7aced172000-08-15 01:13:23 +0000742 base, ext = os.path.splitext(os.path.basename(filename))
743 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000744 return True
David Scherer7aced172000-08-15 01:13:23 +0000745 try:
746 f = open(filename)
747 line = f.readline()
748 f.close()
749 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000750 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000751 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000752
753 def close_hook(self):
754 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000755 self.flist.unregister_maybe_terminate(self)
756 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000757
758 def set_close_hook(self, close_hook):
759 self.close_hook = close_hook
760
761 def filename_change_hook(self):
762 if self.flist:
763 self.flist.filename_changed_edit(self)
764 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000765 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000766 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000767
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000768 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000769 if self.color:
770 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000771 if self.ispythonsource(self.io.filename):
772 self.color = self.ColorDelegator()
773 # can add more colorizers here...
774 if self.color:
775 self.per.removefilter(self.undo)
776 self.per.insertfilter(self.color)
777 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000778
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000779 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000780 if not self.color:
781 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000782 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000783 self.per.removefilter(self.color)
784 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000785
Steven M. Gavab77d3432002-03-02 07:16:21 +0000786 def ResetColorizer(self):
Terry Jan Reedy13755382014-10-09 18:44:26 -0400787 "Update the color theme"
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000788 # Called from self.filename_change_hook and from configDialog.py
789 self._rmcolorizer()
790 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000791 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000792 normal_colors = idleConf.GetHighlight(theme, 'normal')
793 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
794 select_colors = idleConf.GetHighlight(theme, 'hilite')
795 self.text.config(
796 foreground=normal_colors['foreground'],
797 background=normal_colors['background'],
798 insertbackground=cursor_color,
799 selectforeground=select_colors['foreground'],
800 selectbackground=select_colors['background'],
801 )
David Scherer7aced172000-08-15 01:13:23 +0000802
Steven M. Gavab1585412002-03-12 00:21:56 +0000803 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000804 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000805 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000806 fontWeight='normal'
807 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
808 fontWeight='bold'
809 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200810 idleConf.GetOption('main','EditorWindow','font-size',
811 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000812 fontWeight))
813
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000814 def RemoveKeybindings(self):
815 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000816 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000817 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000818 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000819 self.text.event_delete(event, *keylist)
820 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000821 xkeydefs = idleConf.GetExtensionBindings(extensionName)
822 if xkeydefs:
823 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000824 self.text.event_delete(event, *keylist)
825
826 def ApplyKeybindings(self):
827 "Update the keybindings after they are changed"
828 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000829 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000830 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000831 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000832 xkeydefs = idleConf.GetExtensionBindings(extensionName)
833 if xkeydefs:
834 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000835 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000836 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000837 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000838 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000839 for item in menu[1]:
840 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000841 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000842 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000843 menu = self.menudict[menubarItem]
Ned Deily14ef0c82013-07-20 14:38:24 -0700844 end = menu.index(END)
845 if end is None:
846 # Skip empty menus
847 continue
848 end += 1
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000849 for index in range(0, end):
850 if menu.type(index) == 'command':
851 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000852 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000853 itemName = menu.entrycget(index, 'label')
854 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000855 if menubarItem in menuEventDict:
856 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000857 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000858 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000859 accel = get_accelerator(keydefs, event)
860 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000861
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000862 def set_notabs_indentwidth(self):
863 "Update the indentwidth if changed and not using tabs in this window"
864 # Called from configDialog.py
865 if not self.usetabs:
866 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
867 type='int')
868
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000869 def reset_help_menu_entries(self):
870 "Update the additional help entries on the Help menu"
871 help_list = idleConf.GetAllExtraHelpSourcesList()
872 helpmenu = self.menudict['help']
873 # first delete the extra help entries, if any
874 helpmenu_length = helpmenu.index(END)
875 if helpmenu_length > self.base_helpmenu_length:
876 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
877 # then rebuild them
878 if help_list:
879 helpmenu.add_separator()
880 for entry in help_list:
881 cmd = self.__extra_help_callback(entry[1])
882 helpmenu.add_command(label=entry[0], command=cmd)
883 # and update the menu dictionary
884 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000885
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000886 def __extra_help_callback(self, helpfile):
887 "Create a callback with the helpfile value frozen at definition time"
888 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000889 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000890 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000891 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000892 try:
893 os.startfile(helpfile)
894 except WindowsError as why:
895 tkMessageBox.showerror(title='Document Start Failure',
896 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000897 else:
898 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000899 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000900
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000901 def update_recent_files_list(self, new_file=None):
902 "Load and update the recent files list and menus"
903 rf_list = []
904 if os.path.exists(self.recent_files_path):
Terry Jan Reedyf9489432013-08-04 15:39:56 -0400905 with open(self.recent_files_path, 'r') as rf_list_file:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000906 rf_list = rf_list_file.readlines()
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000907 if new_file:
908 new_file = os.path.abspath(new_file) + '\n'
909 if new_file in rf_list:
910 rf_list.remove(new_file) # move to top
911 rf_list.insert(0, new_file)
912 # clean and save the recent files list
913 bad_paths = []
914 for path in rf_list:
915 if '\0' in path or not os.path.exists(path[0:-1]):
916 bad_paths.append(path)
917 rf_list = [path for path in rf_list if path not in bad_paths]
918 ulchars = "1234567890ABCDEFGHIJK"
919 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000920 try:
Ned Deily40ad0412011-12-14 14:57:43 -0800921 with open(self.recent_files_path, 'w') as rf_file:
922 rf_file.writelines(rf_list)
923 except IOError as err:
924 if not getattr(self.root, "recentfilelist_error_displayed", False):
925 self.root.recentfilelist_error_displayed = True
926 tkMessageBox.showerror(title='IDLE Error',
927 message='Unable to update Recent Files list:\n%s'
928 % str(err),
929 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000930 # for each edit window instance, construct the recent files menu
931 for instance in self.top.instance_dict.keys():
932 menu = instance.recent_files_menu
Ned Deily71489842012-05-29 10:42:34 -0700933 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000934 for i, file_name in enumerate(rf_list):
935 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000936 # make unicode string to display non-ASCII chars correctly
937 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000938 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000939 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000940 command=callback,
941 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000942
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000943 def __recent_file_callback(self, file_name):
944 def open_recent_file(fn_closure=file_name):
945 self.io.open(editFile=fn_closure)
946 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000947
David Scherer7aced172000-08-15 01:13:23 +0000948 def saved_change_hook(self):
949 short = self.short_title()
950 long = self.long_title()
951 if short and long:
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400952 title = short + " - " + long + _py_version
David Scherer7aced172000-08-15 01:13:23 +0000953 elif short:
954 title = short
955 elif long:
956 title = long
957 else:
958 title = "Untitled"
959 icon = short or long or title
960 if not self.get_saved():
961 title = "*%s*" % title
962 icon = "*%s" % icon
963 self.top.wm_title(title)
964 self.top.wm_iconname(icon)
965
966 def get_saved(self):
967 return self.undo.get_saved()
968
969 def set_saved(self, flag):
970 self.undo.set_saved(flag)
971
972 def reset_undo(self):
973 self.undo.reset_undo()
974
975 def short_title(self):
976 filename = self.io.filename
977 if filename:
978 filename = os.path.basename(filename)
Terry Jan Reedy59243652014-01-23 00:36:37 -0500979 else:
980 filename = "Untitled"
Martin v. Löwis307021f2005-11-27 16:59:04 +0000981 # return unicode string to display non-ASCII chars correctly
Terry Jan Reedyc11633e2014-08-14 21:54:38 -0400982 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000983
984 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000985 # return unicode string to display non-ASCII chars correctly
986 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000987
988 def center_insert_event(self, event):
989 self.center()
990
991 def center(self, mark="insert"):
992 text = self.text
993 top, bot = self.getwindowlines()
994 lineno = self.getlineno(mark)
995 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000996 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000997 text.yview(float(newtop))
998
999 def getwindowlines(self):
1000 text = self.text
1001 top = self.getlineno("@0,0")
1002 bot = self.getlineno("@0,65535")
1003 if top == bot and text.winfo_height() == 1:
1004 # Geometry manager hasn't run yet
1005 height = int(text['height'])
1006 bot = top + height - 1
1007 return top, bot
1008
1009 def getlineno(self, mark="insert"):
1010 text = self.text
1011 return int(float(text.index(mark)))
1012
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001013 def get_geometry(self):
1014 "Return (width, height, x, y)"
1015 geom = self.top.wm_geometry()
1016 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
1017 tuple = (map(int, m.groups()))
1018 return tuple
1019
David Scherer7aced172000-08-15 01:13:23 +00001020 def close_event(self, event):
1021 self.close()
1022
1023 def maybesave(self):
1024 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001025 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001026 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001027 self.top.deiconify()
1028 self.top.lower()
1029 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001030 return self.io.maybesave()
1031
1032 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001033 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +00001034 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001035 self._close()
1036 return reply
1037
1038 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001039 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001040 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001041 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001042 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001043 self.io.close()
1044 self.io = None
1045 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001046 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001047 self.color.close(False)
1048 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001049 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001050 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001051 self.per.close()
1052 self.per = None
1053 self.top.destroy()
1054 if self.close_hook:
1055 # unless override: unregister from flist, terminate if last window
1056 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001057
1058 def load_extensions(self):
1059 self.extensions = {}
1060 self.load_standard_extensions()
1061
1062 def unload_extensions(self):
1063 for ins in self.extensions.values():
1064 if hasattr(ins, "close"):
1065 ins.close()
1066 self.extensions = {}
1067
1068 def load_standard_extensions(self):
1069 for name in self.get_standard_extension_names():
1070 try:
1071 self.load_extension(name)
1072 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001073 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +00001074 import traceback
1075 traceback.print_exc()
1076
1077 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001078 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001079
1080 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001081 try:
1082 mod = __import__(name, globals(), locals(), [])
1083 except ImportError:
1084 print "\nFailed to import extension: ", name
1085 return
David Scherer7aced172000-08-15 01:13:23 +00001086 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001087 keydefs = idleConf.GetExtensionBindings(name)
1088 if hasattr(cls, "menudefs"):
1089 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001090 ins = cls(self)
1091 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001092 if keydefs:
1093 self.apply_bindings(keydefs)
1094 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001095 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001096 while methodname[:1] == '<':
1097 methodname = methodname[1:]
1098 while methodname[-1:] == '>':
1099 methodname = methodname[:-1]
1100 methodname = methodname + "_event"
1101 if hasattr(ins, methodname):
1102 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001103
1104 def apply_bindings(self, keydefs=None):
1105 if keydefs is None:
1106 keydefs = self.Bindings.default_keydefs
1107 text = self.text
1108 text.keydefs = keydefs
1109 for event, keylist in keydefs.items():
1110 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001111 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001112
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001113 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001114 """Add appropriate entries to the menus and submenus
1115
1116 Menus that are absent or None in self.menudict are ignored.
1117 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001118 if menudefs is None:
1119 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001120 if keydefs is None:
1121 keydefs = self.Bindings.default_keydefs
1122 menudict = self.menudict
1123 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001124 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001125 menu = menudict.get(mname)
1126 if not menu:
1127 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001128 for entry in entrylist:
1129 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001130 menu.add_separator()
1131 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001132 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001133 checkbutton = (label[:1] == '!')
1134 if checkbutton:
1135 label = label[1:]
1136 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001137 accelerator = get_accelerator(keydefs, eventname)
1138 def command(text=text, eventname=eventname):
1139 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001140 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001141 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001142 menu.add_checkbutton(label=label, underline=underline,
1143 command=command, accelerator=accelerator,
1144 variable=var)
1145 else:
1146 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001147 command=command,
1148 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001149
1150 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001151 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001152 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001153 value = var.get()
1154 return value
1155 else:
1156 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001157
1158 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001159 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001160 if var:
1161 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001162 else:
1163 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001164
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001165 def get_var_obj(self, name, vartype=None):
1166 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001167 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001168 # create a Tkinter variable object with self.text as master:
1169 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001170 return var
1171
1172 # Tk implementations of "virtual text methods" -- each platform
1173 # reusing IDLE's support code needs to define these for its GUI's
1174 # flavor of widget.
1175
1176 # Is character at text_index in a Python string? Return 0 for
1177 # "guaranteed no", true for anything else. This info is expensive
1178 # to compute ab initio, but is probably already known by the
1179 # platform's colorizer.
1180
1181 def is_char_in_string(self, text_index):
1182 if self.color:
1183 # Return true iff colorizer hasn't (re)gotten this far
1184 # yet, or the character is tagged as being in a string
1185 return self.text.tag_prevrange("TODO", text_index) or \
1186 "STRING" in self.text.tag_names(text_index)
1187 else:
1188 # The colorizer is missing: assume the worst
1189 return 1
1190
1191 # If a selection is defined in the text widget, return (start,
1192 # end) as Tkinter text indices, otherwise return (None, None)
1193 def get_selection_indices(self):
1194 try:
1195 first = self.text.index("sel.first")
1196 last = self.text.index("sel.last")
1197 return first, last
1198 except TclError:
1199 return None, None
1200
1201 # Return the text widget's current view of what a tab stop means
1202 # (equivalent width in spaces).
1203
1204 def get_tabwidth(self):
1205 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1206 return int(current)
1207
1208 # Set the text widget's current view of what a tab stop means.
1209
1210 def set_tabwidth(self, newtabwidth):
1211 text = self.text
1212 if self.get_tabwidth() != newtabwidth:
1213 pixels = text.tk.call("font", "measure", text["font"],
1214 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001215 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001216 text.configure(tabs=pixels)
1217
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001218 # If ispythonsource and guess are true, guess a good value for
1219 # indentwidth based on file content (if possible), and if
1220 # indentwidth != tabwidth set usetabs false.
1221 # In any case, adjust the Text widget's view of what a tab
1222 # character means.
1223
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001224 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001225 if guess and ispythonsource:
1226 i = self.guess_indent()
1227 if 2 <= i <= 8:
1228 self.indentwidth = i
1229 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001230 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001231 self.set_tabwidth(self.tabwidth)
1232
1233 def smart_backspace_event(self, event):
1234 text = self.text
1235 first, last = self.get_selection_indices()
1236 if first and last:
1237 text.delete(first, last)
1238 text.mark_set("insert", first)
1239 return "break"
1240 # Delete whitespace left, until hitting a real char or closest
1241 # preceding virtual tab stop.
1242 chars = text.get("insert linestart", "insert")
1243 if chars == '':
1244 if text.compare("insert", ">", "1.0"):
1245 # easy: delete preceding newline
1246 text.delete("insert-1c")
1247 else:
1248 text.bell() # at start of buffer
1249 return "break"
1250 if chars[-1] not in " \t":
1251 # easy: delete preceding real char
1252 text.delete("insert-1c")
1253 return "break"
1254 # Ick. It may require *inserting* spaces if we back up over a
1255 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001256 tabwidth = self.tabwidth
1257 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001258 assert have > 0
1259 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001260 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001261 if self.context_use_ps1:
1262 last_line_of_prompt = sys.ps1.split('\n')[-1]
1263 else:
1264 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001265 ncharsdeleted = 0
1266 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001267 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001268 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001269 chars = chars[:-1]
1270 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001271 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001272 if have <= want or chars[-1] not in " \t":
1273 break
1274 text.undo_block_start()
1275 text.delete("insert-%dc" % ncharsdeleted, "insert")
1276 if have < want:
1277 text.insert("insert", ' ' * (want - have))
1278 text.undo_block_stop()
1279 return "break"
1280
1281 def smart_indent_event(self, event):
1282 # if intraline selection:
1283 # delete it
1284 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001285 # do indent-region
1286 # else:
1287 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001288 text = self.text
1289 first, last = self.get_selection_indices()
1290 text.undo_block_start()
1291 try:
1292 if first and last:
1293 if index2line(first) != index2line(last):
1294 return self.indent_region_event(event)
1295 text.delete(first, last)
1296 text.mark_set("insert", first)
1297 prefix = text.get("insert linestart", "insert")
1298 raw, effective = classifyws(prefix, self.tabwidth)
1299 if raw == len(prefix):
1300 # only whitespace to the left
1301 self.reindent_to(effective + self.indentwidth)
1302 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001303 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001304 if self.usetabs:
1305 pad = '\t'
1306 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001307 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001308 n = self.indentwidth
1309 pad = ' ' * (n - effective % n)
1310 text.insert("insert", pad)
1311 text.see("insert")
1312 return "break"
1313 finally:
1314 text.undo_block_stop()
1315
1316 def newline_and_indent_event(self, event):
1317 text = self.text
1318 first, last = self.get_selection_indices()
1319 text.undo_block_start()
1320 try:
1321 if first and last:
1322 text.delete(first, last)
1323 text.mark_set("insert", first)
1324 line = text.get("insert linestart", "insert")
1325 i, n = 0, len(line)
1326 while i < n and line[i] in " \t":
1327 i = i+1
1328 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001329 # the cursor is in or at leading indentation in a continuation
1330 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001331 text.insert("insert linestart", '\n')
1332 return "break"
1333 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001334 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001335 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001336 last_line_of_prompt = sys.ps1.split('\n')[-1]
1337 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001338 line = line[:-1]
1339 i = i+1
1340 if i:
1341 text.delete("insert - %d chars" % i, "insert")
1342 # strip whitespace after insert point
1343 while text.get("insert") in " \t":
1344 text.delete("insert")
1345 # start new line
1346 text.insert("insert", '\n')
1347
1348 # adjust indentation for continuations and block
1349 # open/close first need to find the last stmt
1350 lno = index2line(text.index('insert'))
1351 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001352 if not self.context_use_ps1:
1353 for context in self.num_context_lines:
1354 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001355 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001356 rawtext = text.get(startatindex, "insert")
1357 y.set_str(rawtext)
1358 bod = y.find_good_parse_start(
1359 self.context_use_ps1,
1360 self._build_char_in_string_func(startatindex))
1361 if bod is not None or startat == 1:
1362 break
1363 y.set_lo(bod or 0)
1364 else:
1365 r = text.tag_prevrange("console", "insert")
1366 if r:
1367 startatindex = r[1]
1368 else:
1369 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001370 rawtext = text.get(startatindex, "insert")
1371 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001372 y.set_lo(0)
1373
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001374 c = y.get_continuation_type()
1375 if c != PyParse.C_NONE:
1376 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001377 if c == PyParse.C_STRING_FIRST_LINE:
1378 # after the first line of a string; do not indent at all
1379 pass
1380 elif c == PyParse.C_STRING_NEXT_LINES:
1381 # inside a string which started before this line;
1382 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001383 text.insert("insert", indent)
1384 elif c == PyParse.C_BRACKET:
1385 # line up with the first (if any) element of the
1386 # last open bracket structure; else indent one
1387 # level beyond the indent of the line with the
1388 # last open bracket
1389 self.reindent_to(y.compute_bracket_indent())
1390 elif c == PyParse.C_BACKSLASH:
1391 # if more than one line in this stmt already, just
1392 # mimic the current indent; else if initial line
1393 # has a start on an assignment stmt, indent to
1394 # beyond leftmost =; else to beyond first chunk of
1395 # non-whitespace on initial line
1396 if y.get_num_lines_in_stmt() > 1:
1397 text.insert("insert", indent)
1398 else:
1399 self.reindent_to(y.compute_backslash_indent())
1400 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001401 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001402 return "break"
1403
1404 # This line starts a brand new stmt; indent relative to
1405 # indentation of initial line of closest preceding
1406 # interesting stmt.
1407 indent = y.get_base_indent_string()
1408 text.insert("insert", indent)
1409 if y.is_block_opener():
1410 self.smart_indent_event(event)
1411 elif indent and y.is_block_closer():
1412 self.smart_backspace_event(event)
1413 return "break"
1414 finally:
1415 text.see("insert")
1416 text.undo_block_stop()
1417
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001418 # Our editwin provides a is_char_in_string function that works
1419 # with a Tk text index, but PyParse only knows about offsets into
1420 # a string. This builds a function for PyParse that accepts an
1421 # offset.
1422
1423 def _build_char_in_string_func(self, startindex):
1424 def inner(offset, _startindex=startindex,
1425 _icis=self.is_char_in_string):
1426 return _icis(_startindex + "+%dc" % offset)
1427 return inner
1428
1429 def indent_region_event(self, event):
1430 head, tail, chars, lines = self.get_region()
1431 for pos in range(len(lines)):
1432 line = lines[pos]
1433 if line:
1434 raw, effective = classifyws(line, self.tabwidth)
1435 effective = effective + self.indentwidth
1436 lines[pos] = self._make_blanks(effective) + line[raw:]
1437 self.set_region(head, tail, chars, lines)
1438 return "break"
1439
1440 def dedent_region_event(self, event):
1441 head, tail, chars, lines = self.get_region()
1442 for pos in range(len(lines)):
1443 line = lines[pos]
1444 if line:
1445 raw, effective = classifyws(line, self.tabwidth)
1446 effective = max(effective - self.indentwidth, 0)
1447 lines[pos] = self._make_blanks(effective) + line[raw:]
1448 self.set_region(head, tail, chars, lines)
1449 return "break"
1450
1451 def comment_region_event(self, event):
1452 head, tail, chars, lines = self.get_region()
1453 for pos in range(len(lines) - 1):
1454 line = lines[pos]
1455 lines[pos] = '##' + line
1456 self.set_region(head, tail, chars, lines)
1457
1458 def uncomment_region_event(self, event):
1459 head, tail, chars, lines = self.get_region()
1460 for pos in range(len(lines)):
1461 line = lines[pos]
1462 if not line:
1463 continue
1464 if line[:2] == '##':
1465 line = line[2:]
1466 elif line[:1] == '#':
1467 line = line[1:]
1468 lines[pos] = line
1469 self.set_region(head, tail, chars, lines)
1470
1471 def tabify_region_event(self, event):
1472 head, tail, chars, lines = self.get_region()
1473 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001474 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001475 for pos in range(len(lines)):
1476 line = lines[pos]
1477 if line:
1478 raw, effective = classifyws(line, tabwidth)
1479 ntabs, nspaces = divmod(effective, tabwidth)
1480 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1481 self.set_region(head, tail, chars, lines)
1482
1483 def untabify_region_event(self, event):
1484 head, tail, chars, lines = self.get_region()
1485 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001486 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001487 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001488 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001489 self.set_region(head, tail, chars, lines)
1490
1491 def toggle_tabs_event(self, event):
1492 if self.askyesno(
1493 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001494 "Turn tabs " + ("on", "off")[self.usetabs] +
1495 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001496 ("will be", "remains at")[self.usetabs] + " 8." +
1497 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001498 parent=self.text):
1499 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001500 # Try to prevent inconsistent indentation.
1501 # User must change indent width manually after using tabs.
1502 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001503 return "break"
1504
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001505 # XXX this isn't bound to anything -- see tabwidth comments
1506## def change_tabwidth_event(self, event):
1507## new = self._asktabwidth()
1508## if new != self.tabwidth:
1509## self.tabwidth = new
1510## self.set_indentation_params(0, guess=0)
1511## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001512
1513 def change_indentwidth_event(self, event):
1514 new = self.askinteger(
1515 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001516 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001517 parent=self.text,
1518 initialvalue=self.indentwidth,
1519 minvalue=2,
1520 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001521 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001522 self.indentwidth = new
1523 return "break"
1524
1525 def get_region(self):
1526 text = self.text
1527 first, last = self.get_selection_indices()
1528 if first and last:
1529 head = text.index(first + " linestart")
1530 tail = text.index(last + "-1c lineend +1c")
1531 else:
1532 head = text.index("insert linestart")
1533 tail = text.index("insert lineend +1c")
1534 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001535 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001536 return head, tail, chars, lines
1537
1538 def set_region(self, head, tail, chars, lines):
1539 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001540 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001541 if newchars == chars:
1542 text.bell()
1543 return
1544 text.tag_remove("sel", "1.0", "end")
1545 text.mark_set("insert", head)
1546 text.undo_block_start()
1547 text.delete(head, tail)
1548 text.insert(head, newchars)
1549 text.undo_block_stop()
1550 text.tag_add("sel", head, "insert")
1551
1552 # Make string that displays as n leading blanks.
1553
1554 def _make_blanks(self, n):
1555 if self.usetabs:
1556 ntabs, nspaces = divmod(n, self.tabwidth)
1557 return '\t' * ntabs + ' ' * nspaces
1558 else:
1559 return ' ' * n
1560
1561 # Delete from beginning of line to insert point, then reinsert
1562 # column logical (meaning use tabs if appropriate) spaces.
1563
1564 def reindent_to(self, column):
1565 text = self.text
1566 text.undo_block_start()
1567 if text.compare("insert linestart", "!=", "insert"):
1568 text.delete("insert linestart", "insert")
1569 if column:
1570 text.insert("insert", self._make_blanks(column))
1571 text.undo_block_stop()
1572
1573 def _asktabwidth(self):
1574 return self.askinteger(
1575 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001576 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001577 parent=self.text,
1578 initialvalue=self.indentwidth,
1579 minvalue=2,
Roger Serwy75b249c2013-04-06 20:26:53 -05001580 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001581
1582 # Guess indentwidth from text content.
1583 # Return guessed indentwidth. This should not be believed unless
1584 # it's in a reasonable range (e.g., it will be 0 if no indented
1585 # blocks are found).
1586
1587 def guess_indent(self):
1588 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1589 if opener and indented:
1590 raw, indentsmall = classifyws(opener, self.tabwidth)
1591 raw, indentlarge = classifyws(indented, self.tabwidth)
1592 else:
1593 indentsmall = indentlarge = 0
1594 return indentlarge - indentsmall
1595
1596# "line.col" -> line, as an int
1597def index2line(index):
1598 return int(float(index))
1599
1600# Look at the leading whitespace in s.
1601# Return pair (# of leading ws characters,
1602# effective # of leading blanks after expanding
1603# tabs to width tabwidth)
1604
1605def classifyws(s, tabwidth):
1606 raw = effective = 0
1607 for ch in s:
1608 if ch == ' ':
1609 raw = raw + 1
1610 effective = effective + 1
1611 elif ch == '\t':
1612 raw = raw + 1
1613 effective = (effective // tabwidth + 1) * tabwidth
1614 else:
1615 break
1616 return raw, effective
1617
1618import tokenize
1619_tokenize = tokenize
1620del tokenize
1621
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001622class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001623
1624 # .run() chews over the Text widget, looking for a block opener
1625 # and the stmt following it. Returns a pair,
1626 # (line containing block opener, line containing stmt)
1627 # Either or both may be None.
1628
1629 def __init__(self, text, tabwidth):
1630 self.text = text
1631 self.tabwidth = tabwidth
1632 self.i = self.finished = 0
1633 self.blkopenline = self.indentedline = None
1634
1635 def readline(self):
1636 if self.finished:
1637 return ""
1638 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001639 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001640 if self.text.compare(mark, ">=", "end"):
1641 return ""
1642 return self.text.get(mark, mark + " lineend+1c")
1643
1644 def tokeneater(self, type, token, start, end, line,
1645 INDENT=_tokenize.INDENT,
1646 NAME=_tokenize.NAME,
1647 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1648 if self.finished:
1649 pass
1650 elif type == NAME and token in OPENERS:
1651 self.blkopenline = line
1652 elif type == INDENT and self.blkopenline:
1653 self.indentedline = line
1654 self.finished = 1
1655
1656 def run(self):
1657 save_tabsize = _tokenize.tabsize
1658 _tokenize.tabsize = self.tabwidth
1659 try:
1660 try:
1661 _tokenize.tokenize(self.readline, self.tokeneater)
Serhiy Storchaka61006a22012-12-27 21:34:23 +02001662 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001663 # since we cut off the tokenizer early, we can trigger
1664 # spurious errors
1665 pass
1666 finally:
1667 _tokenize.tabsize = save_tabsize
1668 return self.blkopenline, self.indentedline
1669
1670### end autoindent code ###
1671
David Scherer7aced172000-08-15 01:13:23 +00001672def prepstr(s):
1673 # Helper to extract the underscore from a string, e.g.
1674 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001675 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001676 if i >= 0:
1677 s = s[:i] + s[i+1:]
1678 return i, s
1679
1680
1681keynames = {
1682 'bracketleft': '[',
1683 'bracketright': ']',
1684 'slash': '/',
1685}
1686
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001687def get_accelerator(keydefs, eventname):
1688 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001689 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1690 # if not keylist:
Ned Deily57847df2014-03-27 20:47:04 -07001691 if (not keylist) or (macosxSupport.isCocoaTk() and eventname in {
Ned Deily60651532011-01-31 00:52:49 +00001692 "<<open-module>>",
1693 "<<goto-line>>",
1694 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001695 return ""
1696 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001697 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001698 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1699 s = re.sub("Key-", "", s)
1700 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1701 s = re.sub("Control-", "Ctrl-", s)
1702 s = re.sub("-", "+", s)
1703 s = re.sub("><", " ", s)
1704 s = re.sub("<", "", s)
1705 s = re.sub(">", "", s)
1706 return s
1707
1708
1709def fixwordbreaks(root):
1710 # Make sure that Tk's double-click and next/previous word
1711 # operations use our definition of a word (i.e. an identifier)
1712 tk = root.tk
1713 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1714 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1715 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1716
1717
Terry Jan Reedycf834762014-10-17 01:31:29 -04001718def _editor_window(parent): # htest #
1719 # error if close master window first - timer event, after script
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001720 root = parent
David Scherer7aced172000-08-15 01:13:23 +00001721 fixwordbreaks(root)
David Scherer7aced172000-08-15 01:13:23 +00001722 if sys.argv[1:]:
1723 filename = sys.argv[1]
1724 else:
1725 filename = None
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001726 macosxSupport.setupApp(root, None)
David Scherer7aced172000-08-15 01:13:23 +00001727 edit = EditorWindow(root=root, filename=filename)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001728 edit.text.bind("<<close-all-windows>>", edit.close_event)
Terry Jan Reedycf834762014-10-17 01:31:29 -04001729 # Does not stop error, neither does following
1730 # edit.text.bind("<<close-window>>", edit.close_event)
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001731
David Scherer7aced172000-08-15 01:13:23 +00001732
1733if __name__ == '__main__':
Terry Jan Reedy00b0bd52014-05-11 23:32:20 -04001734 from idlelib.idle_test.htest import run
Terry Jan Reedy985ef282014-05-27 02:47:38 -04001735 run(_help_dialog, _editor_window)