blob: de74e58c0fb6a845499c9dfa4fb0e4d2c69fa7af [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)
31 if micro:
Benjamin Petersonb48f6342009-06-22 19:36:31 +000032 release += '%s' % (micro,)
33 if level == 'candidate':
34 release += 'rc%s' % (serial,)
35 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000036 release += '%s%s' % (level[0], serial)
37 return release
38
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000039def _find_module(fullname, path=None):
40 """Version of imp.find_module() that handles hierarchical module names"""
41
42 file = None
43 for tgt in fullname.split('.'):
44 if file is not None:
45 file.close() # close intermediate files
46 (file, filename, descr) = imp.find_module(tgt, path)
47 if descr[2] == imp.PY_SOURCE:
48 break # find but not load the source file
49 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000050 try:
51 path = module.__path__
52 except AttributeError:
Kurt B. Kaiserad667422007-08-23 01:06:15 +000053 raise ImportError('No source for module ' + module.__name__)
Raymond Hettingerf6445e82011-04-12 18:30:14 -070054 if descr[2] != imp.PY_SOURCE:
55 # If all of the above fails and didn't raise an exception,fallback
56 # to a straight import which can find __init__.py in a package.
57 m = __import__(fullname)
58 try:
59 filename = m.__file__
60 except AttributeError:
61 pass
62 else:
63 file = None
Raymond Hettinger2df393c2011-04-12 18:57:55 -070064 descr = os.path.splitext(filename)[1], None, imp.PY_SOURCE
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000065 return file, filename, descr
66
Terry Jan Reedye91e7632012-02-05 15:14:20 -050067
68class HelpDialog(object):
69
70 def __init__(self):
71 self.parent = None # parent of help window
72 self.dlg = None # the help window iteself
73
74 def display(self, parent, near=None):
75 """ Display the help dialog.
76
77 parent - parent widget for the help window
78
79 near - a Toplevel widget (e.g. EditorWindow or PyShell)
80 to use as a reference for placing the help window
81 """
82 if self.dlg is None:
83 self.show_dialog(parent)
84 if near:
85 self.nearwindow(near)
86
87 def show_dialog(self, parent):
88 self.parent = parent
89 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
90 self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
91 dlg.bind('<Destroy>', self.destroy, '+')
92
93 def nearwindow(self, near):
94 # Place the help dialog near the window specified by parent.
95 # Note - this may not reposition the window in Metacity
96 # if "/apps/metacity/general/disable_workarounds" is enabled
97 dlg = self.dlg
98 geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
99 dlg.withdraw()
100 dlg.geometry("=+%d+%d" % geom)
101 dlg.deiconify()
102 dlg.lift()
103
104 def destroy(self, ev=None):
105 self.dlg = None
106 self.parent = None
107
108helpDialog = HelpDialog() # singleton instance
109
110
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +0000111class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000112 from idlelib.Percolator import Percolator
113 from idlelib.ColorDelegator import ColorDelegator
114 from idlelib.UndoDelegator import UndoDelegator
115 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
116 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +0000117 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000118 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +0000119
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000120 help_url = None
David Scherer7aced172000-08-15 01:13:23 +0000121
122 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000123 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +0000124 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000125 if sys.platform.count('linux'):
126 # look for html docs in a couple of standard places
127 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
128 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
129 dochome = '/var/www/html/python/index.html'
130 else:
131 basepath = '/usr/share/doc/' # standard location
132 dochome = os.path.join(basepath, pyver,
133 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000134 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +0000135 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +0000136 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +0000137 if os.path.isfile(chmfile):
138 dochome = chmfile
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000139 elif macosxSupport.runningAsOSXApp():
140 # documentation is stored inside the python framework
141 dochome = os.path.join(sys.prefix,
142 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000143 dochome = os.path.normpath(dochome)
144 if os.path.isfile(dochome):
145 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000146 if sys.platform == 'darwin':
147 # Safari requires real file:-URLs
148 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000149 else:
Benjamin Peterson152b6572009-01-20 15:01:54 +0000150 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +0000151 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +0000152 self.flist = flist
153 root = root or flist.root
154 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000155 try:
156 sys.ps1
157 except AttributeError:
158 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000159 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000160 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000161 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000162 self.tkinter_vars = flist.vars
Ezio Melotti42da6632011-03-15 05:18:48 +0200163 #self.top.instance_dict makes flist.inversedict available to
164 #configDialog.py so it can access all EditorWindow instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000165 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000166 else:
167 self.tkinter_vars = {} # keys: Tkinter event names
168 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000169 self.top.instance_dict = {}
170 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000171 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000172 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000173 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000174 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000175 text_options = {
176 'name': 'text',
177 'padx': 5,
178 'wrap': 'none',
179 'width': self.width,
180 'height': idleConf.GetOption('main', 'EditorWindow', 'height')}
181 if TkVersion >= 8.5:
182 # Starting with tk 8.5 we have to set the new tabstyle option
183 # to 'wordprocessor' to achieve the same display of tabs as in
184 # older tk versions.
185 text_options['tabstyle'] = 'wordprocessor'
186 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000187 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000188
189 self.createmenubar()
190 self.apply_bindings()
191
192 self.top.protocol("WM_DELETE_WINDOW", self.close)
193 self.top.bind("<<close-window>>", self.close_event)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000194 if macosxSupport.runningAsOSXApp():
195 # Command-W on editorwindows doesn't work without this.
196 text.bind('<<close-window>>', self.close_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000197 # Some OS X systems have only one mouse button,
198 # so use control-click for pulldown menus there.
199 # (Note, AquaTk defines <2> as the right button if
200 # present and the Tk Text widget already binds <2>.)
201 text.bind("<Control-Button-1>",self.right_menu_event)
202 else:
203 # Elsewhere, use right-click for pulldown menus.
204 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000205 text.bind("<<cut>>", self.cut)
206 text.bind("<<copy>>", self.copy)
207 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000208 text.bind("<<center-insert>>", self.center_insert_event)
209 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000210 text.bind("<<python-docs>>", self.python_docs)
211 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000212 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000213 text.bind("<<open-module>>", self.open_module)
214 text.bind("<<do-nothing>>", lambda event: "break")
215 text.bind("<<select-all>>", self.select_all)
216 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000217 text.bind("<<find>>", self.find_event)
218 text.bind("<<find-again>>", self.find_again_event)
219 text.bind("<<find-in-files>>", self.find_in_files_event)
220 text.bind("<<find-selection>>", self.find_selection_event)
221 text.bind("<<replace>>", self.replace_event)
222 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000223 text.bind("<<smart-backspace>>",self.smart_backspace_event)
224 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
225 text.bind("<<smart-indent>>",self.smart_indent_event)
226 text.bind("<<indent-region>>",self.indent_region_event)
227 text.bind("<<dedent-region>>",self.dedent_region_event)
228 text.bind("<<comment-region>>",self.comment_region_event)
229 text.bind("<<uncomment-region>>",self.uncomment_region_event)
230 text.bind("<<tabify-region>>",self.tabify_region_event)
231 text.bind("<<untabify-region>>",self.untabify_region_event)
232 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
233 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000234 text.bind("<Left>", self.move_at_edge_if_selection(0))
235 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000236 text.bind("<<del-word-left>>", self.del_word_left)
237 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000238 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000239
David Scherer7aced172000-08-15 01:13:23 +0000240 if flist:
241 flist.inversedict[self] = key
242 if key:
243 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000244 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000245 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
246 text.bind("<<open-class-browser>>", self.open_class_browser)
247 text.bind("<<open-path-browser>>", self.open_path_browser)
248
Steven M. Gava898a3652001-10-07 11:10:44 +0000249 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000250 vbar['command'] = text.yview
251 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000252 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000253 fontWeight = 'normal'
254 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000255 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000256 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
257 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
258 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000259 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
260 text.pack(side=TOP, fill=BOTH, expand=1)
261 text.focus_set()
262
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000263 # usetabs true -> literal tab characters are used by indent and
264 # dedent cmds, possibly mixed with spaces if
265 # indentwidth is not a multiple of tabwidth,
266 # which will cause Tabnanny to nag!
267 # false -> tab characters are converted to spaces by indent
268 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000269 # Although use-spaces=0 can be configured manually in config-main.def,
270 # configuration of tabs v. spaces is not supported in the configuration
271 # dialog. IDLE promotes the preferred Python indentation: use spaces!
272 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
273 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000274
275 # tabwidth is the display width of a literal tab character.
276 # CAUTION: telling Tk to use anything other than its default
277 # tab setting causes it to use an entirely different tabbing algorithm,
278 # treating tab stops as fixed distances from the left margin.
279 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000280 self.tabwidth = 8 # must remain 8 until Tk is fixed.
281
282 # indentwidth is the number of screen characters per indent level.
283 # The recommended Python indentation is four spaces.
284 self.indentwidth = self.tabwidth
285 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000286
287 # If context_use_ps1 is true, parsing searches back for a ps1 line;
288 # else searches for a popular (if, def, ...) Python stmt.
289 self.context_use_ps1 = False
290
291 # When searching backwards for a reliable place to begin parsing,
292 # first start num_context_lines[0] lines back, then
293 # num_context_lines[1] lines back if that didn't work, and so on.
294 # The last value should be huge (larger than the # of lines in a
295 # conceivable file).
296 # Making the initial values larger slows things down more often.
297 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000298 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000299 self.undo = undo = self.UndoDelegator()
300 per.insertfilter(undo)
301 text.undo_block_start = undo.undo_block_start
302 text.undo_block_stop = undo.undo_block_stop
303 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000304 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000305 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000306 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000307 self.good_load = False
308 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000309 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000310 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000311 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000312 if io.loadfile(filename):
313 self.good_load = True
314 is_py_src = self.ispythonsource(filename)
315 self.set_indentation_params(is_py_src)
316 if is_py_src:
317 self.color = color = self.ColorDelegator()
318 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000319 else:
320 io.set_filename(filename)
Christian Heimesa156e092008-02-16 07:38:31 +0000321 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000322 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000323 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000324 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000325 menu = self.menudict.get('windows')
326 if menu:
327 end = menu.index("end")
328 if end is None:
329 end = -1
330 if end >= 0:
331 menu.add_separator()
332 end = end + 1
333 self.wmenu_end = end
334 WindowList.register_callback(self.postwindowsmenu)
335
336 # Some abstractions so IDLE extensions are cross-IDE
337 self.askyesno = tkMessageBox.askyesno
338 self.askinteger = tkSimpleDialog.askinteger
339 self.showerror = tkMessageBox.showerror
340
Martin v. Löwis307021f2005-11-27 16:59:04 +0000341 def _filename_to_unicode(self, filename):
342 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000343 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000344 return filename
345 else:
346 try:
347 return filename.decode(self.filesystemencoding)
348 except UnicodeDecodeError:
349 # XXX
350 try:
351 return filename.decode(self.encoding)
352 except UnicodeDecodeError:
353 # byte-to-byte conversion
354 return filename.decode('iso8859-1')
355
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000356 def new_callback(self, event):
357 dirname, basename = self.io.defaultfilename()
358 self.flist.new(dirname)
359 return "break"
360
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000361 def home_callback(self, event):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400362 if (event.state & 4) != 0 and event.keysym == "Home":
363 # state&4==Control. If <Control-Home>, use the Tk binding.
364 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000365 if self.text.index("iomark") and \
366 self.text.compare("iomark", "<=", "insert lineend") and \
367 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400368 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000369 insertpt = int(self.text.index("iomark").split(".")[1])
370 else:
371 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000372 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000373 if line[insertpt] not in (' ','\t'):
374 break
375 else:
376 insertpt=len(line)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000377 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000378 if insertpt == lineat:
379 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000380 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000381 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400382 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000383 self.text.tag_remove("sel", "1.0", "end")
384 else:
385 if not self.text.index("sel.first"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400386 self.text.mark_set("my_anchor", "insert") # there was no previous selection
387 else:
388 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
389 self.text.mark_set("my_anchor", "sel.first") # extend back
390 else:
391 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000392 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400393 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000394 if self.text.compare(first,">",last):
395 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000396 self.text.tag_remove("sel", "1.0", "end")
397 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000398 self.text.mark_set("insert", dest)
399 self.text.see("insert")
400 return "break"
401
David Scherer7aced172000-08-15 01:13:23 +0000402 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000403 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000404 if macosxSupport.runningAsOSXApp():
405 # Insert some padding to avoid obscuring some of the statusbar
406 # by the resize widget.
407 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000408 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
409 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
410 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000411 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
412 self.text.event_add("<<set-line-and-column>>",
413 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000414 self.text.after_idle(self.set_line_and_column)
415
416 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000417 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000418 self.status_bar.set_label('column', 'Col: %s' % column)
419 self.status_bar.set_label('line', 'Ln: %s' % line)
420
David Scherer7aced172000-08-15 01:13:23 +0000421 menu_specs = [
422 ("file", "_File"),
423 ("edit", "_Edit"),
424 ("format", "F_ormat"),
425 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000426 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000427 ("windows", "_Windows"),
428 ("help", "_Help"),
429 ]
430
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000431 if macosxSupport.runningAsOSXApp():
432 del menu_specs[-3]
433 menu_specs[-2] = ("windows", "_Window")
434
435
David Scherer7aced172000-08-15 01:13:23 +0000436 def createmenubar(self):
437 mbar = self.menubar
438 self.menudict = menudict = {}
439 for name, label in self.menu_specs:
440 underline, label = prepstr(label)
441 menudict[name] = menu = Menu(mbar, name=name)
442 mbar.add_cascade(label=label, menu=menu, underline=underline)
Georg Brandlaedd2892010-12-19 10:10:32 +0000443 if macosxSupport.isCarbonAquaTk(self.root):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000444 # Insert the application menu
445 menudict['application'] = menu = Menu(mbar, name='apple')
446 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000447 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000448 self.recent_files_menu = Menu(self.menubar)
449 self.menudict['file'].insert_cascade(3, label='Recent Files',
450 underline=0,
451 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000452 self.base_helpmenu_length = self.menudict['help'].index(END)
453 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000454
455 def postwindowsmenu(self):
456 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000457 menu = self.menudict['windows']
458 end = menu.index("end")
459 if end is None:
460 end = -1
461 if end > self.wmenu_end:
462 menu.delete(self.wmenu_end+1, end)
463 WindowList.add_windows_to_menu(menu)
464
465 rmenu = None
466
467 def right_menu_event(self, event):
468 self.text.tag_remove("sel", "1.0", "end")
469 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
470 if not self.rmenu:
471 self.make_rmenu()
472 rmenu = self.rmenu
473 self.event = event
474 iswin = sys.platform[:3] == 'win'
475 if iswin:
476 self.text.config(cursor="arrow")
477 rmenu.tk_popup(event.x_root, event.y_root)
478 if iswin:
479 self.text.config(cursor="ibeam")
480
481 rmenu_specs = [
482 # ("Label", "<<virtual-event>>"), ...
483 ("Close", "<<close-window>>"), # Example
484 ]
485
486 def make_rmenu(self):
487 rmenu = Menu(self.text, tearoff=0)
488 for label, eventname in self.rmenu_specs:
489 def command(text=self.text, eventname=eventname):
490 text.event_generate(eventname)
491 rmenu.add_command(label=label, command=command)
492 self.rmenu = rmenu
493
494 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000495 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000496
Steven M. Gava3b55a892001-11-21 05:56:26 +0000497 def config_dialog(self, event=None):
498 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000499
David Scherer7aced172000-08-15 01:13:23 +0000500 def help_dialog(self, event=None):
Terry Jan Reedye91e7632012-02-05 15:14:20 -0500501 if self.root:
502 parent = self.root
503 else:
504 parent = self.top
505 helpDialog.display(parent, near=self.top)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000506
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000507 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000508 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000509 try:
510 os.startfile(self.help_url)
511 except WindowsError as why:
512 tkMessageBox.showerror(title='Document Start Failure',
513 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000514 else:
515 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000516 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000517
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000518 def cut(self,event):
519 self.text.event_generate("<<Cut>>")
520 return "break"
521
522 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000523 if not self.text.tag_ranges("sel"):
524 # There is no selection, so do nothing and maybe interrupt.
525 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000526 self.text.event_generate("<<Copy>>")
527 return "break"
528
529 def paste(self,event):
530 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000531 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000532 return "break"
533
David Scherer7aced172000-08-15 01:13:23 +0000534 def select_all(self, event=None):
535 self.text.tag_add("sel", "1.0", "end-1c")
536 self.text.mark_set("insert", "1.0")
537 self.text.see("insert")
538 return "break"
539
540 def remove_selection(self, event=None):
541 self.text.tag_remove("sel", "1.0", "end")
542 self.text.see("insert")
543
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000544 def move_at_edge_if_selection(self, edge_index):
545 """Cursor move begins at start or end of selection
546
547 When a left/right cursor key is pressed create and return to Tkinter a
548 function which causes a cursor move from the associated edge of the
549 selection.
550
551 """
552 self_text_index = self.text.index
553 self_text_mark_set = self.text.mark_set
554 edges_table = ("sel.first+1c", "sel.last-1c")
555 def move_at_edge(event):
556 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
557 try:
558 self_text_index("sel.first")
559 self_text_mark_set("insert", edges_table[edge_index])
560 except TclError:
561 pass
562 return move_at_edge
563
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000564 def del_word_left(self, event):
565 self.text.event_generate('<Meta-Delete>')
566 return "break"
567
568 def del_word_right(self, event):
569 self.text.event_generate('<Meta-d>')
570 return "break"
571
Steven M. Gavac5976402002-01-04 03:06:08 +0000572 def find_event(self, event):
573 SearchDialog.find(self.text)
574 return "break"
575
576 def find_again_event(self, event):
577 SearchDialog.find_again(self.text)
578 return "break"
579
580 def find_selection_event(self, event):
581 SearchDialog.find_selection(self.text)
582 return "break"
583
584 def find_in_files_event(self, event):
585 GrepDialog.grep(self.text, self.io, self.flist)
586 return "break"
587
588 def replace_event(self, event):
589 ReplaceDialog.replace(self.text)
590 return "break"
591
592 def goto_line_event(self, event):
593 text = self.text
594 lineno = tkSimpleDialog.askinteger("Goto",
595 "Go to line number:",parent=text)
596 if lineno is None:
597 return "break"
598 if lineno <= 0:
599 text.bell()
600 return "break"
601 text.mark_set("insert", "%d.0" % lineno)
602 text.see("insert")
603
David Scherer7aced172000-08-15 01:13:23 +0000604 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000605 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000606 try:
607 name = self.text.get("sel.first", "sel.last")
608 except TclError:
609 name = ""
610 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000611 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000612 name = tkSimpleDialog.askstring("Module",
613 "Enter the name of a Python module\n"
614 "to search on sys.path and open:",
615 parent=self.text, initialvalue=name)
616 if name:
617 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000618 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000619 return
David Scherer7aced172000-08-15 01:13:23 +0000620 # XXX Ought to insert current file's directory in front of path
621 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000622 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000623 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000624 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
625 return
626 if type != imp.PY_SOURCE:
627 tkMessageBox.showerror("Unsupported type",
628 "%s is not a source module" % name, parent=self.text)
629 return
630 if f:
631 f.close()
632 if self.flist:
633 self.flist.open(file)
634 else:
635 self.io.loadfile(file)
636
637 def open_class_browser(self, event=None):
638 filename = self.io.filename
639 if not filename:
640 tkMessageBox.showerror(
641 "No filename",
642 "This buffer has no associated filename",
643 master=self.text)
644 self.text.focus_set()
645 return None
646 head, tail = os.path.split(filename)
647 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000648 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000649 ClassBrowser.ClassBrowser(self.flist, base, [head])
650
651 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000652 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000653 PathBrowser.PathBrowser(self.flist)
654
655 def gotoline(self, lineno):
656 if lineno is not None and lineno > 0:
657 self.text.mark_set("insert", "%d.0" % lineno)
658 self.text.tag_remove("sel", "1.0", "end")
659 self.text.tag_add("sel", "insert", "insert +1l")
660 self.center()
661
662 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000663 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000664 return True
David Scherer7aced172000-08-15 01:13:23 +0000665 base, ext = os.path.splitext(os.path.basename(filename))
666 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000667 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000668 line = self.text.get('1.0', '1.0 lineend')
669 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000670
671 def close_hook(self):
672 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000673 self.flist.unregister_maybe_terminate(self)
674 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000675
676 def set_close_hook(self, close_hook):
677 self.close_hook = close_hook
678
679 def filename_change_hook(self):
680 if self.flist:
681 self.flist.filename_changed_edit(self)
682 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000683 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000684 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000685
Christian Heimesa156e092008-02-16 07:38:31 +0000686 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000687 if self.color:
688 return
Christian Heimesa156e092008-02-16 07:38:31 +0000689 if self.ispythonsource(self.io.filename):
690 self.color = self.ColorDelegator()
691 # can add more colorizers here...
692 if self.color:
693 self.per.removefilter(self.undo)
694 self.per.insertfilter(self.color)
695 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000696
Christian Heimesa156e092008-02-16 07:38:31 +0000697 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000698 if not self.color:
699 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000700 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000701 self.per.removefilter(self.color)
702 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000703
Steven M. Gavab77d3432002-03-02 07:16:21 +0000704 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000705 "Update the colour theme"
706 # Called from self.filename_change_hook and from configDialog.py
707 self._rmcolorizer()
708 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000709 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000710 normal_colors = idleConf.GetHighlight(theme, 'normal')
711 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
712 select_colors = idleConf.GetHighlight(theme, 'hilite')
713 self.text.config(
714 foreground=normal_colors['foreground'],
715 background=normal_colors['background'],
716 insertbackground=cursor_color,
717 selectforeground=select_colors['foreground'],
718 selectbackground=select_colors['background'],
719 )
David Scherer7aced172000-08-15 01:13:23 +0000720
Guido van Rossum33d26892007-08-05 15:29:28 +0000721 IDENTCHARS = string.ascii_letters + string.digits + "_"
722
723 def colorize_syntax_error(self, text, pos):
724 text.tag_add("ERROR", pos)
725 char = text.get(pos)
726 if char and char in self.IDENTCHARS:
727 text.tag_add("ERROR", pos + " wordstart", pos)
728 if '\n' == text.get(pos): # error at line end
729 text.mark_set("insert", pos)
730 else:
731 text.mark_set("insert", pos + "+1c")
732 text.see(pos)
733
Steven M. Gavab1585412002-03-12 00:21:56 +0000734 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000735 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000736 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000737 fontWeight='normal'
738 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
739 fontWeight='bold'
740 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
741 idleConf.GetOption('main','EditorWindow','font-size'),
742 fontWeight))
743
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000744 def RemoveKeybindings(self):
745 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000746 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000747 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000748 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000749 self.text.event_delete(event, *keylist)
750 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000751 xkeydefs = idleConf.GetExtensionBindings(extensionName)
752 if xkeydefs:
753 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000754 self.text.event_delete(event, *keylist)
755
756 def ApplyKeybindings(self):
757 "Update the keybindings after they are changed"
758 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000759 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000760 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000761 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000762 xkeydefs = idleConf.GetExtensionBindings(extensionName)
763 if xkeydefs:
764 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000765 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000766 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000767 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000768 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000769 for item in menu[1]:
770 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000771 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000772 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000773 menu = self.menudict[menubarItem]
774 end = menu.index(END) + 1
775 for index in range(0, end):
776 if menu.type(index) == 'command':
777 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000778 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000779 itemName = menu.entrycget(index, 'label')
780 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000781 if menubarItem in menuEventDict:
782 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000783 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000784 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000785 accel = get_accelerator(keydefs, event)
786 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000787
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000788 def set_notabs_indentwidth(self):
789 "Update the indentwidth if changed and not using tabs in this window"
790 # Called from configDialog.py
791 if not self.usetabs:
792 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
793 type='int')
794
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000795 def reset_help_menu_entries(self):
796 "Update the additional help entries on the Help menu"
797 help_list = idleConf.GetAllExtraHelpSourcesList()
798 helpmenu = self.menudict['help']
799 # first delete the extra help entries, if any
800 helpmenu_length = helpmenu.index(END)
801 if helpmenu_length > self.base_helpmenu_length:
802 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
803 # then rebuild them
804 if help_list:
805 helpmenu.add_separator()
806 for entry in help_list:
807 cmd = self.__extra_help_callback(entry[1])
808 helpmenu.add_command(label=entry[0], command=cmd)
809 # and update the menu dictionary
810 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000811
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000812 def __extra_help_callback(self, helpfile):
813 "Create a callback with the helpfile value frozen at definition time"
814 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000815 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000816 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000817 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000818 try:
819 os.startfile(helpfile)
820 except WindowsError as why:
821 tkMessageBox.showerror(title='Document Start Failure',
822 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000823 else:
824 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000825 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000826
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000827 def update_recent_files_list(self, new_file=None):
828 "Load and update the recent files list and menus"
829 rf_list = []
830 if os.path.exists(self.recent_files_path):
Ned Deily122539e2011-01-24 21:46:44 +0000831 rf_list_file = open(self.recent_files_path,'r',
832 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000833 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000834 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000835 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000836 rf_list_file.close()
837 if new_file:
838 new_file = os.path.abspath(new_file) + '\n'
839 if new_file in rf_list:
840 rf_list.remove(new_file) # move to top
841 rf_list.insert(0, new_file)
842 # clean and save the recent files list
843 bad_paths = []
844 for path in rf_list:
845 if '\0' in path or not os.path.exists(path[0:-1]):
846 bad_paths.append(path)
847 rf_list = [path for path in rf_list if path not in bad_paths]
848 ulchars = "1234567890ABCDEFGHIJK"
849 rf_list = rf_list[0:len(ulchars)]
Steven M. Gava1d46e402002-03-27 08:40:46 +0000850 try:
Ned Deilyf505b742011-12-14 14:58:24 -0800851 with open(self.recent_files_path, 'w',
852 encoding='utf_8', errors='replace') as rf_file:
853 rf_file.writelines(rf_list)
854 except IOError as err:
855 if not getattr(self.root, "recentfilelist_error_displayed", False):
856 self.root.recentfilelist_error_displayed = True
857 tkMessageBox.showerror(title='IDLE Error',
858 message='Unable to update Recent Files list:\n%s'
859 % str(err),
860 parent=self.text)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000861 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000862 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000863 menu = instance.recent_files_menu
864 menu.delete(1, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000865 for i, file_name in enumerate(rf_list):
866 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000867 # make unicode string to display non-ASCII chars correctly
868 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000869 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000870 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000871 command=callback,
872 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000873
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000874 def __recent_file_callback(self, file_name):
875 def open_recent_file(fn_closure=file_name):
876 self.io.open(editFile=fn_closure)
877 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000878
David Scherer7aced172000-08-15 01:13:23 +0000879 def saved_change_hook(self):
880 short = self.short_title()
881 long = self.long_title()
882 if short and long:
883 title = short + " - " + long
884 elif short:
885 title = short
886 elif long:
887 title = long
888 else:
889 title = "Untitled"
890 icon = short or long or title
891 if not self.get_saved():
892 title = "*%s*" % title
893 icon = "*%s" % icon
894 self.top.wm_title(title)
895 self.top.wm_iconname(icon)
896
897 def get_saved(self):
898 return self.undo.get_saved()
899
900 def set_saved(self, flag):
901 self.undo.set_saved(flag)
902
903 def reset_undo(self):
904 self.undo.reset_undo()
905
906 def short_title(self):
907 filename = self.io.filename
908 if filename:
909 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000910 # return unicode string to display non-ASCII chars correctly
911 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000912
913 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000914 # return unicode string to display non-ASCII chars correctly
915 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000916
917 def center_insert_event(self, event):
918 self.center()
919
920 def center(self, mark="insert"):
921 text = self.text
922 top, bot = self.getwindowlines()
923 lineno = self.getlineno(mark)
924 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000925 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000926 text.yview(float(newtop))
927
928 def getwindowlines(self):
929 text = self.text
930 top = self.getlineno("@0,0")
931 bot = self.getlineno("@0,65535")
932 if top == bot and text.winfo_height() == 1:
933 # Geometry manager hasn't run yet
934 height = int(text['height'])
935 bot = top + height - 1
936 return top, bot
937
938 def getlineno(self, mark="insert"):
939 text = self.text
940 return int(float(text.index(mark)))
941
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000942 def get_geometry(self):
943 "Return (width, height, x, y)"
944 geom = self.top.wm_geometry()
945 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000946 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000947
David Scherer7aced172000-08-15 01:13:23 +0000948 def close_event(self, event):
949 self.close()
950
951 def maybesave(self):
952 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000953 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000954 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000955 self.top.deiconify()
956 self.top.lower()
957 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000958 return self.io.maybesave()
959
960 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000961 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000962 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000963 self._close()
964 return reply
965
966 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000967 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000968 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000969 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000970 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000971 self.io.close()
972 self.io = None
973 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000974 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000975 self.color.close(False)
976 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000977 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000978 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000979 self.per.close()
980 self.per = None
981 self.top.destroy()
982 if self.close_hook:
983 # unless override: unregister from flist, terminate if last window
984 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000985
986 def load_extensions(self):
987 self.extensions = {}
988 self.load_standard_extensions()
989
990 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000991 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000992 if hasattr(ins, "close"):
993 ins.close()
994 self.extensions = {}
995
996 def load_standard_extensions(self):
997 for name in self.get_standard_extension_names():
998 try:
999 self.load_extension(name)
1000 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001001 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +00001002 traceback.print_exc()
1003
1004 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001005 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +00001006
1007 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001008 try:
Brett Cannonaef82d32012-04-14 20:44:23 -04001009 try:
1010 mod = importlib.import_module('.' + name, package=__package__)
1011 except ImportError:
1012 mod = importlib.import_module(name)
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +00001013 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +00001014 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +00001015 raise
David Scherer7aced172000-08-15 01:13:23 +00001016 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +00001017 keydefs = idleConf.GetExtensionBindings(name)
1018 if hasattr(cls, "menudefs"):
1019 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +00001020 ins = cls(self)
1021 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +00001022 if keydefs:
1023 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001024 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001025 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +00001026 while methodname[:1] == '<':
1027 methodname = methodname[1:]
1028 while methodname[-1:] == '>':
1029 methodname = methodname[:-1]
1030 methodname = methodname + "_event"
1031 if hasattr(ins, methodname):
1032 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +00001033
1034 def apply_bindings(self, keydefs=None):
1035 if keydefs is None:
1036 keydefs = self.Bindings.default_keydefs
1037 text = self.text
1038 text.keydefs = keydefs
1039 for event, keylist in keydefs.items():
1040 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +00001041 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +00001042
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001043 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +00001044 """Add appropriate entries to the menus and submenus
1045
1046 Menus that are absent or None in self.menudict are ignored.
1047 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001048 if menudefs is None:
1049 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +00001050 if keydefs is None:
1051 keydefs = self.Bindings.default_keydefs
1052 menudict = self.menudict
1053 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001054 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +00001055 menu = menudict.get(mname)
1056 if not menu:
1057 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001058 for entry in entrylist:
1059 if not entry:
David Scherer7aced172000-08-15 01:13:23 +00001060 menu.add_separator()
1061 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001062 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001063 checkbutton = (label[:1] == '!')
1064 if checkbutton:
1065 label = label[1:]
1066 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001067 accelerator = get_accelerator(keydefs, eventname)
1068 def command(text=text, eventname=eventname):
1069 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001070 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001071 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001072 menu.add_checkbutton(label=label, underline=underline,
1073 command=command, accelerator=accelerator,
1074 variable=var)
1075 else:
1076 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001077 command=command,
1078 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001079
1080 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001081 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001082 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001083 value = var.get()
1084 return value
1085 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001086 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001087
1088 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001089 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001090 if var:
1091 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001092 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001093 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001094
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001095 def get_var_obj(self, name, vartype=None):
1096 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001097 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001098 # create a Tkinter variable object with self.text as master:
1099 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001100 return var
1101
1102 # Tk implementations of "virtual text methods" -- each platform
1103 # reusing IDLE's support code needs to define these for its GUI's
1104 # flavor of widget.
1105
1106 # Is character at text_index in a Python string? Return 0 for
1107 # "guaranteed no", true for anything else. This info is expensive
1108 # to compute ab initio, but is probably already known by the
1109 # platform's colorizer.
1110
1111 def is_char_in_string(self, text_index):
1112 if self.color:
1113 # Return true iff colorizer hasn't (re)gotten this far
1114 # yet, or the character is tagged as being in a string
1115 return self.text.tag_prevrange("TODO", text_index) or \
1116 "STRING" in self.text.tag_names(text_index)
1117 else:
1118 # The colorizer is missing: assume the worst
1119 return 1
1120
1121 # If a selection is defined in the text widget, return (start,
1122 # end) as Tkinter text indices, otherwise return (None, None)
1123 def get_selection_indices(self):
1124 try:
1125 first = self.text.index("sel.first")
1126 last = self.text.index("sel.last")
1127 return first, last
1128 except TclError:
1129 return None, None
1130
1131 # Return the text widget's current view of what a tab stop means
1132 # (equivalent width in spaces).
1133
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001134 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001135 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1136 return int(current)
1137
1138 # Set the text widget's current view of what a tab stop means.
1139
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001140 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001141 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001142 if self.get_tk_tabwidth() != newtabwidth:
1143 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001144 pixels = text.tk.call("font", "measure", text["font"],
1145 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001146 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001147 text.configure(tabs=pixels)
1148
Guido van Rossum33d26892007-08-05 15:29:28 +00001149### begin autoindent code ### (configuration was moved to beginning of class)
1150
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001151 def set_indentation_params(self, is_py_src, guess=True):
1152 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001153 i = self.guess_indent()
1154 if 2 <= i <= 8:
1155 self.indentwidth = i
1156 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001157 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001158 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001159
1160 def smart_backspace_event(self, event):
1161 text = self.text
1162 first, last = self.get_selection_indices()
1163 if first and last:
1164 text.delete(first, last)
1165 text.mark_set("insert", first)
1166 return "break"
1167 # Delete whitespace left, until hitting a real char or closest
1168 # preceding virtual tab stop.
1169 chars = text.get("insert linestart", "insert")
1170 if chars == '':
1171 if text.compare("insert", ">", "1.0"):
1172 # easy: delete preceding newline
1173 text.delete("insert-1c")
1174 else:
1175 text.bell() # at start of buffer
1176 return "break"
1177 if chars[-1] not in " \t":
1178 # easy: delete preceding real char
1179 text.delete("insert-1c")
1180 return "break"
1181 # Ick. It may require *inserting* spaces if we back up over a
1182 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001183 tabwidth = self.tabwidth
1184 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001185 assert have > 0
1186 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001187 # Debug prompt is multilined....
Terry Jan Reedy7f53aea2012-01-15 19:03:23 -05001188 if self.context_use_ps1:
1189 last_line_of_prompt = sys.ps1.split('\n')[-1]
1190 else:
1191 last_line_of_prompt = ''
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001192 ncharsdeleted = 0
1193 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001194 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001195 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001196 chars = chars[:-1]
1197 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001198 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001199 if have <= want or chars[-1] not in " \t":
1200 break
1201 text.undo_block_start()
1202 text.delete("insert-%dc" % ncharsdeleted, "insert")
1203 if have < want:
1204 text.insert("insert", ' ' * (want - have))
1205 text.undo_block_stop()
1206 return "break"
1207
1208 def smart_indent_event(self, event):
1209 # if intraline selection:
1210 # delete it
1211 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001212 # do indent-region
1213 # else:
1214 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001215 text = self.text
1216 first, last = self.get_selection_indices()
1217 text.undo_block_start()
1218 try:
1219 if first and last:
1220 if index2line(first) != index2line(last):
1221 return self.indent_region_event(event)
1222 text.delete(first, last)
1223 text.mark_set("insert", first)
1224 prefix = text.get("insert linestart", "insert")
1225 raw, effective = classifyws(prefix, self.tabwidth)
1226 if raw == len(prefix):
1227 # only whitespace to the left
1228 self.reindent_to(effective + self.indentwidth)
1229 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001230 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001231 if self.usetabs:
1232 pad = '\t'
1233 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001234 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001235 n = self.indentwidth
1236 pad = ' ' * (n - effective % n)
1237 text.insert("insert", pad)
1238 text.see("insert")
1239 return "break"
1240 finally:
1241 text.undo_block_stop()
1242
1243 def newline_and_indent_event(self, event):
1244 text = self.text
1245 first, last = self.get_selection_indices()
1246 text.undo_block_start()
1247 try:
1248 if first and last:
1249 text.delete(first, last)
1250 text.mark_set("insert", first)
1251 line = text.get("insert linestart", "insert")
1252 i, n = 0, len(line)
1253 while i < n and line[i] in " \t":
1254 i = i+1
1255 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001256 # the cursor is in or at leading indentation in a continuation
1257 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001258 text.insert("insert linestart", '\n')
1259 return "break"
1260 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001261 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001262 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001263 last_line_of_prompt = sys.ps1.split('\n')[-1]
1264 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001265 line = line[:-1]
1266 i = i+1
1267 if i:
1268 text.delete("insert - %d chars" % i, "insert")
1269 # strip whitespace after insert point
1270 while text.get("insert") in " \t":
1271 text.delete("insert")
1272 # start new line
1273 text.insert("insert", '\n')
1274
1275 # adjust indentation for continuations and block
1276 # open/close first need to find the last stmt
1277 lno = index2line(text.index('insert'))
1278 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001279 if not self.context_use_ps1:
1280 for context in self.num_context_lines:
1281 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001282 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001283 rawtext = text.get(startatindex, "insert")
1284 y.set_str(rawtext)
1285 bod = y.find_good_parse_start(
1286 self.context_use_ps1,
1287 self._build_char_in_string_func(startatindex))
1288 if bod is not None or startat == 1:
1289 break
1290 y.set_lo(bod or 0)
1291 else:
1292 r = text.tag_prevrange("console", "insert")
1293 if r:
1294 startatindex = r[1]
1295 else:
1296 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001297 rawtext = text.get(startatindex, "insert")
1298 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001299 y.set_lo(0)
1300
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001301 c = y.get_continuation_type()
1302 if c != PyParse.C_NONE:
1303 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001304 if c == PyParse.C_STRING_FIRST_LINE:
1305 # after the first line of a string; do not indent at all
1306 pass
1307 elif c == PyParse.C_STRING_NEXT_LINES:
1308 # inside a string which started before this line;
1309 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001310 text.insert("insert", indent)
1311 elif c == PyParse.C_BRACKET:
1312 # line up with the first (if any) element of the
1313 # last open bracket structure; else indent one
1314 # level beyond the indent of the line with the
1315 # last open bracket
1316 self.reindent_to(y.compute_bracket_indent())
1317 elif c == PyParse.C_BACKSLASH:
1318 # if more than one line in this stmt already, just
1319 # mimic the current indent; else if initial line
1320 # has a start on an assignment stmt, indent to
1321 # beyond leftmost =; else to beyond first chunk of
1322 # non-whitespace on initial line
1323 if y.get_num_lines_in_stmt() > 1:
1324 text.insert("insert", indent)
1325 else:
1326 self.reindent_to(y.compute_backslash_indent())
1327 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001328 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001329 return "break"
1330
1331 # This line starts a brand new stmt; indent relative to
1332 # indentation of initial line of closest preceding
1333 # interesting stmt.
1334 indent = y.get_base_indent_string()
1335 text.insert("insert", indent)
1336 if y.is_block_opener():
1337 self.smart_indent_event(event)
1338 elif indent and y.is_block_closer():
1339 self.smart_backspace_event(event)
1340 return "break"
1341 finally:
1342 text.see("insert")
1343 text.undo_block_stop()
1344
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001345 # Our editwin provides a is_char_in_string function that works
1346 # with a Tk text index, but PyParse only knows about offsets into
1347 # a string. This builds a function for PyParse that accepts an
1348 # offset.
1349
1350 def _build_char_in_string_func(self, startindex):
1351 def inner(offset, _startindex=startindex,
1352 _icis=self.is_char_in_string):
1353 return _icis(_startindex + "+%dc" % offset)
1354 return inner
1355
1356 def indent_region_event(self, event):
1357 head, tail, chars, lines = self.get_region()
1358 for pos in range(len(lines)):
1359 line = lines[pos]
1360 if line:
1361 raw, effective = classifyws(line, self.tabwidth)
1362 effective = effective + self.indentwidth
1363 lines[pos] = self._make_blanks(effective) + line[raw:]
1364 self.set_region(head, tail, chars, lines)
1365 return "break"
1366
1367 def dedent_region_event(self, event):
1368 head, tail, chars, lines = self.get_region()
1369 for pos in range(len(lines)):
1370 line = lines[pos]
1371 if line:
1372 raw, effective = classifyws(line, self.tabwidth)
1373 effective = max(effective - self.indentwidth, 0)
1374 lines[pos] = self._make_blanks(effective) + line[raw:]
1375 self.set_region(head, tail, chars, lines)
1376 return "break"
1377
1378 def comment_region_event(self, event):
1379 head, tail, chars, lines = self.get_region()
1380 for pos in range(len(lines) - 1):
1381 line = lines[pos]
1382 lines[pos] = '##' + line
1383 self.set_region(head, tail, chars, lines)
1384
1385 def uncomment_region_event(self, event):
1386 head, tail, chars, lines = self.get_region()
1387 for pos in range(len(lines)):
1388 line = lines[pos]
1389 if not line:
1390 continue
1391 if line[:2] == '##':
1392 line = line[2:]
1393 elif line[:1] == '#':
1394 line = line[1:]
1395 lines[pos] = line
1396 self.set_region(head, tail, chars, lines)
1397
1398 def tabify_region_event(self, event):
1399 head, tail, chars, lines = self.get_region()
1400 tabwidth = self._asktabwidth()
1401 for pos in range(len(lines)):
1402 line = lines[pos]
1403 if line:
1404 raw, effective = classifyws(line, tabwidth)
1405 ntabs, nspaces = divmod(effective, tabwidth)
1406 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1407 self.set_region(head, tail, chars, lines)
1408
1409 def untabify_region_event(self, event):
1410 head, tail, chars, lines = self.get_region()
1411 tabwidth = self._asktabwidth()
1412 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001413 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001414 self.set_region(head, tail, chars, lines)
1415
1416 def toggle_tabs_event(self, event):
1417 if self.askyesno(
1418 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001419 "Turn tabs " + ("on", "off")[self.usetabs] +
1420 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001421 ("will be", "remains at")[self.usetabs] + " 8." +
1422 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001423 parent=self.text):
1424 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001425 # Try to prevent inconsistent indentation.
1426 # User must change indent width manually after using tabs.
1427 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001428 return "break"
1429
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001430 # XXX this isn't bound to anything -- see tabwidth comments
1431## def change_tabwidth_event(self, event):
1432## new = self._asktabwidth()
1433## if new != self.tabwidth:
1434## self.tabwidth = new
1435## self.set_indentation_params(0, guess=0)
1436## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001437
1438 def change_indentwidth_event(self, event):
1439 new = self.askinteger(
1440 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001441 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001442 parent=self.text,
1443 initialvalue=self.indentwidth,
1444 minvalue=2,
1445 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001446 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001447 self.indentwidth = new
1448 return "break"
1449
1450 def get_region(self):
1451 text = self.text
1452 first, last = self.get_selection_indices()
1453 if first and last:
1454 head = text.index(first + " linestart")
1455 tail = text.index(last + "-1c lineend +1c")
1456 else:
1457 head = text.index("insert linestart")
1458 tail = text.index("insert lineend +1c")
1459 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001460 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001461 return head, tail, chars, lines
1462
1463 def set_region(self, head, tail, chars, lines):
1464 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001465 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001466 if newchars == chars:
1467 text.bell()
1468 return
1469 text.tag_remove("sel", "1.0", "end")
1470 text.mark_set("insert", head)
1471 text.undo_block_start()
1472 text.delete(head, tail)
1473 text.insert(head, newchars)
1474 text.undo_block_stop()
1475 text.tag_add("sel", head, "insert")
1476
1477 # Make string that displays as n leading blanks.
1478
1479 def _make_blanks(self, n):
1480 if self.usetabs:
1481 ntabs, nspaces = divmod(n, self.tabwidth)
1482 return '\t' * ntabs + ' ' * nspaces
1483 else:
1484 return ' ' * n
1485
1486 # Delete from beginning of line to insert point, then reinsert
1487 # column logical (meaning use tabs if appropriate) spaces.
1488
1489 def reindent_to(self, column):
1490 text = self.text
1491 text.undo_block_start()
1492 if text.compare("insert linestart", "!=", "insert"):
1493 text.delete("insert linestart", "insert")
1494 if column:
1495 text.insert("insert", self._make_blanks(column))
1496 text.undo_block_stop()
1497
1498 def _asktabwidth(self):
1499 return self.askinteger(
1500 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001501 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001502 parent=self.text,
1503 initialvalue=self.indentwidth,
1504 minvalue=2,
1505 maxvalue=16) or self.tabwidth
1506
1507 # Guess indentwidth from text content.
1508 # Return guessed indentwidth. This should not be believed unless
1509 # it's in a reasonable range (e.g., it will be 0 if no indented
1510 # blocks are found).
1511
1512 def guess_indent(self):
1513 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1514 if opener and indented:
1515 raw, indentsmall = classifyws(opener, self.tabwidth)
1516 raw, indentlarge = classifyws(indented, self.tabwidth)
1517 else:
1518 indentsmall = indentlarge = 0
1519 return indentlarge - indentsmall
1520
1521# "line.col" -> line, as an int
1522def index2line(index):
1523 return int(float(index))
1524
1525# Look at the leading whitespace in s.
1526# Return pair (# of leading ws characters,
1527# effective # of leading blanks after expanding
1528# tabs to width tabwidth)
1529
1530def classifyws(s, tabwidth):
1531 raw = effective = 0
1532 for ch in s:
1533 if ch == ' ':
1534 raw = raw + 1
1535 effective = effective + 1
1536 elif ch == '\t':
1537 raw = raw + 1
1538 effective = (effective // tabwidth + 1) * tabwidth
1539 else:
1540 break
1541 return raw, effective
1542
1543import tokenize
1544_tokenize = tokenize
1545del tokenize
1546
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001547class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001548
1549 # .run() chews over the Text widget, looking for a block opener
1550 # and the stmt following it. Returns a pair,
1551 # (line containing block opener, line containing stmt)
1552 # Either or both may be None.
1553
1554 def __init__(self, text, tabwidth):
1555 self.text = text
1556 self.tabwidth = tabwidth
1557 self.i = self.finished = 0
1558 self.blkopenline = self.indentedline = None
1559
1560 def readline(self):
1561 if self.finished:
1562 return ""
1563 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001564 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001565 if self.text.compare(mark, ">=", "end"):
1566 return ""
1567 return self.text.get(mark, mark + " lineend+1c")
1568
1569 def tokeneater(self, type, token, start, end, line,
1570 INDENT=_tokenize.INDENT,
1571 NAME=_tokenize.NAME,
1572 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1573 if self.finished:
1574 pass
1575 elif type == NAME and token in OPENERS:
1576 self.blkopenline = line
1577 elif type == INDENT and self.blkopenline:
1578 self.indentedline = line
1579 self.finished = 1
1580
1581 def run(self):
1582 save_tabsize = _tokenize.tabsize
1583 _tokenize.tabsize = self.tabwidth
1584 try:
1585 try:
Trent Nelson428de652008-03-18 22:41:35 +00001586 tokens = _tokenize.generate_tokens(self.readline)
1587 for token in tokens:
1588 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001589 except _tokenize.TokenError:
1590 # since we cut off the tokenizer early, we can trigger
1591 # spurious errors
1592 pass
1593 finally:
1594 _tokenize.tabsize = save_tabsize
1595 return self.blkopenline, self.indentedline
1596
1597### end autoindent code ###
1598
David Scherer7aced172000-08-15 01:13:23 +00001599def prepstr(s):
1600 # Helper to extract the underscore from a string, e.g.
1601 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001602 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001603 if i >= 0:
1604 s = s[:i] + s[i+1:]
1605 return i, s
1606
1607
1608keynames = {
1609 'bracketleft': '[',
1610 'bracketright': ']',
1611 'slash': '/',
1612}
1613
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001614def get_accelerator(keydefs, eventname):
1615 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001616 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1617 # if not keylist:
1618 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1619 "<<open-module>>",
1620 "<<goto-line>>",
1621 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001622 return ""
1623 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001624 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001625 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1626 s = re.sub("Key-", "", s)
1627 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1628 s = re.sub("Control-", "Ctrl-", s)
1629 s = re.sub("-", "+", s)
1630 s = re.sub("><", " ", s)
1631 s = re.sub("<", "", s)
1632 s = re.sub(">", "", s)
1633 return s
1634
1635
1636def fixwordbreaks(root):
1637 # Make sure that Tk's double-click and next/previous word
1638 # operations use our definition of a word (i.e. an identifier)
1639 tk = root.tk
1640 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1641 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1642 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1643
1644
1645def test():
1646 root = Tk()
1647 fixwordbreaks(root)
1648 root.withdraw()
1649 if sys.argv[1:]:
1650 filename = sys.argv[1]
1651 else:
1652 filename = None
1653 edit = EditorWindow(root=root, filename=filename)
1654 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001655 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001656 root.mainloop()
1657 root.destroy()
1658
1659if __name__ == '__main__':
1660 test()