blob: 38cbfcfc559269c4519d2be7f84c1a7be99f2e86 [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():
470 del menu_specs[-3]
471 menu_specs[-2] = ("windows", "_Window")
472
473
David Scherer7aced172000-08-15 01:13:23 +0000474 def createmenubar(self):
475 mbar = self.menubar
476 self.menudict = menudict = {}
477 for name, label in self.menu_specs:
478 underline, label = prepstr(label)
479 menudict[name] = menu = Menu(mbar, name=name)
480 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000481
Ned Deily4a705502011-01-18 04:33:22 +0000482 if macosxSupport.isCarbonAquaTk(self.root):
Ronald Oussoren19302d92006-06-11 14:33:36 +0000483 # Insert the application menu
484 menudict['application'] = menu = Menu(mbar, name='apple')
485 mbar.add_cascade(label='IDLE', menu=menu)
486
David Scherer7aced172000-08-15 01:13:23 +0000487 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000488 self.base_helpmenu_length = self.menudict['help'].index(END)
489 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000490
491 def postwindowsmenu(self):
492 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000493 menu = self.menudict['windows']
494 end = menu.index("end")
495 if end is None:
496 end = -1
497 if end > self.wmenu_end:
498 menu.delete(self.wmenu_end+1, end)
499 WindowList.add_windows_to_menu(menu)
500
501 rmenu = None
502
503 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000504 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
505 if not self.rmenu:
506 self.make_rmenu()
507 rmenu = self.rmenu
508 self.event = event
509 iswin = sys.platform[:3] == 'win'
510 if iswin:
511 self.text.config(cursor="arrow")
Andrew Svetlov5018db72012-11-01 22:39:14 +0200512
Roger Serwy231a8fd2013-04-07 12:15:52 -0500513 for item in self.rmenu_specs:
514 try:
515 label, eventname, verify_state = item
516 except ValueError: # see issue1207589
517 continue
518
Andrew Svetlov5018db72012-11-01 22:39:14 +0200519 if verify_state is None:
520 continue
521 state = getattr(self, verify_state)()
522 rmenu.entryconfigure(label, state=state)
523
David Scherer7aced172000-08-15 01:13:23 +0000524 rmenu.tk_popup(event.x_root, event.y_root)
525 if iswin:
526 self.text.config(cursor="ibeam")
527
528 rmenu_specs = [
Andrew Svetlov5018db72012-11-01 22:39:14 +0200529 # ("Label", "<<virtual-event>>", "statefuncname"), ...
530 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000531 ]
532
533 def make_rmenu(self):
534 rmenu = Menu(self.text, tearoff=0)
Roger Serwy231a8fd2013-04-07 12:15:52 -0500535 for item in self.rmenu_specs:
536 label, eventname = item[0], item[1]
Andrew Svetlov5018db72012-11-01 22:39:14 +0200537 if label is not None:
538 def command(text=self.text, eventname=eventname):
539 text.event_generate(eventname)
540 rmenu.add_command(label=label, command=command)
541 else:
542 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000543 self.rmenu = rmenu
544
Andrew Svetlov5018db72012-11-01 22:39:14 +0200545 def rmenu_check_cut(self):
546 return self.rmenu_check_copy()
547
548 def rmenu_check_copy(self):
549 try:
550 indx = self.text.index('sel.first')
551 except TclError:
552 return 'disabled'
553 else:
554 return 'normal' if indx else 'disabled'
555
556 def rmenu_check_paste(self):
557 try:
558 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
559 except TclError:
560 return 'disabled'
561 else:
562 return 'normal'
563
David Scherer7aced172000-08-15 01:13:23 +0000564 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000565 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000566
Steven M. Gava3b55a892001-11-21 05:56:26 +0000567 def config_dialog(self, event=None):
568 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000569
David Scherer7aced172000-08-15 01:13:23 +0000570 def help_dialog(self, event=None):
Terry Jan Reedyadb87e22012-02-05 15:10:55 -0500571 if self.root:
572 parent = self.root
573 else:
574 parent = self.top
575 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000576
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000577 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000578 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000579 try:
580 os.startfile(self.help_url)
581 except WindowsError as why:
582 tkMessageBox.showerror(title='Document Start Failure',
583 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000584 else:
585 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000586 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000587
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000588 def cut(self,event):
589 self.text.event_generate("<<Cut>>")
590 return "break"
591
592 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000593 if not self.text.tag_ranges("sel"):
594 # There is no selection, so do nothing and maybe interrupt.
595 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000596 self.text.event_generate("<<Copy>>")
597 return "break"
598
599 def paste(self,event):
600 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000601 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000602 return "break"
603
David Scherer7aced172000-08-15 01:13:23 +0000604 def select_all(self, event=None):
605 self.text.tag_add("sel", "1.0", "end-1c")
606 self.text.mark_set("insert", "1.0")
607 self.text.see("insert")
608 return "break"
609
610 def remove_selection(self, event=None):
611 self.text.tag_remove("sel", "1.0", "end")
612 self.text.see("insert")
613
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000614 def move_at_edge_if_selection(self, edge_index):
615 """Cursor move begins at start or end of selection
616
617 When a left/right cursor key is pressed create and return to Tkinter a
618 function which causes a cursor move from the associated edge of the
619 selection.
620
621 """
622 self_text_index = self.text.index
623 self_text_mark_set = self.text.mark_set
624 edges_table = ("sel.first+1c", "sel.last-1c")
625 def move_at_edge(event):
626 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
627 try:
628 self_text_index("sel.first")
629 self_text_mark_set("insert", edges_table[edge_index])
630 except TclError:
631 pass
632 return move_at_edge
633
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000634 def del_word_left(self, event):
635 self.text.event_generate('<Meta-Delete>')
636 return "break"
637
638 def del_word_right(self, event):
639 self.text.event_generate('<Meta-d>')
640 return "break"
641
Steven M. Gavac5976402002-01-04 03:06:08 +0000642 def find_event(self, event):
643 SearchDialog.find(self.text)
644 return "break"
645
646 def find_again_event(self, event):
647 SearchDialog.find_again(self.text)
648 return "break"
649
650 def find_selection_event(self, event):
651 SearchDialog.find_selection(self.text)
652 return "break"
653
654 def find_in_files_event(self, event):
655 GrepDialog.grep(self.text, self.io, self.flist)
656 return "break"
657
658 def replace_event(self, event):
659 ReplaceDialog.replace(self.text)
660 return "break"
661
662 def goto_line_event(self, event):
663 text = self.text
664 lineno = tkSimpleDialog.askinteger("Goto",
665 "Go to line number:",parent=text)
666 if lineno is None:
667 return "break"
668 if lineno <= 0:
669 text.bell()
670 return "break"
671 text.mark_set("insert", "%d.0" % lineno)
672 text.see("insert")
673
David Scherer7aced172000-08-15 01:13:23 +0000674 def open_module(self, event=None):
675 # XXX Shouldn't this be in IOBinding or in FileList?
676 try:
677 name = self.text.get("sel.first", "sel.last")
678 except TclError:
679 name = ""
680 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000681 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000682 name = tkSimpleDialog.askstring("Module",
683 "Enter the name of a Python module\n"
684 "to search on sys.path and open:",
685 parent=self.text, initialvalue=name)
686 if name:
687 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000688 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000689 return
David Scherer7aced172000-08-15 01:13:23 +0000690 # XXX Ought to insert current file's directory in front of path
691 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000692 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000693 except (NameError, ImportError), msg:
694 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
695 return
696 if type != imp.PY_SOURCE:
697 tkMessageBox.showerror("Unsupported type",
698 "%s is not a source module" % name, parent=self.text)
699 return
700 if f:
701 f.close()
702 if self.flist:
703 self.flist.open(file)
704 else:
705 self.io.loadfile(file)
706
707 def open_class_browser(self, event=None):
708 filename = self.io.filename
709 if not filename:
710 tkMessageBox.showerror(
711 "No filename",
712 "This buffer has no associated filename",
713 master=self.text)
714 self.text.focus_set()
715 return None
716 head, tail = os.path.split(filename)
717 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000718 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000719 ClassBrowser.ClassBrowser(self.flist, base, [head])
720
721 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000722 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000723 PathBrowser.PathBrowser(self.flist)
724
725 def gotoline(self, lineno):
726 if lineno is not None and lineno > 0:
727 self.text.mark_set("insert", "%d.0" % lineno)
728 self.text.tag_remove("sel", "1.0", "end")
729 self.text.tag_add("sel", "insert", "insert +1l")
730 self.center()
731
732 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000733 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000734 return True
David Scherer7aced172000-08-15 01:13:23 +0000735 base, ext = os.path.splitext(os.path.basename(filename))
736 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000737 return True
David Scherer7aced172000-08-15 01:13:23 +0000738 try:
739 f = open(filename)
740 line = f.readline()
741 f.close()
742 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000743 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000744 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000745
746 def close_hook(self):
747 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000748 self.flist.unregister_maybe_terminate(self)
749 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000750
751 def set_close_hook(self, close_hook):
752 self.close_hook = close_hook
753
754 def filename_change_hook(self):
755 if self.flist:
756 self.flist.filename_changed_edit(self)
757 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000758 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000759 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000760
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000761 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000762 if self.color:
763 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000764 if self.ispythonsource(self.io.filename):
765 self.color = self.ColorDelegator()
766 # can add more colorizers here...
767 if self.color:
768 self.per.removefilter(self.undo)
769 self.per.insertfilter(self.color)
770 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000771
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000772 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000773 if not self.color:
774 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000775 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000776 self.per.removefilter(self.color)
777 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000778
Steven M. Gavab77d3432002-03-02 07:16:21 +0000779 def ResetColorizer(self):
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000780 "Update the colour theme"
781 # Called from self.filename_change_hook and from configDialog.py
782 self._rmcolorizer()
783 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000784 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000785 normal_colors = idleConf.GetHighlight(theme, 'normal')
786 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
787 select_colors = idleConf.GetHighlight(theme, 'hilite')
788 self.text.config(
789 foreground=normal_colors['foreground'],
790 background=normal_colors['background'],
791 insertbackground=cursor_color,
792 selectforeground=select_colors['foreground'],
793 selectbackground=select_colors['background'],
794 )
David Scherer7aced172000-08-15 01:13:23 +0000795
Steven M. Gavab1585412002-03-12 00:21:56 +0000796 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000797 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000798 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000799 fontWeight='normal'
800 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
801 fontWeight='bold'
802 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Andrew Svetlovd8590ff2012-12-24 13:17:59 +0200803 idleConf.GetOption('main','EditorWindow','font-size',
804 type='int'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000805 fontWeight))
806
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000807 def RemoveKeybindings(self):
808 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000809 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000810 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000811 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000812 self.text.event_delete(event, *keylist)
813 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000814 xkeydefs = idleConf.GetExtensionBindings(extensionName)
815 if xkeydefs:
816 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000817 self.text.event_delete(event, *keylist)
818
819 def ApplyKeybindings(self):
820 "Update the keybindings after they are changed"
821 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000822 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000823 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000824 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000825 xkeydefs = idleConf.GetExtensionBindings(extensionName)
826 if xkeydefs:
827 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000828 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000829 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000830 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000831 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000832 for item in menu[1]:
833 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000834 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000835 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000836 menu = self.menudict[menubarItem]
837 end = menu.index(END) + 1
838 for index in range(0, end):
839 if menu.type(index) == 'command':
840 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000841 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000842 itemName = menu.entrycget(index, 'label')
843 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000844 if menubarItem in menuEventDict:
845 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000846 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000847 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000848 accel = get_accelerator(keydefs, event)
849 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000850
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000851 def set_notabs_indentwidth(self):
852 "Update the indentwidth if changed and not using tabs in this window"
853 # Called from configDialog.py
854 if not self.usetabs:
855 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
856 type='int')
857
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000858 def reset_help_menu_entries(self):
859 "Update the additional help entries on the Help menu"
860 help_list = idleConf.GetAllExtraHelpSourcesList()
861 helpmenu = self.menudict['help']
862 # first delete the extra help entries, if any
863 helpmenu_length = helpmenu.index(END)
864 if helpmenu_length > self.base_helpmenu_length:
865 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
866 # then rebuild them
867 if help_list:
868 helpmenu.add_separator()
869 for entry in help_list:
870 cmd = self.__extra_help_callback(entry[1])
871 helpmenu.add_command(label=entry[0], command=cmd)
872 # and update the menu dictionary
873 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000874
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000875 def __extra_help_callback(self, helpfile):
876 "Create a callback with the helpfile value frozen at definition time"
877 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000878 if not helpfile.startswith(('www', 'http')):
Terry Reedy247327a2011-01-01 02:32:46 +0000879 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000880 if sys.platform[:3] == 'win':
Terry Reedy247327a2011-01-01 02:32:46 +0000881 try:
882 os.startfile(helpfile)
883 except WindowsError as why:
884 tkMessageBox.showerror(title='Document Start Failure',
885 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000886 else:
887 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000888 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000889
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000890 def update_recent_files_list(self, new_file=None):
891 "Load and update the recent files list and menus"
892 rf_list = []
893 if os.path.exists(self.recent_files_path):
894 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000895 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000896 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000897 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000898 rf_list_file.close()
899 if new_file:
900 new_file = os.path.abspath(new_file) + '\n'
901 if new_file in rf_list:
902 rf_list.remove(new_file) # move to top
903 rf_list.insert(0, new_file)
904 # clean and save the recent files list
905 bad_paths = []
906 for path in rf_list:
907 if '\0' in path or not os.path.exists(path[0:-1]):
908 bad_paths.append(path)
909 rf_list = [path for path in rf_list if path not in bad_paths]
910 ulchars = "1234567890ABCDEFGHIJK"
911 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000912 try:
Ned Deily40ad0412011-12-14 14:57:43 -0800913 with open(self.recent_files_path, 'w') as rf_file:
914 rf_file.writelines(rf_list)
915 except IOError as err:
916 if not getattr(self.root, "recentfilelist_error_displayed", False):
917 self.root.recentfilelist_error_displayed = True
918 tkMessageBox.showerror(title='IDLE Error',
919 message='Unable to update Recent Files list:\n%s'
920 % str(err),
921 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000922 # for each edit window instance, construct the recent files menu
923 for instance in self.top.instance_dict.keys():
924 menu = instance.recent_files_menu
Ned Deily71489842012-05-29 10:42:34 -0700925 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000926 for i, file_name in enumerate(rf_list):
927 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000928 # make unicode string to display non-ASCII chars correctly
929 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000930 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000931 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000932 command=callback,
933 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000934
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000935 def __recent_file_callback(self, file_name):
936 def open_recent_file(fn_closure=file_name):
937 self.io.open(editFile=fn_closure)
938 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000939
David Scherer7aced172000-08-15 01:13:23 +0000940 def saved_change_hook(self):
941 short = self.short_title()
942 long = self.long_title()
943 if short and long:
944 title = short + " - " + long
945 elif short:
946 title = short
947 elif long:
948 title = long
949 else:
950 title = "Untitled"
951 icon = short or long or title
952 if not self.get_saved():
953 title = "*%s*" % title
954 icon = "*%s" % icon
955 self.top.wm_title(title)
956 self.top.wm_iconname(icon)
957
958 def get_saved(self):
959 return self.undo.get_saved()
960
961 def set_saved(self, flag):
962 self.undo.set_saved(flag)
963
964 def reset_undo(self):
965 self.undo.reset_undo()
966
967 def short_title(self):
968 filename = self.io.filename
969 if filename:
970 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000971 # return unicode string to display non-ASCII chars correctly
972 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000973
974 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000975 # return unicode string to display non-ASCII chars correctly
976 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000977
978 def center_insert_event(self, event):
979 self.center()
980
981 def center(self, mark="insert"):
982 text = self.text
983 top, bot = self.getwindowlines()
984 lineno = self.getlineno(mark)
985 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000986 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000987 text.yview(float(newtop))
988
989 def getwindowlines(self):
990 text = self.text
991 top = self.getlineno("@0,0")
992 bot = self.getlineno("@0,65535")
993 if top == bot and text.winfo_height() == 1:
994 # Geometry manager hasn't run yet
995 height = int(text['height'])
996 bot = top + height - 1
997 return top, bot
998
999 def getlineno(self, mark="insert"):
1000 text = self.text
1001 return int(float(text.index(mark)))
1002
Kurt B. Kaiser1061e722003-01-04 01:43:53 +00001003 def get_geometry(self):
1004 "Return (width, height, x, y)"
1005 geom = self.top.wm_geometry()
1006 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
1007 tuple = (map(int, m.groups()))
1008 return tuple
1009
David Scherer7aced172000-08-15 01:13:23 +00001010 def close_event(self, event):
1011 self.close()
1012
1013 def maybesave(self):
1014 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +00001015 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001016 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +00001017 self.top.deiconify()
1018 self.top.lower()
1019 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +00001020 return self.io.maybesave()
1021
1022 def close(self):
David Scherer7aced172000-08-15 01:13:23 +00001023 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +00001024 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +00001025 self._close()
1026 return reply
1027
1028 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +00001029 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00001030 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +00001031 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +00001032 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001033 self.io.close()
1034 self.io = None
1035 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001036 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001037 self.color.close(False)
1038 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001039 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001040 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001041 self.per.close()
1042 self.per = None
1043 self.top.destroy()
1044 if self.close_hook:
1045 # unless override: unregister from flist, terminate if last window
1046 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001047
1048 def load_extensions(self):
1049 self.extensions = {}
1050 self.load_standard_extensions()
1051
1052 def unload_extensions(self):
1053 for ins in self.extensions.values():
1054 if hasattr(ins, "close"):
1055 ins.close()
1056 self.extensions = {}
1057
1058 def load_standard_extensions(self):
1059 for name in self.get_standard_extension_names():
1060 try:
1061 self.load_extension(name)
1062 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001063 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +00001064 import traceback
1065 traceback.print_exc()
1066
1067 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001068 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001069
1070 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001071 try:
1072 mod = __import__(name, globals(), locals(), [])
1073 except ImportError:
1074 print "\nFailed to import extension: ", name
1075 return
David Scherer7aced172000-08-15 01:13:23 +00001076 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001077 keydefs = idleConf.GetExtensionBindings(name)
1078 if hasattr(cls, "menudefs"):
1079 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001080 ins = cls(self)
1081 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001082 if keydefs:
1083 self.apply_bindings(keydefs)
1084 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001085 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001086 while methodname[:1] == '<':
1087 methodname = methodname[1:]
1088 while methodname[-1:] == '>':
1089 methodname = methodname[:-1]
1090 methodname = methodname + "_event"
1091 if hasattr(ins, methodname):
1092 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001093
1094 def apply_bindings(self, keydefs=None):
1095 if keydefs is None:
1096 keydefs = self.Bindings.default_keydefs
1097 text = self.text
1098 text.keydefs = keydefs
1099 for event, keylist in keydefs.items():
1100 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001101 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001102
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001103 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001104 """Add appropriate entries to the menus and submenus
1105
1106 Menus that are absent or None in self.menudict are ignored.
1107 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001108 if menudefs is None:
1109 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001110 if keydefs is None:
1111 keydefs = self.Bindings.default_keydefs
1112 menudict = self.menudict
1113 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001114 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001115 menu = menudict.get(mname)
1116 if not menu:
1117 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001118 for entry in entrylist:
1119 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001120 menu.add_separator()
1121 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001122 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001123 checkbutton = (label[:1] == '!')
1124 if checkbutton:
1125 label = label[1:]
1126 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001127 accelerator = get_accelerator(keydefs, eventname)
1128 def command(text=text, eventname=eventname):
1129 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001130 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001131 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001132 menu.add_checkbutton(label=label, underline=underline,
1133 command=command, accelerator=accelerator,
1134 variable=var)
1135 else:
1136 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001137 command=command,
1138 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001139
1140 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001141 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001142 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001143 value = var.get()
1144 return value
1145 else:
1146 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001147
1148 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001149 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001150 if var:
1151 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001152 else:
1153 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001154
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001155 def get_var_obj(self, name, vartype=None):
1156 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001157 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001158 # create a Tkinter variable object with self.text as master:
1159 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001160 return var
1161
1162 # Tk implementations of "virtual text methods" -- each platform
1163 # reusing IDLE's support code needs to define these for its GUI's
1164 # flavor of widget.
1165
1166 # Is character at text_index in a Python string? Return 0 for
1167 # "guaranteed no", true for anything else. This info is expensive
1168 # to compute ab initio, but is probably already known by the
1169 # platform's colorizer.
1170
1171 def is_char_in_string(self, text_index):
1172 if self.color:
1173 # Return true iff colorizer hasn't (re)gotten this far
1174 # yet, or the character is tagged as being in a string
1175 return self.text.tag_prevrange("TODO", text_index) or \
1176 "STRING" in self.text.tag_names(text_index)
1177 else:
1178 # The colorizer is missing: assume the worst
1179 return 1
1180
1181 # If a selection is defined in the text widget, return (start,
1182 # end) as Tkinter text indices, otherwise return (None, None)
1183 def get_selection_indices(self):
1184 try:
1185 first = self.text.index("sel.first")
1186 last = self.text.index("sel.last")
1187 return first, last
1188 except TclError:
1189 return None, None
1190
1191 # Return the text widget's current view of what a tab stop means
1192 # (equivalent width in spaces).
1193
1194 def get_tabwidth(self):
1195 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1196 return int(current)
1197
1198 # Set the text widget's current view of what a tab stop means.
1199
1200 def set_tabwidth(self, newtabwidth):
1201 text = self.text
1202 if self.get_tabwidth() != newtabwidth:
1203 pixels = text.tk.call("font", "measure", text["font"],
1204 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001205 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001206 text.configure(tabs=pixels)
1207
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001208 # If ispythonsource and guess are true, guess a good value for
1209 # indentwidth based on file content (if possible), and if
1210 # indentwidth != tabwidth set usetabs false.
1211 # In any case, adjust the Text widget's view of what a tab
1212 # character means.
1213
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001214 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001215 if guess and ispythonsource:
1216 i = self.guess_indent()
1217 if 2 <= i <= 8:
1218 self.indentwidth = i
1219 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001220 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001221 self.set_tabwidth(self.tabwidth)
1222
1223 def smart_backspace_event(self, event):
1224 text = self.text
1225 first, last = self.get_selection_indices()
1226 if first and last:
1227 text.delete(first, last)
1228 text.mark_set("insert", first)
1229 return "break"
1230 # Delete whitespace left, until hitting a real char or closest
1231 # preceding virtual tab stop.
1232 chars = text.get("insert linestart", "insert")
1233 if chars == '':
1234 if text.compare("insert", ">", "1.0"):
1235 # easy: delete preceding newline
1236 text.delete("insert-1c")
1237 else:
1238 text.bell() # at start of buffer
1239 return "break"
1240 if chars[-1] not in " \t":
1241 # easy: delete preceding real char
1242 text.delete("insert-1c")
1243 return "break"
1244 # Ick. It may require *inserting* spaces if we back up over a
1245 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001246 tabwidth = self.tabwidth
1247 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001248 assert have > 0
1249 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001250 # Debug prompt is multilined....
Terry Jan Reedy8ef4a702012-01-15 19:02:50 -05001251 if self.context_use_ps1:
1252 last_line_of_prompt = sys.ps1.split('\n')[-1]
1253 else:
1254 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001255 ncharsdeleted = 0
1256 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001257 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001258 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001259 chars = chars[:-1]
1260 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001261 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001262 if have <= want or chars[-1] not in " \t":
1263 break
1264 text.undo_block_start()
1265 text.delete("insert-%dc" % ncharsdeleted, "insert")
1266 if have < want:
1267 text.insert("insert", ' ' * (want - have))
1268 text.undo_block_stop()
1269 return "break"
1270
1271 def smart_indent_event(self, event):
1272 # if intraline selection:
1273 # delete it
1274 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001275 # do indent-region
1276 # else:
1277 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001278 text = self.text
1279 first, last = self.get_selection_indices()
1280 text.undo_block_start()
1281 try:
1282 if first and last:
1283 if index2line(first) != index2line(last):
1284 return self.indent_region_event(event)
1285 text.delete(first, last)
1286 text.mark_set("insert", first)
1287 prefix = text.get("insert linestart", "insert")
1288 raw, effective = classifyws(prefix, self.tabwidth)
1289 if raw == len(prefix):
1290 # only whitespace to the left
1291 self.reindent_to(effective + self.indentwidth)
1292 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001293 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001294 if self.usetabs:
1295 pad = '\t'
1296 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001297 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001298 n = self.indentwidth
1299 pad = ' ' * (n - effective % n)
1300 text.insert("insert", pad)
1301 text.see("insert")
1302 return "break"
1303 finally:
1304 text.undo_block_stop()
1305
1306 def newline_and_indent_event(self, event):
1307 text = self.text
1308 first, last = self.get_selection_indices()
1309 text.undo_block_start()
1310 try:
1311 if first and last:
1312 text.delete(first, last)
1313 text.mark_set("insert", first)
1314 line = text.get("insert linestart", "insert")
1315 i, n = 0, len(line)
1316 while i < n and line[i] in " \t":
1317 i = i+1
1318 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001319 # the cursor is in or at leading indentation in a continuation
1320 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001321 text.insert("insert linestart", '\n')
1322 return "break"
1323 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001324 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001325 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001326 last_line_of_prompt = sys.ps1.split('\n')[-1]
1327 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001328 line = line[:-1]
1329 i = i+1
1330 if i:
1331 text.delete("insert - %d chars" % i, "insert")
1332 # strip whitespace after insert point
1333 while text.get("insert") in " \t":
1334 text.delete("insert")
1335 # start new line
1336 text.insert("insert", '\n')
1337
1338 # adjust indentation for continuations and block
1339 # open/close first need to find the last stmt
1340 lno = index2line(text.index('insert'))
1341 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001342 if not self.context_use_ps1:
1343 for context in self.num_context_lines:
1344 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001345 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001346 rawtext = text.get(startatindex, "insert")
1347 y.set_str(rawtext)
1348 bod = y.find_good_parse_start(
1349 self.context_use_ps1,
1350 self._build_char_in_string_func(startatindex))
1351 if bod is not None or startat == 1:
1352 break
1353 y.set_lo(bod or 0)
1354 else:
1355 r = text.tag_prevrange("console", "insert")
1356 if r:
1357 startatindex = r[1]
1358 else:
1359 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001360 rawtext = text.get(startatindex, "insert")
1361 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001362 y.set_lo(0)
1363
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001364 c = y.get_continuation_type()
1365 if c != PyParse.C_NONE:
1366 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001367 if c == PyParse.C_STRING_FIRST_LINE:
1368 # after the first line of a string; do not indent at all
1369 pass
1370 elif c == PyParse.C_STRING_NEXT_LINES:
1371 # inside a string which started before this line;
1372 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001373 text.insert("insert", indent)
1374 elif c == PyParse.C_BRACKET:
1375 # line up with the first (if any) element of the
1376 # last open bracket structure; else indent one
1377 # level beyond the indent of the line with the
1378 # last open bracket
1379 self.reindent_to(y.compute_bracket_indent())
1380 elif c == PyParse.C_BACKSLASH:
1381 # if more than one line in this stmt already, just
1382 # mimic the current indent; else if initial line
1383 # has a start on an assignment stmt, indent to
1384 # beyond leftmost =; else to beyond first chunk of
1385 # non-whitespace on initial line
1386 if y.get_num_lines_in_stmt() > 1:
1387 text.insert("insert", indent)
1388 else:
1389 self.reindent_to(y.compute_backslash_indent())
1390 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001391 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001392 return "break"
1393
1394 # This line starts a brand new stmt; indent relative to
1395 # indentation of initial line of closest preceding
1396 # interesting stmt.
1397 indent = y.get_base_indent_string()
1398 text.insert("insert", indent)
1399 if y.is_block_opener():
1400 self.smart_indent_event(event)
1401 elif indent and y.is_block_closer():
1402 self.smart_backspace_event(event)
1403 return "break"
1404 finally:
1405 text.see("insert")
1406 text.undo_block_stop()
1407
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001408 # Our editwin provides a is_char_in_string function that works
1409 # with a Tk text index, but PyParse only knows about offsets into
1410 # a string. This builds a function for PyParse that accepts an
1411 # offset.
1412
1413 def _build_char_in_string_func(self, startindex):
1414 def inner(offset, _startindex=startindex,
1415 _icis=self.is_char_in_string):
1416 return _icis(_startindex + "+%dc" % offset)
1417 return inner
1418
1419 def indent_region_event(self, event):
1420 head, tail, chars, lines = self.get_region()
1421 for pos in range(len(lines)):
1422 line = lines[pos]
1423 if line:
1424 raw, effective = classifyws(line, self.tabwidth)
1425 effective = effective + self.indentwidth
1426 lines[pos] = self._make_blanks(effective) + line[raw:]
1427 self.set_region(head, tail, chars, lines)
1428 return "break"
1429
1430 def dedent_region_event(self, event):
1431 head, tail, chars, lines = self.get_region()
1432 for pos in range(len(lines)):
1433 line = lines[pos]
1434 if line:
1435 raw, effective = classifyws(line, self.tabwidth)
1436 effective = max(effective - self.indentwidth, 0)
1437 lines[pos] = self._make_blanks(effective) + line[raw:]
1438 self.set_region(head, tail, chars, lines)
1439 return "break"
1440
1441 def comment_region_event(self, event):
1442 head, tail, chars, lines = self.get_region()
1443 for pos in range(len(lines) - 1):
1444 line = lines[pos]
1445 lines[pos] = '##' + line
1446 self.set_region(head, tail, chars, lines)
1447
1448 def uncomment_region_event(self, event):
1449 head, tail, chars, lines = self.get_region()
1450 for pos in range(len(lines)):
1451 line = lines[pos]
1452 if not line:
1453 continue
1454 if line[:2] == '##':
1455 line = line[2:]
1456 elif line[:1] == '#':
1457 line = line[1:]
1458 lines[pos] = line
1459 self.set_region(head, tail, chars, lines)
1460
1461 def tabify_region_event(self, event):
1462 head, tail, chars, lines = self.get_region()
1463 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001464 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001465 for pos in range(len(lines)):
1466 line = lines[pos]
1467 if line:
1468 raw, effective = classifyws(line, tabwidth)
1469 ntabs, nspaces = divmod(effective, tabwidth)
1470 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1471 self.set_region(head, tail, chars, lines)
1472
1473 def untabify_region_event(self, event):
1474 head, tail, chars, lines = self.get_region()
1475 tabwidth = self._asktabwidth()
Roger Serwy75b249c2013-04-06 20:26:53 -05001476 if tabwidth is None: return
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001477 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001478 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001479 self.set_region(head, tail, chars, lines)
1480
1481 def toggle_tabs_event(self, event):
1482 if self.askyesno(
1483 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001484 "Turn tabs " + ("on", "off")[self.usetabs] +
1485 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001486 ("will be", "remains at")[self.usetabs] + " 8." +
1487 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001488 parent=self.text):
1489 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001490 # Try to prevent inconsistent indentation.
1491 # User must change indent width manually after using tabs.
1492 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001493 return "break"
1494
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001495 # XXX this isn't bound to anything -- see tabwidth comments
1496## def change_tabwidth_event(self, event):
1497## new = self._asktabwidth()
1498## if new != self.tabwidth:
1499## self.tabwidth = new
1500## self.set_indentation_params(0, guess=0)
1501## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001502
1503 def change_indentwidth_event(self, event):
1504 new = self.askinteger(
1505 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001506 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001507 parent=self.text,
1508 initialvalue=self.indentwidth,
1509 minvalue=2,
1510 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001511 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001512 self.indentwidth = new
1513 return "break"
1514
1515 def get_region(self):
1516 text = self.text
1517 first, last = self.get_selection_indices()
1518 if first and last:
1519 head = text.index(first + " linestart")
1520 tail = text.index(last + "-1c lineend +1c")
1521 else:
1522 head = text.index("insert linestart")
1523 tail = text.index("insert lineend +1c")
1524 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001525 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001526 return head, tail, chars, lines
1527
1528 def set_region(self, head, tail, chars, lines):
1529 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001530 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001531 if newchars == chars:
1532 text.bell()
1533 return
1534 text.tag_remove("sel", "1.0", "end")
1535 text.mark_set("insert", head)
1536 text.undo_block_start()
1537 text.delete(head, tail)
1538 text.insert(head, newchars)
1539 text.undo_block_stop()
1540 text.tag_add("sel", head, "insert")
1541
1542 # Make string that displays as n leading blanks.
1543
1544 def _make_blanks(self, n):
1545 if self.usetabs:
1546 ntabs, nspaces = divmod(n, self.tabwidth)
1547 return '\t' * ntabs + ' ' * nspaces
1548 else:
1549 return ' ' * n
1550
1551 # Delete from beginning of line to insert point, then reinsert
1552 # column logical (meaning use tabs if appropriate) spaces.
1553
1554 def reindent_to(self, column):
1555 text = self.text
1556 text.undo_block_start()
1557 if text.compare("insert linestart", "!=", "insert"):
1558 text.delete("insert linestart", "insert")
1559 if column:
1560 text.insert("insert", self._make_blanks(column))
1561 text.undo_block_stop()
1562
1563 def _asktabwidth(self):
1564 return self.askinteger(
1565 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001566 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001567 parent=self.text,
1568 initialvalue=self.indentwidth,
1569 minvalue=2,
Roger Serwy75b249c2013-04-06 20:26:53 -05001570 maxvalue=16)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001571
1572 # Guess indentwidth from text content.
1573 # Return guessed indentwidth. This should not be believed unless
1574 # it's in a reasonable range (e.g., it will be 0 if no indented
1575 # blocks are found).
1576
1577 def guess_indent(self):
1578 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1579 if opener and indented:
1580 raw, indentsmall = classifyws(opener, self.tabwidth)
1581 raw, indentlarge = classifyws(indented, self.tabwidth)
1582 else:
1583 indentsmall = indentlarge = 0
1584 return indentlarge - indentsmall
1585
1586# "line.col" -> line, as an int
1587def index2line(index):
1588 return int(float(index))
1589
1590# Look at the leading whitespace in s.
1591# Return pair (# of leading ws characters,
1592# effective # of leading blanks after expanding
1593# tabs to width tabwidth)
1594
1595def classifyws(s, tabwidth):
1596 raw = effective = 0
1597 for ch in s:
1598 if ch == ' ':
1599 raw = raw + 1
1600 effective = effective + 1
1601 elif ch == '\t':
1602 raw = raw + 1
1603 effective = (effective // tabwidth + 1) * tabwidth
1604 else:
1605 break
1606 return raw, effective
1607
1608import tokenize
1609_tokenize = tokenize
1610del tokenize
1611
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001612class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001613
1614 # .run() chews over the Text widget, looking for a block opener
1615 # and the stmt following it. Returns a pair,
1616 # (line containing block opener, line containing stmt)
1617 # Either or both may be None.
1618
1619 def __init__(self, text, tabwidth):
1620 self.text = text
1621 self.tabwidth = tabwidth
1622 self.i = self.finished = 0
1623 self.blkopenline = self.indentedline = None
1624
1625 def readline(self):
1626 if self.finished:
1627 return ""
1628 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001629 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001630 if self.text.compare(mark, ">=", "end"):
1631 return ""
1632 return self.text.get(mark, mark + " lineend+1c")
1633
1634 def tokeneater(self, type, token, start, end, line,
1635 INDENT=_tokenize.INDENT,
1636 NAME=_tokenize.NAME,
1637 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1638 if self.finished:
1639 pass
1640 elif type == NAME and token in OPENERS:
1641 self.blkopenline = line
1642 elif type == INDENT and self.blkopenline:
1643 self.indentedline = line
1644 self.finished = 1
1645
1646 def run(self):
1647 save_tabsize = _tokenize.tabsize
1648 _tokenize.tabsize = self.tabwidth
1649 try:
1650 try:
1651 _tokenize.tokenize(self.readline, self.tokeneater)
Serhiy Storchaka61006a22012-12-27 21:34:23 +02001652 except (_tokenize.TokenError, SyntaxError):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001653 # since we cut off the tokenizer early, we can trigger
1654 # spurious errors
1655 pass
1656 finally:
1657 _tokenize.tabsize = save_tabsize
1658 return self.blkopenline, self.indentedline
1659
1660### end autoindent code ###
1661
David Scherer7aced172000-08-15 01:13:23 +00001662def prepstr(s):
1663 # Helper to extract the underscore from a string, e.g.
1664 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001665 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001666 if i >= 0:
1667 s = s[:i] + s[i+1:]
1668 return i, s
1669
1670
1671keynames = {
1672 'bracketleft': '[',
1673 'bracketright': ']',
1674 'slash': '/',
1675}
1676
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001677def get_accelerator(keydefs, eventname):
1678 keylist = keydefs.get(eventname)
Ned Deily60651532011-01-31 00:52:49 +00001679 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1680 # if not keylist:
1681 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1682 "<<open-module>>",
1683 "<<goto-line>>",
1684 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001685 return ""
1686 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001687 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001688 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1689 s = re.sub("Key-", "", s)
1690 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1691 s = re.sub("Control-", "Ctrl-", s)
1692 s = re.sub("-", "+", s)
1693 s = re.sub("><", " ", s)
1694 s = re.sub("<", "", s)
1695 s = re.sub(">", "", s)
1696 return s
1697
1698
1699def fixwordbreaks(root):
1700 # Make sure that Tk's double-click and next/previous word
1701 # operations use our definition of a word (i.e. an identifier)
1702 tk = root.tk
1703 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1704 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1705 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1706
1707
1708def test():
1709 root = Tk()
1710 fixwordbreaks(root)
1711 root.withdraw()
1712 if sys.argv[1:]:
1713 filename = sys.argv[1]
1714 else:
1715 filename = None
1716 edit = EditorWindow(root=root, filename=filename)
1717 edit.set_close_hook(root.quit)
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001718 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001719 root.mainloop()
1720 root.destroy()
1721
1722if __name__ == '__main__':
1723 test()