blob: a37cb1d9bcfb98881333f83292d6d8c186aef704 [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
Ezio Melotti42da6632011-03-15 05:18:48 +0200107 #self.top.instance_dict makes flist.inversedict available to
108 #configDialog.py so it can access all EditorWindow instances
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):
Ned Deily122539e2011-01-24 21:46:44 +0000775 rf_list_file = open(self.recent_files_path,'r',
776 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000777 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000778 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000779 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000780 rf_list_file.close()
781 if new_file:
782 new_file = os.path.abspath(new_file) + '\n'
783 if new_file in rf_list:
784 rf_list.remove(new_file) # move to top
785 rf_list.insert(0, new_file)
786 # clean and save the recent files list
787 bad_paths = []
788 for path in rf_list:
789 if '\0' in path or not os.path.exists(path[0:-1]):
790 bad_paths.append(path)
791 rf_list = [path for path in rf_list if path not in bad_paths]
792 ulchars = "1234567890ABCDEFGHIJK"
793 rf_list = rf_list[0:len(ulchars)]
Ned Deily122539e2011-01-24 21:46:44 +0000794 rf_file = open(self.recent_files_path, 'w',
795 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000796 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000797 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000798 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000799 rf_file.close()
800 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000801 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000802 menu = instance.recent_files_menu
803 menu.delete(1, END) # clear, and rebuild:
Guilherme Polo1fff0082009-08-14 15:05:30 +0000804 for i, file_name in enumerate(rf_list):
805 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000806 # make unicode string to display non-ASCII chars correctly
807 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000808 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000809 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000810 command=callback,
811 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000812
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000813 def __recent_file_callback(self, file_name):
814 def open_recent_file(fn_closure=file_name):
815 self.io.open(editFile=fn_closure)
816 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000817
David Scherer7aced172000-08-15 01:13:23 +0000818 def saved_change_hook(self):
819 short = self.short_title()
820 long = self.long_title()
821 if short and long:
822 title = short + " - " + long
823 elif short:
824 title = short
825 elif long:
826 title = long
827 else:
828 title = "Untitled"
829 icon = short or long or title
830 if not self.get_saved():
831 title = "*%s*" % title
832 icon = "*%s" % icon
833 self.top.wm_title(title)
834 self.top.wm_iconname(icon)
835
836 def get_saved(self):
837 return self.undo.get_saved()
838
839 def set_saved(self, flag):
840 self.undo.set_saved(flag)
841
842 def reset_undo(self):
843 self.undo.reset_undo()
844
845 def short_title(self):
846 filename = self.io.filename
847 if filename:
848 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000849 # return unicode string to display non-ASCII chars correctly
850 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000851
852 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000853 # return unicode string to display non-ASCII chars correctly
854 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000855
856 def center_insert_event(self, event):
857 self.center()
858
859 def center(self, mark="insert"):
860 text = self.text
861 top, bot = self.getwindowlines()
862 lineno = self.getlineno(mark)
863 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000864 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000865 text.yview(float(newtop))
866
867 def getwindowlines(self):
868 text = self.text
869 top = self.getlineno("@0,0")
870 bot = self.getlineno("@0,65535")
871 if top == bot and text.winfo_height() == 1:
872 # Geometry manager hasn't run yet
873 height = int(text['height'])
874 bot = top + height - 1
875 return top, bot
876
877 def getlineno(self, mark="insert"):
878 text = self.text
879 return int(float(text.index(mark)))
880
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000881 def get_geometry(self):
882 "Return (width, height, x, y)"
883 geom = self.top.wm_geometry()
884 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000885 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000886
David Scherer7aced172000-08-15 01:13:23 +0000887 def close_event(self, event):
888 self.close()
889
890 def maybesave(self):
891 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000892 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000893 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000894 self.top.deiconify()
895 self.top.lower()
896 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000897 return self.io.maybesave()
898
899 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000900 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000901 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000902 self._close()
903 return reply
904
905 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000906 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000907 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000908 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000909 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000910 self.io.close()
911 self.io = None
912 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000913 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000914 self.color.close(False)
915 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000916 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000917 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000918 self.per.close()
919 self.per = None
920 self.top.destroy()
921 if self.close_hook:
922 # unless override: unregister from flist, terminate if last window
923 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000924
925 def load_extensions(self):
926 self.extensions = {}
927 self.load_standard_extensions()
928
929 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000930 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000931 if hasattr(ins, "close"):
932 ins.close()
933 self.extensions = {}
934
935 def load_standard_extensions(self):
936 for name in self.get_standard_extension_names():
937 try:
938 self.load_extension(name)
939 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000940 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000941 traceback.print_exc()
942
943 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000944 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000945
946 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000947 try:
948 mod = __import__(name, globals(), locals(), [])
949 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000950 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000951 raise
David Scherer7aced172000-08-15 01:13:23 +0000952 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000953 keydefs = idleConf.GetExtensionBindings(name)
954 if hasattr(cls, "menudefs"):
955 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000956 ins = cls(self)
957 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000958 if keydefs:
959 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000960 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000961 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000962 while methodname[:1] == '<':
963 methodname = methodname[1:]
964 while methodname[-1:] == '>':
965 methodname = methodname[:-1]
966 methodname = methodname + "_event"
967 if hasattr(ins, methodname):
968 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000969
970 def apply_bindings(self, keydefs=None):
971 if keydefs is None:
972 keydefs = self.Bindings.default_keydefs
973 text = self.text
974 text.keydefs = keydefs
975 for event, keylist in keydefs.items():
976 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000977 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000978
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000979 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000980 """Add appropriate entries to the menus and submenus
981
982 Menus that are absent or None in self.menudict are ignored.
983 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000984 if menudefs is None:
985 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000986 if keydefs is None:
987 keydefs = self.Bindings.default_keydefs
988 menudict = self.menudict
989 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000990 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000991 menu = menudict.get(mname)
992 if not menu:
993 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000994 for entry in entrylist:
995 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000996 menu.add_separator()
997 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000998 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000999 checkbutton = (label[:1] == '!')
1000 if checkbutton:
1001 label = label[1:]
1002 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001003 accelerator = get_accelerator(keydefs, eventname)
1004 def command(text=text, eventname=eventname):
1005 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001006 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001007 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001008 menu.add_checkbutton(label=label, underline=underline,
1009 command=command, accelerator=accelerator,
1010 variable=var)
1011 else:
1012 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001013 command=command,
1014 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001015
1016 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001017 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001018 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001019 value = var.get()
1020 return value
1021 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001022 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001023
1024 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001025 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001026 if var:
1027 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001028 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001029 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001030
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001031 def get_var_obj(self, name, vartype=None):
1032 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001033 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001034 # create a Tkinter variable object with self.text as master:
1035 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001036 return var
1037
1038 # Tk implementations of "virtual text methods" -- each platform
1039 # reusing IDLE's support code needs to define these for its GUI's
1040 # flavor of widget.
1041
1042 # Is character at text_index in a Python string? Return 0 for
1043 # "guaranteed no", true for anything else. This info is expensive
1044 # to compute ab initio, but is probably already known by the
1045 # platform's colorizer.
1046
1047 def is_char_in_string(self, text_index):
1048 if self.color:
1049 # Return true iff colorizer hasn't (re)gotten this far
1050 # yet, or the character is tagged as being in a string
1051 return self.text.tag_prevrange("TODO", text_index) or \
1052 "STRING" in self.text.tag_names(text_index)
1053 else:
1054 # The colorizer is missing: assume the worst
1055 return 1
1056
1057 # If a selection is defined in the text widget, return (start,
1058 # end) as Tkinter text indices, otherwise return (None, None)
1059 def get_selection_indices(self):
1060 try:
1061 first = self.text.index("sel.first")
1062 last = self.text.index("sel.last")
1063 return first, last
1064 except TclError:
1065 return None, None
1066
1067 # Return the text widget's current view of what a tab stop means
1068 # (equivalent width in spaces).
1069
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001070 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001071 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1072 return int(current)
1073
1074 # Set the text widget's current view of what a tab stop means.
1075
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001076 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001077 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001078 if self.get_tk_tabwidth() != newtabwidth:
1079 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001080 pixels = text.tk.call("font", "measure", text["font"],
1081 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001082 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001083 text.configure(tabs=pixels)
1084
Guido van Rossum33d26892007-08-05 15:29:28 +00001085### begin autoindent code ### (configuration was moved to beginning of class)
1086
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001087 def set_indentation_params(self, is_py_src, guess=True):
1088 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001089 i = self.guess_indent()
1090 if 2 <= i <= 8:
1091 self.indentwidth = i
1092 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001093 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001094 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001095
1096 def smart_backspace_event(self, event):
1097 text = self.text
1098 first, last = self.get_selection_indices()
1099 if first and last:
1100 text.delete(first, last)
1101 text.mark_set("insert", first)
1102 return "break"
1103 # Delete whitespace left, until hitting a real char or closest
1104 # preceding virtual tab stop.
1105 chars = text.get("insert linestart", "insert")
1106 if chars == '':
1107 if text.compare("insert", ">", "1.0"):
1108 # easy: delete preceding newline
1109 text.delete("insert-1c")
1110 else:
1111 text.bell() # at start of buffer
1112 return "break"
1113 if chars[-1] not in " \t":
1114 # easy: delete preceding real char
1115 text.delete("insert-1c")
1116 return "break"
1117 # Ick. It may require *inserting* spaces if we back up over a
1118 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001119 tabwidth = self.tabwidth
1120 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001121 assert have > 0
1122 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001123 # Debug prompt is multilined....
1124 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001125 ncharsdeleted = 0
1126 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001127 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001128 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001129 chars = chars[:-1]
1130 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001131 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001132 if have <= want or chars[-1] not in " \t":
1133 break
1134 text.undo_block_start()
1135 text.delete("insert-%dc" % ncharsdeleted, "insert")
1136 if have < want:
1137 text.insert("insert", ' ' * (want - have))
1138 text.undo_block_stop()
1139 return "break"
1140
1141 def smart_indent_event(self, event):
1142 # if intraline selection:
1143 # delete it
1144 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001145 # do indent-region
1146 # else:
1147 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001148 text = self.text
1149 first, last = self.get_selection_indices()
1150 text.undo_block_start()
1151 try:
1152 if first and last:
1153 if index2line(first) != index2line(last):
1154 return self.indent_region_event(event)
1155 text.delete(first, last)
1156 text.mark_set("insert", first)
1157 prefix = text.get("insert linestart", "insert")
1158 raw, effective = classifyws(prefix, self.tabwidth)
1159 if raw == len(prefix):
1160 # only whitespace to the left
1161 self.reindent_to(effective + self.indentwidth)
1162 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001163 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001164 if self.usetabs:
1165 pad = '\t'
1166 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001167 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001168 n = self.indentwidth
1169 pad = ' ' * (n - effective % n)
1170 text.insert("insert", pad)
1171 text.see("insert")
1172 return "break"
1173 finally:
1174 text.undo_block_stop()
1175
1176 def newline_and_indent_event(self, event):
1177 text = self.text
1178 first, last = self.get_selection_indices()
1179 text.undo_block_start()
1180 try:
1181 if first and last:
1182 text.delete(first, last)
1183 text.mark_set("insert", first)
1184 line = text.get("insert linestart", "insert")
1185 i, n = 0, len(line)
1186 while i < n and line[i] in " \t":
1187 i = i+1
1188 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001189 # the cursor is in or at leading indentation in a continuation
1190 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001191 text.insert("insert linestart", '\n')
1192 return "break"
1193 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001194 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001195 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001196 last_line_of_prompt = sys.ps1.split('\n')[-1]
1197 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001198 line = line[:-1]
1199 i = i+1
1200 if i:
1201 text.delete("insert - %d chars" % i, "insert")
1202 # strip whitespace after insert point
1203 while text.get("insert") in " \t":
1204 text.delete("insert")
1205 # start new line
1206 text.insert("insert", '\n')
1207
1208 # adjust indentation for continuations and block
1209 # open/close first need to find the last stmt
1210 lno = index2line(text.index('insert'))
1211 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001212 if not self.context_use_ps1:
1213 for context in self.num_context_lines:
1214 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001215 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001216 rawtext = text.get(startatindex, "insert")
1217 y.set_str(rawtext)
1218 bod = y.find_good_parse_start(
1219 self.context_use_ps1,
1220 self._build_char_in_string_func(startatindex))
1221 if bod is not None or startat == 1:
1222 break
1223 y.set_lo(bod or 0)
1224 else:
1225 r = text.tag_prevrange("console", "insert")
1226 if r:
1227 startatindex = r[1]
1228 else:
1229 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001230 rawtext = text.get(startatindex, "insert")
1231 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001232 y.set_lo(0)
1233
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001234 c = y.get_continuation_type()
1235 if c != PyParse.C_NONE:
1236 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001237 if c == PyParse.C_STRING_FIRST_LINE:
1238 # after the first line of a string; do not indent at all
1239 pass
1240 elif c == PyParse.C_STRING_NEXT_LINES:
1241 # inside a string which started before this line;
1242 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001243 text.insert("insert", indent)
1244 elif c == PyParse.C_BRACKET:
1245 # line up with the first (if any) element of the
1246 # last open bracket structure; else indent one
1247 # level beyond the indent of the line with the
1248 # last open bracket
1249 self.reindent_to(y.compute_bracket_indent())
1250 elif c == PyParse.C_BACKSLASH:
1251 # if more than one line in this stmt already, just
1252 # mimic the current indent; else if initial line
1253 # has a start on an assignment stmt, indent to
1254 # beyond leftmost =; else to beyond first chunk of
1255 # non-whitespace on initial line
1256 if y.get_num_lines_in_stmt() > 1:
1257 text.insert("insert", indent)
1258 else:
1259 self.reindent_to(y.compute_backslash_indent())
1260 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001261 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001262 return "break"
1263
1264 # This line starts a brand new stmt; indent relative to
1265 # indentation of initial line of closest preceding
1266 # interesting stmt.
1267 indent = y.get_base_indent_string()
1268 text.insert("insert", indent)
1269 if y.is_block_opener():
1270 self.smart_indent_event(event)
1271 elif indent and y.is_block_closer():
1272 self.smart_backspace_event(event)
1273 return "break"
1274 finally:
1275 text.see("insert")
1276 text.undo_block_stop()
1277
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001278 # Our editwin provides a is_char_in_string function that works
1279 # with a Tk text index, but PyParse only knows about offsets into
1280 # a string. This builds a function for PyParse that accepts an
1281 # offset.
1282
1283 def _build_char_in_string_func(self, startindex):
1284 def inner(offset, _startindex=startindex,
1285 _icis=self.is_char_in_string):
1286 return _icis(_startindex + "+%dc" % offset)
1287 return inner
1288
1289 def indent_region_event(self, event):
1290 head, tail, chars, lines = self.get_region()
1291 for pos in range(len(lines)):
1292 line = lines[pos]
1293 if line:
1294 raw, effective = classifyws(line, self.tabwidth)
1295 effective = effective + self.indentwidth
1296 lines[pos] = self._make_blanks(effective) + line[raw:]
1297 self.set_region(head, tail, chars, lines)
1298 return "break"
1299
1300 def dedent_region_event(self, event):
1301 head, tail, chars, lines = self.get_region()
1302 for pos in range(len(lines)):
1303 line = lines[pos]
1304 if line:
1305 raw, effective = classifyws(line, self.tabwidth)
1306 effective = max(effective - self.indentwidth, 0)
1307 lines[pos] = self._make_blanks(effective) + line[raw:]
1308 self.set_region(head, tail, chars, lines)
1309 return "break"
1310
1311 def comment_region_event(self, event):
1312 head, tail, chars, lines = self.get_region()
1313 for pos in range(len(lines) - 1):
1314 line = lines[pos]
1315 lines[pos] = '##' + line
1316 self.set_region(head, tail, chars, lines)
1317
1318 def uncomment_region_event(self, event):
1319 head, tail, chars, lines = self.get_region()
1320 for pos in range(len(lines)):
1321 line = lines[pos]
1322 if not line:
1323 continue
1324 if line[:2] == '##':
1325 line = line[2:]
1326 elif line[:1] == '#':
1327 line = line[1:]
1328 lines[pos] = line
1329 self.set_region(head, tail, chars, lines)
1330
1331 def tabify_region_event(self, event):
1332 head, tail, chars, lines = self.get_region()
1333 tabwidth = self._asktabwidth()
1334 for pos in range(len(lines)):
1335 line = lines[pos]
1336 if line:
1337 raw, effective = classifyws(line, tabwidth)
1338 ntabs, nspaces = divmod(effective, tabwidth)
1339 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1340 self.set_region(head, tail, chars, lines)
1341
1342 def untabify_region_event(self, event):
1343 head, tail, chars, lines = self.get_region()
1344 tabwidth = self._asktabwidth()
1345 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001346 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001347 self.set_region(head, tail, chars, lines)
1348
1349 def toggle_tabs_event(self, event):
1350 if self.askyesno(
1351 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001352 "Turn tabs " + ("on", "off")[self.usetabs] +
1353 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001354 ("will be", "remains at")[self.usetabs] + " 8." +
1355 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001356 parent=self.text):
1357 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001358 # Try to prevent inconsistent indentation.
1359 # User must change indent width manually after using tabs.
1360 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001361 return "break"
1362
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001363 # XXX this isn't bound to anything -- see tabwidth comments
1364## def change_tabwidth_event(self, event):
1365## new = self._asktabwidth()
1366## if new != self.tabwidth:
1367## self.tabwidth = new
1368## self.set_indentation_params(0, guess=0)
1369## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001370
1371 def change_indentwidth_event(self, event):
1372 new = self.askinteger(
1373 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001374 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001375 parent=self.text,
1376 initialvalue=self.indentwidth,
1377 minvalue=2,
1378 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001379 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001380 self.indentwidth = new
1381 return "break"
1382
1383 def get_region(self):
1384 text = self.text
1385 first, last = self.get_selection_indices()
1386 if first and last:
1387 head = text.index(first + " linestart")
1388 tail = text.index(last + "-1c lineend +1c")
1389 else:
1390 head = text.index("insert linestart")
1391 tail = text.index("insert lineend +1c")
1392 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001393 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001394 return head, tail, chars, lines
1395
1396 def set_region(self, head, tail, chars, lines):
1397 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001398 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001399 if newchars == chars:
1400 text.bell()
1401 return
1402 text.tag_remove("sel", "1.0", "end")
1403 text.mark_set("insert", head)
1404 text.undo_block_start()
1405 text.delete(head, tail)
1406 text.insert(head, newchars)
1407 text.undo_block_stop()
1408 text.tag_add("sel", head, "insert")
1409
1410 # Make string that displays as n leading blanks.
1411
1412 def _make_blanks(self, n):
1413 if self.usetabs:
1414 ntabs, nspaces = divmod(n, self.tabwidth)
1415 return '\t' * ntabs + ' ' * nspaces
1416 else:
1417 return ' ' * n
1418
1419 # Delete from beginning of line to insert point, then reinsert
1420 # column logical (meaning use tabs if appropriate) spaces.
1421
1422 def reindent_to(self, column):
1423 text = self.text
1424 text.undo_block_start()
1425 if text.compare("insert linestart", "!=", "insert"):
1426 text.delete("insert linestart", "insert")
1427 if column:
1428 text.insert("insert", self._make_blanks(column))
1429 text.undo_block_stop()
1430
1431 def _asktabwidth(self):
1432 return self.askinteger(
1433 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001434 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001435 parent=self.text,
1436 initialvalue=self.indentwidth,
1437 minvalue=2,
1438 maxvalue=16) or self.tabwidth
1439
1440 # Guess indentwidth from text content.
1441 # Return guessed indentwidth. This should not be believed unless
1442 # it's in a reasonable range (e.g., it will be 0 if no indented
1443 # blocks are found).
1444
1445 def guess_indent(self):
1446 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1447 if opener and indented:
1448 raw, indentsmall = classifyws(opener, self.tabwidth)
1449 raw, indentlarge = classifyws(indented, self.tabwidth)
1450 else:
1451 indentsmall = indentlarge = 0
1452 return indentlarge - indentsmall
1453
1454# "line.col" -> line, as an int
1455def index2line(index):
1456 return int(float(index))
1457
1458# Look at the leading whitespace in s.
1459# Return pair (# of leading ws characters,
1460# effective # of leading blanks after expanding
1461# tabs to width tabwidth)
1462
1463def classifyws(s, tabwidth):
1464 raw = effective = 0
1465 for ch in s:
1466 if ch == ' ':
1467 raw = raw + 1
1468 effective = effective + 1
1469 elif ch == '\t':
1470 raw = raw + 1
1471 effective = (effective // tabwidth + 1) * tabwidth
1472 else:
1473 break
1474 return raw, effective
1475
1476import tokenize
1477_tokenize = tokenize
1478del tokenize
1479
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001480class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001481
1482 # .run() chews over the Text widget, looking for a block opener
1483 # and the stmt following it. Returns a pair,
1484 # (line containing block opener, line containing stmt)
1485 # Either or both may be None.
1486
1487 def __init__(self, text, tabwidth):
1488 self.text = text
1489 self.tabwidth = tabwidth
1490 self.i = self.finished = 0
1491 self.blkopenline = self.indentedline = None
1492
1493 def readline(self):
1494 if self.finished:
1495 return ""
1496 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001497 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001498 if self.text.compare(mark, ">=", "end"):
1499 return ""
1500 return self.text.get(mark, mark + " lineend+1c")
1501
1502 def tokeneater(self, type, token, start, end, line,
1503 INDENT=_tokenize.INDENT,
1504 NAME=_tokenize.NAME,
1505 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1506 if self.finished:
1507 pass
1508 elif type == NAME and token in OPENERS:
1509 self.blkopenline = line
1510 elif type == INDENT and self.blkopenline:
1511 self.indentedline = line
1512 self.finished = 1
1513
1514 def run(self):
1515 save_tabsize = _tokenize.tabsize
1516 _tokenize.tabsize = self.tabwidth
1517 try:
1518 try:
Trent Nelson428de652008-03-18 22:41:35 +00001519 tokens = _tokenize.generate_tokens(self.readline)
1520 for token in tokens:
1521 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001522 except _tokenize.TokenError:
1523 # since we cut off the tokenizer early, we can trigger
1524 # spurious errors
1525 pass
1526 finally:
1527 _tokenize.tabsize = save_tabsize
1528 return self.blkopenline, self.indentedline
1529
1530### end autoindent code ###
1531
David Scherer7aced172000-08-15 01:13:23 +00001532def prepstr(s):
1533 # Helper to extract the underscore from a string, e.g.
1534 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001535 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001536 if i >= 0:
1537 s = s[:i] + s[i+1:]
1538 return i, s
1539
1540
1541keynames = {
1542 'bracketleft': '[',
1543 'bracketright': ']',
1544 'slash': '/',
1545}
1546
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001547def get_accelerator(keydefs, eventname):
1548 keylist = keydefs.get(eventname)
Ned Deily70063932011-01-29 18:29:01 +00001549 # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1550 # if not keylist:
1551 if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
1552 "<<open-module>>",
1553 "<<goto-line>>",
1554 "<<change-indentwidth>>"}):
David Scherer7aced172000-08-15 01:13:23 +00001555 return ""
1556 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001557 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001558 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1559 s = re.sub("Key-", "", s)
1560 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1561 s = re.sub("Control-", "Ctrl-", s)
1562 s = re.sub("-", "+", s)
1563 s = re.sub("><", " ", s)
1564 s = re.sub("<", "", s)
1565 s = re.sub(">", "", s)
1566 return s
1567
1568
1569def fixwordbreaks(root):
1570 # Make sure that Tk's double-click and next/previous word
1571 # operations use our definition of a word (i.e. an identifier)
1572 tk = root.tk
1573 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1574 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1575 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1576
1577
1578def test():
1579 root = Tk()
1580 fixwordbreaks(root)
1581 root.withdraw()
1582 if sys.argv[1:]:
1583 filename = sys.argv[1]
1584 else:
1585 filename = None
1586 edit = EditorWindow(root=root, filename=filename)
1587 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001588 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001589 root.mainloop()
1590 root.destroy()
1591
1592if __name__ == '__main__':
1593 test()