blob: 2cbf9c339bd52657b4ed143df5045f7fb346af75 [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':
Steven M. Gava931625d2002-04-22 00:38:26 +0000454 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000455 else:
456 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000457 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000458
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000459 def cut(self,event):
460 self.text.event_generate("<<Cut>>")
461 return "break"
462
463 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000464 if not self.text.tag_ranges("sel"):
465 # There is no selection, so do nothing and maybe interrupt.
466 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000467 self.text.event_generate("<<Copy>>")
468 return "break"
469
470 def paste(self,event):
471 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000472 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000473 return "break"
474
David Scherer7aced172000-08-15 01:13:23 +0000475 def select_all(self, event=None):
476 self.text.tag_add("sel", "1.0", "end-1c")
477 self.text.mark_set("insert", "1.0")
478 self.text.see("insert")
479 return "break"
480
481 def remove_selection(self, event=None):
482 self.text.tag_remove("sel", "1.0", "end")
483 self.text.see("insert")
484
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000485 def move_at_edge_if_selection(self, edge_index):
486 """Cursor move begins at start or end of selection
487
488 When a left/right cursor key is pressed create and return to Tkinter a
489 function which causes a cursor move from the associated edge of the
490 selection.
491
492 """
493 self_text_index = self.text.index
494 self_text_mark_set = self.text.mark_set
495 edges_table = ("sel.first+1c", "sel.last-1c")
496 def move_at_edge(event):
497 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
498 try:
499 self_text_index("sel.first")
500 self_text_mark_set("insert", edges_table[edge_index])
501 except TclError:
502 pass
503 return move_at_edge
504
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000505 def del_word_left(self, event):
506 self.text.event_generate('<Meta-Delete>')
507 return "break"
508
509 def del_word_right(self, event):
510 self.text.event_generate('<Meta-d>')
511 return "break"
512
Steven M. Gavac5976402002-01-04 03:06:08 +0000513 def find_event(self, event):
514 SearchDialog.find(self.text)
515 return "break"
516
517 def find_again_event(self, event):
518 SearchDialog.find_again(self.text)
519 return "break"
520
521 def find_selection_event(self, event):
522 SearchDialog.find_selection(self.text)
523 return "break"
524
525 def find_in_files_event(self, event):
526 GrepDialog.grep(self.text, self.io, self.flist)
527 return "break"
528
529 def replace_event(self, event):
530 ReplaceDialog.replace(self.text)
531 return "break"
532
533 def goto_line_event(self, event):
534 text = self.text
535 lineno = tkSimpleDialog.askinteger("Goto",
536 "Go to line number:",parent=text)
537 if lineno is None:
538 return "break"
539 if lineno <= 0:
540 text.bell()
541 return "break"
542 text.mark_set("insert", "%d.0" % lineno)
543 text.see("insert")
544
David Scherer7aced172000-08-15 01:13:23 +0000545 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000546 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000547 try:
548 name = self.text.get("sel.first", "sel.last")
549 except TclError:
550 name = ""
551 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000552 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000553 name = tkSimpleDialog.askstring("Module",
554 "Enter the name of a Python module\n"
555 "to search on sys.path and open:",
556 parent=self.text, initialvalue=name)
557 if name:
558 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000559 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000560 return
David Scherer7aced172000-08-15 01:13:23 +0000561 # XXX Ought to insert current file's directory in front of path
562 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000563 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000564 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000565 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
566 return
567 if type != imp.PY_SOURCE:
568 tkMessageBox.showerror("Unsupported type",
569 "%s is not a source module" % name, parent=self.text)
570 return
571 if f:
572 f.close()
573 if self.flist:
574 self.flist.open(file)
575 else:
576 self.io.loadfile(file)
577
578 def open_class_browser(self, event=None):
579 filename = self.io.filename
580 if not filename:
581 tkMessageBox.showerror(
582 "No filename",
583 "This buffer has no associated filename",
584 master=self.text)
585 self.text.focus_set()
586 return None
587 head, tail = os.path.split(filename)
588 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000589 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000590 ClassBrowser.ClassBrowser(self.flist, base, [head])
591
592 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000593 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000594 PathBrowser.PathBrowser(self.flist)
595
596 def gotoline(self, lineno):
597 if lineno is not None and lineno > 0:
598 self.text.mark_set("insert", "%d.0" % lineno)
599 self.text.tag_remove("sel", "1.0", "end")
600 self.text.tag_add("sel", "insert", "insert +1l")
601 self.center()
602
603 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000604 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000605 return True
David Scherer7aced172000-08-15 01:13:23 +0000606 base, ext = os.path.splitext(os.path.basename(filename))
607 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000608 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000609 line = self.text.get('1.0', '1.0 lineend')
610 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000611
612 def close_hook(self):
613 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000614 self.flist.unregister_maybe_terminate(self)
615 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000616
617 def set_close_hook(self, close_hook):
618 self.close_hook = close_hook
619
620 def filename_change_hook(self):
621 if self.flist:
622 self.flist.filename_changed_edit(self)
623 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000624 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000625 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000626
Christian Heimesa156e092008-02-16 07:38:31 +0000627 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000628 if self.color:
629 return
Christian Heimesa156e092008-02-16 07:38:31 +0000630 if self.ispythonsource(self.io.filename):
631 self.color = self.ColorDelegator()
632 # can add more colorizers here...
633 if self.color:
634 self.per.removefilter(self.undo)
635 self.per.insertfilter(self.color)
636 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000637
Christian Heimesa156e092008-02-16 07:38:31 +0000638 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000639 if not self.color:
640 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000641 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000642 self.per.removefilter(self.color)
643 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000644
Steven M. Gavab77d3432002-03-02 07:16:21 +0000645 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000646 "Update the colour theme"
647 # Called from self.filename_change_hook and from configDialog.py
648 self._rmcolorizer()
649 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000650 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000651 normal_colors = idleConf.GetHighlight(theme, 'normal')
652 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
653 select_colors = idleConf.GetHighlight(theme, 'hilite')
654 self.text.config(
655 foreground=normal_colors['foreground'],
656 background=normal_colors['background'],
657 insertbackground=cursor_color,
658 selectforeground=select_colors['foreground'],
659 selectbackground=select_colors['background'],
660 )
David Scherer7aced172000-08-15 01:13:23 +0000661
Guido van Rossum33d26892007-08-05 15:29:28 +0000662 IDENTCHARS = string.ascii_letters + string.digits + "_"
663
664 def colorize_syntax_error(self, text, pos):
665 text.tag_add("ERROR", pos)
666 char = text.get(pos)
667 if char and char in self.IDENTCHARS:
668 text.tag_add("ERROR", pos + " wordstart", pos)
669 if '\n' == text.get(pos): # error at line end
670 text.mark_set("insert", pos)
671 else:
672 text.mark_set("insert", pos + "+1c")
673 text.see(pos)
674
Steven M. Gavab1585412002-03-12 00:21:56 +0000675 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000676 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000677 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000678 fontWeight='normal'
679 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
680 fontWeight='bold'
681 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
682 idleConf.GetOption('main','EditorWindow','font-size'),
683 fontWeight))
684
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000685 def RemoveKeybindings(self):
686 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000687 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000688 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000689 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000690 self.text.event_delete(event, *keylist)
691 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000692 xkeydefs = idleConf.GetExtensionBindings(extensionName)
693 if xkeydefs:
694 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000695 self.text.event_delete(event, *keylist)
696
697 def ApplyKeybindings(self):
698 "Update the keybindings after they are changed"
699 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000700 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000701 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000702 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000703 xkeydefs = idleConf.GetExtensionBindings(extensionName)
704 if xkeydefs:
705 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000706 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000707 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000708 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000709 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000710 for item in menu[1]:
711 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000712 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000713 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000714 menu = self.menudict[menubarItem]
715 end = menu.index(END) + 1
716 for index in range(0, end):
717 if menu.type(index) == 'command':
718 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000719 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000720 itemName = menu.entrycget(index, 'label')
721 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000722 if menubarItem in menuEventDict:
723 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000724 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000725 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000726 accel = get_accelerator(keydefs, event)
727 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000728
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000729 def set_notabs_indentwidth(self):
730 "Update the indentwidth if changed and not using tabs in this window"
731 # Called from configDialog.py
732 if not self.usetabs:
733 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
734 type='int')
735
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000736 def reset_help_menu_entries(self):
737 "Update the additional help entries on the Help menu"
738 help_list = idleConf.GetAllExtraHelpSourcesList()
739 helpmenu = self.menudict['help']
740 # first delete the extra help entries, if any
741 helpmenu_length = helpmenu.index(END)
742 if helpmenu_length > self.base_helpmenu_length:
743 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
744 # then rebuild them
745 if help_list:
746 helpmenu.add_separator()
747 for entry in help_list:
748 cmd = self.__extra_help_callback(entry[1])
749 helpmenu.add_command(label=entry[0], command=cmd)
750 # and update the menu dictionary
751 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000752
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000753 def __extra_help_callback(self, helpfile):
754 "Create a callback with the helpfile value frozen at definition time"
755 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000756 if not helpfile.startswith(('www', 'http')):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000757 url = os.path.normpath(helpfile)
758 if sys.platform[:3] == 'win':
759 os.startfile(helpfile)
760 else:
761 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000762 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000763
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000764 def update_recent_files_list(self, new_file=None):
765 "Load and update the recent files list and menus"
766 rf_list = []
767 if os.path.exists(self.recent_files_path):
768 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000769 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000770 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000771 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000772 rf_list_file.close()
773 if new_file:
774 new_file = os.path.abspath(new_file) + '\n'
775 if new_file in rf_list:
776 rf_list.remove(new_file) # move to top
777 rf_list.insert(0, new_file)
778 # clean and save the recent files list
779 bad_paths = []
780 for path in rf_list:
781 if '\0' in path or not os.path.exists(path[0:-1]):
782 bad_paths.append(path)
783 rf_list = [path for path in rf_list if path not in bad_paths]
784 ulchars = "1234567890ABCDEFGHIJK"
785 rf_list = rf_list[0:len(ulchars)]
786 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000787 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000788 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000789 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000790 rf_file.close()
791 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000792 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000793 menu = instance.recent_files_menu
794 menu.delete(1, END) # clear, and rebuild:
795 for i, file in zip(count(), rf_list):
796 file_name = file[0:-1] # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000797 # make unicode string to display non-ASCII chars correctly
798 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000799 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000800 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000801 command=callback,
802 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000803
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000804 def __recent_file_callback(self, file_name):
805 def open_recent_file(fn_closure=file_name):
806 self.io.open(editFile=fn_closure)
807 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000808
David Scherer7aced172000-08-15 01:13:23 +0000809 def saved_change_hook(self):
810 short = self.short_title()
811 long = self.long_title()
812 if short and long:
813 title = short + " - " + long
814 elif short:
815 title = short
816 elif long:
817 title = long
818 else:
819 title = "Untitled"
820 icon = short or long or title
821 if not self.get_saved():
822 title = "*%s*" % title
823 icon = "*%s" % icon
824 self.top.wm_title(title)
825 self.top.wm_iconname(icon)
826
827 def get_saved(self):
828 return self.undo.get_saved()
829
830 def set_saved(self, flag):
831 self.undo.set_saved(flag)
832
833 def reset_undo(self):
834 self.undo.reset_undo()
835
836 def short_title(self):
837 filename = self.io.filename
838 if filename:
839 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000840 # return unicode string to display non-ASCII chars correctly
841 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000842
843 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000844 # return unicode string to display non-ASCII chars correctly
845 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000846
847 def center_insert_event(self, event):
848 self.center()
849
850 def center(self, mark="insert"):
851 text = self.text
852 top, bot = self.getwindowlines()
853 lineno = self.getlineno(mark)
854 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000855 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000856 text.yview(float(newtop))
857
858 def getwindowlines(self):
859 text = self.text
860 top = self.getlineno("@0,0")
861 bot = self.getlineno("@0,65535")
862 if top == bot and text.winfo_height() == 1:
863 # Geometry manager hasn't run yet
864 height = int(text['height'])
865 bot = top + height - 1
866 return top, bot
867
868 def getlineno(self, mark="insert"):
869 text = self.text
870 return int(float(text.index(mark)))
871
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000872 def get_geometry(self):
873 "Return (width, height, x, y)"
874 geom = self.top.wm_geometry()
875 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000876 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000877
David Scherer7aced172000-08-15 01:13:23 +0000878 def close_event(self, event):
879 self.close()
880
881 def maybesave(self):
882 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000883 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000884 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000885 self.top.deiconify()
886 self.top.lower()
887 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000888 return self.io.maybesave()
889
890 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000891 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000892 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000893 self._close()
894 return reply
895
896 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000897 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000898 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000899 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000900 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000901 self.io.close()
902 self.io = None
903 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000904 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000905 self.color.close(False)
906 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000907 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000908 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000909 self.per.close()
910 self.per = None
911 self.top.destroy()
912 if self.close_hook:
913 # unless override: unregister from flist, terminate if last window
914 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000915
916 def load_extensions(self):
917 self.extensions = {}
918 self.load_standard_extensions()
919
920 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000921 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000922 if hasattr(ins, "close"):
923 ins.close()
924 self.extensions = {}
925
926 def load_standard_extensions(self):
927 for name in self.get_standard_extension_names():
928 try:
929 self.load_extension(name)
930 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000931 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000932 traceback.print_exc()
933
934 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000935 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000936
937 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000938 try:
939 mod = __import__(name, globals(), locals(), [])
940 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000941 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000942 raise
David Scherer7aced172000-08-15 01:13:23 +0000943 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000944 keydefs = idleConf.GetExtensionBindings(name)
945 if hasattr(cls, "menudefs"):
946 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000947 ins = cls(self)
948 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000949 if keydefs:
950 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000951 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000952 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000953 while methodname[:1] == '<':
954 methodname = methodname[1:]
955 while methodname[-1:] == '>':
956 methodname = methodname[:-1]
957 methodname = methodname + "_event"
958 if hasattr(ins, methodname):
959 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000960
961 def apply_bindings(self, keydefs=None):
962 if keydefs is None:
963 keydefs = self.Bindings.default_keydefs
964 text = self.text
965 text.keydefs = keydefs
966 for event, keylist in keydefs.items():
967 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000968 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000969
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000970 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000971 """Add appropriate entries to the menus and submenus
972
973 Menus that are absent or None in self.menudict are ignored.
974 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000975 if menudefs is None:
976 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000977 if keydefs is None:
978 keydefs = self.Bindings.default_keydefs
979 menudict = self.menudict
980 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000981 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000982 menu = menudict.get(mname)
983 if not menu:
984 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000985 for entry in entrylist:
986 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000987 menu.add_separator()
988 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000989 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000990 checkbutton = (label[:1] == '!')
991 if checkbutton:
992 label = label[1:]
993 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000994 accelerator = get_accelerator(keydefs, eventname)
995 def command(text=text, eventname=eventname):
996 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000997 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000998 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000999 menu.add_checkbutton(label=label, underline=underline,
1000 command=command, accelerator=accelerator,
1001 variable=var)
1002 else:
1003 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001004 command=command,
1005 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001006
1007 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001008 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001009 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001010 value = var.get()
1011 return value
1012 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001013 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001014
1015 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001016 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001017 if var:
1018 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001019 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001020 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001021
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001022 def get_var_obj(self, name, vartype=None):
1023 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001024 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001025 # create a Tkinter variable object with self.text as master:
1026 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001027 return var
1028
1029 # Tk implementations of "virtual text methods" -- each platform
1030 # reusing IDLE's support code needs to define these for its GUI's
1031 # flavor of widget.
1032
1033 # Is character at text_index in a Python string? Return 0 for
1034 # "guaranteed no", true for anything else. This info is expensive
1035 # to compute ab initio, but is probably already known by the
1036 # platform's colorizer.
1037
1038 def is_char_in_string(self, text_index):
1039 if self.color:
1040 # Return true iff colorizer hasn't (re)gotten this far
1041 # yet, or the character is tagged as being in a string
1042 return self.text.tag_prevrange("TODO", text_index) or \
1043 "STRING" in self.text.tag_names(text_index)
1044 else:
1045 # The colorizer is missing: assume the worst
1046 return 1
1047
1048 # If a selection is defined in the text widget, return (start,
1049 # end) as Tkinter text indices, otherwise return (None, None)
1050 def get_selection_indices(self):
1051 try:
1052 first = self.text.index("sel.first")
1053 last = self.text.index("sel.last")
1054 return first, last
1055 except TclError:
1056 return None, None
1057
1058 # Return the text widget's current view of what a tab stop means
1059 # (equivalent width in spaces).
1060
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001061 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001062 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1063 return int(current)
1064
1065 # Set the text widget's current view of what a tab stop means.
1066
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001067 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001068 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001069 if self.get_tk_tabwidth() != newtabwidth:
1070 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001071 pixels = text.tk.call("font", "measure", text["font"],
1072 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001073 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001074 text.configure(tabs=pixels)
1075
Guido van Rossum33d26892007-08-05 15:29:28 +00001076### begin autoindent code ### (configuration was moved to beginning of class)
1077
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001078 def set_indentation_params(self, is_py_src, guess=True):
1079 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001080 i = self.guess_indent()
1081 if 2 <= i <= 8:
1082 self.indentwidth = i
1083 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001084 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001085 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001086
1087 def smart_backspace_event(self, event):
1088 text = self.text
1089 first, last = self.get_selection_indices()
1090 if first and last:
1091 text.delete(first, last)
1092 text.mark_set("insert", first)
1093 return "break"
1094 # Delete whitespace left, until hitting a real char or closest
1095 # preceding virtual tab stop.
1096 chars = text.get("insert linestart", "insert")
1097 if chars == '':
1098 if text.compare("insert", ">", "1.0"):
1099 # easy: delete preceding newline
1100 text.delete("insert-1c")
1101 else:
1102 text.bell() # at start of buffer
1103 return "break"
1104 if chars[-1] not in " \t":
1105 # easy: delete preceding real char
1106 text.delete("insert-1c")
1107 return "break"
1108 # Ick. It may require *inserting* spaces if we back up over a
1109 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001110 tabwidth = self.tabwidth
1111 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001112 assert have > 0
1113 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001114 # Debug prompt is multilined....
1115 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001116 ncharsdeleted = 0
1117 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001118 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001119 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001120 chars = chars[:-1]
1121 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001122 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001123 if have <= want or chars[-1] not in " \t":
1124 break
1125 text.undo_block_start()
1126 text.delete("insert-%dc" % ncharsdeleted, "insert")
1127 if have < want:
1128 text.insert("insert", ' ' * (want - have))
1129 text.undo_block_stop()
1130 return "break"
1131
1132 def smart_indent_event(self, event):
1133 # if intraline selection:
1134 # delete it
1135 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001136 # do indent-region
1137 # else:
1138 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001139 text = self.text
1140 first, last = self.get_selection_indices()
1141 text.undo_block_start()
1142 try:
1143 if first and last:
1144 if index2line(first) != index2line(last):
1145 return self.indent_region_event(event)
1146 text.delete(first, last)
1147 text.mark_set("insert", first)
1148 prefix = text.get("insert linestart", "insert")
1149 raw, effective = classifyws(prefix, self.tabwidth)
1150 if raw == len(prefix):
1151 # only whitespace to the left
1152 self.reindent_to(effective + self.indentwidth)
1153 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001154 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001155 if self.usetabs:
1156 pad = '\t'
1157 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001158 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001159 n = self.indentwidth
1160 pad = ' ' * (n - effective % n)
1161 text.insert("insert", pad)
1162 text.see("insert")
1163 return "break"
1164 finally:
1165 text.undo_block_stop()
1166
1167 def newline_and_indent_event(self, event):
1168 text = self.text
1169 first, last = self.get_selection_indices()
1170 text.undo_block_start()
1171 try:
1172 if first and last:
1173 text.delete(first, last)
1174 text.mark_set("insert", first)
1175 line = text.get("insert linestart", "insert")
1176 i, n = 0, len(line)
1177 while i < n and line[i] in " \t":
1178 i = i+1
1179 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001180 # the cursor is in or at leading indentation in a continuation
1181 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001182 text.insert("insert linestart", '\n')
1183 return "break"
1184 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001185 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001186 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001187 last_line_of_prompt = sys.ps1.split('\n')[-1]
1188 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001189 line = line[:-1]
1190 i = i+1
1191 if i:
1192 text.delete("insert - %d chars" % i, "insert")
1193 # strip whitespace after insert point
1194 while text.get("insert") in " \t":
1195 text.delete("insert")
1196 # start new line
1197 text.insert("insert", '\n')
1198
1199 # adjust indentation for continuations and block
1200 # open/close first need to find the last stmt
1201 lno = index2line(text.index('insert'))
1202 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001203 if not self.context_use_ps1:
1204 for context in self.num_context_lines:
1205 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001206 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001207 rawtext = text.get(startatindex, "insert")
1208 y.set_str(rawtext)
1209 bod = y.find_good_parse_start(
1210 self.context_use_ps1,
1211 self._build_char_in_string_func(startatindex))
1212 if bod is not None or startat == 1:
1213 break
1214 y.set_lo(bod or 0)
1215 else:
1216 r = text.tag_prevrange("console", "insert")
1217 if r:
1218 startatindex = r[1]
1219 else:
1220 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001221 rawtext = text.get(startatindex, "insert")
1222 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001223 y.set_lo(0)
1224
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001225 c = y.get_continuation_type()
1226 if c != PyParse.C_NONE:
1227 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001228 if c == PyParse.C_STRING_FIRST_LINE:
1229 # after the first line of a string; do not indent at all
1230 pass
1231 elif c == PyParse.C_STRING_NEXT_LINES:
1232 # inside a string which started before this line;
1233 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001234 text.insert("insert", indent)
1235 elif c == PyParse.C_BRACKET:
1236 # line up with the first (if any) element of the
1237 # last open bracket structure; else indent one
1238 # level beyond the indent of the line with the
1239 # last open bracket
1240 self.reindent_to(y.compute_bracket_indent())
1241 elif c == PyParse.C_BACKSLASH:
1242 # if more than one line in this stmt already, just
1243 # mimic the current indent; else if initial line
1244 # has a start on an assignment stmt, indent to
1245 # beyond leftmost =; else to beyond first chunk of
1246 # non-whitespace on initial line
1247 if y.get_num_lines_in_stmt() > 1:
1248 text.insert("insert", indent)
1249 else:
1250 self.reindent_to(y.compute_backslash_indent())
1251 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001252 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001253 return "break"
1254
1255 # This line starts a brand new stmt; indent relative to
1256 # indentation of initial line of closest preceding
1257 # interesting stmt.
1258 indent = y.get_base_indent_string()
1259 text.insert("insert", indent)
1260 if y.is_block_opener():
1261 self.smart_indent_event(event)
1262 elif indent and y.is_block_closer():
1263 self.smart_backspace_event(event)
1264 return "break"
1265 finally:
1266 text.see("insert")
1267 text.undo_block_stop()
1268
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001269 # Our editwin provides a is_char_in_string function that works
1270 # with a Tk text index, but PyParse only knows about offsets into
1271 # a string. This builds a function for PyParse that accepts an
1272 # offset.
1273
1274 def _build_char_in_string_func(self, startindex):
1275 def inner(offset, _startindex=startindex,
1276 _icis=self.is_char_in_string):
1277 return _icis(_startindex + "+%dc" % offset)
1278 return inner
1279
1280 def indent_region_event(self, event):
1281 head, tail, chars, lines = self.get_region()
1282 for pos in range(len(lines)):
1283 line = lines[pos]
1284 if line:
1285 raw, effective = classifyws(line, self.tabwidth)
1286 effective = effective + self.indentwidth
1287 lines[pos] = self._make_blanks(effective) + line[raw:]
1288 self.set_region(head, tail, chars, lines)
1289 return "break"
1290
1291 def dedent_region_event(self, event):
1292 head, tail, chars, lines = self.get_region()
1293 for pos in range(len(lines)):
1294 line = lines[pos]
1295 if line:
1296 raw, effective = classifyws(line, self.tabwidth)
1297 effective = max(effective - self.indentwidth, 0)
1298 lines[pos] = self._make_blanks(effective) + line[raw:]
1299 self.set_region(head, tail, chars, lines)
1300 return "break"
1301
1302 def comment_region_event(self, event):
1303 head, tail, chars, lines = self.get_region()
1304 for pos in range(len(lines) - 1):
1305 line = lines[pos]
1306 lines[pos] = '##' + line
1307 self.set_region(head, tail, chars, lines)
1308
1309 def uncomment_region_event(self, event):
1310 head, tail, chars, lines = self.get_region()
1311 for pos in range(len(lines)):
1312 line = lines[pos]
1313 if not line:
1314 continue
1315 if line[:2] == '##':
1316 line = line[2:]
1317 elif line[:1] == '#':
1318 line = line[1:]
1319 lines[pos] = line
1320 self.set_region(head, tail, chars, lines)
1321
1322 def tabify_region_event(self, event):
1323 head, tail, chars, lines = self.get_region()
1324 tabwidth = self._asktabwidth()
1325 for pos in range(len(lines)):
1326 line = lines[pos]
1327 if line:
1328 raw, effective = classifyws(line, tabwidth)
1329 ntabs, nspaces = divmod(effective, tabwidth)
1330 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1331 self.set_region(head, tail, chars, lines)
1332
1333 def untabify_region_event(self, event):
1334 head, tail, chars, lines = self.get_region()
1335 tabwidth = self._asktabwidth()
1336 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001337 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001338 self.set_region(head, tail, chars, lines)
1339
1340 def toggle_tabs_event(self, event):
1341 if self.askyesno(
1342 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001343 "Turn tabs " + ("on", "off")[self.usetabs] +
1344 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001345 ("will be", "remains at")[self.usetabs] + " 8." +
1346 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001347 parent=self.text):
1348 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001349 # Try to prevent inconsistent indentation.
1350 # User must change indent width manually after using tabs.
1351 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001352 return "break"
1353
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001354 # XXX this isn't bound to anything -- see tabwidth comments
1355## def change_tabwidth_event(self, event):
1356## new = self._asktabwidth()
1357## if new != self.tabwidth:
1358## self.tabwidth = new
1359## self.set_indentation_params(0, guess=0)
1360## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001361
1362 def change_indentwidth_event(self, event):
1363 new = self.askinteger(
1364 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001365 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001366 parent=self.text,
1367 initialvalue=self.indentwidth,
1368 minvalue=2,
1369 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001370 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001371 self.indentwidth = new
1372 return "break"
1373
1374 def get_region(self):
1375 text = self.text
1376 first, last = self.get_selection_indices()
1377 if first and last:
1378 head = text.index(first + " linestart")
1379 tail = text.index(last + "-1c lineend +1c")
1380 else:
1381 head = text.index("insert linestart")
1382 tail = text.index("insert lineend +1c")
1383 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001384 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001385 return head, tail, chars, lines
1386
1387 def set_region(self, head, tail, chars, lines):
1388 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001389 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001390 if newchars == chars:
1391 text.bell()
1392 return
1393 text.tag_remove("sel", "1.0", "end")
1394 text.mark_set("insert", head)
1395 text.undo_block_start()
1396 text.delete(head, tail)
1397 text.insert(head, newchars)
1398 text.undo_block_stop()
1399 text.tag_add("sel", head, "insert")
1400
1401 # Make string that displays as n leading blanks.
1402
1403 def _make_blanks(self, n):
1404 if self.usetabs:
1405 ntabs, nspaces = divmod(n, self.tabwidth)
1406 return '\t' * ntabs + ' ' * nspaces
1407 else:
1408 return ' ' * n
1409
1410 # Delete from beginning of line to insert point, then reinsert
1411 # column logical (meaning use tabs if appropriate) spaces.
1412
1413 def reindent_to(self, column):
1414 text = self.text
1415 text.undo_block_start()
1416 if text.compare("insert linestart", "!=", "insert"):
1417 text.delete("insert linestart", "insert")
1418 if column:
1419 text.insert("insert", self._make_blanks(column))
1420 text.undo_block_stop()
1421
1422 def _asktabwidth(self):
1423 return self.askinteger(
1424 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001425 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001426 parent=self.text,
1427 initialvalue=self.indentwidth,
1428 minvalue=2,
1429 maxvalue=16) or self.tabwidth
1430
1431 # Guess indentwidth from text content.
1432 # Return guessed indentwidth. This should not be believed unless
1433 # it's in a reasonable range (e.g., it will be 0 if no indented
1434 # blocks are found).
1435
1436 def guess_indent(self):
1437 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1438 if opener and indented:
1439 raw, indentsmall = classifyws(opener, self.tabwidth)
1440 raw, indentlarge = classifyws(indented, self.tabwidth)
1441 else:
1442 indentsmall = indentlarge = 0
1443 return indentlarge - indentsmall
1444
1445# "line.col" -> line, as an int
1446def index2line(index):
1447 return int(float(index))
1448
1449# Look at the leading whitespace in s.
1450# Return pair (# of leading ws characters,
1451# effective # of leading blanks after expanding
1452# tabs to width tabwidth)
1453
1454def classifyws(s, tabwidth):
1455 raw = effective = 0
1456 for ch in s:
1457 if ch == ' ':
1458 raw = raw + 1
1459 effective = effective + 1
1460 elif ch == '\t':
1461 raw = raw + 1
1462 effective = (effective // tabwidth + 1) * tabwidth
1463 else:
1464 break
1465 return raw, effective
1466
1467import tokenize
1468_tokenize = tokenize
1469del tokenize
1470
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001471class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001472
1473 # .run() chews over the Text widget, looking for a block opener
1474 # and the stmt following it. Returns a pair,
1475 # (line containing block opener, line containing stmt)
1476 # Either or both may be None.
1477
1478 def __init__(self, text, tabwidth):
1479 self.text = text
1480 self.tabwidth = tabwidth
1481 self.i = self.finished = 0
1482 self.blkopenline = self.indentedline = None
1483
1484 def readline(self):
1485 if self.finished:
1486 return ""
1487 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001488 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001489 if self.text.compare(mark, ">=", "end"):
1490 return ""
1491 return self.text.get(mark, mark + " lineend+1c")
1492
1493 def tokeneater(self, type, token, start, end, line,
1494 INDENT=_tokenize.INDENT,
1495 NAME=_tokenize.NAME,
1496 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1497 if self.finished:
1498 pass
1499 elif type == NAME and token in OPENERS:
1500 self.blkopenline = line
1501 elif type == INDENT and self.blkopenline:
1502 self.indentedline = line
1503 self.finished = 1
1504
1505 def run(self):
1506 save_tabsize = _tokenize.tabsize
1507 _tokenize.tabsize = self.tabwidth
1508 try:
1509 try:
Trent Nelson428de652008-03-18 22:41:35 +00001510 tokens = _tokenize.generate_tokens(self.readline)
1511 for token in tokens:
1512 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001513 except _tokenize.TokenError:
1514 # since we cut off the tokenizer early, we can trigger
1515 # spurious errors
1516 pass
1517 finally:
1518 _tokenize.tabsize = save_tabsize
1519 return self.blkopenline, self.indentedline
1520
1521### end autoindent code ###
1522
David Scherer7aced172000-08-15 01:13:23 +00001523def prepstr(s):
1524 # Helper to extract the underscore from a string, e.g.
1525 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001526 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001527 if i >= 0:
1528 s = s[:i] + s[i+1:]
1529 return i, s
1530
1531
1532keynames = {
1533 'bracketleft': '[',
1534 'bracketright': ']',
1535 'slash': '/',
1536}
1537
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001538def get_accelerator(keydefs, eventname):
1539 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001540 if not keylist:
1541 return ""
1542 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001543 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001544 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1545 s = re.sub("Key-", "", s)
1546 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1547 s = re.sub("Control-", "Ctrl-", s)
1548 s = re.sub("-", "+", s)
1549 s = re.sub("><", " ", s)
1550 s = re.sub("<", "", s)
1551 s = re.sub(">", "", s)
1552 return s
1553
1554
1555def fixwordbreaks(root):
1556 # Make sure that Tk's double-click and next/previous word
1557 # operations use our definition of a word (i.e. an identifier)
1558 tk = root.tk
1559 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1560 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1561 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1562
1563
1564def test():
1565 root = Tk()
1566 fixwordbreaks(root)
1567 root.withdraw()
1568 if sys.argv[1:]:
1569 filename = sys.argv[1]
1570 else:
1571 filename = None
1572 edit = EditorWindow(root=root, filename=filename)
1573 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001574 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001575 root.mainloop()
1576 root.destroy()
1577
1578if __name__ == '__main__':
1579 test()