blob: e74b619fb26dc72104241db2bb00fad954f3cc32 [file] [log] [blame]
Brett Cannonaef82d32012-04-14 20:44:23 -04001import imp
2import importlib
David Scherer7aced172000-08-15 01:13:23 +00003import os
David Scherer7aced172000-08-15 01:13:23 +00004import re
Guido van Rossum33d26892007-08-05 15:29:28 +00005import string
Brett Cannonaef82d32012-04-14 20:44:23 -04006import sys
Georg Brandl14fc4272008-05-17 18:39:55 +00007from tkinter import *
8import tkinter.simpledialog as tkSimpleDialog
9import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +000010import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000011import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000012
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000013from idlelib.MultiCall import MultiCallCreator
14from idlelib import idlever
15from idlelib import WindowList
16from idlelib import SearchDialog
17from idlelib import GrepDialog
18from idlelib import ReplaceDialog
19from idlelib import PyParse
20from idlelib.configHandler import idleConf
21from idlelib import aboutDialog, textView, configDialog
22from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000023
24# The default tab setting for a Text widget, in average-width characters.
25TK_TABWIDTH_DEFAULT = 8
26
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000027def _sphinx_version():
28 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
29 major, minor, micro, level, serial = sys.version_info
30 release = '%s%s' % (major, minor)
Martin v. Löwis7f9d1812012-05-01 16:31:18 +020031 release += '%s' % (micro,)
Benjamin Petersonb48f6342009-06-22 19:36:31 +000032 if level == 'candidate':
33 release += 'rc%s' % (serial,)
34 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000035 release += '%s%s' % (level[0], serial)
36 return release
37
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000038def _find_module(fullname, path=None):
39 """Version of imp.find_module() that handles hierarchical module names"""
40
41 file = None
42 for tgt in fullname.split('.'):
43 if file is not None:
44 file.close() # close intermediate files
45 (file, filename, descr) = imp.find_module(tgt, path)
46 if descr[2] == imp.PY_SOURCE:
47 break # find but not load the source file
48 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000049 try:
50 path = module.__path__
51 except AttributeError:
Kurt B. Kaiserad667422007-08-23 01:06:15 +000052 raise ImportError('No source for module ' + module.__name__)
Raymond Hettingerf6445e82011-04-12 18:30:14 -070053 if descr[2] != imp.PY_SOURCE:
54 # If all of the above fails and didn't raise an exception,fallback
55 # to a straight import which can find __init__.py in a package.
56 m = __import__(fullname)
57 try:
58 filename = m.__file__
59 except AttributeError:
60 pass
61 else:
62 file = None
Raymond Hettinger2df393c2011-04-12 18:57:55 -070063 descr = os.path.splitext(filename)[1], None, imp.PY_SOURCE
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000064 return file, filename, descr
65
Terry Jan Reedye91e7632012-02-05 15:14:20 -050066
67class HelpDialog(object):
68
69 def __init__(self):
70 self.parent = None # parent of help window
71 self.dlg = None # the help window iteself
72
73 def display(self, parent, near=None):
74 """ Display the help dialog.
75
76 parent - parent widget for the help window
77
78 near - a Toplevel widget (e.g. EditorWindow or PyShell)
79 to use as a reference for placing the help window
80 """
81 if self.dlg is None:
82 self.show_dialog(parent)
83 if near:
84 self.nearwindow(near)
85
86 def show_dialog(self, parent):
87 self.parent = parent
88 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
89 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
90 dlg.bind('<Destroy>', self.destroy, '+')
91
92 def nearwindow(self, near):
93 # Place the help dialog near the window specified by parent.
94 # Note - this may not reposition the window in Metacity
95 # if "/apps/metacity/general/disable_workarounds" is enabled
96 dlg = self.dlg
97 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
98 dlg.withdraw()
99 dlg.geometry("=+%d+%d" % geom)
100 dlg.deiconify()
101 dlg.lift()
102
103 def destroy(self, ev=None):
104 self.dlg = None
105 self.parent = None
106
107helpDialog = HelpDialog() # singleton instance
108
109
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +0000110class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000111 from idlelib.Percolator import Percolator
112 from idlelib.ColorDelegator import ColorDelegator
113 from idlelib.UndoDelegator import UndoDelegator
114 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
115 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +0000116 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000117 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +0000118
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000119 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000120
121 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000122 if EditorWindow.help_url is None:
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100123 dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000124 if sys.platform.count('linux'):
125 # look for html docs in a couple of standard places
126 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
127 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
128 dochome = '/var/www/html/python/index.html'
129 else:
130 basepath = '/usr/share/doc/' # standard location
131 dochome = os.path.join(basepath, pyver,
132 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000133 elif sys.platform[:3] == 'win':
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100134 chmfile = os.path.join(sys.base_prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +0000135 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000136 if os.path.isfile(chmfile):
137 dochome = chmfile
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000138 elif macosxSupport.runningAsOSXApp():
139 # documentation is stored inside the python framework
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100140 dochome = os.path.join(sys.base_prefix,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000141 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000142 dochome = os.path.normpath(dochome)
143 if os.path.isfile(dochome):
144 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000145 if sys.platform == 'darwin':
146 # Safari requires real file:-URLs
147 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000148 else:
Benjamin Peterson152b6572009-01-20 15:01:54 +0000149 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000150 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +0000151 self.flist = flist
152 root = root or flist.root
153 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000154 try:
155 sys.ps1
156 except AttributeError:
157 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000158 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000159 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000160 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000161 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200162 #self.top.instance_dict makes flist.inversedict available to
163 #configDialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000164 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000165 else:
166 self.tkinter_vars = {} # keys: Tkinter event names
167 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000168 self.top.instance_dict = {}
169 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000170 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000171 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000172 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000173 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000174 text_options = {
175 'name': 'text',
176 'padx': 5,
177 'wrap': 'none',
178 'width': self.width,
179 'height': idleConf.GetOption('main', 'EditorWindow', 'height')}
180 if TkVersion >= 8.5:
181 # Starting with tk 8.5 we have to set the new tabstyle option
182 # to 'wordprocessor' to achieve the same display of tabs as in
183 # older tk versions.
184 text_options['tabstyle'] = 'wordprocessor'
185 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000186 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000187
188 self.createmenubar()
189 self.apply_bindings()
190
191 self.top.protocol("WM_DELETE_WINDOW", self.close)
192 self.top.bind("<<close-window>>", self.close_event)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000193 if macosxSupport.runningAsOSXApp():
194 # Command-W on editorwindows doesn't work without this.
195 text.bind('<<close-window>>', self.close_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000196 # Some OS X systems have only one mouse button,
197 # so use control-click for pulldown menus there.
198 # (Note, AquaTk defines <2> as the right button if
199 # present and the Tk Text widget already binds <2>.)
200 text.bind("<Control-Button-1>",self.right_menu_event)
201 else:
202 # Elsewhere, use right-click for pulldown menus.
203 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000204 text.bind("<<cut>>", self.cut)
205 text.bind("<<copy>>", self.copy)
206 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000207 text.bind("<<center-insert>>", self.center_insert_event)
208 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000209 text.bind("<<python-docs>>", self.python_docs)
210 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000211 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000212 text.bind("<<open-module>>", self.open_module)
213 text.bind("<<do-nothing>>", lambda event: "break")
214 text.bind("<<select-all>>", self.select_all)
215 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000216 text.bind("<<find>>", self.find_event)
217 text.bind("<<find-again>>", self.find_again_event)
218 text.bind("<<find-in-files>>", self.find_in_files_event)
219 text.bind("<<find-selection>>", self.find_selection_event)
220 text.bind("<<replace>>", self.replace_event)
221 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000222 text.bind("<<smart-backspace>>",self.smart_backspace_event)
223 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
224 text.bind("<<smart-indent>>",self.smart_indent_event)
225 text.bind("<<indent-region>>",self.indent_region_event)
226 text.bind("<<dedent-region>>",self.dedent_region_event)
227 text.bind("<<comment-region>>",self.comment_region_event)
228 text.bind("<<uncomment-region>>",self.uncomment_region_event)
229 text.bind("<<tabify-region>>",self.tabify_region_event)
230 text.bind("<<untabify-region>>",self.untabify_region_event)
231 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
232 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000233 text.bind("<Left>", self.move_at_edge_if_selection(0))
234 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000235 text.bind("<<del-word-left>>", self.del_word_left)
236 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000237 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000238
David Scherer7aced172000-08-15 01:13:23 +0000239 if flist:
240 flist.inversedict[self] = key
241 if key:
242 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000243 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000244 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
245 text.bind("<<open-class-browser>>", self.open_class_browser)
246 text.bind("<<open-path-browser>>", self.open_path_browser)
247
Steven M. Gava898a3652001-10-07 11:10:44 +0000248 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000249 vbar['command'] = text.yview
250 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000251 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000252 fontWeight = 'normal'
253 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000254 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000255 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
256 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
257 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000258 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
259 text.pack(side=TOP, fill=BOTH, expand=1)
260 text.focus_set()
261
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000262 # usetabs true -> literal tab characters are used by indent and
263 # dedent cmds, possibly mixed with spaces if
264 # indentwidth is not a multiple of tabwidth,
265 # which will cause Tabnanny to nag!
266 # false -> tab characters are converted to spaces by indent
267 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000268 # Although use-spaces=0 can be configured manually in config-main.def,
269 # configuration of tabs v. spaces is not supported in the configuration
270 # dialog. IDLE promotes the preferred Python indentation: use spaces!
271 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
272 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000273
274 # tabwidth is the display width of a literal tab character.
275 # CAUTION: telling Tk to use anything other than its default
276 # tab setting causes it to use an entirely different tabbing algorithm,
277 # treating tab stops as fixed distances from the left margin.
278 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000279 self.tabwidth = 8 # must remain 8 until Tk is fixed.
280
281 # indentwidth is the number of screen characters per indent level.
282 # The recommended Python indentation is four spaces.
283 self.indentwidth = self.tabwidth
284 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000285
286 # If context_use_ps1 is true, parsing searches back for a ps1 line;
287 # else searches for a popular (if, def, ...) Python stmt.
288 self.context_use_ps1 = False
289
290 # When searching backwards for a reliable place to begin parsing,
291 # first start num_context_lines[0] lines back, then
292 # num_context_lines[1] lines back if that didn't work, and so on.
293 # The last value should be huge (larger than the # of lines in a
294 # conceivable file).
295 # Making the initial values larger slows things down more often.
296 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000297 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000298 self.undo = undo = self.UndoDelegator()
299 per.insertfilter(undo)
300 text.undo_block_start = undo.undo_block_start
301 text.undo_block_stop = undo.undo_block_stop
302 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000303 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000304 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000305 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000306 self.good_load = False
307 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000308 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000309 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000310 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000311 if io.loadfile(filename):
312 self.good_load = True
313 is_py_src = self.ispythonsource(filename)
314 self.set_indentation_params(is_py_src)
315 if is_py_src:
316 self.color = color = self.ColorDelegator()
317 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000318 else:
319 io.set_filename(filename)
Christian Heimesa156e092008-02-16 07:38:31 +0000320 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000321 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000322 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000323 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000324 menu = self.menudict.get('windows')
325 if menu:
326 end = menu.index("end")
327 if end is None:
328 end = -1
329 if end >= 0:
330 menu.add_separator()
331 end = end + 1
332 self.wmenu_end = end
333 WindowList.register_callback(self.postwindowsmenu)
334
335 # Some abstractions so IDLE extensions are cross-IDE
336 self.askyesno = tkMessageBox.askyesno
337 self.askinteger = tkSimpleDialog.askinteger
338 self.showerror = tkMessageBox.showerror
339
Martin v. Löwis307021f2005-11-27 16:59:04 +0000340 def _filename_to_unicode(self, filename):
341 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000342 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000343 return filename
344 else:
345 try:
346 return filename.decode(self.filesystemencoding)
347 except UnicodeDecodeError:
348 # XXX
349 try:
350 return filename.decode(self.encoding)
351 except UnicodeDecodeError:
352 # byte-to-byte conversion
353 return filename.decode('iso8859-1')
354
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000355 def new_callback(self, event):
356 dirname, basename = self.io.defaultfilename()
357 self.flist.new(dirname)
358 return "break"
359
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000360 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400361 if (event.state & 4) != 0 and event.keysym == "Home":
362 # state&4==Control. If <Control-Home>, use the Tk binding.
363 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000364 if self.text.index("iomark") and \
365 self.text.compare("iomark", "<=", "insert lineend") and \
366 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400367 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000368 insertpt = int(self.text.index("iomark").split(".")[1])
369 else:
370 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000371 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000372 if line[insertpt] not in (' ','\t'):
373 break
374 else:
375 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000376 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000377 if insertpt == lineat:
378 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000379 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000380 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400381 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000382 self.text.tag_remove("sel", "1.0", "end")
383 else:
384 if not self.text.index("sel.first"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400385 self.text.mark_set("my_anchor", "insert") # there was no previous selection
386 else:
387 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
388 self.text.mark_set("my_anchor", "sel.first") # extend back
389 else:
390 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000391 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400392 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000393 if self.text.compare(first,">",last):
394 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000395 self.text.tag_remove("sel", "1.0", "end")
396 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000397 self.text.mark_set("insert", dest)
398 self.text.see("insert")
399 return "break"
400
David Scherer7aced172000-08-15 01:13:23 +0000401 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000402 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000403 if macosxSupport.runningAsOSXApp():
404 # Insert some padding to avoid obscuring some of the statusbar
405 # by the resize widget.
406 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000407 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
408 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
409 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000410 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
411 self.text.event_add("<<set-line-and-column>>",
412 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000413 self.text.after_idle(self.set_line_and_column)
414
415 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000416 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000417 self.status_bar.set_label('column', 'Col: %s' % column)
418 self.status_bar.set_label('line', 'Ln: %s' % line)
419
David Scherer7aced172000-08-15 01:13:23 +0000420 menu_specs = [
421 ("file", "_File"),
422 ("edit", "_Edit"),
423 ("format", "F_ormat"),
424 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000425 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000426 ("windows", "_Windows"),
427 ("help", "_Help"),
428 ]
429
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000430 if macosxSupport.runningAsOSXApp():
431 del menu_specs[-3]
432 menu_specs[-2] = ("windows", "_Window")
433
434
David Scherer7aced172000-08-15 01:13:23 +0000435 def createmenubar(self):
436 mbar = self.menubar
437 self.menudict = menudict = {}
438 for name, label in self.menu_specs:
439 underline, label = prepstr(label)
440 menudict[name] = menu = Menu(mbar, name=name)
441 mbar.add_cascade(label=label, menu=menu, underline=underline)
Georg Brandlaedd2892010-12-19 10:10:32 +0000442 if macosxSupport.isCarbonAquaTk(self.root):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000443 # Insert the application menu
444 menudict['application'] = menu = Menu(mbar, name='apple')
445 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000446 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000447 self.recent_files_menu = Menu(self.menubar)
448 self.menudict['file'].insert_cascade(3, label='Recent Files',
449 underline=0,
450 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000451 self.base_helpmenu_length = self.menudict['help'].index(END)
452 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000453
454 def postwindowsmenu(self):
455 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000456 menu = self.menudict['windows']
457 end = menu.index("end")
458 if end is None:
459 end = -1
460 if end > self.wmenu_end:
461 menu.delete(self.wmenu_end+1, end)
462 WindowList.add_windows_to_menu(menu)
463
464 rmenu = None
465
466 def right_menu_event(self, event):
David Scherer7aced172000-08-15 01:13:23 +0000467 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
468 if not self.rmenu:
469 self.make_rmenu()
470 rmenu = self.rmenu
471 self.event = event
472 iswin = sys.platform[:3] == 'win'
473 if iswin:
474 self.text.config(cursor="arrow")
Andrew Svetlovd1837672012-11-01 22:41:19 +0200475
476 for label, eventname, verify_state in self.rmenu_specs:
477 if verify_state is None:
478 continue
479 state = getattr(self, verify_state)()
480 rmenu.entryconfigure(label, state=state)
481
482
David Scherer7aced172000-08-15 01:13:23 +0000483 rmenu.tk_popup(event.x_root, event.y_root)
484 if iswin:
485 self.text.config(cursor="ibeam")
486
487 rmenu_specs = [
Andrew Svetlovd1837672012-11-01 22:41:19 +0200488 # ("Label", "<<virtual-event>>", "statefuncname"), ...
489 ("Close", "<<close-window>>", None), # Example
David Scherer7aced172000-08-15 01:13:23 +0000490 ]
491
492 def make_rmenu(self):
493 rmenu = Menu(self.text, tearoff=0)
Andrew Svetlovd1837672012-11-01 22:41:19 +0200494 for label, eventname, _ in self.rmenu_specs:
495 if label is not None:
496 def command(text=self.text, eventname=eventname):
497 text.event_generate(eventname)
498 rmenu.add_command(label=label, command=command)
499 else:
500 rmenu.add_separator()
David Scherer7aced172000-08-15 01:13:23 +0000501 self.rmenu = rmenu
502
Andrew Svetlovd1837672012-11-01 22:41:19 +0200503 def rmenu_check_cut(self):
504 return self.rmenu_check_copy()
505
506 def rmenu_check_copy(self):
507 try:
508 indx = self.text.index('sel.first')
509 except TclError:
510 return 'disabled'
511 else:
512 return 'normal' if indx else 'disabled'
513
514 def rmenu_check_paste(self):
515 try:
516 self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
517 except TclError:
518 return 'disabled'
519 else:
520 return 'normal'
521
David Scherer7aced172000-08-15 01:13:23 +0000522 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000523 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000524
Steven M. Gava3b55a892001-11-21 05:56:26 +0000525 def config_dialog(self, event=None):
526 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000527
David Scherer7aced172000-08-15 01:13:23 +0000528 def help_dialog(self, event=None):
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500529 if self.root:
530 parent = self.root
531 else:
532 parent = self.top
533 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000534
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000535 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000536 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000537 try:
538 os.startfile(self.help_url)
539 except WindowsError as why:
540 tkMessageBox.showerror(title='Document Start Failure',
541 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000542 else:
543 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000544 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000545
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000546 def cut(self,event):
547 self.text.event_generate("<<Cut>>")
548 return "break"
549
550 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000551 if not self.text.tag_ranges("sel"):
552 # There is no selection, so do nothing and maybe interrupt.
553 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000554 self.text.event_generate("<<Copy>>")
555 return "break"
556
557 def paste(self,event):
558 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000559 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000560 return "break"
561
David Scherer7aced172000-08-15 01:13:23 +0000562 def select_all(self, event=None):
563 self.text.tag_add("sel", "1.0", "end-1c")
564 self.text.mark_set("insert", "1.0")
565 self.text.see("insert")
566 return "break"
567
568 def remove_selection(self, event=None):
569 self.text.tag_remove("sel", "1.0", "end")
570 self.text.see("insert")
571
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000572 def move_at_edge_if_selection(self, edge_index):
573 """Cursor move begins at start or end of selection
574
575 When a left/right cursor key is pressed create and return to Tkinter a
576 function which causes a cursor move from the associated edge of the
577 selection.
578
579 """
580 self_text_index = self.text.index
581 self_text_mark_set = self.text.mark_set
582 edges_table = ("sel.first+1c", "sel.last-1c")
583 def move_at_edge(event):
584 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
585 try:
586 self_text_index("sel.first")
587 self_text_mark_set("insert", edges_table[edge_index])
588 except TclError:
589 pass
590 return move_at_edge
591
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000592 def del_word_left(self, event):
593 self.text.event_generate('<Meta-Delete>')
594 return "break"
595
596 def del_word_right(self, event):
597 self.text.event_generate('<Meta-d>')
598 return "break"
599
Steven M. Gavac5976402002-01-04 03:06:08 +0000600 def find_event(self, event):
601 SearchDialog.find(self.text)
602 return "break"
603
604 def find_again_event(self, event):
605 SearchDialog.find_again(self.text)
606 return "break"
607
608 def find_selection_event(self, event):
609 SearchDialog.find_selection(self.text)
610 return "break"
611
612 def find_in_files_event(self, event):
613 GrepDialog.grep(self.text, self.io, self.flist)
614 return "break"
615
616 def replace_event(self, event):
617 ReplaceDialog.replace(self.text)
618 return "break"
619
620 def goto_line_event(self, event):
621 text = self.text
622 lineno = tkSimpleDialog.askinteger("Goto",
623 "Go to line number:",parent=text)
624 if lineno is None:
625 return "break"
626 if lineno <= 0:
627 text.bell()
628 return "break"
629 text.mark_set("insert", "%d.0" % lineno)
630 text.see("insert")
631
David Scherer7aced172000-08-15 01:13:23 +0000632 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000633 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000634 try:
635 name = self.text.get("sel.first", "sel.last")
636 except TclError:
637 name = ""
638 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000639 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000640 name = tkSimpleDialog.askstring("Module",
641 "Enter the name of a Python module\n"
642 "to search on sys.path and open:",
643 parent=self.text, initialvalue=name)
644 if name:
645 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000646 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000647 return
David Scherer7aced172000-08-15 01:13:23 +0000648 # XXX Ought to insert current file's directory in front of path
649 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000650 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000651 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000652 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
653 return
654 if type != imp.PY_SOURCE:
655 tkMessageBox.showerror("Unsupported type",
656 "%s is not a source module" % name, parent=self.text)
657 return
658 if f:
659 f.close()
660 if self.flist:
661 self.flist.open(file)
662 else:
663 self.io.loadfile(file)
664
665 def open_class_browser(self, event=None):
666 filename = self.io.filename
667 if not filename:
668 tkMessageBox.showerror(
669 "No filename",
670 "This buffer has no associated filename",
671 master=self.text)
672 self.text.focus_set()
673 return None
674 head, tail = os.path.split(filename)
675 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000676 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000677 ClassBrowser.ClassBrowser(self.flist, base, [head])
678
679 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000680 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000681 PathBrowser.PathBrowser(self.flist)
682
683 def gotoline(self, lineno):
684 if lineno is not None and lineno > 0:
685 self.text.mark_set("insert", "%d.0" % lineno)
686 self.text.tag_remove("sel", "1.0", "end")
687 self.text.tag_add("sel", "insert", "insert +1l")
688 self.center()
689
690 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000691 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000692 return True
David Scherer7aced172000-08-15 01:13:23 +0000693 base, ext = os.path.splitext(os.path.basename(filename))
694 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000695 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000696 line = self.text.get('1.0', '1.0 lineend')
697 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000698
699 def close_hook(self):
700 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000701 self.flist.unregister_maybe_terminate(self)
702 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000703
704 def set_close_hook(self, close_hook):
705 self.close_hook = close_hook
706
707 def filename_change_hook(self):
708 if self.flist:
709 self.flist.filename_changed_edit(self)
710 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000711 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000712 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000713
Christian Heimesa156e092008-02-16 07:38:31 +0000714 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000715 if self.color:
716 return
Christian Heimesa156e092008-02-16 07:38:31 +0000717 if self.ispythonsource(self.io.filename):
718 self.color = self.ColorDelegator()
719 # can add more colorizers here...
720 if self.color:
721 self.per.removefilter(self.undo)
722 self.per.insertfilter(self.color)
723 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000724
Christian Heimesa156e092008-02-16 07:38:31 +0000725 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000726 if not self.color:
727 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000728 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000729 self.per.removefilter(self.color)
730 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000731
Steven M. Gavab77d3432002-03-02 07:16:21 +0000732 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000733 "Update the colour theme"
734 # Called from self.filename_change_hook and from configDialog.py
735 self._rmcolorizer()
736 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000737 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000738 normal_colors = idleConf.GetHighlight(theme, 'normal')
739 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
740 select_colors = idleConf.GetHighlight(theme, 'hilite')
741 self.text.config(
742 foreground=normal_colors['foreground'],
743 background=normal_colors['background'],
744 insertbackground=cursor_color,
745 selectforeground=select_colors['foreground'],
746 selectbackground=select_colors['background'],
747 )
David Scherer7aced172000-08-15 01:13:23 +0000748
Guido van Rossum33d26892007-08-05 15:29:28 +0000749 IDENTCHARS = string.ascii_letters + string.digits + "_"
750
751 def colorize_syntax_error(self, text, pos):
752 text.tag_add("ERROR", pos)
753 char = text.get(pos)
754 if char and char in self.IDENTCHARS:
755 text.tag_add("ERROR", pos + " wordstart", pos)
756 if '\n' == text.get(pos): # error at line end
757 text.mark_set("insert", pos)
758 else:
759 text.mark_set("insert", pos + "+1c")
760 text.see(pos)
761
Steven M. Gavab1585412002-03-12 00:21:56 +0000762 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000763 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000764 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000765 fontWeight='normal'
766 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
767 fontWeight='bold'
768 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
769 idleConf.GetOption('main','EditorWindow','font-size'),
770 fontWeight))
771
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000772 def RemoveKeybindings(self):
773 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000774 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000775 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000776 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000777 self.text.event_delete(event, *keylist)
778 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000779 xkeydefs = idleConf.GetExtensionBindings(extensionName)
780 if xkeydefs:
781 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000782 self.text.event_delete(event, *keylist)
783
784 def ApplyKeybindings(self):
785 "Update the keybindings after they are changed"
786 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000787 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000788 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000789 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000790 xkeydefs = idleConf.GetExtensionBindings(extensionName)
791 if xkeydefs:
792 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000793 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000794 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000795 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000796 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000797 for item in menu[1]:
798 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000799 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000800 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000801 menu = self.menudict[menubarItem]
802 end = menu.index(END) + 1
803 for index in range(0, end):
804 if menu.type(index) == 'command':
805 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000806 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000807 itemName = menu.entrycget(index, 'label')
808 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000809 if menubarItem in menuEventDict:
810 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000811 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000812 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000813 accel = get_accelerator(keydefs, event)
814 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000815
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000816 def set_notabs_indentwidth(self):
817 "Update the indentwidth if changed and not using tabs in this window"
818 # Called from configDialog.py
819 if not self.usetabs:
820 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
821 type='int')
822
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000823 def reset_help_menu_entries(self):
824 "Update the additional help entries on the Help menu"
825 help_list = idleConf.GetAllExtraHelpSourcesList()
826 helpmenu = self.menudict['help']
827 # first delete the extra help entries, if any
828 helpmenu_length = helpmenu.index(END)
829 if helpmenu_length > self.base_helpmenu_length:
830 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
831 # then rebuild them
832 if help_list:
833 helpmenu.add_separator()
834 for entry in help_list:
835 cmd = self.__extra_help_callback(entry[1])
836 helpmenu.add_command(label=entry[0], command=cmd)
837 # and update the menu dictionary
838 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000839
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000840 def __extra_help_callback(self, helpfile):
841 "Create a callback with the helpfile value frozen at definition time"
842 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000843 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000844 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000845 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000846 try:
847 os.startfile(helpfile)
848 except WindowsError as why:
849 tkMessageBox.showerror(title='Document Start Failure',
850 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000851 else:
852 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000853 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000854
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000855 def update_recent_files_list(self, new_file=None):
856 "Load and update the recent files list and menus"
857 rf_list = []
858 if os.path.exists(self.recent_files_path):
Ned Deily122539e2011-01-24 21:46:44 +0000859 rf_list_file = open(self.recent_files_path,'r',
860 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000861 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000862 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000863 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000864 rf_list_file.close()
865 if new_file:
866 new_file = os.path.abspath(new_file) + '\n'
867 if new_file in rf_list:
868 rf_list.remove(new_file) # move to top
869 rf_list.insert(0, new_file)
870 # clean and save the recent files list
871 bad_paths = []
872 for path in rf_list:
873 if '\0' in path or not os.path.exists(path[0:-1]):
874 bad_paths.append(path)
875 rf_list = [path for path in rf_list if path not in bad_paths]
876 ulchars = "1234567890ABCDEFGHIJK"
877 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000878 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800879 with open(self.recent_files_path, 'w',
880 encoding='utf_8', errors='replace') as rf_file:
881 rf_file.writelines(rf_list)
882 except IOError as err:
883 if not getattr(self.root, "recentfilelist_error_displayed", False):
884 self.root.recentfilelist_error_displayed = True
885 tkMessageBox.showerror(title='IDLE Error',
886 message='Unable to update Recent Files list:\n%s'
887 % str(err),
888 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000889 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000890 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000891 menu = instance.recent_files_menu
Ned Deily3aee9412012-05-29 10:43:36 -0700892 menu.delete(0, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000893 for i, file_name in enumerate(rf_list):
894 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000895 # make unicode string to display non-ASCII chars correctly
896 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000897 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000898 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000899 command=callback,
900 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000901
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000902 def __recent_file_callback(self, file_name):
903 def open_recent_file(fn_closure=file_name):
904 self.io.open(editFile=fn_closure)
905 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000906
David Scherer7aced172000-08-15 01:13:23 +0000907 def saved_change_hook(self):
908 short = self.short_title()
909 long = self.long_title()
910 if short and long:
911 title = short + " - " + long
912 elif short:
913 title = short
914 elif long:
915 title = long
916 else:
917 title = "Untitled"
918 icon = short or long or title
919 if not self.get_saved():
920 title = "*%s*" % title
921 icon = "*%s" % icon
922 self.top.wm_title(title)
923 self.top.wm_iconname(icon)
924
925 def get_saved(self):
926 return self.undo.get_saved()
927
928 def set_saved(self, flag):
929 self.undo.set_saved(flag)
930
931 def reset_undo(self):
932 self.undo.reset_undo()
933
934 def short_title(self):
935 filename = self.io.filename
936 if filename:
937 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000938 # return unicode string to display non-ASCII chars correctly
939 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000940
941 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000942 # return unicode string to display non-ASCII chars correctly
943 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000944
945 def center_insert_event(self, event):
946 self.center()
947
948 def center(self, mark="insert"):
949 text = self.text
950 top, bot = self.getwindowlines()
951 lineno = self.getlineno(mark)
952 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000953 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000954 text.yview(float(newtop))
955
956 def getwindowlines(self):
957 text = self.text
958 top = self.getlineno("@0,0")
959 bot = self.getlineno("@0,65535")
960 if top == bot and text.winfo_height() == 1:
961 # Geometry manager hasn't run yet
962 height = int(text['height'])
963 bot = top + height - 1
964 return top, bot
965
966 def getlineno(self, mark="insert"):
967 text = self.text
968 return int(float(text.index(mark)))
969
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000970 def get_geometry(self):
971 "Return (width, height, x, y)"
972 geom = self.top.wm_geometry()
973 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000974 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000975
David Scherer7aced172000-08-15 01:13:23 +0000976 def close_event(self, event):
977 self.close()
978
979 def maybesave(self):
980 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000981 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000982 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000983 self.top.deiconify()
984 self.top.lower()
985 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000986 return self.io.maybesave()
987
988 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000989 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000990 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000991 self._close()
992 return reply
993
994 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000995 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000996 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000997 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000998 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000999 self.io.close()
1000 self.io = None
1001 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +00001002 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001003 self.color.close(False)
1004 self.color = None
David Scherer7aced172000-08-15 01:13:23 +00001005 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001006 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001007 self.per.close()
1008 self.per = None
1009 self.top.destroy()
1010 if self.close_hook:
1011 # unless override: unregister from flist, terminate if last window
1012 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +00001013
1014 def load_extensions(self):
1015 self.extensions = {}
1016 self.load_standard_extensions()
1017
1018 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001019 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +00001020 if hasattr(ins, "close"):
1021 ins.close()
1022 self.extensions = {}
1023
1024 def load_standard_extensions(self):
1025 for name in self.get_standard_extension_names():
1026 try:
1027 self.load_extension(name)
1028 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001029 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001030 traceback.print_exc()
1031
1032 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001033 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001034
1035 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001036 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001037 try:
1038 mod = importlib.import_module('.' + name, package=__package__)
1039 except ImportError:
1040 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001041 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001042 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001043 raise
David Scherer7aced172000-08-15 01:13:23 +00001044 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001045 keydefs = idleConf.GetExtensionBindings(name)
1046 if hasattr(cls, "menudefs"):
1047 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001048 ins = cls(self)
1049 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001050 if keydefs:
1051 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001052 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001053 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001054 while methodname[:1] == '<':
1055 methodname = methodname[1:]
1056 while methodname[-1:] == '>':
1057 methodname = methodname[:-1]
1058 methodname = methodname + "_event"
1059 if hasattr(ins, methodname):
1060 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001061
1062 def apply_bindings(self, keydefs=None):
1063 if keydefs is None:
1064 keydefs = self.Bindings.default_keydefs
1065 text = self.text
1066 text.keydefs = keydefs
1067 for event, keylist in keydefs.items():
1068 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001069 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001070
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001071 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001072 """Add appropriate entries to the menus and submenus
1073
1074 Menus that are absent or None in self.menudict are ignored.
1075 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001076 if menudefs is None:
1077 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001078 if keydefs is None:
1079 keydefs = self.Bindings.default_keydefs
1080 menudict = self.menudict
1081 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001082 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001083 menu = menudict.get(mname)
1084 if not menu:
1085 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001086 for entry in entrylist:
1087 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001088 menu.add_separator()
1089 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001090 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001091 checkbutton = (label[:1] == '!')
1092 if checkbutton:
1093 label = label[1:]
1094 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001095 accelerator = get_accelerator(keydefs, eventname)
1096 def command(text=text, eventname=eventname):
1097 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001098 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001099 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001100 menu.add_checkbutton(label=label, underline=underline,
1101 command=command, accelerator=accelerator,
1102 variable=var)
1103 else:
1104 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001105 command=command,
1106 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001107
1108 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001109 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001110 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001111 value = var.get()
1112 return value
1113 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001114 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001115
1116 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001117 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001118 if var:
1119 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001120 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001121 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001122
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001123 def get_var_obj(self, name, vartype=None):
1124 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001125 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001126 # create a Tkinter variable object with self.text as master:
1127 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001128 return var
1129
1130 # Tk implementations of "virtual text methods" -- each platform
1131 # reusing IDLE's support code needs to define these for its GUI's
1132 # flavor of widget.
1133
1134 # Is character at text_index in a Python string? Return 0 for
1135 # "guaranteed no", true for anything else. This info is expensive
1136 # to compute ab initio, but is probably already known by the
1137 # platform's colorizer.
1138
1139 def is_char_in_string(self, text_index):
1140 if self.color:
1141 # Return true iff colorizer hasn't (re)gotten this far
1142 # yet, or the character is tagged as being in a string
1143 return self.text.tag_prevrange("TODO", text_index) or \
1144 "STRING" in self.text.tag_names(text_index)
1145 else:
1146 # The colorizer is missing: assume the worst
1147 return 1
1148
1149 # If a selection is defined in the text widget, return (start,
1150 # end) as Tkinter text indices, otherwise return (None, None)
1151 def get_selection_indices(self):
1152 try:
1153 first = self.text.index("sel.first")
1154 last = self.text.index("sel.last")
1155 return first, last
1156 except TclError:
1157 return None, None
1158
1159 # Return the text widget's current view of what a tab stop means
1160 # (equivalent width in spaces).
1161
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001162 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001163 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1164 return int(current)
1165
1166 # Set the text widget's current view of what a tab stop means.
1167
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001168 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001169 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001170 if self.get_tk_tabwidth() != newtabwidth:
1171 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001172 pixels = text.tk.call("font", "measure", text["font"],
1173 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001174 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001175 text.configure(tabs=pixels)
1176
Guido van Rossum33d26892007-08-05 15:29:28 +00001177### begin autoindent code ### (configuration was moved to beginning of class)
1178
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001179 def set_indentation_params(self, is_py_src, guess=True):
1180 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001181 i = self.guess_indent()
1182 if 2 <= i <= 8:
1183 self.indentwidth = i
1184 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001185 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001186 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001187
1188 def smart_backspace_event(self, event):
1189 text = self.text
1190 first, last = self.get_selection_indices()
1191 if first and last:
1192 text.delete(first, last)
1193 text.mark_set("insert", first)
1194 return "break"
1195 # Delete whitespace left, until hitting a real char or closest
1196 # preceding virtual tab stop.
1197 chars = text.get("insert linestart", "insert")
1198 if chars == '':
1199 if text.compare("insert", ">", "1.0"):
1200 # easy: delete preceding newline
1201 text.delete("insert-1c")
1202 else:
1203 text.bell() # at start of buffer
1204 return "break"
1205 if chars[-1] not in " \t":
1206 # easy: delete preceding real char
1207 text.delete("insert-1c")
1208 return "break"
1209 # Ick. It may require *inserting* spaces if we back up over a
1210 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001211 tabwidth = self.tabwidth
1212 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001213 assert have > 0
1214 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001215 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001216 if self.context_use_ps1:
1217 last_line_of_prompt = sys.ps1.split('\n')[-1]
1218 else:
1219 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001220 ncharsdeleted = 0
1221 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001222 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001223 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001224 chars = chars[:-1]
1225 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001226 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001227 if have <= want or chars[-1] not in " \t":
1228 break
1229 text.undo_block_start()
1230 text.delete("insert-%dc" % ncharsdeleted, "insert")
1231 if have < want:
1232 text.insert("insert", ' ' * (want - have))
1233 text.undo_block_stop()
1234 return "break"
1235
1236 def smart_indent_event(self, event):
1237 # if intraline selection:
1238 # delete it
1239 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001240 # do indent-region
1241 # else:
1242 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001243 text = self.text
1244 first, last = self.get_selection_indices()
1245 text.undo_block_start()
1246 try:
1247 if first and last:
1248 if index2line(first) != index2line(last):
1249 return self.indent_region_event(event)
1250 text.delete(first, last)
1251 text.mark_set("insert", first)
1252 prefix = text.get("insert linestart", "insert")
1253 raw, effective = classifyws(prefix, self.tabwidth)
1254 if raw == len(prefix):
1255 # only whitespace to the left
1256 self.reindent_to(effective + self.indentwidth)
1257 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001258 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001259 if self.usetabs:
1260 pad = '\t'
1261 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001262 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001263 n = self.indentwidth
1264 pad = ' ' * (n - effective % n)
1265 text.insert("insert", pad)
1266 text.see("insert")
1267 return "break"
1268 finally:
1269 text.undo_block_stop()
1270
1271 def newline_and_indent_event(self, event):
1272 text = self.text
1273 first, last = self.get_selection_indices()
1274 text.undo_block_start()
1275 try:
1276 if first and last:
1277 text.delete(first, last)
1278 text.mark_set("insert", first)
1279 line = text.get("insert linestart", "insert")
1280 i, n = 0, len(line)
1281 while i < n and line[i] in " \t":
1282 i = i+1
1283 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001284 # the cursor is in or at leading indentation in a continuation
1285 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001286 text.insert("insert linestart", '\n')
1287 return "break"
1288 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001289 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001290 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001291 last_line_of_prompt = sys.ps1.split('\n')[-1]
1292 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001293 line = line[:-1]
1294 i = i+1
1295 if i:
1296 text.delete("insert - %d chars" % i, "insert")
1297 # strip whitespace after insert point
1298 while text.get("insert") in " \t":
1299 text.delete("insert")
1300 # start new line
1301 text.insert("insert", '\n')
1302
1303 # adjust indentation for continuations and block
1304 # open/close first need to find the last stmt
1305 lno = index2line(text.index('insert'))
1306 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001307 if not self.context_use_ps1:
1308 for context in self.num_context_lines:
1309 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001310 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001311 rawtext = text.get(startatindex, "insert")
1312 y.set_str(rawtext)
1313 bod = y.find_good_parse_start(
1314 self.context_use_ps1,
1315 self._build_char_in_string_func(startatindex))
1316 if bod is not None or startat == 1:
1317 break
1318 y.set_lo(bod or 0)
1319 else:
1320 r = text.tag_prevrange("console", "insert")
1321 if r:
1322 startatindex = r[1]
1323 else:
1324 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001325 rawtext = text.get(startatindex, "insert")
1326 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001327 y.set_lo(0)
1328
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001329 c = y.get_continuation_type()
1330 if c != PyParse.C_NONE:
1331 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001332 if c == PyParse.C_STRING_FIRST_LINE:
1333 # after the first line of a string; do not indent at all
1334 pass
1335 elif c == PyParse.C_STRING_NEXT_LINES:
1336 # inside a string which started before this line;
1337 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001338 text.insert("insert", indent)
1339 elif c == PyParse.C_BRACKET:
1340 # line up with the first (if any) element of the
1341 # last open bracket structure; else indent one
1342 # level beyond the indent of the line with the
1343 # last open bracket
1344 self.reindent_to(y.compute_bracket_indent())
1345 elif c == PyParse.C_BACKSLASH:
1346 # if more than one line in this stmt already, just
1347 # mimic the current indent; else if initial line
1348 # has a start on an assignment stmt, indent to
1349 # beyond leftmost =; else to beyond first chunk of
1350 # non-whitespace on initial line
1351 if y.get_num_lines_in_stmt() > 1:
1352 text.insert("insert", indent)
1353 else:
1354 self.reindent_to(y.compute_backslash_indent())
1355 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001356 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001357 return "break"
1358
1359 # This line starts a brand new stmt; indent relative to
1360 # indentation of initial line of closest preceding
1361 # interesting stmt.
1362 indent = y.get_base_indent_string()
1363 text.insert("insert", indent)
1364 if y.is_block_opener():
1365 self.smart_indent_event(event)
1366 elif indent and y.is_block_closer():
1367 self.smart_backspace_event(event)
1368 return "break"
1369 finally:
1370 text.see("insert")
1371 text.undo_block_stop()
1372
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001373 # Our editwin provides a is_char_in_string function that works
1374 # with a Tk text index, but PyParse only knows about offsets into
1375 # a string. This builds a function for PyParse that accepts an
1376 # offset.
1377
1378 def _build_char_in_string_func(self, startindex):
1379 def inner(offset, _startindex=startindex,
1380 _icis=self.is_char_in_string):
1381 return _icis(_startindex + "+%dc" % offset)
1382 return inner
1383
1384 def indent_region_event(self, event):
1385 head, tail, chars, lines = self.get_region()
1386 for pos in range(len(lines)):
1387 line = lines[pos]
1388 if line:
1389 raw, effective = classifyws(line, self.tabwidth)
1390 effective = effective + self.indentwidth
1391 lines[pos] = self._make_blanks(effective) + line[raw:]
1392 self.set_region(head, tail, chars, lines)
1393 return "break"
1394
1395 def dedent_region_event(self, event):
1396 head, tail, chars, lines = self.get_region()
1397 for pos in range(len(lines)):
1398 line = lines[pos]
1399 if line:
1400 raw, effective = classifyws(line, self.tabwidth)
1401 effective = max(effective - self.indentwidth, 0)
1402 lines[pos] = self._make_blanks(effective) + line[raw:]
1403 self.set_region(head, tail, chars, lines)
1404 return "break"
1405
1406 def comment_region_event(self, event):
1407 head, tail, chars, lines = self.get_region()
1408 for pos in range(len(lines) - 1):
1409 line = lines[pos]
1410 lines[pos] = '##' + line
1411 self.set_region(head, tail, chars, lines)
1412
1413 def uncomment_region_event(self, event):
1414 head, tail, chars, lines = self.get_region()
1415 for pos in range(len(lines)):
1416 line = lines[pos]
1417 if not line:
1418 continue
1419 if line[:2] == '##':
1420 line = line[2:]
1421 elif line[:1] == '#':
1422 line = line[1:]
1423 lines[pos] = line
1424 self.set_region(head, tail, chars, lines)
1425
1426 def tabify_region_event(self, event):
1427 head, tail, chars, lines = self.get_region()
1428 tabwidth = self._asktabwidth()
1429 for pos in range(len(lines)):
1430 line = lines[pos]
1431 if line:
1432 raw, effective = classifyws(line, tabwidth)
1433 ntabs, nspaces = divmod(effective, tabwidth)
1434 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1435 self.set_region(head, tail, chars, lines)
1436
1437 def untabify_region_event(self, event):
1438 head, tail, chars, lines = self.get_region()
1439 tabwidth = self._asktabwidth()
1440 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001441 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001442 self.set_region(head, tail, chars, lines)
1443
1444 def toggle_tabs_event(self, event):
1445 if self.askyesno(
1446 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001447 "Turn tabs " + ("on", "off")[self.usetabs] +
1448 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001449 ("will be", "remains at")[self.usetabs] + " 8." +
1450 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001451 parent=self.text):
1452 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001453 # Try to prevent inconsistent indentation.
1454 # User must change indent width manually after using tabs.
1455 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001456 return "break"
1457
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001458 # XXX this isn't bound to anything -- see tabwidth comments
1459## def change_tabwidth_event(self, event):
1460## new = self._asktabwidth()
1461## if new != self.tabwidth:
1462## self.tabwidth = new
1463## self.set_indentation_params(0, guess=0)
1464## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001465
1466 def change_indentwidth_event(self, event):
1467 new = self.askinteger(
1468 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001469 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001470 parent=self.text,
1471 initialvalue=self.indentwidth,
1472 minvalue=2,
1473 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001474 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001475 self.indentwidth = new
1476 return "break"
1477
1478 def get_region(self):
1479 text = self.text
1480 first, last = self.get_selection_indices()
1481 if first and last:
1482 head = text.index(first + " linestart")
1483 tail = text.index(last + "-1c lineend +1c")
1484 else:
1485 head = text.index("insert linestart")
1486 tail = text.index("insert lineend +1c")
1487 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001488 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001489 return head, tail, chars, lines
1490
1491 def set_region(self, head, tail, chars, lines):
1492 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001493 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001494 if newchars == chars:
1495 text.bell()
1496 return
1497 text.tag_remove("sel", "1.0", "end")
1498 text.mark_set("insert", head)
1499 text.undo_block_start()
1500 text.delete(head, tail)
1501 text.insert(head, newchars)
1502 text.undo_block_stop()
1503 text.tag_add("sel", head, "insert")
1504
1505 # Make string that displays as n leading blanks.
1506
1507 def _make_blanks(self, n):
1508 if self.usetabs:
1509 ntabs, nspaces = divmod(n, self.tabwidth)
1510 return '\t' * ntabs + ' ' * nspaces
1511 else:
1512 return ' ' * n
1513
1514 # Delete from beginning of line to insert point, then reinsert
1515 # column logical (meaning use tabs if appropriate) spaces.
1516
1517 def reindent_to(self, column):
1518 text = self.text
1519 text.undo_block_start()
1520 if text.compare("insert linestart", "!=", "insert"):
1521 text.delete("insert linestart", "insert")
1522 if column:
1523 text.insert("insert", self._make_blanks(column))
1524 text.undo_block_stop()
1525
1526 def _asktabwidth(self):
1527 return self.askinteger(
1528 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001529 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001530 parent=self.text,
1531 initialvalue=self.indentwidth,
1532 minvalue=2,
1533 maxvalue=16) or self.tabwidth
1534
1535 # Guess indentwidth from text content.
1536 # Return guessed indentwidth. This should not be believed unless
1537 # it's in a reasonable range (e.g., it will be 0 if no indented
1538 # blocks are found).
1539
1540 def guess_indent(self):
1541 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1542 if opener and indented:
1543 raw, indentsmall = classifyws(opener, self.tabwidth)
1544 raw, indentlarge = classifyws(indented, self.tabwidth)
1545 else:
1546 indentsmall = indentlarge = 0
1547 return indentlarge - indentsmall
1548
1549# "line.col" -> line, as an int
1550def index2line(index):
1551 return int(float(index))
1552
1553# Look at the leading whitespace in s.
1554# Return pair (# of leading ws characters,
1555# effective # of leading blanks after expanding
1556# tabs to width tabwidth)
1557
1558def classifyws(s, tabwidth):
1559 raw = effective = 0
1560 for ch in s:
1561 if ch == ' ':
1562 raw = raw + 1
1563 effective = effective + 1
1564 elif ch == '\t':
1565 raw = raw + 1
1566 effective = (effective // tabwidth + 1) * tabwidth
1567 else:
1568 break
1569 return raw, effective
1570
1571import tokenize
1572_tokenize = tokenize
1573del tokenize
1574
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001575class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001576
1577 # .run() chews over the Text widget, looking for a block opener
1578 # and the stmt following it. Returns a pair,
1579 # (line containing block opener, line containing stmt)
1580 # Either or both may be None.
1581
1582 def __init__(self, text, tabwidth):
1583 self.text = text
1584 self.tabwidth = tabwidth
1585 self.i = self.finished = 0
1586 self.blkopenline = self.indentedline = None
1587
1588 def readline(self):
1589 if self.finished:
1590 return ""
1591 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001592 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001593 if self.text.compare(mark, ">=", "end"):
1594 return ""
1595 return self.text.get(mark, mark + " lineend+1c")
1596
1597 def tokeneater(self, type, token, start, end, line,
1598 INDENT=_tokenize.INDENT,
1599 NAME=_tokenize.NAME,
1600 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1601 if self.finished:
1602 pass
1603 elif type == NAME and token in OPENERS:
1604 self.blkopenline = line
1605 elif type == INDENT and self.blkopenline:
1606 self.indentedline = line
1607 self.finished = 1
1608
1609 def run(self):
1610 save_tabsize = _tokenize.tabsize
1611 _tokenize.tabsize = self.tabwidth
1612 try:
1613 try:
Trent Nelson428de652008-03-18 22:41:35 +00001614 tokens = _tokenize.generate_tokens(self.readline)
1615 for token in tokens:
1616 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001617 except _tokenize.TokenError:
1618 # since we cut off the tokenizer early, we can trigger
1619 # spurious errors
1620 pass
1621 finally:
1622 _tokenize.tabsize = save_tabsize
1623 return self.blkopenline, self.indentedline
1624
1625### end autoindent code ###
1626
David Scherer7aced172000-08-15 01:13:23 +00001627def prepstr(s):
1628 # Helper to extract the underscore from a string, e.g.
1629 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001630 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001631 if i >= 0:
1632 s = s[:i] + s[i+1:]
1633 return i, s
1634
1635
1636keynames = {
1637 'bracketleft': '[',
1638 'bracketright': ']',
1639 'slash': '/',
1640}
1641
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001642def get_accelerator(keydefs, eventname):
1643 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001644 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1645 # if not keylist:
1646 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1647 "<<open-module>>",
1648 "<<goto-line>>",
1649 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001650 return ""
1651 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001652 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001653 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1654 s = re.sub("Key-", "", s)
1655 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1656 s = re.sub("Control-", "Ctrl-", s)
1657 s = re.sub("-", "+", s)
1658 s = re.sub("><", " ", s)
1659 s = re.sub("<", "", s)
1660 s = re.sub(">", "", s)
1661 return s
1662
1663
1664def fixwordbreaks(root):
1665 # Make sure that Tk's double-click and next/previous word
1666 # operations use our definition of a word (i.e. an identifier)
1667 tk = root.tk
1668 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1669 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1670 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1671
1672
1673def test():
1674 root = Tk()
1675 fixwordbreaks(root)
1676 root.withdraw()
1677 if sys.argv[1:]:
1678 filename = sys.argv[1]
1679 else:
1680 filename = None
1681 edit = EditorWindow(root=root, filename=filename)
1682 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001683 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001684 root.mainloop()
1685 root.destroy()
1686
1687if __name__ == '__main__':
1688 test()