blob: 7b7eb3939dc546c5019247347161cd44c6e64d3d [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
David Scherer7aced172000-08-15 01:13:23 +00003import re
Guido van Rossum33d26892007-08-05 15:29:28 +00004import string
David Scherer7aced172000-08-15 01:13:23 +00005import imp
Georg Brandl14fc4272008-05-17 18:39:55 +00006from tkinter import *
7import tkinter.simpledialog as tkSimpleDialog
8import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +00009import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000010import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000011
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000012from idlelib.MultiCall import MultiCallCreator
13from idlelib import idlever
14from idlelib import WindowList
15from idlelib import SearchDialog
16from idlelib import GrepDialog
17from idlelib import ReplaceDialog
18from idlelib import PyParse
19from idlelib.configHandler import idleConf
20from idlelib import aboutDialog, textView, configDialog
21from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000022
23# The default tab setting for a Text widget, in average-width characters.
24TK_TABWIDTH_DEFAULT = 8
25
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000026def _sphinx_version():
27 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
28 major, minor, micro, level, serial = sys.version_info
29 release = '%s%s' % (major, minor)
30 if micro:
Benjamin Petersonb48f6342009-06-22 19:36:31 +000031 release += '%s' % (micro,)
32 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__)
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000053 return file, filename, descr
54
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000055class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000056 from idlelib.Percolator import Percolator
57 from idlelib.ColorDelegator import ColorDelegator
58 from idlelib.UndoDelegator import UndoDelegator
59 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
60 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000061 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000062 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000063
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000064 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000065
66 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000067 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000068 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000069 if sys.platform.count('linux'):
70 # look for html docs in a couple of standard places
71 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
72 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
73 dochome = '/var/www/html/python/index.html'
74 else:
75 basepath = '/usr/share/doc/' # standard location
76 dochome = os.path.join(basepath, pyver,
77 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000078 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +000079 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000080 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +000081 if os.path.isfile(chmfile):
82 dochome = chmfile
Thomas Wouters0e3f5912006-08-11 14:57:12 +000083 elif macosxSupport.runningAsOSXApp():
84 # documentation is stored inside the python framework
85 dochome = os.path.join(sys.prefix,
86 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000087 dochome = os.path.normpath(dochome)
88 if os.path.isfile(dochome):
89 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +000090 if sys.platform == 'darwin':
91 # Safari requires real file:-URLs
92 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000093 else:
Benjamin Peterson152b6572009-01-20 15:01:54 +000094 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +000095 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000096 self.flist = flist
97 root = root or flist.root
98 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +000099 try:
100 sys.ps1
101 except AttributeError:
102 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000103 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000104 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000105 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000106 self.tkinter_vars = flist.vars
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000107 #self.top.instance_dict makes flist.inversedict avalable to
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000108 #configDialog.py so it can access all EditorWindow instaces
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000109 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000110 else:
111 self.tkinter_vars = {} # keys: Tkinter event names
112 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000113 self.top.instance_dict = {}
114 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000115 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000116 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000117 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000118 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000119 text_options = {
120 'name': 'text',
121 'padx': 5,
122 'wrap': 'none',
123 'width': self.width,
124 'height': idleConf.GetOption('main', 'EditorWindow', 'height')}
125 if TkVersion >= 8.5:
126 # Starting with tk 8.5 we have to set the new tabstyle option
127 # to 'wordprocessor' to achieve the same display of tabs as in
128 # older tk versions.
129 text_options['tabstyle'] = 'wordprocessor'
130 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000131 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000132
133 self.createmenubar()
134 self.apply_bindings()
135
136 self.top.protocol("WM_DELETE_WINDOW", self.close)
137 self.top.bind("<<close-window>>", self.close_event)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000138 if macosxSupport.runningAsOSXApp():
139 # Command-W on editorwindows doesn't work without this.
140 text.bind('<<close-window>>', self.close_event)
R. David Murrayb68a7bc2010-12-18 17:19:10 +0000141 # Some OS X systems have only one mouse button,
142 # so use control-click for pulldown menus there.
143 # (Note, AquaTk defines <2> as the right button if
144 # present and the Tk Text widget already binds <2>.)
145 text.bind("<Control-Button-1>",self.right_menu_event)
146 else:
147 # Elsewhere, use right-click for pulldown menus.
148 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000149 text.bind("<<cut>>", self.cut)
150 text.bind("<<copy>>", self.copy)
151 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000152 text.bind("<<center-insert>>", self.center_insert_event)
153 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000154 text.bind("<<python-docs>>", self.python_docs)
155 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000156 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000157 text.bind("<<open-module>>", self.open_module)
158 text.bind("<<do-nothing>>", lambda event: "break")
159 text.bind("<<select-all>>", self.select_all)
160 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000161 text.bind("<<find>>", self.find_event)
162 text.bind("<<find-again>>", self.find_again_event)
163 text.bind("<<find-in-files>>", self.find_in_files_event)
164 text.bind("<<find-selection>>", self.find_selection_event)
165 text.bind("<<replace>>", self.replace_event)
166 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000167 text.bind("<<smart-backspace>>",self.smart_backspace_event)
168 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
169 text.bind("<<smart-indent>>",self.smart_indent_event)
170 text.bind("<<indent-region>>",self.indent_region_event)
171 text.bind("<<dedent-region>>",self.dedent_region_event)
172 text.bind("<<comment-region>>",self.comment_region_event)
173 text.bind("<<uncomment-region>>",self.uncomment_region_event)
174 text.bind("<<tabify-region>>",self.tabify_region_event)
175 text.bind("<<untabify-region>>",self.untabify_region_event)
176 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
177 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000178 text.bind("<Left>", self.move_at_edge_if_selection(0))
179 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000180 text.bind("<<del-word-left>>", self.del_word_left)
181 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000182 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000183
David Scherer7aced172000-08-15 01:13:23 +0000184 if flist:
185 flist.inversedict[self] = key
186 if key:
187 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000188 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000189 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
190 text.bind("<<open-class-browser>>", self.open_class_browser)
191 text.bind("<<open-path-browser>>", self.open_path_browser)
192
Steven M. Gava898a3652001-10-07 11:10:44 +0000193 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000194 vbar['command'] = text.yview
195 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000196 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000197 fontWeight = 'normal'
198 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000199 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000200 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
201 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
202 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000203 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
204 text.pack(side=TOP, fill=BOTH, expand=1)
205 text.focus_set()
206
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000207 # usetabs true -> literal tab characters are used by indent and
208 # dedent cmds, possibly mixed with spaces if
209 # indentwidth is not a multiple of tabwidth,
210 # which will cause Tabnanny to nag!
211 # false -> tab characters are converted to spaces by indent
212 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000213 # Although use-spaces=0 can be configured manually in config-main.def,
214 # configuration of tabs v. spaces is not supported in the configuration
215 # dialog. IDLE promotes the preferred Python indentation: use spaces!
216 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
217 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000218
219 # tabwidth is the display width of a literal tab character.
220 # CAUTION: telling Tk to use anything other than its default
221 # tab setting causes it to use an entirely different tabbing algorithm,
222 # treating tab stops as fixed distances from the left margin.
223 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000224 self.tabwidth = 8 # must remain 8 until Tk is fixed.
225
226 # indentwidth is the number of screen characters per indent level.
227 # The recommended Python indentation is four spaces.
228 self.indentwidth = self.tabwidth
229 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000230
231 # If context_use_ps1 is true, parsing searches back for a ps1 line;
232 # else searches for a popular (if, def, ...) Python stmt.
233 self.context_use_ps1 = False
234
235 # When searching backwards for a reliable place to begin parsing,
236 # first start num_context_lines[0] lines back, then
237 # num_context_lines[1] lines back if that didn't work, and so on.
238 # The last value should be huge (larger than the # of lines in a
239 # conceivable file).
240 # Making the initial values larger slows things down more often.
241 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000242 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000243 self.undo = undo = self.UndoDelegator()
244 per.insertfilter(undo)
245 text.undo_block_start = undo.undo_block_start
246 text.undo_block_stop = undo.undo_block_stop
247 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000248 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000249 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000250 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000251 self.good_load = False
252 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000253 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000254 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000255 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000256 if io.loadfile(filename):
257 self.good_load = True
258 is_py_src = self.ispythonsource(filename)
259 self.set_indentation_params(is_py_src)
260 if is_py_src:
261 self.color = color = self.ColorDelegator()
262 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000263 else:
264 io.set_filename(filename)
Christian Heimesa156e092008-02-16 07:38:31 +0000265 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000266 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000267 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000268 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000269 menu = self.menudict.get('windows')
270 if menu:
271 end = menu.index("end")
272 if end is None:
273 end = -1
274 if end >= 0:
275 menu.add_separator()
276 end = end + 1
277 self.wmenu_end = end
278 WindowList.register_callback(self.postwindowsmenu)
279
280 # Some abstractions so IDLE extensions are cross-IDE
281 self.askyesno = tkMessageBox.askyesno
282 self.askinteger = tkSimpleDialog.askinteger
283 self.showerror = tkMessageBox.showerror
284
Martin v. Löwis307021f2005-11-27 16:59:04 +0000285 def _filename_to_unicode(self, filename):
286 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000287 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000288 return filename
289 else:
290 try:
291 return filename.decode(self.filesystemencoding)
292 except UnicodeDecodeError:
293 # XXX
294 try:
295 return filename.decode(self.encoding)
296 except UnicodeDecodeError:
297 # byte-to-byte conversion
298 return filename.decode('iso8859-1')
299
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000300 def new_callback(self, event):
301 dirname, basename = self.io.defaultfilename()
302 self.flist.new(dirname)
303 return "break"
304
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000305 def home_callback(self, event):
306 if (event.state & 12) != 0 and event.keysym == "Home":
307 # state&1==shift, state&4==control, state&8==alt
308 return # <Modifier-Home>; fall back to class binding
309
310 if self.text.index("iomark") and \
311 self.text.compare("iomark", "<=", "insert lineend") and \
312 self.text.compare("insert linestart", "<=", "iomark"):
313 insertpt = int(self.text.index("iomark").split(".")[1])
314 else:
315 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000316 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000317 if line[insertpt] not in (' ','\t'):
318 break
319 else:
320 insertpt=len(line)
321
322 lineat = int(self.text.index("insert").split('.')[1])
323
324 if insertpt == lineat:
325 insertpt = 0
326
327 dest = "insert linestart+"+str(insertpt)+"c"
328
329 if (event.state&1) == 0:
330 # shift not pressed
331 self.text.tag_remove("sel", "1.0", "end")
332 else:
333 if not self.text.index("sel.first"):
334 self.text.mark_set("anchor","insert")
335
336 first = self.text.index(dest)
337 last = self.text.index("anchor")
338
339 if self.text.compare(first,">",last):
340 first,last = last,first
341
342 self.text.tag_remove("sel", "1.0", "end")
343 self.text.tag_add("sel", first, last)
344
345 self.text.mark_set("insert", dest)
346 self.text.see("insert")
347 return "break"
348
David Scherer7aced172000-08-15 01:13:23 +0000349 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000350 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000351 if macosxSupport.runningAsOSXApp():
352 # Insert some padding to avoid obscuring some of the statusbar
353 # by the resize widget.
354 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000355 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
356 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
357 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000358 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
359 self.text.event_add("<<set-line-and-column>>",
360 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000361 self.text.after_idle(self.set_line_and_column)
362
363 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000364 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000365 self.status_bar.set_label('column', 'Col: %s' % column)
366 self.status_bar.set_label('line', 'Ln: %s' % line)
367
David Scherer7aced172000-08-15 01:13:23 +0000368 menu_specs = [
369 ("file", "_File"),
370 ("edit", "_Edit"),
371 ("format", "F_ormat"),
372 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000373 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000374 ("windows", "_Windows"),
375 ("help", "_Help"),
376 ]
377
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000378 if macosxSupport.runningAsOSXApp():
379 del menu_specs[-3]
380 menu_specs[-2] = ("windows", "_Window")
381
382
David Scherer7aced172000-08-15 01:13:23 +0000383 def createmenubar(self):
384 mbar = self.menubar
385 self.menudict = menudict = {}
386 for name, label in self.menu_specs:
387 underline, label = prepstr(label)
388 menudict[name] = menu = Menu(mbar, name=name)
389 mbar.add_cascade(label=label, menu=menu, underline=underline)
Georg Brandlaedd2892010-12-19 10:10:32 +0000390 if macosxSupport.isCarbonAquaTk(self.root):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000391 # Insert the application menu
392 menudict['application'] = menu = Menu(mbar, name='apple')
393 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000394 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000395 self.recent_files_menu = Menu(self.menubar)
396 self.menudict['file'].insert_cascade(3, label='Recent Files',
397 underline=0,
398 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000399 self.base_helpmenu_length = self.menudict['help'].index(END)
400 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000401
402 def postwindowsmenu(self):
403 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000404 menu = self.menudict['windows']
405 end = menu.index("end")
406 if end is None:
407 end = -1
408 if end > self.wmenu_end:
409 menu.delete(self.wmenu_end+1, end)
410 WindowList.add_windows_to_menu(menu)
411
412 rmenu = None
413
414 def right_menu_event(self, event):
415 self.text.tag_remove("sel", "1.0", "end")
416 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
417 if not self.rmenu:
418 self.make_rmenu()
419 rmenu = self.rmenu
420 self.event = event
421 iswin = sys.platform[:3] == 'win'
422 if iswin:
423 self.text.config(cursor="arrow")
424 rmenu.tk_popup(event.x_root, event.y_root)
425 if iswin:
426 self.text.config(cursor="ibeam")
427
428 rmenu_specs = [
429 # ("Label", "<<virtual-event>>"), ...
430 ("Close", "<<close-window>>"), # Example
431 ]
432
433 def make_rmenu(self):
434 rmenu = Menu(self.text, tearoff=0)
435 for label, eventname in self.rmenu_specs:
436 def command(text=self.text, eventname=eventname):
437 text.event_generate(eventname)
438 rmenu.add_command(label=label, command=command)
439 self.rmenu = rmenu
440
441 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000442 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000443
Steven M. Gava3b55a892001-11-21 05:56:26 +0000444 def config_dialog(self, event=None):
445 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000446
David Scherer7aced172000-08-15 01:13:23 +0000447 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000448 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000449 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000450
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000451 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000452 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000453 try:
454 os.startfile(self.help_url)
455 except WindowsError as why:
456 tkMessageBox.showerror(title='Document Start Failure',
457 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000458 else:
459 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000460 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000461
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000462 def cut(self,event):
463 self.text.event_generate("<<Cut>>")
464 return "break"
465
466 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000467 if not self.text.tag_ranges("sel"):
468 # There is no selection, so do nothing and maybe interrupt.
469 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000470 self.text.event_generate("<<Copy>>")
471 return "break"
472
473 def paste(self,event):
474 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000475 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000476 return "break"
477
David Scherer7aced172000-08-15 01:13:23 +0000478 def select_all(self, event=None):
479 self.text.tag_add("sel", "1.0", "end-1c")
480 self.text.mark_set("insert", "1.0")
481 self.text.see("insert")
482 return "break"
483
484 def remove_selection(self, event=None):
485 self.text.tag_remove("sel", "1.0", "end")
486 self.text.see("insert")
487
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000488 def move_at_edge_if_selection(self, edge_index):
489 """Cursor move begins at start or end of selection
490
491 When a left/right cursor key is pressed create and return to Tkinter a
492 function which causes a cursor move from the associated edge of the
493 selection.
494
495 """
496 self_text_index = self.text.index
497 self_text_mark_set = self.text.mark_set
498 edges_table = ("sel.first+1c", "sel.last-1c")
499 def move_at_edge(event):
500 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
501 try:
502 self_text_index("sel.first")
503 self_text_mark_set("insert", edges_table[edge_index])
504 except TclError:
505 pass
506 return move_at_edge
507
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000508 def del_word_left(self, event):
509 self.text.event_generate('<Meta-Delete>')
510 return "break"
511
512 def del_word_right(self, event):
513 self.text.event_generate('<Meta-d>')
514 return "break"
515
Steven M. Gavac5976402002-01-04 03:06:08 +0000516 def find_event(self, event):
517 SearchDialog.find(self.text)
518 return "break"
519
520 def find_again_event(self, event):
521 SearchDialog.find_again(self.text)
522 return "break"
523
524 def find_selection_event(self, event):
525 SearchDialog.find_selection(self.text)
526 return "break"
527
528 def find_in_files_event(self, event):
529 GrepDialog.grep(self.text, self.io, self.flist)
530 return "break"
531
532 def replace_event(self, event):
533 ReplaceDialog.replace(self.text)
534 return "break"
535
536 def goto_line_event(self, event):
537 text = self.text
538 lineno = tkSimpleDialog.askinteger("Goto",
539 "Go to line number:",parent=text)
540 if lineno is None:
541 return "break"
542 if lineno <= 0:
543 text.bell()
544 return "break"
545 text.mark_set("insert", "%d.0" % lineno)
546 text.see("insert")
547
David Scherer7aced172000-08-15 01:13:23 +0000548 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000549 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000550 try:
551 name = self.text.get("sel.first", "sel.last")
552 except TclError:
553 name = ""
554 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000555 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000556 name = tkSimpleDialog.askstring("Module",
557 "Enter the name of a Python module\n"
558 "to search on sys.path and open:",
559 parent=self.text, initialvalue=name)
560 if name:
561 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000562 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000563 return
David Scherer7aced172000-08-15 01:13:23 +0000564 # XXX Ought to insert current file's directory in front of path
565 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000566 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000567 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000568 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
569 return
570 if type != imp.PY_SOURCE:
571 tkMessageBox.showerror("Unsupported type",
572 "%s is not a source module" % name, parent=self.text)
573 return
574 if f:
575 f.close()
576 if self.flist:
577 self.flist.open(file)
578 else:
579 self.io.loadfile(file)
580
581 def open_class_browser(self, event=None):
582 filename = self.io.filename
583 if not filename:
584 tkMessageBox.showerror(
585 "No filename",
586 "This buffer has no associated filename",
587 master=self.text)
588 self.text.focus_set()
589 return None
590 head, tail = os.path.split(filename)
591 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000592 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000593 ClassBrowser.ClassBrowser(self.flist, base, [head])
594
595 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000596 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000597 PathBrowser.PathBrowser(self.flist)
598
599 def gotoline(self, lineno):
600 if lineno is not None and lineno > 0:
601 self.text.mark_set("insert", "%d.0" % lineno)
602 self.text.tag_remove("sel", "1.0", "end")
603 self.text.tag_add("sel", "insert", "insert +1l")
604 self.center()
605
606 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000607 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000608 return True
David Scherer7aced172000-08-15 01:13:23 +0000609 base, ext = os.path.splitext(os.path.basename(filename))
610 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000611 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000612 line = self.text.get('1.0', '1.0 lineend')
613 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000614
615 def close_hook(self):
616 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000617 self.flist.unregister_maybe_terminate(self)
618 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000619
620 def set_close_hook(self, close_hook):
621 self.close_hook = close_hook
622
623 def filename_change_hook(self):
624 if self.flist:
625 self.flist.filename_changed_edit(self)
626 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000627 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000628 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000629
Christian Heimesa156e092008-02-16 07:38:31 +0000630 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000631 if self.color:
632 return
Christian Heimesa156e092008-02-16 07:38:31 +0000633 if self.ispythonsource(self.io.filename):
634 self.color = self.ColorDelegator()
635 # can add more colorizers here...
636 if self.color:
637 self.per.removefilter(self.undo)
638 self.per.insertfilter(self.color)
639 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000640
Christian Heimesa156e092008-02-16 07:38:31 +0000641 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000642 if not self.color:
643 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000644 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000645 self.per.removefilter(self.color)
646 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000647
Steven M. Gavab77d3432002-03-02 07:16:21 +0000648 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000649 "Update the colour theme"
650 # Called from self.filename_change_hook and from configDialog.py
651 self._rmcolorizer()
652 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000653 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000654 normal_colors = idleConf.GetHighlight(theme, 'normal')
655 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
656 select_colors = idleConf.GetHighlight(theme, 'hilite')
657 self.text.config(
658 foreground=normal_colors['foreground'],
659 background=normal_colors['background'],
660 insertbackground=cursor_color,
661 selectforeground=select_colors['foreground'],
662 selectbackground=select_colors['background'],
663 )
David Scherer7aced172000-08-15 01:13:23 +0000664
Guido van Rossum33d26892007-08-05 15:29:28 +0000665 IDENTCHARS = string.ascii_letters + string.digits + "_"
666
667 def colorize_syntax_error(self, text, pos):
668 text.tag_add("ERROR", pos)
669 char = text.get(pos)
670 if char and char in self.IDENTCHARS:
671 text.tag_add("ERROR", pos + " wordstart", pos)
672 if '\n' == text.get(pos): # error at line end
673 text.mark_set("insert", pos)
674 else:
675 text.mark_set("insert", pos + "+1c")
676 text.see(pos)
677
Steven M. Gavab1585412002-03-12 00:21:56 +0000678 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000679 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000680 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000681 fontWeight='normal'
682 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
683 fontWeight='bold'
684 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
685 idleConf.GetOption('main','EditorWindow','font-size'),
686 fontWeight))
687
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000688 def RemoveKeybindings(self):
689 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000690 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000691 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000692 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000693 self.text.event_delete(event, *keylist)
694 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000695 xkeydefs = idleConf.GetExtensionBindings(extensionName)
696 if xkeydefs:
697 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000698 self.text.event_delete(event, *keylist)
699
700 def ApplyKeybindings(self):
701 "Update the keybindings after they are changed"
702 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000703 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000704 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000705 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000706 xkeydefs = idleConf.GetExtensionBindings(extensionName)
707 if xkeydefs:
708 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000709 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000710 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000711 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000712 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000713 for item in menu[1]:
714 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000715 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000716 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000717 menu = self.menudict[menubarItem]
718 end = menu.index(END) + 1
719 for index in range(0, end):
720 if menu.type(index) == 'command':
721 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000722 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000723 itemName = menu.entrycget(index, 'label')
724 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000725 if menubarItem in menuEventDict:
726 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000727 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000728 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000729 accel = get_accelerator(keydefs, event)
730 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000731
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000732 def set_notabs_indentwidth(self):
733 "Update the indentwidth if changed and not using tabs in this window"
734 # Called from configDialog.py
735 if not self.usetabs:
736 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
737 type='int')
738
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000739 def reset_help_menu_entries(self):
740 "Update the additional help entries on the Help menu"
741 help_list = idleConf.GetAllExtraHelpSourcesList()
742 helpmenu = self.menudict['help']
743 # first delete the extra help entries, if any
744 helpmenu_length = helpmenu.index(END)
745 if helpmenu_length > self.base_helpmenu_length:
746 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
747 # then rebuild them
748 if help_list:
749 helpmenu.add_separator()
750 for entry in help_list:
751 cmd = self.__extra_help_callback(entry[1])
752 helpmenu.add_command(label=entry[0], command=cmd)
753 # and update the menu dictionary
754 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000755
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000756 def __extra_help_callback(self, helpfile):
757 "Create a callback with the helpfile value frozen at definition time"
758 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000759 if not helpfile.startswith(('www', 'http')):
Terry Reedy6739cc02011-01-01 02:25:36 +0000760 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000761 if sys.platform[:3] == 'win':
Terry Reedy6739cc02011-01-01 02:25:36 +0000762 try:
763 os.startfile(helpfile)
764 except WindowsError as why:
765 tkMessageBox.showerror(title='Document Start Failure',
766 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000767 else:
768 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000769 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000770
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000771 def update_recent_files_list(self, new_file=None):
772 "Load and update the recent files list and menus"
773 rf_list = []
774 if os.path.exists(self.recent_files_path):
775 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000776 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000777 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000778 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000779 rf_list_file.close()
780 if new_file:
781 new_file = os.path.abspath(new_file) + '\n'
782 if new_file in rf_list:
783 rf_list.remove(new_file) # move to top
784 rf_list.insert(0, new_file)
785 # clean and save the recent files list
786 bad_paths = []
787 for path in rf_list:
788 if '\0' in path or not os.path.exists(path[0:-1]):
789 bad_paths.append(path)
790 rf_list = [path for path in rf_list if path not in bad_paths]
791 ulchars = "1234567890ABCDEFGHIJK"
792 rf_list = rf_list[0:len(ulchars)]
793 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000794 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000795 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000796 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000797 rf_file.close()
798 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000799 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000800 menu = instance.recent_files_menu
801 menu.delete(1, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000802 for i, file_name in enumerate(rf_list):
803 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000804 # make unicode string to display non-ASCII chars correctly
805 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000806 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000807 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000808 command=callback,
809 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000810
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000811 def __recent_file_callback(self, file_name):
812 def open_recent_file(fn_closure=file_name):
813 self.io.open(editFile=fn_closure)
814 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000815
David Scherer7aced172000-08-15 01:13:23 +0000816 def saved_change_hook(self):
817 short = self.short_title()
818 long = self.long_title()
819 if short and long:
820 title = short + " - " + long
821 elif short:
822 title = short
823 elif long:
824 title = long
825 else:
826 title = "Untitled"
827 icon = short or long or title
828 if not self.get_saved():
829 title = "*%s*" % title
830 icon = "*%s" % icon
831 self.top.wm_title(title)
832 self.top.wm_iconname(icon)
833
834 def get_saved(self):
835 return self.undo.get_saved()
836
837 def set_saved(self, flag):
838 self.undo.set_saved(flag)
839
840 def reset_undo(self):
841 self.undo.reset_undo()
842
843 def short_title(self):
844 filename = self.io.filename
845 if filename:
846 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000847 # return unicode string to display non-ASCII chars correctly
848 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000849
850 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000851 # return unicode string to display non-ASCII chars correctly
852 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000853
854 def center_insert_event(self, event):
855 self.center()
856
857 def center(self, mark="insert"):
858 text = self.text
859 top, bot = self.getwindowlines()
860 lineno = self.getlineno(mark)
861 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000862 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000863 text.yview(float(newtop))
864
865 def getwindowlines(self):
866 text = self.text
867 top = self.getlineno("@0,0")
868 bot = self.getlineno("@0,65535")
869 if top == bot and text.winfo_height() == 1:
870 # Geometry manager hasn't run yet
871 height = int(text['height'])
872 bot = top + height - 1
873 return top, bot
874
875 def getlineno(self, mark="insert"):
876 text = self.text
877 return int(float(text.index(mark)))
878
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000879 def get_geometry(self):
880 "Return (width, height, x, y)"
881 geom = self.top.wm_geometry()
882 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000883 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000884
David Scherer7aced172000-08-15 01:13:23 +0000885 def close_event(self, event):
886 self.close()
887
888 def maybesave(self):
889 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000890 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000891 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000892 self.top.deiconify()
893 self.top.lower()
894 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000895 return self.io.maybesave()
896
897 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000898 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000899 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000900 self._close()
901 return reply
902
903 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000904 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000905 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000906 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000907 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000908 self.io.close()
909 self.io = None
910 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000911 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000912 self.color.close(False)
913 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000914 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000915 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000916 self.per.close()
917 self.per = None
918 self.top.destroy()
919 if self.close_hook:
920 # unless override: unregister from flist, terminate if last window
921 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000922
923 def load_extensions(self):
924 self.extensions = {}
925 self.load_standard_extensions()
926
927 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000928 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000929 if hasattr(ins, "close"):
930 ins.close()
931 self.extensions = {}
932
933 def load_standard_extensions(self):
934 for name in self.get_standard_extension_names():
935 try:
936 self.load_extension(name)
937 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000938 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000939 traceback.print_exc()
940
941 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000942 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000943
944 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000945 try:
946 mod = __import__(name, globals(), locals(), [])
947 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000948 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000949 raise
David Scherer7aced172000-08-15 01:13:23 +0000950 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000951 keydefs = idleConf.GetExtensionBindings(name)
952 if hasattr(cls, "menudefs"):
953 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000954 ins = cls(self)
955 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000956 if keydefs:
957 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000958 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000959 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000960 while methodname[:1] == '<':
961 methodname = methodname[1:]
962 while methodname[-1:] == '>':
963 methodname = methodname[:-1]
964 methodname = methodname + "_event"
965 if hasattr(ins, methodname):
966 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000967
968 def apply_bindings(self, keydefs=None):
969 if keydefs is None:
970 keydefs = self.Bindings.default_keydefs
971 text = self.text
972 text.keydefs = keydefs
973 for event, keylist in keydefs.items():
974 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000975 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000976
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000977 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000978 """Add appropriate entries to the menus and submenus
979
980 Menus that are absent or None in self.menudict are ignored.
981 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000982 if menudefs is None:
983 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000984 if keydefs is None:
985 keydefs = self.Bindings.default_keydefs
986 menudict = self.menudict
987 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000988 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000989 menu = menudict.get(mname)
990 if not menu:
991 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000992 for entry in entrylist:
993 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000994 menu.add_separator()
995 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000996 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000997 checkbutton = (label[:1] == '!')
998 if checkbutton:
999 label = label[1:]
1000 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001001 accelerator = get_accelerator(keydefs, eventname)
1002 def command(text=text, eventname=eventname):
1003 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001004 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001005 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001006 menu.add_checkbutton(label=label, underline=underline,
1007 command=command, accelerator=accelerator,
1008 variable=var)
1009 else:
1010 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001011 command=command,
1012 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001013
1014 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001015 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001016 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001017 value = var.get()
1018 return value
1019 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001020 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001021
1022 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001023 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001024 if var:
1025 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001026 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001027 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001028
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001029 def get_var_obj(self, name, vartype=None):
1030 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001031 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001032 # create a Tkinter variable object with self.text as master:
1033 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001034 return var
1035
1036 # Tk implementations of "virtual text methods" -- each platform
1037 # reusing IDLE's support code needs to define these for its GUI's
1038 # flavor of widget.
1039
1040 # Is character at text_index in a Python string? Return 0 for
1041 # "guaranteed no", true for anything else. This info is expensive
1042 # to compute ab initio, but is probably already known by the
1043 # platform's colorizer.
1044
1045 def is_char_in_string(self, text_index):
1046 if self.color:
1047 # Return true iff colorizer hasn't (re)gotten this far
1048 # yet, or the character is tagged as being in a string
1049 return self.text.tag_prevrange("TODO", text_index) or \
1050 "STRING" in self.text.tag_names(text_index)
1051 else:
1052 # The colorizer is missing: assume the worst
1053 return 1
1054
1055 # If a selection is defined in the text widget, return (start,
1056 # end) as Tkinter text indices, otherwise return (None, None)
1057 def get_selection_indices(self):
1058 try:
1059 first = self.text.index("sel.first")
1060 last = self.text.index("sel.last")
1061 return first, last
1062 except TclError:
1063 return None, None
1064
1065 # Return the text widget's current view of what a tab stop means
1066 # (equivalent width in spaces).
1067
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001068 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001069 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1070 return int(current)
1071
1072 # Set the text widget's current view of what a tab stop means.
1073
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001074 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001075 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001076 if self.get_tk_tabwidth() != newtabwidth:
1077 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001078 pixels = text.tk.call("font", "measure", text["font"],
1079 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001080 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001081 text.configure(tabs=pixels)
1082
Guido van Rossum33d26892007-08-05 15:29:28 +00001083### begin autoindent code ### (configuration was moved to beginning of class)
1084
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001085 def set_indentation_params(self, is_py_src, guess=True):
1086 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001087 i = self.guess_indent()
1088 if 2 <= i <= 8:
1089 self.indentwidth = i
1090 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001091 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001092 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001093
1094 def smart_backspace_event(self, event):
1095 text = self.text
1096 first, last = self.get_selection_indices()
1097 if first and last:
1098 text.delete(first, last)
1099 text.mark_set("insert", first)
1100 return "break"
1101 # Delete whitespace left, until hitting a real char or closest
1102 # preceding virtual tab stop.
1103 chars = text.get("insert linestart", "insert")
1104 if chars == '':
1105 if text.compare("insert", ">", "1.0"):
1106 # easy: delete preceding newline
1107 text.delete("insert-1c")
1108 else:
1109 text.bell() # at start of buffer
1110 return "break"
1111 if chars[-1] not in " \t":
1112 # easy: delete preceding real char
1113 text.delete("insert-1c")
1114 return "break"
1115 # Ick. It may require *inserting* spaces if we back up over a
1116 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001117 tabwidth = self.tabwidth
1118 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001119 assert have > 0
1120 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001121 # Debug prompt is multilined....
1122 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001123 ncharsdeleted = 0
1124 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001125 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001126 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001127 chars = chars[:-1]
1128 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001129 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001130 if have <= want or chars[-1] not in " \t":
1131 break
1132 text.undo_block_start()
1133 text.delete("insert-%dc" % ncharsdeleted, "insert")
1134 if have < want:
1135 text.insert("insert", ' ' * (want - have))
1136 text.undo_block_stop()
1137 return "break"
1138
1139 def smart_indent_event(self, event):
1140 # if intraline selection:
1141 # delete it
1142 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001143 # do indent-region
1144 # else:
1145 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001146 text = self.text
1147 first, last = self.get_selection_indices()
1148 text.undo_block_start()
1149 try:
1150 if first and last:
1151 if index2line(first) != index2line(last):
1152 return self.indent_region_event(event)
1153 text.delete(first, last)
1154 text.mark_set("insert", first)
1155 prefix = text.get("insert linestart", "insert")
1156 raw, effective = classifyws(prefix, self.tabwidth)
1157 if raw == len(prefix):
1158 # only whitespace to the left
1159 self.reindent_to(effective + self.indentwidth)
1160 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001161 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001162 if self.usetabs:
1163 pad = '\t'
1164 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001165 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001166 n = self.indentwidth
1167 pad = ' ' * (n - effective % n)
1168 text.insert("insert", pad)
1169 text.see("insert")
1170 return "break"
1171 finally:
1172 text.undo_block_stop()
1173
1174 def newline_and_indent_event(self, event):
1175 text = self.text
1176 first, last = self.get_selection_indices()
1177 text.undo_block_start()
1178 try:
1179 if first and last:
1180 text.delete(first, last)
1181 text.mark_set("insert", first)
1182 line = text.get("insert linestart", "insert")
1183 i, n = 0, len(line)
1184 while i < n and line[i] in " \t":
1185 i = i+1
1186 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001187 # the cursor is in or at leading indentation in a continuation
1188 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001189 text.insert("insert linestart", '\n')
1190 return "break"
1191 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001192 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001193 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001194 last_line_of_prompt = sys.ps1.split('\n')[-1]
1195 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001196 line = line[:-1]
1197 i = i+1
1198 if i:
1199 text.delete("insert - %d chars" % i, "insert")
1200 # strip whitespace after insert point
1201 while text.get("insert") in " \t":
1202 text.delete("insert")
1203 # start new line
1204 text.insert("insert", '\n')
1205
1206 # adjust indentation for continuations and block
1207 # open/close first need to find the last stmt
1208 lno = index2line(text.index('insert'))
1209 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001210 if not self.context_use_ps1:
1211 for context in self.num_context_lines:
1212 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001213 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001214 rawtext = text.get(startatindex, "insert")
1215 y.set_str(rawtext)
1216 bod = y.find_good_parse_start(
1217 self.context_use_ps1,
1218 self._build_char_in_string_func(startatindex))
1219 if bod is not None or startat == 1:
1220 break
1221 y.set_lo(bod or 0)
1222 else:
1223 r = text.tag_prevrange("console", "insert")
1224 if r:
1225 startatindex = r[1]
1226 else:
1227 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001228 rawtext = text.get(startatindex, "insert")
1229 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001230 y.set_lo(0)
1231
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001232 c = y.get_continuation_type()
1233 if c != PyParse.C_NONE:
1234 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001235 if c == PyParse.C_STRING_FIRST_LINE:
1236 # after the first line of a string; do not indent at all
1237 pass
1238 elif c == PyParse.C_STRING_NEXT_LINES:
1239 # inside a string which started before this line;
1240 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001241 text.insert("insert", indent)
1242 elif c == PyParse.C_BRACKET:
1243 # line up with the first (if any) element of the
1244 # last open bracket structure; else indent one
1245 # level beyond the indent of the line with the
1246 # last open bracket
1247 self.reindent_to(y.compute_bracket_indent())
1248 elif c == PyParse.C_BACKSLASH:
1249 # if more than one line in this stmt already, just
1250 # mimic the current indent; else if initial line
1251 # has a start on an assignment stmt, indent to
1252 # beyond leftmost =; else to beyond first chunk of
1253 # non-whitespace on initial line
1254 if y.get_num_lines_in_stmt() > 1:
1255 text.insert("insert", indent)
1256 else:
1257 self.reindent_to(y.compute_backslash_indent())
1258 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001259 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001260 return "break"
1261
1262 # This line starts a brand new stmt; indent relative to
1263 # indentation of initial line of closest preceding
1264 # interesting stmt.
1265 indent = y.get_base_indent_string()
1266 text.insert("insert", indent)
1267 if y.is_block_opener():
1268 self.smart_indent_event(event)
1269 elif indent and y.is_block_closer():
1270 self.smart_backspace_event(event)
1271 return "break"
1272 finally:
1273 text.see("insert")
1274 text.undo_block_stop()
1275
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001276 # Our editwin provides a is_char_in_string function that works
1277 # with a Tk text index, but PyParse only knows about offsets into
1278 # a string. This builds a function for PyParse that accepts an
1279 # offset.
1280
1281 def _build_char_in_string_func(self, startindex):
1282 def inner(offset, _startindex=startindex,
1283 _icis=self.is_char_in_string):
1284 return _icis(_startindex + "+%dc" % offset)
1285 return inner
1286
1287 def indent_region_event(self, event):
1288 head, tail, chars, lines = self.get_region()
1289 for pos in range(len(lines)):
1290 line = lines[pos]
1291 if line:
1292 raw, effective = classifyws(line, self.tabwidth)
1293 effective = effective + self.indentwidth
1294 lines[pos] = self._make_blanks(effective) + line[raw:]
1295 self.set_region(head, tail, chars, lines)
1296 return "break"
1297
1298 def dedent_region_event(self, event):
1299 head, tail, chars, lines = self.get_region()
1300 for pos in range(len(lines)):
1301 line = lines[pos]
1302 if line:
1303 raw, effective = classifyws(line, self.tabwidth)
1304 effective = max(effective - self.indentwidth, 0)
1305 lines[pos] = self._make_blanks(effective) + line[raw:]
1306 self.set_region(head, tail, chars, lines)
1307 return "break"
1308
1309 def comment_region_event(self, event):
1310 head, tail, chars, lines = self.get_region()
1311 for pos in range(len(lines) - 1):
1312 line = lines[pos]
1313 lines[pos] = '##' + line
1314 self.set_region(head, tail, chars, lines)
1315
1316 def uncomment_region_event(self, event):
1317 head, tail, chars, lines = self.get_region()
1318 for pos in range(len(lines)):
1319 line = lines[pos]
1320 if not line:
1321 continue
1322 if line[:2] == '##':
1323 line = line[2:]
1324 elif line[:1] == '#':
1325 line = line[1:]
1326 lines[pos] = line
1327 self.set_region(head, tail, chars, lines)
1328
1329 def tabify_region_event(self, event):
1330 head, tail, chars, lines = self.get_region()
1331 tabwidth = self._asktabwidth()
1332 for pos in range(len(lines)):
1333 line = lines[pos]
1334 if line:
1335 raw, effective = classifyws(line, tabwidth)
1336 ntabs, nspaces = divmod(effective, tabwidth)
1337 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1338 self.set_region(head, tail, chars, lines)
1339
1340 def untabify_region_event(self, event):
1341 head, tail, chars, lines = self.get_region()
1342 tabwidth = self._asktabwidth()
1343 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001344 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001345 self.set_region(head, tail, chars, lines)
1346
1347 def toggle_tabs_event(self, event):
1348 if self.askyesno(
1349 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001350 "Turn tabs " + ("on", "off")[self.usetabs] +
1351 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001352 ("will be", "remains at")[self.usetabs] + " 8." +
1353 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001354 parent=self.text):
1355 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001356 # Try to prevent inconsistent indentation.
1357 # User must change indent width manually after using tabs.
1358 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001359 return "break"
1360
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001361 # XXX this isn't bound to anything -- see tabwidth comments
1362## def change_tabwidth_event(self, event):
1363## new = self._asktabwidth()
1364## if new != self.tabwidth:
1365## self.tabwidth = new
1366## self.set_indentation_params(0, guess=0)
1367## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001368
1369 def change_indentwidth_event(self, event):
1370 new = self.askinteger(
1371 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001372 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001373 parent=self.text,
1374 initialvalue=self.indentwidth,
1375 minvalue=2,
1376 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001377 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001378 self.indentwidth = new
1379 return "break"
1380
1381 def get_region(self):
1382 text = self.text
1383 first, last = self.get_selection_indices()
1384 if first and last:
1385 head = text.index(first + " linestart")
1386 tail = text.index(last + "-1c lineend +1c")
1387 else:
1388 head = text.index("insert linestart")
1389 tail = text.index("insert lineend +1c")
1390 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001391 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001392 return head, tail, chars, lines
1393
1394 def set_region(self, head, tail, chars, lines):
1395 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001396 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001397 if newchars == chars:
1398 text.bell()
1399 return
1400 text.tag_remove("sel", "1.0", "end")
1401 text.mark_set("insert", head)
1402 text.undo_block_start()
1403 text.delete(head, tail)
1404 text.insert(head, newchars)
1405 text.undo_block_stop()
1406 text.tag_add("sel", head, "insert")
1407
1408 # Make string that displays as n leading blanks.
1409
1410 def _make_blanks(self, n):
1411 if self.usetabs:
1412 ntabs, nspaces = divmod(n, self.tabwidth)
1413 return '\t' * ntabs + ' ' * nspaces
1414 else:
1415 return ' ' * n
1416
1417 # Delete from beginning of line to insert point, then reinsert
1418 # column logical (meaning use tabs if appropriate) spaces.
1419
1420 def reindent_to(self, column):
1421 text = self.text
1422 text.undo_block_start()
1423 if text.compare("insert linestart", "!=", "insert"):
1424 text.delete("insert linestart", "insert")
1425 if column:
1426 text.insert("insert", self._make_blanks(column))
1427 text.undo_block_stop()
1428
1429 def _asktabwidth(self):
1430 return self.askinteger(
1431 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001432 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001433 parent=self.text,
1434 initialvalue=self.indentwidth,
1435 minvalue=2,
1436 maxvalue=16) or self.tabwidth
1437
1438 # Guess indentwidth from text content.
1439 # Return guessed indentwidth. This should not be believed unless
1440 # it's in a reasonable range (e.g., it will be 0 if no indented
1441 # blocks are found).
1442
1443 def guess_indent(self):
1444 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1445 if opener and indented:
1446 raw, indentsmall = classifyws(opener, self.tabwidth)
1447 raw, indentlarge = classifyws(indented, self.tabwidth)
1448 else:
1449 indentsmall = indentlarge = 0
1450 return indentlarge - indentsmall
1451
1452# "line.col" -> line, as an int
1453def index2line(index):
1454 return int(float(index))
1455
1456# Look at the leading whitespace in s.
1457# Return pair (# of leading ws characters,
1458# effective # of leading blanks after expanding
1459# tabs to width tabwidth)
1460
1461def classifyws(s, tabwidth):
1462 raw = effective = 0
1463 for ch in s:
1464 if ch == ' ':
1465 raw = raw + 1
1466 effective = effective + 1
1467 elif ch == '\t':
1468 raw = raw + 1
1469 effective = (effective // tabwidth + 1) * tabwidth
1470 else:
1471 break
1472 return raw, effective
1473
1474import tokenize
1475_tokenize = tokenize
1476del tokenize
1477
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001478class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001479
1480 # .run() chews over the Text widget, looking for a block opener
1481 # and the stmt following it. Returns a pair,
1482 # (line containing block opener, line containing stmt)
1483 # Either or both may be None.
1484
1485 def __init__(self, text, tabwidth):
1486 self.text = text
1487 self.tabwidth = tabwidth
1488 self.i = self.finished = 0
1489 self.blkopenline = self.indentedline = None
1490
1491 def readline(self):
1492 if self.finished:
1493 return ""
1494 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001495 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001496 if self.text.compare(mark, ">=", "end"):
1497 return ""
1498 return self.text.get(mark, mark + " lineend+1c")
1499
1500 def tokeneater(self, type, token, start, end, line,
1501 INDENT=_tokenize.INDENT,
1502 NAME=_tokenize.NAME,
1503 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1504 if self.finished:
1505 pass
1506 elif type == NAME and token in OPENERS:
1507 self.blkopenline = line
1508 elif type == INDENT and self.blkopenline:
1509 self.indentedline = line
1510 self.finished = 1
1511
1512 def run(self):
1513 save_tabsize = _tokenize.tabsize
1514 _tokenize.tabsize = self.tabwidth
1515 try:
1516 try:
Trent Nelson428de652008-03-18 22:41:35 +00001517 tokens = _tokenize.generate_tokens(self.readline)
1518 for token in tokens:
1519 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001520 except _tokenize.TokenError:
1521 # since we cut off the tokenizer early, we can trigger
1522 # spurious errors
1523 pass
1524 finally:
1525 _tokenize.tabsize = save_tabsize
1526 return self.blkopenline, self.indentedline
1527
1528### end autoindent code ###
1529
David Scherer7aced172000-08-15 01:13:23 +00001530def prepstr(s):
1531 # Helper to extract the underscore from a string, e.g.
1532 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001533 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001534 if i >= 0:
1535 s = s[:i] + s[i+1:]
1536 return i, s
1537
1538
1539keynames = {
1540 'bracketleft': '[',
1541 'bracketright': ']',
1542 'slash': '/',
1543}
1544
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001545def get_accelerator(keydefs, eventname):
1546 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001547 if not keylist:
1548 return ""
1549 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001550 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001551 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1552 s = re.sub("Key-", "", s)
1553 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1554 s = re.sub("Control-", "Ctrl-", s)
1555 s = re.sub("-", "+", s)
1556 s = re.sub("><", " ", s)
1557 s = re.sub("<", "", s)
1558 s = re.sub(">", "", s)
1559 return s
1560
1561
1562def fixwordbreaks(root):
1563 # Make sure that Tk's double-click and next/previous word
1564 # operations use our definition of a word (i.e. an identifier)
1565 tk = root.tk
1566 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1567 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1568 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1569
1570
1571def test():
1572 root = Tk()
1573 fixwordbreaks(root)
1574 root.withdraw()
1575 if sys.argv[1:]:
1576 filename = sys.argv[1]
1577 else:
1578 filename = None
1579 edit = EditorWindow(root=root, filename=filename)
1580 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001581 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001582 root.mainloop()
1583 root.destroy()
1584
1585if __name__ == '__main__':
1586 test()