blob: 20a2b26bafc4049b2e816e380efdcab8abab9f82 [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):
776 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000777 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000778 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000779 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000780 rf_list_file.close()
781 if new_file:
782 new_file = os.path.abspath(new_file) + '\n'
783 if new_file in rf_list:
784 rf_list.remove(new_file) # move to top
785 rf_list.insert(0, new_file)
786 # clean and save the recent files list
787 bad_paths = []
788 for path in rf_list:
789 if '\0' in path or not os.path.exists(path[0:-1]):
790 bad_paths.append(path)
791 rf_list = [path for path in rf_list if path not in bad_paths]
792 ulchars = "1234567890ABCDEFGHIJK"
793 rf_list = rf_list[0:len(ulchars)]
794 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000795 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000796 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000797 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000798 rf_file.close()
799 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000800 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000801 menu = instance.recent_files_menu
802 menu.delete(1, END) # clear, and rebuild:
803 for i, file in zip(count(), rf_list):
804 file_name = file[0:-1] # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000805 # make unicode string to display non-ASCII chars correctly
806 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000807 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000808 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000809 command=callback,
810 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000811
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000812 def __recent_file_callback(self, file_name):
813 def open_recent_file(fn_closure=file_name):
814 self.io.open(editFile=fn_closure)
815 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000816
David Scherer7aced172000-08-15 01:13:23 +0000817 def saved_change_hook(self):
818 short = self.short_title()
819 long = self.long_title()
820 if short and long:
821 title = short + " - " + long
822 elif short:
823 title = short
824 elif long:
825 title = long
826 else:
827 title = "Untitled"
828 icon = short or long or title
829 if not self.get_saved():
830 title = "*%s*" % title
831 icon = "*%s" % icon
832 self.top.wm_title(title)
833 self.top.wm_iconname(icon)
834
835 def get_saved(self):
836 return self.undo.get_saved()
837
838 def set_saved(self, flag):
839 self.undo.set_saved(flag)
840
841 def reset_undo(self):
842 self.undo.reset_undo()
843
844 def short_title(self):
845 filename = self.io.filename
846 if filename:
847 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000848 # return unicode string to display non-ASCII chars correctly
849 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000850
851 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000852 # return unicode string to display non-ASCII chars correctly
853 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000854
855 def center_insert_event(self, event):
856 self.center()
857
858 def center(self, mark="insert"):
859 text = self.text
860 top, bot = self.getwindowlines()
861 lineno = self.getlineno(mark)
862 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000863 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000864 text.yview(float(newtop))
865
866 def getwindowlines(self):
867 text = self.text
868 top = self.getlineno("@0,0")
869 bot = self.getlineno("@0,65535")
870 if top == bot and text.winfo_height() == 1:
871 # Geometry manager hasn't run yet
872 height = int(text['height'])
873 bot = top + height - 1
874 return top, bot
875
876 def getlineno(self, mark="insert"):
877 text = self.text
878 return int(float(text.index(mark)))
879
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000880 def get_geometry(self):
881 "Return (width, height, x, y)"
882 geom = self.top.wm_geometry()
883 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000884 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000885
David Scherer7aced172000-08-15 01:13:23 +0000886 def close_event(self, event):
887 self.close()
888
889 def maybesave(self):
890 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000891 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000892 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000893 self.top.deiconify()
894 self.top.lower()
895 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000896 return self.io.maybesave()
897
898 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000899 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000900 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000901 self._close()
902 return reply
903
904 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000905 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000906 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000907 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000908 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000909 self.io.close()
910 self.io = None
911 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000912 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000913 self.color.close(False)
914 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000915 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000916 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000917 self.per.close()
918 self.per = None
919 self.top.destroy()
920 if self.close_hook:
921 # unless override: unregister from flist, terminate if last window
922 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000923
924 def load_extensions(self):
925 self.extensions = {}
926 self.load_standard_extensions()
927
928 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000929 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000930 if hasattr(ins, "close"):
931 ins.close()
932 self.extensions = {}
933
934 def load_standard_extensions(self):
935 for name in self.get_standard_extension_names():
936 try:
937 self.load_extension(name)
938 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000939 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000940 traceback.print_exc()
941
942 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000943 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000944
945 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000946 try:
947 mod = __import__(name, globals(), locals(), [])
948 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000949 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000950 raise
David Scherer7aced172000-08-15 01:13:23 +0000951 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000952 keydefs = idleConf.GetExtensionBindings(name)
953 if hasattr(cls, "menudefs"):
954 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000955 ins = cls(self)
956 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000957 if keydefs:
958 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000959 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000960 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000961 while methodname[:1] == '<':
962 methodname = methodname[1:]
963 while methodname[-1:] == '>':
964 methodname = methodname[:-1]
965 methodname = methodname + "_event"
966 if hasattr(ins, methodname):
967 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000968
969 def apply_bindings(self, keydefs=None):
970 if keydefs is None:
971 keydefs = self.Bindings.default_keydefs
972 text = self.text
973 text.keydefs = keydefs
974 for event, keylist in keydefs.items():
975 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000976 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000977
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000978 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000979 """Add appropriate entries to the menus and submenus
980
981 Menus that are absent or None in self.menudict are ignored.
982 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000983 if menudefs is None:
984 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000985 if keydefs is None:
986 keydefs = self.Bindings.default_keydefs
987 menudict = self.menudict
988 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000989 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000990 menu = menudict.get(mname)
991 if not menu:
992 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000993 for entry in entrylist:
994 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000995 menu.add_separator()
996 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000997 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000998 checkbutton = (label[:1] == '!')
999 if checkbutton:
1000 label = label[1:]
1001 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001002 accelerator = get_accelerator(keydefs, eventname)
1003 def command(text=text, eventname=eventname):
1004 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001005 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001006 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001007 menu.add_checkbutton(label=label, underline=underline,
1008 command=command, accelerator=accelerator,
1009 variable=var)
1010 else:
1011 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001012 command=command,
1013 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001014
1015 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001016 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001017 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001018 value = var.get()
1019 return value
1020 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001021 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001022
1023 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001024 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001025 if var:
1026 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001027 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001028 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001029
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001030 def get_var_obj(self, name, vartype=None):
1031 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001032 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001033 # create a Tkinter variable object with self.text as master:
1034 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001035 return var
1036
1037 # Tk implementations of "virtual text methods" -- each platform
1038 # reusing IDLE's support code needs to define these for its GUI's
1039 # flavor of widget.
1040
1041 # Is character at text_index in a Python string? Return 0 for
1042 # "guaranteed no", true for anything else. This info is expensive
1043 # to compute ab initio, but is probably already known by the
1044 # platform's colorizer.
1045
1046 def is_char_in_string(self, text_index):
1047 if self.color:
1048 # Return true iff colorizer hasn't (re)gotten this far
1049 # yet, or the character is tagged as being in a string
1050 return self.text.tag_prevrange("TODO", text_index) or \
1051 "STRING" in self.text.tag_names(text_index)
1052 else:
1053 # The colorizer is missing: assume the worst
1054 return 1
1055
1056 # If a selection is defined in the text widget, return (start,
1057 # end) as Tkinter text indices, otherwise return (None, None)
1058 def get_selection_indices(self):
1059 try:
1060 first = self.text.index("sel.first")
1061 last = self.text.index("sel.last")
1062 return first, last
1063 except TclError:
1064 return None, None
1065
1066 # Return the text widget's current view of what a tab stop means
1067 # (equivalent width in spaces).
1068
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001069 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001070 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1071 return int(current)
1072
1073 # Set the text widget's current view of what a tab stop means.
1074
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001075 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001076 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001077 if self.get_tk_tabwidth() != newtabwidth:
1078 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001079 pixels = text.tk.call("font", "measure", text["font"],
1080 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001081 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001082 text.configure(tabs=pixels)
1083
Guido van Rossum33d26892007-08-05 15:29:28 +00001084### begin autoindent code ### (configuration was moved to beginning of class)
1085
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001086 def set_indentation_params(self, is_py_src, guess=True):
1087 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001088 i = self.guess_indent()
1089 if 2 <= i <= 8:
1090 self.indentwidth = i
1091 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001092 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001093 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001094
1095 def smart_backspace_event(self, event):
1096 text = self.text
1097 first, last = self.get_selection_indices()
1098 if first and last:
1099 text.delete(first, last)
1100 text.mark_set("insert", first)
1101 return "break"
1102 # Delete whitespace left, until hitting a real char or closest
1103 # preceding virtual tab stop.
1104 chars = text.get("insert linestart", "insert")
1105 if chars == '':
1106 if text.compare("insert", ">", "1.0"):
1107 # easy: delete preceding newline
1108 text.delete("insert-1c")
1109 else:
1110 text.bell() # at start of buffer
1111 return "break"
1112 if chars[-1] not in " \t":
1113 # easy: delete preceding real char
1114 text.delete("insert-1c")
1115 return "break"
1116 # Ick. It may require *inserting* spaces if we back up over a
1117 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001118 tabwidth = self.tabwidth
1119 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001120 assert have > 0
1121 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001122 # Debug prompt is multilined....
1123 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001124 ncharsdeleted = 0
1125 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001126 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001127 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001128 chars = chars[:-1]
1129 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001130 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001131 if have <= want or chars[-1] not in " \t":
1132 break
1133 text.undo_block_start()
1134 text.delete("insert-%dc" % ncharsdeleted, "insert")
1135 if have < want:
1136 text.insert("insert", ' ' * (want - have))
1137 text.undo_block_stop()
1138 return "break"
1139
1140 def smart_indent_event(self, event):
1141 # if intraline selection:
1142 # delete it
1143 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001144 # do indent-region
1145 # else:
1146 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001147 text = self.text
1148 first, last = self.get_selection_indices()
1149 text.undo_block_start()
1150 try:
1151 if first and last:
1152 if index2line(first) != index2line(last):
1153 return self.indent_region_event(event)
1154 text.delete(first, last)
1155 text.mark_set("insert", first)
1156 prefix = text.get("insert linestart", "insert")
1157 raw, effective = classifyws(prefix, self.tabwidth)
1158 if raw == len(prefix):
1159 # only whitespace to the left
1160 self.reindent_to(effective + self.indentwidth)
1161 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001162 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001163 if self.usetabs:
1164 pad = '\t'
1165 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001166 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001167 n = self.indentwidth
1168 pad = ' ' * (n - effective % n)
1169 text.insert("insert", pad)
1170 text.see("insert")
1171 return "break"
1172 finally:
1173 text.undo_block_stop()
1174
1175 def newline_and_indent_event(self, event):
1176 text = self.text
1177 first, last = self.get_selection_indices()
1178 text.undo_block_start()
1179 try:
1180 if first and last:
1181 text.delete(first, last)
1182 text.mark_set("insert", first)
1183 line = text.get("insert linestart", "insert")
1184 i, n = 0, len(line)
1185 while i < n and line[i] in " \t":
1186 i = i+1
1187 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001188 # the cursor is in or at leading indentation in a continuation
1189 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001190 text.insert("insert linestart", '\n')
1191 return "break"
1192 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001193 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001194 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001195 last_line_of_prompt = sys.ps1.split('\n')[-1]
1196 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001197 line = line[:-1]
1198 i = i+1
1199 if i:
1200 text.delete("insert - %d chars" % i, "insert")
1201 # strip whitespace after insert point
1202 while text.get("insert") in " \t":
1203 text.delete("insert")
1204 # start new line
1205 text.insert("insert", '\n')
1206
1207 # adjust indentation for continuations and block
1208 # open/close first need to find the last stmt
1209 lno = index2line(text.index('insert'))
1210 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001211 if not self.context_use_ps1:
1212 for context in self.num_context_lines:
1213 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001214 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001215 rawtext = text.get(startatindex, "insert")
1216 y.set_str(rawtext)
1217 bod = y.find_good_parse_start(
1218 self.context_use_ps1,
1219 self._build_char_in_string_func(startatindex))
1220 if bod is not None or startat == 1:
1221 break
1222 y.set_lo(bod or 0)
1223 else:
1224 r = text.tag_prevrange("console", "insert")
1225 if r:
1226 startatindex = r[1]
1227 else:
1228 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001229 rawtext = text.get(startatindex, "insert")
1230 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001231 y.set_lo(0)
1232
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001233 c = y.get_continuation_type()
1234 if c != PyParse.C_NONE:
1235 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001236 if c == PyParse.C_STRING_FIRST_LINE:
1237 # after the first line of a string; do not indent at all
1238 pass
1239 elif c == PyParse.C_STRING_NEXT_LINES:
1240 # inside a string which started before this line;
1241 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001242 text.insert("insert", indent)
1243 elif c == PyParse.C_BRACKET:
1244 # line up with the first (if any) element of the
1245 # last open bracket structure; else indent one
1246 # level beyond the indent of the line with the
1247 # last open bracket
1248 self.reindent_to(y.compute_bracket_indent())
1249 elif c == PyParse.C_BACKSLASH:
1250 # if more than one line in this stmt already, just
1251 # mimic the current indent; else if initial line
1252 # has a start on an assignment stmt, indent to
1253 # beyond leftmost =; else to beyond first chunk of
1254 # non-whitespace on initial line
1255 if y.get_num_lines_in_stmt() > 1:
1256 text.insert("insert", indent)
1257 else:
1258 self.reindent_to(y.compute_backslash_indent())
1259 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001260 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001261 return "break"
1262
1263 # This line starts a brand new stmt; indent relative to
1264 # indentation of initial line of closest preceding
1265 # interesting stmt.
1266 indent = y.get_base_indent_string()
1267 text.insert("insert", indent)
1268 if y.is_block_opener():
1269 self.smart_indent_event(event)
1270 elif indent and y.is_block_closer():
1271 self.smart_backspace_event(event)
1272 return "break"
1273 finally:
1274 text.see("insert")
1275 text.undo_block_stop()
1276
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001277 # Our editwin provides a is_char_in_string function that works
1278 # with a Tk text index, but PyParse only knows about offsets into
1279 # a string. This builds a function for PyParse that accepts an
1280 # offset.
1281
1282 def _build_char_in_string_func(self, startindex):
1283 def inner(offset, _startindex=startindex,
1284 _icis=self.is_char_in_string):
1285 return _icis(_startindex + "+%dc" % offset)
1286 return inner
1287
1288 def indent_region_event(self, event):
1289 head, tail, chars, lines = self.get_region()
1290 for pos in range(len(lines)):
1291 line = lines[pos]
1292 if line:
1293 raw, effective = classifyws(line, self.tabwidth)
1294 effective = effective + self.indentwidth
1295 lines[pos] = self._make_blanks(effective) + line[raw:]
1296 self.set_region(head, tail, chars, lines)
1297 return "break"
1298
1299 def dedent_region_event(self, event):
1300 head, tail, chars, lines = self.get_region()
1301 for pos in range(len(lines)):
1302 line = lines[pos]
1303 if line:
1304 raw, effective = classifyws(line, self.tabwidth)
1305 effective = max(effective - self.indentwidth, 0)
1306 lines[pos] = self._make_blanks(effective) + line[raw:]
1307 self.set_region(head, tail, chars, lines)
1308 return "break"
1309
1310 def comment_region_event(self, event):
1311 head, tail, chars, lines = self.get_region()
1312 for pos in range(len(lines) - 1):
1313 line = lines[pos]
1314 lines[pos] = '##' + line
1315 self.set_region(head, tail, chars, lines)
1316
1317 def uncomment_region_event(self, event):
1318 head, tail, chars, lines = self.get_region()
1319 for pos in range(len(lines)):
1320 line = lines[pos]
1321 if not line:
1322 continue
1323 if line[:2] == '##':
1324 line = line[2:]
1325 elif line[:1] == '#':
1326 line = line[1:]
1327 lines[pos] = line
1328 self.set_region(head, tail, chars, lines)
1329
1330 def tabify_region_event(self, event):
1331 head, tail, chars, lines = self.get_region()
1332 tabwidth = self._asktabwidth()
1333 for pos in range(len(lines)):
1334 line = lines[pos]
1335 if line:
1336 raw, effective = classifyws(line, tabwidth)
1337 ntabs, nspaces = divmod(effective, tabwidth)
1338 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1339 self.set_region(head, tail, chars, lines)
1340
1341 def untabify_region_event(self, event):
1342 head, tail, chars, lines = self.get_region()
1343 tabwidth = self._asktabwidth()
1344 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001345 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001346 self.set_region(head, tail, chars, lines)
1347
1348 def toggle_tabs_event(self, event):
1349 if self.askyesno(
1350 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001351 "Turn tabs " + ("on", "off")[self.usetabs] +
1352 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001353 ("will be", "remains at")[self.usetabs] + " 8." +
1354 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001355 parent=self.text):
1356 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001357 # Try to prevent inconsistent indentation.
1358 # User must change indent width manually after using tabs.
1359 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001360 return "break"
1361
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001362 # XXX this isn't bound to anything -- see tabwidth comments
1363## def change_tabwidth_event(self, event):
1364## new = self._asktabwidth()
1365## if new != self.tabwidth:
1366## self.tabwidth = new
1367## self.set_indentation_params(0, guess=0)
1368## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001369
1370 def change_indentwidth_event(self, event):
1371 new = self.askinteger(
1372 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001373 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001374 parent=self.text,
1375 initialvalue=self.indentwidth,
1376 minvalue=2,
1377 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001378 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001379 self.indentwidth = new
1380 return "break"
1381
1382 def get_region(self):
1383 text = self.text
1384 first, last = self.get_selection_indices()
1385 if first and last:
1386 head = text.index(first + " linestart")
1387 tail = text.index(last + "-1c lineend +1c")
1388 else:
1389 head = text.index("insert linestart")
1390 tail = text.index("insert lineend +1c")
1391 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001392 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001393 return head, tail, chars, lines
1394
1395 def set_region(self, head, tail, chars, lines):
1396 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001397 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001398 if newchars == chars:
1399 text.bell()
1400 return
1401 text.tag_remove("sel", "1.0", "end")
1402 text.mark_set("insert", head)
1403 text.undo_block_start()
1404 text.delete(head, tail)
1405 text.insert(head, newchars)
1406 text.undo_block_stop()
1407 text.tag_add("sel", head, "insert")
1408
1409 # Make string that displays as n leading blanks.
1410
1411 def _make_blanks(self, n):
1412 if self.usetabs:
1413 ntabs, nspaces = divmod(n, self.tabwidth)
1414 return '\t' * ntabs + ' ' * nspaces
1415 else:
1416 return ' ' * n
1417
1418 # Delete from beginning of line to insert point, then reinsert
1419 # column logical (meaning use tabs if appropriate) spaces.
1420
1421 def reindent_to(self, column):
1422 text = self.text
1423 text.undo_block_start()
1424 if text.compare("insert linestart", "!=", "insert"):
1425 text.delete("insert linestart", "insert")
1426 if column:
1427 text.insert("insert", self._make_blanks(column))
1428 text.undo_block_stop()
1429
1430 def _asktabwidth(self):
1431 return self.askinteger(
1432 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001433 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001434 parent=self.text,
1435 initialvalue=self.indentwidth,
1436 minvalue=2,
1437 maxvalue=16) or self.tabwidth
1438
1439 # Guess indentwidth from text content.
1440 # Return guessed indentwidth. This should not be believed unless
1441 # it's in a reasonable range (e.g., it will be 0 if no indented
1442 # blocks are found).
1443
1444 def guess_indent(self):
1445 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1446 if opener and indented:
1447 raw, indentsmall = classifyws(opener, self.tabwidth)
1448 raw, indentlarge = classifyws(indented, self.tabwidth)
1449 else:
1450 indentsmall = indentlarge = 0
1451 return indentlarge - indentsmall
1452
1453# "line.col" -> line, as an int
1454def index2line(index):
1455 return int(float(index))
1456
1457# Look at the leading whitespace in s.
1458# Return pair (# of leading ws characters,
1459# effective # of leading blanks after expanding
1460# tabs to width tabwidth)
1461
1462def classifyws(s, tabwidth):
1463 raw = effective = 0
1464 for ch in s:
1465 if ch == ' ':
1466 raw = raw + 1
1467 effective = effective + 1
1468 elif ch == '\t':
1469 raw = raw + 1
1470 effective = (effective // tabwidth + 1) * tabwidth
1471 else:
1472 break
1473 return raw, effective
1474
1475import tokenize
1476_tokenize = tokenize
1477del tokenize
1478
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001479class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001480
1481 # .run() chews over the Text widget, looking for a block opener
1482 # and the stmt following it. Returns a pair,
1483 # (line containing block opener, line containing stmt)
1484 # Either or both may be None.
1485
1486 def __init__(self, text, tabwidth):
1487 self.text = text
1488 self.tabwidth = tabwidth
1489 self.i = self.finished = 0
1490 self.blkopenline = self.indentedline = None
1491
1492 def readline(self):
1493 if self.finished:
1494 return ""
1495 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001496 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001497 if self.text.compare(mark, ">=", "end"):
1498 return ""
1499 return self.text.get(mark, mark + " lineend+1c")
1500
1501 def tokeneater(self, type, token, start, end, line,
1502 INDENT=_tokenize.INDENT,
1503 NAME=_tokenize.NAME,
1504 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1505 if self.finished:
1506 pass
1507 elif type == NAME and token in OPENERS:
1508 self.blkopenline = line
1509 elif type == INDENT and self.blkopenline:
1510 self.indentedline = line
1511 self.finished = 1
1512
1513 def run(self):
1514 save_tabsize = _tokenize.tabsize
1515 _tokenize.tabsize = self.tabwidth
1516 try:
1517 try:
Trent Nelson428de652008-03-18 22:41:35 +00001518 tokens = _tokenize.generate_tokens(self.readline)
1519 for token in tokens:
1520 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001521 except _tokenize.TokenError:
1522 # since we cut off the tokenizer early, we can trigger
1523 # spurious errors
1524 pass
1525 finally:
1526 _tokenize.tabsize = save_tabsize
1527 return self.blkopenline, self.indentedline
1528
1529### end autoindent code ###
1530
David Scherer7aced172000-08-15 01:13:23 +00001531def prepstr(s):
1532 # Helper to extract the underscore from a string, e.g.
1533 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001534 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001535 if i >= 0:
1536 s = s[:i] + s[i+1:]
1537 return i, s
1538
1539
1540keynames = {
1541 'bracketleft': '[',
1542 'bracketright': ']',
1543 'slash': '/',
1544}
1545
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001546def get_accelerator(keydefs, eventname):
1547 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001548 if not keylist:
1549 return ""
1550 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001551 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001552 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1553 s = re.sub("Key-", "", s)
1554 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1555 s = re.sub("Control-", "Ctrl-", s)
1556 s = re.sub("-", "+", s)
1557 s = re.sub("><", " ", s)
1558 s = re.sub("<", "", s)
1559 s = re.sub(">", "", s)
1560 return s
1561
1562
1563def fixwordbreaks(root):
1564 # Make sure that Tk's double-click and next/previous word
1565 # operations use our definition of a word (i.e. an identifier)
1566 tk = root.tk
1567 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1568 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1569 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1570
1571
1572def test():
1573 root = Tk()
1574 fixwordbreaks(root)
1575 root.withdraw()
1576 if sys.argv[1:]:
1577 filename = sys.argv[1]
1578 else:
1579 filename = None
1580 edit = EditorWindow(root=root, filename=filename)
1581 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001582 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001583 root.mainloop()
1584 root.destroy()
1585
1586if __name__ == '__main__':
1587 test()