blob: 98ec02bf7dd46a035f743afc7399ef82d48846e2 [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
Ezio Melotti42da6632011-03-15 05:18:48 +0200108 #self.top.instance_dict makes flist.inversedict available to
109 #configDialog.py so it can access all EditorWindow instances
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):
Kurt B. Kaiser75fc5662011-03-21 02:13:42 -0400307 if (event.state & 4) != 0 and event.keysym == "Home":
308 # state&4==Control. If <Control-Home>, use the Tk binding.
309 return
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000310 if self.text.index("iomark") and \
311 self.text.compare("iomark", "<=", "insert lineend") and \
312 self.text.compare("insert linestart", "<=", "iomark"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400313 # In Shell on input line, go to just after prompt
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000314 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)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000322 lineat = int(self.text.index("insert").split('.')[1])
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000323 if insertpt == lineat:
324 insertpt = 0
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000325 dest = "insert linestart+"+str(insertpt)+"c"
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000326 if (event.state&1) == 0:
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400327 # shift was not pressed
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000328 self.text.tag_remove("sel", "1.0", "end")
329 else:
330 if not self.text.index("sel.first"):
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400331 self.text.mark_set("my_anchor", "insert") # there was no previous selection
332 else:
333 if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
334 self.text.mark_set("my_anchor", "sel.first") # extend back
335 else:
336 self.text.mark_set("my_anchor", "sel.last") # extend forward
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000337 first = self.text.index(dest)
Kurt B. Kaiser946f1722011-03-25 20:29:13 -0400338 last = self.text.index("my_anchor")
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000339 if self.text.compare(first,">",last):
340 first,last = last,first
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000341 self.text.tag_remove("sel", "1.0", "end")
342 self.text.tag_add("sel", first, last)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000343 self.text.mark_set("insert", dest)
344 self.text.see("insert")
345 return "break"
346
David Scherer7aced172000-08-15 01:13:23 +0000347 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000348 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000349 if macosxSupport.runningAsOSXApp():
350 # Insert some padding to avoid obscuring some of the statusbar
351 # by the resize widget.
352 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000353 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
354 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
355 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000356 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
357 self.text.event_add("<<set-line-and-column>>",
358 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000359 self.text.after_idle(self.set_line_and_column)
360
361 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000362 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000363 self.status_bar.set_label('column', 'Col: %s' % column)
364 self.status_bar.set_label('line', 'Ln: %s' % line)
365
David Scherer7aced172000-08-15 01:13:23 +0000366 menu_specs = [
367 ("file", "_File"),
368 ("edit", "_Edit"),
369 ("format", "F_ormat"),
370 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000371 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000372 ("windows", "_Windows"),
373 ("help", "_Help"),
374 ]
375
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000376 if macosxSupport.runningAsOSXApp():
377 del menu_specs[-3]
378 menu_specs[-2] = ("windows", "_Window")
379
380
David Scherer7aced172000-08-15 01:13:23 +0000381 def createmenubar(self):
382 mbar = self.menubar
383 self.menudict = menudict = {}
384 for name, label in self.menu_specs:
385 underline, label = prepstr(label)
386 menudict[name] = menu = Menu(mbar, name=name)
387 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren827822e2009-02-12 15:01:44 +0000388 if macosxSupport.runningAsOSXApp():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000389 # Insert the application menu
390 menudict['application'] = menu = Menu(mbar, name='apple')
391 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000392 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000393 self.recent_files_menu = Menu(self.menubar)
394 self.menudict['file'].insert_cascade(3, label='Recent Files',
395 underline=0,
396 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000397 self.base_helpmenu_length = self.menudict['help'].index(END)
398 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000399
400 def postwindowsmenu(self):
401 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000402 menu = self.menudict['windows']
403 end = menu.index("end")
404 if end is None:
405 end = -1
406 if end > self.wmenu_end:
407 menu.delete(self.wmenu_end+1, end)
408 WindowList.add_windows_to_menu(menu)
409
410 rmenu = None
411
412 def right_menu_event(self, event):
413 self.text.tag_remove("sel", "1.0", "end")
414 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
415 if not self.rmenu:
416 self.make_rmenu()
417 rmenu = self.rmenu
418 self.event = event
419 iswin = sys.platform[:3] == 'win'
420 if iswin:
421 self.text.config(cursor="arrow")
422 rmenu.tk_popup(event.x_root, event.y_root)
423 if iswin:
424 self.text.config(cursor="ibeam")
425
426 rmenu_specs = [
427 # ("Label", "<<virtual-event>>"), ...
428 ("Close", "<<close-window>>"), # Example
429 ]
430
431 def make_rmenu(self):
432 rmenu = Menu(self.text, tearoff=0)
433 for label, eventname in self.rmenu_specs:
434 def command(text=self.text, eventname=eventname):
435 text.event_generate(eventname)
436 rmenu.add_command(label=label, command=command)
437 self.rmenu = rmenu
438
439 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000440 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000441
Steven M. Gava3b55a892001-11-21 05:56:26 +0000442 def config_dialog(self, event=None):
443 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000444
David Scherer7aced172000-08-15 01:13:23 +0000445 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000446 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000447 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000448
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000449 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000450 if sys.platform[:3] == 'win':
Terry Reedy574a3cf2011-01-01 02:28:54 +0000451 try:
452 os.startfile(self.help_url)
453 except WindowsError as why:
454 tkMessageBox.showerror(title='Document Start Failure',
455 message=str(why), parent=self.text)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000456 else:
457 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000458 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000459
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000460 def cut(self,event):
461 self.text.event_generate("<<Cut>>")
462 return "break"
463
464 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000465 if not self.text.tag_ranges("sel"):
466 # There is no selection, so do nothing and maybe interrupt.
467 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000468 self.text.event_generate("<<Copy>>")
469 return "break"
470
471 def paste(self,event):
472 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000473 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000474 return "break"
475
David Scherer7aced172000-08-15 01:13:23 +0000476 def select_all(self, event=None):
477 self.text.tag_add("sel", "1.0", "end-1c")
478 self.text.mark_set("insert", "1.0")
479 self.text.see("insert")
480 return "break"
481
482 def remove_selection(self, event=None):
483 self.text.tag_remove("sel", "1.0", "end")
484 self.text.see("insert")
485
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000486 def move_at_edge_if_selection(self, edge_index):
487 """Cursor move begins at start or end of selection
488
489 When a left/right cursor key is pressed create and return to Tkinter a
490 function which causes a cursor move from the associated edge of the
491 selection.
492
493 """
494 self_text_index = self.text.index
495 self_text_mark_set = self.text.mark_set
496 edges_table = ("sel.first+1c", "sel.last-1c")
497 def move_at_edge(event):
498 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
499 try:
500 self_text_index("sel.first")
501 self_text_mark_set("insert", edges_table[edge_index])
502 except TclError:
503 pass
504 return move_at_edge
505
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000506 def del_word_left(self, event):
507 self.text.event_generate('<Meta-Delete>')
508 return "break"
509
510 def del_word_right(self, event):
511 self.text.event_generate('<Meta-d>')
512 return "break"
513
Steven M. Gavac5976402002-01-04 03:06:08 +0000514 def find_event(self, event):
515 SearchDialog.find(self.text)
516 return "break"
517
518 def find_again_event(self, event):
519 SearchDialog.find_again(self.text)
520 return "break"
521
522 def find_selection_event(self, event):
523 SearchDialog.find_selection(self.text)
524 return "break"
525
526 def find_in_files_event(self, event):
527 GrepDialog.grep(self.text, self.io, self.flist)
528 return "break"
529
530 def replace_event(self, event):
531 ReplaceDialog.replace(self.text)
532 return "break"
533
534 def goto_line_event(self, event):
535 text = self.text
536 lineno = tkSimpleDialog.askinteger("Goto",
537 "Go to line number:",parent=text)
538 if lineno is None:
539 return "break"
540 if lineno <= 0:
541 text.bell()
542 return "break"
543 text.mark_set("insert", "%d.0" % lineno)
544 text.see("insert")
545
David Scherer7aced172000-08-15 01:13:23 +0000546 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000547 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000548 try:
549 name = self.text.get("sel.first", "sel.last")
550 except TclError:
551 name = ""
552 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000553 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000554 name = tkSimpleDialog.askstring("Module",
555 "Enter the name of a Python module\n"
556 "to search on sys.path and open:",
557 parent=self.text, initialvalue=name)
558 if name:
559 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000560 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000561 return
David Scherer7aced172000-08-15 01:13:23 +0000562 # XXX Ought to insert current file's directory in front of path
563 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000564 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000565 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000566 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
567 return
568 if type != imp.PY_SOURCE:
569 tkMessageBox.showerror("Unsupported type",
570 "%s is not a source module" % name, parent=self.text)
571 return
572 if f:
573 f.close()
574 if self.flist:
575 self.flist.open(file)
576 else:
577 self.io.loadfile(file)
578
579 def open_class_browser(self, event=None):
580 filename = self.io.filename
581 if not filename:
582 tkMessageBox.showerror(
583 "No filename",
584 "This buffer has no associated filename",
585 master=self.text)
586 self.text.focus_set()
587 return None
588 head, tail = os.path.split(filename)
589 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000590 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000591 ClassBrowser.ClassBrowser(self.flist, base, [head])
592
593 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000594 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000595 PathBrowser.PathBrowser(self.flist)
596
597 def gotoline(self, lineno):
598 if lineno is not None and lineno > 0:
599 self.text.mark_set("insert", "%d.0" % lineno)
600 self.text.tag_remove("sel", "1.0", "end")
601 self.text.tag_add("sel", "insert", "insert +1l")
602 self.center()
603
604 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000605 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000606 return True
David Scherer7aced172000-08-15 01:13:23 +0000607 base, ext = os.path.splitext(os.path.basename(filename))
608 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000609 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000610 line = self.text.get('1.0', '1.0 lineend')
611 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000612
613 def close_hook(self):
614 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000615 self.flist.unregister_maybe_terminate(self)
616 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000617
618 def set_close_hook(self, close_hook):
619 self.close_hook = close_hook
620
621 def filename_change_hook(self):
622 if self.flist:
623 self.flist.filename_changed_edit(self)
624 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000625 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000626 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000627
Christian Heimesa156e092008-02-16 07:38:31 +0000628 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000629 if self.color:
630 return
Christian Heimesa156e092008-02-16 07:38:31 +0000631 if self.ispythonsource(self.io.filename):
632 self.color = self.ColorDelegator()
633 # can add more colorizers here...
634 if self.color:
635 self.per.removefilter(self.undo)
636 self.per.insertfilter(self.color)
637 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000638
Christian Heimesa156e092008-02-16 07:38:31 +0000639 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000640 if not self.color:
641 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000642 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000643 self.per.removefilter(self.color)
644 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000645
Steven M. Gavab77d3432002-03-02 07:16:21 +0000646 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000647 "Update the colour theme"
648 # Called from self.filename_change_hook and from configDialog.py
649 self._rmcolorizer()
650 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000651 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000652 normal_colors = idleConf.GetHighlight(theme, 'normal')
653 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
654 select_colors = idleConf.GetHighlight(theme, 'hilite')
655 self.text.config(
656 foreground=normal_colors['foreground'],
657 background=normal_colors['background'],
658 insertbackground=cursor_color,
659 selectforeground=select_colors['foreground'],
660 selectbackground=select_colors['background'],
661 )
David Scherer7aced172000-08-15 01:13:23 +0000662
Guido van Rossum33d26892007-08-05 15:29:28 +0000663 IDENTCHARS = string.ascii_letters + string.digits + "_"
664
665 def colorize_syntax_error(self, text, pos):
666 text.tag_add("ERROR", pos)
667 char = text.get(pos)
668 if char and char in self.IDENTCHARS:
669 text.tag_add("ERROR", pos + " wordstart", pos)
670 if '\n' == text.get(pos): # error at line end
671 text.mark_set("insert", pos)
672 else:
673 text.mark_set("insert", pos + "+1c")
674 text.see(pos)
675
Steven M. Gavab1585412002-03-12 00:21:56 +0000676 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000677 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000678 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000679 fontWeight='normal'
680 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
681 fontWeight='bold'
682 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
683 idleConf.GetOption('main','EditorWindow','font-size'),
684 fontWeight))
685
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000686 def RemoveKeybindings(self):
687 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000688 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000689 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000690 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000691 self.text.event_delete(event, *keylist)
692 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000693 xkeydefs = idleConf.GetExtensionBindings(extensionName)
694 if xkeydefs:
695 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000696 self.text.event_delete(event, *keylist)
697
698 def ApplyKeybindings(self):
699 "Update the keybindings after they are changed"
700 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000701 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000702 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000703 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000704 xkeydefs = idleConf.GetExtensionBindings(extensionName)
705 if xkeydefs:
706 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000707 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000708 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000709 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000710 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000711 for item in menu[1]:
712 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000713 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000714 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000715 menu = self.menudict[menubarItem]
716 end = menu.index(END) + 1
717 for index in range(0, end):
718 if menu.type(index) == 'command':
719 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000720 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000721 itemName = menu.entrycget(index, 'label')
722 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000723 if menubarItem in menuEventDict:
724 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000725 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000726 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000727 accel = get_accelerator(keydefs, event)
728 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000729
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000730 def set_notabs_indentwidth(self):
731 "Update the indentwidth if changed and not using tabs in this window"
732 # Called from configDialog.py
733 if not self.usetabs:
734 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
735 type='int')
736
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000737 def reset_help_menu_entries(self):
738 "Update the additional help entries on the Help menu"
739 help_list = idleConf.GetAllExtraHelpSourcesList()
740 helpmenu = self.menudict['help']
741 # first delete the extra help entries, if any
742 helpmenu_length = helpmenu.index(END)
743 if helpmenu_length > self.base_helpmenu_length:
744 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
745 # then rebuild them
746 if help_list:
747 helpmenu.add_separator()
748 for entry in help_list:
749 cmd = self.__extra_help_callback(entry[1])
750 helpmenu.add_command(label=entry[0], command=cmd)
751 # and update the menu dictionary
752 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000753
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000754 def __extra_help_callback(self, helpfile):
755 "Create a callback with the helpfile value frozen at definition time"
756 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000757 if not helpfile.startswith(('www', 'http')):
Terry Reedy574a3cf2011-01-01 02:28:54 +0000758 helpfile = os.path.normpath(helpfile)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000759 if sys.platform[:3] == 'win':
Terry Reedy574a3cf2011-01-01 02:28:54 +0000760 try:
761 os.startfile(helpfile)
762 except WindowsError as why:
763 tkMessageBox.showerror(title='Document Start Failure',
764 message=str(why), parent=self.text)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000765 else:
766 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000767 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000768
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000769 def update_recent_files_list(self, new_file=None):
770 "Load and update the recent files list and menus"
771 rf_list = []
772 if os.path.exists(self.recent_files_path):
Ned Deilyab5dd002011-01-24 22:22:06 +0000773 rf_list_file = open(self.recent_files_path,'r',
774 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000775 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000776 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000777 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000778 rf_list_file.close()
779 if new_file:
780 new_file = os.path.abspath(new_file) + '\n'
781 if new_file in rf_list:
782 rf_list.remove(new_file) # move to top
783 rf_list.insert(0, new_file)
784 # clean and save the recent files list
785 bad_paths = []
786 for path in rf_list:
787 if '\0' in path or not os.path.exists(path[0:-1]):
788 bad_paths.append(path)
789 rf_list = [path for path in rf_list if path not in bad_paths]
790 ulchars = "1234567890ABCDEFGHIJK"
791 rf_list = rf_list[0:len(ulchars)]
Ned Deilyab5dd002011-01-24 22:22:06 +0000792 rf_file = open(self.recent_files_path, 'w',
793 encoding='utf_8', errors='replace')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000794 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000795 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000796 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000797 rf_file.close()
798 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000799 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000800 menu = instance.recent_files_menu
801 menu.delete(1, END) # clear, and rebuild:
802 for i, file in zip(count(), rf_list):
803 file_name = file[0:-1] # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000804 # make unicode string to display non-ASCII chars correctly
805 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000806 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000807 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000808 command=callback,
809 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000810
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000811 def __recent_file_callback(self, file_name):
812 def open_recent_file(fn_closure=file_name):
813 self.io.open(editFile=fn_closure)
814 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000815
David Scherer7aced172000-08-15 01:13:23 +0000816 def saved_change_hook(self):
817 short = self.short_title()
818 long = self.long_title()
819 if short and long:
820 title = short + " - " + long
821 elif short:
822 title = short
823 elif long:
824 title = long
825 else:
826 title = "Untitled"
827 icon = short or long or title
828 if not self.get_saved():
829 title = "*%s*" % title
830 icon = "*%s" % icon
831 self.top.wm_title(title)
832 self.top.wm_iconname(icon)
833
834 def get_saved(self):
835 return self.undo.get_saved()
836
837 def set_saved(self, flag):
838 self.undo.set_saved(flag)
839
840 def reset_undo(self):
841 self.undo.reset_undo()
842
843 def short_title(self):
844 filename = self.io.filename
845 if filename:
846 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000847 # return unicode string to display non-ASCII chars correctly
848 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000849
850 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000851 # return unicode string to display non-ASCII chars correctly
852 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000853
854 def center_insert_event(self, event):
855 self.center()
856
857 def center(self, mark="insert"):
858 text = self.text
859 top, bot = self.getwindowlines()
860 lineno = self.getlineno(mark)
861 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000862 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000863 text.yview(float(newtop))
864
865 def getwindowlines(self):
866 text = self.text
867 top = self.getlineno("@0,0")
868 bot = self.getlineno("@0,65535")
869 if top == bot and text.winfo_height() == 1:
870 # Geometry manager hasn't run yet
871 height = int(text['height'])
872 bot = top + height - 1
873 return top, bot
874
875 def getlineno(self, mark="insert"):
876 text = self.text
877 return int(float(text.index(mark)))
878
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000879 def get_geometry(self):
880 "Return (width, height, x, y)"
881 geom = self.top.wm_geometry()
882 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000883 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000884
David Scherer7aced172000-08-15 01:13:23 +0000885 def close_event(self, event):
886 self.close()
887
888 def maybesave(self):
889 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000890 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000891 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000892 self.top.deiconify()
893 self.top.lower()
894 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000895 return self.io.maybesave()
896
897 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000898 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000899 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000900 self._close()
901 return reply
902
903 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000904 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000905 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000906 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000907 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000908 self.io.close()
909 self.io = None
910 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000911 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000912 self.color.close(False)
913 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000914 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000915 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000916 self.per.close()
917 self.per = None
918 self.top.destroy()
919 if self.close_hook:
920 # unless override: unregister from flist, terminate if last window
921 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000922
923 def load_extensions(self):
924 self.extensions = {}
925 self.load_standard_extensions()
926
927 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000928 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000929 if hasattr(ins, "close"):
930 ins.close()
931 self.extensions = {}
932
933 def load_standard_extensions(self):
934 for name in self.get_standard_extension_names():
935 try:
936 self.load_extension(name)
937 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000938 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000939 traceback.print_exc()
940
941 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000942 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000943
944 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000945 try:
946 mod = __import__(name, globals(), locals(), [])
947 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000948 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000949 raise
David Scherer7aced172000-08-15 01:13:23 +0000950 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000951 keydefs = idleConf.GetExtensionBindings(name)
952 if hasattr(cls, "menudefs"):
953 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000954 ins = cls(self)
955 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000956 if keydefs:
957 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000958 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000959 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000960 while methodname[:1] == '<':
961 methodname = methodname[1:]
962 while methodname[-1:] == '>':
963 methodname = methodname[:-1]
964 methodname = methodname + "_event"
965 if hasattr(ins, methodname):
966 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000967
968 def apply_bindings(self, keydefs=None):
969 if keydefs is None:
970 keydefs = self.Bindings.default_keydefs
971 text = self.text
972 text.keydefs = keydefs
973 for event, keylist in keydefs.items():
974 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000975 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000976
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000977 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000978 """Add appropriate entries to the menus and submenus
979
980 Menus that are absent or None in self.menudict are ignored.
981 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000982 if menudefs is None:
983 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000984 if keydefs is None:
985 keydefs = self.Bindings.default_keydefs
986 menudict = self.menudict
987 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000988 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000989 menu = menudict.get(mname)
990 if not menu:
991 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000992 for entry in entrylist:
993 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000994 menu.add_separator()
995 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000996 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000997 checkbutton = (label[:1] == '!')
998 if checkbutton:
999 label = label[1:]
1000 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001001 accelerator = get_accelerator(keydefs, eventname)
1002 def command(text=text, eventname=eventname):
1003 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001004 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001005 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +00001006 menu.add_checkbutton(label=label, underline=underline,
1007 command=command, accelerator=accelerator,
1008 variable=var)
1009 else:
1010 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +00001011 command=command,
1012 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001013
1014 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001015 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001016 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001017 value = var.get()
1018 return value
1019 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001020 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001021
1022 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001023 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001024 if var:
1025 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001026 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001027 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001028
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001029 def get_var_obj(self, name, vartype=None):
1030 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001031 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001032 # create a Tkinter variable object with self.text as master:
1033 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001034 return var
1035
1036 # Tk implementations of "virtual text methods" -- each platform
1037 # reusing IDLE's support code needs to define these for its GUI's
1038 # flavor of widget.
1039
1040 # Is character at text_index in a Python string? Return 0 for
1041 # "guaranteed no", true for anything else. This info is expensive
1042 # to compute ab initio, but is probably already known by the
1043 # platform's colorizer.
1044
1045 def is_char_in_string(self, text_index):
1046 if self.color:
1047 # Return true iff colorizer hasn't (re)gotten this far
1048 # yet, or the character is tagged as being in a string
1049 return self.text.tag_prevrange("TODO", text_index) or \
1050 "STRING" in self.text.tag_names(text_index)
1051 else:
1052 # The colorizer is missing: assume the worst
1053 return 1
1054
1055 # If a selection is defined in the text widget, return (start,
1056 # end) as Tkinter text indices, otherwise return (None, None)
1057 def get_selection_indices(self):
1058 try:
1059 first = self.text.index("sel.first")
1060 last = self.text.index("sel.last")
1061 return first, last
1062 except TclError:
1063 return None, None
1064
1065 # Return the text widget's current view of what a tab stop means
1066 # (equivalent width in spaces).
1067
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001068 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001069 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1070 return int(current)
1071
1072 # Set the text widget's current view of what a tab stop means.
1073
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001074 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001075 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001076 if self.get_tk_tabwidth() != newtabwidth:
1077 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001078 pixels = text.tk.call("font", "measure", text["font"],
1079 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001080 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001081 text.configure(tabs=pixels)
1082
Guido van Rossum33d26892007-08-05 15:29:28 +00001083### begin autoindent code ### (configuration was moved to beginning of class)
1084
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001085 def set_indentation_params(self, is_py_src, guess=True):
1086 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001087 i = self.guess_indent()
1088 if 2 <= i <= 8:
1089 self.indentwidth = i
1090 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001091 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001092 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001093
1094 def smart_backspace_event(self, event):
1095 text = self.text
1096 first, last = self.get_selection_indices()
1097 if first and last:
1098 text.delete(first, last)
1099 text.mark_set("insert", first)
1100 return "break"
1101 # Delete whitespace left, until hitting a real char or closest
1102 # preceding virtual tab stop.
1103 chars = text.get("insert linestart", "insert")
1104 if chars == '':
1105 if text.compare("insert", ">", "1.0"):
1106 # easy: delete preceding newline
1107 text.delete("insert-1c")
1108 else:
1109 text.bell() # at start of buffer
1110 return "break"
1111 if chars[-1] not in " \t":
1112 # easy: delete preceding real char
1113 text.delete("insert-1c")
1114 return "break"
1115 # Ick. It may require *inserting* spaces if we back up over a
1116 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001117 tabwidth = self.tabwidth
1118 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001119 assert have > 0
1120 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001121 # Debug prompt is multilined....
1122 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001123 ncharsdeleted = 0
1124 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001125 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001126 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001127 chars = chars[:-1]
1128 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001129 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001130 if have <= want or chars[-1] not in " \t":
1131 break
1132 text.undo_block_start()
1133 text.delete("insert-%dc" % ncharsdeleted, "insert")
1134 if have < want:
1135 text.insert("insert", ' ' * (want - have))
1136 text.undo_block_stop()
1137 return "break"
1138
1139 def smart_indent_event(self, event):
1140 # if intraline selection:
1141 # delete it
1142 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001143 # do indent-region
1144 # else:
1145 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001146 text = self.text
1147 first, last = self.get_selection_indices()
1148 text.undo_block_start()
1149 try:
1150 if first and last:
1151 if index2line(first) != index2line(last):
1152 return self.indent_region_event(event)
1153 text.delete(first, last)
1154 text.mark_set("insert", first)
1155 prefix = text.get("insert linestart", "insert")
1156 raw, effective = classifyws(prefix, self.tabwidth)
1157 if raw == len(prefix):
1158 # only whitespace to the left
1159 self.reindent_to(effective + self.indentwidth)
1160 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001161 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001162 if self.usetabs:
1163 pad = '\t'
1164 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001165 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001166 n = self.indentwidth
1167 pad = ' ' * (n - effective % n)
1168 text.insert("insert", pad)
1169 text.see("insert")
1170 return "break"
1171 finally:
1172 text.undo_block_stop()
1173
1174 def newline_and_indent_event(self, event):
1175 text = self.text
1176 first, last = self.get_selection_indices()
1177 text.undo_block_start()
1178 try:
1179 if first and last:
1180 text.delete(first, last)
1181 text.mark_set("insert", first)
1182 line = text.get("insert linestart", "insert")
1183 i, n = 0, len(line)
1184 while i < n and line[i] in " \t":
1185 i = i+1
1186 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001187 # the cursor is in or at leading indentation in a continuation
1188 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001189 text.insert("insert linestart", '\n')
1190 return "break"
1191 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001192 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001193 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001194 last_line_of_prompt = sys.ps1.split('\n')[-1]
1195 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001196 line = line[:-1]
1197 i = i+1
1198 if i:
1199 text.delete("insert - %d chars" % i, "insert")
1200 # strip whitespace after insert point
1201 while text.get("insert") in " \t":
1202 text.delete("insert")
1203 # start new line
1204 text.insert("insert", '\n')
1205
1206 # adjust indentation for continuations and block
1207 # open/close first need to find the last stmt
1208 lno = index2line(text.index('insert'))
1209 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001210 if not self.context_use_ps1:
1211 for context in self.num_context_lines:
1212 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001213 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001214 rawtext = text.get(startatindex, "insert")
1215 y.set_str(rawtext)
1216 bod = y.find_good_parse_start(
1217 self.context_use_ps1,
1218 self._build_char_in_string_func(startatindex))
1219 if bod is not None or startat == 1:
1220 break
1221 y.set_lo(bod or 0)
1222 else:
1223 r = text.tag_prevrange("console", "insert")
1224 if r:
1225 startatindex = r[1]
1226 else:
1227 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001228 rawtext = text.get(startatindex, "insert")
1229 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001230 y.set_lo(0)
1231
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001232 c = y.get_continuation_type()
1233 if c != PyParse.C_NONE:
1234 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001235 if c == PyParse.C_STRING_FIRST_LINE:
1236 # after the first line of a string; do not indent at all
1237 pass
1238 elif c == PyParse.C_STRING_NEXT_LINES:
1239 # inside a string which started before this line;
1240 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001241 text.insert("insert", indent)
1242 elif c == PyParse.C_BRACKET:
1243 # line up with the first (if any) element of the
1244 # last open bracket structure; else indent one
1245 # level beyond the indent of the line with the
1246 # last open bracket
1247 self.reindent_to(y.compute_bracket_indent())
1248 elif c == PyParse.C_BACKSLASH:
1249 # if more than one line in this stmt already, just
1250 # mimic the current indent; else if initial line
1251 # has a start on an assignment stmt, indent to
1252 # beyond leftmost =; else to beyond first chunk of
1253 # non-whitespace on initial line
1254 if y.get_num_lines_in_stmt() > 1:
1255 text.insert("insert", indent)
1256 else:
1257 self.reindent_to(y.compute_backslash_indent())
1258 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001259 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001260 return "break"
1261
1262 # This line starts a brand new stmt; indent relative to
1263 # indentation of initial line of closest preceding
1264 # interesting stmt.
1265 indent = y.get_base_indent_string()
1266 text.insert("insert", indent)
1267 if y.is_block_opener():
1268 self.smart_indent_event(event)
1269 elif indent and y.is_block_closer():
1270 self.smart_backspace_event(event)
1271 return "break"
1272 finally:
1273 text.see("insert")
1274 text.undo_block_stop()
1275
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001276 # Our editwin provides a is_char_in_string function that works
1277 # with a Tk text index, but PyParse only knows about offsets into
1278 # a string. This builds a function for PyParse that accepts an
1279 # offset.
1280
1281 def _build_char_in_string_func(self, startindex):
1282 def inner(offset, _startindex=startindex,
1283 _icis=self.is_char_in_string):
1284 return _icis(_startindex + "+%dc" % offset)
1285 return inner
1286
1287 def indent_region_event(self, event):
1288 head, tail, chars, lines = self.get_region()
1289 for pos in range(len(lines)):
1290 line = lines[pos]
1291 if line:
1292 raw, effective = classifyws(line, self.tabwidth)
1293 effective = effective + self.indentwidth
1294 lines[pos] = self._make_blanks(effective) + line[raw:]
1295 self.set_region(head, tail, chars, lines)
1296 return "break"
1297
1298 def dedent_region_event(self, event):
1299 head, tail, chars, lines = self.get_region()
1300 for pos in range(len(lines)):
1301 line = lines[pos]
1302 if line:
1303 raw, effective = classifyws(line, self.tabwidth)
1304 effective = max(effective - self.indentwidth, 0)
1305 lines[pos] = self._make_blanks(effective) + line[raw:]
1306 self.set_region(head, tail, chars, lines)
1307 return "break"
1308
1309 def comment_region_event(self, event):
1310 head, tail, chars, lines = self.get_region()
1311 for pos in range(len(lines) - 1):
1312 line = lines[pos]
1313 lines[pos] = '##' + line
1314 self.set_region(head, tail, chars, lines)
1315
1316 def uncomment_region_event(self, event):
1317 head, tail, chars, lines = self.get_region()
1318 for pos in range(len(lines)):
1319 line = lines[pos]
1320 if not line:
1321 continue
1322 if line[:2] == '##':
1323 line = line[2:]
1324 elif line[:1] == '#':
1325 line = line[1:]
1326 lines[pos] = line
1327 self.set_region(head, tail, chars, lines)
1328
1329 def tabify_region_event(self, event):
1330 head, tail, chars, lines = self.get_region()
1331 tabwidth = self._asktabwidth()
1332 for pos in range(len(lines)):
1333 line = lines[pos]
1334 if line:
1335 raw, effective = classifyws(line, tabwidth)
1336 ntabs, nspaces = divmod(effective, tabwidth)
1337 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1338 self.set_region(head, tail, chars, lines)
1339
1340 def untabify_region_event(self, event):
1341 head, tail, chars, lines = self.get_region()
1342 tabwidth = self._asktabwidth()
1343 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001344 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001345 self.set_region(head, tail, chars, lines)
1346
1347 def toggle_tabs_event(self, event):
1348 if self.askyesno(
1349 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001350 "Turn tabs " + ("on", "off")[self.usetabs] +
1351 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001352 ("will be", "remains at")[self.usetabs] + " 8." +
1353 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001354 parent=self.text):
1355 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001356 # Try to prevent inconsistent indentation.
1357 # User must change indent width manually after using tabs.
1358 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001359 return "break"
1360
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001361 # XXX this isn't bound to anything -- see tabwidth comments
1362## def change_tabwidth_event(self, event):
1363## new = self._asktabwidth()
1364## if new != self.tabwidth:
1365## self.tabwidth = new
1366## self.set_indentation_params(0, guess=0)
1367## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001368
1369 def change_indentwidth_event(self, event):
1370 new = self.askinteger(
1371 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001372 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001373 parent=self.text,
1374 initialvalue=self.indentwidth,
1375 minvalue=2,
1376 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001377 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001378 self.indentwidth = new
1379 return "break"
1380
1381 def get_region(self):
1382 text = self.text
1383 first, last = self.get_selection_indices()
1384 if first and last:
1385 head = text.index(first + " linestart")
1386 tail = text.index(last + "-1c lineend +1c")
1387 else:
1388 head = text.index("insert linestart")
1389 tail = text.index("insert lineend +1c")
1390 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001391 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001392 return head, tail, chars, lines
1393
1394 def set_region(self, head, tail, chars, lines):
1395 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001396 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001397 if newchars == chars:
1398 text.bell()
1399 return
1400 text.tag_remove("sel", "1.0", "end")
1401 text.mark_set("insert", head)
1402 text.undo_block_start()
1403 text.delete(head, tail)
1404 text.insert(head, newchars)
1405 text.undo_block_stop()
1406 text.tag_add("sel", head, "insert")
1407
1408 # Make string that displays as n leading blanks.
1409
1410 def _make_blanks(self, n):
1411 if self.usetabs:
1412 ntabs, nspaces = divmod(n, self.tabwidth)
1413 return '\t' * ntabs + ' ' * nspaces
1414 else:
1415 return ' ' * n
1416
1417 # Delete from beginning of line to insert point, then reinsert
1418 # column logical (meaning use tabs if appropriate) spaces.
1419
1420 def reindent_to(self, column):
1421 text = self.text
1422 text.undo_block_start()
1423 if text.compare("insert linestart", "!=", "insert"):
1424 text.delete("insert linestart", "insert")
1425 if column:
1426 text.insert("insert", self._make_blanks(column))
1427 text.undo_block_stop()
1428
1429 def _asktabwidth(self):
1430 return self.askinteger(
1431 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001432 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001433 parent=self.text,
1434 initialvalue=self.indentwidth,
1435 minvalue=2,
1436 maxvalue=16) or self.tabwidth
1437
1438 # Guess indentwidth from text content.
1439 # Return guessed indentwidth. This should not be believed unless
1440 # it's in a reasonable range (e.g., it will be 0 if no indented
1441 # blocks are found).
1442
1443 def guess_indent(self):
1444 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1445 if opener and indented:
1446 raw, indentsmall = classifyws(opener, self.tabwidth)
1447 raw, indentlarge = classifyws(indented, self.tabwidth)
1448 else:
1449 indentsmall = indentlarge = 0
1450 return indentlarge - indentsmall
1451
1452# "line.col" -> line, as an int
1453def index2line(index):
1454 return int(float(index))
1455
1456# Look at the leading whitespace in s.
1457# Return pair (# of leading ws characters,
1458# effective # of leading blanks after expanding
1459# tabs to width tabwidth)
1460
1461def classifyws(s, tabwidth):
1462 raw = effective = 0
1463 for ch in s:
1464 if ch == ' ':
1465 raw = raw + 1
1466 effective = effective + 1
1467 elif ch == '\t':
1468 raw = raw + 1
1469 effective = (effective // tabwidth + 1) * tabwidth
1470 else:
1471 break
1472 return raw, effective
1473
1474import tokenize
1475_tokenize = tokenize
1476del tokenize
1477
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001478class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001479
1480 # .run() chews over the Text widget, looking for a block opener
1481 # and the stmt following it. Returns a pair,
1482 # (line containing block opener, line containing stmt)
1483 # Either or both may be None.
1484
1485 def __init__(self, text, tabwidth):
1486 self.text = text
1487 self.tabwidth = tabwidth
1488 self.i = self.finished = 0
1489 self.blkopenline = self.indentedline = None
1490
1491 def readline(self):
1492 if self.finished:
1493 return ""
1494 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001495 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001496 if self.text.compare(mark, ">=", "end"):
1497 return ""
1498 return self.text.get(mark, mark + " lineend+1c")
1499
1500 def tokeneater(self, type, token, start, end, line,
1501 INDENT=_tokenize.INDENT,
1502 NAME=_tokenize.NAME,
1503 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1504 if self.finished:
1505 pass
1506 elif type == NAME and token in OPENERS:
1507 self.blkopenline = line
1508 elif type == INDENT and self.blkopenline:
1509 self.indentedline = line
1510 self.finished = 1
1511
1512 def run(self):
1513 save_tabsize = _tokenize.tabsize
1514 _tokenize.tabsize = self.tabwidth
1515 try:
1516 try:
Trent Nelson428de652008-03-18 22:41:35 +00001517 tokens = _tokenize.generate_tokens(self.readline)
1518 for token in tokens:
1519 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001520 except _tokenize.TokenError:
1521 # since we cut off the tokenizer early, we can trigger
1522 # spurious errors
1523 pass
1524 finally:
1525 _tokenize.tabsize = save_tabsize
1526 return self.blkopenline, self.indentedline
1527
1528### end autoindent code ###
1529
David Scherer7aced172000-08-15 01:13:23 +00001530def prepstr(s):
1531 # Helper to extract the underscore from a string, e.g.
1532 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001533 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001534 if i >= 0:
1535 s = s[:i] + s[i+1:]
1536 return i, s
1537
1538
1539keynames = {
1540 'bracketleft': '[',
1541 'bracketright': ']',
1542 'slash': '/',
1543}
1544
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001545def get_accelerator(keydefs, eventname):
1546 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001547 if not keylist:
1548 return ""
1549 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001550 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001551 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1552 s = re.sub("Key-", "", s)
1553 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1554 s = re.sub("Control-", "Ctrl-", s)
1555 s = re.sub("-", "+", s)
1556 s = re.sub("><", " ", s)
1557 s = re.sub("<", "", s)
1558 s = re.sub(">", "", s)
1559 return s
1560
1561
1562def fixwordbreaks(root):
1563 # Make sure that Tk's double-click and next/previous word
1564 # operations use our definition of a word (i.e. an identifier)
1565 tk = root.tk
1566 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1567 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1568 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1569
1570
1571def test():
1572 root = Tk()
1573 fixwordbreaks(root)
1574 root.withdraw()
1575 if sys.argv[1:]:
1576 filename = sys.argv[1]
1577 else:
1578 filename = None
1579 edit = EditorWindow(root=root, filename=filename)
1580 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001581 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001582 root.mainloop()
1583 root.destroy()
1584
1585if __name__ == '__main__':
1586 test()