blob: a38cdd117593dc592ac430801071c6f5f51a85ce [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:
32 release += '%s' % micro
33 if level != 'final':
34 release += '%s%s' % (level[0], serial)
35 return release
36
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000037def _find_module(fullname, path=None):
38 """Version of imp.find_module() that handles hierarchical module names"""
39
40 file = None
41 for tgt in fullname.split('.'):
42 if file is not None:
43 file.close() # close intermediate files
44 (file, filename, descr) = imp.find_module(tgt, path)
45 if descr[2] == imp.PY_SOURCE:
46 break # find but not load the source file
47 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000048 try:
49 path = module.__path__
50 except AttributeError:
Kurt B. Kaiserad667422007-08-23 01:06:15 +000051 raise ImportError('No source for module ' + module.__name__)
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000052 return file, filename, descr
53
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000054class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000055 from idlelib.Percolator import Percolator
56 from idlelib.ColorDelegator import ColorDelegator
57 from idlelib.UndoDelegator import UndoDelegator
58 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
59 from idlelib import Bindings
Guilherme Polo5424b0a2008-05-25 15:26:44 +000060 from tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000061 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000062
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000063 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000064
65 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000066 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000067 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000068 if sys.platform.count('linux'):
69 # look for html docs in a couple of standard places
70 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
71 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
72 dochome = '/var/www/html/python/index.html'
73 else:
74 basepath = '/usr/share/doc/' # standard location
75 dochome = os.path.join(basepath, pyver,
76 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000077 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +000078 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserc34ed8e2009-04-26 01:33:55 +000079 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +000080 if os.path.isfile(chmfile):
81 dochome = chmfile
Thomas Wouters0e3f5912006-08-11 14:57:12 +000082 elif macosxSupport.runningAsOSXApp():
83 # documentation is stored inside the python framework
84 dochome = os.path.join(sys.prefix,
85 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000086 dochome = os.path.normpath(dochome)
87 if os.path.isfile(dochome):
88 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +000089 if sys.platform == 'darwin':
90 # Safari requires real file:-URLs
91 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000092 else:
Benjamin Peterson152b6572009-01-20 15:01:54 +000093 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +000094 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000095 self.flist = flist
96 root = root or flist.root
97 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +000098 try:
99 sys.ps1
100 except AttributeError:
101 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000102 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000103 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000104 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000105 self.tkinter_vars = flist.vars
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000106 #self.top.instance_dict makes flist.inversedict avalable to
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000107 #configDialog.py so it can access all EditorWindow instaces
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000108 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000109 else:
110 self.tkinter_vars = {} # keys: Tkinter event names
111 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000112 self.top.instance_dict = {}
113 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000114 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000115 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000116 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000117 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiser113f0e82009-04-04 20:38:52 +0000118 text_options = {
119 'name': 'text',
120 'padx': 5,
121 'wrap': 'none',
122 'width': self.width,
123 'height': idleConf.GetOption('main', 'EditorWindow', 'height')}
124 if TkVersion >= 8.5:
125 # Starting with tk 8.5 we have to set the new tabstyle option
126 # to 'wordprocessor' to achieve the same display of tabs as in
127 # older tk versions.
128 text_options['tabstyle'] = 'wordprocessor'
129 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000130 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000131
132 self.createmenubar()
133 self.apply_bindings()
134
135 self.top.protocol("WM_DELETE_WINDOW", self.close)
136 self.top.bind("<<close-window>>", self.close_event)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000137 if macosxSupport.runningAsOSXApp():
138 # Command-W on editorwindows doesn't work without this.
139 text.bind('<<close-window>>', self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000140 text.bind("<<cut>>", self.cut)
141 text.bind("<<copy>>", self.copy)
142 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000143 text.bind("<<center-insert>>", self.center_insert_event)
144 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000145 text.bind("<<python-docs>>", self.python_docs)
146 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000147 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000148 text.bind("<<open-module>>", self.open_module)
149 text.bind("<<do-nothing>>", lambda event: "break")
150 text.bind("<<select-all>>", self.select_all)
151 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000152 text.bind("<<find>>", self.find_event)
153 text.bind("<<find-again>>", self.find_again_event)
154 text.bind("<<find-in-files>>", self.find_in_files_event)
155 text.bind("<<find-selection>>", self.find_selection_event)
156 text.bind("<<replace>>", self.replace_event)
157 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000158 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000159 text.bind("<<smart-backspace>>",self.smart_backspace_event)
160 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
161 text.bind("<<smart-indent>>",self.smart_indent_event)
162 text.bind("<<indent-region>>",self.indent_region_event)
163 text.bind("<<dedent-region>>",self.dedent_region_event)
164 text.bind("<<comment-region>>",self.comment_region_event)
165 text.bind("<<uncomment-region>>",self.uncomment_region_event)
166 text.bind("<<tabify-region>>",self.tabify_region_event)
167 text.bind("<<untabify-region>>",self.untabify_region_event)
168 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
169 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000170 text.bind("<Left>", self.move_at_edge_if_selection(0))
171 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000172 text.bind("<<del-word-left>>", self.del_word_left)
173 text.bind("<<del-word-right>>", self.del_word_right)
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000174 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000175
David Scherer7aced172000-08-15 01:13:23 +0000176 if flist:
177 flist.inversedict[self] = key
178 if key:
179 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000180 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000181 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
182 text.bind("<<open-class-browser>>", self.open_class_browser)
183 text.bind("<<open-path-browser>>", self.open_path_browser)
184
Steven M. Gava898a3652001-10-07 11:10:44 +0000185 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000186 vbar['command'] = text.yview
187 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000188 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000189 fontWeight = 'normal'
190 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000191 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000192 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
193 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
194 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000195 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
196 text.pack(side=TOP, fill=BOTH, expand=1)
197 text.focus_set()
198
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000199 # usetabs true -> literal tab characters are used by indent and
200 # dedent cmds, possibly mixed with spaces if
201 # indentwidth is not a multiple of tabwidth,
202 # which will cause Tabnanny to nag!
203 # false -> tab characters are converted to spaces by indent
204 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000205 # Although use-spaces=0 can be configured manually in config-main.def,
206 # configuration of tabs v. spaces is not supported in the configuration
207 # dialog. IDLE promotes the preferred Python indentation: use spaces!
208 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
209 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000210
211 # tabwidth is the display width of a literal tab character.
212 # CAUTION: telling Tk to use anything other than its default
213 # tab setting causes it to use an entirely different tabbing algorithm,
214 # treating tab stops as fixed distances from the left margin.
215 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000216 self.tabwidth = 8 # must remain 8 until Tk is fixed.
217
218 # indentwidth is the number of screen characters per indent level.
219 # The recommended Python indentation is four spaces.
220 self.indentwidth = self.tabwidth
221 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000222
223 # If context_use_ps1 is true, parsing searches back for a ps1 line;
224 # else searches for a popular (if, def, ...) Python stmt.
225 self.context_use_ps1 = False
226
227 # When searching backwards for a reliable place to begin parsing,
228 # first start num_context_lines[0] lines back, then
229 # num_context_lines[1] lines back if that didn't work, and so on.
230 # The last value should be huge (larger than the # of lines in a
231 # conceivable file).
232 # Making the initial values larger slows things down more often.
233 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000234 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000235 self.undo = undo = self.UndoDelegator()
236 per.insertfilter(undo)
237 text.undo_block_start = undo.undo_block_start
238 text.undo_block_stop = undo.undo_block_stop
239 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000240 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000241 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000242 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000243 self.good_load = False
244 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000245 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000246 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000247 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000248 if io.loadfile(filename):
249 self.good_load = True
250 is_py_src = self.ispythonsource(filename)
251 self.set_indentation_params(is_py_src)
252 if is_py_src:
253 self.color = color = self.ColorDelegator()
254 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000255 else:
256 io.set_filename(filename)
Christian Heimesa156e092008-02-16 07:38:31 +0000257 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000258 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000259 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000260 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000261 menu = self.menudict.get('windows')
262 if menu:
263 end = menu.index("end")
264 if end is None:
265 end = -1
266 if end >= 0:
267 menu.add_separator()
268 end = end + 1
269 self.wmenu_end = end
270 WindowList.register_callback(self.postwindowsmenu)
271
272 # Some abstractions so IDLE extensions are cross-IDE
273 self.askyesno = tkMessageBox.askyesno
274 self.askinteger = tkSimpleDialog.askinteger
275 self.showerror = tkMessageBox.showerror
276
Martin v. Löwis307021f2005-11-27 16:59:04 +0000277 def _filename_to_unicode(self, filename):
278 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000279 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000280 return filename
281 else:
282 try:
283 return filename.decode(self.filesystemencoding)
284 except UnicodeDecodeError:
285 # XXX
286 try:
287 return filename.decode(self.encoding)
288 except UnicodeDecodeError:
289 # byte-to-byte conversion
290 return filename.decode('iso8859-1')
291
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000292 def new_callback(self, event):
293 dirname, basename = self.io.defaultfilename()
294 self.flist.new(dirname)
295 return "break"
296
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000297 def home_callback(self, event):
298 if (event.state & 12) != 0 and event.keysym == "Home":
299 # state&1==shift, state&4==control, state&8==alt
300 return # <Modifier-Home>; fall back to class binding
301
302 if self.text.index("iomark") and \
303 self.text.compare("iomark", "<=", "insert lineend") and \
304 self.text.compare("insert linestart", "<=", "iomark"):
305 insertpt = int(self.text.index("iomark").split(".")[1])
306 else:
307 line = self.text.get("insert linestart", "insert lineend")
Georg Brandl7d4f39a2008-07-19 13:53:58 +0000308 for insertpt in range(len(line)):
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000309 if line[insertpt] not in (' ','\t'):
310 break
311 else:
312 insertpt=len(line)
313
314 lineat = int(self.text.index("insert").split('.')[1])
315
316 if insertpt == lineat:
317 insertpt = 0
318
319 dest = "insert linestart+"+str(insertpt)+"c"
320
321 if (event.state&1) == 0:
322 # shift not pressed
323 self.text.tag_remove("sel", "1.0", "end")
324 else:
325 if not self.text.index("sel.first"):
326 self.text.mark_set("anchor","insert")
327
328 first = self.text.index(dest)
329 last = self.text.index("anchor")
330
331 if self.text.compare(first,">",last):
332 first,last = last,first
333
334 self.text.tag_remove("sel", "1.0", "end")
335 self.text.tag_add("sel", first, last)
336
337 self.text.mark_set("insert", dest)
338 self.text.see("insert")
339 return "break"
340
David Scherer7aced172000-08-15 01:13:23 +0000341 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000342 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000343 if macosxSupport.runningAsOSXApp():
344 # Insert some padding to avoid obscuring some of the statusbar
345 # by the resize widget.
346 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000347 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
348 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
349 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000350 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
351 self.text.event_add("<<set-line-and-column>>",
352 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000353 self.text.after_idle(self.set_line_and_column)
354
355 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000356 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000357 self.status_bar.set_label('column', 'Col: %s' % column)
358 self.status_bar.set_label('line', 'Ln: %s' % line)
359
David Scherer7aced172000-08-15 01:13:23 +0000360 menu_specs = [
361 ("file", "_File"),
362 ("edit", "_Edit"),
363 ("format", "F_ormat"),
364 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000365 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000366 ("windows", "_Windows"),
367 ("help", "_Help"),
368 ]
369
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000370 if macosxSupport.runningAsOSXApp():
371 del menu_specs[-3]
372 menu_specs[-2] = ("windows", "_Window")
373
374
David Scherer7aced172000-08-15 01:13:23 +0000375 def createmenubar(self):
376 mbar = self.menubar
377 self.menudict = menudict = {}
378 for name, label in self.menu_specs:
379 underline, label = prepstr(label)
380 menudict[name] = menu = Menu(mbar, name=name)
381 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren827822e2009-02-12 15:01:44 +0000382 if macosxSupport.runningAsOSXApp():
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000383 # Insert the application menu
384 menudict['application'] = menu = Menu(mbar, name='apple')
385 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000386 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000387 self.recent_files_menu = Menu(self.menubar)
388 self.menudict['file'].insert_cascade(3, label='Recent Files',
389 underline=0,
390 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000391 self.base_helpmenu_length = self.menudict['help'].index(END)
392 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000393
394 def postwindowsmenu(self):
395 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000396 menu = self.menudict['windows']
397 end = menu.index("end")
398 if end is None:
399 end = -1
400 if end > self.wmenu_end:
401 menu.delete(self.wmenu_end+1, end)
402 WindowList.add_windows_to_menu(menu)
403
404 rmenu = None
405
406 def right_menu_event(self, event):
407 self.text.tag_remove("sel", "1.0", "end")
408 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
409 if not self.rmenu:
410 self.make_rmenu()
411 rmenu = self.rmenu
412 self.event = event
413 iswin = sys.platform[:3] == 'win'
414 if iswin:
415 self.text.config(cursor="arrow")
416 rmenu.tk_popup(event.x_root, event.y_root)
417 if iswin:
418 self.text.config(cursor="ibeam")
419
420 rmenu_specs = [
421 # ("Label", "<<virtual-event>>"), ...
422 ("Close", "<<close-window>>"), # Example
423 ]
424
425 def make_rmenu(self):
426 rmenu = Menu(self.text, tearoff=0)
427 for label, eventname in self.rmenu_specs:
428 def command(text=self.text, eventname=eventname):
429 text.event_generate(eventname)
430 rmenu.add_command(label=label, command=command)
431 self.rmenu = rmenu
432
433 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000434 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000435
Steven M. Gava3b55a892001-11-21 05:56:26 +0000436 def config_dialog(self, event=None):
437 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000438
David Scherer7aced172000-08-15 01:13:23 +0000439 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000440 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000441 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000442
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000443 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000444 if sys.platform[:3] == 'win':
Steven M. Gava931625d2002-04-22 00:38:26 +0000445 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000446 else:
447 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000448 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000449
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000450 def cut(self,event):
451 self.text.event_generate("<<Cut>>")
452 return "break"
453
454 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000455 if not self.text.tag_ranges("sel"):
456 # There is no selection, so do nothing and maybe interrupt.
457 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000458 self.text.event_generate("<<Copy>>")
459 return "break"
460
461 def paste(self,event):
462 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000463 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000464 return "break"
465
David Scherer7aced172000-08-15 01:13:23 +0000466 def select_all(self, event=None):
467 self.text.tag_add("sel", "1.0", "end-1c")
468 self.text.mark_set("insert", "1.0")
469 self.text.see("insert")
470 return "break"
471
472 def remove_selection(self, event=None):
473 self.text.tag_remove("sel", "1.0", "end")
474 self.text.see("insert")
475
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000476 def move_at_edge_if_selection(self, edge_index):
477 """Cursor move begins at start or end of selection
478
479 When a left/right cursor key is pressed create and return to Tkinter a
480 function which causes a cursor move from the associated edge of the
481 selection.
482
483 """
484 self_text_index = self.text.index
485 self_text_mark_set = self.text.mark_set
486 edges_table = ("sel.first+1c", "sel.last-1c")
487 def move_at_edge(event):
488 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
489 try:
490 self_text_index("sel.first")
491 self_text_mark_set("insert", edges_table[edge_index])
492 except TclError:
493 pass
494 return move_at_edge
495
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000496 def del_word_left(self, event):
497 self.text.event_generate('<Meta-Delete>')
498 return "break"
499
500 def del_word_right(self, event):
501 self.text.event_generate('<Meta-d>')
502 return "break"
503
Steven M. Gavac5976402002-01-04 03:06:08 +0000504 def find_event(self, event):
505 SearchDialog.find(self.text)
506 return "break"
507
508 def find_again_event(self, event):
509 SearchDialog.find_again(self.text)
510 return "break"
511
512 def find_selection_event(self, event):
513 SearchDialog.find_selection(self.text)
514 return "break"
515
516 def find_in_files_event(self, event):
517 GrepDialog.grep(self.text, self.io, self.flist)
518 return "break"
519
520 def replace_event(self, event):
521 ReplaceDialog.replace(self.text)
522 return "break"
523
524 def goto_line_event(self, event):
525 text = self.text
526 lineno = tkSimpleDialog.askinteger("Goto",
527 "Go to line number:",parent=text)
528 if lineno is None:
529 return "break"
530 if lineno <= 0:
531 text.bell()
532 return "break"
533 text.mark_set("insert", "%d.0" % lineno)
534 text.see("insert")
535
David Scherer7aced172000-08-15 01:13:23 +0000536 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000537 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000538 try:
539 name = self.text.get("sel.first", "sel.last")
540 except TclError:
541 name = ""
542 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000543 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000544 name = tkSimpleDialog.askstring("Module",
545 "Enter the name of a Python module\n"
546 "to search on sys.path and open:",
547 parent=self.text, initialvalue=name)
548 if name:
549 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000550 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000551 return
David Scherer7aced172000-08-15 01:13:23 +0000552 # XXX Ought to insert current file's directory in front of path
553 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000554 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000555 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000556 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
557 return
558 if type != imp.PY_SOURCE:
559 tkMessageBox.showerror("Unsupported type",
560 "%s is not a source module" % name, parent=self.text)
561 return
562 if f:
563 f.close()
564 if self.flist:
565 self.flist.open(file)
566 else:
567 self.io.loadfile(file)
568
569 def open_class_browser(self, event=None):
570 filename = self.io.filename
571 if not filename:
572 tkMessageBox.showerror(
573 "No filename",
574 "This buffer has no associated filename",
575 master=self.text)
576 self.text.focus_set()
577 return None
578 head, tail = os.path.split(filename)
579 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000580 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000581 ClassBrowser.ClassBrowser(self.flist, base, [head])
582
583 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000584 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000585 PathBrowser.PathBrowser(self.flist)
586
587 def gotoline(self, lineno):
588 if lineno is not None and lineno > 0:
589 self.text.mark_set("insert", "%d.0" % lineno)
590 self.text.tag_remove("sel", "1.0", "end")
591 self.text.tag_add("sel", "insert", "insert +1l")
592 self.center()
593
594 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000595 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000596 return True
David Scherer7aced172000-08-15 01:13:23 +0000597 base, ext = os.path.splitext(os.path.basename(filename))
598 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000599 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000600 line = self.text.get('1.0', '1.0 lineend')
601 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000602
603 def close_hook(self):
604 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000605 self.flist.unregister_maybe_terminate(self)
606 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000607
608 def set_close_hook(self, close_hook):
609 self.close_hook = close_hook
610
611 def filename_change_hook(self):
612 if self.flist:
613 self.flist.filename_changed_edit(self)
614 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000615 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000616 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000617
Christian Heimesa156e092008-02-16 07:38:31 +0000618 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000619 if self.color:
620 return
Christian Heimesa156e092008-02-16 07:38:31 +0000621 if self.ispythonsource(self.io.filename):
622 self.color = self.ColorDelegator()
623 # can add more colorizers here...
624 if self.color:
625 self.per.removefilter(self.undo)
626 self.per.insertfilter(self.color)
627 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000628
Christian Heimesa156e092008-02-16 07:38:31 +0000629 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000630 if not self.color:
631 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000632 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000633 self.per.removefilter(self.color)
634 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000635
Steven M. Gavab77d3432002-03-02 07:16:21 +0000636 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000637 "Update the colour theme"
638 # Called from self.filename_change_hook and from configDialog.py
639 self._rmcolorizer()
640 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000641 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000642 normal_colors = idleConf.GetHighlight(theme, 'normal')
643 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
644 select_colors = idleConf.GetHighlight(theme, 'hilite')
645 self.text.config(
646 foreground=normal_colors['foreground'],
647 background=normal_colors['background'],
648 insertbackground=cursor_color,
649 selectforeground=select_colors['foreground'],
650 selectbackground=select_colors['background'],
651 )
David Scherer7aced172000-08-15 01:13:23 +0000652
Guido van Rossum33d26892007-08-05 15:29:28 +0000653 IDENTCHARS = string.ascii_letters + string.digits + "_"
654
655 def colorize_syntax_error(self, text, pos):
656 text.tag_add("ERROR", pos)
657 char = text.get(pos)
658 if char and char in self.IDENTCHARS:
659 text.tag_add("ERROR", pos + " wordstart", pos)
660 if '\n' == text.get(pos): # error at line end
661 text.mark_set("insert", pos)
662 else:
663 text.mark_set("insert", pos + "+1c")
664 text.see(pos)
665
Steven M. Gavab1585412002-03-12 00:21:56 +0000666 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000667 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000668 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000669 fontWeight='normal'
670 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
671 fontWeight='bold'
672 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
673 idleConf.GetOption('main','EditorWindow','font-size'),
674 fontWeight))
675
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000676 def RemoveKeybindings(self):
677 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000678 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000679 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000680 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000681 self.text.event_delete(event, *keylist)
682 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000683 xkeydefs = idleConf.GetExtensionBindings(extensionName)
684 if xkeydefs:
685 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000686 self.text.event_delete(event, *keylist)
687
688 def ApplyKeybindings(self):
689 "Update the keybindings after they are changed"
690 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000691 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000692 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000693 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000694 xkeydefs = idleConf.GetExtensionBindings(extensionName)
695 if xkeydefs:
696 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000697 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000698 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000699 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000700 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000701 for item in menu[1]:
702 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000703 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000704 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000705 menu = self.menudict[menubarItem]
706 end = menu.index(END) + 1
707 for index in range(0, end):
708 if menu.type(index) == 'command':
709 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000710 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000711 itemName = menu.entrycget(index, 'label')
712 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000713 if menubarItem in menuEventDict:
714 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000715 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000716 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000717 accel = get_accelerator(keydefs, event)
718 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000719
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000720 def set_notabs_indentwidth(self):
721 "Update the indentwidth if changed and not using tabs in this window"
722 # Called from configDialog.py
723 if not self.usetabs:
724 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
725 type='int')
726
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000727 def reset_help_menu_entries(self):
728 "Update the additional help entries on the Help menu"
729 help_list = idleConf.GetAllExtraHelpSourcesList()
730 helpmenu = self.menudict['help']
731 # first delete the extra help entries, if any
732 helpmenu_length = helpmenu.index(END)
733 if helpmenu_length > self.base_helpmenu_length:
734 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
735 # then rebuild them
736 if help_list:
737 helpmenu.add_separator()
738 for entry in help_list:
739 cmd = self.__extra_help_callback(entry[1])
740 helpmenu.add_command(label=entry[0], command=cmd)
741 # and update the menu dictionary
742 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000743
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000744 def __extra_help_callback(self, helpfile):
745 "Create a callback with the helpfile value frozen at definition time"
746 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000747 if not helpfile.startswith(('www', 'http')):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000748 url = os.path.normpath(helpfile)
749 if sys.platform[:3] == 'win':
750 os.startfile(helpfile)
751 else:
752 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000753 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000754
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000755 def update_recent_files_list(self, new_file=None):
756 "Load and update the recent files list and menus"
757 rf_list = []
758 if os.path.exists(self.recent_files_path):
759 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000760 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000761 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000762 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000763 rf_list_file.close()
764 if new_file:
765 new_file = os.path.abspath(new_file) + '\n'
766 if new_file in rf_list:
767 rf_list.remove(new_file) # move to top
768 rf_list.insert(0, new_file)
769 # clean and save the recent files list
770 bad_paths = []
771 for path in rf_list:
772 if '\0' in path or not os.path.exists(path[0:-1]):
773 bad_paths.append(path)
774 rf_list = [path for path in rf_list if path not in bad_paths]
775 ulchars = "1234567890ABCDEFGHIJK"
776 rf_list = rf_list[0:len(ulchars)]
777 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000778 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000779 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000780 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000781 rf_file.close()
782 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000783 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000784 menu = instance.recent_files_menu
785 menu.delete(1, END) # clear, and rebuild:
786 for i, file in zip(count(), rf_list):
787 file_name = file[0:-1] # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000788 # make unicode string to display non-ASCII chars correctly
789 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000790 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000791 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000792 command=callback,
793 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000794
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000795 def __recent_file_callback(self, file_name):
796 def open_recent_file(fn_closure=file_name):
797 self.io.open(editFile=fn_closure)
798 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000799
David Scherer7aced172000-08-15 01:13:23 +0000800 def saved_change_hook(self):
801 short = self.short_title()
802 long = self.long_title()
803 if short and long:
804 title = short + " - " + long
805 elif short:
806 title = short
807 elif long:
808 title = long
809 else:
810 title = "Untitled"
811 icon = short or long or title
812 if not self.get_saved():
813 title = "*%s*" % title
814 icon = "*%s" % icon
815 self.top.wm_title(title)
816 self.top.wm_iconname(icon)
817
818 def get_saved(self):
819 return self.undo.get_saved()
820
821 def set_saved(self, flag):
822 self.undo.set_saved(flag)
823
824 def reset_undo(self):
825 self.undo.reset_undo()
826
827 def short_title(self):
828 filename = self.io.filename
829 if filename:
830 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000831 # return unicode string to display non-ASCII chars correctly
832 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000833
834 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000835 # return unicode string to display non-ASCII chars correctly
836 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000837
838 def center_insert_event(self, event):
839 self.center()
840
841 def center(self, mark="insert"):
842 text = self.text
843 top, bot = self.getwindowlines()
844 lineno = self.getlineno(mark)
845 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000846 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000847 text.yview(float(newtop))
848
849 def getwindowlines(self):
850 text = self.text
851 top = self.getlineno("@0,0")
852 bot = self.getlineno("@0,65535")
853 if top == bot and text.winfo_height() == 1:
854 # Geometry manager hasn't run yet
855 height = int(text['height'])
856 bot = top + height - 1
857 return top, bot
858
859 def getlineno(self, mark="insert"):
860 text = self.text
861 return int(float(text.index(mark)))
862
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000863 def get_geometry(self):
864 "Return (width, height, x, y)"
865 geom = self.top.wm_geometry()
866 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000867 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000868
David Scherer7aced172000-08-15 01:13:23 +0000869 def close_event(self, event):
870 self.close()
871
872 def maybesave(self):
873 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000874 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000875 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000876 self.top.deiconify()
877 self.top.lower()
878 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000879 return self.io.maybesave()
880
881 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000882 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000883 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000884 self._close()
885 return reply
886
887 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000888 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000889 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000890 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000891 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000892 self.io.close()
893 self.io = None
894 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000895 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000896 self.color.close(False)
897 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000898 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000899 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000900 self.per.close()
901 self.per = None
902 self.top.destroy()
903 if self.close_hook:
904 # unless override: unregister from flist, terminate if last window
905 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000906
907 def load_extensions(self):
908 self.extensions = {}
909 self.load_standard_extensions()
910
911 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000912 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000913 if hasattr(ins, "close"):
914 ins.close()
915 self.extensions = {}
916
917 def load_standard_extensions(self):
918 for name in self.get_standard_extension_names():
919 try:
920 self.load_extension(name)
921 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000922 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000923 traceback.print_exc()
924
925 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000926 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000927
928 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000929 try:
930 mod = __import__(name, globals(), locals(), [])
931 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000932 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000933 raise
David Scherer7aced172000-08-15 01:13:23 +0000934 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000935 keydefs = idleConf.GetExtensionBindings(name)
936 if hasattr(cls, "menudefs"):
937 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000938 ins = cls(self)
939 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000940 if keydefs:
941 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000942 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000943 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000944 while methodname[:1] == '<':
945 methodname = methodname[1:]
946 while methodname[-1:] == '>':
947 methodname = methodname[:-1]
948 methodname = methodname + "_event"
949 if hasattr(ins, methodname):
950 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000951
952 def apply_bindings(self, keydefs=None):
953 if keydefs is None:
954 keydefs = self.Bindings.default_keydefs
955 text = self.text
956 text.keydefs = keydefs
957 for event, keylist in keydefs.items():
958 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000959 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000960
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000961 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000962 """Add appropriate entries to the menus and submenus
963
964 Menus that are absent or None in self.menudict are ignored.
965 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000966 if menudefs is None:
967 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000968 if keydefs is None:
969 keydefs = self.Bindings.default_keydefs
970 menudict = self.menudict
971 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000972 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000973 menu = menudict.get(mname)
974 if not menu:
975 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000976 for entry in entrylist:
977 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000978 menu.add_separator()
979 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000980 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000981 checkbutton = (label[:1] == '!')
982 if checkbutton:
983 label = label[1:]
984 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000985 accelerator = get_accelerator(keydefs, eventname)
986 def command(text=text, eventname=eventname):
987 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000988 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000989 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000990 menu.add_checkbutton(label=label, underline=underline,
991 command=command, accelerator=accelerator,
992 variable=var)
993 else:
994 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000995 command=command,
996 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000997
998 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000999 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001000 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001001 value = var.get()
1002 return value
1003 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001004 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001005
1006 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001007 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001008 if var:
1009 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001010 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +00001011 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +00001012
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001013 def get_var_obj(self, name, vartype=None):
1014 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001015 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001016 # create a Tkinter variable object with self.text as master:
1017 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001018 return var
1019
1020 # Tk implementations of "virtual text methods" -- each platform
1021 # reusing IDLE's support code needs to define these for its GUI's
1022 # flavor of widget.
1023
1024 # Is character at text_index in a Python string? Return 0 for
1025 # "guaranteed no", true for anything else. This info is expensive
1026 # to compute ab initio, but is probably already known by the
1027 # platform's colorizer.
1028
1029 def is_char_in_string(self, text_index):
1030 if self.color:
1031 # Return true iff colorizer hasn't (re)gotten this far
1032 # yet, or the character is tagged as being in a string
1033 return self.text.tag_prevrange("TODO", text_index) or \
1034 "STRING" in self.text.tag_names(text_index)
1035 else:
1036 # The colorizer is missing: assume the worst
1037 return 1
1038
1039 # If a selection is defined in the text widget, return (start,
1040 # end) as Tkinter text indices, otherwise return (None, None)
1041 def get_selection_indices(self):
1042 try:
1043 first = self.text.index("sel.first")
1044 last = self.text.index("sel.last")
1045 return first, last
1046 except TclError:
1047 return None, None
1048
1049 # Return the text widget's current view of what a tab stop means
1050 # (equivalent width in spaces).
1051
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001052 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +00001053 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1054 return int(current)
1055
1056 # Set the text widget's current view of what a tab stop means.
1057
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001058 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001059 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001060 if self.get_tk_tabwidth() != newtabwidth:
1061 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001062 pixels = text.tk.call("font", "measure", text["font"],
1063 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001064 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001065 text.configure(tabs=pixels)
1066
Guido van Rossum33d26892007-08-05 15:29:28 +00001067### begin autoindent code ### (configuration was moved to beginning of class)
1068
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001069 def set_indentation_params(self, is_py_src, guess=True):
1070 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001071 i = self.guess_indent()
1072 if 2 <= i <= 8:
1073 self.indentwidth = i
1074 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001075 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001076 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001077
1078 def smart_backspace_event(self, event):
1079 text = self.text
1080 first, last = self.get_selection_indices()
1081 if first and last:
1082 text.delete(first, last)
1083 text.mark_set("insert", first)
1084 return "break"
1085 # Delete whitespace left, until hitting a real char or closest
1086 # preceding virtual tab stop.
1087 chars = text.get("insert linestart", "insert")
1088 if chars == '':
1089 if text.compare("insert", ">", "1.0"):
1090 # easy: delete preceding newline
1091 text.delete("insert-1c")
1092 else:
1093 text.bell() # at start of buffer
1094 return "break"
1095 if chars[-1] not in " \t":
1096 # easy: delete preceding real char
1097 text.delete("insert-1c")
1098 return "break"
1099 # Ick. It may require *inserting* spaces if we back up over a
1100 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001101 tabwidth = self.tabwidth
1102 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001103 assert have > 0
1104 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001105 # Debug prompt is multilined....
1106 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001107 ncharsdeleted = 0
1108 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001109 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001110 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001111 chars = chars[:-1]
1112 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001113 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001114 if have <= want or chars[-1] not in " \t":
1115 break
1116 text.undo_block_start()
1117 text.delete("insert-%dc" % ncharsdeleted, "insert")
1118 if have < want:
1119 text.insert("insert", ' ' * (want - have))
1120 text.undo_block_stop()
1121 return "break"
1122
1123 def smart_indent_event(self, event):
1124 # if intraline selection:
1125 # delete it
1126 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001127 # do indent-region
1128 # else:
1129 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001130 text = self.text
1131 first, last = self.get_selection_indices()
1132 text.undo_block_start()
1133 try:
1134 if first and last:
1135 if index2line(first) != index2line(last):
1136 return self.indent_region_event(event)
1137 text.delete(first, last)
1138 text.mark_set("insert", first)
1139 prefix = text.get("insert linestart", "insert")
1140 raw, effective = classifyws(prefix, self.tabwidth)
1141 if raw == len(prefix):
1142 # only whitespace to the left
1143 self.reindent_to(effective + self.indentwidth)
1144 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001145 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001146 if self.usetabs:
1147 pad = '\t'
1148 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001149 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001150 n = self.indentwidth
1151 pad = ' ' * (n - effective % n)
1152 text.insert("insert", pad)
1153 text.see("insert")
1154 return "break"
1155 finally:
1156 text.undo_block_stop()
1157
1158 def newline_and_indent_event(self, event):
1159 text = self.text
1160 first, last = self.get_selection_indices()
1161 text.undo_block_start()
1162 try:
1163 if first and last:
1164 text.delete(first, last)
1165 text.mark_set("insert", first)
1166 line = text.get("insert linestart", "insert")
1167 i, n = 0, len(line)
1168 while i < n and line[i] in " \t":
1169 i = i+1
1170 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001171 # the cursor is in or at leading indentation in a continuation
1172 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001173 text.insert("insert linestart", '\n')
1174 return "break"
1175 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001176 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001177 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001178 last_line_of_prompt = sys.ps1.split('\n')[-1]
1179 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001180 line = line[:-1]
1181 i = i+1
1182 if i:
1183 text.delete("insert - %d chars" % i, "insert")
1184 # strip whitespace after insert point
1185 while text.get("insert") in " \t":
1186 text.delete("insert")
1187 # start new line
1188 text.insert("insert", '\n')
1189
1190 # adjust indentation for continuations and block
1191 # open/close first need to find the last stmt
1192 lno = index2line(text.index('insert'))
1193 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001194 if not self.context_use_ps1:
1195 for context in self.num_context_lines:
1196 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001197 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001198 rawtext = text.get(startatindex, "insert")
1199 y.set_str(rawtext)
1200 bod = y.find_good_parse_start(
1201 self.context_use_ps1,
1202 self._build_char_in_string_func(startatindex))
1203 if bod is not None or startat == 1:
1204 break
1205 y.set_lo(bod or 0)
1206 else:
1207 r = text.tag_prevrange("console", "insert")
1208 if r:
1209 startatindex = r[1]
1210 else:
1211 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001212 rawtext = text.get(startatindex, "insert")
1213 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001214 y.set_lo(0)
1215
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001216 c = y.get_continuation_type()
1217 if c != PyParse.C_NONE:
1218 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001219 if c == PyParse.C_STRING_FIRST_LINE:
1220 # after the first line of a string; do not indent at all
1221 pass
1222 elif c == PyParse.C_STRING_NEXT_LINES:
1223 # inside a string which started before this line;
1224 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001225 text.insert("insert", indent)
1226 elif c == PyParse.C_BRACKET:
1227 # line up with the first (if any) element of the
1228 # last open bracket structure; else indent one
1229 # level beyond the indent of the line with the
1230 # last open bracket
1231 self.reindent_to(y.compute_bracket_indent())
1232 elif c == PyParse.C_BACKSLASH:
1233 # if more than one line in this stmt already, just
1234 # mimic the current indent; else if initial line
1235 # has a start on an assignment stmt, indent to
1236 # beyond leftmost =; else to beyond first chunk of
1237 # non-whitespace on initial line
1238 if y.get_num_lines_in_stmt() > 1:
1239 text.insert("insert", indent)
1240 else:
1241 self.reindent_to(y.compute_backslash_indent())
1242 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001243 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001244 return "break"
1245
1246 # This line starts a brand new stmt; indent relative to
1247 # indentation of initial line of closest preceding
1248 # interesting stmt.
1249 indent = y.get_base_indent_string()
1250 text.insert("insert", indent)
1251 if y.is_block_opener():
1252 self.smart_indent_event(event)
1253 elif indent and y.is_block_closer():
1254 self.smart_backspace_event(event)
1255 return "break"
1256 finally:
1257 text.see("insert")
1258 text.undo_block_stop()
1259
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001260 # Our editwin provides a is_char_in_string function that works
1261 # with a Tk text index, but PyParse only knows about offsets into
1262 # a string. This builds a function for PyParse that accepts an
1263 # offset.
1264
1265 def _build_char_in_string_func(self, startindex):
1266 def inner(offset, _startindex=startindex,
1267 _icis=self.is_char_in_string):
1268 return _icis(_startindex + "+%dc" % offset)
1269 return inner
1270
1271 def indent_region_event(self, event):
1272 head, tail, chars, lines = self.get_region()
1273 for pos in range(len(lines)):
1274 line = lines[pos]
1275 if line:
1276 raw, effective = classifyws(line, self.tabwidth)
1277 effective = effective + self.indentwidth
1278 lines[pos] = self._make_blanks(effective) + line[raw:]
1279 self.set_region(head, tail, chars, lines)
1280 return "break"
1281
1282 def dedent_region_event(self, event):
1283 head, tail, chars, lines = self.get_region()
1284 for pos in range(len(lines)):
1285 line = lines[pos]
1286 if line:
1287 raw, effective = classifyws(line, self.tabwidth)
1288 effective = max(effective - self.indentwidth, 0)
1289 lines[pos] = self._make_blanks(effective) + line[raw:]
1290 self.set_region(head, tail, chars, lines)
1291 return "break"
1292
1293 def comment_region_event(self, event):
1294 head, tail, chars, lines = self.get_region()
1295 for pos in range(len(lines) - 1):
1296 line = lines[pos]
1297 lines[pos] = '##' + line
1298 self.set_region(head, tail, chars, lines)
1299
1300 def uncomment_region_event(self, event):
1301 head, tail, chars, lines = self.get_region()
1302 for pos in range(len(lines)):
1303 line = lines[pos]
1304 if not line:
1305 continue
1306 if line[:2] == '##':
1307 line = line[2:]
1308 elif line[:1] == '#':
1309 line = line[1:]
1310 lines[pos] = line
1311 self.set_region(head, tail, chars, lines)
1312
1313 def tabify_region_event(self, event):
1314 head, tail, chars, lines = self.get_region()
1315 tabwidth = self._asktabwidth()
1316 for pos in range(len(lines)):
1317 line = lines[pos]
1318 if line:
1319 raw, effective = classifyws(line, tabwidth)
1320 ntabs, nspaces = divmod(effective, tabwidth)
1321 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1322 self.set_region(head, tail, chars, lines)
1323
1324 def untabify_region_event(self, event):
1325 head, tail, chars, lines = self.get_region()
1326 tabwidth = self._asktabwidth()
1327 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001328 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001329 self.set_region(head, tail, chars, lines)
1330
1331 def toggle_tabs_event(self, event):
1332 if self.askyesno(
1333 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001334 "Turn tabs " + ("on", "off")[self.usetabs] +
1335 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001336 ("will be", "remains at")[self.usetabs] + " 8." +
1337 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001338 parent=self.text):
1339 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001340 # Try to prevent inconsistent indentation.
1341 # User must change indent width manually after using tabs.
1342 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001343 return "break"
1344
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001345 # XXX this isn't bound to anything -- see tabwidth comments
1346## def change_tabwidth_event(self, event):
1347## new = self._asktabwidth()
1348## if new != self.tabwidth:
1349## self.tabwidth = new
1350## self.set_indentation_params(0, guess=0)
1351## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001352
1353 def change_indentwidth_event(self, event):
1354 new = self.askinteger(
1355 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001356 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001357 parent=self.text,
1358 initialvalue=self.indentwidth,
1359 minvalue=2,
1360 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001361 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001362 self.indentwidth = new
1363 return "break"
1364
1365 def get_region(self):
1366 text = self.text
1367 first, last = self.get_selection_indices()
1368 if first and last:
1369 head = text.index(first + " linestart")
1370 tail = text.index(last + "-1c lineend +1c")
1371 else:
1372 head = text.index("insert linestart")
1373 tail = text.index("insert lineend +1c")
1374 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001375 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001376 return head, tail, chars, lines
1377
1378 def set_region(self, head, tail, chars, lines):
1379 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001380 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001381 if newchars == chars:
1382 text.bell()
1383 return
1384 text.tag_remove("sel", "1.0", "end")
1385 text.mark_set("insert", head)
1386 text.undo_block_start()
1387 text.delete(head, tail)
1388 text.insert(head, newchars)
1389 text.undo_block_stop()
1390 text.tag_add("sel", head, "insert")
1391
1392 # Make string that displays as n leading blanks.
1393
1394 def _make_blanks(self, n):
1395 if self.usetabs:
1396 ntabs, nspaces = divmod(n, self.tabwidth)
1397 return '\t' * ntabs + ' ' * nspaces
1398 else:
1399 return ' ' * n
1400
1401 # Delete from beginning of line to insert point, then reinsert
1402 # column logical (meaning use tabs if appropriate) spaces.
1403
1404 def reindent_to(self, column):
1405 text = self.text
1406 text.undo_block_start()
1407 if text.compare("insert linestart", "!=", "insert"):
1408 text.delete("insert linestart", "insert")
1409 if column:
1410 text.insert("insert", self._make_blanks(column))
1411 text.undo_block_stop()
1412
1413 def _asktabwidth(self):
1414 return self.askinteger(
1415 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001416 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001417 parent=self.text,
1418 initialvalue=self.indentwidth,
1419 minvalue=2,
1420 maxvalue=16) or self.tabwidth
1421
1422 # Guess indentwidth from text content.
1423 # Return guessed indentwidth. This should not be believed unless
1424 # it's in a reasonable range (e.g., it will be 0 if no indented
1425 # blocks are found).
1426
1427 def guess_indent(self):
1428 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1429 if opener and indented:
1430 raw, indentsmall = classifyws(opener, self.tabwidth)
1431 raw, indentlarge = classifyws(indented, self.tabwidth)
1432 else:
1433 indentsmall = indentlarge = 0
1434 return indentlarge - indentsmall
1435
1436# "line.col" -> line, as an int
1437def index2line(index):
1438 return int(float(index))
1439
1440# Look at the leading whitespace in s.
1441# Return pair (# of leading ws characters,
1442# effective # of leading blanks after expanding
1443# tabs to width tabwidth)
1444
1445def classifyws(s, tabwidth):
1446 raw = effective = 0
1447 for ch in s:
1448 if ch == ' ':
1449 raw = raw + 1
1450 effective = effective + 1
1451 elif ch == '\t':
1452 raw = raw + 1
1453 effective = (effective // tabwidth + 1) * tabwidth
1454 else:
1455 break
1456 return raw, effective
1457
1458import tokenize
1459_tokenize = tokenize
1460del tokenize
1461
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001462class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001463
1464 # .run() chews over the Text widget, looking for a block opener
1465 # and the stmt following it. Returns a pair,
1466 # (line containing block opener, line containing stmt)
1467 # Either or both may be None.
1468
1469 def __init__(self, text, tabwidth):
1470 self.text = text
1471 self.tabwidth = tabwidth
1472 self.i = self.finished = 0
1473 self.blkopenline = self.indentedline = None
1474
1475 def readline(self):
1476 if self.finished:
1477 return ""
1478 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001479 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001480 if self.text.compare(mark, ">=", "end"):
1481 return ""
1482 return self.text.get(mark, mark + " lineend+1c")
1483
1484 def tokeneater(self, type, token, start, end, line,
1485 INDENT=_tokenize.INDENT,
1486 NAME=_tokenize.NAME,
1487 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1488 if self.finished:
1489 pass
1490 elif type == NAME and token in OPENERS:
1491 self.blkopenline = line
1492 elif type == INDENT and self.blkopenline:
1493 self.indentedline = line
1494 self.finished = 1
1495
1496 def run(self):
1497 save_tabsize = _tokenize.tabsize
1498 _tokenize.tabsize = self.tabwidth
1499 try:
1500 try:
Trent Nelson428de652008-03-18 22:41:35 +00001501 tokens = _tokenize.generate_tokens(self.readline)
1502 for token in tokens:
1503 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001504 except _tokenize.TokenError:
1505 # since we cut off the tokenizer early, we can trigger
1506 # spurious errors
1507 pass
1508 finally:
1509 _tokenize.tabsize = save_tabsize
1510 return self.blkopenline, self.indentedline
1511
1512### end autoindent code ###
1513
David Scherer7aced172000-08-15 01:13:23 +00001514def prepstr(s):
1515 # Helper to extract the underscore from a string, e.g.
1516 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001517 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001518 if i >= 0:
1519 s = s[:i] + s[i+1:]
1520 return i, s
1521
1522
1523keynames = {
1524 'bracketleft': '[',
1525 'bracketright': ']',
1526 'slash': '/',
1527}
1528
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001529def get_accelerator(keydefs, eventname):
1530 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001531 if not keylist:
1532 return ""
1533 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001534 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001535 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1536 s = re.sub("Key-", "", s)
1537 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1538 s = re.sub("Control-", "Ctrl-", s)
1539 s = re.sub("-", "+", s)
1540 s = re.sub("><", " ", s)
1541 s = re.sub("<", "", s)
1542 s = re.sub(">", "", s)
1543 return s
1544
1545
1546def fixwordbreaks(root):
1547 # Make sure that Tk's double-click and next/previous word
1548 # operations use our definition of a word (i.e. an identifier)
1549 tk = root.tk
1550 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1551 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1552 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1553
1554
1555def test():
1556 root = Tk()
1557 fixwordbreaks(root)
1558 root.withdraw()
1559 if sys.argv[1:]:
1560 filename = sys.argv[1]
1561 else:
1562 filename = None
1563 edit = EditorWindow(root=root, filename=filename)
1564 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001565 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001566 root.mainloop()
1567 root.destroy()
1568
1569if __name__ == '__main__':
1570 test()