blob: 14c76de87ffd3784585a77026525bc0f8fe0c146 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
David Scherer7aced172000-08-15 01:13:23 +00003import re
4import imp
Georg Brandl6634bf22008-05-20 07:13:37 +00005from Tkinter import *
6import tkSimpleDialog
7import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00008import webbrowser
Florent Xiclunad630c042010-04-02 07:24:52 +00009
10from idlelib.MultiCall import MultiCallCreator
11from idlelib import idlever
12from 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
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000024def _sphinx_version():
25 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
26 major, minor, micro, level, serial = sys.version_info
27 release = '%s%s' % (major, minor)
28 if micro:
Benjamin Petersonfb234632009-06-13 15:42:23 +000029 release += '%s' % (micro,)
30 if level == 'candidate':
31 release += 'rc%s' % (serial,)
32 elif level != 'final':
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000033 release += '%s%s' % (level[0], serial)
34 return release
35
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000036def _find_module(fullname, path=None):
37 """Version of imp.find_module() that handles hierarchical module names"""
38
39 file = None
40 for tgt in fullname.split('.'):
41 if file is not None:
42 file.close() # close intermediate files
43 (file, filename, descr) = imp.find_module(tgt, path)
44 if descr[2] == imp.PY_SOURCE:
45 break # find but not load the source file
46 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000047 try:
48 path = module.__path__
49 except AttributeError:
50 raise ImportError, 'No source for module ' + module.__name__
Raymond Hettinger179816d2011-04-12 18:54:46 -070051 if descr[2] != imp.PY_SOURCE:
52 # If all of the above fails and didn't raise an exception,fallback
53 # to a straight import which can find __init__.py in a package.
54 m = __import__(fullname)
55 try:
56 filename = m.__file__
57 except AttributeError:
58 pass
59 else:
60 file = None
61 base, ext = os.path.splitext(filename)
62 if ext == '.pyc':
63 ext = '.py'
64 filename = base + ext
65 descr = filename, None, imp.PY_SOURCE
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000066 return file, filename, descr
67
Terry Jan Reedyadb87e22012-02-05 15:10:55 -050068
69class HelpDialog(object):
70
71 def __init__(self):
72 self.parent = None # parent of help window
73 self.dlg = None # the help window iteself
74
75 def display(self, parent, near=None):
76 """ Display the help dialog.
77
78 parent - parent widget for the help window
79
80 near - a Toplevel widget (e.g. EditorWindow or PyShell)
81 to use as a reference for placing the help window
82 """
83 if self.dlg is None:
84 self.show_dialog(parent)
85 if near:
86 self.nearwindow(near)
87
88 def show_dialog(self, parent):
89 self.parent = parent
90 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
91 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
92 dlg.bind('<Destroy>', self.destroy, '+')
93
94 def nearwindow(self, near):
95 # Place the help dialog near the window specified by parent.
96 # Note - this may not reposition the window in Metacity
97 # if "/apps/metacity/general/disable_workarounds" is enabled
98 dlg = self.dlg
99 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
100 dlg.withdraw()
101 dlg.geometry("=+%d+%d" % geom)
102 dlg.deiconify()
103 dlg.lift()
104
105 def destroy(self, ev=None):
106 self.dlg = None
107 self.parent = None
108
109helpDialog = HelpDialog() # singleton instance
110
111
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +0000112class EditorWindow(object):
Florent Xiclunad630c042010-04-02 07:24:52 +0000113 from idlelib.Percolator import Percolator
114 from idlelib.ColorDelegator import ColorDelegator
115 from idlelib.UndoDelegator import UndoDelegator
116 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
117 from idlelib import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +0000118 from Tkinter import Toplevel
Florent Xiclunad630c042010-04-02 07:24:52 +0000119 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +0000120
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000121 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000122
123 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000124 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +0000125 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000126 if sys.platform.count('linux'):
127 # look for html docs in a couple of standard places
128 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
129 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
130 dochome = '/var/www/html/python/index.html'
131 else:
132 basepath = '/usr/share/doc/' # standard location
133 dochome = os.path.join(basepath, pyver,
134 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000135 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +0000136 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +0000137 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000138 if os.path.isfile(chmfile):
139 dochome = chmfile
Ronald Oussoren19302d92006-06-11 14:33:36 +0000140 elif macosxSupport.runningAsOSXApp():
141 # documentation is stored inside the python framework
142 dochome = os.path.join(sys.prefix,
143 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000144 dochome = os.path.normpath(dochome)
145 if os.path.isfile(dochome):
146 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +0000147 if sys.platform == 'darwin':
148 # Safari requires real file:-URLs
149 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000150 else:
Raymond Hettinger7b4c2be2009-01-20 10:46:23 +0000151 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000152 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +0000153 self.flist = flist
154 root = root or flist.root
155 self.root = root
Kurt B. Kaiserb3c4d162006-07-24 17:13:23 +0000156 try:
157 sys.ps1
158 except AttributeError:
159 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000160 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000161 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000162 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000163 self.tkinter_vars = flist.vars
Ezio Melotti24b07bc2011-03-15 18:55:01 +0200164 #self.top.instance_dict makes flist.inversedict available to
165 #configDialog.py so it can access all EditorWindow instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000166 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000167 else:
168 self.tkinter_vars = {} # keys: Tkinter event names
169 # values: Tkinter variable instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000170 self.top.instance_dict = {}
171 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000172 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000173 self.text_frame = text_frame = Frame(top)
Martin v. Löwis4ebbefe2006-11-22 08:50:02 +0000174 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200175 self.width = idleConf.GetOption('main','EditorWindow','width', type='int')
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000176 text_options = {
177 'name': 'text',
178 'padx': 5,
179 'wrap': 'none',
180 'width': self.width,
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200181 'height': idleConf.GetOption('main', 'EditorWindow', 'height', type='int')}
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000182 if TkVersion >= 8.5:
183 # Starting with tk 8.5 we have to set the new tabstyle option
184 # to 'wordprocessor' to achieve the same display of tabs as in
185 # older tk versions.
186 text_options['tabstyle'] = 'wordprocessor'
187 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000188 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000189
190 self.createmenubar()
191 self.apply_bindings()
192
193 self.top.protocol("WM_DELETE_WINDOW", self.close)
194 self.top.bind("<<close-window>>", self.close_event)
Ronald Oussoren17db4952006-07-23 09:41:09 +0000195 if macosxSupport.runningAsOSXApp():
196 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000197 text.bind('<<close-window>>', self.close_event)
R. David Murray3f752ab2010-12-18 17:22:18 +0000198 # Some OS X systems have only one mouse button,
199 # so use control-click for pulldown menus there.
200 # (Note, AquaTk defines <2> as the right button if
201 # present and the Tk Text widget already binds <2>.)
202 text.bind("<Control-Button-1>",self.right_menu_event)
203 else:
204 # Elsewhere, use right-click for pulldown menus.
205 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000206 text.bind("<<cut>>", self.cut)
207 text.bind("<<copy>>", self.copy)
208 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000209 text.bind("<<center-insert>>", self.center_insert_event)
210 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000211 text.bind("<<python-docs>>", self.python_docs)
212 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000213 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000214 text.bind("<<open-module>>", self.open_module)
215 text.bind("<<do-nothing>>", lambda event: "break")
216 text.bind("<<select-all>>", self.select_all)
217 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000218 text.bind("<<find>>", self.find_event)
219 text.bind("<<find-again>>", self.find_again_event)
220 text.bind("<<find-in-files>>", self.find_in_files_event)
221 text.bind("<<find-selection>>", self.find_selection_event)
222 text.bind("<<replace>>", self.replace_event)
223 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000224 text.bind("<<smart-backspace>>",self.smart_backspace_event)
225 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
226 text.bind("<<smart-indent>>",self.smart_indent_event)
227 text.bind("<<indent-region>>",self.indent_region_event)
228 text.bind("<<dedent-region>>",self.dedent_region_event)
229 text.bind("<<comment-region>>",self.comment_region_event)
230 text.bind("<<uncomment-region>>",self.uncomment_region_event)
231 text.bind("<<tabify-region>>",self.tabify_region_event)
232 text.bind("<<untabify-region>>",self.untabify_region_event)
233 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
234 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000235 text.bind("<Left>", self.move_at_edge_if_selection(0))
236 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000237 text.bind("<<del-word-left>>", self.del_word_left)
238 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000239 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000240
David Scherer7aced172000-08-15 01:13:23 +0000241 if flist:
242 flist.inversedict[self] = key
243 if key:
244 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000245 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000246 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
247 text.bind("<<open-class-browser>>", self.open_class_browser)
248 text.bind("<<open-path-browser>>", self.open_path_browser)
249
Steven M. Gava898a3652001-10-07 11:10:44 +0000250 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000251 vbar['command'] = text.yview
252 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000253 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000254 fontWeight = 'normal'
255 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000256 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000257 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200258 idleConf.GetOption('main', 'EditorWindow',
259 'font-size', type='int'),
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000260 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000261 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
262 text.pack(side=TOP, fill=BOTH, expand=1)
263 text.focus_set()
264
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000265 # usetabs true -> literal tab characters are used by indent and
266 # dedent cmds, possibly mixed with spaces if
267 # indentwidth is not a multiple of tabwidth,
268 # which will cause Tabnanny to nag!
269 # false -> tab characters are converted to spaces by indent
270 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000271 # Although use-spaces=0 can be configured manually in config-main.def,
272 # configuration of tabs v. spaces is not supported in the configuration
273 # dialog. IDLE promotes the preferred Python indentation: use spaces!
274 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
275 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000276
277 # tabwidth is the display width of a literal tab character.
278 # CAUTION: telling Tk to use anything other than its default
279 # tab setting causes it to use an entirely different tabbing algorithm,
280 # treating tab stops as fixed distances from the left margin.
281 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000282 self.tabwidth = 8 # must remain 8 until Tk is fixed.
283
284 # indentwidth is the number of screen characters per indent level.
285 # The recommended Python indentation is four spaces.
286 self.indentwidth = self.tabwidth
287 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000288
289 # If context_use_ps1 is true, parsing searches back for a ps1 line;
290 # else searches for a popular (if, def, ...) Python stmt.
291 self.context_use_ps1 = False
292
293 # When searching backwards for a reliable place to begin parsing,
294 # first start num_context_lines[0] lines back, then
295 # num_context_lines[1] lines back if that didn't work, and so on.
296 # The last value should be huge (larger than the # of lines in a
297 # conceivable file).
298 # Making the initial values larger slows things down more often.
299 self.num_context_lines = 50, 500, 5000000
300
David Scherer7aced172000-08-15 01:13:23 +0000301 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000302
303 self.undo = undo = self.UndoDelegator()
304 per.insertfilter(undo)
305 text.undo_block_start = undo.undo_block_start
306 text.undo_block_stop = undo.undo_block_stop
307 undo.set_saved_change_hook(self.saved_change_hook)
308
309 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000310 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000311 io.set_filename_change_hook(self.filename_change_hook)
312
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000313 # Create the recent files submenu
314 self.recent_files_menu = Menu(self.menubar)
315 self.menudict['file'].insert_cascade(3, label='Recent Files',
316 underline=0,
317 menu=self.recent_files_menu)
318 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000319
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000320 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000321 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000322 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000323 io.loadfile(filename)
324 else:
325 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000326 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000327 self.saved_change_hook()
328
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000329 self.set_indentation_params(self.ispythonsource(filename))
330
David Scherer7aced172000-08-15 01:13:23 +0000331 self.load_extensions()
332
333 menu = self.menudict.get('windows')
334 if menu:
335 end = menu.index("end")
336 if end is None:
337 end = -1
338 if end >= 0:
339 menu.add_separator()
340 end = end + 1
341 self.wmenu_end = end
342 WindowList.register_callback(self.postwindowsmenu)
343
344 # Some abstractions so IDLE extensions are cross-IDE
345 self.askyesno = tkMessageBox.askyesno
346 self.askinteger = tkSimpleDialog.askinteger
347 self.showerror = tkMessageBox.showerror
348
Roger Serwy02c0ed02013-05-20 22:13:39 -0500349 self._highlight_workaround() # Fix selection tags on Windows
350
351 def _highlight_workaround(self):
352 # On Windows, Tk removes painting of the selection
353 # tags which is different behavior than on Linux and Mac.
354 # See issue14146 for more information.
355 if not sys.platform.startswith('win'):
356 return
357
358 text = self.text
359 text.event_add("<<Highlight-FocusOut>>", "<FocusOut>")
360 text.event_add("<<Highlight-FocusIn>>", "<FocusIn>")
361 def highlight_fix(focus):
362 sel_range = text.tag_ranges("sel")
363 if sel_range:
364 if focus == 'out':
365 HILITE_CONFIG = idleConf.GetHighlight(
366 idleConf.CurrentTheme(), 'hilite')
367 text.tag_config("sel_fix", HILITE_CONFIG)
368 text.tag_raise("sel_fix")
369 text.tag_add("sel_fix", *sel_range)
370 elif focus == 'in':
371 text.tag_remove("sel_fix", "1.0", "end")
372
373 text.bind("<<Highlight-FocusOut>>",
374 lambda ev: highlight_fix("out"))
375 text.bind("<<Highlight-FocusIn>>",
376 lambda ev: highlight_fix("in"))
377
378
Martin v. Löwis307021f2005-11-27 16:59:04 +0000379 def _filename_to_unicode(self, filename):
380 """convert filename to unicode in order to display it in Tk"""
381 if isinstance(filename, unicode) or not filename:
382 return filename
383 else:
384 try:
385 return filename.decode(self.filesystemencoding)
386 except UnicodeDecodeError:
387 # XXX
388 try:
389 return filename.decode(self.encoding)
390 except UnicodeDecodeError:
391 # byte-to-byte conversion
392 return filename.decode('iso8859-1')
393
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000394 def new_callback(self, event):
395 dirname, basename = self.io.defaultfilename()
396 self.flist.new(dirname)
397 return "break"
398
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000399 def home_callback(self, event):
Kurt B. Kaiser020d3d92011-03-17 00:05:38 -0400400 if (event.state & 4) != 0 and event.keysym == "Home":
401 # state&4==Control. If <Control-Home>, use the Tk binding.
402 return
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000403 if self.text.index("iomark") and \
404 self.text.compare("iomark", "<=", "insert lineend") and \
405 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400406 # In Shell on input line, go to just after prompt
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000407 insertpt = int(self.text.index("iomark").split(".")[1])
408 else:
409 line = self.text.get("insert linestart", "insert lineend")
410 for insertpt in xrange(len(line)):
411 if line[insertpt] not in (' ','\t'):
412 break
413 else:
414 insertpt=len(line)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000415 lineat = int(self.text.index("insert").split('.')[1])
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000416 if insertpt == lineat:
417 insertpt = 0
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000418 dest = "insert linestart+"+str(insertpt)+"c"
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000419 if (event.state&1) == 0:
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400420 # shift was not pressed
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000421 self.text.tag_remove("sel", "1.0", "end")
422 else:
423 if not self.text.index("sel.first"):
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400424 self.text.mark_set("my_anchor", "insert") # there was no previous selection
425 else:
426 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
427 self.text.mark_set("my_anchor", "sel.first") # extend back
428 else:
429 self.text.mark_set("my_anchor", "sel.last") # extend forward
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000430 first = self.text.index(dest)
Kurt B. Kaiser7548bce2011-03-25 17:48:27 -0400431 last = self.text.index("my_anchor")
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000432 if self.text.compare(first,">",last):
433 first,last = last,first
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000434 self.text.tag_remove("sel", "1.0", "end")
435 self.text.tag_add("sel", first, last)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000436 self.text.mark_set("insert", dest)
437 self.text.see("insert")
438 return "break"
439
David Scherer7aced172000-08-15 01:13:23 +0000440 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000441 self.status_bar = self.MultiStatusBar(self.top)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000442 if macosxSupport.runningAsOSXApp():
443 # Insert some padding to avoid obscuring some of the statusbar
444 # by the resize widget.
445 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000446 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
447 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
448 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000449 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
450 self.text.event_add("<<set-line-and-column>>",
451 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000452 self.text.after_idle(self.set_line_and_column)
453
454 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000455 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000456 self.status_bar.set_label('column', 'Col: %s' % column)
457 self.status_bar.set_label('line', 'Ln: %s' % line)
458
David Scherer7aced172000-08-15 01:13:23 +0000459 menu_specs = [
460 ("file", "_File"),
461 ("edit", "_Edit"),
462 ("format", "F_ormat"),
463 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000464 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000465 ("windows", "_Windows"),
466 ("help", "_Help"),
467 ]
468
Ronald Oussoren19302d92006-06-11 14:33:36 +0000469 if macosxSupport.runningAsOSXApp():
Ronald Oussoren19302d92006-06-11 14:33:36 +0000470 menu_specs[-2] = ("windows", "_Window")
471
472
David Scherer7aced172000-08-15 01:13:23 +0000473 def createmenubar(self):
474 mbar = self.menubar
475 self.menudict = menudict = {}
476 for name, label in self.menu_specs:
477 underline, label = prepstr(label)
478 menudict[name] = menu = Menu(mbar, name=name)
479 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000480
Ned Deily4a705502011-01-18 04:33:22 +0000481 if macosxSupport.isCarbonAquaTk(self.root):
Ronald Oussoren19302d92006-06-11 14:33:36 +0000482 # Insert the application menu
483 menudict['application'] = menu = Menu(mbar, name='apple')
484 mbar.add_cascade(label='IDLE', menu=menu)
485
David Scherer7aced172000-08-15 01:13:23 +0000486 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000487 self.base_helpmenu_length = self.menudict['help'].index(END)
488 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000489
490 def postwindowsmenu(self):
491 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000492 menu = self.menudict['windows']
493 end = menu.index("end")
494 if end is None:
495 end = -1
496 if end > self.wmenu_end:
497 menu.delete(self.wmenu_end+1, end)
498 WindowList.add_windows_to_menu(menu)
499
500 rmenu = None
501
502 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000503 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
504 if not self.rmenu:
505 self.make_rmenu()
506 rmenu = self.rmenu
507 self.event = event
508 iswin = sys.platform[:3] == 'win'
509 if iswin:
510 self.text.config(cursor="arrow")
Andrew Svetlov5018db72012-11-01 22:39:14 +0200511
Roger Serwy231a8fd2013-04-07 12:15:52 -0500512 for item in self.rmenu_specs:
513 try:
514 label, eventname, verify_state = item
515 except ValueError: # see issue1207589
516 continue
517
Andrew Svetlov5018db72012-11-01 22:39:14 +0200518 if verify_state is None:
519 continue
520 state = getattr(self, verify_state)()
521 rmenu.entryconfigure(label, state=state)
522
David Scherer7aced172000-08-15 01:13:23 +0000523 rmenu.tk_popup(event.x_root, event.y_root)
524 if iswin:
525 self.text.config(cursor="ibeam")
526
527 rmenu_specs = [
Andrew Svetlov5018db72012-11-01 22:39:14 +0200528 # ("Label", "<<virtual-event>>", "statefuncname"), ...
529 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000530 ]
531
532 def make_rmenu(self):
533 rmenu = Menu(self.text, tearoff=0)
Roger Serwy231a8fd2013-04-07 12:15:52 -0500534 for item in self.rmenu_specs:
535 label, eventname = item[0], item[1]
Andrew Svetlov5018db72012-11-01 22:39:14 +0200536 if label is not None:
537 def command(text=self.text, eventname=eventname):
538 text.event_generate(eventname)
539 rmenu.add_command(label=label, command=command)
540 else:
541 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000542 self.rmenu = rmenu
543
Andrew Svetlov5018db72012-11-01 22:39:14 +0200544 def rmenu_check_cut(self):
545 return self.rmenu_check_copy()
546
547 def rmenu_check_copy(self):
548 try:
549 indx = self.text.index('sel.first')
550 except TclError:
551 return 'disabled'
552 else:
553 return 'normal' if indx else 'disabled'
554
555 def rmenu_check_paste(self):
556 try:
557 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
558 except TclError:
559 return 'disabled'
560 else:
561 return 'normal'
562
David Scherer7aced172000-08-15 01:13:23 +0000563 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000564 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000565
Steven M. Gava3b55a892001-11-21 05:56:26 +0000566 def config_dialog(self, event=None):
567 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000568
David Scherer7aced172000-08-15 01:13:23 +0000569 def help_dialog(self, event=None):
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500570 if self.root:
571 parent = self.root
572 else:
573 parent = self.top
574 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000575
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000576 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000577 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000578 try:
579 os.startfile(self.help_url)
580 except WindowsError as why:
581 tkMessageBox.showerror(title='Document Start Failure',
582 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000583 else:
584 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000585 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000586
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000587 def cut(self,event):
588 self.text.event_generate("<<Cut>>")
589 return "break"
590
591 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000592 if not self.text.tag_ranges("sel"):
593 # There is no selection, so do nothing and maybe interrupt.
594 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000595 self.text.event_generate("<<Copy>>")
596 return "break"
597
598 def paste(self,event):
599 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000600 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000601 return "break"
602
David Scherer7aced172000-08-15 01:13:23 +0000603 def select_all(self, event=None):
604 self.text.tag_add("sel", "1.0", "end-1c")
605 self.text.mark_set("insert", "1.0")
606 self.text.see("insert")
607 return "break"
608
609 def remove_selection(self, event=None):
610 self.text.tag_remove("sel", "1.0", "end")
611 self.text.see("insert")
612
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000613 def move_at_edge_if_selection(self, edge_index):
614 """Cursor move begins at start or end of selection
615
616 When a left/right cursor key is pressed create and return to Tkinter a
617 function which causes a cursor move from the associated edge of the
618 selection.
619
620 """
621 self_text_index = self.text.index
622 self_text_mark_set = self.text.mark_set
623 edges_table = ("sel.first+1c", "sel.last-1c")
624 def move_at_edge(event):
625 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
626 try:
627 self_text_index("sel.first")
628 self_text_mark_set("insert", edges_table[edge_index])
629 except TclError:
630 pass
631 return move_at_edge
632
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000633 def del_word_left(self, event):
634 self.text.event_generate('<Meta-Delete>')
635 return "break"
636
637 def del_word_right(self, event):
638 self.text.event_generate('<Meta-d>')
639 return "break"
640
Steven M. Gavac5976402002-01-04 03:06:08 +0000641 def find_event(self, event):
642 SearchDialog.find(self.text)
643 return "break"
644
645 def find_again_event(self, event):
646 SearchDialog.find_again(self.text)
647 return "break"
648
649 def find_selection_event(self, event):
650 SearchDialog.find_selection(self.text)
651 return "break"
652
653 def find_in_files_event(self, event):
654 GrepDialog.grep(self.text, self.io, self.flist)
655 return "break"
656
657 def replace_event(self, event):
658 ReplaceDialog.replace(self.text)
659 return "break"
660
661 def goto_line_event(self, event):
662 text = self.text
663 lineno = tkSimpleDialog.askinteger("Goto",
664 "Go to line number:",parent=text)
665 if lineno is None:
666 return "break"
667 if lineno <= 0:
668 text.bell()
669 return "break"
670 text.mark_set("insert", "%d.0" % lineno)
671 text.see("insert")
672
David Scherer7aced172000-08-15 01:13:23 +0000673 def open_module(self, event=None):
674 # XXX Shouldn't this be in IOBinding or in FileList?
675 try:
676 name = self.text.get("sel.first", "sel.last")
677 except TclError:
678 name = ""
679 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000680 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000681 name = tkSimpleDialog.askstring("Module",
682 "Enter the name of a Python module\n"
683 "to search on sys.path and open:",
684 parent=self.text, initialvalue=name)
685 if name:
686 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000687 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000688 return
David Scherer7aced172000-08-15 01:13:23 +0000689 # XXX Ought to insert current file's directory in front of path
690 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000691 (f, file, (suffix, mode, type)) = _find_module(name)
Terry Jan Reedy2b149862013-06-29 00:59:34 -0400692 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000693 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
694 return
695 if type != imp.PY_SOURCE:
696 tkMessageBox.showerror("Unsupported type",
697 "%s is not a source module" % name, parent=self.text)
698 return
699 if f:
700 f.close()
701 if self.flist:
702 self.flist.open(file)
703 else:
704 self.io.loadfile(file)
705
706 def open_class_browser(self, event=None):
707 filename = self.io.filename
708 if not filename:
709 tkMessageBox.showerror(
710 "No filename",
711 "This buffer has no associated filename",
712 master=self.text)
713 self.text.focus_set()
714 return None
715 head, tail = os.path.split(filename)
716 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000717 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000718 ClassBrowser.ClassBrowser(self.flist, base, [head])
719
720 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000721 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000722 PathBrowser.PathBrowser(self.flist)
723
724 def gotoline(self, lineno):
725 if lineno is not None and lineno > 0:
726 self.text.mark_set("insert", "%d.0" % lineno)
727 self.text.tag_remove("sel", "1.0", "end")
728 self.text.tag_add("sel", "insert", "insert +1l")
729 self.center()
730
731 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000732 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000733 return True
David Scherer7aced172000-08-15 01:13:23 +0000734 base, ext = os.path.splitext(os.path.basename(filename))
735 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000736 return True
David Scherer7aced172000-08-15 01:13:23 +0000737 try:
738 f = open(filename)
739 line = f.readline()
740 f.close()
741 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000742 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000743 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000744
745 def close_hook(self):
746 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000747 self.flist.unregister_maybe_terminate(self)
748 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000749
750 def set_close_hook(self, close_hook):
751 self.close_hook = close_hook
752
753 def filename_change_hook(self):
754 if self.flist:
755 self.flist.filename_changed_edit(self)
756 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000757 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000758 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000759
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000760 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000761 if self.color:
762 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000763 if self.ispythonsource(self.io.filename):
764 self.color = self.ColorDelegator()
765 # can add more colorizers here...
766 if self.color:
767 self.per.removefilter(self.undo)
768 self.per.insertfilter(self.color)
769 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000770
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000771 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000772 if not self.color:
773 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000774 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000775 self.per.removefilter(self.color)
776 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000777
Steven M. Gavab77d3432002-03-02 07:16:21 +0000778 def ResetColorizer(self):
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000779 "Update the colour theme"
780 # Called from self.filename_change_hook and from configDialog.py
781 self._rmcolorizer()
782 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000783 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000784 normal_colors = idleConf.GetHighlight(theme, 'normal')
785 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
786 select_colors = idleConf.GetHighlight(theme, 'hilite')
787 self.text.config(
788 foreground=normal_colors['foreground'],
789 background=normal_colors['background'],
790 insertbackground=cursor_color,
791 selectforeground=select_colors['foreground'],
792 selectbackground=select_colors['background'],
793 )
David Scherer7aced172000-08-15 01:13:23 +0000794
Steven M. Gavab1585412002-03-12 00:21:56 +0000795 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000796 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000797 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000798 fontWeight='normal'
799 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
800 fontWeight='bold'
801 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200802 idleConf.GetOption('main','EditorWindow','font-size',
803 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000804 fontWeight))
805
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000806 def RemoveKeybindings(self):
807 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000808 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000809 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000810 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000811 self.text.event_delete(event, *keylist)
812 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000813 xkeydefs = idleConf.GetExtensionBindings(extensionName)
814 if xkeydefs:
815 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000816 self.text.event_delete(event, *keylist)
817
818 def ApplyKeybindings(self):
819 "Update the keybindings after they are changed"
820 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000821 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000822 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000823 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000824 xkeydefs = idleConf.GetExtensionBindings(extensionName)
825 if xkeydefs:
826 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000827 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000828 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000829 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000830 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000831 for item in menu[1]:
832 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000833 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000834 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000835 menu = self.menudict[menubarItem]
836 end = menu.index(END) + 1
837 for index in range(0, end):
838 if menu.type(index) == 'command':
839 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000840 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000841 itemName = menu.entrycget(index, 'label')
842 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000843 if menubarItem in menuEventDict:
844 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000845 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000846 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000847 accel = get_accelerator(keydefs, event)
848 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000849
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000850 def set_notabs_indentwidth(self):
851 "Update the indentwidth if changed and not using tabs in this window"
852 # Called from configDialog.py
853 if not self.usetabs:
854 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
855 type='int')
856
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000857 def reset_help_menu_entries(self):
858 "Update the additional help entries on the Help menu"
859 help_list = idleConf.GetAllExtraHelpSourcesList()
860 helpmenu = self.menudict['help']
861 # first delete the extra help entries, if any
862 helpmenu_length = helpmenu.index(END)
863 if helpmenu_length > self.base_helpmenu_length:
864 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
865 # then rebuild them
866 if help_list:
867 helpmenu.add_separator()
868 for entry in help_list:
869 cmd = self.__extra_help_callback(entry[1])
870 helpmenu.add_command(label=entry[0], command=cmd)
871 # and update the menu dictionary
872 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000873
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000874 def __extra_help_callback(self, helpfile):
875 "Create a callback with the helpfile value frozen at definition time"
876 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000877 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000878 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000879 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000880 try:
881 os.startfile(helpfile)
882 except WindowsError as why:
883 tkMessageBox.showerror(title='Document Start Failure',
884 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000885 else:
886 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000887 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000888
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000889 def update_recent_files_list(self, new_file=None):
890 "Load and update the recent files list and menus"
891 rf_list = []
892 if os.path.exists(self.recent_files_path):
893 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000894 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000895 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000896 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000897 rf_list_file.close()
898 if new_file:
899 new_file = os.path.abspath(new_file) + '\n'
900 if new_file in rf_list:
901 rf_list.remove(new_file) # move to top
902 rf_list.insert(0, new_file)
903 # clean and save the recent files list
904 bad_paths = []
905 for path in rf_list:
906 if '\0' in path or not os.path.exists(path[0:-1]):
907 bad_paths.append(path)
908 rf_list = [path for path in rf_list if path not in bad_paths]
909 ulchars = "1234567890ABCDEFGHIJK"
910 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000911 try:
Ned Deily40ad0412011-12-14 14:57:43 -0800912 with open(self.recent_files_path, 'w') as rf_file:
913 rf_file.writelines(rf_list)
914 except IOError as err:
915 if not getattr(self.root, "recentfilelist_error_displayed", False):
916 self.root.recentfilelist_error_displayed = True
917 tkMessageBox.showerror(title='IDLE Error',
918 message='Unable to update Recent Files list:\n%s'
919 % str(err),
920 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000921 # for each edit window instance, construct the recent files menu
922 for instance in self.top.instance_dict.keys():
923 menu = instance.recent_files_menu
Ned Deily71489842012-05-29 10:42:34 -0700924 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000925 for i, file_name in enumerate(rf_list):
926 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000927 # make unicode string to display non-ASCII chars correctly
928 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000929 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000930 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000931 command=callback,
932 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000933
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000934 def __recent_file_callback(self, file_name):
935 def open_recent_file(fn_closure=file_name):
936 self.io.open(editFile=fn_closure)
937 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000938
David Scherer7aced172000-08-15 01:13:23 +0000939 def saved_change_hook(self):
940 short = self.short_title()
941 long = self.long_title()
942 if short and long:
943 title = short + " - " + long
944 elif short:
945 title = short
946 elif long:
947 title = long
948 else:
949 title = "Untitled"
950 icon = short or long or title
951 if not self.get_saved():
952 title = "*%s*" % title
953 icon = "*%s" % icon
954 self.top.wm_title(title)
955 self.top.wm_iconname(icon)
956
957 def get_saved(self):
958 return self.undo.get_saved()
959
960 def set_saved(self, flag):
961 self.undo.set_saved(flag)
962
963 def reset_undo(self):
964 self.undo.reset_undo()
965
966 def short_title(self):
967 filename = self.io.filename
968 if filename:
969 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000970 # return unicode string to display non-ASCII chars correctly
971 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000972
973 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000974 # return unicode string to display non-ASCII chars correctly
975 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000976
977 def center_insert_event(self, event):
978 self.center()
979
980 def center(self, mark="insert"):
981 text = self.text
982 top, bot = self.getwindowlines()
983 lineno = self.getlineno(mark)
984 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000985 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000986 text.yview(float(newtop))
987
988 def getwindowlines(self):
989 text = self.text
990 top = self.getlineno("@0,0")
991 bot = self.getlineno("@0,65535")
992 if top == bot and text.winfo_height() == 1:
993 # Geometry manager hasn't run yet
994 height = int(text['height'])
995 bot = top + height - 1
996 return top, bot
997
998 def getlineno(self, mark="insert"):
999 text = self.text
1000 return int(float(text.index(mark)))
1001
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001002 def get_geometry(self):
1003 "Return (width, height, x, y)"
1004 geom = self.top.wm_geometry()
1005 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
1006 tuple = (map(int, m.groups()))
1007 return tuple
1008
David Scherer7aced172000-08-15 01:13:23 +00001009 def close_event(self, event):
1010 self.close()
1011
1012 def maybesave(self):
1013 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001014 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001015 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001016 self.top.deiconify()
1017 self.top.lower()
1018 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001019 return self.io.maybesave()
1020
1021 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001022 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +00001023 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001024 self._close()
1025 return reply
1026
1027 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001028 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001029 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001030 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001031 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001032 self.io.close()
1033 self.io = None
1034 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001035 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001036 self.color.close(False)
1037 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001038 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001039 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001040 self.per.close()
1041 self.per = None
1042 self.top.destroy()
1043 if self.close_hook:
1044 # unless override: unregister from flist, terminate if last window
1045 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001046
1047 def load_extensions(self):
1048 self.extensions = {}
1049 self.load_standard_extensions()
1050
1051 def unload_extensions(self):
1052 for ins in self.extensions.values():
1053 if hasattr(ins, "close"):
1054 ins.close()
1055 self.extensions = {}
1056
1057 def load_standard_extensions(self):
1058 for name in self.get_standard_extension_names():
1059 try:
1060 self.load_extension(name)
1061 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001062 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +00001063 import traceback
1064 traceback.print_exc()
1065
1066 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001067 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001068
1069 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001070 try:
1071 mod = __import__(name, globals(), locals(), [])
1072 except ImportError:
1073 print "\nFailed to import extension: ", name
1074 return
David Scherer7aced172000-08-15 01:13:23 +00001075 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001076 keydefs = idleConf.GetExtensionBindings(name)
1077 if hasattr(cls, "menudefs"):
1078 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001079 ins = cls(self)
1080 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001081 if keydefs:
1082 self.apply_bindings(keydefs)
1083 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001084 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001085 while methodname[:1] == '<':
1086 methodname = methodname[1:]
1087 while methodname[-1:] == '>':
1088 methodname = methodname[:-1]
1089 methodname = methodname + "_event"
1090 if hasattr(ins, methodname):
1091 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001092
1093 def apply_bindings(self, keydefs=None):
1094 if keydefs is None:
1095 keydefs = self.Bindings.default_keydefs
1096 text = self.text
1097 text.keydefs = keydefs
1098 for event, keylist in keydefs.items():
1099 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001100 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001101
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001102 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001103 """Add appropriate entries to the menus and submenus
1104
1105 Menus that are absent or None in self.menudict are ignored.
1106 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001107 if menudefs is None:
1108 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001109 if keydefs is None:
1110 keydefs = self.Bindings.default_keydefs
1111 menudict = self.menudict
1112 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001113 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001114 menu = menudict.get(mname)
1115 if not menu:
1116 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001117 for entry in entrylist:
1118 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001119 menu.add_separator()
1120 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001121 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001122 checkbutton = (label[:1] == '!')
1123 if checkbutton:
1124 label = label[1:]
1125 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001126 accelerator = get_accelerator(keydefs, eventname)
1127 def command(text=text, eventname=eventname):
1128 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001129 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001130 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001131 menu.add_checkbutton(label=label, underline=underline,
1132 command=command, accelerator=accelerator,
1133 variable=var)
1134 else:
1135 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001136 command=command,
1137 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001138
1139 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001140 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001141 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001142 value = var.get()
1143 return value
1144 else:
1145 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001146
1147 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001148 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001149 if var:
1150 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001151 else:
1152 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001153
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001154 def get_var_obj(self, name, vartype=None):
1155 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001156 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001157 # create a Tkinter variable object with self.text as master:
1158 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001159 return var
1160
1161 # Tk implementations of "virtual text methods" -- each platform
1162 # reusing IDLE's support code needs to define these for its GUI's
1163 # flavor of widget.
1164
1165 # Is character at text_index in a Python string? Return 0 for
1166 # "guaranteed no", true for anything else. This info is expensive
1167 # to compute ab initio, but is probably already known by the
1168 # platform's colorizer.
1169
1170 def is_char_in_string(self, text_index):
1171 if self.color:
1172 # Return true iff colorizer hasn't (re)gotten this far
1173 # yet, or the character is tagged as being in a string
1174 return self.text.tag_prevrange("TODO", text_index) or \
1175 "STRING" in self.text.tag_names(text_index)
1176 else:
1177 # The colorizer is missing: assume the worst
1178 return 1
1179
1180 # If a selection is defined in the text widget, return (start,
1181 # end) as Tkinter text indices, otherwise return (None, None)
1182 def get_selection_indices(self):
1183 try:
1184 first = self.text.index("sel.first")
1185 last = self.text.index("sel.last")
1186 return first, last
1187 except TclError:
1188 return None, None
1189
1190 # Return the text widget's current view of what a tab stop means
1191 # (equivalent width in spaces).
1192
1193 def get_tabwidth(self):
1194 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1195 return int(current)
1196
1197 # Set the text widget's current view of what a tab stop means.
1198
1199 def set_tabwidth(self, newtabwidth):
1200 text = self.text
1201 if self.get_tabwidth() != newtabwidth:
1202 pixels = text.tk.call("font", "measure", text["font"],
1203 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001204 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001205 text.configure(tabs=pixels)
1206
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001207 # If ispythonsource and guess are true, guess a good value for
1208 # indentwidth based on file content (if possible), and if
1209 # indentwidth != tabwidth set usetabs false.
1210 # In any case, adjust the Text widget's view of what a tab
1211 # character means.
1212
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001213 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001214 if guess and ispythonsource:
1215 i = self.guess_indent()
1216 if 2 <= i <= 8:
1217 self.indentwidth = i
1218 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001219 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001220 self.set_tabwidth(self.tabwidth)
1221
1222 def smart_backspace_event(self, event):
1223 text = self.text
1224 first, last = self.get_selection_indices()
1225 if first and last:
1226 text.delete(first, last)
1227 text.mark_set("insert", first)
1228 return "break"
1229 # Delete whitespace left, until hitting a real char or closest
1230 # preceding virtual tab stop.
1231 chars = text.get("insert linestart", "insert")
1232 if chars == '':
1233 if text.compare("insert", ">", "1.0"):
1234 # easy: delete preceding newline
1235 text.delete("insert-1c")
1236 else:
1237 text.bell() # at start of buffer
1238 return "break"
1239 if chars[-1] not in " \t":
1240 # easy: delete preceding real char
1241 text.delete("insert-1c")
1242 return "break"
1243 # Ick. It may require *inserting* spaces if we back up over a
1244 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001245 tabwidth = self.tabwidth
1246 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001247 assert have > 0
1248 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001249 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001250 if self.context_use_ps1:
1251 last_line_of_prompt = sys.ps1.split('\n')[-1]
1252 else:
1253 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001254 ncharsdeleted = 0
1255 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001256 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001257 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001258 chars = chars[:-1]
1259 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001260 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001261 if have <= want or chars[-1] not in " \t":
1262 break
1263 text.undo_block_start()
1264 text.delete("insert-%dc" % ncharsdeleted, "insert")
1265 if have < want:
1266 text.insert("insert", ' ' * (want - have))
1267 text.undo_block_stop()
1268 return "break"
1269
1270 def smart_indent_event(self, event):
1271 # if intraline selection:
1272 # delete it
1273 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001274 # do indent-region
1275 # else:
1276 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001277 text = self.text
1278 first, last = self.get_selection_indices()
1279 text.undo_block_start()
1280 try:
1281 if first and last:
1282 if index2line(first) != index2line(last):
1283 return self.indent_region_event(event)
1284 text.delete(first, last)
1285 text.mark_set("insert", first)
1286 prefix = text.get("insert linestart", "insert")
1287 raw, effective = classifyws(prefix, self.tabwidth)
1288 if raw == len(prefix):
1289 # only whitespace to the left
1290 self.reindent_to(effective + self.indentwidth)
1291 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001292 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001293 if self.usetabs:
1294 pad = '\t'
1295 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001296 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001297 n = self.indentwidth
1298 pad = ' ' * (n - effective % n)
1299 text.insert("insert", pad)
1300 text.see("insert")
1301 return "break"
1302 finally:
1303 text.undo_block_stop()
1304
1305 def newline_and_indent_event(self, event):
1306 text = self.text
1307 first, last = self.get_selection_indices()
1308 text.undo_block_start()
1309 try:
1310 if first and last:
1311 text.delete(first, last)
1312 text.mark_set("insert", first)
1313 line = text.get("insert linestart", "insert")
1314 i, n = 0, len(line)
1315 while i < n and line[i] in " \t":
1316 i = i+1
1317 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001318 # the cursor is in or at leading indentation in a continuation
1319 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001320 text.insert("insert linestart", '\n')
1321 return "break"
1322 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001323 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001324 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001325 last_line_of_prompt = sys.ps1.split('\n')[-1]
1326 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001327 line = line[:-1]
1328 i = i+1
1329 if i:
1330 text.delete("insert - %d chars" % i, "insert")
1331 # strip whitespace after insert point
1332 while text.get("insert") in " \t":
1333 text.delete("insert")
1334 # start new line
1335 text.insert("insert", '\n')
1336
1337 # adjust indentation for continuations and block
1338 # open/close first need to find the last stmt
1339 lno = index2line(text.index('insert'))
1340 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001341 if not self.context_use_ps1:
1342 for context in self.num_context_lines:
1343 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001344 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001345 rawtext = text.get(startatindex, "insert")
1346 y.set_str(rawtext)
1347 bod = y.find_good_parse_start(
1348 self.context_use_ps1,
1349 self._build_char_in_string_func(startatindex))
1350 if bod is not None or startat == 1:
1351 break
1352 y.set_lo(bod or 0)
1353 else:
1354 r = text.tag_prevrange("console", "insert")
1355 if r:
1356 startatindex = r[1]
1357 else:
1358 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001359 rawtext = text.get(startatindex, "insert")
1360 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001361 y.set_lo(0)
1362
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001363 c = y.get_continuation_type()
1364 if c != PyParse.C_NONE:
1365 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001366 if c == PyParse.C_STRING_FIRST_LINE:
1367 # after the first line of a string; do not indent at all
1368 pass
1369 elif c == PyParse.C_STRING_NEXT_LINES:
1370 # inside a string which started before this line;
1371 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001372 text.insert("insert", indent)
1373 elif c == PyParse.C_BRACKET:
1374 # line up with the first (if any) element of the
1375 # last open bracket structure; else indent one
1376 # level beyond the indent of the line with the
1377 # last open bracket
1378 self.reindent_to(y.compute_bracket_indent())
1379 elif c == PyParse.C_BACKSLASH:
1380 # if more than one line in this stmt already, just
1381 # mimic the current indent; else if initial line
1382 # has a start on an assignment stmt, indent to
1383 # beyond leftmost =; else to beyond first chunk of
1384 # non-whitespace on initial line
1385 if y.get_num_lines_in_stmt() > 1:
1386 text.insert("insert", indent)
1387 else:
1388 self.reindent_to(y.compute_backslash_indent())
1389 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001390 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001391 return "break"
1392
1393 # This line starts a brand new stmt; indent relative to
1394 # indentation of initial line of closest preceding
1395 # interesting stmt.
1396 indent = y.get_base_indent_string()
1397 text.insert("insert", indent)
1398 if y.is_block_opener():
1399 self.smart_indent_event(event)
1400 elif indent and y.is_block_closer():
1401 self.smart_backspace_event(event)
1402 return "break"
1403 finally:
1404 text.see("insert")
1405 text.undo_block_stop()
1406
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001407 # Our editwin provides a is_char_in_string function that works
1408 # with a Tk text index, but PyParse only knows about offsets into
1409 # a string. This builds a function for PyParse that accepts an
1410 # offset.
1411
1412 def _build_char_in_string_func(self, startindex):
1413 def inner(offset, _startindex=startindex,
1414 _icis=self.is_char_in_string):
1415 return _icis(_startindex + "+%dc" % offset)
1416 return inner
1417
1418 def indent_region_event(self, event):
1419 head, tail, chars, lines = self.get_region()
1420 for pos in range(len(lines)):
1421 line = lines[pos]
1422 if line:
1423 raw, effective = classifyws(line, self.tabwidth)
1424 effective = effective + self.indentwidth
1425 lines[pos] = self._make_blanks(effective) + line[raw:]
1426 self.set_region(head, tail, chars, lines)
1427 return "break"
1428
1429 def dedent_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 = max(effective - self.indentwidth, 0)
1436 lines[pos] = self._make_blanks(effective) + line[raw:]
1437 self.set_region(head, tail, chars, lines)
1438 return "break"
1439
1440 def comment_region_event(self, event):
1441 head, tail, chars, lines = self.get_region()
1442 for pos in range(len(lines) - 1):
1443 line = lines[pos]
1444 lines[pos] = '##' + line
1445 self.set_region(head, tail, chars, lines)
1446
1447 def uncomment_region_event(self, event):
1448 head, tail, chars, lines = self.get_region()
1449 for pos in range(len(lines)):
1450 line = lines[pos]
1451 if not line:
1452 continue
1453 if line[:2] == '##':
1454 line = line[2:]
1455 elif line[:1] == '#':
1456 line = line[1:]
1457 lines[pos] = line
1458 self.set_region(head, tail, chars, lines)
1459
1460 def tabify_region_event(self, event):
1461 head, tail, chars, lines = self.get_region()
1462 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001463 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001464 for pos in range(len(lines)):
1465 line = lines[pos]
1466 if line:
1467 raw, effective = classifyws(line, tabwidth)
1468 ntabs, nspaces = divmod(effective, tabwidth)
1469 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1470 self.set_region(head, tail, chars, lines)
1471
1472 def untabify_region_event(self, event):
1473 head, tail, chars, lines = self.get_region()
1474 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001475 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001476 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001477 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001478 self.set_region(head, tail, chars, lines)
1479
1480 def toggle_tabs_event(self, event):
1481 if self.askyesno(
1482 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001483 "Turn tabs " + ("on", "off")[self.usetabs] +
1484 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001485 ("will be", "remains at")[self.usetabs] + " 8." +
1486 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001487 parent=self.text):
1488 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001489 # Try to prevent inconsistent indentation.
1490 # User must change indent width manually after using tabs.
1491 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001492 return "break"
1493
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001494 # XXX this isn't bound to anything -- see tabwidth comments
1495## def change_tabwidth_event(self, event):
1496## new = self._asktabwidth()
1497## if new != self.tabwidth:
1498## self.tabwidth = new
1499## self.set_indentation_params(0, guess=0)
1500## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001501
1502 def change_indentwidth_event(self, event):
1503 new = self.askinteger(
1504 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001505 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001506 parent=self.text,
1507 initialvalue=self.indentwidth,
1508 minvalue=2,
1509 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001510 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001511 self.indentwidth = new
1512 return "break"
1513
1514 def get_region(self):
1515 text = self.text
1516 first, last = self.get_selection_indices()
1517 if first and last:
1518 head = text.index(first + " linestart")
1519 tail = text.index(last + "-1c lineend +1c")
1520 else:
1521 head = text.index("insert linestart")
1522 tail = text.index("insert lineend +1c")
1523 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001524 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001525 return head, tail, chars, lines
1526
1527 def set_region(self, head, tail, chars, lines):
1528 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001529 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001530 if newchars == chars:
1531 text.bell()
1532 return
1533 text.tag_remove("sel", "1.0", "end")
1534 text.mark_set("insert", head)
1535 text.undo_block_start()
1536 text.delete(head, tail)
1537 text.insert(head, newchars)
1538 text.undo_block_stop()
1539 text.tag_add("sel", head, "insert")
1540
1541 # Make string that displays as n leading blanks.
1542
1543 def _make_blanks(self, n):
1544 if self.usetabs:
1545 ntabs, nspaces = divmod(n, self.tabwidth)
1546 return '\t' * ntabs + ' ' * nspaces
1547 else:
1548 return ' ' * n
1549
1550 # Delete from beginning of line to insert point, then reinsert
1551 # column logical (meaning use tabs if appropriate) spaces.
1552
1553 def reindent_to(self, column):
1554 text = self.text
1555 text.undo_block_start()
1556 if text.compare("insert linestart", "!=", "insert"):
1557 text.delete("insert linestart", "insert")
1558 if column:
1559 text.insert("insert", self._make_blanks(column))
1560 text.undo_block_stop()
1561
1562 def _asktabwidth(self):
1563 return self.askinteger(
1564 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001565 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001566 parent=self.text,
1567 initialvalue=self.indentwidth,
1568 minvalue=2,
Roger Serwy75b249c2013-04-06 20:26:53 -05001569 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001570
1571 # Guess indentwidth from text content.
1572 # Return guessed indentwidth. This should not be believed unless
1573 # it's in a reasonable range (e.g., it will be 0 if no indented
1574 # blocks are found).
1575
1576 def guess_indent(self):
1577 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1578 if opener and indented:
1579 raw, indentsmall = classifyws(opener, self.tabwidth)
1580 raw, indentlarge = classifyws(indented, self.tabwidth)
1581 else:
1582 indentsmall = indentlarge = 0
1583 return indentlarge - indentsmall
1584
1585# "line.col" -> line, as an int
1586def index2line(index):
1587 return int(float(index))
1588
1589# Look at the leading whitespace in s.
1590# Return pair (# of leading ws characters,
1591# effective # of leading blanks after expanding
1592# tabs to width tabwidth)
1593
1594def classifyws(s, tabwidth):
1595 raw = effective = 0
1596 for ch in s:
1597 if ch == ' ':
1598 raw = raw + 1
1599 effective = effective + 1
1600 elif ch == '\t':
1601 raw = raw + 1
1602 effective = (effective // tabwidth + 1) * tabwidth
1603 else:
1604 break
1605 return raw, effective
1606
1607import tokenize
1608_tokenize = tokenize
1609del tokenize
1610
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001611class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001612
1613 # .run() chews over the Text widget, looking for a block opener
1614 # and the stmt following it. Returns a pair,
1615 # (line containing block opener, line containing stmt)
1616 # Either or both may be None.
1617
1618 def __init__(self, text, tabwidth):
1619 self.text = text
1620 self.tabwidth = tabwidth
1621 self.i = self.finished = 0
1622 self.blkopenline = self.indentedline = None
1623
1624 def readline(self):
1625 if self.finished:
1626 return ""
1627 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001628 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001629 if self.text.compare(mark, ">=", "end"):
1630 return ""
1631 return self.text.get(mark, mark + " lineend+1c")
1632
1633 def tokeneater(self, type, token, start, end, line,
1634 INDENT=_tokenize.INDENT,
1635 NAME=_tokenize.NAME,
1636 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1637 if self.finished:
1638 pass
1639 elif type == NAME and token in OPENERS:
1640 self.blkopenline = line
1641 elif type == INDENT and self.blkopenline:
1642 self.indentedline = line
1643 self.finished = 1
1644
1645 def run(self):
1646 save_tabsize = _tokenize.tabsize
1647 _tokenize.tabsize = self.tabwidth
1648 try:
1649 try:
1650 _tokenize.tokenize(self.readline, self.tokeneater)
Serhiy Storchaka61006a22012-12-27 21:34:23 +02001651 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001652 # since we cut off the tokenizer early, we can trigger
1653 # spurious errors
1654 pass
1655 finally:
1656 _tokenize.tabsize = save_tabsize
1657 return self.blkopenline, self.indentedline
1658
1659### end autoindent code ###
1660
David Scherer7aced172000-08-15 01:13:23 +00001661def prepstr(s):
1662 # Helper to extract the underscore from a string, e.g.
1663 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001664 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001665 if i >= 0:
1666 s = s[:i] + s[i+1:]
1667 return i, s
1668
1669
1670keynames = {
1671 'bracketleft': '[',
1672 'bracketright': ']',
1673 'slash': '/',
1674}
1675
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001676def get_accelerator(keydefs, eventname):
1677 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001678 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1679 # if not keylist:
1680 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1681 "<<open-module>>",
1682 "<<goto-line>>",
1683 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001684 return ""
1685 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001686 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001687 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1688 s = re.sub("Key-", "", s)
1689 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1690 s = re.sub("Control-", "Ctrl-", s)
1691 s = re.sub("-", "+", s)
1692 s = re.sub("><", " ", s)
1693 s = re.sub("<", "", s)
1694 s = re.sub(">", "", s)
1695 return s
1696
1697
1698def fixwordbreaks(root):
1699 # Make sure that Tk's double-click and next/previous word
1700 # operations use our definition of a word (i.e. an identifier)
1701 tk = root.tk
1702 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1703 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1704 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1705
1706
1707def test():
1708 root = Tk()
1709 fixwordbreaks(root)
1710 root.withdraw()
1711 if sys.argv[1:]:
1712 filename = sys.argv[1]
1713 else:
1714 filename = None
1715 edit = EditorWindow(root=root, filename=filename)
1716 edit.set_close_hook(root.quit)
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001717 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001718 root.mainloop()
1719 root.destroy()
1720
1721if __name__ == '__main__':
1722 test()