blob: c1e9e1e7ff90a96d5dfd55279b698c64367ac8f3 [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)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000142 text.bind("<<cut>>", self.cut)
143 text.bind("<<copy>>", self.copy)
144 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000145 text.bind("<<center-insert>>", self.center_insert_event)
146 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000147 text.bind("<<python-docs>>", self.python_docs)
148 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000149 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000150 text.bind("<<open-module>>", self.open_module)
151 text.bind("<<do-nothing>>", lambda event: "break")
152 text.bind("<<select-all>>", self.select_all)
153 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000154 text.bind("<<find>>", self.find_event)
155 text.bind("<<find-again>>", self.find_again_event)
156 text.bind("<<find-in-files>>", self.find_in_files_event)
157 text.bind("<<find-selection>>", self.find_selection_event)
158 text.bind("<<replace>>", self.replace_event)
159 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000160 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000161 text.bind("<<smart-backspace>>",self.smart_backspace_event)
162 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
163 text.bind("<<smart-indent>>",self.smart_indent_event)
164 text.bind("<<indent-region>>",self.indent_region_event)
165 text.bind("<<dedent-region>>",self.dedent_region_event)
166 text.bind("<<comment-region>>",self.comment_region_event)
167 text.bind("<<uncomment-region>>",self.uncomment_region_event)
168 text.bind("<<tabify-region>>",self.tabify_region_event)
169 text.bind("<<untabify-region>>",self.untabify_region_event)
170 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
171 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000172 text.bind("<Left>", self.move_at_edge_if_selection(0))
173 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000174 text.bind("<<del-word-left>>", self.del_word_left)
175 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000176 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000177
David Scherer7aced172000-08-15 01:13:23 +0000178 if flist:
179 flist.inversedict[self] = key
180 if key:
181 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000182 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000183 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
184 text.bind("<<open-class-browser>>", self.open_class_browser)
185 text.bind("<<open-path-browser>>", self.open_path_browser)
186
Steven M. Gava898a3652001-10-07 11:10:44 +0000187 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000188 vbar['command'] = text.yview
189 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000190 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000191 fontWeight = 'normal'
192 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000193 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000194 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
195 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
196 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000197 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
198 text.pack(side=TOP, fill=BOTH, expand=1)
199 text.focus_set()
200
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000201 # usetabs true -> literal tab characters are used by indent and
202 # dedent cmds, possibly mixed with spaces if
203 # indentwidth is not a multiple of tabwidth,
204 # which will cause Tabnanny to nag!
205 # false -> tab characters are converted to spaces by indent
206 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000207 # Although use-spaces=0 can be configured manually in config-main.def,
208 # configuration of tabs v. spaces is not supported in the configuration
209 # dialog. IDLE promotes the preferred Python indentation: use spaces!
210 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
211 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000212
213 # tabwidth is the display width of a literal tab character.
214 # CAUTION: telling Tk to use anything other than its default
215 # tab setting causes it to use an entirely different tabbing algorithm,
216 # treating tab stops as fixed distances from the left margin.
217 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000218 self.tabwidth = 8 # must remain 8 until Tk is fixed.
219
220 # indentwidth is the number of screen characters per indent level.
221 # The recommended Python indentation is four spaces.
222 self.indentwidth = self.tabwidth
223 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000224
225 # If context_use_ps1 is true, parsing searches back for a ps1 line;
226 # else searches for a popular (if, def, ...) Python stmt.
227 self.context_use_ps1 = False
228
229 # When searching backwards for a reliable place to begin parsing,
230 # first start num_context_lines[0] lines back, then
231 # num_context_lines[1] lines back if that didn't work, and so on.
232 # The last value should be huge (larger than the # of lines in a
233 # conceivable file).
234 # Making the initial values larger slows things down more often.
235 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000236 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000237 self.undo = undo = self.UndoDelegator()
238 per.insertfilter(undo)
239 text.undo_block_start = undo.undo_block_start
240 text.undo_block_stop = undo.undo_block_stop
241 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000242 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000243 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000244 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000245 self.good_load = False
246 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000247 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000248 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000249 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000250 if io.loadfile(filename):
251 self.good_load = True
252 is_py_src = self.ispythonsource(filename)
253 self.set_indentation_params(is_py_src)
254 if is_py_src:
255 self.color = color = self.ColorDelegator()
256 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000257 else:
258 io.set_filename(filename)
Christian Heimesa156e092008-02-16 07:38:31 +0000259 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000260 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000261 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000262 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000263 menu = self.menudict.get('windows')
264 if menu:
265 end = menu.index("end")
266 if end is None:
267 end = -1
268 if end >= 0:
269 menu.add_separator()
270 end = end + 1
271 self.wmenu_end = end
272 WindowList.register_callback(self.postwindowsmenu)
273
274 # Some abstractions so IDLE extensions are cross-IDE
275 self.askyesno = tkMessageBox.askyesno
276 self.askinteger = tkSimpleDialog.askinteger
277 self.showerror = tkMessageBox.showerror
278
Martin v. Löwis307021f2005-11-27 16:59:04 +0000279 def _filename_to_unicode(self, filename):
280 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000281 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000282 return filename
283 else:
284 try:
285 return filename.decode(self.filesystemencoding)
286 except UnicodeDecodeError:
287 # XXX
288 try:
289 return filename.decode(self.encoding)
290 except UnicodeDecodeError:
291 # byte-to-byte conversion
292 return filename.decode('iso8859-1')
293
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000294 def new_callback(self, event):
295 dirname, basename = self.io.defaultfilename()
296 self.flist.new(dirname)
297 return "break"
298
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000299 def home_callback(self, event):
300 if (event.state & 12) != 0 and event.keysym == "Home":
301 # state&1==shift, state&4==control, state&8==alt
302 return # <Modifier-Home>; fall back to class binding
303
304 if self.text.index("iomark") and \
305 self.text.compare("iomark", "<=", "insert lineend") and \
306 self.text.compare("insert linestart", "<=", "iomark"):
307 insertpt = int(self.text.index("iomark").split(".")[1])
308 else:
309 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000310 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000311 if line[insertpt] not in (' ','\t'):
312 break
313 else:
314 insertpt=len(line)
315
316 lineat = int(self.text.index("insert").split('.')[1])
317
318 if insertpt == lineat:
319 insertpt = 0
320
321 dest = "insert linestart+"+str(insertpt)+"c"
322
323 if (event.state&1) == 0:
324 # shift not pressed
325 self.text.tag_remove("sel", "1.0", "end")
326 else:
327 if not self.text.index("sel.first"):
328 self.text.mark_set("anchor","insert")
329
330 first = self.text.index(dest)
331 last = self.text.index("anchor")
332
333 if self.text.compare(first,">",last):
334 first,last = last,first
335
336 self.text.tag_remove("sel", "1.0", "end")
337 self.text.tag_add("sel", first, last)
338
339 self.text.mark_set("insert", dest)
340 self.text.see("insert")
341 return "break"
342
David Scherer7aced172000-08-15 01:13:23 +0000343 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000344 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000345 if macosxSupport.runningAsOSXApp():
346 # Insert some padding to avoid obscuring some of the statusbar
347 # by the resize widget.
348 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000349 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
350 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
351 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000352 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
353 self.text.event_add("<<set-line-and-column>>",
354 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000355 self.text.after_idle(self.set_line_and_column)
356
357 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000358 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000359 self.status_bar.set_label('column', 'Col: %s' % column)
360 self.status_bar.set_label('line', 'Ln: %s' % line)
361
David Scherer7aced172000-08-15 01:13:23 +0000362 menu_specs = [
363 ("file", "_File"),
364 ("edit", "_Edit"),
365 ("format", "F_ormat"),
366 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000367 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000368 ("windows", "_Windows"),
369 ("help", "_Help"),
370 ]
371
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000372 if macosxSupport.runningAsOSXApp():
373 del menu_specs[-3]
374 menu_specs[-2] = ("windows", "_Window")
375
376
David Scherer7aced172000-08-15 01:13:23 +0000377 def createmenubar(self):
378 mbar = self.menubar
379 self.menudict = menudict = {}
380 for name, label in self.menu_specs:
381 underline, label = prepstr(label)
382 menudict[name] = menu = Menu(mbar, name=name)
383 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren827822e2009-02-12 15:01:44 +0000384 if macosxSupport.runningAsOSXApp():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000385 # Insert the application menu
386 menudict['application'] = menu = Menu(mbar, name='apple')
387 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000388 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000389 self.recent_files_menu = Menu(self.menubar)
390 self.menudict['file'].insert_cascade(3, label='Recent Files',
391 underline=0,
392 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000393 self.base_helpmenu_length = self.menudict['help'].index(END)
394 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000395
396 def postwindowsmenu(self):
397 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000398 menu = self.menudict['windows']
399 end = menu.index("end")
400 if end is None:
401 end = -1
402 if end > self.wmenu_end:
403 menu.delete(self.wmenu_end+1, end)
404 WindowList.add_windows_to_menu(menu)
405
406 rmenu = None
407
408 def right_menu_event(self, event):
409 self.text.tag_remove("sel", "1.0", "end")
410 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
411 if not self.rmenu:
412 self.make_rmenu()
413 rmenu = self.rmenu
414 self.event = event
415 iswin = sys.platform[:3] == 'win'
416 if iswin:
417 self.text.config(cursor="arrow")
418 rmenu.tk_popup(event.x_root, event.y_root)
419 if iswin:
420 self.text.config(cursor="ibeam")
421
422 rmenu_specs = [
423 # ("Label", "<<virtual-event>>"), ...
424 ("Close", "<<close-window>>"), # Example
425 ]
426
427 def make_rmenu(self):
428 rmenu = Menu(self.text, tearoff=0)
429 for label, eventname in self.rmenu_specs:
430 def command(text=self.text, eventname=eventname):
431 text.event_generate(eventname)
432 rmenu.add_command(label=label, command=command)
433 self.rmenu = rmenu
434
435 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000436 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000437
Steven M. Gava3b55a892001-11-21 05:56:26 +0000438 def config_dialog(self, event=None):
439 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000440
David Scherer7aced172000-08-15 01:13:23 +0000441 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000442 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000443 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000444
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000445 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000446 if sys.platform[:3] == 'win':
Steven M. Gava931625d2002-04-22 00:38:26 +0000447 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000448 else:
449 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000450 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000451
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000452 def cut(self,event):
453 self.text.event_generate("<<Cut>>")
454 return "break"
455
456 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000457 if not self.text.tag_ranges("sel"):
458 # There is no selection, so do nothing and maybe interrupt.
459 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000460 self.text.event_generate("<<Copy>>")
461 return "break"
462
463 def paste(self,event):
464 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000465 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000466 return "break"
467
David Scherer7aced172000-08-15 01:13:23 +0000468 def select_all(self, event=None):
469 self.text.tag_add("sel", "1.0", "end-1c")
470 self.text.mark_set("insert", "1.0")
471 self.text.see("insert")
472 return "break"
473
474 def remove_selection(self, event=None):
475 self.text.tag_remove("sel", "1.0", "end")
476 self.text.see("insert")
477
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000478 def move_at_edge_if_selection(self, edge_index):
479 """Cursor move begins at start or end of selection
480
481 When a left/right cursor key is pressed create and return to Tkinter a
482 function which causes a cursor move from the associated edge of the
483 selection.
484
485 """
486 self_text_index = self.text.index
487 self_text_mark_set = self.text.mark_set
488 edges_table = ("sel.first+1c", "sel.last-1c")
489 def move_at_edge(event):
490 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
491 try:
492 self_text_index("sel.first")
493 self_text_mark_set("insert", edges_table[edge_index])
494 except TclError:
495 pass
496 return move_at_edge
497
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000498 def del_word_left(self, event):
499 self.text.event_generate('<Meta-Delete>')
500 return "break"
501
502 def del_word_right(self, event):
503 self.text.event_generate('<Meta-d>')
504 return "break"
505
Steven M. Gavac5976402002-01-04 03:06:08 +0000506 def find_event(self, event):
507 SearchDialog.find(self.text)
508 return "break"
509
510 def find_again_event(self, event):
511 SearchDialog.find_again(self.text)
512 return "break"
513
514 def find_selection_event(self, event):
515 SearchDialog.find_selection(self.text)
516 return "break"
517
518 def find_in_files_event(self, event):
519 GrepDialog.grep(self.text, self.io, self.flist)
520 return "break"
521
522 def replace_event(self, event):
523 ReplaceDialog.replace(self.text)
524 return "break"
525
526 def goto_line_event(self, event):
527 text = self.text
528 lineno = tkSimpleDialog.askinteger("Goto",
529 "Go to line number:",parent=text)
530 if lineno is None:
531 return "break"
532 if lineno <= 0:
533 text.bell()
534 return "break"
535 text.mark_set("insert", "%d.0" % lineno)
536 text.see("insert")
537
David Scherer7aced172000-08-15 01:13:23 +0000538 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000539 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000540 try:
541 name = self.text.get("sel.first", "sel.last")
542 except TclError:
543 name = ""
544 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000545 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000546 name = tkSimpleDialog.askstring("Module",
547 "Enter the name of a Python module\n"
548 "to search on sys.path and open:",
549 parent=self.text, initialvalue=name)
550 if name:
551 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000552 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000553 return
David Scherer7aced172000-08-15 01:13:23 +0000554 # XXX Ought to insert current file's directory in front of path
555 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000556 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000557 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000558 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
559 return
560 if type != imp.PY_SOURCE:
561 tkMessageBox.showerror("Unsupported type",
562 "%s is not a source module" % name, parent=self.text)
563 return
564 if f:
565 f.close()
566 if self.flist:
567 self.flist.open(file)
568 else:
569 self.io.loadfile(file)
570
571 def open_class_browser(self, event=None):
572 filename = self.io.filename
573 if not filename:
574 tkMessageBox.showerror(
575 "No filename",
576 "This buffer has no associated filename",
577 master=self.text)
578 self.text.focus_set()
579 return None
580 head, tail = os.path.split(filename)
581 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000582 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000583 ClassBrowser.ClassBrowser(self.flist, base, [head])
584
585 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000586 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000587 PathBrowser.PathBrowser(self.flist)
588
589 def gotoline(self, lineno):
590 if lineno is not None and lineno > 0:
591 self.text.mark_set("insert", "%d.0" % lineno)
592 self.text.tag_remove("sel", "1.0", "end")
593 self.text.tag_add("sel", "insert", "insert +1l")
594 self.center()
595
596 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000597 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000598 return True
David Scherer7aced172000-08-15 01:13:23 +0000599 base, ext = os.path.splitext(os.path.basename(filename))
600 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000601 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000602 line = self.text.get('1.0', '1.0 lineend')
603 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000604
605 def close_hook(self):
606 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000607 self.flist.unregister_maybe_terminate(self)
608 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000609
610 def set_close_hook(self, close_hook):
611 self.close_hook = close_hook
612
613 def filename_change_hook(self):
614 if self.flist:
615 self.flist.filename_changed_edit(self)
616 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000617 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000618 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000619
Christian Heimesa156e092008-02-16 07:38:31 +0000620 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000621 if self.color:
622 return
Christian Heimesa156e092008-02-16 07:38:31 +0000623 if self.ispythonsource(self.io.filename):
624 self.color = self.ColorDelegator()
625 # can add more colorizers here...
626 if self.color:
627 self.per.removefilter(self.undo)
628 self.per.insertfilter(self.color)
629 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000630
Christian Heimesa156e092008-02-16 07:38:31 +0000631 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000632 if not self.color:
633 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000634 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000635 self.per.removefilter(self.color)
636 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000637
Steven M. Gavab77d3432002-03-02 07:16:21 +0000638 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000639 "Update the colour theme"
640 # Called from self.filename_change_hook and from configDialog.py
641 self._rmcolorizer()
642 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000643 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000644 normal_colors = idleConf.GetHighlight(theme, 'normal')
645 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
646 select_colors = idleConf.GetHighlight(theme, 'hilite')
647 self.text.config(
648 foreground=normal_colors['foreground'],
649 background=normal_colors['background'],
650 insertbackground=cursor_color,
651 selectforeground=select_colors['foreground'],
652 selectbackground=select_colors['background'],
653 )
David Scherer7aced172000-08-15 01:13:23 +0000654
Guido van Rossum33d26892007-08-05 15:29:28 +0000655 IDENTCHARS = string.ascii_letters + string.digits + "_"
656
657 def colorize_syntax_error(self, text, pos):
658 text.tag_add("ERROR", pos)
659 char = text.get(pos)
660 if char and char in self.IDENTCHARS:
661 text.tag_add("ERROR", pos + " wordstart", pos)
662 if '\n' == text.get(pos): # error at line end
663 text.mark_set("insert", pos)
664 else:
665 text.mark_set("insert", pos + "+1c")
666 text.see(pos)
667
Steven M. Gavab1585412002-03-12 00:21:56 +0000668 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000669 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000670 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000671 fontWeight='normal'
672 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
673 fontWeight='bold'
674 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
675 idleConf.GetOption('main','EditorWindow','font-size'),
676 fontWeight))
677
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000678 def RemoveKeybindings(self):
679 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000680 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000681 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000682 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000683 self.text.event_delete(event, *keylist)
684 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000685 xkeydefs = idleConf.GetExtensionBindings(extensionName)
686 if xkeydefs:
687 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000688 self.text.event_delete(event, *keylist)
689
690 def ApplyKeybindings(self):
691 "Update the keybindings after they are changed"
692 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000693 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000694 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000695 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 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000699 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000700 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000701 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000702 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000703 for item in menu[1]:
704 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000705 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000706 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000707 menu = self.menudict[menubarItem]
708 end = menu.index(END) + 1
709 for index in range(0, end):
710 if menu.type(index) == 'command':
711 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000712 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000713 itemName = menu.entrycget(index, 'label')
714 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000715 if menubarItem in menuEventDict:
716 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000717 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000718 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000719 accel = get_accelerator(keydefs, event)
720 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000721
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000722 def set_notabs_indentwidth(self):
723 "Update the indentwidth if changed and not using tabs in this window"
724 # Called from configDialog.py
725 if not self.usetabs:
726 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
727 type='int')
728
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000729 def reset_help_menu_entries(self):
730 "Update the additional help entries on the Help menu"
731 help_list = idleConf.GetAllExtraHelpSourcesList()
732 helpmenu = self.menudict['help']
733 # first delete the extra help entries, if any
734 helpmenu_length = helpmenu.index(END)
735 if helpmenu_length > self.base_helpmenu_length:
736 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
737 # then rebuild them
738 if help_list:
739 helpmenu.add_separator()
740 for entry in help_list:
741 cmd = self.__extra_help_callback(entry[1])
742 helpmenu.add_command(label=entry[0], command=cmd)
743 # and update the menu dictionary
744 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000745
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000746 def __extra_help_callback(self, helpfile):
747 "Create a callback with the helpfile value frozen at definition time"
748 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000749 if not helpfile.startswith(('www', 'http')):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000750 url = os.path.normpath(helpfile)
751 if sys.platform[:3] == 'win':
752 os.startfile(helpfile)
753 else:
754 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000755 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000756
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000757 def update_recent_files_list(self, new_file=None):
758 "Load and update the recent files list and menus"
759 rf_list = []
760 if os.path.exists(self.recent_files_path):
761 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000762 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000763 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000764 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000765 rf_list_file.close()
766 if new_file:
767 new_file = os.path.abspath(new_file) + '\n'
768 if new_file in rf_list:
769 rf_list.remove(new_file) # move to top
770 rf_list.insert(0, new_file)
771 # clean and save the recent files list
772 bad_paths = []
773 for path in rf_list:
774 if '\0' in path or not os.path.exists(path[0:-1]):
775 bad_paths.append(path)
776 rf_list = [path for path in rf_list if path not in bad_paths]
777 ulchars = "1234567890ABCDEFGHIJK"
778 rf_list = rf_list[0:len(ulchars)]
779 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000780 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000781 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000782 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000783 rf_file.close()
784 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000785 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000786 menu = instance.recent_files_menu
787 menu.delete(1, END) # clear, and rebuild:
788 for i, file in zip(count(), rf_list):
789 file_name = file[0:-1] # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000790 # make unicode string to display non-ASCII chars correctly
791 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000792 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000793 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000794 command=callback,
795 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000796
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000797 def __recent_file_callback(self, file_name):
798 def open_recent_file(fn_closure=file_name):
799 self.io.open(editFile=fn_closure)
800 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000801
David Scherer7aced172000-08-15 01:13:23 +0000802 def saved_change_hook(self):
803 short = self.short_title()
804 long = self.long_title()
805 if short and long:
806 title = short + " - " + long
807 elif short:
808 title = short
809 elif long:
810 title = long
811 else:
812 title = "Untitled"
813 icon = short or long or title
814 if not self.get_saved():
815 title = "*%s*" % title
816 icon = "*%s" % icon
817 self.top.wm_title(title)
818 self.top.wm_iconname(icon)
819
820 def get_saved(self):
821 return self.undo.get_saved()
822
823 def set_saved(self, flag):
824 self.undo.set_saved(flag)
825
826 def reset_undo(self):
827 self.undo.reset_undo()
828
829 def short_title(self):
830 filename = self.io.filename
831 if filename:
832 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000833 # return unicode string to display non-ASCII chars correctly
834 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000835
836 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000837 # return unicode string to display non-ASCII chars correctly
838 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000839
840 def center_insert_event(self, event):
841 self.center()
842
843 def center(self, mark="insert"):
844 text = self.text
845 top, bot = self.getwindowlines()
846 lineno = self.getlineno(mark)
847 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000848 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000849 text.yview(float(newtop))
850
851 def getwindowlines(self):
852 text = self.text
853 top = self.getlineno("@0,0")
854 bot = self.getlineno("@0,65535")
855 if top == bot and text.winfo_height() == 1:
856 # Geometry manager hasn't run yet
857 height = int(text['height'])
858 bot = top + height - 1
859 return top, bot
860
861 def getlineno(self, mark="insert"):
862 text = self.text
863 return int(float(text.index(mark)))
864
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000865 def get_geometry(self):
866 "Return (width, height, x, y)"
867 geom = self.top.wm_geometry()
868 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000869 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000870
David Scherer7aced172000-08-15 01:13:23 +0000871 def close_event(self, event):
872 self.close()
873
874 def maybesave(self):
875 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000876 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000877 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000878 self.top.deiconify()
879 self.top.lower()
880 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000881 return self.io.maybesave()
882
883 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000884 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000885 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000886 self._close()
887 return reply
888
889 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000890 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000891 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000892 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000893 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000894 self.io.close()
895 self.io = None
896 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000897 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000898 self.color.close(False)
899 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000900 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000901 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000902 self.per.close()
903 self.per = None
904 self.top.destroy()
905 if self.close_hook:
906 # unless override: unregister from flist, terminate if last window
907 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000908
909 def load_extensions(self):
910 self.extensions = {}
911 self.load_standard_extensions()
912
913 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000914 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000915 if hasattr(ins, "close"):
916 ins.close()
917 self.extensions = {}
918
919 def load_standard_extensions(self):
920 for name in self.get_standard_extension_names():
921 try:
922 self.load_extension(name)
923 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000924 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000925 traceback.print_exc()
926
927 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000928 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000929
930 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000931 try:
932 mod = __import__(name, globals(), locals(), [])
933 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000934 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000935 raise
David Scherer7aced172000-08-15 01:13:23 +0000936 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000937 keydefs = idleConf.GetExtensionBindings(name)
938 if hasattr(cls, "menudefs"):
939 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000940 ins = cls(self)
941 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000942 if keydefs:
943 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000944 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000945 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000946 while methodname[:1] == '<':
947 methodname = methodname[1:]
948 while methodname[-1:] == '>':
949 methodname = methodname[:-1]
950 methodname = methodname + "_event"
951 if hasattr(ins, methodname):
952 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000953
954 def apply_bindings(self, keydefs=None):
955 if keydefs is None:
956 keydefs = self.Bindings.default_keydefs
957 text = self.text
958 text.keydefs = keydefs
959 for event, keylist in keydefs.items():
960 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000961 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000962
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000963 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000964 """Add appropriate entries to the menus and submenus
965
966 Menus that are absent or None in self.menudict are ignored.
967 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000968 if menudefs is None:
969 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000970 if keydefs is None:
971 keydefs = self.Bindings.default_keydefs
972 menudict = self.menudict
973 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000974 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000975 menu = menudict.get(mname)
976 if not menu:
977 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000978 for entry in entrylist:
979 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000980 menu.add_separator()
981 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000982 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000983 checkbutton = (label[:1] == '!')
984 if checkbutton:
985 label = label[1:]
986 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000987 accelerator = get_accelerator(keydefs, eventname)
988 def command(text=text, eventname=eventname):
989 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000990 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000991 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000992 menu.add_checkbutton(label=label, underline=underline,
993 command=command, accelerator=accelerator,
994 variable=var)
995 else:
996 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000997 command=command,
998 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000999
1000 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001001 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001002 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001003 value = var.get()
1004 return value
1005 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001006 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001007
1008 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001009 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001010 if var:
1011 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001012 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001013 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001014
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001015 def get_var_obj(self, name, vartype=None):
1016 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001017 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001018 # create a Tkinter variable object with self.text as master:
1019 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001020 return var
1021
1022 # Tk implementations of "virtual text methods" -- each platform
1023 # reusing IDLE's support code needs to define these for its GUI's
1024 # flavor of widget.
1025
1026 # Is character at text_index in a Python string? Return 0 for
1027 # "guaranteed no", true for anything else. This info is expensive
1028 # to compute ab initio, but is probably already known by the
1029 # platform's colorizer.
1030
1031 def is_char_in_string(self, text_index):
1032 if self.color:
1033 # Return true iff colorizer hasn't (re)gotten this far
1034 # yet, or the character is tagged as being in a string
1035 return self.text.tag_prevrange("TODO", text_index) or \
1036 "STRING" in self.text.tag_names(text_index)
1037 else:
1038 # The colorizer is missing: assume the worst
1039 return 1
1040
1041 # If a selection is defined in the text widget, return (start,
1042 # end) as Tkinter text indices, otherwise return (None, None)
1043 def get_selection_indices(self):
1044 try:
1045 first = self.text.index("sel.first")
1046 last = self.text.index("sel.last")
1047 return first, last
1048 except TclError:
1049 return None, None
1050
1051 # Return the text widget's current view of what a tab stop means
1052 # (equivalent width in spaces).
1053
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001054 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001055 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1056 return int(current)
1057
1058 # Set the text widget's current view of what a tab stop means.
1059
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001060 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001061 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001062 if self.get_tk_tabwidth() != newtabwidth:
1063 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001064 pixels = text.tk.call("font", "measure", text["font"],
1065 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001066 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001067 text.configure(tabs=pixels)
1068
Guido van Rossum33d26892007-08-05 15:29:28 +00001069### begin autoindent code ### (configuration was moved to beginning of class)
1070
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001071 def set_indentation_params(self, is_py_src, guess=True):
1072 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001073 i = self.guess_indent()
1074 if 2 <= i <= 8:
1075 self.indentwidth = i
1076 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001077 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001078 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001079
1080 def smart_backspace_event(self, event):
1081 text = self.text
1082 first, last = self.get_selection_indices()
1083 if first and last:
1084 text.delete(first, last)
1085 text.mark_set("insert", first)
1086 return "break"
1087 # Delete whitespace left, until hitting a real char or closest
1088 # preceding virtual tab stop.
1089 chars = text.get("insert linestart", "insert")
1090 if chars == '':
1091 if text.compare("insert", ">", "1.0"):
1092 # easy: delete preceding newline
1093 text.delete("insert-1c")
1094 else:
1095 text.bell() # at start of buffer
1096 return "break"
1097 if chars[-1] not in " \t":
1098 # easy: delete preceding real char
1099 text.delete("insert-1c")
1100 return "break"
1101 # Ick. It may require *inserting* spaces if we back up over a
1102 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001103 tabwidth = self.tabwidth
1104 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001105 assert have > 0
1106 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001107 # Debug prompt is multilined....
1108 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001109 ncharsdeleted = 0
1110 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001111 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001112 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001113 chars = chars[:-1]
1114 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001115 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001116 if have <= want or chars[-1] not in " \t":
1117 break
1118 text.undo_block_start()
1119 text.delete("insert-%dc" % ncharsdeleted, "insert")
1120 if have < want:
1121 text.insert("insert", ' ' * (want - have))
1122 text.undo_block_stop()
1123 return "break"
1124
1125 def smart_indent_event(self, event):
1126 # if intraline selection:
1127 # delete it
1128 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001129 # do indent-region
1130 # else:
1131 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001132 text = self.text
1133 first, last = self.get_selection_indices()
1134 text.undo_block_start()
1135 try:
1136 if first and last:
1137 if index2line(first) != index2line(last):
1138 return self.indent_region_event(event)
1139 text.delete(first, last)
1140 text.mark_set("insert", first)
1141 prefix = text.get("insert linestart", "insert")
1142 raw, effective = classifyws(prefix, self.tabwidth)
1143 if raw == len(prefix):
1144 # only whitespace to the left
1145 self.reindent_to(effective + self.indentwidth)
1146 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001147 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001148 if self.usetabs:
1149 pad = '\t'
1150 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001151 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001152 n = self.indentwidth
1153 pad = ' ' * (n - effective % n)
1154 text.insert("insert", pad)
1155 text.see("insert")
1156 return "break"
1157 finally:
1158 text.undo_block_stop()
1159
1160 def newline_and_indent_event(self, event):
1161 text = self.text
1162 first, last = self.get_selection_indices()
1163 text.undo_block_start()
1164 try:
1165 if first and last:
1166 text.delete(first, last)
1167 text.mark_set("insert", first)
1168 line = text.get("insert linestart", "insert")
1169 i, n = 0, len(line)
1170 while i < n and line[i] in " \t":
1171 i = i+1
1172 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001173 # the cursor is in or at leading indentation in a continuation
1174 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001175 text.insert("insert linestart", '\n')
1176 return "break"
1177 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001178 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001179 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001180 last_line_of_prompt = sys.ps1.split('\n')[-1]
1181 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001182 line = line[:-1]
1183 i = i+1
1184 if i:
1185 text.delete("insert - %d chars" % i, "insert")
1186 # strip whitespace after insert point
1187 while text.get("insert") in " \t":
1188 text.delete("insert")
1189 # start new line
1190 text.insert("insert", '\n')
1191
1192 # adjust indentation for continuations and block
1193 # open/close first need to find the last stmt
1194 lno = index2line(text.index('insert'))
1195 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001196 if not self.context_use_ps1:
1197 for context in self.num_context_lines:
1198 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001199 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001200 rawtext = text.get(startatindex, "insert")
1201 y.set_str(rawtext)
1202 bod = y.find_good_parse_start(
1203 self.context_use_ps1,
1204 self._build_char_in_string_func(startatindex))
1205 if bod is not None or startat == 1:
1206 break
1207 y.set_lo(bod or 0)
1208 else:
1209 r = text.tag_prevrange("console", "insert")
1210 if r:
1211 startatindex = r[1]
1212 else:
1213 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001214 rawtext = text.get(startatindex, "insert")
1215 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001216 y.set_lo(0)
1217
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001218 c = y.get_continuation_type()
1219 if c != PyParse.C_NONE:
1220 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001221 if c == PyParse.C_STRING_FIRST_LINE:
1222 # after the first line of a string; do not indent at all
1223 pass
1224 elif c == PyParse.C_STRING_NEXT_LINES:
1225 # inside a string which started before this line;
1226 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001227 text.insert("insert", indent)
1228 elif c == PyParse.C_BRACKET:
1229 # line up with the first (if any) element of the
1230 # last open bracket structure; else indent one
1231 # level beyond the indent of the line with the
1232 # last open bracket
1233 self.reindent_to(y.compute_bracket_indent())
1234 elif c == PyParse.C_BACKSLASH:
1235 # if more than one line in this stmt already, just
1236 # mimic the current indent; else if initial line
1237 # has a start on an assignment stmt, indent to
1238 # beyond leftmost =; else to beyond first chunk of
1239 # non-whitespace on initial line
1240 if y.get_num_lines_in_stmt() > 1:
1241 text.insert("insert", indent)
1242 else:
1243 self.reindent_to(y.compute_backslash_indent())
1244 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001245 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001246 return "break"
1247
1248 # This line starts a brand new stmt; indent relative to
1249 # indentation of initial line of closest preceding
1250 # interesting stmt.
1251 indent = y.get_base_indent_string()
1252 text.insert("insert", indent)
1253 if y.is_block_opener():
1254 self.smart_indent_event(event)
1255 elif indent and y.is_block_closer():
1256 self.smart_backspace_event(event)
1257 return "break"
1258 finally:
1259 text.see("insert")
1260 text.undo_block_stop()
1261
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001262 # Our editwin provides a is_char_in_string function that works
1263 # with a Tk text index, but PyParse only knows about offsets into
1264 # a string. This builds a function for PyParse that accepts an
1265 # offset.
1266
1267 def _build_char_in_string_func(self, startindex):
1268 def inner(offset, _startindex=startindex,
1269 _icis=self.is_char_in_string):
1270 return _icis(_startindex + "+%dc" % offset)
1271 return inner
1272
1273 def indent_region_event(self, event):
1274 head, tail, chars, lines = self.get_region()
1275 for pos in range(len(lines)):
1276 line = lines[pos]
1277 if line:
1278 raw, effective = classifyws(line, self.tabwidth)
1279 effective = effective + self.indentwidth
1280 lines[pos] = self._make_blanks(effective) + line[raw:]
1281 self.set_region(head, tail, chars, lines)
1282 return "break"
1283
1284 def dedent_region_event(self, event):
1285 head, tail, chars, lines = self.get_region()
1286 for pos in range(len(lines)):
1287 line = lines[pos]
1288 if line:
1289 raw, effective = classifyws(line, self.tabwidth)
1290 effective = max(effective - self.indentwidth, 0)
1291 lines[pos] = self._make_blanks(effective) + line[raw:]
1292 self.set_region(head, tail, chars, lines)
1293 return "break"
1294
1295 def comment_region_event(self, event):
1296 head, tail, chars, lines = self.get_region()
1297 for pos in range(len(lines) - 1):
1298 line = lines[pos]
1299 lines[pos] = '##' + line
1300 self.set_region(head, tail, chars, lines)
1301
1302 def uncomment_region_event(self, event):
1303 head, tail, chars, lines = self.get_region()
1304 for pos in range(len(lines)):
1305 line = lines[pos]
1306 if not line:
1307 continue
1308 if line[:2] == '##':
1309 line = line[2:]
1310 elif line[:1] == '#':
1311 line = line[1:]
1312 lines[pos] = line
1313 self.set_region(head, tail, chars, lines)
1314
1315 def tabify_region_event(self, event):
1316 head, tail, chars, lines = self.get_region()
1317 tabwidth = self._asktabwidth()
1318 for pos in range(len(lines)):
1319 line = lines[pos]
1320 if line:
1321 raw, effective = classifyws(line, tabwidth)
1322 ntabs, nspaces = divmod(effective, tabwidth)
1323 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1324 self.set_region(head, tail, chars, lines)
1325
1326 def untabify_region_event(self, event):
1327 head, tail, chars, lines = self.get_region()
1328 tabwidth = self._asktabwidth()
1329 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001330 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001331 self.set_region(head, tail, chars, lines)
1332
1333 def toggle_tabs_event(self, event):
1334 if self.askyesno(
1335 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001336 "Turn tabs " + ("on", "off")[self.usetabs] +
1337 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001338 ("will be", "remains at")[self.usetabs] + " 8." +
1339 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001340 parent=self.text):
1341 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001342 # Try to prevent inconsistent indentation.
1343 # User must change indent width manually after using tabs.
1344 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001345 return "break"
1346
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001347 # XXX this isn't bound to anything -- see tabwidth comments
1348## def change_tabwidth_event(self, event):
1349## new = self._asktabwidth()
1350## if new != self.tabwidth:
1351## self.tabwidth = new
1352## self.set_indentation_params(0, guess=0)
1353## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001354
1355 def change_indentwidth_event(self, event):
1356 new = self.askinteger(
1357 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001358 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001359 parent=self.text,
1360 initialvalue=self.indentwidth,
1361 minvalue=2,
1362 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001363 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001364 self.indentwidth = new
1365 return "break"
1366
1367 def get_region(self):
1368 text = self.text
1369 first, last = self.get_selection_indices()
1370 if first and last:
1371 head = text.index(first + " linestart")
1372 tail = text.index(last + "-1c lineend +1c")
1373 else:
1374 head = text.index("insert linestart")
1375 tail = text.index("insert lineend +1c")
1376 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001377 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001378 return head, tail, chars, lines
1379
1380 def set_region(self, head, tail, chars, lines):
1381 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001382 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001383 if newchars == chars:
1384 text.bell()
1385 return
1386 text.tag_remove("sel", "1.0", "end")
1387 text.mark_set("insert", head)
1388 text.undo_block_start()
1389 text.delete(head, tail)
1390 text.insert(head, newchars)
1391 text.undo_block_stop()
1392 text.tag_add("sel", head, "insert")
1393
1394 # Make string that displays as n leading blanks.
1395
1396 def _make_blanks(self, n):
1397 if self.usetabs:
1398 ntabs, nspaces = divmod(n, self.tabwidth)
1399 return '\t' * ntabs + ' ' * nspaces
1400 else:
1401 return ' ' * n
1402
1403 # Delete from beginning of line to insert point, then reinsert
1404 # column logical (meaning use tabs if appropriate) spaces.
1405
1406 def reindent_to(self, column):
1407 text = self.text
1408 text.undo_block_start()
1409 if text.compare("insert linestart", "!=", "insert"):
1410 text.delete("insert linestart", "insert")
1411 if column:
1412 text.insert("insert", self._make_blanks(column))
1413 text.undo_block_stop()
1414
1415 def _asktabwidth(self):
1416 return self.askinteger(
1417 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001418 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001419 parent=self.text,
1420 initialvalue=self.indentwidth,
1421 minvalue=2,
1422 maxvalue=16) or self.tabwidth
1423
1424 # Guess indentwidth from text content.
1425 # Return guessed indentwidth. This should not be believed unless
1426 # it's in a reasonable range (e.g., it will be 0 if no indented
1427 # blocks are found).
1428
1429 def guess_indent(self):
1430 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1431 if opener and indented:
1432 raw, indentsmall = classifyws(opener, self.tabwidth)
1433 raw, indentlarge = classifyws(indented, self.tabwidth)
1434 else:
1435 indentsmall = indentlarge = 0
1436 return indentlarge - indentsmall
1437
1438# "line.col" -> line, as an int
1439def index2line(index):
1440 return int(float(index))
1441
1442# Look at the leading whitespace in s.
1443# Return pair (# of leading ws characters,
1444# effective # of leading blanks after expanding
1445# tabs to width tabwidth)
1446
1447def classifyws(s, tabwidth):
1448 raw = effective = 0
1449 for ch in s:
1450 if ch == ' ':
1451 raw = raw + 1
1452 effective = effective + 1
1453 elif ch == '\t':
1454 raw = raw + 1
1455 effective = (effective // tabwidth + 1) * tabwidth
1456 else:
1457 break
1458 return raw, effective
1459
1460import tokenize
1461_tokenize = tokenize
1462del tokenize
1463
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001464class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001465
1466 # .run() chews over the Text widget, looking for a block opener
1467 # and the stmt following it. Returns a pair,
1468 # (line containing block opener, line containing stmt)
1469 # Either or both may be None.
1470
1471 def __init__(self, text, tabwidth):
1472 self.text = text
1473 self.tabwidth = tabwidth
1474 self.i = self.finished = 0
1475 self.blkopenline = self.indentedline = None
1476
1477 def readline(self):
1478 if self.finished:
1479 return ""
1480 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001481 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001482 if self.text.compare(mark, ">=", "end"):
1483 return ""
1484 return self.text.get(mark, mark + " lineend+1c")
1485
1486 def tokeneater(self, type, token, start, end, line,
1487 INDENT=_tokenize.INDENT,
1488 NAME=_tokenize.NAME,
1489 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1490 if self.finished:
1491 pass
1492 elif type == NAME and token in OPENERS:
1493 self.blkopenline = line
1494 elif type == INDENT and self.blkopenline:
1495 self.indentedline = line
1496 self.finished = 1
1497
1498 def run(self):
1499 save_tabsize = _tokenize.tabsize
1500 _tokenize.tabsize = self.tabwidth
1501 try:
1502 try:
Trent Nelson428de652008-03-18 22:41:35 +00001503 tokens = _tokenize.generate_tokens(self.readline)
1504 for token in tokens:
1505 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001506 except _tokenize.TokenError:
1507 # since we cut off the tokenizer early, we can trigger
1508 # spurious errors
1509 pass
1510 finally:
1511 _tokenize.tabsize = save_tabsize
1512 return self.blkopenline, self.indentedline
1513
1514### end autoindent code ###
1515
David Scherer7aced172000-08-15 01:13:23 +00001516def prepstr(s):
1517 # Helper to extract the underscore from a string, e.g.
1518 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001519 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001520 if i >= 0:
1521 s = s[:i] + s[i+1:]
1522 return i, s
1523
1524
1525keynames = {
1526 'bracketleft': '[',
1527 'bracketright': ']',
1528 'slash': '/',
1529}
1530
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001531def get_accelerator(keydefs, eventname):
1532 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001533 if not keylist:
1534 return ""
1535 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001536 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001537 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1538 s = re.sub("Key-", "", s)
1539 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1540 s = re.sub("Control-", "Ctrl-", s)
1541 s = re.sub("-", "+", s)
1542 s = re.sub("><", " ", s)
1543 s = re.sub("<", "", s)
1544 s = re.sub(">", "", s)
1545 return s
1546
1547
1548def fixwordbreaks(root):
1549 # Make sure that Tk's double-click and next/previous word
1550 # operations use our definition of a word (i.e. an identifier)
1551 tk = root.tk
1552 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1553 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1554 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1555
1556
1557def test():
1558 root = Tk()
1559 fixwordbreaks(root)
1560 root.withdraw()
1561 if sys.argv[1:]:
1562 filename = sys.argv[1]
1563 else:
1564 filename = None
1565 edit = EditorWindow(root=root, filename=filename)
1566 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001567 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001568 root.mainloop()
1569 root.destroy()
1570
1571if __name__ == '__main__':
1572 test()