blob: c32064dd319be7659d70fff45ed43c48173a206c [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:
Thomas Heller84ef1532003-09-23 20:53:10 +0000123 dochome = os.path.join(sys.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':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +0000134 chmfile = os.path.join(sys.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
140 dochome = os.path.join(sys.prefix,
141 '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):
467 self.text.tag_remove("sel", "1.0", "end")
468 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
469 if not self.rmenu:
470 self.make_rmenu()
471 rmenu = self.rmenu
472 self.event = event
473 iswin = sys.platform[:3] == 'win'
474 if iswin:
475 self.text.config(cursor="arrow")
476 rmenu.tk_popup(event.x_root, event.y_root)
477 if iswin:
478 self.text.config(cursor="ibeam")
479
480 rmenu_specs = [
481 # ("Label", "<<virtual-event>>"), ...
482 ("Close", "<<close-window>>"), # Example
483 ]
484
485 def make_rmenu(self):
486 rmenu = Menu(self.text, tearoff=0)
487 for label, eventname in self.rmenu_specs:
488 def command(text=self.text, eventname=eventname):
489 text.event_generate(eventname)
490 rmenu.add_command(label=label, command=command)
491 self.rmenu = rmenu
492
493 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000494 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000495
Steven M. Gava3b55a892001-11-21 05:56:26 +0000496 def config_dialog(self, event=None):
497 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000498
David Scherer7aced172000-08-15 01:13:23 +0000499 def help_dialog(self, event=None):
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500500 if self.root:
501 parent = self.root
502 else:
503 parent = self.top
504 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000505
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000506 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000507 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000508 try:
509 os.startfile(self.help_url)
510 except WindowsError as why:
511 tkMessageBox.showerror(title='Document Start Failure',
512 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000513 else:
514 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000515 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000516
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000517 def cut(self,event):
518 self.text.event_generate("<<Cut>>")
519 return "break"
520
521 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000522 if not self.text.tag_ranges("sel"):
523 # There is no selection, so do nothing and maybe interrupt.
524 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000525 self.text.event_generate("<<Copy>>")
526 return "break"
527
528 def paste(self,event):
529 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000530 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000531 return "break"
532
David Scherer7aced172000-08-15 01:13:23 +0000533 def select_all(self, event=None):
534 self.text.tag_add("sel", "1.0", "end-1c")
535 self.text.mark_set("insert", "1.0")
536 self.text.see("insert")
537 return "break"
538
539 def remove_selection(self, event=None):
540 self.text.tag_remove("sel", "1.0", "end")
541 self.text.see("insert")
542
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000543 def move_at_edge_if_selection(self, edge_index):
544 """Cursor move begins at start or end of selection
545
546 When a left/right cursor key is pressed create and return to Tkinter a
547 function which causes a cursor move from the associated edge of the
548 selection.
549
550 """
551 self_text_index = self.text.index
552 self_text_mark_set = self.text.mark_set
553 edges_table = ("sel.first+1c", "sel.last-1c")
554 def move_at_edge(event):
555 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
556 try:
557 self_text_index("sel.first")
558 self_text_mark_set("insert", edges_table[edge_index])
559 except TclError:
560 pass
561 return move_at_edge
562
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000563 def del_word_left(self, event):
564 self.text.event_generate('<Meta-Delete>')
565 return "break"
566
567 def del_word_right(self, event):
568 self.text.event_generate('<Meta-d>')
569 return "break"
570
Steven M. Gavac5976402002-01-04 03:06:08 +0000571 def find_event(self, event):
572 SearchDialog.find(self.text)
573 return "break"
574
575 def find_again_event(self, event):
576 SearchDialog.find_again(self.text)
577 return "break"
578
579 def find_selection_event(self, event):
580 SearchDialog.find_selection(self.text)
581 return "break"
582
583 def find_in_files_event(self, event):
584 GrepDialog.grep(self.text, self.io, self.flist)
585 return "break"
586
587 def replace_event(self, event):
588 ReplaceDialog.replace(self.text)
589 return "break"
590
591 def goto_line_event(self, event):
592 text = self.text
593 lineno = tkSimpleDialog.askinteger("Goto",
594 "Go to line number:",parent=text)
595 if lineno is None:
596 return "break"
597 if lineno <= 0:
598 text.bell()
599 return "break"
600 text.mark_set("insert", "%d.0" % lineno)
601 text.see("insert")
602
David Scherer7aced172000-08-15 01:13:23 +0000603 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000604 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000605 try:
606 name = self.text.get("sel.first", "sel.last")
607 except TclError:
608 name = ""
609 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000610 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000611 name = tkSimpleDialog.askstring("Module",
612 "Enter the name of a Python module\n"
613 "to search on sys.path and open:",
614 parent=self.text, initialvalue=name)
615 if name:
616 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000617 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000618 return
David Scherer7aced172000-08-15 01:13:23 +0000619 # XXX Ought to insert current file's directory in front of path
620 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000621 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000622 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000623 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
624 return
625 if type != imp.PY_SOURCE:
626 tkMessageBox.showerror("Unsupported type",
627 "%s is not a source module" % name, parent=self.text)
628 return
629 if f:
630 f.close()
631 if self.flist:
632 self.flist.open(file)
633 else:
634 self.io.loadfile(file)
635
636 def open_class_browser(self, event=None):
637 filename = self.io.filename
638 if not filename:
639 tkMessageBox.showerror(
640 "No filename",
641 "This buffer has no associated filename",
642 master=self.text)
643 self.text.focus_set()
644 return None
645 head, tail = os.path.split(filename)
646 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000647 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000648 ClassBrowser.ClassBrowser(self.flist, base, [head])
649
650 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000651 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000652 PathBrowser.PathBrowser(self.flist)
653
654 def gotoline(self, lineno):
655 if lineno is not None and lineno > 0:
656 self.text.mark_set("insert", "%d.0" % lineno)
657 self.text.tag_remove("sel", "1.0", "end")
658 self.text.tag_add("sel", "insert", "insert +1l")
659 self.center()
660
661 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000662 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000663 return True
David Scherer7aced172000-08-15 01:13:23 +0000664 base, ext = os.path.splitext(os.path.basename(filename))
665 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000666 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000667 line = self.text.get('1.0', '1.0 lineend')
668 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000669
670 def close_hook(self):
671 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000672 self.flist.unregister_maybe_terminate(self)
673 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000674
675 def set_close_hook(self, close_hook):
676 self.close_hook = close_hook
677
678 def filename_change_hook(self):
679 if self.flist:
680 self.flist.filename_changed_edit(self)
681 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000682 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000683 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000684
Christian Heimesa156e092008-02-16 07:38:31 +0000685 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000686 if self.color:
687 return
Christian Heimesa156e092008-02-16 07:38:31 +0000688 if self.ispythonsource(self.io.filename):
689 self.color = self.ColorDelegator()
690 # can add more colorizers here...
691 if self.color:
692 self.per.removefilter(self.undo)
693 self.per.insertfilter(self.color)
694 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000695
Christian Heimesa156e092008-02-16 07:38:31 +0000696 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000697 if not self.color:
698 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000699 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000700 self.per.removefilter(self.color)
701 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000702
Steven M. Gavab77d3432002-03-02 07:16:21 +0000703 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000704 "Update the colour theme"
705 # Called from self.filename_change_hook and from configDialog.py
706 self._rmcolorizer()
707 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000708 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000709 normal_colors = idleConf.GetHighlight(theme, 'normal')
710 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
711 select_colors = idleConf.GetHighlight(theme, 'hilite')
712 self.text.config(
713 foreground=normal_colors['foreground'],
714 background=normal_colors['background'],
715 insertbackground=cursor_color,
716 selectforeground=select_colors['foreground'],
717 selectbackground=select_colors['background'],
718 )
David Scherer7aced172000-08-15 01:13:23 +0000719
Guido van Rossum33d26892007-08-05 15:29:28 +0000720 IDENTCHARS = string.ascii_letters + string.digits + "_"
721
722 def colorize_syntax_error(self, text, pos):
723 text.tag_add("ERROR", pos)
724 char = text.get(pos)
725 if char and char in self.IDENTCHARS:
726 text.tag_add("ERROR", pos + " wordstart", pos)
727 if '\n' == text.get(pos): # error at line end
728 text.mark_set("insert", pos)
729 else:
730 text.mark_set("insert", pos + "+1c")
731 text.see(pos)
732
Steven M. Gavab1585412002-03-12 00:21:56 +0000733 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000734 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000735 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000736 fontWeight='normal'
737 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
738 fontWeight='bold'
739 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
740 idleConf.GetOption('main','EditorWindow','font-size'),
741 fontWeight))
742
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000743 def RemoveKeybindings(self):
744 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000745 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000746 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000747 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000748 self.text.event_delete(event, *keylist)
749 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000750 xkeydefs = idleConf.GetExtensionBindings(extensionName)
751 if xkeydefs:
752 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000753 self.text.event_delete(event, *keylist)
754
755 def ApplyKeybindings(self):
756 "Update the keybindings after they are changed"
757 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000758 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000759 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000760 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000761 xkeydefs = idleConf.GetExtensionBindings(extensionName)
762 if xkeydefs:
763 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000764 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000765 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000766 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000767 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000768 for item in menu[1]:
769 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000770 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000771 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000772 menu = self.menudict[menubarItem]
773 end = menu.index(END) + 1
774 for index in range(0, end):
775 if menu.type(index) == 'command':
776 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000777 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000778 itemName = menu.entrycget(index, 'label')
779 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000780 if menubarItem in menuEventDict:
781 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000782 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000783 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000784 accel = get_accelerator(keydefs, event)
785 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000786
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000787 def set_notabs_indentwidth(self):
788 "Update the indentwidth if changed and not using tabs in this window"
789 # Called from configDialog.py
790 if not self.usetabs:
791 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
792 type='int')
793
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000794 def reset_help_menu_entries(self):
795 "Update the additional help entries on the Help menu"
796 help_list = idleConf.GetAllExtraHelpSourcesList()
797 helpmenu = self.menudict['help']
798 # first delete the extra help entries, if any
799 helpmenu_length = helpmenu.index(END)
800 if helpmenu_length > self.base_helpmenu_length:
801 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
802 # then rebuild them
803 if help_list:
804 helpmenu.add_separator()
805 for entry in help_list:
806 cmd = self.__extra_help_callback(entry[1])
807 helpmenu.add_command(label=entry[0], command=cmd)
808 # and update the menu dictionary
809 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000810
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000811 def __extra_help_callback(self, helpfile):
812 "Create a callback with the helpfile value frozen at definition time"
813 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000814 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000815 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000816 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000817 try:
818 os.startfile(helpfile)
819 except WindowsError as why:
820 tkMessageBox.showerror(title='Document Start Failure',
821 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000822 else:
823 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000824 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000825
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000826 def update_recent_files_list(self, new_file=None):
827 "Load and update the recent files list and menus"
828 rf_list = []
829 if os.path.exists(self.recent_files_path):
Ned Deily122539e2011-01-24 21:46:44 +0000830 rf_list_file = open(self.recent_files_path,'r',
831 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000832 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000833 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000834 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000835 rf_list_file.close()
836 if new_file:
837 new_file = os.path.abspath(new_file) + '\n'
838 if new_file in rf_list:
839 rf_list.remove(new_file) # move to top
840 rf_list.insert(0, new_file)
841 # clean and save the recent files list
842 bad_paths = []
843 for path in rf_list:
844 if '\0' in path or not os.path.exists(path[0:-1]):
845 bad_paths.append(path)
846 rf_list = [path for path in rf_list if path not in bad_paths]
847 ulchars = "1234567890ABCDEFGHIJK"
848 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000849 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800850 with open(self.recent_files_path, 'w',
851 encoding='utf_8', errors='replace') as rf_file:
852 rf_file.writelines(rf_list)
853 except IOError as err:
854 if not getattr(self.root, "recentfilelist_error_displayed", False):
855 self.root.recentfilelist_error_displayed = True
856 tkMessageBox.showerror(title='IDLE Error',
857 message='Unable to update Recent Files list:\n%s'
858 % str(err),
859 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000860 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000861 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000862 menu = instance.recent_files_menu
863 menu.delete(1, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000864 for i, file_name in enumerate(rf_list):
865 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000866 # make unicode string to display non-ASCII chars correctly
867 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000868 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000869 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000870 command=callback,
871 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000872
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000873 def __recent_file_callback(self, file_name):
874 def open_recent_file(fn_closure=file_name):
875 self.io.open(editFile=fn_closure)
876 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000877
David Scherer7aced172000-08-15 01:13:23 +0000878 def saved_change_hook(self):
879 short = self.short_title()
880 long = self.long_title()
881 if short and long:
882 title = short + " - " + long
883 elif short:
884 title = short
885 elif long:
886 title = long
887 else:
888 title = "Untitled"
889 icon = short or long or title
890 if not self.get_saved():
891 title = "*%s*" % title
892 icon = "*%s" % icon
893 self.top.wm_title(title)
894 self.top.wm_iconname(icon)
895
896 def get_saved(self):
897 return self.undo.get_saved()
898
899 def set_saved(self, flag):
900 self.undo.set_saved(flag)
901
902 def reset_undo(self):
903 self.undo.reset_undo()
904
905 def short_title(self):
906 filename = self.io.filename
907 if filename:
908 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000909 # return unicode string to display non-ASCII chars correctly
910 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000911
912 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000913 # return unicode string to display non-ASCII chars correctly
914 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000915
916 def center_insert_event(self, event):
917 self.center()
918
919 def center(self, mark="insert"):
920 text = self.text
921 top, bot = self.getwindowlines()
922 lineno = self.getlineno(mark)
923 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000924 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000925 text.yview(float(newtop))
926
927 def getwindowlines(self):
928 text = self.text
929 top = self.getlineno("@0,0")
930 bot = self.getlineno("@0,65535")
931 if top == bot and text.winfo_height() == 1:
932 # Geometry manager hasn't run yet
933 height = int(text['height'])
934 bot = top + height - 1
935 return top, bot
936
937 def getlineno(self, mark="insert"):
938 text = self.text
939 return int(float(text.index(mark)))
940
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000941 def get_geometry(self):
942 "Return (width, height, x, y)"
943 geom = self.top.wm_geometry()
944 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000945 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000946
David Scherer7aced172000-08-15 01:13:23 +0000947 def close_event(self, event):
948 self.close()
949
950 def maybesave(self):
951 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000952 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000953 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000954 self.top.deiconify()
955 self.top.lower()
956 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000957 return self.io.maybesave()
958
959 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000960 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000961 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000962 self._close()
963 return reply
964
965 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000966 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000967 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000968 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000969 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000970 self.io.close()
971 self.io = None
972 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000973 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000974 self.color.close(False)
975 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000976 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000977 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000978 self.per.close()
979 self.per = None
980 self.top.destroy()
981 if self.close_hook:
982 # unless override: unregister from flist, terminate if last window
983 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000984
985 def load_extensions(self):
986 self.extensions = {}
987 self.load_standard_extensions()
988
989 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000990 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000991 if hasattr(ins, "close"):
992 ins.close()
993 self.extensions = {}
994
995 def load_standard_extensions(self):
996 for name in self.get_standard_extension_names():
997 try:
998 self.load_extension(name)
999 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001000 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001001 traceback.print_exc()
1002
1003 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001004 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001005
1006 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001007 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001008 try:
1009 mod = importlib.import_module('.' + name, package=__package__)
1010 except ImportError:
1011 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001012 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001013 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001014 raise
David Scherer7aced172000-08-15 01:13:23 +00001015 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001016 keydefs = idleConf.GetExtensionBindings(name)
1017 if hasattr(cls, "menudefs"):
1018 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001019 ins = cls(self)
1020 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001021 if keydefs:
1022 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001023 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001024 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001025 while methodname[:1] == '<':
1026 methodname = methodname[1:]
1027 while methodname[-1:] == '>':
1028 methodname = methodname[:-1]
1029 methodname = methodname + "_event"
1030 if hasattr(ins, methodname):
1031 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001032
1033 def apply_bindings(self, keydefs=None):
1034 if keydefs is None:
1035 keydefs = self.Bindings.default_keydefs
1036 text = self.text
1037 text.keydefs = keydefs
1038 for event, keylist in keydefs.items():
1039 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001040 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001041
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001042 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001043 """Add appropriate entries to the menus and submenus
1044
1045 Menus that are absent or None in self.menudict are ignored.
1046 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001047 if menudefs is None:
1048 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001049 if keydefs is None:
1050 keydefs = self.Bindings.default_keydefs
1051 menudict = self.menudict
1052 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001053 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001054 menu = menudict.get(mname)
1055 if not menu:
1056 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001057 for entry in entrylist:
1058 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001059 menu.add_separator()
1060 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001061 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001062 checkbutton = (label[:1] == '!')
1063 if checkbutton:
1064 label = label[1:]
1065 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001066 accelerator = get_accelerator(keydefs, eventname)
1067 def command(text=text, eventname=eventname):
1068 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001069 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001070 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001071 menu.add_checkbutton(label=label, underline=underline,
1072 command=command, accelerator=accelerator,
1073 variable=var)
1074 else:
1075 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001076 command=command,
1077 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001078
1079 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001080 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001081 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001082 value = var.get()
1083 return value
1084 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001085 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001086
1087 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001088 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001089 if var:
1090 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001091 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001092 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001093
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001094 def get_var_obj(self, name, vartype=None):
1095 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001096 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001097 # create a Tkinter variable object with self.text as master:
1098 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001099 return var
1100
1101 # Tk implementations of "virtual text methods" -- each platform
1102 # reusing IDLE's support code needs to define these for its GUI's
1103 # flavor of widget.
1104
1105 # Is character at text_index in a Python string? Return 0 for
1106 # "guaranteed no", true for anything else. This info is expensive
1107 # to compute ab initio, but is probably already known by the
1108 # platform's colorizer.
1109
1110 def is_char_in_string(self, text_index):
1111 if self.color:
1112 # Return true iff colorizer hasn't (re)gotten this far
1113 # yet, or the character is tagged as being in a string
1114 return self.text.tag_prevrange("TODO", text_index) or \
1115 "STRING" in self.text.tag_names(text_index)
1116 else:
1117 # The colorizer is missing: assume the worst
1118 return 1
1119
1120 # If a selection is defined in the text widget, return (start,
1121 # end) as Tkinter text indices, otherwise return (None, None)
1122 def get_selection_indices(self):
1123 try:
1124 first = self.text.index("sel.first")
1125 last = self.text.index("sel.last")
1126 return first, last
1127 except TclError:
1128 return None, None
1129
1130 # Return the text widget's current view of what a tab stop means
1131 # (equivalent width in spaces).
1132
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001133 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001134 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1135 return int(current)
1136
1137 # Set the text widget's current view of what a tab stop means.
1138
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001139 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001140 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001141 if self.get_tk_tabwidth() != newtabwidth:
1142 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001143 pixels = text.tk.call("font", "measure", text["font"],
1144 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001145 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001146 text.configure(tabs=pixels)
1147
Guido van Rossum33d26892007-08-05 15:29:28 +00001148### begin autoindent code ### (configuration was moved to beginning of class)
1149
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001150 def set_indentation_params(self, is_py_src, guess=True):
1151 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001152 i = self.guess_indent()
1153 if 2 <= i <= 8:
1154 self.indentwidth = i
1155 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001156 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001157 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001158
1159 def smart_backspace_event(self, event):
1160 text = self.text
1161 first, last = self.get_selection_indices()
1162 if first and last:
1163 text.delete(first, last)
1164 text.mark_set("insert", first)
1165 return "break"
1166 # Delete whitespace left, until hitting a real char or closest
1167 # preceding virtual tab stop.
1168 chars = text.get("insert linestart", "insert")
1169 if chars == '':
1170 if text.compare("insert", ">", "1.0"):
1171 # easy: delete preceding newline
1172 text.delete("insert-1c")
1173 else:
1174 text.bell() # at start of buffer
1175 return "break"
1176 if chars[-1] not in " \t":
1177 # easy: delete preceding real char
1178 text.delete("insert-1c")
1179 return "break"
1180 # Ick. It may require *inserting* spaces if we back up over a
1181 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001182 tabwidth = self.tabwidth
1183 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001184 assert have > 0
1185 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001186 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001187 if self.context_use_ps1:
1188 last_line_of_prompt = sys.ps1.split('\n')[-1]
1189 else:
1190 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001191 ncharsdeleted = 0
1192 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001193 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001194 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001195 chars = chars[:-1]
1196 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001197 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001198 if have <= want or chars[-1] not in " \t":
1199 break
1200 text.undo_block_start()
1201 text.delete("insert-%dc" % ncharsdeleted, "insert")
1202 if have < want:
1203 text.insert("insert", ' ' * (want - have))
1204 text.undo_block_stop()
1205 return "break"
1206
1207 def smart_indent_event(self, event):
1208 # if intraline selection:
1209 # delete it
1210 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001211 # do indent-region
1212 # else:
1213 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001214 text = self.text
1215 first, last = self.get_selection_indices()
1216 text.undo_block_start()
1217 try:
1218 if first and last:
1219 if index2line(first) != index2line(last):
1220 return self.indent_region_event(event)
1221 text.delete(first, last)
1222 text.mark_set("insert", first)
1223 prefix = text.get("insert linestart", "insert")
1224 raw, effective = classifyws(prefix, self.tabwidth)
1225 if raw == len(prefix):
1226 # only whitespace to the left
1227 self.reindent_to(effective + self.indentwidth)
1228 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001229 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001230 if self.usetabs:
1231 pad = '\t'
1232 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001233 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001234 n = self.indentwidth
1235 pad = ' ' * (n - effective % n)
1236 text.insert("insert", pad)
1237 text.see("insert")
1238 return "break"
1239 finally:
1240 text.undo_block_stop()
1241
1242 def newline_and_indent_event(self, event):
1243 text = self.text
1244 first, last = self.get_selection_indices()
1245 text.undo_block_start()
1246 try:
1247 if first and last:
1248 text.delete(first, last)
1249 text.mark_set("insert", first)
1250 line = text.get("insert linestart", "insert")
1251 i, n = 0, len(line)
1252 while i < n and line[i] in " \t":
1253 i = i+1
1254 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001255 # the cursor is in or at leading indentation in a continuation
1256 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001257 text.insert("insert linestart", '\n')
1258 return "break"
1259 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001260 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001261 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001262 last_line_of_prompt = sys.ps1.split('\n')[-1]
1263 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001264 line = line[:-1]
1265 i = i+1
1266 if i:
1267 text.delete("insert - %d chars" % i, "insert")
1268 # strip whitespace after insert point
1269 while text.get("insert") in " \t":
1270 text.delete("insert")
1271 # start new line
1272 text.insert("insert", '\n')
1273
1274 # adjust indentation for continuations and block
1275 # open/close first need to find the last stmt
1276 lno = index2line(text.index('insert'))
1277 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001278 if not self.context_use_ps1:
1279 for context in self.num_context_lines:
1280 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001281 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001282 rawtext = text.get(startatindex, "insert")
1283 y.set_str(rawtext)
1284 bod = y.find_good_parse_start(
1285 self.context_use_ps1,
1286 self._build_char_in_string_func(startatindex))
1287 if bod is not None or startat == 1:
1288 break
1289 y.set_lo(bod or 0)
1290 else:
1291 r = text.tag_prevrange("console", "insert")
1292 if r:
1293 startatindex = r[1]
1294 else:
1295 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001296 rawtext = text.get(startatindex, "insert")
1297 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001298 y.set_lo(0)
1299
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001300 c = y.get_continuation_type()
1301 if c != PyParse.C_NONE:
1302 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001303 if c == PyParse.C_STRING_FIRST_LINE:
1304 # after the first line of a string; do not indent at all
1305 pass
1306 elif c == PyParse.C_STRING_NEXT_LINES:
1307 # inside a string which started before this line;
1308 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001309 text.insert("insert", indent)
1310 elif c == PyParse.C_BRACKET:
1311 # line up with the first (if any) element of the
1312 # last open bracket structure; else indent one
1313 # level beyond the indent of the line with the
1314 # last open bracket
1315 self.reindent_to(y.compute_bracket_indent())
1316 elif c == PyParse.C_BACKSLASH:
1317 # if more than one line in this stmt already, just
1318 # mimic the current indent; else if initial line
1319 # has a start on an assignment stmt, indent to
1320 # beyond leftmost =; else to beyond first chunk of
1321 # non-whitespace on initial line
1322 if y.get_num_lines_in_stmt() > 1:
1323 text.insert("insert", indent)
1324 else:
1325 self.reindent_to(y.compute_backslash_indent())
1326 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001327 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001328 return "break"
1329
1330 # This line starts a brand new stmt; indent relative to
1331 # indentation of initial line of closest preceding
1332 # interesting stmt.
1333 indent = y.get_base_indent_string()
1334 text.insert("insert", indent)
1335 if y.is_block_opener():
1336 self.smart_indent_event(event)
1337 elif indent and y.is_block_closer():
1338 self.smart_backspace_event(event)
1339 return "break"
1340 finally:
1341 text.see("insert")
1342 text.undo_block_stop()
1343
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001344 # Our editwin provides a is_char_in_string function that works
1345 # with a Tk text index, but PyParse only knows about offsets into
1346 # a string. This builds a function for PyParse that accepts an
1347 # offset.
1348
1349 def _build_char_in_string_func(self, startindex):
1350 def inner(offset, _startindex=startindex,
1351 _icis=self.is_char_in_string):
1352 return _icis(_startindex + "+%dc" % offset)
1353 return inner
1354
1355 def indent_region_event(self, event):
1356 head, tail, chars, lines = self.get_region()
1357 for pos in range(len(lines)):
1358 line = lines[pos]
1359 if line:
1360 raw, effective = classifyws(line, self.tabwidth)
1361 effective = effective + self.indentwidth
1362 lines[pos] = self._make_blanks(effective) + line[raw:]
1363 self.set_region(head, tail, chars, lines)
1364 return "break"
1365
1366 def dedent_region_event(self, event):
1367 head, tail, chars, lines = self.get_region()
1368 for pos in range(len(lines)):
1369 line = lines[pos]
1370 if line:
1371 raw, effective = classifyws(line, self.tabwidth)
1372 effective = max(effective - self.indentwidth, 0)
1373 lines[pos] = self._make_blanks(effective) + line[raw:]
1374 self.set_region(head, tail, chars, lines)
1375 return "break"
1376
1377 def comment_region_event(self, event):
1378 head, tail, chars, lines = self.get_region()
1379 for pos in range(len(lines) - 1):
1380 line = lines[pos]
1381 lines[pos] = '##' + line
1382 self.set_region(head, tail, chars, lines)
1383
1384 def uncomment_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 not line:
1389 continue
1390 if line[:2] == '##':
1391 line = line[2:]
1392 elif line[:1] == '#':
1393 line = line[1:]
1394 lines[pos] = line
1395 self.set_region(head, tail, chars, lines)
1396
1397 def tabify_region_event(self, event):
1398 head, tail, chars, lines = self.get_region()
1399 tabwidth = self._asktabwidth()
1400 for pos in range(len(lines)):
1401 line = lines[pos]
1402 if line:
1403 raw, effective = classifyws(line, tabwidth)
1404 ntabs, nspaces = divmod(effective, tabwidth)
1405 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1406 self.set_region(head, tail, chars, lines)
1407
1408 def untabify_region_event(self, event):
1409 head, tail, chars, lines = self.get_region()
1410 tabwidth = self._asktabwidth()
1411 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001412 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001413 self.set_region(head, tail, chars, lines)
1414
1415 def toggle_tabs_event(self, event):
1416 if self.askyesno(
1417 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001418 "Turn tabs " + ("on", "off")[self.usetabs] +
1419 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001420 ("will be", "remains at")[self.usetabs] + " 8." +
1421 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001422 parent=self.text):
1423 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001424 # Try to prevent inconsistent indentation.
1425 # User must change indent width manually after using tabs.
1426 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001427 return "break"
1428
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001429 # XXX this isn't bound to anything -- see tabwidth comments
1430## def change_tabwidth_event(self, event):
1431## new = self._asktabwidth()
1432## if new != self.tabwidth:
1433## self.tabwidth = new
1434## self.set_indentation_params(0, guess=0)
1435## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001436
1437 def change_indentwidth_event(self, event):
1438 new = self.askinteger(
1439 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001440 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001441 parent=self.text,
1442 initialvalue=self.indentwidth,
1443 minvalue=2,
1444 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001445 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001446 self.indentwidth = new
1447 return "break"
1448
1449 def get_region(self):
1450 text = self.text
1451 first, last = self.get_selection_indices()
1452 if first and last:
1453 head = text.index(first + " linestart")
1454 tail = text.index(last + "-1c lineend +1c")
1455 else:
1456 head = text.index("insert linestart")
1457 tail = text.index("insert lineend +1c")
1458 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001459 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001460 return head, tail, chars, lines
1461
1462 def set_region(self, head, tail, chars, lines):
1463 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001464 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001465 if newchars == chars:
1466 text.bell()
1467 return
1468 text.tag_remove("sel", "1.0", "end")
1469 text.mark_set("insert", head)
1470 text.undo_block_start()
1471 text.delete(head, tail)
1472 text.insert(head, newchars)
1473 text.undo_block_stop()
1474 text.tag_add("sel", head, "insert")
1475
1476 # Make string that displays as n leading blanks.
1477
1478 def _make_blanks(self, n):
1479 if self.usetabs:
1480 ntabs, nspaces = divmod(n, self.tabwidth)
1481 return '\t' * ntabs + ' ' * nspaces
1482 else:
1483 return ' ' * n
1484
1485 # Delete from beginning of line to insert point, then reinsert
1486 # column logical (meaning use tabs if appropriate) spaces.
1487
1488 def reindent_to(self, column):
1489 text = self.text
1490 text.undo_block_start()
1491 if text.compare("insert linestart", "!=", "insert"):
1492 text.delete("insert linestart", "insert")
1493 if column:
1494 text.insert("insert", self._make_blanks(column))
1495 text.undo_block_stop()
1496
1497 def _asktabwidth(self):
1498 return self.askinteger(
1499 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001500 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001501 parent=self.text,
1502 initialvalue=self.indentwidth,
1503 minvalue=2,
1504 maxvalue=16) or self.tabwidth
1505
1506 # Guess indentwidth from text content.
1507 # Return guessed indentwidth. This should not be believed unless
1508 # it's in a reasonable range (e.g., it will be 0 if no indented
1509 # blocks are found).
1510
1511 def guess_indent(self):
1512 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1513 if opener and indented:
1514 raw, indentsmall = classifyws(opener, self.tabwidth)
1515 raw, indentlarge = classifyws(indented, self.tabwidth)
1516 else:
1517 indentsmall = indentlarge = 0
1518 return indentlarge - indentsmall
1519
1520# "line.col" -> line, as an int
1521def index2line(index):
1522 return int(float(index))
1523
1524# Look at the leading whitespace in s.
1525# Return pair (# of leading ws characters,
1526# effective # of leading blanks after expanding
1527# tabs to width tabwidth)
1528
1529def classifyws(s, tabwidth):
1530 raw = effective = 0
1531 for ch in s:
1532 if ch == ' ':
1533 raw = raw + 1
1534 effective = effective + 1
1535 elif ch == '\t':
1536 raw = raw + 1
1537 effective = (effective // tabwidth + 1) * tabwidth
1538 else:
1539 break
1540 return raw, effective
1541
1542import tokenize
1543_tokenize = tokenize
1544del tokenize
1545
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001546class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001547
1548 # .run() chews over the Text widget, looking for a block opener
1549 # and the stmt following it. Returns a pair,
1550 # (line containing block opener, line containing stmt)
1551 # Either or both may be None.
1552
1553 def __init__(self, text, tabwidth):
1554 self.text = text
1555 self.tabwidth = tabwidth
1556 self.i = self.finished = 0
1557 self.blkopenline = self.indentedline = None
1558
1559 def readline(self):
1560 if self.finished:
1561 return ""
1562 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001563 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001564 if self.text.compare(mark, ">=", "end"):
1565 return ""
1566 return self.text.get(mark, mark + " lineend+1c")
1567
1568 def tokeneater(self, type, token, start, end, line,
1569 INDENT=_tokenize.INDENT,
1570 NAME=_tokenize.NAME,
1571 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1572 if self.finished:
1573 pass
1574 elif type == NAME and token in OPENERS:
1575 self.blkopenline = line
1576 elif type == INDENT and self.blkopenline:
1577 self.indentedline = line
1578 self.finished = 1
1579
1580 def run(self):
1581 save_tabsize = _tokenize.tabsize
1582 _tokenize.tabsize = self.tabwidth
1583 try:
1584 try:
Trent Nelson428de652008-03-18 22:41:35 +00001585 tokens = _tokenize.generate_tokens(self.readline)
1586 for token in tokens:
1587 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001588 except _tokenize.TokenError:
1589 # since we cut off the tokenizer early, we can trigger
1590 # spurious errors
1591 pass
1592 finally:
1593 _tokenize.tabsize = save_tabsize
1594 return self.blkopenline, self.indentedline
1595
1596### end autoindent code ###
1597
David Scherer7aced172000-08-15 01:13:23 +00001598def prepstr(s):
1599 # Helper to extract the underscore from a string, e.g.
1600 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001601 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001602 if i >= 0:
1603 s = s[:i] + s[i+1:]
1604 return i, s
1605
1606
1607keynames = {
1608 'bracketleft': '[',
1609 'bracketright': ']',
1610 'slash': '/',
1611}
1612
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001613def get_accelerator(keydefs, eventname):
1614 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001615 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1616 # if not keylist:
1617 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1618 "<<open-module>>",
1619 "<<goto-line>>",
1620 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001621 return ""
1622 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001623 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001624 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1625 s = re.sub("Key-", "", s)
1626 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1627 s = re.sub("Control-", "Ctrl-", s)
1628 s = re.sub("-", "+", s)
1629 s = re.sub("><", " ", s)
1630 s = re.sub("<", "", s)
1631 s = re.sub(">", "", s)
1632 return s
1633
1634
1635def fixwordbreaks(root):
1636 # Make sure that Tk's double-click and next/previous word
1637 # operations use our definition of a word (i.e. an identifier)
1638 tk = root.tk
1639 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1640 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1641 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1642
1643
1644def test():
1645 root = Tk()
1646 fixwordbreaks(root)
1647 root.withdraw()
1648 if sys.argv[1:]:
1649 filename = sys.argv[1]
1650 else:
1651 filename = None
1652 edit = EditorWindow(root=root, filename=filename)
1653 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001654 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001655 root.mainloop()
1656 root.destroy()
1657
1658if __name__ == '__main__':
1659 test()