blob: a0a7b47267e8321c33d663cbb08217878a830bc9 [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:
Kurt B. Kaiserd953f6e2007-08-31 21:40:34 +000085 EditorWindow.help_url = "http://docs.python.org/dev/3.0/"
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
David Scherer7aced172000-08-15 01:13:23 +0000227 self.per = per = self.Percolator(text)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000228 self.color = None
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000229 self.undo = undo = self.UndoDelegator()
230 per.insertfilter(undo)
231 text.undo_block_start = undo.undo_block_start
232 text.undo_block_stop = undo.undo_block_stop
233 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000234 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000235 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000236 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000237 self.good_load = False
238 self.set_indentation_params(False)
David Scherer7aced172000-08-15 01:13:23 +0000239 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000240 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000241 if io.loadfile(filename):
242 self.good_load = True
243 is_py_src = self.ispythonsource(filename)
244 self.set_indentation_params(is_py_src)
245 if is_py_src:
246 self.color = color = self.ColorDelegator()
247 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000248 else:
249 io.set_filename(filename)
David Scherer7aced172000-08-15 01:13:23 +0000250 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000251 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000252 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000253 menu = self.menudict.get('windows')
254 if menu:
255 end = menu.index("end")
256 if end is None:
257 end = -1
258 if end >= 0:
259 menu.add_separator()
260 end = end + 1
261 self.wmenu_end = end
262 WindowList.register_callback(self.postwindowsmenu)
263
264 # Some abstractions so IDLE extensions are cross-IDE
265 self.askyesno = tkMessageBox.askyesno
266 self.askinteger = tkSimpleDialog.askinteger
267 self.showerror = tkMessageBox.showerror
268
Martin v. Löwis307021f2005-11-27 16:59:04 +0000269 def _filename_to_unicode(self, filename):
270 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000271 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000272 return filename
273 else:
274 try:
275 return filename.decode(self.filesystemencoding)
276 except UnicodeDecodeError:
277 # XXX
278 try:
279 return filename.decode(self.encoding)
280 except UnicodeDecodeError:
281 # byte-to-byte conversion
282 return filename.decode('iso8859-1')
283
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000284 def new_callback(self, event):
285 dirname, basename = self.io.defaultfilename()
286 self.flist.new(dirname)
287 return "break"
288
David Scherer7aced172000-08-15 01:13:23 +0000289 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000290 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000291 if macosxSupport.runningAsOSXApp():
292 # Insert some padding to avoid obscuring some of the statusbar
293 # by the resize widget.
294 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000295 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
296 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
297 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000298 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
299 self.text.event_add("<<set-line-and-column>>",
300 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000301 self.text.after_idle(self.set_line_and_column)
302
303 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000304 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000305 self.status_bar.set_label('column', 'Col: %s' % column)
306 self.status_bar.set_label('line', 'Ln: %s' % line)
307
David Scherer7aced172000-08-15 01:13:23 +0000308 menu_specs = [
309 ("file", "_File"),
310 ("edit", "_Edit"),
311 ("format", "F_ormat"),
312 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000313 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000314 ("windows", "_Windows"),
315 ("help", "_Help"),
316 ]
317
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000318 if macosxSupport.runningAsOSXApp():
319 del menu_specs[-3]
320 menu_specs[-2] = ("windows", "_Window")
321
322
David Scherer7aced172000-08-15 01:13:23 +0000323 def createmenubar(self):
324 mbar = self.menubar
325 self.menudict = menudict = {}
326 for name, label in self.menu_specs:
327 underline, label = prepstr(label)
328 menudict[name] = menu = Menu(mbar, name=name)
329 mbar.add_cascade(label=label, menu=menu, underline=underline)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000330 if sys.platform == 'darwin' and '.framework' in sys.executable:
331 # Insert the application menu
332 menudict['application'] = menu = Menu(mbar, name='apple')
333 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000334 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000335 self.recent_files_menu = Menu(self.menubar)
336 self.menudict['file'].insert_cascade(3, label='Recent Files',
337 underline=0,
338 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000339 self.base_helpmenu_length = self.menudict['help'].index(END)
340 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000341
342 def postwindowsmenu(self):
343 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000344 menu = self.menudict['windows']
345 end = menu.index("end")
346 if end is None:
347 end = -1
348 if end > self.wmenu_end:
349 menu.delete(self.wmenu_end+1, end)
350 WindowList.add_windows_to_menu(menu)
351
352 rmenu = None
353
354 def right_menu_event(self, event):
355 self.text.tag_remove("sel", "1.0", "end")
356 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
357 if not self.rmenu:
358 self.make_rmenu()
359 rmenu = self.rmenu
360 self.event = event
361 iswin = sys.platform[:3] == 'win'
362 if iswin:
363 self.text.config(cursor="arrow")
364 rmenu.tk_popup(event.x_root, event.y_root)
365 if iswin:
366 self.text.config(cursor="ibeam")
367
368 rmenu_specs = [
369 # ("Label", "<<virtual-event>>"), ...
370 ("Close", "<<close-window>>"), # Example
371 ]
372
373 def make_rmenu(self):
374 rmenu = Menu(self.text, tearoff=0)
375 for label, eventname in self.rmenu_specs:
376 def command(text=self.text, eventname=eventname):
377 text.event_generate(eventname)
378 rmenu.add_command(label=label, command=command)
379 self.rmenu = rmenu
380
381 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000382 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000383
Steven M. Gava3b55a892001-11-21 05:56:26 +0000384 def config_dialog(self, event=None):
385 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000386
David Scherer7aced172000-08-15 01:13:23 +0000387 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000388 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000389 textView.TextViewer(self.top,'Help',fn)
390
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000391 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000392 if sys.platform[:3] == 'win':
Steven M. Gava931625d2002-04-22 00:38:26 +0000393 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000394 else:
395 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000396 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000397
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000398 def cut(self,event):
399 self.text.event_generate("<<Cut>>")
400 return "break"
401
402 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000403 if not self.text.tag_ranges("sel"):
404 # There is no selection, so do nothing and maybe interrupt.
405 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000406 self.text.event_generate("<<Copy>>")
407 return "break"
408
409 def paste(self,event):
410 self.text.event_generate("<<Paste>>")
411 return "break"
412
David Scherer7aced172000-08-15 01:13:23 +0000413 def select_all(self, event=None):
414 self.text.tag_add("sel", "1.0", "end-1c")
415 self.text.mark_set("insert", "1.0")
416 self.text.see("insert")
417 return "break"
418
419 def remove_selection(self, event=None):
420 self.text.tag_remove("sel", "1.0", "end")
421 self.text.see("insert")
422
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000423 def move_at_edge_if_selection(self, edge_index):
424 """Cursor move begins at start or end of selection
425
426 When a left/right cursor key is pressed create and return to Tkinter a
427 function which causes a cursor move from the associated edge of the
428 selection.
429
430 """
431 self_text_index = self.text.index
432 self_text_mark_set = self.text.mark_set
433 edges_table = ("sel.first+1c", "sel.last-1c")
434 def move_at_edge(event):
435 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
436 try:
437 self_text_index("sel.first")
438 self_text_mark_set("insert", edges_table[edge_index])
439 except TclError:
440 pass
441 return move_at_edge
442
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000443 def del_word_left(self, event):
444 self.text.event_generate('<Meta-Delete>')
445 return "break"
446
447 def del_word_right(self, event):
448 self.text.event_generate('<Meta-d>')
449 return "break"
450
Steven M. Gavac5976402002-01-04 03:06:08 +0000451 def find_event(self, event):
452 SearchDialog.find(self.text)
453 return "break"
454
455 def find_again_event(self, event):
456 SearchDialog.find_again(self.text)
457 return "break"
458
459 def find_selection_event(self, event):
460 SearchDialog.find_selection(self.text)
461 return "break"
462
463 def find_in_files_event(self, event):
464 GrepDialog.grep(self.text, self.io, self.flist)
465 return "break"
466
467 def replace_event(self, event):
468 ReplaceDialog.replace(self.text)
469 return "break"
470
471 def goto_line_event(self, event):
472 text = self.text
473 lineno = tkSimpleDialog.askinteger("Goto",
474 "Go to line number:",parent=text)
475 if lineno is None:
476 return "break"
477 if lineno <= 0:
478 text.bell()
479 return "break"
480 text.mark_set("insert", "%d.0" % lineno)
481 text.see("insert")
482
David Scherer7aced172000-08-15 01:13:23 +0000483 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000484 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000485 try:
486 name = self.text.get("sel.first", "sel.last")
487 except TclError:
488 name = ""
489 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000490 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000491 name = tkSimpleDialog.askstring("Module",
492 "Enter the name of a Python module\n"
493 "to search on sys.path and open:",
494 parent=self.text, initialvalue=name)
495 if name:
496 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000497 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000498 return
David Scherer7aced172000-08-15 01:13:23 +0000499 # XXX Ought to insert current file's directory in front of path
500 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000501 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000502 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000503 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
504 return
505 if type != imp.PY_SOURCE:
506 tkMessageBox.showerror("Unsupported type",
507 "%s is not a source module" % name, parent=self.text)
508 return
509 if f:
510 f.close()
511 if self.flist:
512 self.flist.open(file)
513 else:
514 self.io.loadfile(file)
515
516 def open_class_browser(self, event=None):
517 filename = self.io.filename
518 if not filename:
519 tkMessageBox.showerror(
520 "No filename",
521 "This buffer has no associated filename",
522 master=self.text)
523 self.text.focus_set()
524 return None
525 head, tail = os.path.split(filename)
526 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000527 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000528 ClassBrowser.ClassBrowser(self.flist, base, [head])
529
530 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000531 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000532 PathBrowser.PathBrowser(self.flist)
533
534 def gotoline(self, lineno):
535 if lineno is not None and lineno > 0:
536 self.text.mark_set("insert", "%d.0" % lineno)
537 self.text.tag_remove("sel", "1.0", "end")
538 self.text.tag_add("sel", "insert", "insert +1l")
539 self.center()
540
541 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000542 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000543 return True
David Scherer7aced172000-08-15 01:13:23 +0000544 base, ext = os.path.splitext(os.path.basename(filename))
545 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000546 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000547 line = self.text.get('1.0', '1.0 lineend')
548 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000549
550 def close_hook(self):
551 if self.flist:
552 self.flist.close_edit(self)
553
554 def set_close_hook(self, close_hook):
555 self.close_hook = close_hook
556
557 def filename_change_hook(self):
558 if self.flist:
559 self.flist.filename_changed_edit(self)
560 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000561 self.top.update_windowlist_registry(self)
David Scherer7aced172000-08-15 01:13:23 +0000562 if self.ispythonsource(self.io.filename):
563 self.addcolorizer()
564 else:
565 self.rmcolorizer()
566
567 def addcolorizer(self):
568 if self.color:
569 return
David Scherer7aced172000-08-15 01:13:23 +0000570 self.per.removefilter(self.undo)
571 self.color = self.ColorDelegator()
572 self.per.insertfilter(self.color)
573 self.per.insertfilter(self.undo)
574
575 def rmcolorizer(self):
576 if not self.color:
577 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000578 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000579 self.per.removefilter(self.undo)
580 self.per.removefilter(self.color)
581 self.color = None
582 self.per.insertfilter(self.undo)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000583
Steven M. Gavab77d3432002-03-02 07:16:21 +0000584 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000585 "Update the colour theme if it is changed"
586 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000587 if self.color:
588 self.color = self.ColorDelegator()
589 self.per.insertfilter(self.color)
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000590 theme = idleConf.GetOption('main','Theme','name')
591 self.text.config(idleConf.GetHighlight(theme, "normal"))
David Scherer7aced172000-08-15 01:13:23 +0000592
Guido van Rossum33d26892007-08-05 15:29:28 +0000593 IDENTCHARS = string.ascii_letters + string.digits + "_"
594
595 def colorize_syntax_error(self, text, pos):
596 text.tag_add("ERROR", pos)
597 char = text.get(pos)
598 if char and char in self.IDENTCHARS:
599 text.tag_add("ERROR", pos + " wordstart", pos)
600 if '\n' == text.get(pos): # error at line end
601 text.mark_set("insert", pos)
602 else:
603 text.mark_set("insert", pos + "+1c")
604 text.see(pos)
605
Steven M. Gavab1585412002-03-12 00:21:56 +0000606 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000607 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000608 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000609 fontWeight='normal'
610 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
611 fontWeight='bold'
612 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
613 idleConf.GetOption('main','EditorWindow','font-size'),
614 fontWeight))
615
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000616 def RemoveKeybindings(self):
617 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000618 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000619 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000620 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000621 self.text.event_delete(event, *keylist)
622 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000623 xkeydefs = idleConf.GetExtensionBindings(extensionName)
624 if xkeydefs:
625 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000626 self.text.event_delete(event, *keylist)
627
628 def ApplyKeybindings(self):
629 "Update the keybindings after they are changed"
630 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000631 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000632 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000633 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000634 xkeydefs = idleConf.GetExtensionBindings(extensionName)
635 if xkeydefs:
636 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000637 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000638 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000639 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000640 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000641 for item in menu[1]:
642 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000643 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000644 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000645 menu = self.menudict[menubarItem]
646 end = menu.index(END) + 1
647 for index in range(0, end):
648 if menu.type(index) == 'command':
649 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000650 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000651 itemName = menu.entrycget(index, 'label')
652 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000653 if menubarItem in menuEventDict:
654 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000655 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000656 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000657 accel = get_accelerator(keydefs, event)
658 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000659
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000660 def set_notabs_indentwidth(self):
661 "Update the indentwidth if changed and not using tabs in this window"
662 # Called from configDialog.py
663 if not self.usetabs:
664 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
665 type='int')
666
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000667 def reset_help_menu_entries(self):
668 "Update the additional help entries on the Help menu"
669 help_list = idleConf.GetAllExtraHelpSourcesList()
670 helpmenu = self.menudict['help']
671 # first delete the extra help entries, if any
672 helpmenu_length = helpmenu.index(END)
673 if helpmenu_length > self.base_helpmenu_length:
674 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
675 # then rebuild them
676 if help_list:
677 helpmenu.add_separator()
678 for entry in help_list:
679 cmd = self.__extra_help_callback(entry[1])
680 helpmenu.add_command(label=entry[0], command=cmd)
681 # and update the menu dictionary
682 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000683
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000684 def __extra_help_callback(self, helpfile):
685 "Create a callback with the helpfile value frozen at definition time"
686 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000687 if not helpfile.startswith(('www', 'http')):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000688 url = os.path.normpath(helpfile)
689 if sys.platform[:3] == 'win':
690 os.startfile(helpfile)
691 else:
692 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000693 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000694
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000695 def update_recent_files_list(self, new_file=None):
696 "Load and update the recent files list and menus"
697 rf_list = []
698 if os.path.exists(self.recent_files_path):
699 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000700 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000701 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000702 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000703 rf_list_file.close()
704 if new_file:
705 new_file = os.path.abspath(new_file) + '\n'
706 if new_file in rf_list:
707 rf_list.remove(new_file) # move to top
708 rf_list.insert(0, new_file)
709 # clean and save the recent files list
710 bad_paths = []
711 for path in rf_list:
712 if '\0' in path or not os.path.exists(path[0:-1]):
713 bad_paths.append(path)
714 rf_list = [path for path in rf_list if path not in bad_paths]
715 ulchars = "1234567890ABCDEFGHIJK"
716 rf_list = rf_list[0:len(ulchars)]
717 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000718 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000719 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000720 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000721 rf_file.close()
722 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000723 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000724 menu = instance.recent_files_menu
725 menu.delete(1, END) # clear, and rebuild:
726 for i, file in zip(count(), rf_list):
727 file_name = file[0:-1] # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000728 # make unicode string to display non-ASCII chars correctly
729 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000730 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000731 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000732 command=callback,
733 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000734
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000735 def __recent_file_callback(self, file_name):
736 def open_recent_file(fn_closure=file_name):
737 self.io.open(editFile=fn_closure)
738 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000739
David Scherer7aced172000-08-15 01:13:23 +0000740 def saved_change_hook(self):
741 short = self.short_title()
742 long = self.long_title()
743 if short and long:
744 title = short + " - " + long
745 elif short:
746 title = short
747 elif long:
748 title = long
749 else:
750 title = "Untitled"
751 icon = short or long or title
752 if not self.get_saved():
753 title = "*%s*" % title
754 icon = "*%s" % icon
755 self.top.wm_title(title)
756 self.top.wm_iconname(icon)
757
758 def get_saved(self):
759 return self.undo.get_saved()
760
761 def set_saved(self, flag):
762 self.undo.set_saved(flag)
763
764 def reset_undo(self):
765 self.undo.reset_undo()
766
767 def short_title(self):
768 filename = self.io.filename
769 if filename:
770 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000771 # return unicode string to display non-ASCII chars correctly
772 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000773
774 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000775 # return unicode string to display non-ASCII chars correctly
776 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000777
778 def center_insert_event(self, event):
779 self.center()
780
781 def center(self, mark="insert"):
782 text = self.text
783 top, bot = self.getwindowlines()
784 lineno = self.getlineno(mark)
785 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000786 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000787 text.yview(float(newtop))
788
789 def getwindowlines(self):
790 text = self.text
791 top = self.getlineno("@0,0")
792 bot = self.getlineno("@0,65535")
793 if top == bot and text.winfo_height() == 1:
794 # Geometry manager hasn't run yet
795 height = int(text['height'])
796 bot = top + height - 1
797 return top, bot
798
799 def getlineno(self, mark="insert"):
800 text = self.text
801 return int(float(text.index(mark)))
802
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000803 def get_geometry(self):
804 "Return (width, height, x, y)"
805 geom = self.top.wm_geometry()
806 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000807 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000808
David Scherer7aced172000-08-15 01:13:23 +0000809 def close_event(self, event):
810 self.close()
811
812 def maybesave(self):
813 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000814 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000815 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000816 self.top.deiconify()
817 self.top.lower()
818 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000819 return self.io.maybesave()
820
821 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000822 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000823 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000824 self._close()
825 return reply
826
827 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000828 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000829 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000830 WindowList.unregister_callback(self.postwindowsmenu)
831 if self.close_hook:
832 self.close_hook()
833 self.flist = None
834 colorizing = 0
835 self.unload_extensions()
836 self.io.close(); self.io = None
837 self.undo = None # XXX
838 if self.color:
839 colorizing = self.color.colorizing
840 doh = colorizing and self.top
841 self.color.close(doh) # Cancel colorization
842 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000843 self.tkinter_vars = None
David Scherer7aced172000-08-15 01:13:23 +0000844 self.per.close(); self.per = None
845 if not colorizing:
846 self.top.destroy()
847
848 def load_extensions(self):
849 self.extensions = {}
850 self.load_standard_extensions()
851
852 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000853 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000854 if hasattr(ins, "close"):
855 ins.close()
856 self.extensions = {}
857
858 def load_standard_extensions(self):
859 for name in self.get_standard_extension_names():
860 try:
861 self.load_extension(name)
862 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000863 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000864 traceback.print_exc()
865
866 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000867 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000868
869 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000870 try:
871 mod = __import__(name, globals(), locals(), [])
872 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000873 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000874 raise
David Scherer7aced172000-08-15 01:13:23 +0000875 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000876 keydefs = idleConf.GetExtensionBindings(name)
877 if hasattr(cls, "menudefs"):
878 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000879 ins = cls(self)
880 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000881 if keydefs:
882 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000883 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000884 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000885 while methodname[:1] == '<':
886 methodname = methodname[1:]
887 while methodname[-1:] == '>':
888 methodname = methodname[:-1]
889 methodname = methodname + "_event"
890 if hasattr(ins, methodname):
891 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000892
893 def apply_bindings(self, keydefs=None):
894 if keydefs is None:
895 keydefs = self.Bindings.default_keydefs
896 text = self.text
897 text.keydefs = keydefs
898 for event, keylist in keydefs.items():
899 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000900 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000901
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000902 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000903 """Add appropriate entries to the menus and submenus
904
905 Menus that are absent or None in self.menudict are ignored.
906 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000907 if menudefs is None:
908 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000909 if keydefs is None:
910 keydefs = self.Bindings.default_keydefs
911 menudict = self.menudict
912 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000913 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000914 menu = menudict.get(mname)
915 if not menu:
916 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000917 for entry in entrylist:
918 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000919 menu.add_separator()
920 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000921 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000922 checkbutton = (label[:1] == '!')
923 if checkbutton:
924 label = label[1:]
925 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000926 accelerator = get_accelerator(keydefs, eventname)
927 def command(text=text, eventname=eventname):
928 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000929 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000930 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000931 menu.add_checkbutton(label=label, underline=underline,
932 command=command, accelerator=accelerator,
933 variable=var)
934 else:
935 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000936 command=command,
937 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000938
939 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000940 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +0000941 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000942 value = var.get()
943 return value
944 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000945 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +0000946
947 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000948 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +0000949 if var:
950 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000951 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000952 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +0000953
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000954 def get_var_obj(self, name, vartype=None):
955 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +0000956 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000957 # create a Tkinter variable object with self.text as master:
958 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000959 return var
960
961 # Tk implementations of "virtual text methods" -- each platform
962 # reusing IDLE's support code needs to define these for its GUI's
963 # flavor of widget.
964
965 # Is character at text_index in a Python string? Return 0 for
966 # "guaranteed no", true for anything else. This info is expensive
967 # to compute ab initio, but is probably already known by the
968 # platform's colorizer.
969
970 def is_char_in_string(self, text_index):
971 if self.color:
972 # Return true iff colorizer hasn't (re)gotten this far
973 # yet, or the character is tagged as being in a string
974 return self.text.tag_prevrange("TODO", text_index) or \
975 "STRING" in self.text.tag_names(text_index)
976 else:
977 # The colorizer is missing: assume the worst
978 return 1
979
980 # If a selection is defined in the text widget, return (start,
981 # end) as Tkinter text indices, otherwise return (None, None)
982 def get_selection_indices(self):
983 try:
984 first = self.text.index("sel.first")
985 last = self.text.index("sel.last")
986 return first, last
987 except TclError:
988 return None, None
989
990 # Return the text widget's current view of what a tab stop means
991 # (equivalent width in spaces).
992
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000993 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +0000994 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
995 return int(current)
996
997 # Set the text widget's current view of what a tab stop means.
998
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000999 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001000 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001001 if self.get_tk_tabwidth() != newtabwidth:
1002 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001003 pixels = text.tk.call("font", "measure", text["font"],
1004 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001005 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001006 text.configure(tabs=pixels)
1007
Guido van Rossum33d26892007-08-05 15:29:28 +00001008### begin autoindent code ### (configuration was moved to beginning of class)
1009
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001010 def set_indentation_params(self, is_py_src, guess=True):
1011 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001012 i = self.guess_indent()
1013 if 2 <= i <= 8:
1014 self.indentwidth = i
1015 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001016 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001017 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001018
1019 def smart_backspace_event(self, event):
1020 text = self.text
1021 first, last = self.get_selection_indices()
1022 if first and last:
1023 text.delete(first, last)
1024 text.mark_set("insert", first)
1025 return "break"
1026 # Delete whitespace left, until hitting a real char or closest
1027 # preceding virtual tab stop.
1028 chars = text.get("insert linestart", "insert")
1029 if chars == '':
1030 if text.compare("insert", ">", "1.0"):
1031 # easy: delete preceding newline
1032 text.delete("insert-1c")
1033 else:
1034 text.bell() # at start of buffer
1035 return "break"
1036 if chars[-1] not in " \t":
1037 # easy: delete preceding real char
1038 text.delete("insert-1c")
1039 return "break"
1040 # Ick. It may require *inserting* spaces if we back up over a
1041 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001042 tabwidth = self.tabwidth
1043 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001044 assert have > 0
1045 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001046 # Debug prompt is multilined....
1047 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001048 ncharsdeleted = 0
1049 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001050 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001051 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001052 chars = chars[:-1]
1053 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001054 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001055 if have <= want or chars[-1] not in " \t":
1056 break
1057 text.undo_block_start()
1058 text.delete("insert-%dc" % ncharsdeleted, "insert")
1059 if have < want:
1060 text.insert("insert", ' ' * (want - have))
1061 text.undo_block_stop()
1062 return "break"
1063
1064 def smart_indent_event(self, event):
1065 # if intraline selection:
1066 # delete it
1067 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001068 # do indent-region
1069 # else:
1070 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001071 text = self.text
1072 first, last = self.get_selection_indices()
1073 text.undo_block_start()
1074 try:
1075 if first and last:
1076 if index2line(first) != index2line(last):
1077 return self.indent_region_event(event)
1078 text.delete(first, last)
1079 text.mark_set("insert", first)
1080 prefix = text.get("insert linestart", "insert")
1081 raw, effective = classifyws(prefix, self.tabwidth)
1082 if raw == len(prefix):
1083 # only whitespace to the left
1084 self.reindent_to(effective + self.indentwidth)
1085 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001086 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001087 if self.usetabs:
1088 pad = '\t'
1089 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001090 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001091 n = self.indentwidth
1092 pad = ' ' * (n - effective % n)
1093 text.insert("insert", pad)
1094 text.see("insert")
1095 return "break"
1096 finally:
1097 text.undo_block_stop()
1098
1099 def newline_and_indent_event(self, event):
1100 text = self.text
1101 first, last = self.get_selection_indices()
1102 text.undo_block_start()
1103 try:
1104 if first and last:
1105 text.delete(first, last)
1106 text.mark_set("insert", first)
1107 line = text.get("insert linestart", "insert")
1108 i, n = 0, len(line)
1109 while i < n and line[i] in " \t":
1110 i = i+1
1111 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001112 # the cursor is in or at leading indentation in a continuation
1113 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001114 text.insert("insert linestart", '\n')
1115 return "break"
1116 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001117 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001118 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001119 last_line_of_prompt = sys.ps1.split('\n')[-1]
1120 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001121 line = line[:-1]
1122 i = i+1
1123 if i:
1124 text.delete("insert - %d chars" % i, "insert")
1125 # strip whitespace after insert point
1126 while text.get("insert") in " \t":
1127 text.delete("insert")
1128 # start new line
1129 text.insert("insert", '\n')
1130
1131 # adjust indentation for continuations and block
1132 # open/close first need to find the last stmt
1133 lno = index2line(text.index('insert'))
1134 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001135 if not self.context_use_ps1:
1136 for context in self.num_context_lines:
1137 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001138 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001139 rawtext = text.get(startatindex, "insert")
1140 y.set_str(rawtext)
1141 bod = y.find_good_parse_start(
1142 self.context_use_ps1,
1143 self._build_char_in_string_func(startatindex))
1144 if bod is not None or startat == 1:
1145 break
1146 y.set_lo(bod or 0)
1147 else:
1148 r = text.tag_prevrange("console", "insert")
1149 if r:
1150 startatindex = r[1]
1151 else:
1152 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001153 rawtext = text.get(startatindex, "insert")
1154 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001155 y.set_lo(0)
1156
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001157 c = y.get_continuation_type()
1158 if c != PyParse.C_NONE:
1159 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001160 if c == PyParse.C_STRING_FIRST_LINE:
1161 # after the first line of a string; do not indent at all
1162 pass
1163 elif c == PyParse.C_STRING_NEXT_LINES:
1164 # inside a string which started before this line;
1165 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001166 text.insert("insert", indent)
1167 elif c == PyParse.C_BRACKET:
1168 # line up with the first (if any) element of the
1169 # last open bracket structure; else indent one
1170 # level beyond the indent of the line with the
1171 # last open bracket
1172 self.reindent_to(y.compute_bracket_indent())
1173 elif c == PyParse.C_BACKSLASH:
1174 # if more than one line in this stmt already, just
1175 # mimic the current indent; else if initial line
1176 # has a start on an assignment stmt, indent to
1177 # beyond leftmost =; else to beyond first chunk of
1178 # non-whitespace on initial line
1179 if y.get_num_lines_in_stmt() > 1:
1180 text.insert("insert", indent)
1181 else:
1182 self.reindent_to(y.compute_backslash_indent())
1183 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001184 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001185 return "break"
1186
1187 # This line starts a brand new stmt; indent relative to
1188 # indentation of initial line of closest preceding
1189 # interesting stmt.
1190 indent = y.get_base_indent_string()
1191 text.insert("insert", indent)
1192 if y.is_block_opener():
1193 self.smart_indent_event(event)
1194 elif indent and y.is_block_closer():
1195 self.smart_backspace_event(event)
1196 return "break"
1197 finally:
1198 text.see("insert")
1199 text.undo_block_stop()
1200
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001201 # Our editwin provides a is_char_in_string function that works
1202 # with a Tk text index, but PyParse only knows about offsets into
1203 # a string. This builds a function for PyParse that accepts an
1204 # offset.
1205
1206 def _build_char_in_string_func(self, startindex):
1207 def inner(offset, _startindex=startindex,
1208 _icis=self.is_char_in_string):
1209 return _icis(_startindex + "+%dc" % offset)
1210 return inner
1211
1212 def indent_region_event(self, event):
1213 head, tail, chars, lines = self.get_region()
1214 for pos in range(len(lines)):
1215 line = lines[pos]
1216 if line:
1217 raw, effective = classifyws(line, self.tabwidth)
1218 effective = effective + self.indentwidth
1219 lines[pos] = self._make_blanks(effective) + line[raw:]
1220 self.set_region(head, tail, chars, lines)
1221 return "break"
1222
1223 def dedent_region_event(self, event):
1224 head, tail, chars, lines = self.get_region()
1225 for pos in range(len(lines)):
1226 line = lines[pos]
1227 if line:
1228 raw, effective = classifyws(line, self.tabwidth)
1229 effective = max(effective - self.indentwidth, 0)
1230 lines[pos] = self._make_blanks(effective) + line[raw:]
1231 self.set_region(head, tail, chars, lines)
1232 return "break"
1233
1234 def comment_region_event(self, event):
1235 head, tail, chars, lines = self.get_region()
1236 for pos in range(len(lines) - 1):
1237 line = lines[pos]
1238 lines[pos] = '##' + line
1239 self.set_region(head, tail, chars, lines)
1240
1241 def uncomment_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 not line:
1246 continue
1247 if line[:2] == '##':
1248 line = line[2:]
1249 elif line[:1] == '#':
1250 line = line[1:]
1251 lines[pos] = line
1252 self.set_region(head, tail, chars, lines)
1253
1254 def tabify_region_event(self, event):
1255 head, tail, chars, lines = self.get_region()
1256 tabwidth = self._asktabwidth()
1257 for pos in range(len(lines)):
1258 line = lines[pos]
1259 if line:
1260 raw, effective = classifyws(line, tabwidth)
1261 ntabs, nspaces = divmod(effective, tabwidth)
1262 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1263 self.set_region(head, tail, chars, lines)
1264
1265 def untabify_region_event(self, event):
1266 head, tail, chars, lines = self.get_region()
1267 tabwidth = self._asktabwidth()
1268 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001269 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001270 self.set_region(head, tail, chars, lines)
1271
1272 def toggle_tabs_event(self, event):
1273 if self.askyesno(
1274 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001275 "Turn tabs " + ("on", "off")[self.usetabs] +
1276 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001277 ("will be", "remains at")[self.usetabs] + " 8." +
1278 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001279 parent=self.text):
1280 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001281 # Try to prevent inconsistent indentation.
1282 # User must change indent width manually after using tabs.
1283 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001284 return "break"
1285
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001286 # XXX this isn't bound to anything -- see tabwidth comments
1287## def change_tabwidth_event(self, event):
1288## new = self._asktabwidth()
1289## if new != self.tabwidth:
1290## self.tabwidth = new
1291## self.set_indentation_params(0, guess=0)
1292## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001293
1294 def change_indentwidth_event(self, event):
1295 new = self.askinteger(
1296 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001297 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001298 parent=self.text,
1299 initialvalue=self.indentwidth,
1300 minvalue=2,
1301 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001302 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001303 self.indentwidth = new
1304 return "break"
1305
1306 def get_region(self):
1307 text = self.text
1308 first, last = self.get_selection_indices()
1309 if first and last:
1310 head = text.index(first + " linestart")
1311 tail = text.index(last + "-1c lineend +1c")
1312 else:
1313 head = text.index("insert linestart")
1314 tail = text.index("insert lineend +1c")
1315 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001316 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001317 return head, tail, chars, lines
1318
1319 def set_region(self, head, tail, chars, lines):
1320 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001321 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001322 if newchars == chars:
1323 text.bell()
1324 return
1325 text.tag_remove("sel", "1.0", "end")
1326 text.mark_set("insert", head)
1327 text.undo_block_start()
1328 text.delete(head, tail)
1329 text.insert(head, newchars)
1330 text.undo_block_stop()
1331 text.tag_add("sel", head, "insert")
1332
1333 # Make string that displays as n leading blanks.
1334
1335 def _make_blanks(self, n):
1336 if self.usetabs:
1337 ntabs, nspaces = divmod(n, self.tabwidth)
1338 return '\t' * ntabs + ' ' * nspaces
1339 else:
1340 return ' ' * n
1341
1342 # Delete from beginning of line to insert point, then reinsert
1343 # column logical (meaning use tabs if appropriate) spaces.
1344
1345 def reindent_to(self, column):
1346 text = self.text
1347 text.undo_block_start()
1348 if text.compare("insert linestart", "!=", "insert"):
1349 text.delete("insert linestart", "insert")
1350 if column:
1351 text.insert("insert", self._make_blanks(column))
1352 text.undo_block_stop()
1353
1354 def _asktabwidth(self):
1355 return self.askinteger(
1356 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001357 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001358 parent=self.text,
1359 initialvalue=self.indentwidth,
1360 minvalue=2,
1361 maxvalue=16) or self.tabwidth
1362
1363 # Guess indentwidth from text content.
1364 # Return guessed indentwidth. This should not be believed unless
1365 # it's in a reasonable range (e.g., it will be 0 if no indented
1366 # blocks are found).
1367
1368 def guess_indent(self):
1369 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1370 if opener and indented:
1371 raw, indentsmall = classifyws(opener, self.tabwidth)
1372 raw, indentlarge = classifyws(indented, self.tabwidth)
1373 else:
1374 indentsmall = indentlarge = 0
1375 return indentlarge - indentsmall
1376
1377# "line.col" -> line, as an int
1378def index2line(index):
1379 return int(float(index))
1380
1381# Look at the leading whitespace in s.
1382# Return pair (# of leading ws characters,
1383# effective # of leading blanks after expanding
1384# tabs to width tabwidth)
1385
1386def classifyws(s, tabwidth):
1387 raw = effective = 0
1388 for ch in s:
1389 if ch == ' ':
1390 raw = raw + 1
1391 effective = effective + 1
1392 elif ch == '\t':
1393 raw = raw + 1
1394 effective = (effective // tabwidth + 1) * tabwidth
1395 else:
1396 break
1397 return raw, effective
1398
1399import tokenize
1400_tokenize = tokenize
1401del tokenize
1402
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001403class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001404
1405 # .run() chews over the Text widget, looking for a block opener
1406 # and the stmt following it. Returns a pair,
1407 # (line containing block opener, line containing stmt)
1408 # Either or both may be None.
1409
1410 def __init__(self, text, tabwidth):
1411 self.text = text
1412 self.tabwidth = tabwidth
1413 self.i = self.finished = 0
1414 self.blkopenline = self.indentedline = None
1415
1416 def readline(self):
1417 if self.finished:
1418 return ""
1419 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001420 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001421 if self.text.compare(mark, ">=", "end"):
1422 return ""
1423 return self.text.get(mark, mark + " lineend+1c")
1424
1425 def tokeneater(self, type, token, start, end, line,
1426 INDENT=_tokenize.INDENT,
1427 NAME=_tokenize.NAME,
1428 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1429 if self.finished:
1430 pass
1431 elif type == NAME and token in OPENERS:
1432 self.blkopenline = line
1433 elif type == INDENT and self.blkopenline:
1434 self.indentedline = line
1435 self.finished = 1
1436
1437 def run(self):
1438 save_tabsize = _tokenize.tabsize
1439 _tokenize.tabsize = self.tabwidth
1440 try:
1441 try:
1442 _tokenize.tokenize(self.readline, self.tokeneater)
1443 except _tokenize.TokenError:
1444 # since we cut off the tokenizer early, we can trigger
1445 # spurious errors
1446 pass
1447 finally:
1448 _tokenize.tabsize = save_tabsize
1449 return self.blkopenline, self.indentedline
1450
1451### end autoindent code ###
1452
David Scherer7aced172000-08-15 01:13:23 +00001453def prepstr(s):
1454 # Helper to extract the underscore from a string, e.g.
1455 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001456 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001457 if i >= 0:
1458 s = s[:i] + s[i+1:]
1459 return i, s
1460
1461
1462keynames = {
1463 'bracketleft': '[',
1464 'bracketright': ']',
1465 'slash': '/',
1466}
1467
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001468def get_accelerator(keydefs, eventname):
1469 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001470 if not keylist:
1471 return ""
1472 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001473 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001474 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1475 s = re.sub("Key-", "", s)
1476 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1477 s = re.sub("Control-", "Ctrl-", s)
1478 s = re.sub("-", "+", s)
1479 s = re.sub("><", " ", s)
1480 s = re.sub("<", "", s)
1481 s = re.sub(">", "", s)
1482 return s
1483
1484
1485def fixwordbreaks(root):
1486 # Make sure that Tk's double-click and next/previous word
1487 # operations use our definition of a word (i.e. an identifier)
1488 tk = root.tk
1489 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1490 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1491 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1492
1493
1494def test():
1495 root = Tk()
1496 fixwordbreaks(root)
1497 root.withdraw()
1498 if sys.argv[1:]:
1499 filename = sys.argv[1]
1500 else:
1501 filename = None
1502 edit = EditorWindow(root=root, filename=filename)
1503 edit.set_close_hook(root.quit)
1504 root.mainloop()
1505 root.destroy()
1506
1507if __name__ == '__main__':
1508 test()