blob: ab75f3ad649c1347af1ee4d56dc2a14e44805846 [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
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00006from itertools import count
Georg Brandl14fc4272008-05-17 18:39:55 +00007from tkinter import *
8import tkinter.simpledialog as tkSimpleDialog
9import tkinter.messagebox as tkMessageBox
Guido van Rossum36e0a922007-07-20 04:05:57 +000010import traceback
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000011import webbrowser
Guido van Rossum36e0a922007-07-20 04:05:57 +000012
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000013from idlelib.MultiCall import MultiCallCreator
14from idlelib import idlever
15from idlelib import WindowList
16from idlelib import SearchDialog
17from idlelib import GrepDialog
18from idlelib import ReplaceDialog
19from idlelib import PyParse
20from idlelib.configHandler import idleConf
21from idlelib import aboutDialog, textView, configDialog
22from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000023
24# The default tab setting for a Text widget, in average-width characters.
25TK_TABWIDTH_DEFAULT = 8
26
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000027def _sphinx_version():
28 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
29 major, minor, micro, level, serial = sys.version_info
30 release = '%s%s' % (major, minor)
31 if micro:
Benjamin Petersonb48f6342009-06-22 19:36:31 +000032 release += '%s' % (micro,)
33 if level == 'candidate':
34 release += 'rc%s' % (serial,)
35 elif level != 'final':
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000036 release += '%s%s' % (level[0], serial)
37 return release
38
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000039def _find_module(fullname, path=None):
40 """Version of imp.find_module() that handles hierarchical module names"""
41
42 file = None
43 for tgt in fullname.split('.'):
44 if file is not None:
45 file.close() # close intermediate files
46 (file, filename, descr) = imp.find_module(tgt, path)
47 if descr[2] == imp.PY_SOURCE:
48 break # find but not load the source file
49 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000050 try:
51 path = module.__path__
52 except AttributeError:
Kurt B. Kaiserad667422007-08-23 01:06:15 +000053 raise ImportError('No source for module ' + module.__name__)
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000054 return file, filename, descr
55
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000056class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000057 from idlelib.Percolator import Percolator
58 from idlelib.ColorDelegator import ColorDelegator
59 from idlelib.UndoDelegator import UndoDelegator
60 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
61 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000062 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000063 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000064
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000065 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000066
67 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000068 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000069 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000070 if sys.platform.count('linux'):
71 # look for html docs in a couple of standard places
72 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
73 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
74 dochome = '/var/www/html/python/index.html'
75 else:
76 basepath = '/usr/share/doc/' # standard location
77 dochome = os.path.join(basepath, pyver,
78 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000079 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +000080 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000081 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +000082 if os.path.isfile(chmfile):
83 dochome = chmfile
Thomas Wouters0e3f5912006-08-11 14:57:12 +000084 elif macosxSupport.runningAsOSXApp():
85 # documentation is stored inside the python framework
86 dochome = os.path.join(sys.prefix,
87 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000088 dochome = os.path.normpath(dochome)
89 if os.path.isfile(dochome):
90 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +000091 if sys.platform == 'darwin':
92 # Safari requires real file:-URLs
93 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000094 else:
Benjamin Peterson152b6572009-01-20 15:01:54 +000095 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +000096 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000097 self.flist = flist
98 root = root or flist.root
99 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000100 try:
101 sys.ps1
102 except AttributeError:
103 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000104 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000105 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000106 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000107 self.tkinter_vars = flist.vars
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000108 #self.top.instance_dict makes flist.inversedict avalable to
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000109 #configDialog.py so it can access all EditorWindow instaces
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000110 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000111 else:
112 self.tkinter_vars = {} # keys: Tkinter event names
113 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000114 self.top.instance_dict = {}
115 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000116 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000117 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000118 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000119 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000120 text_options = {
121 'name': 'text',
122 'padx': 5,
123 'wrap': 'none',
124 'width': self.width,
125 'height': idleConf.GetOption('main', 'EditorWindow', 'height')}
126 if TkVersion >= 8.5:
127 # Starting with tk 8.5 we have to set the new tabstyle option
128 # to 'wordprocessor' to achieve the same display of tabs as in
129 # older tk versions.
130 text_options['tabstyle'] = 'wordprocessor'
131 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000132 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000133
134 self.createmenubar()
135 self.apply_bindings()
136
137 self.top.protocol("WM_DELETE_WINDOW", self.close)
138 self.top.bind("<<close-window>>", self.close_event)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000139 if macosxSupport.runningAsOSXApp():
140 # Command-W on editorwindows doesn't work without this.
141 text.bind('<<close-window>>', self.close_event)
R. David Murray28b77ec2010-12-18 17:21:30 +0000142 # Some OS X systems have only one mouse button,
143 # so use control-click for pulldown menus there.
144 # (Note, AquaTk defines <2> as the right button if
145 # present and the Tk Text widget already binds <2>.)
146 text.bind("<Control-Button-1>",self.right_menu_event)
147 else:
148 # Elsewhere, use right-click for pulldown menus.
149 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000150 text.bind("<<cut>>", self.cut)
151 text.bind("<<copy>>", self.copy)
152 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000153 text.bind("<<center-insert>>", self.center_insert_event)
154 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000155 text.bind("<<python-docs>>", self.python_docs)
156 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000157 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000158 text.bind("<<open-module>>", self.open_module)
159 text.bind("<<do-nothing>>", lambda event: "break")
160 text.bind("<<select-all>>", self.select_all)
161 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000162 text.bind("<<find>>", self.find_event)
163 text.bind("<<find-again>>", self.find_again_event)
164 text.bind("<<find-in-files>>", self.find_in_files_event)
165 text.bind("<<find-selection>>", self.find_selection_event)
166 text.bind("<<replace>>", self.replace_event)
167 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000168 text.bind("<<smart-backspace>>",self.smart_backspace_event)
169 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
170 text.bind("<<smart-indent>>",self.smart_indent_event)
171 text.bind("<<indent-region>>",self.indent_region_event)
172 text.bind("<<dedent-region>>",self.dedent_region_event)
173 text.bind("<<comment-region>>",self.comment_region_event)
174 text.bind("<<uncomment-region>>",self.uncomment_region_event)
175 text.bind("<<tabify-region>>",self.tabify_region_event)
176 text.bind("<<untabify-region>>",self.untabify_region_event)
177 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
178 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000179 text.bind("<Left>", self.move_at_edge_if_selection(0))
180 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000181 text.bind("<<del-word-left>>", self.del_word_left)
182 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000183 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000184
David Scherer7aced172000-08-15 01:13:23 +0000185 if flist:
186 flist.inversedict[self] = key
187 if key:
188 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000189 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000190 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
191 text.bind("<<open-class-browser>>", self.open_class_browser)
192 text.bind("<<open-path-browser>>", self.open_path_browser)
193
Steven M. Gava898a3652001-10-07 11:10:44 +0000194 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000195 vbar['command'] = text.yview
196 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000197 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000198 fontWeight = 'normal'
199 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000200 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000201 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
202 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
203 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000204 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
205 text.pack(side=TOP, fill=BOTH, expand=1)
206 text.focus_set()
207
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000208 # usetabs true -> literal tab characters are used by indent and
209 # dedent cmds, possibly mixed with spaces if
210 # indentwidth is not a multiple of tabwidth,
211 # which will cause Tabnanny to nag!
212 # false -> tab characters are converted to spaces by indent
213 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000214 # Although use-spaces=0 can be configured manually in config-main.def,
215 # configuration of tabs v. spaces is not supported in the configuration
216 # dialog. IDLE promotes the preferred Python indentation: use spaces!
217 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
218 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000219
220 # tabwidth is the display width of a literal tab character.
221 # CAUTION: telling Tk to use anything other than its default
222 # tab setting causes it to use an entirely different tabbing algorithm,
223 # treating tab stops as fixed distances from the left margin.
224 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000225 self.tabwidth = 8 # must remain 8 until Tk is fixed.
226
227 # indentwidth is the number of screen characters per indent level.
228 # The recommended Python indentation is four spaces.
229 self.indentwidth = self.tabwidth
230 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000231
232 # If context_use_ps1 is true, parsing searches back for a ps1 line;
233 # else searches for a popular (if, def, ...) Python stmt.
234 self.context_use_ps1 = False
235
236 # When searching backwards for a reliable place to begin parsing,
237 # first start num_context_lines[0] lines back, then
238 # num_context_lines[1] lines back if that didn't work, and so on.
239 # The last value should be huge (larger than the # of lines in a
240 # conceivable file).
241 # Making the initial values larger slows things down more often.
242 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000243 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000244 self.undo = undo = self.UndoDelegator()
245 per.insertfilter(undo)
246 text.undo_block_start = undo.undo_block_start
247 text.undo_block_stop = undo.undo_block_stop
248 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000249 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000250 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000251 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000252 self.good_load = False
253 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000254 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000255 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000256 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000257 if io.loadfile(filename):
258 self.good_load = True
259 is_py_src = self.ispythonsource(filename)
260 self.set_indentation_params(is_py_src)
261 if is_py_src:
262 self.color = color = self.ColorDelegator()
263 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000264 else:
265 io.set_filename(filename)
Christian Heimesa156e092008-02-16 07:38:31 +0000266 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000267 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000268 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000269 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000270 menu = self.menudict.get('windows')
271 if menu:
272 end = menu.index("end")
273 if end is None:
274 end = -1
275 if end >= 0:
276 menu.add_separator()
277 end = end + 1
278 self.wmenu_end = end
279 WindowList.register_callback(self.postwindowsmenu)
280
281 # Some abstractions so IDLE extensions are cross-IDE
282 self.askyesno = tkMessageBox.askyesno
283 self.askinteger = tkSimpleDialog.askinteger
284 self.showerror = tkMessageBox.showerror
285
Martin v. Löwis307021f2005-11-27 16:59:04 +0000286 def _filename_to_unicode(self, filename):
287 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000288 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000289 return filename
290 else:
291 try:
292 return filename.decode(self.filesystemencoding)
293 except UnicodeDecodeError:
294 # XXX
295 try:
296 return filename.decode(self.encoding)
297 except UnicodeDecodeError:
298 # byte-to-byte conversion
299 return filename.decode('iso8859-1')
300
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000301 def new_callback(self, event):
302 dirname, basename = self.io.defaultfilename()
303 self.flist.new(dirname)
304 return "break"
305
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000306 def home_callback(self, event):
307 if (event.state & 12) != 0 and event.keysym == "Home":
308 # state&1==shift, state&4==control, state&8==alt
309 return # <Modifier-Home>; fall back to class binding
310
311 if self.text.index("iomark") and \
312 self.text.compare("iomark", "<=", "insert lineend") and \
313 self.text.compare("insert linestart", "<=", "iomark"):
314 insertpt = int(self.text.index("iomark").split(".")[1])
315 else:
316 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000317 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000318 if line[insertpt] not in (' ','\t'):
319 break
320 else:
321 insertpt=len(line)
322
323 lineat = int(self.text.index("insert").split('.')[1])
324
325 if insertpt == lineat:
326 insertpt = 0
327
328 dest = "insert linestart+"+str(insertpt)+"c"
329
330 if (event.state&1) == 0:
331 # shift not pressed
332 self.text.tag_remove("sel", "1.0", "end")
333 else:
334 if not self.text.index("sel.first"):
335 self.text.mark_set("anchor","insert")
336
337 first = self.text.index(dest)
338 last = self.text.index("anchor")
339
340 if self.text.compare(first,">",last):
341 first,last = last,first
342
343 self.text.tag_remove("sel", "1.0", "end")
344 self.text.tag_add("sel", first, last)
345
346 self.text.mark_set("insert", dest)
347 self.text.see("insert")
348 return "break"
349
David Scherer7aced172000-08-15 01:13:23 +0000350 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000351 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000352 if macosxSupport.runningAsOSXApp():
353 # Insert some padding to avoid obscuring some of the statusbar
354 # by the resize widget.
355 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000356 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
357 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
358 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000359 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
360 self.text.event_add("<<set-line-and-column>>",
361 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000362 self.text.after_idle(self.set_line_and_column)
363
364 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000365 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000366 self.status_bar.set_label('column', 'Col: %s' % column)
367 self.status_bar.set_label('line', 'Ln: %s' % line)
368
David Scherer7aced172000-08-15 01:13:23 +0000369 menu_specs = [
370 ("file", "_File"),
371 ("edit", "_Edit"),
372 ("format", "F_ormat"),
373 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000374 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000375 ("windows", "_Windows"),
376 ("help", "_Help"),
377 ]
378
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000379 if macosxSupport.runningAsOSXApp():
380 del menu_specs[-3]
381 menu_specs[-2] = ("windows", "_Window")
382
383
David Scherer7aced172000-08-15 01:13:23 +0000384 def createmenubar(self):
385 mbar = self.menubar
386 self.menudict = menudict = {}
387 for name, label in self.menu_specs:
388 underline, label = prepstr(label)
389 menudict[name] = menu = Menu(mbar, name=name)
390 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren827822e2009-02-12 15:01:44 +0000391 if macosxSupport.runningAsOSXApp():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000392 # Insert the application menu
393 menudict['application'] = menu = Menu(mbar, name='apple')
394 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000395 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000396 self.recent_files_menu = Menu(self.menubar)
397 self.menudict['file'].insert_cascade(3, label='Recent Files',
398 underline=0,
399 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000400 self.base_helpmenu_length = self.menudict['help'].index(END)
401 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000402
403 def postwindowsmenu(self):
404 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000405 menu = self.menudict['windows']
406 end = menu.index("end")
407 if end is None:
408 end = -1
409 if end > self.wmenu_end:
410 menu.delete(self.wmenu_end+1, end)
411 WindowList.add_windows_to_menu(menu)
412
413 rmenu = None
414
415 def right_menu_event(self, event):
416 self.text.tag_remove("sel", "1.0", "end")
417 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
418 if not self.rmenu:
419 self.make_rmenu()
420 rmenu = self.rmenu
421 self.event = event
422 iswin = sys.platform[:3] == 'win'
423 if iswin:
424 self.text.config(cursor="arrow")
425 rmenu.tk_popup(event.x_root, event.y_root)
426 if iswin:
427 self.text.config(cursor="ibeam")
428
429 rmenu_specs = [
430 # ("Label", "<<virtual-event>>"), ...
431 ("Close", "<<close-window>>"), # Example
432 ]
433
434 def make_rmenu(self):
435 rmenu = Menu(self.text, tearoff=0)
436 for label, eventname in self.rmenu_specs:
437 def command(text=self.text, eventname=eventname):
438 text.event_generate(eventname)
439 rmenu.add_command(label=label, command=command)
440 self.rmenu = rmenu
441
442 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000443 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000444
Steven M. Gava3b55a892001-11-21 05:56:26 +0000445 def config_dialog(self, event=None):
446 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000447
David Scherer7aced172000-08-15 01:13:23 +0000448 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000449 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000450 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000451
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000452 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000453 if sys.platform[:3] == 'win':
Terry Reedy574a3cf2011-01-01 02:28:54 +0000454 try:
455 os.startfile(self.help_url)
456 except WindowsError as why:
457 tkMessageBox.showerror(title='Document Start Failure',
458 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000459 else:
460 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000461 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000462
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000463 def cut(self,event):
464 self.text.event_generate("<<Cut>>")
465 return "break"
466
467 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000468 if not self.text.tag_ranges("sel"):
469 # There is no selection, so do nothing and maybe interrupt.
470 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000471 self.text.event_generate("<<Copy>>")
472 return "break"
473
474 def paste(self,event):
475 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000476 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000477 return "break"
478
David Scherer7aced172000-08-15 01:13:23 +0000479 def select_all(self, event=None):
480 self.text.tag_add("sel", "1.0", "end-1c")
481 self.text.mark_set("insert", "1.0")
482 self.text.see("insert")
483 return "break"
484
485 def remove_selection(self, event=None):
486 self.text.tag_remove("sel", "1.0", "end")
487 self.text.see("insert")
488
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000489 def move_at_edge_if_selection(self, edge_index):
490 """Cursor move begins at start or end of selection
491
492 When a left/right cursor key is pressed create and return to Tkinter a
493 function which causes a cursor move from the associated edge of the
494 selection.
495
496 """
497 self_text_index = self.text.index
498 self_text_mark_set = self.text.mark_set
499 edges_table = ("sel.first+1c", "sel.last-1c")
500 def move_at_edge(event):
501 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
502 try:
503 self_text_index("sel.first")
504 self_text_mark_set("insert", edges_table[edge_index])
505 except TclError:
506 pass
507 return move_at_edge
508
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000509 def del_word_left(self, event):
510 self.text.event_generate('<Meta-Delete>')
511 return "break"
512
513 def del_word_right(self, event):
514 self.text.event_generate('<Meta-d>')
515 return "break"
516
Steven M. Gavac5976402002-01-04 03:06:08 +0000517 def find_event(self, event):
518 SearchDialog.find(self.text)
519 return "break"
520
521 def find_again_event(self, event):
522 SearchDialog.find_again(self.text)
523 return "break"
524
525 def find_selection_event(self, event):
526 SearchDialog.find_selection(self.text)
527 return "break"
528
529 def find_in_files_event(self, event):
530 GrepDialog.grep(self.text, self.io, self.flist)
531 return "break"
532
533 def replace_event(self, event):
534 ReplaceDialog.replace(self.text)
535 return "break"
536
537 def goto_line_event(self, event):
538 text = self.text
539 lineno = tkSimpleDialog.askinteger("Goto",
540 "Go to line number:",parent=text)
541 if lineno is None:
542 return "break"
543 if lineno <= 0:
544 text.bell()
545 return "break"
546 text.mark_set("insert", "%d.0" % lineno)
547 text.see("insert")
548
David Scherer7aced172000-08-15 01:13:23 +0000549 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000550 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000551 try:
552 name = self.text.get("sel.first", "sel.last")
553 except TclError:
554 name = ""
555 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000556 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000557 name = tkSimpleDialog.askstring("Module",
558 "Enter the name of a Python module\n"
559 "to search on sys.path and open:",
560 parent=self.text, initialvalue=name)
561 if name:
562 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000563 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000564 return
David Scherer7aced172000-08-15 01:13:23 +0000565 # XXX Ought to insert current file's directory in front of path
566 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000567 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000568 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000569 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
570 return
571 if type != imp.PY_SOURCE:
572 tkMessageBox.showerror("Unsupported type",
573 "%s is not a source module" % name, parent=self.text)
574 return
575 if f:
576 f.close()
577 if self.flist:
578 self.flist.open(file)
579 else:
580 self.io.loadfile(file)
581
582 def open_class_browser(self, event=None):
583 filename = self.io.filename
584 if not filename:
585 tkMessageBox.showerror(
586 "No filename",
587 "This buffer has no associated filename",
588 master=self.text)
589 self.text.focus_set()
590 return None
591 head, tail = os.path.split(filename)
592 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000593 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000594 ClassBrowser.ClassBrowser(self.flist, base, [head])
595
596 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000597 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000598 PathBrowser.PathBrowser(self.flist)
599
600 def gotoline(self, lineno):
601 if lineno is not None and lineno > 0:
602 self.text.mark_set("insert", "%d.0" % lineno)
603 self.text.tag_remove("sel", "1.0", "end")
604 self.text.tag_add("sel", "insert", "insert +1l")
605 self.center()
606
607 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000608 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000609 return True
David Scherer7aced172000-08-15 01:13:23 +0000610 base, ext = os.path.splitext(os.path.basename(filename))
611 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000612 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000613 line = self.text.get('1.0', '1.0 lineend')
614 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000615
616 def close_hook(self):
617 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000618 self.flist.unregister_maybe_terminate(self)
619 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000620
621 def set_close_hook(self, close_hook):
622 self.close_hook = close_hook
623
624 def filename_change_hook(self):
625 if self.flist:
626 self.flist.filename_changed_edit(self)
627 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000628 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000629 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000630
Christian Heimesa156e092008-02-16 07:38:31 +0000631 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000632 if self.color:
633 return
Christian Heimesa156e092008-02-16 07:38:31 +0000634 if self.ispythonsource(self.io.filename):
635 self.color = self.ColorDelegator()
636 # can add more colorizers here...
637 if self.color:
638 self.per.removefilter(self.undo)
639 self.per.insertfilter(self.color)
640 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000641
Christian Heimesa156e092008-02-16 07:38:31 +0000642 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000643 if not self.color:
644 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000645 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000646 self.per.removefilter(self.color)
647 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000648
Steven M. Gavab77d3432002-03-02 07:16:21 +0000649 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000650 "Update the colour theme"
651 # Called from self.filename_change_hook and from configDialog.py
652 self._rmcolorizer()
653 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000654 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000655 normal_colors = idleConf.GetHighlight(theme, 'normal')
656 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
657 select_colors = idleConf.GetHighlight(theme, 'hilite')
658 self.text.config(
659 foreground=normal_colors['foreground'],
660 background=normal_colors['background'],
661 insertbackground=cursor_color,
662 selectforeground=select_colors['foreground'],
663 selectbackground=select_colors['background'],
664 )
David Scherer7aced172000-08-15 01:13:23 +0000665
Guido van Rossum33d26892007-08-05 15:29:28 +0000666 IDENTCHARS = string.ascii_letters + string.digits + "_"
667
668 def colorize_syntax_error(self, text, pos):
669 text.tag_add("ERROR", pos)
670 char = text.get(pos)
671 if char and char in self.IDENTCHARS:
672 text.tag_add("ERROR", pos + " wordstart", pos)
673 if '\n' == text.get(pos): # error at line end
674 text.mark_set("insert", pos)
675 else:
676 text.mark_set("insert", pos + "+1c")
677 text.see(pos)
678
Steven M. Gavab1585412002-03-12 00:21:56 +0000679 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000680 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000681 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000682 fontWeight='normal'
683 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
684 fontWeight='bold'
685 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
686 idleConf.GetOption('main','EditorWindow','font-size'),
687 fontWeight))
688
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000689 def RemoveKeybindings(self):
690 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000691 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000692 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000693 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000694 self.text.event_delete(event, *keylist)
695 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000696 xkeydefs = idleConf.GetExtensionBindings(extensionName)
697 if xkeydefs:
698 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000699 self.text.event_delete(event, *keylist)
700
701 def ApplyKeybindings(self):
702 "Update the keybindings after they are changed"
703 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000704 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000705 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000706 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000707 xkeydefs = idleConf.GetExtensionBindings(extensionName)
708 if xkeydefs:
709 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000710 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000711 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000712 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000713 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000714 for item in menu[1]:
715 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000716 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000717 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000718 menu = self.menudict[menubarItem]
719 end = menu.index(END) + 1
720 for index in range(0, end):
721 if menu.type(index) == 'command':
722 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000723 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000724 itemName = menu.entrycget(index, 'label')
725 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000726 if menubarItem in menuEventDict:
727 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000728 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000729 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000730 accel = get_accelerator(keydefs, event)
731 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000732
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000733 def set_notabs_indentwidth(self):
734 "Update the indentwidth if changed and not using tabs in this window"
735 # Called from configDialog.py
736 if not self.usetabs:
737 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
738 type='int')
739
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000740 def reset_help_menu_entries(self):
741 "Update the additional help entries on the Help menu"
742 help_list = idleConf.GetAllExtraHelpSourcesList()
743 helpmenu = self.menudict['help']
744 # first delete the extra help entries, if any
745 helpmenu_length = helpmenu.index(END)
746 if helpmenu_length > self.base_helpmenu_length:
747 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
748 # then rebuild them
749 if help_list:
750 helpmenu.add_separator()
751 for entry in help_list:
752 cmd = self.__extra_help_callback(entry[1])
753 helpmenu.add_command(label=entry[0], command=cmd)
754 # and update the menu dictionary
755 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000756
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000757 def __extra_help_callback(self, helpfile):
758 "Create a callback with the helpfile value frozen at definition time"
759 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000760 if not helpfile.startswith(('www', 'http')):
Terry Reedy574a3cf2011-01-01 02:28:54 +0000761 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000762 if sys.platform[:3] == 'win':
Terry Reedy574a3cf2011-01-01 02:28:54 +0000763 try:
764 os.startfile(helpfile)
765 except WindowsError as why:
766 tkMessageBox.showerror(title='Document Start Failure',
767 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000768 else:
769 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000770 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000771
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000772 def update_recent_files_list(self, new_file=None):
773 "Load and update the recent files list and menus"
774 rf_list = []
775 if os.path.exists(self.recent_files_path):
Ned Deilyab5dd002011-01-24 22:22:06 +0000776 rf_list_file = open(self.recent_files_path,'r',
777 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000778 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000779 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000780 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000781 rf_list_file.close()
782 if new_file:
783 new_file = os.path.abspath(new_file) + '\n'
784 if new_file in rf_list:
785 rf_list.remove(new_file) # move to top
786 rf_list.insert(0, new_file)
787 # clean and save the recent files list
788 bad_paths = []
789 for path in rf_list:
790 if '\0' in path or not os.path.exists(path[0:-1]):
791 bad_paths.append(path)
792 rf_list = [path for path in rf_list if path not in bad_paths]
793 ulchars = "1234567890ABCDEFGHIJK"
794 rf_list = rf_list[0:len(ulchars)]
Ned Deilyab5dd002011-01-24 22:22:06 +0000795 rf_file = open(self.recent_files_path, 'w',
796 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000797 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000798 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000799 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000800 rf_file.close()
801 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000802 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000803 menu = instance.recent_files_menu
804 menu.delete(1, END) # clear, and rebuild:
805 for i, file in zip(count(), rf_list):
806 file_name = file[0:-1] # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000807 # make unicode string to display non-ASCII chars correctly
808 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000809 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000810 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000811 command=callback,
812 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000813
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000814 def __recent_file_callback(self, file_name):
815 def open_recent_file(fn_closure=file_name):
816 self.io.open(editFile=fn_closure)
817 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000818
David Scherer7aced172000-08-15 01:13:23 +0000819 def saved_change_hook(self):
820 short = self.short_title()
821 long = self.long_title()
822 if short and long:
823 title = short + " - " + long
824 elif short:
825 title = short
826 elif long:
827 title = long
828 else:
829 title = "Untitled"
830 icon = short or long or title
831 if not self.get_saved():
832 title = "*%s*" % title
833 icon = "*%s" % icon
834 self.top.wm_title(title)
835 self.top.wm_iconname(icon)
836
837 def get_saved(self):
838 return self.undo.get_saved()
839
840 def set_saved(self, flag):
841 self.undo.set_saved(flag)
842
843 def reset_undo(self):
844 self.undo.reset_undo()
845
846 def short_title(self):
847 filename = self.io.filename
848 if filename:
849 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000850 # return unicode string to display non-ASCII chars correctly
851 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000852
853 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000854 # return unicode string to display non-ASCII chars correctly
855 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000856
857 def center_insert_event(self, event):
858 self.center()
859
860 def center(self, mark="insert"):
861 text = self.text
862 top, bot = self.getwindowlines()
863 lineno = self.getlineno(mark)
864 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000865 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000866 text.yview(float(newtop))
867
868 def getwindowlines(self):
869 text = self.text
870 top = self.getlineno("@0,0")
871 bot = self.getlineno("@0,65535")
872 if top == bot and text.winfo_height() == 1:
873 # Geometry manager hasn't run yet
874 height = int(text['height'])
875 bot = top + height - 1
876 return top, bot
877
878 def getlineno(self, mark="insert"):
879 text = self.text
880 return int(float(text.index(mark)))
881
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000882 def get_geometry(self):
883 "Return (width, height, x, y)"
884 geom = self.top.wm_geometry()
885 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000886 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000887
David Scherer7aced172000-08-15 01:13:23 +0000888 def close_event(self, event):
889 self.close()
890
891 def maybesave(self):
892 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000893 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000894 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000895 self.top.deiconify()
896 self.top.lower()
897 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000898 return self.io.maybesave()
899
900 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000901 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000902 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000903 self._close()
904 return reply
905
906 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000907 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000908 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000909 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000910 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000911 self.io.close()
912 self.io = None
913 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000914 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000915 self.color.close(False)
916 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000917 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000918 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000919 self.per.close()
920 self.per = None
921 self.top.destroy()
922 if self.close_hook:
923 # unless override: unregister from flist, terminate if last window
924 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000925
926 def load_extensions(self):
927 self.extensions = {}
928 self.load_standard_extensions()
929
930 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000931 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000932 if hasattr(ins, "close"):
933 ins.close()
934 self.extensions = {}
935
936 def load_standard_extensions(self):
937 for name in self.get_standard_extension_names():
938 try:
939 self.load_extension(name)
940 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000941 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000942 traceback.print_exc()
943
944 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000945 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000946
947 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000948 try:
949 mod = __import__(name, globals(), locals(), [])
950 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000951 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000952 raise
David Scherer7aced172000-08-15 01:13:23 +0000953 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000954 keydefs = idleConf.GetExtensionBindings(name)
955 if hasattr(cls, "menudefs"):
956 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000957 ins = cls(self)
958 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000959 if keydefs:
960 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000961 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000962 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000963 while methodname[:1] == '<':
964 methodname = methodname[1:]
965 while methodname[-1:] == '>':
966 methodname = methodname[:-1]
967 methodname = methodname + "_event"
968 if hasattr(ins, methodname):
969 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000970
971 def apply_bindings(self, keydefs=None):
972 if keydefs is None:
973 keydefs = self.Bindings.default_keydefs
974 text = self.text
975 text.keydefs = keydefs
976 for event, keylist in keydefs.items():
977 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000978 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000979
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000980 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000981 """Add appropriate entries to the menus and submenus
982
983 Menus that are absent or None in self.menudict are ignored.
984 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000985 if menudefs is None:
986 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000987 if keydefs is None:
988 keydefs = self.Bindings.default_keydefs
989 menudict = self.menudict
990 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000991 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000992 menu = menudict.get(mname)
993 if not menu:
994 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000995 for entry in entrylist:
996 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000997 menu.add_separator()
998 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000999 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +00001000 checkbutton = (label[:1] == '!')
1001 if checkbutton:
1002 label = label[1:]
1003 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001004 accelerator = get_accelerator(keydefs, eventname)
1005 def command(text=text, eventname=eventname):
1006 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001007 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001008 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001009 menu.add_checkbutton(label=label, underline=underline,
1010 command=command, accelerator=accelerator,
1011 variable=var)
1012 else:
1013 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001014 command=command,
1015 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001016
1017 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001018 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001019 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001020 value = var.get()
1021 return value
1022 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001023 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001024
1025 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001026 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001027 if var:
1028 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001029 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001030 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001031
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001032 def get_var_obj(self, name, vartype=None):
1033 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001034 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001035 # create a Tkinter variable object with self.text as master:
1036 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001037 return var
1038
1039 # Tk implementations of "virtual text methods" -- each platform
1040 # reusing IDLE's support code needs to define these for its GUI's
1041 # flavor of widget.
1042
1043 # Is character at text_index in a Python string? Return 0 for
1044 # "guaranteed no", true for anything else. This info is expensive
1045 # to compute ab initio, but is probably already known by the
1046 # platform's colorizer.
1047
1048 def is_char_in_string(self, text_index):
1049 if self.color:
1050 # Return true iff colorizer hasn't (re)gotten this far
1051 # yet, or the character is tagged as being in a string
1052 return self.text.tag_prevrange("TODO", text_index) or \
1053 "STRING" in self.text.tag_names(text_index)
1054 else:
1055 # The colorizer is missing: assume the worst
1056 return 1
1057
1058 # If a selection is defined in the text widget, return (start,
1059 # end) as Tkinter text indices, otherwise return (None, None)
1060 def get_selection_indices(self):
1061 try:
1062 first = self.text.index("sel.first")
1063 last = self.text.index("sel.last")
1064 return first, last
1065 except TclError:
1066 return None, None
1067
1068 # Return the text widget's current view of what a tab stop means
1069 # (equivalent width in spaces).
1070
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001071 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001072 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1073 return int(current)
1074
1075 # Set the text widget's current view of what a tab stop means.
1076
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001077 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001078 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001079 if self.get_tk_tabwidth() != newtabwidth:
1080 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001081 pixels = text.tk.call("font", "measure", text["font"],
1082 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001083 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001084 text.configure(tabs=pixels)
1085
Guido van Rossum33d26892007-08-05 15:29:28 +00001086### begin autoindent code ### (configuration was moved to beginning of class)
1087
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001088 def set_indentation_params(self, is_py_src, guess=True):
1089 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001090 i = self.guess_indent()
1091 if 2 <= i <= 8:
1092 self.indentwidth = i
1093 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001094 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001095 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001096
1097 def smart_backspace_event(self, event):
1098 text = self.text
1099 first, last = self.get_selection_indices()
1100 if first and last:
1101 text.delete(first, last)
1102 text.mark_set("insert", first)
1103 return "break"
1104 # Delete whitespace left, until hitting a real char or closest
1105 # preceding virtual tab stop.
1106 chars = text.get("insert linestart", "insert")
1107 if chars == '':
1108 if text.compare("insert", ">", "1.0"):
1109 # easy: delete preceding newline
1110 text.delete("insert-1c")
1111 else:
1112 text.bell() # at start of buffer
1113 return "break"
1114 if chars[-1] not in " \t":
1115 # easy: delete preceding real char
1116 text.delete("insert-1c")
1117 return "break"
1118 # Ick. It may require *inserting* spaces if we back up over a
1119 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001120 tabwidth = self.tabwidth
1121 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001122 assert have > 0
1123 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001124 # Debug prompt is multilined....
1125 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001126 ncharsdeleted = 0
1127 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001128 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001129 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001130 chars = chars[:-1]
1131 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001132 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001133 if have <= want or chars[-1] not in " \t":
1134 break
1135 text.undo_block_start()
1136 text.delete("insert-%dc" % ncharsdeleted, "insert")
1137 if have < want:
1138 text.insert("insert", ' ' * (want - have))
1139 text.undo_block_stop()
1140 return "break"
1141
1142 def smart_indent_event(self, event):
1143 # if intraline selection:
1144 # delete it
1145 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001146 # do indent-region
1147 # else:
1148 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001149 text = self.text
1150 first, last = self.get_selection_indices()
1151 text.undo_block_start()
1152 try:
1153 if first and last:
1154 if index2line(first) != index2line(last):
1155 return self.indent_region_event(event)
1156 text.delete(first, last)
1157 text.mark_set("insert", first)
1158 prefix = text.get("insert linestart", "insert")
1159 raw, effective = classifyws(prefix, self.tabwidth)
1160 if raw == len(prefix):
1161 # only whitespace to the left
1162 self.reindent_to(effective + self.indentwidth)
1163 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001164 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001165 if self.usetabs:
1166 pad = '\t'
1167 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001168 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001169 n = self.indentwidth
1170 pad = ' ' * (n - effective % n)
1171 text.insert("insert", pad)
1172 text.see("insert")
1173 return "break"
1174 finally:
1175 text.undo_block_stop()
1176
1177 def newline_and_indent_event(self, event):
1178 text = self.text
1179 first, last = self.get_selection_indices()
1180 text.undo_block_start()
1181 try:
1182 if first and last:
1183 text.delete(first, last)
1184 text.mark_set("insert", first)
1185 line = text.get("insert linestart", "insert")
1186 i, n = 0, len(line)
1187 while i < n and line[i] in " \t":
1188 i = i+1
1189 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001190 # the cursor is in or at leading indentation in a continuation
1191 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001192 text.insert("insert linestart", '\n')
1193 return "break"
1194 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001195 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001196 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001197 last_line_of_prompt = sys.ps1.split('\n')[-1]
1198 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001199 line = line[:-1]
1200 i = i+1
1201 if i:
1202 text.delete("insert - %d chars" % i, "insert")
1203 # strip whitespace after insert point
1204 while text.get("insert") in " \t":
1205 text.delete("insert")
1206 # start new line
1207 text.insert("insert", '\n')
1208
1209 # adjust indentation for continuations and block
1210 # open/close first need to find the last stmt
1211 lno = index2line(text.index('insert'))
1212 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001213 if not self.context_use_ps1:
1214 for context in self.num_context_lines:
1215 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001216 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001217 rawtext = text.get(startatindex, "insert")
1218 y.set_str(rawtext)
1219 bod = y.find_good_parse_start(
1220 self.context_use_ps1,
1221 self._build_char_in_string_func(startatindex))
1222 if bod is not None or startat == 1:
1223 break
1224 y.set_lo(bod or 0)
1225 else:
1226 r = text.tag_prevrange("console", "insert")
1227 if r:
1228 startatindex = r[1]
1229 else:
1230 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001231 rawtext = text.get(startatindex, "insert")
1232 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001233 y.set_lo(0)
1234
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001235 c = y.get_continuation_type()
1236 if c != PyParse.C_NONE:
1237 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001238 if c == PyParse.C_STRING_FIRST_LINE:
1239 # after the first line of a string; do not indent at all
1240 pass
1241 elif c == PyParse.C_STRING_NEXT_LINES:
1242 # inside a string which started before this line;
1243 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001244 text.insert("insert", indent)
1245 elif c == PyParse.C_BRACKET:
1246 # line up with the first (if any) element of the
1247 # last open bracket structure; else indent one
1248 # level beyond the indent of the line with the
1249 # last open bracket
1250 self.reindent_to(y.compute_bracket_indent())
1251 elif c == PyParse.C_BACKSLASH:
1252 # if more than one line in this stmt already, just
1253 # mimic the current indent; else if initial line
1254 # has a start on an assignment stmt, indent to
1255 # beyond leftmost =; else to beyond first chunk of
1256 # non-whitespace on initial line
1257 if y.get_num_lines_in_stmt() > 1:
1258 text.insert("insert", indent)
1259 else:
1260 self.reindent_to(y.compute_backslash_indent())
1261 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001262 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001263 return "break"
1264
1265 # This line starts a brand new stmt; indent relative to
1266 # indentation of initial line of closest preceding
1267 # interesting stmt.
1268 indent = y.get_base_indent_string()
1269 text.insert("insert", indent)
1270 if y.is_block_opener():
1271 self.smart_indent_event(event)
1272 elif indent and y.is_block_closer():
1273 self.smart_backspace_event(event)
1274 return "break"
1275 finally:
1276 text.see("insert")
1277 text.undo_block_stop()
1278
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001279 # Our editwin provides a is_char_in_string function that works
1280 # with a Tk text index, but PyParse only knows about offsets into
1281 # a string. This builds a function for PyParse that accepts an
1282 # offset.
1283
1284 def _build_char_in_string_func(self, startindex):
1285 def inner(offset, _startindex=startindex,
1286 _icis=self.is_char_in_string):
1287 return _icis(_startindex + "+%dc" % offset)
1288 return inner
1289
1290 def indent_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 = effective + self.indentwidth
1297 lines[pos] = self._make_blanks(effective) + line[raw:]
1298 self.set_region(head, tail, chars, lines)
1299 return "break"
1300
1301 def dedent_region_event(self, event):
1302 head, tail, chars, lines = self.get_region()
1303 for pos in range(len(lines)):
1304 line = lines[pos]
1305 if line:
1306 raw, effective = classifyws(line, self.tabwidth)
1307 effective = max(effective - self.indentwidth, 0)
1308 lines[pos] = self._make_blanks(effective) + line[raw:]
1309 self.set_region(head, tail, chars, lines)
1310 return "break"
1311
1312 def comment_region_event(self, event):
1313 head, tail, chars, lines = self.get_region()
1314 for pos in range(len(lines) - 1):
1315 line = lines[pos]
1316 lines[pos] = '##' + line
1317 self.set_region(head, tail, chars, lines)
1318
1319 def uncomment_region_event(self, event):
1320 head, tail, chars, lines = self.get_region()
1321 for pos in range(len(lines)):
1322 line = lines[pos]
1323 if not line:
1324 continue
1325 if line[:2] == '##':
1326 line = line[2:]
1327 elif line[:1] == '#':
1328 line = line[1:]
1329 lines[pos] = line
1330 self.set_region(head, tail, chars, lines)
1331
1332 def tabify_region_event(self, event):
1333 head, tail, chars, lines = self.get_region()
1334 tabwidth = self._asktabwidth()
1335 for pos in range(len(lines)):
1336 line = lines[pos]
1337 if line:
1338 raw, effective = classifyws(line, tabwidth)
1339 ntabs, nspaces = divmod(effective, tabwidth)
1340 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1341 self.set_region(head, tail, chars, lines)
1342
1343 def untabify_region_event(self, event):
1344 head, tail, chars, lines = self.get_region()
1345 tabwidth = self._asktabwidth()
1346 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001347 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001348 self.set_region(head, tail, chars, lines)
1349
1350 def toggle_tabs_event(self, event):
1351 if self.askyesno(
1352 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001353 "Turn tabs " + ("on", "off")[self.usetabs] +
1354 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001355 ("will be", "remains at")[self.usetabs] + " 8." +
1356 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001357 parent=self.text):
1358 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001359 # Try to prevent inconsistent indentation.
1360 # User must change indent width manually after using tabs.
1361 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001362 return "break"
1363
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001364 # XXX this isn't bound to anything -- see tabwidth comments
1365## def change_tabwidth_event(self, event):
1366## new = self._asktabwidth()
1367## if new != self.tabwidth:
1368## self.tabwidth = new
1369## self.set_indentation_params(0, guess=0)
1370## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001371
1372 def change_indentwidth_event(self, event):
1373 new = self.askinteger(
1374 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001375 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001376 parent=self.text,
1377 initialvalue=self.indentwidth,
1378 minvalue=2,
1379 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001380 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001381 self.indentwidth = new
1382 return "break"
1383
1384 def get_region(self):
1385 text = self.text
1386 first, last = self.get_selection_indices()
1387 if first and last:
1388 head = text.index(first + " linestart")
1389 tail = text.index(last + "-1c lineend +1c")
1390 else:
1391 head = text.index("insert linestart")
1392 tail = text.index("insert lineend +1c")
1393 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001394 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001395 return head, tail, chars, lines
1396
1397 def set_region(self, head, tail, chars, lines):
1398 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001399 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001400 if newchars == chars:
1401 text.bell()
1402 return
1403 text.tag_remove("sel", "1.0", "end")
1404 text.mark_set("insert", head)
1405 text.undo_block_start()
1406 text.delete(head, tail)
1407 text.insert(head, newchars)
1408 text.undo_block_stop()
1409 text.tag_add("sel", head, "insert")
1410
1411 # Make string that displays as n leading blanks.
1412
1413 def _make_blanks(self, n):
1414 if self.usetabs:
1415 ntabs, nspaces = divmod(n, self.tabwidth)
1416 return '\t' * ntabs + ' ' * nspaces
1417 else:
1418 return ' ' * n
1419
1420 # Delete from beginning of line to insert point, then reinsert
1421 # column logical (meaning use tabs if appropriate) spaces.
1422
1423 def reindent_to(self, column):
1424 text = self.text
1425 text.undo_block_start()
1426 if text.compare("insert linestart", "!=", "insert"):
1427 text.delete("insert linestart", "insert")
1428 if column:
1429 text.insert("insert", self._make_blanks(column))
1430 text.undo_block_stop()
1431
1432 def _asktabwidth(self):
1433 return self.askinteger(
1434 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001435 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001436 parent=self.text,
1437 initialvalue=self.indentwidth,
1438 minvalue=2,
1439 maxvalue=16) or self.tabwidth
1440
1441 # Guess indentwidth from text content.
1442 # Return guessed indentwidth. This should not be believed unless
1443 # it's in a reasonable range (e.g., it will be 0 if no indented
1444 # blocks are found).
1445
1446 def guess_indent(self):
1447 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1448 if opener and indented:
1449 raw, indentsmall = classifyws(opener, self.tabwidth)
1450 raw, indentlarge = classifyws(indented, self.tabwidth)
1451 else:
1452 indentsmall = indentlarge = 0
1453 return indentlarge - indentsmall
1454
1455# "line.col" -> line, as an int
1456def index2line(index):
1457 return int(float(index))
1458
1459# Look at the leading whitespace in s.
1460# Return pair (# of leading ws characters,
1461# effective # of leading blanks after expanding
1462# tabs to width tabwidth)
1463
1464def classifyws(s, tabwidth):
1465 raw = effective = 0
1466 for ch in s:
1467 if ch == ' ':
1468 raw = raw + 1
1469 effective = effective + 1
1470 elif ch == '\t':
1471 raw = raw + 1
1472 effective = (effective // tabwidth + 1) * tabwidth
1473 else:
1474 break
1475 return raw, effective
1476
1477import tokenize
1478_tokenize = tokenize
1479del tokenize
1480
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001481class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001482
1483 # .run() chews over the Text widget, looking for a block opener
1484 # and the stmt following it. Returns a pair,
1485 # (line containing block opener, line containing stmt)
1486 # Either or both may be None.
1487
1488 def __init__(self, text, tabwidth):
1489 self.text = text
1490 self.tabwidth = tabwidth
1491 self.i = self.finished = 0
1492 self.blkopenline = self.indentedline = None
1493
1494 def readline(self):
1495 if self.finished:
1496 return ""
1497 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001498 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001499 if self.text.compare(mark, ">=", "end"):
1500 return ""
1501 return self.text.get(mark, mark + " lineend+1c")
1502
1503 def tokeneater(self, type, token, start, end, line,
1504 INDENT=_tokenize.INDENT,
1505 NAME=_tokenize.NAME,
1506 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1507 if self.finished:
1508 pass
1509 elif type == NAME and token in OPENERS:
1510 self.blkopenline = line
1511 elif type == INDENT and self.blkopenline:
1512 self.indentedline = line
1513 self.finished = 1
1514
1515 def run(self):
1516 save_tabsize = _tokenize.tabsize
1517 _tokenize.tabsize = self.tabwidth
1518 try:
1519 try:
Trent Nelson428de652008-03-18 22:41:35 +00001520 tokens = _tokenize.generate_tokens(self.readline)
1521 for token in tokens:
1522 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001523 except _tokenize.TokenError:
1524 # since we cut off the tokenizer early, we can trigger
1525 # spurious errors
1526 pass
1527 finally:
1528 _tokenize.tabsize = save_tabsize
1529 return self.blkopenline, self.indentedline
1530
1531### end autoindent code ###
1532
David Scherer7aced172000-08-15 01:13:23 +00001533def prepstr(s):
1534 # Helper to extract the underscore from a string, e.g.
1535 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001536 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001537 if i >= 0:
1538 s = s[:i] + s[i+1:]
1539 return i, s
1540
1541
1542keynames = {
1543 'bracketleft': '[',
1544 'bracketright': ']',
1545 'slash': '/',
1546}
1547
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001548def get_accelerator(keydefs, eventname):
1549 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001550 if not keylist:
1551 return ""
1552 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001553 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001554 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1555 s = re.sub("Key-", "", s)
1556 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1557 s = re.sub("Control-", "Ctrl-", s)
1558 s = re.sub("-", "+", s)
1559 s = re.sub("><", " ", s)
1560 s = re.sub("<", "", s)
1561 s = re.sub(">", "", s)
1562 return s
1563
1564
1565def fixwordbreaks(root):
1566 # Make sure that Tk's double-click and next/previous word
1567 # operations use our definition of a word (i.e. an identifier)
1568 tk = root.tk
1569 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1570 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1571 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1572
1573
1574def test():
1575 root = Tk()
1576 fixwordbreaks(root)
1577 root.withdraw()
1578 if sys.argv[1:]:
1579 filename = sys.argv[1]
1580 else:
1581 filename = None
1582 edit = EditorWindow(root=root, filename=filename)
1583 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001584 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001585 root.mainloop()
1586 root.destroy()
1587
1588if __name__ == '__main__':
1589 test()