blob: 7161069abfa01c48c7c0fcff08a442bd7677d366 [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
David Scherer7aced172000-08-15 01:13:23 +00007from Tkinter import *
8import tkSimpleDialog
9import 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. Kaiser220ecbc2002-09-16 02:13:15 +000027def _find_module(fullname, path=None):
28 """Version of imp.find_module() that handles hierarchical module names"""
29
30 file = None
31 for tgt in fullname.split('.'):
32 if file is not None:
33 file.close() # close intermediate files
34 (file, filename, descr) = imp.find_module(tgt, path)
35 if descr[2] == imp.PY_SOURCE:
36 break # find but not load the source file
37 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000038 try:
39 path = module.__path__
40 except AttributeError:
Kurt B. Kaiserad667422007-08-23 01:06:15 +000041 raise ImportError('No source for module ' + module.__name__)
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000042 return file, filename, descr
43
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000044class EditorWindow(object):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000045 from idlelib.Percolator import Percolator
46 from idlelib.ColorDelegator import ColorDelegator
47 from idlelib.UndoDelegator import UndoDelegator
48 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
49 from idlelib import Bindings
David Scherer7aced172000-08-15 01:13:23 +000050 from Tkinter import Toplevel
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000051 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000052
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000053 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000054
55 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000056 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000057 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000058 if sys.platform.count('linux'):
59 # look for html docs in a couple of standard places
60 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
61 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
62 dochome = '/var/www/html/python/index.html'
63 else:
64 basepath = '/usr/share/doc/' # standard location
65 dochome = os.path.join(basepath, pyver,
66 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000067 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +000068 chmfile = os.path.join(sys.prefix, 'Doc',
69 'Python%d%d.chm' % sys.version_info[:2])
Thomas Heller84ef1532003-09-23 20:53:10 +000070 if os.path.isfile(chmfile):
71 dochome = chmfile
Thomas Wouters0e3f5912006-08-11 14:57:12 +000072
73 elif macosxSupport.runningAsOSXApp():
74 # documentation is stored inside the python framework
75 dochome = os.path.join(sys.prefix,
76 'Resources/English.lproj/Documentation/index.html')
77
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000078 dochome = os.path.normpath(dochome)
79 if os.path.isfile(dochome):
80 EditorWindow.help_url = dochome
Thomas Wouters0e3f5912006-08-11 14:57:12 +000081 if sys.platform == 'darwin':
82 # Safari requires real file:-URLs
83 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000084 else:
85 EditorWindow.help_url = "http://www.python.org/doc/current"
Steven M. Gavadc72f482002-01-03 11:51:07 +000086 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000087 self.flist = flist
88 root = root or flist.root
89 self.root = root
Thomas Wouters0e3f5912006-08-11 14:57:12 +000090 try:
91 sys.ps1
92 except AttributeError:
93 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +000094 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +000095 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000096 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +000097 self.tkinter_vars = flist.vars
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +000098 #self.top.instance_dict makes flist.inversedict avalable to
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000099 #configDialog.py so it can access all EditorWindow instaces
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000100 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000101 else:
102 self.tkinter_vars = {} # keys: Tkinter event names
103 # values: Tkinter variable instances
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000104 self.top.instance_dict = {}
105 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000106 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000107 self.text_frame = text_frame = Frame(top)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000108 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000109 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000110 self.text = text = MultiCallCreator(Text)(
111 text_frame, name='text', padx=5, wrap='none',
Steven M. Gavadc72f482002-01-03 11:51:07 +0000112 foreground=idleConf.GetHighlight(currentTheme,
113 'normal',fgBg='fg'),
114 background=idleConf.GetHighlight(currentTheme,
115 'normal',fgBg='bg'),
116 highlightcolor=idleConf.GetHighlight(currentTheme,
117 'hilite',fgBg='fg'),
118 highlightbackground=idleConf.GetHighlight(currentTheme,
119 'hilite',fgBg='bg'),
120 insertbackground=idleConf.GetHighlight(currentTheme,
121 'cursor',fgBg='fg'),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000122 width=self.width,
Steven M. Gavadc72f482002-01-03 11:51:07 +0000123 height=idleConf.GetOption('main','EditorWindow','height') )
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000124 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000125
126 self.createmenubar()
127 self.apply_bindings()
128
129 self.top.protocol("WM_DELETE_WINDOW", self.close)
130 self.top.bind("<<close-window>>", self.close_event)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000131 if macosxSupport.runningAsOSXApp():
132 # Command-W on editorwindows doesn't work without this.
133 text.bind('<<close-window>>', self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000134 text.bind("<<cut>>", self.cut)
135 text.bind("<<copy>>", self.copy)
136 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000137 text.bind("<<center-insert>>", self.center_insert_event)
138 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000139 text.bind("<<python-docs>>", self.python_docs)
140 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000141 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000142 text.bind("<<open-module>>", self.open_module)
143 text.bind("<<do-nothing>>", lambda event: "break")
144 text.bind("<<select-all>>", self.select_all)
145 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000146 text.bind("<<find>>", self.find_event)
147 text.bind("<<find-again>>", self.find_again_event)
148 text.bind("<<find-in-files>>", self.find_in_files_event)
149 text.bind("<<find-selection>>", self.find_selection_event)
150 text.bind("<<replace>>", self.replace_event)
151 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000152 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000153 text.bind("<<smart-backspace>>",self.smart_backspace_event)
154 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
155 text.bind("<<smart-indent>>",self.smart_indent_event)
156 text.bind("<<indent-region>>",self.indent_region_event)
157 text.bind("<<dedent-region>>",self.dedent_region_event)
158 text.bind("<<comment-region>>",self.comment_region_event)
159 text.bind("<<uncomment-region>>",self.uncomment_region_event)
160 text.bind("<<tabify-region>>",self.tabify_region_event)
161 text.bind("<<untabify-region>>",self.untabify_region_event)
162 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
163 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000164 text.bind("<Left>", self.move_at_edge_if_selection(0))
165 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000166 text.bind("<<del-word-left>>", self.del_word_left)
167 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000168
David Scherer7aced172000-08-15 01:13:23 +0000169 if flist:
170 flist.inversedict[self] = key
171 if key:
172 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000173 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000174 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
175 text.bind("<<open-class-browser>>", self.open_class_browser)
176 text.bind("<<open-path-browser>>", self.open_path_browser)
177
Steven M. Gava898a3652001-10-07 11:10:44 +0000178 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000179 vbar['command'] = text.yview
180 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000181 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000182 fontWeight = 'normal'
183 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000184 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000185 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
186 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
187 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000188 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
189 text.pack(side=TOP, fill=BOTH, expand=1)
190 text.focus_set()
191
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000192 # usetabs true -> literal tab characters are used by indent and
193 # dedent cmds, possibly mixed with spaces if
194 # indentwidth is not a multiple of tabwidth,
195 # which will cause Tabnanny to nag!
196 # false -> tab characters are converted to spaces by indent
197 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000198 # Although use-spaces=0 can be configured manually in config-main.def,
199 # configuration of tabs v. spaces is not supported in the configuration
200 # dialog. IDLE promotes the preferred Python indentation: use spaces!
201 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
202 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000203
204 # tabwidth is the display width of a literal tab character.
205 # CAUTION: telling Tk to use anything other than its default
206 # tab setting causes it to use an entirely different tabbing algorithm,
207 # treating tab stops as fixed distances from the left margin.
208 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000209 self.tabwidth = 8 # must remain 8 until Tk is fixed.
210
211 # indentwidth is the number of screen characters per indent level.
212 # The recommended Python indentation is four spaces.
213 self.indentwidth = self.tabwidth
214 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000215
216 # If context_use_ps1 is true, parsing searches back for a ps1 line;
217 # else searches for a popular (if, def, ...) Python stmt.
218 self.context_use_ps1 = False
219
220 # When searching backwards for a reliable place to begin parsing,
221 # first start num_context_lines[0] lines back, then
222 # num_context_lines[1] lines back if that didn't work, and so on.
223 # The last value should be huge (larger than the # of lines in a
224 # conceivable file).
225 # Making the initial values larger slows things down more often.
226 self.num_context_lines = 50, 500, 5000000
227
David Scherer7aced172000-08-15 01:13:23 +0000228 self.per = per = self.Percolator(text)
229 if self.ispythonsource(filename):
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000230 self.color = color = self.ColorDelegator()
231 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000232 else:
David Scherer7aced172000-08-15 01:13:23 +0000233 self.color = None
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000234
235 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)
240
241 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000242 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000243 io.set_filename_change_hook(self.filename_change_hook)
244
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000245 # Create the recent files submenu
246 self.recent_files_menu = Menu(self.menubar)
247 self.menudict['file'].insert_cascade(3, label='Recent Files',
248 underline=0,
249 menu=self.recent_files_menu)
250 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000251
David Scherer7aced172000-08-15 01:13:23 +0000252 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000253 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000254 io.loadfile(filename)
255 else:
256 io.set_filename(filename)
David Scherer7aced172000-08-15 01:13:23 +0000257 self.saved_change_hook()
258
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000259 self.set_indentation_params(self.ispythonsource(filename))
260
David Scherer7aced172000-08-15 01:13:23 +0000261 self.load_extensions()
262
263 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
David Scherer7aced172000-08-15 01:13:23 +0000299 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000300 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000301 if macosxSupport.runningAsOSXApp():
302 # Insert some padding to avoid obscuring some of the statusbar
303 # by the resize widget.
304 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000305 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
306 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
307 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000308 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
309 self.text.event_add("<<set-line-and-column>>",
310 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000311 self.text.after_idle(self.set_line_and_column)
312
313 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000314 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000315 self.status_bar.set_label('column', 'Col: %s' % column)
316 self.status_bar.set_label('line', 'Ln: %s' % line)
317
David Scherer7aced172000-08-15 01:13:23 +0000318 menu_specs = [
319 ("file", "_File"),
320 ("edit", "_Edit"),
321 ("format", "F_ormat"),
322 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000323 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000324 ("windows", "_Windows"),
325 ("help", "_Help"),
326 ]
327
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000328 if macosxSupport.runningAsOSXApp():
329 del menu_specs[-3]
330 menu_specs[-2] = ("windows", "_Window")
331
332
David Scherer7aced172000-08-15 01:13:23 +0000333 def createmenubar(self):
334 mbar = self.menubar
335 self.menudict = menudict = {}
336 for name, label in self.menu_specs:
337 underline, label = prepstr(label)
338 menudict[name] = menu = Menu(mbar, name=name)
339 mbar.add_cascade(label=label, menu=menu, underline=underline)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000340
341 if sys.platform == 'darwin' and '.framework' in sys.executable:
342 # Insert the application menu
343 menudict['application'] = menu = Menu(mbar, name='apple')
344 mbar.add_cascade(label='IDLE', menu=menu)
345
David Scherer7aced172000-08-15 01:13:23 +0000346 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000347 self.base_helpmenu_length = self.menudict['help'].index(END)
348 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000349
350 def postwindowsmenu(self):
351 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000352 menu = self.menudict['windows']
353 end = menu.index("end")
354 if end is None:
355 end = -1
356 if end > self.wmenu_end:
357 menu.delete(self.wmenu_end+1, end)
358 WindowList.add_windows_to_menu(menu)
359
360 rmenu = None
361
362 def right_menu_event(self, event):
363 self.text.tag_remove("sel", "1.0", "end")
364 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
365 if not self.rmenu:
366 self.make_rmenu()
367 rmenu = self.rmenu
368 self.event = event
369 iswin = sys.platform[:3] == 'win'
370 if iswin:
371 self.text.config(cursor="arrow")
372 rmenu.tk_popup(event.x_root, event.y_root)
373 if iswin:
374 self.text.config(cursor="ibeam")
375
376 rmenu_specs = [
377 # ("Label", "<<virtual-event>>"), ...
378 ("Close", "<<close-window>>"), # Example
379 ]
380
381 def make_rmenu(self):
382 rmenu = Menu(self.text, tearoff=0)
383 for label, eventname in self.rmenu_specs:
384 def command(text=self.text, eventname=eventname):
385 text.event_generate(eventname)
386 rmenu.add_command(label=label, command=command)
387 self.rmenu = rmenu
388
389 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000390 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000391
Steven M. Gava3b55a892001-11-21 05:56:26 +0000392 def config_dialog(self, event=None):
393 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000394
David Scherer7aced172000-08-15 01:13:23 +0000395 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000396 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000397 textView.TextViewer(self.top,'Help',fn)
398
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000399 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000400 if sys.platform[:3] == 'win':
Steven M. Gava931625d2002-04-22 00:38:26 +0000401 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000402 else:
403 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000404 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000405
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000406 def cut(self,event):
407 self.text.event_generate("<<Cut>>")
408 return "break"
409
410 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000411 if not self.text.tag_ranges("sel"):
412 # There is no selection, so do nothing and maybe interrupt.
413 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000414 self.text.event_generate("<<Copy>>")
415 return "break"
416
417 def paste(self,event):
418 self.text.event_generate("<<Paste>>")
419 return "break"
420
David Scherer7aced172000-08-15 01:13:23 +0000421 def select_all(self, event=None):
422 self.text.tag_add("sel", "1.0", "end-1c")
423 self.text.mark_set("insert", "1.0")
424 self.text.see("insert")
425 return "break"
426
427 def remove_selection(self, event=None):
428 self.text.tag_remove("sel", "1.0", "end")
429 self.text.see("insert")
430
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000431 def move_at_edge_if_selection(self, edge_index):
432 """Cursor move begins at start or end of selection
433
434 When a left/right cursor key is pressed create and return to Tkinter a
435 function which causes a cursor move from the associated edge of the
436 selection.
437
438 """
439 self_text_index = self.text.index
440 self_text_mark_set = self.text.mark_set
441 edges_table = ("sel.first+1c", "sel.last-1c")
442 def move_at_edge(event):
443 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
444 try:
445 self_text_index("sel.first")
446 self_text_mark_set("insert", edges_table[edge_index])
447 except TclError:
448 pass
449 return move_at_edge
450
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000451 def del_word_left(self, event):
452 self.text.event_generate('<Meta-Delete>')
453 return "break"
454
455 def del_word_right(self, event):
456 self.text.event_generate('<Meta-d>')
457 return "break"
458
Steven M. Gavac5976402002-01-04 03:06:08 +0000459 def find_event(self, event):
460 SearchDialog.find(self.text)
461 return "break"
462
463 def find_again_event(self, event):
464 SearchDialog.find_again(self.text)
465 return "break"
466
467 def find_selection_event(self, event):
468 SearchDialog.find_selection(self.text)
469 return "break"
470
471 def find_in_files_event(self, event):
472 GrepDialog.grep(self.text, self.io, self.flist)
473 return "break"
474
475 def replace_event(self, event):
476 ReplaceDialog.replace(self.text)
477 return "break"
478
479 def goto_line_event(self, event):
480 text = self.text
481 lineno = tkSimpleDialog.askinteger("Goto",
482 "Go to line number:",parent=text)
483 if lineno is None:
484 return "break"
485 if lineno <= 0:
486 text.bell()
487 return "break"
488 text.mark_set("insert", "%d.0" % lineno)
489 text.see("insert")
490
David Scherer7aced172000-08-15 01:13:23 +0000491 def open_module(self, event=None):
492 # XXX Shouldn't this be in IOBinding or in FileList?
493 try:
494 name = self.text.get("sel.first", "sel.last")
495 except TclError:
496 name = ""
497 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000498 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000499 name = tkSimpleDialog.askstring("Module",
500 "Enter the name of a Python module\n"
501 "to search on sys.path and open:",
502 parent=self.text, initialvalue=name)
503 if name:
504 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000505 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000506 return
David Scherer7aced172000-08-15 01:13:23 +0000507 # XXX Ought to insert current file's directory in front of path
508 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000509 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000510 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000511 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
512 return
513 if type != imp.PY_SOURCE:
514 tkMessageBox.showerror("Unsupported type",
515 "%s is not a source module" % name, parent=self.text)
516 return
517 if f:
518 f.close()
519 if self.flist:
520 self.flist.open(file)
521 else:
522 self.io.loadfile(file)
523
524 def open_class_browser(self, event=None):
525 filename = self.io.filename
526 if not filename:
527 tkMessageBox.showerror(
528 "No filename",
529 "This buffer has no associated filename",
530 master=self.text)
531 self.text.focus_set()
532 return None
533 head, tail = os.path.split(filename)
534 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000535 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000536 ClassBrowser.ClassBrowser(self.flist, base, [head])
537
538 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000539 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000540 PathBrowser.PathBrowser(self.flist)
541
542 def gotoline(self, lineno):
543 if lineno is not None and lineno > 0:
544 self.text.mark_set("insert", "%d.0" % lineno)
545 self.text.tag_remove("sel", "1.0", "end")
546 self.text.tag_add("sel", "insert", "insert +1l")
547 self.center()
548
549 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000550 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000551 return True
David Scherer7aced172000-08-15 01:13:23 +0000552 base, ext = os.path.splitext(os.path.basename(filename))
553 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000554 return True
David Scherer7aced172000-08-15 01:13:23 +0000555 try:
556 f = open(filename)
557 line = f.readline()
558 f.close()
559 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000560 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000561 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000562
563 def close_hook(self):
564 if self.flist:
565 self.flist.close_edit(self)
566
567 def set_close_hook(self, close_hook):
568 self.close_hook = close_hook
569
570 def filename_change_hook(self):
571 if self.flist:
572 self.flist.filename_changed_edit(self)
573 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000574 self.top.update_windowlist_registry(self)
David Scherer7aced172000-08-15 01:13:23 +0000575 if self.ispythonsource(self.io.filename):
576 self.addcolorizer()
577 else:
578 self.rmcolorizer()
579
580 def addcolorizer(self):
581 if self.color:
582 return
David Scherer7aced172000-08-15 01:13:23 +0000583 self.per.removefilter(self.undo)
584 self.color = self.ColorDelegator()
585 self.per.insertfilter(self.color)
586 self.per.insertfilter(self.undo)
587
588 def rmcolorizer(self):
589 if not self.color:
590 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000591 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000592 self.per.removefilter(self.undo)
593 self.per.removefilter(self.color)
594 self.color = None
595 self.per.insertfilter(self.undo)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000596
Steven M. Gavab77d3432002-03-02 07:16:21 +0000597 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000598 "Update the colour theme if it is changed"
599 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000600 if self.color:
601 self.color = self.ColorDelegator()
602 self.per.insertfilter(self.color)
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000603 theme = idleConf.GetOption('main','Theme','name')
604 self.text.config(idleConf.GetHighlight(theme, "normal"))
David Scherer7aced172000-08-15 01:13:23 +0000605
Guido van Rossum33d26892007-08-05 15:29:28 +0000606 IDENTCHARS = string.ascii_letters + string.digits + "_"
607
608 def colorize_syntax_error(self, text, pos):
609 text.tag_add("ERROR", pos)
610 char = text.get(pos)
611 if char and char in self.IDENTCHARS:
612 text.tag_add("ERROR", pos + " wordstart", pos)
613 if '\n' == text.get(pos): # error at line end
614 text.mark_set("insert", pos)
615 else:
616 text.mark_set("insert", pos + "+1c")
617 text.see(pos)
618
Steven M. Gavab1585412002-03-12 00:21:56 +0000619 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000620 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000621 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000622 fontWeight='normal'
623 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
624 fontWeight='bold'
625 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
626 idleConf.GetOption('main','EditorWindow','font-size'),
627 fontWeight))
628
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000629 def RemoveKeybindings(self):
630 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000631 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000632 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000633 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000634 self.text.event_delete(event, *keylist)
635 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000636 xkeydefs = idleConf.GetExtensionBindings(extensionName)
637 if xkeydefs:
638 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000639 self.text.event_delete(event, *keylist)
640
641 def ApplyKeybindings(self):
642 "Update the keybindings after they are changed"
643 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000644 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000645 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000646 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000647 xkeydefs = idleConf.GetExtensionBindings(extensionName)
648 if xkeydefs:
649 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000650 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000651 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000652 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000653 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000654 for item in menu[1]:
655 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000656 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000657 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000658 menu = self.menudict[menubarItem]
659 end = menu.index(END) + 1
660 for index in range(0, end):
661 if menu.type(index) == 'command':
662 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000663 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000664 itemName = menu.entrycget(index, 'label')
665 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000666 if menubarItem in menuEventDict:
667 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000668 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000669 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000670 accel = get_accelerator(keydefs, event)
671 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000672
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000673 def set_notabs_indentwidth(self):
674 "Update the indentwidth if changed and not using tabs in this window"
675 # Called from configDialog.py
676 if not self.usetabs:
677 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
678 type='int')
679
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000680 def reset_help_menu_entries(self):
681 "Update the additional help entries on the Help menu"
682 help_list = idleConf.GetAllExtraHelpSourcesList()
683 helpmenu = self.menudict['help']
684 # first delete the extra help entries, if any
685 helpmenu_length = helpmenu.index(END)
686 if helpmenu_length > self.base_helpmenu_length:
687 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
688 # then rebuild them
689 if help_list:
690 helpmenu.add_separator()
691 for entry in help_list:
692 cmd = self.__extra_help_callback(entry[1])
693 helpmenu.add_command(label=entry[0], command=cmd)
694 # and update the menu dictionary
695 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000696
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000697 def __extra_help_callback(self, helpfile):
698 "Create a callback with the helpfile value frozen at definition time"
699 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000700 if not helpfile.startswith(('www', 'http')):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000701 url = os.path.normpath(helpfile)
702 if sys.platform[:3] == 'win':
703 os.startfile(helpfile)
704 else:
705 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000706 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000707
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000708 def update_recent_files_list(self, new_file=None):
709 "Load and update the recent files list and menus"
710 rf_list = []
711 if os.path.exists(self.recent_files_path):
712 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000713 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000714 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000715 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000716 rf_list_file.close()
717 if new_file:
718 new_file = os.path.abspath(new_file) + '\n'
719 if new_file in rf_list:
720 rf_list.remove(new_file) # move to top
721 rf_list.insert(0, new_file)
722 # clean and save the recent files list
723 bad_paths = []
724 for path in rf_list:
725 if '\0' in path or not os.path.exists(path[0:-1]):
726 bad_paths.append(path)
727 rf_list = [path for path in rf_list if path not in bad_paths]
728 ulchars = "1234567890ABCDEFGHIJK"
729 rf_list = rf_list[0:len(ulchars)]
730 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000731 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000732 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000733 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000734 rf_file.close()
735 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000736 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000737 menu = instance.recent_files_menu
738 menu.delete(1, END) # clear, and rebuild:
739 for i, file in zip(count(), rf_list):
740 file_name = file[0:-1] # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000741 # make unicode string to display non-ASCII chars correctly
742 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000743 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000744 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000745 command=callback,
746 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000747
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000748 def __recent_file_callback(self, file_name):
749 def open_recent_file(fn_closure=file_name):
750 self.io.open(editFile=fn_closure)
751 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000752
David Scherer7aced172000-08-15 01:13:23 +0000753 def saved_change_hook(self):
754 short = self.short_title()
755 long = self.long_title()
756 if short and long:
757 title = short + " - " + long
758 elif short:
759 title = short
760 elif long:
761 title = long
762 else:
763 title = "Untitled"
764 icon = short or long or title
765 if not self.get_saved():
766 title = "*%s*" % title
767 icon = "*%s" % icon
768 self.top.wm_title(title)
769 self.top.wm_iconname(icon)
770
771 def get_saved(self):
772 return self.undo.get_saved()
773
774 def set_saved(self, flag):
775 self.undo.set_saved(flag)
776
777 def reset_undo(self):
778 self.undo.reset_undo()
779
780 def short_title(self):
781 filename = self.io.filename
782 if filename:
783 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000784 # return unicode string to display non-ASCII chars correctly
785 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000786
787 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000788 # return unicode string to display non-ASCII chars correctly
789 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000790
791 def center_insert_event(self, event):
792 self.center()
793
794 def center(self, mark="insert"):
795 text = self.text
796 top, bot = self.getwindowlines()
797 lineno = self.getlineno(mark)
798 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000799 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000800 text.yview(float(newtop))
801
802 def getwindowlines(self):
803 text = self.text
804 top = self.getlineno("@0,0")
805 bot = self.getlineno("@0,65535")
806 if top == bot and text.winfo_height() == 1:
807 # Geometry manager hasn't run yet
808 height = int(text['height'])
809 bot = top + height - 1
810 return top, bot
811
812 def getlineno(self, mark="insert"):
813 text = self.text
814 return int(float(text.index(mark)))
815
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000816 def get_geometry(self):
817 "Return (width, height, x, y)"
818 geom = self.top.wm_geometry()
819 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000820 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000821
David Scherer7aced172000-08-15 01:13:23 +0000822 def close_event(self, event):
823 self.close()
824
825 def maybesave(self):
826 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000827 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000828 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000829 self.top.deiconify()
830 self.top.lower()
831 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000832 return self.io.maybesave()
833
834 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000835 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000836 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000837 self._close()
838 return reply
839
840 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000841 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000842 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000843 WindowList.unregister_callback(self.postwindowsmenu)
844 if self.close_hook:
845 self.close_hook()
846 self.flist = None
847 colorizing = 0
848 self.unload_extensions()
849 self.io.close(); self.io = None
850 self.undo = None # XXX
851 if self.color:
852 colorizing = self.color.colorizing
853 doh = colorizing and self.top
854 self.color.close(doh) # Cancel colorization
855 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000856 self.tkinter_vars = None
David Scherer7aced172000-08-15 01:13:23 +0000857 self.per.close(); self.per = None
858 if not colorizing:
859 self.top.destroy()
860
861 def load_extensions(self):
862 self.extensions = {}
863 self.load_standard_extensions()
864
865 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000866 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000867 if hasattr(ins, "close"):
868 ins.close()
869 self.extensions = {}
870
871 def load_standard_extensions(self):
872 for name in self.get_standard_extension_names():
873 try:
874 self.load_extension(name)
875 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000876 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000877 traceback.print_exc()
878
879 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000880 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000881
882 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000883 try:
884 mod = __import__(name, globals(), locals(), [])
885 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000886 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000887 raise
David Scherer7aced172000-08-15 01:13:23 +0000888 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000889 keydefs = idleConf.GetExtensionBindings(name)
890 if hasattr(cls, "menudefs"):
891 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000892 ins = cls(self)
893 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000894 if keydefs:
895 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000896 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000897 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000898 while methodname[:1] == '<':
899 methodname = methodname[1:]
900 while methodname[-1:] == '>':
901 methodname = methodname[:-1]
902 methodname = methodname + "_event"
903 if hasattr(ins, methodname):
904 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000905
906 def apply_bindings(self, keydefs=None):
907 if keydefs is None:
908 keydefs = self.Bindings.default_keydefs
909 text = self.text
910 text.keydefs = keydefs
911 for event, keylist in keydefs.items():
912 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000913 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000914
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000915 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000916 """Add appropriate entries to the menus and submenus
917
918 Menus that are absent or None in self.menudict are ignored.
919 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000920 if menudefs is None:
921 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000922 if keydefs is None:
923 keydefs = self.Bindings.default_keydefs
924 menudict = self.menudict
925 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000926 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000927 menu = menudict.get(mname)
928 if not menu:
929 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000930 for entry in entrylist:
931 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000932 menu.add_separator()
933 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000934 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000935 checkbutton = (label[:1] == '!')
936 if checkbutton:
937 label = label[1:]
938 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000939 accelerator = get_accelerator(keydefs, eventname)
940 def command(text=text, eventname=eventname):
941 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000942 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000943 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000944 menu.add_checkbutton(label=label, underline=underline,
945 command=command, accelerator=accelerator,
946 variable=var)
947 else:
948 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000949 command=command,
950 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000951
952 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000953 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +0000954 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000955 value = var.get()
956 return value
957 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000958 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +0000959
960 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000961 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +0000962 if var:
963 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000964 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000965 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +0000966
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000967 def get_var_obj(self, name, vartype=None):
968 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +0000969 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000970 # create a Tkinter variable object with self.text as master:
971 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000972 return var
973
974 # Tk implementations of "virtual text methods" -- each platform
975 # reusing IDLE's support code needs to define these for its GUI's
976 # flavor of widget.
977
978 # Is character at text_index in a Python string? Return 0 for
979 # "guaranteed no", true for anything else. This info is expensive
980 # to compute ab initio, but is probably already known by the
981 # platform's colorizer.
982
983 def is_char_in_string(self, text_index):
984 if self.color:
985 # Return true iff colorizer hasn't (re)gotten this far
986 # yet, or the character is tagged as being in a string
987 return self.text.tag_prevrange("TODO", text_index) or \
988 "STRING" in self.text.tag_names(text_index)
989 else:
990 # The colorizer is missing: assume the worst
991 return 1
992
993 # If a selection is defined in the text widget, return (start,
994 # end) as Tkinter text indices, otherwise return (None, None)
995 def get_selection_indices(self):
996 try:
997 first = self.text.index("sel.first")
998 last = self.text.index("sel.last")
999 return first, last
1000 except TclError:
1001 return None, None
1002
1003 # Return the text widget's current view of what a tab stop means
1004 # (equivalent width in spaces).
1005
1006 def get_tabwidth(self):
1007 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1008 return int(current)
1009
1010 # Set the text widget's current view of what a tab stop means.
1011
1012 def set_tabwidth(self, newtabwidth):
1013 text = self.text
1014 if self.get_tabwidth() != newtabwidth:
1015 pixels = text.tk.call("font", "measure", text["font"],
1016 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001017 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001018 text.configure(tabs=pixels)
1019
Guido van Rossum33d26892007-08-05 15:29:28 +00001020### begin autoindent code ### (configuration was moved to beginning of class)
1021
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001022 # If ispythonsource and guess are true, guess a good value for
1023 # indentwidth based on file content (if possible), and if
1024 # indentwidth != tabwidth set usetabs false.
1025 # In any case, adjust the Text widget's view of what a tab
1026 # character means.
1027
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001028 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001029 if guess and ispythonsource:
1030 i = self.guess_indent()
1031 if 2 <= i <= 8:
1032 self.indentwidth = i
1033 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001034 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001035 self.set_tabwidth(self.tabwidth)
1036
1037 def smart_backspace_event(self, event):
1038 text = self.text
1039 first, last = self.get_selection_indices()
1040 if first and last:
1041 text.delete(first, last)
1042 text.mark_set("insert", first)
1043 return "break"
1044 # Delete whitespace left, until hitting a real char or closest
1045 # preceding virtual tab stop.
1046 chars = text.get("insert linestart", "insert")
1047 if chars == '':
1048 if text.compare("insert", ">", "1.0"):
1049 # easy: delete preceding newline
1050 text.delete("insert-1c")
1051 else:
1052 text.bell() # at start of buffer
1053 return "break"
1054 if chars[-1] not in " \t":
1055 # easy: delete preceding real char
1056 text.delete("insert-1c")
1057 return "break"
1058 # Ick. It may require *inserting* spaces if we back up over a
1059 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001060 tabwidth = self.tabwidth
1061 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001062 assert have > 0
1063 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001064 # Debug prompt is multilined....
1065 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001066 ncharsdeleted = 0
1067 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001068 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001069 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001070 chars = chars[:-1]
1071 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001072 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001073 if have <= want or chars[-1] not in " \t":
1074 break
1075 text.undo_block_start()
1076 text.delete("insert-%dc" % ncharsdeleted, "insert")
1077 if have < want:
1078 text.insert("insert", ' ' * (want - have))
1079 text.undo_block_stop()
1080 return "break"
1081
1082 def smart_indent_event(self, event):
1083 # if intraline selection:
1084 # delete it
1085 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001086 # do indent-region
1087 # else:
1088 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001089 text = self.text
1090 first, last = self.get_selection_indices()
1091 text.undo_block_start()
1092 try:
1093 if first and last:
1094 if index2line(first) != index2line(last):
1095 return self.indent_region_event(event)
1096 text.delete(first, last)
1097 text.mark_set("insert", first)
1098 prefix = text.get("insert linestart", "insert")
1099 raw, effective = classifyws(prefix, self.tabwidth)
1100 if raw == len(prefix):
1101 # only whitespace to the left
1102 self.reindent_to(effective + self.indentwidth)
1103 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001104 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001105 if self.usetabs:
1106 pad = '\t'
1107 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001108 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001109 n = self.indentwidth
1110 pad = ' ' * (n - effective % n)
1111 text.insert("insert", pad)
1112 text.see("insert")
1113 return "break"
1114 finally:
1115 text.undo_block_stop()
1116
1117 def newline_and_indent_event(self, event):
1118 text = self.text
1119 first, last = self.get_selection_indices()
1120 text.undo_block_start()
1121 try:
1122 if first and last:
1123 text.delete(first, last)
1124 text.mark_set("insert", first)
1125 line = text.get("insert linestart", "insert")
1126 i, n = 0, len(line)
1127 while i < n and line[i] in " \t":
1128 i = i+1
1129 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001130 # the cursor is in or at leading indentation in a continuation
1131 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001132 text.insert("insert linestart", '\n')
1133 return "break"
1134 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001135 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001136 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001137 last_line_of_prompt = sys.ps1.split('\n')[-1]
1138 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001139 line = line[:-1]
1140 i = i+1
1141 if i:
1142 text.delete("insert - %d chars" % i, "insert")
1143 # strip whitespace after insert point
1144 while text.get("insert") in " \t":
1145 text.delete("insert")
1146 # start new line
1147 text.insert("insert", '\n')
1148
1149 # adjust indentation for continuations and block
1150 # open/close first need to find the last stmt
1151 lno = index2line(text.index('insert'))
1152 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001153 if not self.context_use_ps1:
1154 for context in self.num_context_lines:
1155 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001156 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001157 rawtext = text.get(startatindex, "insert")
1158 y.set_str(rawtext)
1159 bod = y.find_good_parse_start(
1160 self.context_use_ps1,
1161 self._build_char_in_string_func(startatindex))
1162 if bod is not None or startat == 1:
1163 break
1164 y.set_lo(bod or 0)
1165 else:
1166 r = text.tag_prevrange("console", "insert")
1167 if r:
1168 startatindex = r[1]
1169 else:
1170 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001171 rawtext = text.get(startatindex, "insert")
1172 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001173 y.set_lo(0)
1174
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001175 c = y.get_continuation_type()
1176 if c != PyParse.C_NONE:
1177 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001178 if c == PyParse.C_STRING_FIRST_LINE:
1179 # after the first line of a string; do not indent at all
1180 pass
1181 elif c == PyParse.C_STRING_NEXT_LINES:
1182 # inside a string which started before this line;
1183 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001184 text.insert("insert", indent)
1185 elif c == PyParse.C_BRACKET:
1186 # line up with the first (if any) element of the
1187 # last open bracket structure; else indent one
1188 # level beyond the indent of the line with the
1189 # last open bracket
1190 self.reindent_to(y.compute_bracket_indent())
1191 elif c == PyParse.C_BACKSLASH:
1192 # if more than one line in this stmt already, just
1193 # mimic the current indent; else if initial line
1194 # has a start on an assignment stmt, indent to
1195 # beyond leftmost =; else to beyond first chunk of
1196 # non-whitespace on initial line
1197 if y.get_num_lines_in_stmt() > 1:
1198 text.insert("insert", indent)
1199 else:
1200 self.reindent_to(y.compute_backslash_indent())
1201 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001202 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001203 return "break"
1204
1205 # This line starts a brand new stmt; indent relative to
1206 # indentation of initial line of closest preceding
1207 # interesting stmt.
1208 indent = y.get_base_indent_string()
1209 text.insert("insert", indent)
1210 if y.is_block_opener():
1211 self.smart_indent_event(event)
1212 elif indent and y.is_block_closer():
1213 self.smart_backspace_event(event)
1214 return "break"
1215 finally:
1216 text.see("insert")
1217 text.undo_block_stop()
1218
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001219 # Our editwin provides a is_char_in_string function that works
1220 # with a Tk text index, but PyParse only knows about offsets into
1221 # a string. This builds a function for PyParse that accepts an
1222 # offset.
1223
1224 def _build_char_in_string_func(self, startindex):
1225 def inner(offset, _startindex=startindex,
1226 _icis=self.is_char_in_string):
1227 return _icis(_startindex + "+%dc" % offset)
1228 return inner
1229
1230 def indent_region_event(self, event):
1231 head, tail, chars, lines = self.get_region()
1232 for pos in range(len(lines)):
1233 line = lines[pos]
1234 if line:
1235 raw, effective = classifyws(line, self.tabwidth)
1236 effective = effective + self.indentwidth
1237 lines[pos] = self._make_blanks(effective) + line[raw:]
1238 self.set_region(head, tail, chars, lines)
1239 return "break"
1240
1241 def dedent_region_event(self, event):
1242 head, tail, chars, lines = self.get_region()
1243 for pos in range(len(lines)):
1244 line = lines[pos]
1245 if line:
1246 raw, effective = classifyws(line, self.tabwidth)
1247 effective = max(effective - self.indentwidth, 0)
1248 lines[pos] = self._make_blanks(effective) + line[raw:]
1249 self.set_region(head, tail, chars, lines)
1250 return "break"
1251
1252 def comment_region_event(self, event):
1253 head, tail, chars, lines = self.get_region()
1254 for pos in range(len(lines) - 1):
1255 line = lines[pos]
1256 lines[pos] = '##' + line
1257 self.set_region(head, tail, chars, lines)
1258
1259 def uncomment_region_event(self, event):
1260 head, tail, chars, lines = self.get_region()
1261 for pos in range(len(lines)):
1262 line = lines[pos]
1263 if not line:
1264 continue
1265 if line[:2] == '##':
1266 line = line[2:]
1267 elif line[:1] == '#':
1268 line = line[1:]
1269 lines[pos] = line
1270 self.set_region(head, tail, chars, lines)
1271
1272 def tabify_region_event(self, event):
1273 head, tail, chars, lines = self.get_region()
1274 tabwidth = self._asktabwidth()
1275 for pos in range(len(lines)):
1276 line = lines[pos]
1277 if line:
1278 raw, effective = classifyws(line, tabwidth)
1279 ntabs, nspaces = divmod(effective, tabwidth)
1280 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1281 self.set_region(head, tail, chars, lines)
1282
1283 def untabify_region_event(self, event):
1284 head, tail, chars, lines = self.get_region()
1285 tabwidth = self._asktabwidth()
1286 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001287 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001288 self.set_region(head, tail, chars, lines)
1289
1290 def toggle_tabs_event(self, event):
1291 if self.askyesno(
1292 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001293 "Turn tabs " + ("on", "off")[self.usetabs] +
1294 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001295 ("will be", "remains at")[self.usetabs] + " 8." +
1296 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001297 parent=self.text):
1298 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001299 # Try to prevent inconsistent indentation.
1300 # User must change indent width manually after using tabs.
1301 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001302 return "break"
1303
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001304 # XXX this isn't bound to anything -- see tabwidth comments
1305## def change_tabwidth_event(self, event):
1306## new = self._asktabwidth()
1307## if new != self.tabwidth:
1308## self.tabwidth = new
1309## self.set_indentation_params(0, guess=0)
1310## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001311
1312 def change_indentwidth_event(self, event):
1313 new = self.askinteger(
1314 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001315 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001316 parent=self.text,
1317 initialvalue=self.indentwidth,
1318 minvalue=2,
1319 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001320 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001321 self.indentwidth = new
1322 return "break"
1323
1324 def get_region(self):
1325 text = self.text
1326 first, last = self.get_selection_indices()
1327 if first and last:
1328 head = text.index(first + " linestart")
1329 tail = text.index(last + "-1c lineend +1c")
1330 else:
1331 head = text.index("insert linestart")
1332 tail = text.index("insert lineend +1c")
1333 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001334 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001335 return head, tail, chars, lines
1336
1337 def set_region(self, head, tail, chars, lines):
1338 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001339 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001340 if newchars == chars:
1341 text.bell()
1342 return
1343 text.tag_remove("sel", "1.0", "end")
1344 text.mark_set("insert", head)
1345 text.undo_block_start()
1346 text.delete(head, tail)
1347 text.insert(head, newchars)
1348 text.undo_block_stop()
1349 text.tag_add("sel", head, "insert")
1350
1351 # Make string that displays as n leading blanks.
1352
1353 def _make_blanks(self, n):
1354 if self.usetabs:
1355 ntabs, nspaces = divmod(n, self.tabwidth)
1356 return '\t' * ntabs + ' ' * nspaces
1357 else:
1358 return ' ' * n
1359
1360 # Delete from beginning of line to insert point, then reinsert
1361 # column logical (meaning use tabs if appropriate) spaces.
1362
1363 def reindent_to(self, column):
1364 text = self.text
1365 text.undo_block_start()
1366 if text.compare("insert linestart", "!=", "insert"):
1367 text.delete("insert linestart", "insert")
1368 if column:
1369 text.insert("insert", self._make_blanks(column))
1370 text.undo_block_stop()
1371
1372 def _asktabwidth(self):
1373 return self.askinteger(
1374 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001375 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001376 parent=self.text,
1377 initialvalue=self.indentwidth,
1378 minvalue=2,
1379 maxvalue=16) or self.tabwidth
1380
1381 # Guess indentwidth from text content.
1382 # Return guessed indentwidth. This should not be believed unless
1383 # it's in a reasonable range (e.g., it will be 0 if no indented
1384 # blocks are found).
1385
1386 def guess_indent(self):
1387 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1388 if opener and indented:
1389 raw, indentsmall = classifyws(opener, self.tabwidth)
1390 raw, indentlarge = classifyws(indented, self.tabwidth)
1391 else:
1392 indentsmall = indentlarge = 0
1393 return indentlarge - indentsmall
1394
1395# "line.col" -> line, as an int
1396def index2line(index):
1397 return int(float(index))
1398
1399# Look at the leading whitespace in s.
1400# Return pair (# of leading ws characters,
1401# effective # of leading blanks after expanding
1402# tabs to width tabwidth)
1403
1404def classifyws(s, tabwidth):
1405 raw = effective = 0
1406 for ch in s:
1407 if ch == ' ':
1408 raw = raw + 1
1409 effective = effective + 1
1410 elif ch == '\t':
1411 raw = raw + 1
1412 effective = (effective // tabwidth + 1) * tabwidth
1413 else:
1414 break
1415 return raw, effective
1416
1417import tokenize
1418_tokenize = tokenize
1419del tokenize
1420
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001421class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001422
1423 # .run() chews over the Text widget, looking for a block opener
1424 # and the stmt following it. Returns a pair,
1425 # (line containing block opener, line containing stmt)
1426 # Either or both may be None.
1427
1428 def __init__(self, text, tabwidth):
1429 self.text = text
1430 self.tabwidth = tabwidth
1431 self.i = self.finished = 0
1432 self.blkopenline = self.indentedline = None
1433
1434 def readline(self):
1435 if self.finished:
1436 return ""
1437 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001438 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001439 if self.text.compare(mark, ">=", "end"):
1440 return ""
1441 return self.text.get(mark, mark + " lineend+1c")
1442
1443 def tokeneater(self, type, token, start, end, line,
1444 INDENT=_tokenize.INDENT,
1445 NAME=_tokenize.NAME,
1446 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1447 if self.finished:
1448 pass
1449 elif type == NAME and token in OPENERS:
1450 self.blkopenline = line
1451 elif type == INDENT and self.blkopenline:
1452 self.indentedline = line
1453 self.finished = 1
1454
1455 def run(self):
1456 save_tabsize = _tokenize.tabsize
1457 _tokenize.tabsize = self.tabwidth
1458 try:
1459 try:
1460 _tokenize.tokenize(self.readline, self.tokeneater)
1461 except _tokenize.TokenError:
1462 # since we cut off the tokenizer early, we can trigger
1463 # spurious errors
1464 pass
1465 finally:
1466 _tokenize.tabsize = save_tabsize
1467 return self.blkopenline, self.indentedline
1468
1469### end autoindent code ###
1470
David Scherer7aced172000-08-15 01:13:23 +00001471def prepstr(s):
1472 # Helper to extract the underscore from a string, e.g.
1473 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001474 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001475 if i >= 0:
1476 s = s[:i] + s[i+1:]
1477 return i, s
1478
1479
1480keynames = {
1481 'bracketleft': '[',
1482 'bracketright': ']',
1483 'slash': '/',
1484}
1485
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001486def get_accelerator(keydefs, eventname):
1487 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001488 if not keylist:
1489 return ""
1490 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001491 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001492 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1493 s = re.sub("Key-", "", s)
1494 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1495 s = re.sub("Control-", "Ctrl-", s)
1496 s = re.sub("-", "+", s)
1497 s = re.sub("><", " ", s)
1498 s = re.sub("<", "", s)
1499 s = re.sub(">", "", s)
1500 return s
1501
1502
1503def fixwordbreaks(root):
1504 # Make sure that Tk's double-click and next/previous word
1505 # operations use our definition of a word (i.e. an identifier)
1506 tk = root.tk
1507 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1508 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1509 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1510
1511
1512def test():
1513 root = Tk()
1514 fixwordbreaks(root)
1515 root.withdraw()
1516 if sys.argv[1:]:
1517 filename = sys.argv[1]
1518 else:
1519 filename = None
1520 edit = EditorWindow(root=root, filename=filename)
1521 edit.set_close_hook(root.quit)
1522 root.mainloop()
1523 root.destroy()
1524
1525if __name__ == '__main__':
1526 test()