blob: b89214cffbabb17a3759347210970ecdc1d9ae38 [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':
Steven M. Gava931625d2002-04-22 00:38:26 +0000453 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000454 else:
455 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000456 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000457
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000458 def cut(self,event):
459 self.text.event_generate("<<Cut>>")
460 return "break"
461
462 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000463 if not self.text.tag_ranges("sel"):
464 # There is no selection, so do nothing and maybe interrupt.
465 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000466 self.text.event_generate("<<Copy>>")
467 return "break"
468
469 def paste(self,event):
470 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000471 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000472 return "break"
473
David Scherer7aced172000-08-15 01:13:23 +0000474 def select_all(self, event=None):
475 self.text.tag_add("sel", "1.0", "end-1c")
476 self.text.mark_set("insert", "1.0")
477 self.text.see("insert")
478 return "break"
479
480 def remove_selection(self, event=None):
481 self.text.tag_remove("sel", "1.0", "end")
482 self.text.see("insert")
483
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000484 def move_at_edge_if_selection(self, edge_index):
485 """Cursor move begins at start or end of selection
486
487 When a left/right cursor key is pressed create and return to Tkinter a
488 function which causes a cursor move from the associated edge of the
489 selection.
490
491 """
492 self_text_index = self.text.index
493 self_text_mark_set = self.text.mark_set
494 edges_table = ("sel.first+1c", "sel.last-1c")
495 def move_at_edge(event):
496 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
497 try:
498 self_text_index("sel.first")
499 self_text_mark_set("insert", edges_table[edge_index])
500 except TclError:
501 pass
502 return move_at_edge
503
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000504 def del_word_left(self, event):
505 self.text.event_generate('<Meta-Delete>')
506 return "break"
507
508 def del_word_right(self, event):
509 self.text.event_generate('<Meta-d>')
510 return "break"
511
Steven M. Gavac5976402002-01-04 03:06:08 +0000512 def find_event(self, event):
513 SearchDialog.find(self.text)
514 return "break"
515
516 def find_again_event(self, event):
517 SearchDialog.find_again(self.text)
518 return "break"
519
520 def find_selection_event(self, event):
521 SearchDialog.find_selection(self.text)
522 return "break"
523
524 def find_in_files_event(self, event):
525 GrepDialog.grep(self.text, self.io, self.flist)
526 return "break"
527
528 def replace_event(self, event):
529 ReplaceDialog.replace(self.text)
530 return "break"
531
532 def goto_line_event(self, event):
533 text = self.text
534 lineno = tkSimpleDialog.askinteger("Goto",
535 "Go to line number:",parent=text)
536 if lineno is None:
537 return "break"
538 if lineno <= 0:
539 text.bell()
540 return "break"
541 text.mark_set("insert", "%d.0" % lineno)
542 text.see("insert")
543
David Scherer7aced172000-08-15 01:13:23 +0000544 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000545 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000546 try:
547 name = self.text.get("sel.first", "sel.last")
548 except TclError:
549 name = ""
550 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000551 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000552 name = tkSimpleDialog.askstring("Module",
553 "Enter the name of a Python module\n"
554 "to search on sys.path and open:",
555 parent=self.text, initialvalue=name)
556 if name:
557 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000558 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000559 return
David Scherer7aced172000-08-15 01:13:23 +0000560 # XXX Ought to insert current file's directory in front of path
561 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000562 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000563 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000564 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
565 return
566 if type != imp.PY_SOURCE:
567 tkMessageBox.showerror("Unsupported type",
568 "%s is not a source module" % name, parent=self.text)
569 return
570 if f:
571 f.close()
572 if self.flist:
573 self.flist.open(file)
574 else:
575 self.io.loadfile(file)
576
577 def open_class_browser(self, event=None):
578 filename = self.io.filename
579 if not filename:
580 tkMessageBox.showerror(
581 "No filename",
582 "This buffer has no associated filename",
583 master=self.text)
584 self.text.focus_set()
585 return None
586 head, tail = os.path.split(filename)
587 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000588 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000589 ClassBrowser.ClassBrowser(self.flist, base, [head])
590
591 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000592 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000593 PathBrowser.PathBrowser(self.flist)
594
595 def gotoline(self, lineno):
596 if lineno is not None and lineno > 0:
597 self.text.mark_set("insert", "%d.0" % lineno)
598 self.text.tag_remove("sel", "1.0", "end")
599 self.text.tag_add("sel", "insert", "insert +1l")
600 self.center()
601
602 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000603 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000604 return True
David Scherer7aced172000-08-15 01:13:23 +0000605 base, ext = os.path.splitext(os.path.basename(filename))
606 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000607 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000608 line = self.text.get('1.0', '1.0 lineend')
609 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000610
611 def close_hook(self):
612 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000613 self.flist.unregister_maybe_terminate(self)
614 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000615
616 def set_close_hook(self, close_hook):
617 self.close_hook = close_hook
618
619 def filename_change_hook(self):
620 if self.flist:
621 self.flist.filename_changed_edit(self)
622 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000623 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000624 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000625
Christian Heimesa156e092008-02-16 07:38:31 +0000626 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000627 if self.color:
628 return
Christian Heimesa156e092008-02-16 07:38:31 +0000629 if self.ispythonsource(self.io.filename):
630 self.color = self.ColorDelegator()
631 # can add more colorizers here...
632 if self.color:
633 self.per.removefilter(self.undo)
634 self.per.insertfilter(self.color)
635 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000636
Christian Heimesa156e092008-02-16 07:38:31 +0000637 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000638 if not self.color:
639 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000640 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000641 self.per.removefilter(self.color)
642 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000643
Steven M. Gavab77d3432002-03-02 07:16:21 +0000644 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000645 "Update the colour theme"
646 # Called from self.filename_change_hook and from configDialog.py
647 self._rmcolorizer()
648 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000649 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000650 normal_colors = idleConf.GetHighlight(theme, 'normal')
651 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
652 select_colors = idleConf.GetHighlight(theme, 'hilite')
653 self.text.config(
654 foreground=normal_colors['foreground'],
655 background=normal_colors['background'],
656 insertbackground=cursor_color,
657 selectforeground=select_colors['foreground'],
658 selectbackground=select_colors['background'],
659 )
David Scherer7aced172000-08-15 01:13:23 +0000660
Guido van Rossum33d26892007-08-05 15:29:28 +0000661 IDENTCHARS = string.ascii_letters + string.digits + "_"
662
663 def colorize_syntax_error(self, text, pos):
664 text.tag_add("ERROR", pos)
665 char = text.get(pos)
666 if char and char in self.IDENTCHARS:
667 text.tag_add("ERROR", pos + " wordstart", pos)
668 if '\n' == text.get(pos): # error at line end
669 text.mark_set("insert", pos)
670 else:
671 text.mark_set("insert", pos + "+1c")
672 text.see(pos)
673
Steven M. Gavab1585412002-03-12 00:21:56 +0000674 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000675 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000676 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000677 fontWeight='normal'
678 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
679 fontWeight='bold'
680 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
681 idleConf.GetOption('main','EditorWindow','font-size'),
682 fontWeight))
683
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000684 def RemoveKeybindings(self):
685 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000686 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000687 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000688 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000689 self.text.event_delete(event, *keylist)
690 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000691 xkeydefs = idleConf.GetExtensionBindings(extensionName)
692 if xkeydefs:
693 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000694 self.text.event_delete(event, *keylist)
695
696 def ApplyKeybindings(self):
697 "Update the keybindings after they are changed"
698 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000699 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000700 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000701 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000702 xkeydefs = idleConf.GetExtensionBindings(extensionName)
703 if xkeydefs:
704 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000705 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000706 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000707 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000708 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000709 for item in menu[1]:
710 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000711 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000712 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000713 menu = self.menudict[menubarItem]
714 end = menu.index(END) + 1
715 for index in range(0, end):
716 if menu.type(index) == 'command':
717 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000718 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000719 itemName = menu.entrycget(index, 'label')
720 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000721 if menubarItem in menuEventDict:
722 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000723 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000724 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000725 accel = get_accelerator(keydefs, event)
726 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000727
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000728 def set_notabs_indentwidth(self):
729 "Update the indentwidth if changed and not using tabs in this window"
730 # Called from configDialog.py
731 if not self.usetabs:
732 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
733 type='int')
734
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000735 def reset_help_menu_entries(self):
736 "Update the additional help entries on the Help menu"
737 help_list = idleConf.GetAllExtraHelpSourcesList()
738 helpmenu = self.menudict['help']
739 # first delete the extra help entries, if any
740 helpmenu_length = helpmenu.index(END)
741 if helpmenu_length > self.base_helpmenu_length:
742 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
743 # then rebuild them
744 if help_list:
745 helpmenu.add_separator()
746 for entry in help_list:
747 cmd = self.__extra_help_callback(entry[1])
748 helpmenu.add_command(label=entry[0], command=cmd)
749 # and update the menu dictionary
750 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000751
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000752 def __extra_help_callback(self, helpfile):
753 "Create a callback with the helpfile value frozen at definition time"
754 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000755 if not helpfile.startswith(('www', 'http')):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000756 url = os.path.normpath(helpfile)
757 if sys.platform[:3] == 'win':
758 os.startfile(helpfile)
759 else:
760 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000761 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000762
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000763 def update_recent_files_list(self, new_file=None):
764 "Load and update the recent files list and menus"
765 rf_list = []
766 if os.path.exists(self.recent_files_path):
767 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000768 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000769 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000770 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000771 rf_list_file.close()
772 if new_file:
773 new_file = os.path.abspath(new_file) + '\n'
774 if new_file in rf_list:
775 rf_list.remove(new_file) # move to top
776 rf_list.insert(0, new_file)
777 # clean and save the recent files list
778 bad_paths = []
779 for path in rf_list:
780 if '\0' in path or not os.path.exists(path[0:-1]):
781 bad_paths.append(path)
782 rf_list = [path for path in rf_list if path not in bad_paths]
783 ulchars = "1234567890ABCDEFGHIJK"
784 rf_list = rf_list[0:len(ulchars)]
785 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000786 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000787 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000788 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000789 rf_file.close()
790 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000791 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000792 menu = instance.recent_files_menu
793 menu.delete(1, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000794 for i, file_name in enumerate(rf_list):
795 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000796 # make unicode string to display non-ASCII chars correctly
797 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000798 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000799 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000800 command=callback,
801 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000802
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000803 def __recent_file_callback(self, file_name):
804 def open_recent_file(fn_closure=file_name):
805 self.io.open(editFile=fn_closure)
806 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000807
David Scherer7aced172000-08-15 01:13:23 +0000808 def saved_change_hook(self):
809 short = self.short_title()
810 long = self.long_title()
811 if short and long:
812 title = short + " - " + long
813 elif short:
814 title = short
815 elif long:
816 title = long
817 else:
818 title = "Untitled"
819 icon = short or long or title
820 if not self.get_saved():
821 title = "*%s*" % title
822 icon = "*%s" % icon
823 self.top.wm_title(title)
824 self.top.wm_iconname(icon)
825
826 def get_saved(self):
827 return self.undo.get_saved()
828
829 def set_saved(self, flag):
830 self.undo.set_saved(flag)
831
832 def reset_undo(self):
833 self.undo.reset_undo()
834
835 def short_title(self):
836 filename = self.io.filename
837 if filename:
838 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000839 # return unicode string to display non-ASCII chars correctly
840 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000841
842 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000843 # return unicode string to display non-ASCII chars correctly
844 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000845
846 def center_insert_event(self, event):
847 self.center()
848
849 def center(self, mark="insert"):
850 text = self.text
851 top, bot = self.getwindowlines()
852 lineno = self.getlineno(mark)
853 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000854 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000855 text.yview(float(newtop))
856
857 def getwindowlines(self):
858 text = self.text
859 top = self.getlineno("@0,0")
860 bot = self.getlineno("@0,65535")
861 if top == bot and text.winfo_height() == 1:
862 # Geometry manager hasn't run yet
863 height = int(text['height'])
864 bot = top + height - 1
865 return top, bot
866
867 def getlineno(self, mark="insert"):
868 text = self.text
869 return int(float(text.index(mark)))
870
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000871 def get_geometry(self):
872 "Return (width, height, x, y)"
873 geom = self.top.wm_geometry()
874 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000875 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000876
David Scherer7aced172000-08-15 01:13:23 +0000877 def close_event(self, event):
878 self.close()
879
880 def maybesave(self):
881 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000882 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000883 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000884 self.top.deiconify()
885 self.top.lower()
886 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000887 return self.io.maybesave()
888
889 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000890 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000891 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000892 self._close()
893 return reply
894
895 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000896 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000897 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000898 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000899 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000900 self.io.close()
901 self.io = None
902 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000903 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000904 self.color.close(False)
905 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000906 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000907 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000908 self.per.close()
909 self.per = None
910 self.top.destroy()
911 if self.close_hook:
912 # unless override: unregister from flist, terminate if last window
913 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000914
915 def load_extensions(self):
916 self.extensions = {}
917 self.load_standard_extensions()
918
919 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000920 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000921 if hasattr(ins, "close"):
922 ins.close()
923 self.extensions = {}
924
925 def load_standard_extensions(self):
926 for name in self.get_standard_extension_names():
927 try:
928 self.load_extension(name)
929 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000930 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000931 traceback.print_exc()
932
933 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000934 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000935
936 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000937 try:
938 mod = __import__(name, globals(), locals(), [])
939 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000940 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000941 raise
David Scherer7aced172000-08-15 01:13:23 +0000942 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000943 keydefs = idleConf.GetExtensionBindings(name)
944 if hasattr(cls, "menudefs"):
945 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000946 ins = cls(self)
947 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000948 if keydefs:
949 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000950 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000951 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000952 while methodname[:1] == '<':
953 methodname = methodname[1:]
954 while methodname[-1:] == '>':
955 methodname = methodname[:-1]
956 methodname = methodname + "_event"
957 if hasattr(ins, methodname):
958 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000959
960 def apply_bindings(self, keydefs=None):
961 if keydefs is None:
962 keydefs = self.Bindings.default_keydefs
963 text = self.text
964 text.keydefs = keydefs
965 for event, keylist in keydefs.items():
966 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000967 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000968
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000969 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000970 """Add appropriate entries to the menus and submenus
971
972 Menus that are absent or None in self.menudict are ignored.
973 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000974 if menudefs is None:
975 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000976 if keydefs is None:
977 keydefs = self.Bindings.default_keydefs
978 menudict = self.menudict
979 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000980 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000981 menu = menudict.get(mname)
982 if not menu:
983 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000984 for entry in entrylist:
985 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000986 menu.add_separator()
987 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000988 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000989 checkbutton = (label[:1] == '!')
990 if checkbutton:
991 label = label[1:]
992 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000993 accelerator = get_accelerator(keydefs, eventname)
994 def command(text=text, eventname=eventname):
995 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000996 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000997 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000998 menu.add_checkbutton(label=label, underline=underline,
999 command=command, accelerator=accelerator,
1000 variable=var)
1001 else:
1002 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001003 command=command,
1004 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001005
1006 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001007 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001008 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001009 value = var.get()
1010 return value
1011 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001012 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001013
1014 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001015 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001016 if var:
1017 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001018 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001019 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001020
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001021 def get_var_obj(self, name, vartype=None):
1022 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001023 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001024 # create a Tkinter variable object with self.text as master:
1025 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001026 return var
1027
1028 # Tk implementations of "virtual text methods" -- each platform
1029 # reusing IDLE's support code needs to define these for its GUI's
1030 # flavor of widget.
1031
1032 # Is character at text_index in a Python string? Return 0 for
1033 # "guaranteed no", true for anything else. This info is expensive
1034 # to compute ab initio, but is probably already known by the
1035 # platform's colorizer.
1036
1037 def is_char_in_string(self, text_index):
1038 if self.color:
1039 # Return true iff colorizer hasn't (re)gotten this far
1040 # yet, or the character is tagged as being in a string
1041 return self.text.tag_prevrange("TODO", text_index) or \
1042 "STRING" in self.text.tag_names(text_index)
1043 else:
1044 # The colorizer is missing: assume the worst
1045 return 1
1046
1047 # If a selection is defined in the text widget, return (start,
1048 # end) as Tkinter text indices, otherwise return (None, None)
1049 def get_selection_indices(self):
1050 try:
1051 first = self.text.index("sel.first")
1052 last = self.text.index("sel.last")
1053 return first, last
1054 except TclError:
1055 return None, None
1056
1057 # Return the text widget's current view of what a tab stop means
1058 # (equivalent width in spaces).
1059
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001060 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001061 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1062 return int(current)
1063
1064 # Set the text widget's current view of what a tab stop means.
1065
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001066 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001067 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001068 if self.get_tk_tabwidth() != newtabwidth:
1069 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001070 pixels = text.tk.call("font", "measure", text["font"],
1071 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001072 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001073 text.configure(tabs=pixels)
1074
Guido van Rossum33d26892007-08-05 15:29:28 +00001075### begin autoindent code ### (configuration was moved to beginning of class)
1076
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001077 def set_indentation_params(self, is_py_src, guess=True):
1078 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001079 i = self.guess_indent()
1080 if 2 <= i <= 8:
1081 self.indentwidth = i
1082 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001083 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001084 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001085
1086 def smart_backspace_event(self, event):
1087 text = self.text
1088 first, last = self.get_selection_indices()
1089 if first and last:
1090 text.delete(first, last)
1091 text.mark_set("insert", first)
1092 return "break"
1093 # Delete whitespace left, until hitting a real char or closest
1094 # preceding virtual tab stop.
1095 chars = text.get("insert linestart", "insert")
1096 if chars == '':
1097 if text.compare("insert", ">", "1.0"):
1098 # easy: delete preceding newline
1099 text.delete("insert-1c")
1100 else:
1101 text.bell() # at start of buffer
1102 return "break"
1103 if chars[-1] not in " \t":
1104 # easy: delete preceding real char
1105 text.delete("insert-1c")
1106 return "break"
1107 # Ick. It may require *inserting* spaces if we back up over a
1108 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001109 tabwidth = self.tabwidth
1110 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001111 assert have > 0
1112 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001113 # Debug prompt is multilined....
1114 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001115 ncharsdeleted = 0
1116 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001117 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001118 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001119 chars = chars[:-1]
1120 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001121 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001122 if have <= want or chars[-1] not in " \t":
1123 break
1124 text.undo_block_start()
1125 text.delete("insert-%dc" % ncharsdeleted, "insert")
1126 if have < want:
1127 text.insert("insert", ' ' * (want - have))
1128 text.undo_block_stop()
1129 return "break"
1130
1131 def smart_indent_event(self, event):
1132 # if intraline selection:
1133 # delete it
1134 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001135 # do indent-region
1136 # else:
1137 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001138 text = self.text
1139 first, last = self.get_selection_indices()
1140 text.undo_block_start()
1141 try:
1142 if first and last:
1143 if index2line(first) != index2line(last):
1144 return self.indent_region_event(event)
1145 text.delete(first, last)
1146 text.mark_set("insert", first)
1147 prefix = text.get("insert linestart", "insert")
1148 raw, effective = classifyws(prefix, self.tabwidth)
1149 if raw == len(prefix):
1150 # only whitespace to the left
1151 self.reindent_to(effective + self.indentwidth)
1152 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001153 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001154 if self.usetabs:
1155 pad = '\t'
1156 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001157 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001158 n = self.indentwidth
1159 pad = ' ' * (n - effective % n)
1160 text.insert("insert", pad)
1161 text.see("insert")
1162 return "break"
1163 finally:
1164 text.undo_block_stop()
1165
1166 def newline_and_indent_event(self, event):
1167 text = self.text
1168 first, last = self.get_selection_indices()
1169 text.undo_block_start()
1170 try:
1171 if first and last:
1172 text.delete(first, last)
1173 text.mark_set("insert", first)
1174 line = text.get("insert linestart", "insert")
1175 i, n = 0, len(line)
1176 while i < n and line[i] in " \t":
1177 i = i+1
1178 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001179 # the cursor is in or at leading indentation in a continuation
1180 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001181 text.insert("insert linestart", '\n')
1182 return "break"
1183 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001184 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001185 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001186 last_line_of_prompt = sys.ps1.split('\n')[-1]
1187 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001188 line = line[:-1]
1189 i = i+1
1190 if i:
1191 text.delete("insert - %d chars" % i, "insert")
1192 # strip whitespace after insert point
1193 while text.get("insert") in " \t":
1194 text.delete("insert")
1195 # start new line
1196 text.insert("insert", '\n')
1197
1198 # adjust indentation for continuations and block
1199 # open/close first need to find the last stmt
1200 lno = index2line(text.index('insert'))
1201 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001202 if not self.context_use_ps1:
1203 for context in self.num_context_lines:
1204 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001205 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001206 rawtext = text.get(startatindex, "insert")
1207 y.set_str(rawtext)
1208 bod = y.find_good_parse_start(
1209 self.context_use_ps1,
1210 self._build_char_in_string_func(startatindex))
1211 if bod is not None or startat == 1:
1212 break
1213 y.set_lo(bod or 0)
1214 else:
1215 r = text.tag_prevrange("console", "insert")
1216 if r:
1217 startatindex = r[1]
1218 else:
1219 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001220 rawtext = text.get(startatindex, "insert")
1221 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001222 y.set_lo(0)
1223
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001224 c = y.get_continuation_type()
1225 if c != PyParse.C_NONE:
1226 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001227 if c == PyParse.C_STRING_FIRST_LINE:
1228 # after the first line of a string; do not indent at all
1229 pass
1230 elif c == PyParse.C_STRING_NEXT_LINES:
1231 # inside a string which started before this line;
1232 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001233 text.insert("insert", indent)
1234 elif c == PyParse.C_BRACKET:
1235 # line up with the first (if any) element of the
1236 # last open bracket structure; else indent one
1237 # level beyond the indent of the line with the
1238 # last open bracket
1239 self.reindent_to(y.compute_bracket_indent())
1240 elif c == PyParse.C_BACKSLASH:
1241 # if more than one line in this stmt already, just
1242 # mimic the current indent; else if initial line
1243 # has a start on an assignment stmt, indent to
1244 # beyond leftmost =; else to beyond first chunk of
1245 # non-whitespace on initial line
1246 if y.get_num_lines_in_stmt() > 1:
1247 text.insert("insert", indent)
1248 else:
1249 self.reindent_to(y.compute_backslash_indent())
1250 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001251 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001252 return "break"
1253
1254 # This line starts a brand new stmt; indent relative to
1255 # indentation of initial line of closest preceding
1256 # interesting stmt.
1257 indent = y.get_base_indent_string()
1258 text.insert("insert", indent)
1259 if y.is_block_opener():
1260 self.smart_indent_event(event)
1261 elif indent and y.is_block_closer():
1262 self.smart_backspace_event(event)
1263 return "break"
1264 finally:
1265 text.see("insert")
1266 text.undo_block_stop()
1267
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001268 # Our editwin provides a is_char_in_string function that works
1269 # with a Tk text index, but PyParse only knows about offsets into
1270 # a string. This builds a function for PyParse that accepts an
1271 # offset.
1272
1273 def _build_char_in_string_func(self, startindex):
1274 def inner(offset, _startindex=startindex,
1275 _icis=self.is_char_in_string):
1276 return _icis(_startindex + "+%dc" % offset)
1277 return inner
1278
1279 def indent_region_event(self, event):
1280 head, tail, chars, lines = self.get_region()
1281 for pos in range(len(lines)):
1282 line = lines[pos]
1283 if line:
1284 raw, effective = classifyws(line, self.tabwidth)
1285 effective = effective + self.indentwidth
1286 lines[pos] = self._make_blanks(effective) + line[raw:]
1287 self.set_region(head, tail, chars, lines)
1288 return "break"
1289
1290 def dedent_region_event(self, event):
1291 head, tail, chars, lines = self.get_region()
1292 for pos in range(len(lines)):
1293 line = lines[pos]
1294 if line:
1295 raw, effective = classifyws(line, self.tabwidth)
1296 effective = max(effective - self.indentwidth, 0)
1297 lines[pos] = self._make_blanks(effective) + line[raw:]
1298 self.set_region(head, tail, chars, lines)
1299 return "break"
1300
1301 def comment_region_event(self, event):
1302 head, tail, chars, lines = self.get_region()
1303 for pos in range(len(lines) - 1):
1304 line = lines[pos]
1305 lines[pos] = '##' + line
1306 self.set_region(head, tail, chars, lines)
1307
1308 def uncomment_region_event(self, event):
1309 head, tail, chars, lines = self.get_region()
1310 for pos in range(len(lines)):
1311 line = lines[pos]
1312 if not line:
1313 continue
1314 if line[:2] == '##':
1315 line = line[2:]
1316 elif line[:1] == '#':
1317 line = line[1:]
1318 lines[pos] = line
1319 self.set_region(head, tail, chars, lines)
1320
1321 def tabify_region_event(self, event):
1322 head, tail, chars, lines = self.get_region()
1323 tabwidth = self._asktabwidth()
1324 for pos in range(len(lines)):
1325 line = lines[pos]
1326 if line:
1327 raw, effective = classifyws(line, tabwidth)
1328 ntabs, nspaces = divmod(effective, tabwidth)
1329 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1330 self.set_region(head, tail, chars, lines)
1331
1332 def untabify_region_event(self, event):
1333 head, tail, chars, lines = self.get_region()
1334 tabwidth = self._asktabwidth()
1335 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001336 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001337 self.set_region(head, tail, chars, lines)
1338
1339 def toggle_tabs_event(self, event):
1340 if self.askyesno(
1341 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001342 "Turn tabs " + ("on", "off")[self.usetabs] +
1343 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001344 ("will be", "remains at")[self.usetabs] + " 8." +
1345 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001346 parent=self.text):
1347 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001348 # Try to prevent inconsistent indentation.
1349 # User must change indent width manually after using tabs.
1350 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001351 return "break"
1352
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001353 # XXX this isn't bound to anything -- see tabwidth comments
1354## def change_tabwidth_event(self, event):
1355## new = self._asktabwidth()
1356## if new != self.tabwidth:
1357## self.tabwidth = new
1358## self.set_indentation_params(0, guess=0)
1359## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001360
1361 def change_indentwidth_event(self, event):
1362 new = self.askinteger(
1363 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001364 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001365 parent=self.text,
1366 initialvalue=self.indentwidth,
1367 minvalue=2,
1368 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001369 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001370 self.indentwidth = new
1371 return "break"
1372
1373 def get_region(self):
1374 text = self.text
1375 first, last = self.get_selection_indices()
1376 if first and last:
1377 head = text.index(first + " linestart")
1378 tail = text.index(last + "-1c lineend +1c")
1379 else:
1380 head = text.index("insert linestart")
1381 tail = text.index("insert lineend +1c")
1382 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001383 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001384 return head, tail, chars, lines
1385
1386 def set_region(self, head, tail, chars, lines):
1387 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001388 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001389 if newchars == chars:
1390 text.bell()
1391 return
1392 text.tag_remove("sel", "1.0", "end")
1393 text.mark_set("insert", head)
1394 text.undo_block_start()
1395 text.delete(head, tail)
1396 text.insert(head, newchars)
1397 text.undo_block_stop()
1398 text.tag_add("sel", head, "insert")
1399
1400 # Make string that displays as n leading blanks.
1401
1402 def _make_blanks(self, n):
1403 if self.usetabs:
1404 ntabs, nspaces = divmod(n, self.tabwidth)
1405 return '\t' * ntabs + ' ' * nspaces
1406 else:
1407 return ' ' * n
1408
1409 # Delete from beginning of line to insert point, then reinsert
1410 # column logical (meaning use tabs if appropriate) spaces.
1411
1412 def reindent_to(self, column):
1413 text = self.text
1414 text.undo_block_start()
1415 if text.compare("insert linestart", "!=", "insert"):
1416 text.delete("insert linestart", "insert")
1417 if column:
1418 text.insert("insert", self._make_blanks(column))
1419 text.undo_block_stop()
1420
1421 def _asktabwidth(self):
1422 return self.askinteger(
1423 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001424 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001425 parent=self.text,
1426 initialvalue=self.indentwidth,
1427 minvalue=2,
1428 maxvalue=16) or self.tabwidth
1429
1430 # Guess indentwidth from text content.
1431 # Return guessed indentwidth. This should not be believed unless
1432 # it's in a reasonable range (e.g., it will be 0 if no indented
1433 # blocks are found).
1434
1435 def guess_indent(self):
1436 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1437 if opener and indented:
1438 raw, indentsmall = classifyws(opener, self.tabwidth)
1439 raw, indentlarge = classifyws(indented, self.tabwidth)
1440 else:
1441 indentsmall = indentlarge = 0
1442 return indentlarge - indentsmall
1443
1444# "line.col" -> line, as an int
1445def index2line(index):
1446 return int(float(index))
1447
1448# Look at the leading whitespace in s.
1449# Return pair (# of leading ws characters,
1450# effective # of leading blanks after expanding
1451# tabs to width tabwidth)
1452
1453def classifyws(s, tabwidth):
1454 raw = effective = 0
1455 for ch in s:
1456 if ch == ' ':
1457 raw = raw + 1
1458 effective = effective + 1
1459 elif ch == '\t':
1460 raw = raw + 1
1461 effective = (effective // tabwidth + 1) * tabwidth
1462 else:
1463 break
1464 return raw, effective
1465
1466import tokenize
1467_tokenize = tokenize
1468del tokenize
1469
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001470class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001471
1472 # .run() chews over the Text widget, looking for a block opener
1473 # and the stmt following it. Returns a pair,
1474 # (line containing block opener, line containing stmt)
1475 # Either or both may be None.
1476
1477 def __init__(self, text, tabwidth):
1478 self.text = text
1479 self.tabwidth = tabwidth
1480 self.i = self.finished = 0
1481 self.blkopenline = self.indentedline = None
1482
1483 def readline(self):
1484 if self.finished:
1485 return ""
1486 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001487 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001488 if self.text.compare(mark, ">=", "end"):
1489 return ""
1490 return self.text.get(mark, mark + " lineend+1c")
1491
1492 def tokeneater(self, type, token, start, end, line,
1493 INDENT=_tokenize.INDENT,
1494 NAME=_tokenize.NAME,
1495 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1496 if self.finished:
1497 pass
1498 elif type == NAME and token in OPENERS:
1499 self.blkopenline = line
1500 elif type == INDENT and self.blkopenline:
1501 self.indentedline = line
1502 self.finished = 1
1503
1504 def run(self):
1505 save_tabsize = _tokenize.tabsize
1506 _tokenize.tabsize = self.tabwidth
1507 try:
1508 try:
Trent Nelson428de652008-03-18 22:41:35 +00001509 tokens = _tokenize.generate_tokens(self.readline)
1510 for token in tokens:
1511 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001512 except _tokenize.TokenError:
1513 # since we cut off the tokenizer early, we can trigger
1514 # spurious errors
1515 pass
1516 finally:
1517 _tokenize.tabsize = save_tabsize
1518 return self.blkopenline, self.indentedline
1519
1520### end autoindent code ###
1521
David Scherer7aced172000-08-15 01:13:23 +00001522def prepstr(s):
1523 # Helper to extract the underscore from a string, e.g.
1524 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001525 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001526 if i >= 0:
1527 s = s[:i] + s[i+1:]
1528 return i, s
1529
1530
1531keynames = {
1532 'bracketleft': '[',
1533 'bracketright': ']',
1534 'slash': '/',
1535}
1536
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001537def get_accelerator(keydefs, eventname):
1538 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001539 if not keylist:
1540 return ""
1541 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001542 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001543 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1544 s = re.sub("Key-", "", s)
1545 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1546 s = re.sub("Control-", "Ctrl-", s)
1547 s = re.sub("-", "+", s)
1548 s = re.sub("><", " ", s)
1549 s = re.sub("<", "", s)
1550 s = re.sub(">", "", s)
1551 return s
1552
1553
1554def fixwordbreaks(root):
1555 # Make sure that Tk's double-click and next/previous word
1556 # operations use our definition of a word (i.e. an identifier)
1557 tk = root.tk
1558 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1559 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1560 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1561
1562
1563def test():
1564 root = Tk()
1565 fixwordbreaks(root)
1566 root.withdraw()
1567 if sys.argv[1:]:
1568 filename = sys.argv[1]
1569 else:
1570 filename = None
1571 edit = EditorWindow(root=root, filename=filename)
1572 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001573 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001574 root.mainloop()
1575 root.destroy()
1576
1577if __name__ == '__main__':
1578 test()