blob: 0cd668abf8a1e1428d51d2980ceebbb5ebc2eceb [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')
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000389 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000390
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>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000411 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000412 return "break"
413
David Scherer7aced172000-08-15 01:13:23 +0000414 def select_all(self, event=None):
415 self.text.tag_add("sel", "1.0", "end-1c")
416 self.text.mark_set("insert", "1.0")
417 self.text.see("insert")
418 return "break"
419
420 def remove_selection(self, event=None):
421 self.text.tag_remove("sel", "1.0", "end")
422 self.text.see("insert")
423
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000424 def move_at_edge_if_selection(self, edge_index):
425 """Cursor move begins at start or end of selection
426
427 When a left/right cursor key is pressed create and return to Tkinter a
428 function which causes a cursor move from the associated edge of the
429 selection.
430
431 """
432 self_text_index = self.text.index
433 self_text_mark_set = self.text.mark_set
434 edges_table = ("sel.first+1c", "sel.last-1c")
435 def move_at_edge(event):
436 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
437 try:
438 self_text_index("sel.first")
439 self_text_mark_set("insert", edges_table[edge_index])
440 except TclError:
441 pass
442 return move_at_edge
443
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000444 def del_word_left(self, event):
445 self.text.event_generate('<Meta-Delete>')
446 return "break"
447
448 def del_word_right(self, event):
449 self.text.event_generate('<Meta-d>')
450 return "break"
451
Steven M. Gavac5976402002-01-04 03:06:08 +0000452 def find_event(self, event):
453 SearchDialog.find(self.text)
454 return "break"
455
456 def find_again_event(self, event):
457 SearchDialog.find_again(self.text)
458 return "break"
459
460 def find_selection_event(self, event):
461 SearchDialog.find_selection(self.text)
462 return "break"
463
464 def find_in_files_event(self, event):
465 GrepDialog.grep(self.text, self.io, self.flist)
466 return "break"
467
468 def replace_event(self, event):
469 ReplaceDialog.replace(self.text)
470 return "break"
471
472 def goto_line_event(self, event):
473 text = self.text
474 lineno = tkSimpleDialog.askinteger("Goto",
475 "Go to line number:",parent=text)
476 if lineno is None:
477 return "break"
478 if lineno <= 0:
479 text.bell()
480 return "break"
481 text.mark_set("insert", "%d.0" % lineno)
482 text.see("insert")
483
David Scherer7aced172000-08-15 01:13:23 +0000484 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000485 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000486 try:
487 name = self.text.get("sel.first", "sel.last")
488 except TclError:
489 name = ""
490 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000491 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000492 name = tkSimpleDialog.askstring("Module",
493 "Enter the name of a Python module\n"
494 "to search on sys.path and open:",
495 parent=self.text, initialvalue=name)
496 if name:
497 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000498 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000499 return
David Scherer7aced172000-08-15 01:13:23 +0000500 # XXX Ought to insert current file's directory in front of path
501 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000502 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000503 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000504 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
505 return
506 if type != imp.PY_SOURCE:
507 tkMessageBox.showerror("Unsupported type",
508 "%s is not a source module" % name, parent=self.text)
509 return
510 if f:
511 f.close()
512 if self.flist:
513 self.flist.open(file)
514 else:
515 self.io.loadfile(file)
516
517 def open_class_browser(self, event=None):
518 filename = self.io.filename
519 if not filename:
520 tkMessageBox.showerror(
521 "No filename",
522 "This buffer has no associated filename",
523 master=self.text)
524 self.text.focus_set()
525 return None
526 head, tail = os.path.split(filename)
527 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000528 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000529 ClassBrowser.ClassBrowser(self.flist, base, [head])
530
531 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000532 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000533 PathBrowser.PathBrowser(self.flist)
534
535 def gotoline(self, lineno):
536 if lineno is not None and lineno > 0:
537 self.text.mark_set("insert", "%d.0" % lineno)
538 self.text.tag_remove("sel", "1.0", "end")
539 self.text.tag_add("sel", "insert", "insert +1l")
540 self.center()
541
542 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000543 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000544 return True
David Scherer7aced172000-08-15 01:13:23 +0000545 base, ext = os.path.splitext(os.path.basename(filename))
546 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000547 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000548 line = self.text.get('1.0', '1.0 lineend')
549 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000550
551 def close_hook(self):
552 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000553 self.flist.unregister_maybe_terminate(self)
554 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000555
556 def set_close_hook(self, close_hook):
557 self.close_hook = close_hook
558
559 def filename_change_hook(self):
560 if self.flist:
561 self.flist.filename_changed_edit(self)
562 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000563 self.top.update_windowlist_registry(self)
David Scherer7aced172000-08-15 01:13:23 +0000564 if self.ispythonsource(self.io.filename):
565 self.addcolorizer()
566 else:
567 self.rmcolorizer()
568
569 def addcolorizer(self):
570 if self.color:
571 return
David Scherer7aced172000-08-15 01:13:23 +0000572 self.per.removefilter(self.undo)
573 self.color = self.ColorDelegator()
574 self.per.insertfilter(self.color)
575 self.per.insertfilter(self.undo)
576
577 def rmcolorizer(self):
578 if not self.color:
579 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000580 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000581 self.per.removefilter(self.undo)
582 self.per.removefilter(self.color)
583 self.color = None
584 self.per.insertfilter(self.undo)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000585
Steven M. Gavab77d3432002-03-02 07:16:21 +0000586 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000587 "Update the colour theme if it is changed"
588 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000589 if self.color:
590 self.color = self.ColorDelegator()
591 self.per.insertfilter(self.color)
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000592 theme = idleConf.GetOption('main','Theme','name')
593 self.text.config(idleConf.GetHighlight(theme, "normal"))
David Scherer7aced172000-08-15 01:13:23 +0000594
Guido van Rossum33d26892007-08-05 15:29:28 +0000595 IDENTCHARS = string.ascii_letters + string.digits + "_"
596
597 def colorize_syntax_error(self, text, pos):
598 text.tag_add("ERROR", pos)
599 char = text.get(pos)
600 if char and char in self.IDENTCHARS:
601 text.tag_add("ERROR", pos + " wordstart", pos)
602 if '\n' == text.get(pos): # error at line end
603 text.mark_set("insert", pos)
604 else:
605 text.mark_set("insert", pos + "+1c")
606 text.see(pos)
607
Steven M. Gavab1585412002-03-12 00:21:56 +0000608 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000609 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000610 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000611 fontWeight='normal'
612 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
613 fontWeight='bold'
614 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
615 idleConf.GetOption('main','EditorWindow','font-size'),
616 fontWeight))
617
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000618 def RemoveKeybindings(self):
619 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000620 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000621 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000622 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000623 self.text.event_delete(event, *keylist)
624 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000625 xkeydefs = idleConf.GetExtensionBindings(extensionName)
626 if xkeydefs:
627 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000628 self.text.event_delete(event, *keylist)
629
630 def ApplyKeybindings(self):
631 "Update the keybindings after they are changed"
632 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000633 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000634 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000635 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 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000639 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000640 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000641 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000642 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000643 for item in menu[1]:
644 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000645 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000646 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000647 menu = self.menudict[menubarItem]
648 end = menu.index(END) + 1
649 for index in range(0, end):
650 if menu.type(index) == 'command':
651 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000652 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000653 itemName = menu.entrycget(index, 'label')
654 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000655 if menubarItem in menuEventDict:
656 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000657 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000658 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000659 accel = get_accelerator(keydefs, event)
660 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000661
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000662 def set_notabs_indentwidth(self):
663 "Update the indentwidth if changed and not using tabs in this window"
664 # Called from configDialog.py
665 if not self.usetabs:
666 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
667 type='int')
668
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000669 def reset_help_menu_entries(self):
670 "Update the additional help entries on the Help menu"
671 help_list = idleConf.GetAllExtraHelpSourcesList()
672 helpmenu = self.menudict['help']
673 # first delete the extra help entries, if any
674 helpmenu_length = helpmenu.index(END)
675 if helpmenu_length > self.base_helpmenu_length:
676 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
677 # then rebuild them
678 if help_list:
679 helpmenu.add_separator()
680 for entry in help_list:
681 cmd = self.__extra_help_callback(entry[1])
682 helpmenu.add_command(label=entry[0], command=cmd)
683 # and update the menu dictionary
684 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000685
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000686 def __extra_help_callback(self, helpfile):
687 "Create a callback with the helpfile value frozen at definition time"
688 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000689 if not helpfile.startswith(('www', 'http')):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000690 url = os.path.normpath(helpfile)
691 if sys.platform[:3] == 'win':
692 os.startfile(helpfile)
693 else:
694 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000695 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000696
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000697 def update_recent_files_list(self, new_file=None):
698 "Load and update the recent files list and menus"
699 rf_list = []
700 if os.path.exists(self.recent_files_path):
701 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000702 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000703 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000704 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000705 rf_list_file.close()
706 if new_file:
707 new_file = os.path.abspath(new_file) + '\n'
708 if new_file in rf_list:
709 rf_list.remove(new_file) # move to top
710 rf_list.insert(0, new_file)
711 # clean and save the recent files list
712 bad_paths = []
713 for path in rf_list:
714 if '\0' in path or not os.path.exists(path[0:-1]):
715 bad_paths.append(path)
716 rf_list = [path for path in rf_list if path not in bad_paths]
717 ulchars = "1234567890ABCDEFGHIJK"
718 rf_list = rf_list[0:len(ulchars)]
719 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000720 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000721 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000722 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000723 rf_file.close()
724 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000725 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000726 menu = instance.recent_files_menu
727 menu.delete(1, END) # clear, and rebuild:
728 for i, file in zip(count(), rf_list):
729 file_name = file[0:-1] # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000730 # make unicode string to display non-ASCII chars correctly
731 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000732 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000733 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000734 command=callback,
735 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000736
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000737 def __recent_file_callback(self, file_name):
738 def open_recent_file(fn_closure=file_name):
739 self.io.open(editFile=fn_closure)
740 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000741
David Scherer7aced172000-08-15 01:13:23 +0000742 def saved_change_hook(self):
743 short = self.short_title()
744 long = self.long_title()
745 if short and long:
746 title = short + " - " + long
747 elif short:
748 title = short
749 elif long:
750 title = long
751 else:
752 title = "Untitled"
753 icon = short or long or title
754 if not self.get_saved():
755 title = "*%s*" % title
756 icon = "*%s" % icon
757 self.top.wm_title(title)
758 self.top.wm_iconname(icon)
759
760 def get_saved(self):
761 return self.undo.get_saved()
762
763 def set_saved(self, flag):
764 self.undo.set_saved(flag)
765
766 def reset_undo(self):
767 self.undo.reset_undo()
768
769 def short_title(self):
770 filename = self.io.filename
771 if filename:
772 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000773 # return unicode string to display non-ASCII chars correctly
774 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000775
776 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000777 # return unicode string to display non-ASCII chars correctly
778 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000779
780 def center_insert_event(self, event):
781 self.center()
782
783 def center(self, mark="insert"):
784 text = self.text
785 top, bot = self.getwindowlines()
786 lineno = self.getlineno(mark)
787 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000788 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000789 text.yview(float(newtop))
790
791 def getwindowlines(self):
792 text = self.text
793 top = self.getlineno("@0,0")
794 bot = self.getlineno("@0,65535")
795 if top == bot and text.winfo_height() == 1:
796 # Geometry manager hasn't run yet
797 height = int(text['height'])
798 bot = top + height - 1
799 return top, bot
800
801 def getlineno(self, mark="insert"):
802 text = self.text
803 return int(float(text.index(mark)))
804
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000805 def get_geometry(self):
806 "Return (width, height, x, y)"
807 geom = self.top.wm_geometry()
808 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000809 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000810
David Scherer7aced172000-08-15 01:13:23 +0000811 def close_event(self, event):
812 self.close()
813
814 def maybesave(self):
815 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000816 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000817 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000818 self.top.deiconify()
819 self.top.lower()
820 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000821 return self.io.maybesave()
822
823 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000824 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000825 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000826 self._close()
827 return reply
828
829 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000830 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000831 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000832 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000833 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000834 self.io.close()
835 self.io = None
836 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000837 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000838 self.color.close(False)
839 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000840 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000841 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000842 self.per.close()
843 self.per = None
844 self.top.destroy()
845 if self.close_hook:
846 # unless override: unregister from flist, terminate if last window
847 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000848
849 def load_extensions(self):
850 self.extensions = {}
851 self.load_standard_extensions()
852
853 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000854 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000855 if hasattr(ins, "close"):
856 ins.close()
857 self.extensions = {}
858
859 def load_standard_extensions(self):
860 for name in self.get_standard_extension_names():
861 try:
862 self.load_extension(name)
863 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000864 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000865 traceback.print_exc()
866
867 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000868 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000869
870 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000871 try:
872 mod = __import__(name, globals(), locals(), [])
873 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000874 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000875 raise
David Scherer7aced172000-08-15 01:13:23 +0000876 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000877 keydefs = idleConf.GetExtensionBindings(name)
878 if hasattr(cls, "menudefs"):
879 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000880 ins = cls(self)
881 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000882 if keydefs:
883 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000884 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000885 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000886 while methodname[:1] == '<':
887 methodname = methodname[1:]
888 while methodname[-1:] == '>':
889 methodname = methodname[:-1]
890 methodname = methodname + "_event"
891 if hasattr(ins, methodname):
892 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000893
894 def apply_bindings(self, keydefs=None):
895 if keydefs is None:
896 keydefs = self.Bindings.default_keydefs
897 text = self.text
898 text.keydefs = keydefs
899 for event, keylist in keydefs.items():
900 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000901 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000902
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000903 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000904 """Add appropriate entries to the menus and submenus
905
906 Menus that are absent or None in self.menudict are ignored.
907 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000908 if menudefs is None:
909 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000910 if keydefs is None:
911 keydefs = self.Bindings.default_keydefs
912 menudict = self.menudict
913 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000914 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000915 menu = menudict.get(mname)
916 if not menu:
917 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000918 for entry in entrylist:
919 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000920 menu.add_separator()
921 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000922 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000923 checkbutton = (label[:1] == '!')
924 if checkbutton:
925 label = label[1:]
926 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000927 accelerator = get_accelerator(keydefs, eventname)
928 def command(text=text, eventname=eventname):
929 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000930 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000931 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000932 menu.add_checkbutton(label=label, underline=underline,
933 command=command, accelerator=accelerator,
934 variable=var)
935 else:
936 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000937 command=command,
938 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000939
940 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000941 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +0000942 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000943 value = var.get()
944 return value
945 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000946 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +0000947
948 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000949 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +0000950 if var:
951 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000952 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000953 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +0000954
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000955 def get_var_obj(self, name, vartype=None):
956 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +0000957 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000958 # create a Tkinter variable object with self.text as master:
959 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000960 return var
961
962 # Tk implementations of "virtual text methods" -- each platform
963 # reusing IDLE's support code needs to define these for its GUI's
964 # flavor of widget.
965
966 # Is character at text_index in a Python string? Return 0 for
967 # "guaranteed no", true for anything else. This info is expensive
968 # to compute ab initio, but is probably already known by the
969 # platform's colorizer.
970
971 def is_char_in_string(self, text_index):
972 if self.color:
973 # Return true iff colorizer hasn't (re)gotten this far
974 # yet, or the character is tagged as being in a string
975 return self.text.tag_prevrange("TODO", text_index) or \
976 "STRING" in self.text.tag_names(text_index)
977 else:
978 # The colorizer is missing: assume the worst
979 return 1
980
981 # If a selection is defined in the text widget, return (start,
982 # end) as Tkinter text indices, otherwise return (None, None)
983 def get_selection_indices(self):
984 try:
985 first = self.text.index("sel.first")
986 last = self.text.index("sel.last")
987 return first, last
988 except TclError:
989 return None, None
990
991 # Return the text widget's current view of what a tab stop means
992 # (equivalent width in spaces).
993
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000994 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +0000995 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
996 return int(current)
997
998 # Set the text widget's current view of what a tab stop means.
999
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001000 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +00001001 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001002 if self.get_tk_tabwidth() != newtabwidth:
1003 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001004 pixels = text.tk.call("font", "measure", text["font"],
1005 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001006 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001007 text.configure(tabs=pixels)
1008
Guido van Rossum33d26892007-08-05 15:29:28 +00001009### begin autoindent code ### (configuration was moved to beginning of class)
1010
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001011 def set_indentation_params(self, is_py_src, guess=True):
1012 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001013 i = self.guess_indent()
1014 if 2 <= i <= 8:
1015 self.indentwidth = i
1016 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001017 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001018 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001019
1020 def smart_backspace_event(self, event):
1021 text = self.text
1022 first, last = self.get_selection_indices()
1023 if first and last:
1024 text.delete(first, last)
1025 text.mark_set("insert", first)
1026 return "break"
1027 # Delete whitespace left, until hitting a real char or closest
1028 # preceding virtual tab stop.
1029 chars = text.get("insert linestart", "insert")
1030 if chars == '':
1031 if text.compare("insert", ">", "1.0"):
1032 # easy: delete preceding newline
1033 text.delete("insert-1c")
1034 else:
1035 text.bell() # at start of buffer
1036 return "break"
1037 if chars[-1] not in " \t":
1038 # easy: delete preceding real char
1039 text.delete("insert-1c")
1040 return "break"
1041 # Ick. It may require *inserting* spaces if we back up over a
1042 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001043 tabwidth = self.tabwidth
1044 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001045 assert have > 0
1046 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001047 # Debug prompt is multilined....
1048 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001049 ncharsdeleted = 0
1050 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001051 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001052 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001053 chars = chars[:-1]
1054 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001055 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001056 if have <= want or chars[-1] not in " \t":
1057 break
1058 text.undo_block_start()
1059 text.delete("insert-%dc" % ncharsdeleted, "insert")
1060 if have < want:
1061 text.insert("insert", ' ' * (want - have))
1062 text.undo_block_stop()
1063 return "break"
1064
1065 def smart_indent_event(self, event):
1066 # if intraline selection:
1067 # delete it
1068 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001069 # do indent-region
1070 # else:
1071 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001072 text = self.text
1073 first, last = self.get_selection_indices()
1074 text.undo_block_start()
1075 try:
1076 if first and last:
1077 if index2line(first) != index2line(last):
1078 return self.indent_region_event(event)
1079 text.delete(first, last)
1080 text.mark_set("insert", first)
1081 prefix = text.get("insert linestart", "insert")
1082 raw, effective = classifyws(prefix, self.tabwidth)
1083 if raw == len(prefix):
1084 # only whitespace to the left
1085 self.reindent_to(effective + self.indentwidth)
1086 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001087 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001088 if self.usetabs:
1089 pad = '\t'
1090 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001091 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001092 n = self.indentwidth
1093 pad = ' ' * (n - effective % n)
1094 text.insert("insert", pad)
1095 text.see("insert")
1096 return "break"
1097 finally:
1098 text.undo_block_stop()
1099
1100 def newline_and_indent_event(self, event):
1101 text = self.text
1102 first, last = self.get_selection_indices()
1103 text.undo_block_start()
1104 try:
1105 if first and last:
1106 text.delete(first, last)
1107 text.mark_set("insert", first)
1108 line = text.get("insert linestart", "insert")
1109 i, n = 0, len(line)
1110 while i < n and line[i] in " \t":
1111 i = i+1
1112 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001113 # the cursor is in or at leading indentation in a continuation
1114 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001115 text.insert("insert linestart", '\n')
1116 return "break"
1117 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001118 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001119 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001120 last_line_of_prompt = sys.ps1.split('\n')[-1]
1121 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001122 line = line[:-1]
1123 i = i+1
1124 if i:
1125 text.delete("insert - %d chars" % i, "insert")
1126 # strip whitespace after insert point
1127 while text.get("insert") in " \t":
1128 text.delete("insert")
1129 # start new line
1130 text.insert("insert", '\n')
1131
1132 # adjust indentation for continuations and block
1133 # open/close first need to find the last stmt
1134 lno = index2line(text.index('insert'))
1135 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001136 if not self.context_use_ps1:
1137 for context in self.num_context_lines:
1138 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001139 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001140 rawtext = text.get(startatindex, "insert")
1141 y.set_str(rawtext)
1142 bod = y.find_good_parse_start(
1143 self.context_use_ps1,
1144 self._build_char_in_string_func(startatindex))
1145 if bod is not None or startat == 1:
1146 break
1147 y.set_lo(bod or 0)
1148 else:
1149 r = text.tag_prevrange("console", "insert")
1150 if r:
1151 startatindex = r[1]
1152 else:
1153 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001154 rawtext = text.get(startatindex, "insert")
1155 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001156 y.set_lo(0)
1157
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001158 c = y.get_continuation_type()
1159 if c != PyParse.C_NONE:
1160 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001161 if c == PyParse.C_STRING_FIRST_LINE:
1162 # after the first line of a string; do not indent at all
1163 pass
1164 elif c == PyParse.C_STRING_NEXT_LINES:
1165 # inside a string which started before this line;
1166 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001167 text.insert("insert", indent)
1168 elif c == PyParse.C_BRACKET:
1169 # line up with the first (if any) element of the
1170 # last open bracket structure; else indent one
1171 # level beyond the indent of the line with the
1172 # last open bracket
1173 self.reindent_to(y.compute_bracket_indent())
1174 elif c == PyParse.C_BACKSLASH:
1175 # if more than one line in this stmt already, just
1176 # mimic the current indent; else if initial line
1177 # has a start on an assignment stmt, indent to
1178 # beyond leftmost =; else to beyond first chunk of
1179 # non-whitespace on initial line
1180 if y.get_num_lines_in_stmt() > 1:
1181 text.insert("insert", indent)
1182 else:
1183 self.reindent_to(y.compute_backslash_indent())
1184 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001185 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001186 return "break"
1187
1188 # This line starts a brand new stmt; indent relative to
1189 # indentation of initial line of closest preceding
1190 # interesting stmt.
1191 indent = y.get_base_indent_string()
1192 text.insert("insert", indent)
1193 if y.is_block_opener():
1194 self.smart_indent_event(event)
1195 elif indent and y.is_block_closer():
1196 self.smart_backspace_event(event)
1197 return "break"
1198 finally:
1199 text.see("insert")
1200 text.undo_block_stop()
1201
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001202 # Our editwin provides a is_char_in_string function that works
1203 # with a Tk text index, but PyParse only knows about offsets into
1204 # a string. This builds a function for PyParse that accepts an
1205 # offset.
1206
1207 def _build_char_in_string_func(self, startindex):
1208 def inner(offset, _startindex=startindex,
1209 _icis=self.is_char_in_string):
1210 return _icis(_startindex + "+%dc" % offset)
1211 return inner
1212
1213 def indent_region_event(self, event):
1214 head, tail, chars, lines = self.get_region()
1215 for pos in range(len(lines)):
1216 line = lines[pos]
1217 if line:
1218 raw, effective = classifyws(line, self.tabwidth)
1219 effective = effective + self.indentwidth
1220 lines[pos] = self._make_blanks(effective) + line[raw:]
1221 self.set_region(head, tail, chars, lines)
1222 return "break"
1223
1224 def dedent_region_event(self, event):
1225 head, tail, chars, lines = self.get_region()
1226 for pos in range(len(lines)):
1227 line = lines[pos]
1228 if line:
1229 raw, effective = classifyws(line, self.tabwidth)
1230 effective = max(effective - self.indentwidth, 0)
1231 lines[pos] = self._make_blanks(effective) + line[raw:]
1232 self.set_region(head, tail, chars, lines)
1233 return "break"
1234
1235 def comment_region_event(self, event):
1236 head, tail, chars, lines = self.get_region()
1237 for pos in range(len(lines) - 1):
1238 line = lines[pos]
1239 lines[pos] = '##' + line
1240 self.set_region(head, tail, chars, lines)
1241
1242 def uncomment_region_event(self, event):
1243 head, tail, chars, lines = self.get_region()
1244 for pos in range(len(lines)):
1245 line = lines[pos]
1246 if not line:
1247 continue
1248 if line[:2] == '##':
1249 line = line[2:]
1250 elif line[:1] == '#':
1251 line = line[1:]
1252 lines[pos] = line
1253 self.set_region(head, tail, chars, lines)
1254
1255 def tabify_region_event(self, event):
1256 head, tail, chars, lines = self.get_region()
1257 tabwidth = self._asktabwidth()
1258 for pos in range(len(lines)):
1259 line = lines[pos]
1260 if line:
1261 raw, effective = classifyws(line, tabwidth)
1262 ntabs, nspaces = divmod(effective, tabwidth)
1263 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1264 self.set_region(head, tail, chars, lines)
1265
1266 def untabify_region_event(self, event):
1267 head, tail, chars, lines = self.get_region()
1268 tabwidth = self._asktabwidth()
1269 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001270 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001271 self.set_region(head, tail, chars, lines)
1272
1273 def toggle_tabs_event(self, event):
1274 if self.askyesno(
1275 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001276 "Turn tabs " + ("on", "off")[self.usetabs] +
1277 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001278 ("will be", "remains at")[self.usetabs] + " 8." +
1279 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001280 parent=self.text):
1281 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001282 # Try to prevent inconsistent indentation.
1283 # User must change indent width manually after using tabs.
1284 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001285 return "break"
1286
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001287 # XXX this isn't bound to anything -- see tabwidth comments
1288## def change_tabwidth_event(self, event):
1289## new = self._asktabwidth()
1290## if new != self.tabwidth:
1291## self.tabwidth = new
1292## self.set_indentation_params(0, guess=0)
1293## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001294
1295 def change_indentwidth_event(self, event):
1296 new = self.askinteger(
1297 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001298 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001299 parent=self.text,
1300 initialvalue=self.indentwidth,
1301 minvalue=2,
1302 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001303 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001304 self.indentwidth = new
1305 return "break"
1306
1307 def get_region(self):
1308 text = self.text
1309 first, last = self.get_selection_indices()
1310 if first and last:
1311 head = text.index(first + " linestart")
1312 tail = text.index(last + "-1c lineend +1c")
1313 else:
1314 head = text.index("insert linestart")
1315 tail = text.index("insert lineend +1c")
1316 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001317 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001318 return head, tail, chars, lines
1319
1320 def set_region(self, head, tail, chars, lines):
1321 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001322 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001323 if newchars == chars:
1324 text.bell()
1325 return
1326 text.tag_remove("sel", "1.0", "end")
1327 text.mark_set("insert", head)
1328 text.undo_block_start()
1329 text.delete(head, tail)
1330 text.insert(head, newchars)
1331 text.undo_block_stop()
1332 text.tag_add("sel", head, "insert")
1333
1334 # Make string that displays as n leading blanks.
1335
1336 def _make_blanks(self, n):
1337 if self.usetabs:
1338 ntabs, nspaces = divmod(n, self.tabwidth)
1339 return '\t' * ntabs + ' ' * nspaces
1340 else:
1341 return ' ' * n
1342
1343 # Delete from beginning of line to insert point, then reinsert
1344 # column logical (meaning use tabs if appropriate) spaces.
1345
1346 def reindent_to(self, column):
1347 text = self.text
1348 text.undo_block_start()
1349 if text.compare("insert linestart", "!=", "insert"):
1350 text.delete("insert linestart", "insert")
1351 if column:
1352 text.insert("insert", self._make_blanks(column))
1353 text.undo_block_stop()
1354
1355 def _asktabwidth(self):
1356 return self.askinteger(
1357 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001358 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001359 parent=self.text,
1360 initialvalue=self.indentwidth,
1361 minvalue=2,
1362 maxvalue=16) or self.tabwidth
1363
1364 # Guess indentwidth from text content.
1365 # Return guessed indentwidth. This should not be believed unless
1366 # it's in a reasonable range (e.g., it will be 0 if no indented
1367 # blocks are found).
1368
1369 def guess_indent(self):
1370 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1371 if opener and indented:
1372 raw, indentsmall = classifyws(opener, self.tabwidth)
1373 raw, indentlarge = classifyws(indented, self.tabwidth)
1374 else:
1375 indentsmall = indentlarge = 0
1376 return indentlarge - indentsmall
1377
1378# "line.col" -> line, as an int
1379def index2line(index):
1380 return int(float(index))
1381
1382# Look at the leading whitespace in s.
1383# Return pair (# of leading ws characters,
1384# effective # of leading blanks after expanding
1385# tabs to width tabwidth)
1386
1387def classifyws(s, tabwidth):
1388 raw = effective = 0
1389 for ch in s:
1390 if ch == ' ':
1391 raw = raw + 1
1392 effective = effective + 1
1393 elif ch == '\t':
1394 raw = raw + 1
1395 effective = (effective // tabwidth + 1) * tabwidth
1396 else:
1397 break
1398 return raw, effective
1399
1400import tokenize
1401_tokenize = tokenize
1402del tokenize
1403
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001404class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001405
1406 # .run() chews over the Text widget, looking for a block opener
1407 # and the stmt following it. Returns a pair,
1408 # (line containing block opener, line containing stmt)
1409 # Either or both may be None.
1410
1411 def __init__(self, text, tabwidth):
1412 self.text = text
1413 self.tabwidth = tabwidth
1414 self.i = self.finished = 0
1415 self.blkopenline = self.indentedline = None
1416
1417 def readline(self):
1418 if self.finished:
1419 return ""
1420 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001421 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001422 if self.text.compare(mark, ">=", "end"):
1423 return ""
1424 return self.text.get(mark, mark + " lineend+1c")
1425
1426 def tokeneater(self, type, token, start, end, line,
1427 INDENT=_tokenize.INDENT,
1428 NAME=_tokenize.NAME,
1429 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1430 if self.finished:
1431 pass
1432 elif type == NAME and token in OPENERS:
1433 self.blkopenline = line
1434 elif type == INDENT and self.blkopenline:
1435 self.indentedline = line
1436 self.finished = 1
1437
1438 def run(self):
1439 save_tabsize = _tokenize.tabsize
1440 _tokenize.tabsize = self.tabwidth
1441 try:
1442 try:
1443 _tokenize.tokenize(self.readline, self.tokeneater)
1444 except _tokenize.TokenError:
1445 # since we cut off the tokenizer early, we can trigger
1446 # spurious errors
1447 pass
1448 finally:
1449 _tokenize.tabsize = save_tabsize
1450 return self.blkopenline, self.indentedline
1451
1452### end autoindent code ###
1453
David Scherer7aced172000-08-15 01:13:23 +00001454def prepstr(s):
1455 # Helper to extract the underscore from a string, e.g.
1456 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001457 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001458 if i >= 0:
1459 s = s[:i] + s[i+1:]
1460 return i, s
1461
1462
1463keynames = {
1464 'bracketleft': '[',
1465 'bracketright': ']',
1466 'slash': '/',
1467}
1468
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001469def get_accelerator(keydefs, eventname):
1470 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001471 if not keylist:
1472 return ""
1473 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001474 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001475 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1476 s = re.sub("Key-", "", s)
1477 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1478 s = re.sub("Control-", "Ctrl-", s)
1479 s = re.sub("-", "+", s)
1480 s = re.sub("><", " ", s)
1481 s = re.sub("<", "", s)
1482 s = re.sub(">", "", s)
1483 return s
1484
1485
1486def fixwordbreaks(root):
1487 # Make sure that Tk's double-click and next/previous word
1488 # operations use our definition of a word (i.e. an identifier)
1489 tk = root.tk
1490 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1491 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1492 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1493
1494
1495def test():
1496 root = Tk()
1497 fixwordbreaks(root)
1498 root.withdraw()
1499 if sys.argv[1:]:
1500 filename = sys.argv[1]
1501 else:
1502 filename = None
1503 edit = EditorWindow(root=root, filename=filename)
1504 edit.set_close_hook(root.quit)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001505 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001506 root.mainloop()
1507 root.destroy()
1508
1509if __name__ == '__main__':
1510 test()