blob: 2b5c5270e0ac6adaca0800df11bd073076a253c6 [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',
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000112 width=self.width,
Steven M. Gavadc72f482002-01-03 11:51:07 +0000113 height=idleConf.GetOption('main','EditorWindow','height') )
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000114 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000115
116 self.createmenubar()
117 self.apply_bindings()
118
119 self.top.protocol("WM_DELETE_WINDOW", self.close)
120 self.top.bind("<<close-window>>", self.close_event)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000121 if macosxSupport.runningAsOSXApp():
122 # Command-W on editorwindows doesn't work without this.
123 text.bind('<<close-window>>', self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000124 text.bind("<<cut>>", self.cut)
125 text.bind("<<copy>>", self.copy)
126 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000127 text.bind("<<center-insert>>", self.center_insert_event)
128 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000129 text.bind("<<python-docs>>", self.python_docs)
130 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000131 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000132 text.bind("<<open-module>>", self.open_module)
133 text.bind("<<do-nothing>>", lambda event: "break")
134 text.bind("<<select-all>>", self.select_all)
135 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000136 text.bind("<<find>>", self.find_event)
137 text.bind("<<find-again>>", self.find_again_event)
138 text.bind("<<find-in-files>>", self.find_in_files_event)
139 text.bind("<<find-selection>>", self.find_selection_event)
140 text.bind("<<replace>>", self.replace_event)
141 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000142 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000143 text.bind("<<smart-backspace>>",self.smart_backspace_event)
144 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
145 text.bind("<<smart-indent>>",self.smart_indent_event)
146 text.bind("<<indent-region>>",self.indent_region_event)
147 text.bind("<<dedent-region>>",self.dedent_region_event)
148 text.bind("<<comment-region>>",self.comment_region_event)
149 text.bind("<<uncomment-region>>",self.uncomment_region_event)
150 text.bind("<<tabify-region>>",self.tabify_region_event)
151 text.bind("<<untabify-region>>",self.untabify_region_event)
152 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
153 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000154 text.bind("<Left>", self.move_at_edge_if_selection(0))
155 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000156 text.bind("<<del-word-left>>", self.del_word_left)
157 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000158
David Scherer7aced172000-08-15 01:13:23 +0000159 if flist:
160 flist.inversedict[self] = key
161 if key:
162 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000163 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000164 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
165 text.bind("<<open-class-browser>>", self.open_class_browser)
166 text.bind("<<open-path-browser>>", self.open_path_browser)
167
Steven M. Gava898a3652001-10-07 11:10:44 +0000168 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000169 vbar['command'] = text.yview
170 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000171 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000172 fontWeight = 'normal'
173 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000174 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000175 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
176 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
177 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000178 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
179 text.pack(side=TOP, fill=BOTH, expand=1)
180 text.focus_set()
181
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000182 # usetabs true -> literal tab characters are used by indent and
183 # dedent cmds, possibly mixed with spaces if
184 # indentwidth is not a multiple of tabwidth,
185 # which will cause Tabnanny to nag!
186 # false -> tab characters are converted to spaces by indent
187 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000188 # Although use-spaces=0 can be configured manually in config-main.def,
189 # configuration of tabs v. spaces is not supported in the configuration
190 # dialog. IDLE promotes the preferred Python indentation: use spaces!
191 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
192 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000193
194 # tabwidth is the display width of a literal tab character.
195 # CAUTION: telling Tk to use anything other than its default
196 # tab setting causes it to use an entirely different tabbing algorithm,
197 # treating tab stops as fixed distances from the left margin.
198 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000199 self.tabwidth = 8 # must remain 8 until Tk is fixed.
200
201 # indentwidth is the number of screen characters per indent level.
202 # The recommended Python indentation is four spaces.
203 self.indentwidth = self.tabwidth
204 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000205
206 # If context_use_ps1 is true, parsing searches back for a ps1 line;
207 # else searches for a popular (if, def, ...) Python stmt.
208 self.context_use_ps1 = False
209
210 # When searching backwards for a reliable place to begin parsing,
211 # first start num_context_lines[0] lines back, then
212 # num_context_lines[1] lines back if that didn't work, and so on.
213 # The last value should be huge (larger than the # of lines in a
214 # conceivable file).
215 # Making the initial values larger slows things down more often.
216 self.num_context_lines = 50, 500, 5000000
David Scherer7aced172000-08-15 01:13:23 +0000217 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000218 self.undo = undo = self.UndoDelegator()
219 per.insertfilter(undo)
220 text.undo_block_start = undo.undo_block_start
221 text.undo_block_stop = undo.undo_block_stop
222 undo.set_saved_change_hook(self.saved_change_hook)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000223 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000224 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000225 io.set_filename_change_hook(self.filename_change_hook)
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000226 self.good_load = False
227 self.set_indentation_params(False)
Christian Heimesa156e092008-02-16 07:38:31 +0000228 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000229 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000230 if os.path.exists(filename) and not os.path.isdir(filename):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000231 if io.loadfile(filename):
232 self.good_load = True
233 is_py_src = self.ispythonsource(filename)
234 self.set_indentation_params(is_py_src)
235 if is_py_src:
236 self.color = color = self.ColorDelegator()
237 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000238 else:
239 io.set_filename(filename)
Christian Heimesa156e092008-02-16 07:38:31 +0000240 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000241 self.saved_change_hook()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000242 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000243 self.load_extensions()
David Scherer7aced172000-08-15 01:13:23 +0000244 menu = self.menudict.get('windows')
245 if menu:
246 end = menu.index("end")
247 if end is None:
248 end = -1
249 if end >= 0:
250 menu.add_separator()
251 end = end + 1
252 self.wmenu_end = end
253 WindowList.register_callback(self.postwindowsmenu)
254
255 # Some abstractions so IDLE extensions are cross-IDE
256 self.askyesno = tkMessageBox.askyesno
257 self.askinteger = tkSimpleDialog.askinteger
258 self.showerror = tkMessageBox.showerror
259
Martin v. Löwis307021f2005-11-27 16:59:04 +0000260 def _filename_to_unicode(self, filename):
261 """convert filename to unicode in order to display it in Tk"""
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000262 if isinstance(filename, str) or not filename:
Martin v. Löwis307021f2005-11-27 16:59:04 +0000263 return filename
264 else:
265 try:
266 return filename.decode(self.filesystemencoding)
267 except UnicodeDecodeError:
268 # XXX
269 try:
270 return filename.decode(self.encoding)
271 except UnicodeDecodeError:
272 # byte-to-byte conversion
273 return filename.decode('iso8859-1')
274
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000275 def new_callback(self, event):
276 dirname, basename = self.io.defaultfilename()
277 self.flist.new(dirname)
278 return "break"
279
David Scherer7aced172000-08-15 01:13:23 +0000280 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000281 self.status_bar = self.MultiStatusBar(self.top)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000282 if macosxSupport.runningAsOSXApp():
283 # Insert some padding to avoid obscuring some of the statusbar
284 # by the resize widget.
285 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000286 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
287 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
288 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000289 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
290 self.text.event_add("<<set-line-and-column>>",
291 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000292 self.text.after_idle(self.set_line_and_column)
293
294 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000295 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000296 self.status_bar.set_label('column', 'Col: %s' % column)
297 self.status_bar.set_label('line', 'Ln: %s' % line)
298
David Scherer7aced172000-08-15 01:13:23 +0000299 menu_specs = [
300 ("file", "_File"),
301 ("edit", "_Edit"),
302 ("format", "F_ormat"),
303 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000304 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000305 ("windows", "_Windows"),
306 ("help", "_Help"),
307 ]
308
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000309 if macosxSupport.runningAsOSXApp():
310 del menu_specs[-3]
311 menu_specs[-2] = ("windows", "_Window")
312
313
David Scherer7aced172000-08-15 01:13:23 +0000314 def createmenubar(self):
315 mbar = self.menubar
316 self.menudict = menudict = {}
317 for name, label in self.menu_specs:
318 underline, label = prepstr(label)
319 menudict[name] = menu = Menu(mbar, name=name)
320 mbar.add_cascade(label=label, menu=menu, underline=underline)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000321 if sys.platform == 'darwin' and '.framework' in sys.executable:
322 # Insert the application menu
323 menudict['application'] = menu = Menu(mbar, name='apple')
324 mbar.add_cascade(label='IDLE', menu=menu)
David Scherer7aced172000-08-15 01:13:23 +0000325 self.fill_menus()
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000326 self.recent_files_menu = Menu(self.menubar)
327 self.menudict['file'].insert_cascade(3, label='Recent Files',
328 underline=0,
329 menu=self.recent_files_menu)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000330 self.base_helpmenu_length = self.menudict['help'].index(END)
331 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000332
333 def postwindowsmenu(self):
334 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000335 menu = self.menudict['windows']
336 end = menu.index("end")
337 if end is None:
338 end = -1
339 if end > self.wmenu_end:
340 menu.delete(self.wmenu_end+1, end)
341 WindowList.add_windows_to_menu(menu)
342
343 rmenu = None
344
345 def right_menu_event(self, event):
346 self.text.tag_remove("sel", "1.0", "end")
347 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
348 if not self.rmenu:
349 self.make_rmenu()
350 rmenu = self.rmenu
351 self.event = event
352 iswin = sys.platform[:3] == 'win'
353 if iswin:
354 self.text.config(cursor="arrow")
355 rmenu.tk_popup(event.x_root, event.y_root)
356 if iswin:
357 self.text.config(cursor="ibeam")
358
359 rmenu_specs = [
360 # ("Label", "<<virtual-event>>"), ...
361 ("Close", "<<close-window>>"), # Example
362 ]
363
364 def make_rmenu(self):
365 rmenu = Menu(self.text, tearoff=0)
366 for label, eventname in self.rmenu_specs:
367 def command(text=self.text, eventname=eventname):
368 text.event_generate(eventname)
369 rmenu.add_command(label=label, command=command)
370 self.rmenu = rmenu
371
372 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000373 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000374
Steven M. Gava3b55a892001-11-21 05:56:26 +0000375 def config_dialog(self, event=None):
376 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000377
David Scherer7aced172000-08-15 01:13:23 +0000378 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000379 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000380 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000381
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000382 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000383 if sys.platform[:3] == 'win':
Steven M. Gava931625d2002-04-22 00:38:26 +0000384 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000385 else:
386 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000387 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000388
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000389 def cut(self,event):
390 self.text.event_generate("<<Cut>>")
391 return "break"
392
393 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000394 if not self.text.tag_ranges("sel"):
395 # There is no selection, so do nothing and maybe interrupt.
396 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000397 self.text.event_generate("<<Copy>>")
398 return "break"
399
400 def paste(self,event):
401 self.text.event_generate("<<Paste>>")
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000402 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000403 return "break"
404
David Scherer7aced172000-08-15 01:13:23 +0000405 def select_all(self, event=None):
406 self.text.tag_add("sel", "1.0", "end-1c")
407 self.text.mark_set("insert", "1.0")
408 self.text.see("insert")
409 return "break"
410
411 def remove_selection(self, event=None):
412 self.text.tag_remove("sel", "1.0", "end")
413 self.text.see("insert")
414
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000415 def move_at_edge_if_selection(self, edge_index):
416 """Cursor move begins at start or end of selection
417
418 When a left/right cursor key is pressed create and return to Tkinter a
419 function which causes a cursor move from the associated edge of the
420 selection.
421
422 """
423 self_text_index = self.text.index
424 self_text_mark_set = self.text.mark_set
425 edges_table = ("sel.first+1c", "sel.last-1c")
426 def move_at_edge(event):
427 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
428 try:
429 self_text_index("sel.first")
430 self_text_mark_set("insert", edges_table[edge_index])
431 except TclError:
432 pass
433 return move_at_edge
434
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000435 def del_word_left(self, event):
436 self.text.event_generate('<Meta-Delete>')
437 return "break"
438
439 def del_word_right(self, event):
440 self.text.event_generate('<Meta-d>')
441 return "break"
442
Steven M. Gavac5976402002-01-04 03:06:08 +0000443 def find_event(self, event):
444 SearchDialog.find(self.text)
445 return "break"
446
447 def find_again_event(self, event):
448 SearchDialog.find_again(self.text)
449 return "break"
450
451 def find_selection_event(self, event):
452 SearchDialog.find_selection(self.text)
453 return "break"
454
455 def find_in_files_event(self, event):
456 GrepDialog.grep(self.text, self.io, self.flist)
457 return "break"
458
459 def replace_event(self, event):
460 ReplaceDialog.replace(self.text)
461 return "break"
462
463 def goto_line_event(self, event):
464 text = self.text
465 lineno = tkSimpleDialog.askinteger("Goto",
466 "Go to line number:",parent=text)
467 if lineno is None:
468 return "break"
469 if lineno <= 0:
470 text.bell()
471 return "break"
472 text.mark_set("insert", "%d.0" % lineno)
473 text.see("insert")
474
David Scherer7aced172000-08-15 01:13:23 +0000475 def open_module(self, event=None):
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000476 # XXX Shouldn't this be in IOBinding?
David Scherer7aced172000-08-15 01:13:23 +0000477 try:
478 name = self.text.get("sel.first", "sel.last")
479 except TclError:
480 name = ""
481 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000482 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000483 name = tkSimpleDialog.askstring("Module",
484 "Enter the name of a Python module\n"
485 "to search on sys.path and open:",
486 parent=self.text, initialvalue=name)
487 if name:
488 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000489 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000490 return
David Scherer7aced172000-08-15 01:13:23 +0000491 # XXX Ought to insert current file's directory in front of path
492 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000493 (f, file, (suffix, mode, type)) = _find_module(name)
Guido van Rossumb940e112007-01-10 16:19:56 +0000494 except (NameError, ImportError) as msg:
David Scherer7aced172000-08-15 01:13:23 +0000495 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
496 return
497 if type != imp.PY_SOURCE:
498 tkMessageBox.showerror("Unsupported type",
499 "%s is not a source module" % name, parent=self.text)
500 return
501 if f:
502 f.close()
503 if self.flist:
504 self.flist.open(file)
505 else:
506 self.io.loadfile(file)
507
508 def open_class_browser(self, event=None):
509 filename = self.io.filename
510 if not filename:
511 tkMessageBox.showerror(
512 "No filename",
513 "This buffer has no associated filename",
514 master=self.text)
515 self.text.focus_set()
516 return None
517 head, tail = os.path.split(filename)
518 base, ext = os.path.splitext(tail)
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000519 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000520 ClassBrowser.ClassBrowser(self.flist, base, [head])
521
522 def open_path_browser(self, event=None):
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +0000523 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000524 PathBrowser.PathBrowser(self.flist)
525
526 def gotoline(self, lineno):
527 if lineno is not None and lineno > 0:
528 self.text.mark_set("insert", "%d.0" % lineno)
529 self.text.tag_remove("sel", "1.0", "end")
530 self.text.tag_add("sel", "insert", "insert +1l")
531 self.center()
532
533 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000534 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000535 return True
David Scherer7aced172000-08-15 01:13:23 +0000536 base, ext = os.path.splitext(os.path.basename(filename))
537 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000538 return True
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000539 line = self.text.get('1.0', '1.0 lineend')
540 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000541
542 def close_hook(self):
543 if self.flist:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000544 self.flist.unregister_maybe_terminate(self)
545 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000546
547 def set_close_hook(self, close_hook):
548 self.close_hook = close_hook
549
550 def filename_change_hook(self):
551 if self.flist:
552 self.flist.filename_changed_edit(self)
553 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000554 self.top.update_windowlist_registry(self)
Christian Heimesa156e092008-02-16 07:38:31 +0000555 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000556
Christian Heimesa156e092008-02-16 07:38:31 +0000557 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000558 if self.color:
559 return
Christian Heimesa156e092008-02-16 07:38:31 +0000560 if self.ispythonsource(self.io.filename):
561 self.color = self.ColorDelegator()
562 # can add more colorizers here...
563 if self.color:
564 self.per.removefilter(self.undo)
565 self.per.insertfilter(self.color)
566 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000567
Christian Heimesa156e092008-02-16 07:38:31 +0000568 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000569 if not self.color:
570 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000571 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000572 self.per.removefilter(self.color)
573 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000574
Steven M. Gavab77d3432002-03-02 07:16:21 +0000575 def ResetColorizer(self):
Christian Heimesa156e092008-02-16 07:38:31 +0000576 "Update the colour theme"
577 # Called from self.filename_change_hook and from configDialog.py
578 self._rmcolorizer()
579 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000580 theme = idleConf.GetOption('main','Theme','name')
Christian Heimesa156e092008-02-16 07:38:31 +0000581 normal_colors = idleConf.GetHighlight(theme, 'normal')
582 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
583 select_colors = idleConf.GetHighlight(theme, 'hilite')
584 self.text.config(
585 foreground=normal_colors['foreground'],
586 background=normal_colors['background'],
587 insertbackground=cursor_color,
588 selectforeground=select_colors['foreground'],
589 selectbackground=select_colors['background'],
590 )
David Scherer7aced172000-08-15 01:13:23 +0000591
Guido van Rossum33d26892007-08-05 15:29:28 +0000592 IDENTCHARS = string.ascii_letters + string.digits + "_"
593
594 def colorize_syntax_error(self, text, pos):
595 text.tag_add("ERROR", pos)
596 char = text.get(pos)
597 if char and char in self.IDENTCHARS:
598 text.tag_add("ERROR", pos + " wordstart", pos)
599 if '\n' == text.get(pos): # error at line end
600 text.mark_set("insert", pos)
601 else:
602 text.mark_set("insert", pos + "+1c")
603 text.see(pos)
604
Steven M. Gavab1585412002-03-12 00:21:56 +0000605 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000606 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000607 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000608 fontWeight='normal'
609 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
610 fontWeight='bold'
611 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
612 idleConf.GetOption('main','EditorWindow','font-size'),
613 fontWeight))
614
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000615 def RemoveKeybindings(self):
616 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000617 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000618 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000619 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000620 self.text.event_delete(event, *keylist)
621 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000622 xkeydefs = idleConf.GetExtensionBindings(extensionName)
623 if xkeydefs:
624 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000625 self.text.event_delete(event, *keylist)
626
627 def ApplyKeybindings(self):
628 "Update the keybindings after they are changed"
629 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000630 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000631 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000632 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000633 xkeydefs = idleConf.GetExtensionBindings(extensionName)
634 if xkeydefs:
635 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000636 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000637 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000638 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000639 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000640 for item in menu[1]:
641 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000642 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000643 for menubarItem in self.menudict:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000644 menu = self.menudict[menubarItem]
645 end = menu.index(END) + 1
646 for index in range(0, end):
647 if menu.type(index) == 'command':
648 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000649 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000650 itemName = menu.entrycget(index, 'label')
651 event = ''
Guido van Rossum811c4e02006-08-22 15:45:46 +0000652 if menubarItem in menuEventDict:
653 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000654 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000655 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000656 accel = get_accelerator(keydefs, event)
657 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000658
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000659 def set_notabs_indentwidth(self):
660 "Update the indentwidth if changed and not using tabs in this window"
661 # Called from configDialog.py
662 if not self.usetabs:
663 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
664 type='int')
665
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000666 def reset_help_menu_entries(self):
667 "Update the additional help entries on the Help menu"
668 help_list = idleConf.GetAllExtraHelpSourcesList()
669 helpmenu = self.menudict['help']
670 # first delete the extra help entries, if any
671 helpmenu_length = helpmenu.index(END)
672 if helpmenu_length > self.base_helpmenu_length:
673 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
674 # then rebuild them
675 if help_list:
676 helpmenu.add_separator()
677 for entry in help_list:
678 cmd = self.__extra_help_callback(entry[1])
679 helpmenu.add_command(label=entry[0], command=cmd)
680 # and update the menu dictionary
681 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000682
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000683 def __extra_help_callback(self, helpfile):
684 "Create a callback with the helpfile value frozen at definition time"
685 def display_extra_help(helpfile=helpfile):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000686 if not helpfile.startswith(('www', 'http')):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000687 url = os.path.normpath(helpfile)
688 if sys.platform[:3] == 'win':
689 os.startfile(helpfile)
690 else:
691 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000692 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000693
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000694 def update_recent_files_list(self, new_file=None):
695 "Load and update the recent files list and menus"
696 rf_list = []
697 if os.path.exists(self.recent_files_path):
698 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000699 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000700 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000701 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000702 rf_list_file.close()
703 if new_file:
704 new_file = os.path.abspath(new_file) + '\n'
705 if new_file in rf_list:
706 rf_list.remove(new_file) # move to top
707 rf_list.insert(0, new_file)
708 # clean and save the recent files list
709 bad_paths = []
710 for path in rf_list:
711 if '\0' in path or not os.path.exists(path[0:-1]):
712 bad_paths.append(path)
713 rf_list = [path for path in rf_list if path not in bad_paths]
714 ulchars = "1234567890ABCDEFGHIJK"
715 rf_list = rf_list[0:len(ulchars)]
716 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000717 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000718 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000719 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000720 rf_file.close()
721 # for each edit window instance, construct the recent files menu
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000722 for instance in self.top.instance_dict:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000723 menu = instance.recent_files_menu
724 menu.delete(1, END) # clear, and rebuild:
725 for i, file in zip(count(), rf_list):
726 file_name = file[0:-1] # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000727 # make unicode string to display non-ASCII chars correctly
728 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000729 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000730 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000731 command=callback,
732 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000733
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000734 def __recent_file_callback(self, file_name):
735 def open_recent_file(fn_closure=file_name):
736 self.io.open(editFile=fn_closure)
737 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000738
David Scherer7aced172000-08-15 01:13:23 +0000739 def saved_change_hook(self):
740 short = self.short_title()
741 long = self.long_title()
742 if short and long:
743 title = short + " - " + long
744 elif short:
745 title = short
746 elif long:
747 title = long
748 else:
749 title = "Untitled"
750 icon = short or long or title
751 if not self.get_saved():
752 title = "*%s*" % title
753 icon = "*%s" % icon
754 self.top.wm_title(title)
755 self.top.wm_iconname(icon)
756
757 def get_saved(self):
758 return self.undo.get_saved()
759
760 def set_saved(self, flag):
761 self.undo.set_saved(flag)
762
763 def reset_undo(self):
764 self.undo.reset_undo()
765
766 def short_title(self):
767 filename = self.io.filename
768 if filename:
769 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000770 # return unicode string to display non-ASCII chars correctly
771 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000772
773 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000774 # return unicode string to display non-ASCII chars correctly
775 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000776
777 def center_insert_event(self, event):
778 self.center()
779
780 def center(self, mark="insert"):
781 text = self.text
782 top, bot = self.getwindowlines()
783 lineno = self.getlineno(mark)
784 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000785 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000786 text.yview(float(newtop))
787
788 def getwindowlines(self):
789 text = self.text
790 top = self.getlineno("@0,0")
791 bot = self.getlineno("@0,65535")
792 if top == bot and text.winfo_height() == 1:
793 # Geometry manager hasn't run yet
794 height = int(text['height'])
795 bot = top + height - 1
796 return top, bot
797
798 def getlineno(self, mark="insert"):
799 text = self.text
800 return int(float(text.index(mark)))
801
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000802 def get_geometry(self):
803 "Return (width, height, x, y)"
804 geom = self.top.wm_geometry()
805 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
Kurt B. Kaiser66aaf742007-08-09 18:00:23 +0000806 return list(map(int, m.groups()))
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000807
David Scherer7aced172000-08-15 01:13:23 +0000808 def close_event(self, event):
809 self.close()
810
811 def maybesave(self):
812 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000813 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000814 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000815 self.top.deiconify()
816 self.top.lower()
817 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000818 return self.io.maybesave()
819
820 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000821 reply = self.maybesave()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000822 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000823 self._close()
824 return reply
825
826 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000827 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000828 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000829 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000830 self.unload_extensions()
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000831 self.io.close()
832 self.io = None
833 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000834 if self.color:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000835 self.color.close(False)
836 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000837 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000838 self.tkinter_vars = None
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000839 self.per.close()
840 self.per = None
841 self.top.destroy()
842 if self.close_hook:
843 # unless override: unregister from flist, terminate if last window
844 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000845
846 def load_extensions(self):
847 self.extensions = {}
848 self.load_standard_extensions()
849
850 def unload_extensions(self):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000851 for ins in list(self.extensions.values()):
David Scherer7aced172000-08-15 01:13:23 +0000852 if hasattr(ins, "close"):
853 ins.close()
854 self.extensions = {}
855
856 def load_standard_extensions(self):
857 for name in self.get_standard_extension_names():
858 try:
859 self.load_extension(name)
860 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000861 print("Failed to load extension", repr(name))
David Scherer7aced172000-08-15 01:13:23 +0000862 traceback.print_exc()
863
864 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000865 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000866
867 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000868 try:
869 mod = __import__(name, globals(), locals(), [])
870 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000871 print("\nFailed to import extension: ", name)
Guido van Rossum36e0a922007-07-20 04:05:57 +0000872 raise
David Scherer7aced172000-08-15 01:13:23 +0000873 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000874 keydefs = idleConf.GetExtensionBindings(name)
875 if hasattr(cls, "menudefs"):
876 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000877 ins = cls(self)
878 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000879 if keydefs:
880 self.apply_bindings(keydefs)
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000881 for vevent in keydefs:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000882 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000883 while methodname[:1] == '<':
884 methodname = methodname[1:]
885 while methodname[-1:] == '>':
886 methodname = methodname[:-1]
887 methodname = methodname + "_event"
888 if hasattr(ins, methodname):
889 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000890
891 def apply_bindings(self, keydefs=None):
892 if keydefs is None:
893 keydefs = self.Bindings.default_keydefs
894 text = self.text
895 text.keydefs = keydefs
896 for event, keylist in keydefs.items():
897 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000898 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000899
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000900 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000901 """Add appropriate entries to the menus and submenus
902
903 Menus that are absent or None in self.menudict are ignored.
904 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000905 if menudefs is None:
906 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000907 if keydefs is None:
908 keydefs = self.Bindings.default_keydefs
909 menudict = self.menudict
910 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000911 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000912 menu = menudict.get(mname)
913 if not menu:
914 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000915 for entry in entrylist:
916 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000917 menu.add_separator()
918 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000919 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000920 checkbutton = (label[:1] == '!')
921 if checkbutton:
922 label = label[1:]
923 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000924 accelerator = get_accelerator(keydefs, eventname)
925 def command(text=text, eventname=eventname):
926 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000927 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000928 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000929 menu.add_checkbutton(label=label, underline=underline,
930 command=command, accelerator=accelerator,
931 variable=var)
932 else:
933 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000934 command=command,
935 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000936
937 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000938 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +0000939 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000940 value = var.get()
941 return value
942 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000943 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +0000944
945 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000946 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +0000947 if var:
948 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000949 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000950 raise NameError(name)
David Scherer7aced172000-08-15 01:13:23 +0000951
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000952 def get_var_obj(self, name, vartype=None):
953 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +0000954 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000955 # create a Tkinter variable object with self.text as master:
956 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000957 return var
958
959 # Tk implementations of "virtual text methods" -- each platform
960 # reusing IDLE's support code needs to define these for its GUI's
961 # flavor of widget.
962
963 # Is character at text_index in a Python string? Return 0 for
964 # "guaranteed no", true for anything else. This info is expensive
965 # to compute ab initio, but is probably already known by the
966 # platform's colorizer.
967
968 def is_char_in_string(self, text_index):
969 if self.color:
970 # Return true iff colorizer hasn't (re)gotten this far
971 # yet, or the character is tagged as being in a string
972 return self.text.tag_prevrange("TODO", text_index) or \
973 "STRING" in self.text.tag_names(text_index)
974 else:
975 # The colorizer is missing: assume the worst
976 return 1
977
978 # If a selection is defined in the text widget, return (start,
979 # end) as Tkinter text indices, otherwise return (None, None)
980 def get_selection_indices(self):
981 try:
982 first = self.text.index("sel.first")
983 last = self.text.index("sel.last")
984 return first, last
985 except TclError:
986 return None, None
987
988 # Return the text widget's current view of what a tab stop means
989 # (equivalent width in spaces).
990
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000991 def get_tk_tabwidth(self):
David Scherer7aced172000-08-15 01:13:23 +0000992 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
993 return int(current)
994
995 # Set the text widget's current view of what a tab stop means.
996
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000997 def set_tk_tabwidth(self, newtabwidth):
David Scherer7aced172000-08-15 01:13:23 +0000998 text = self.text
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +0000999 if self.get_tk_tabwidth() != newtabwidth:
1000 # Set text widget tab width
David Scherer7aced172000-08-15 01:13:23 +00001001 pixels = text.tk.call("font", "measure", text["font"],
1002 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001003 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001004 text.configure(tabs=pixels)
1005
Guido van Rossum33d26892007-08-05 15:29:28 +00001006### begin autoindent code ### (configuration was moved to beginning of class)
1007
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001008 def set_indentation_params(self, is_py_src, guess=True):
1009 if is_py_src and guess:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001010 i = self.guess_indent()
1011 if 2 <= i <= 8:
1012 self.indentwidth = i
1013 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001014 self.usetabs = False
Kurt B. Kaiser105f60e2007-09-06 04:03:04 +00001015 self.set_tk_tabwidth(self.tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001016
1017 def smart_backspace_event(self, event):
1018 text = self.text
1019 first, last = self.get_selection_indices()
1020 if first and last:
1021 text.delete(first, last)
1022 text.mark_set("insert", first)
1023 return "break"
1024 # Delete whitespace left, until hitting a real char or closest
1025 # preceding virtual tab stop.
1026 chars = text.get("insert linestart", "insert")
1027 if chars == '':
1028 if text.compare("insert", ">", "1.0"):
1029 # easy: delete preceding newline
1030 text.delete("insert-1c")
1031 else:
1032 text.bell() # at start of buffer
1033 return "break"
1034 if chars[-1] not in " \t":
1035 # easy: delete preceding real char
1036 text.delete("insert-1c")
1037 return "break"
1038 # Ick. It may require *inserting* spaces if we back up over a
1039 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001040 tabwidth = self.tabwidth
1041 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001042 assert have > 0
1043 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001044 # Debug prompt is multilined....
1045 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001046 ncharsdeleted = 0
1047 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001048 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001049 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001050 chars = chars[:-1]
1051 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001052 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001053 if have <= want or chars[-1] not in " \t":
1054 break
1055 text.undo_block_start()
1056 text.delete("insert-%dc" % ncharsdeleted, "insert")
1057 if have < want:
1058 text.insert("insert", ' ' * (want - have))
1059 text.undo_block_stop()
1060 return "break"
1061
1062 def smart_indent_event(self, event):
1063 # if intraline selection:
1064 # delete it
1065 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001066 # do indent-region
1067 # else:
1068 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001069 text = self.text
1070 first, last = self.get_selection_indices()
1071 text.undo_block_start()
1072 try:
1073 if first and last:
1074 if index2line(first) != index2line(last):
1075 return self.indent_region_event(event)
1076 text.delete(first, last)
1077 text.mark_set("insert", first)
1078 prefix = text.get("insert linestart", "insert")
1079 raw, effective = classifyws(prefix, self.tabwidth)
1080 if raw == len(prefix):
1081 # only whitespace to the left
1082 self.reindent_to(effective + self.indentwidth)
1083 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001084 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001085 if self.usetabs:
1086 pad = '\t'
1087 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001088 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001089 n = self.indentwidth
1090 pad = ' ' * (n - effective % n)
1091 text.insert("insert", pad)
1092 text.see("insert")
1093 return "break"
1094 finally:
1095 text.undo_block_stop()
1096
1097 def newline_and_indent_event(self, event):
1098 text = self.text
1099 first, last = self.get_selection_indices()
1100 text.undo_block_start()
1101 try:
1102 if first and last:
1103 text.delete(first, last)
1104 text.mark_set("insert", first)
1105 line = text.get("insert linestart", "insert")
1106 i, n = 0, len(line)
1107 while i < n and line[i] in " \t":
1108 i = i+1
1109 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001110 # the cursor is in or at leading indentation in a continuation
1111 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001112 text.insert("insert linestart", '\n')
1113 return "break"
1114 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001115 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001116 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001117 last_line_of_prompt = sys.ps1.split('\n')[-1]
1118 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001119 line = line[:-1]
1120 i = i+1
1121 if i:
1122 text.delete("insert - %d chars" % i, "insert")
1123 # strip whitespace after insert point
1124 while text.get("insert") in " \t":
1125 text.delete("insert")
1126 # start new line
1127 text.insert("insert", '\n')
1128
1129 # adjust indentation for continuations and block
1130 # open/close first need to find the last stmt
1131 lno = index2line(text.index('insert'))
1132 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001133 if not self.context_use_ps1:
1134 for context in self.num_context_lines:
1135 startat = max(lno - context, 1)
Brett Cannon0b70cca2006-08-25 02:59:59 +00001136 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001137 rawtext = text.get(startatindex, "insert")
1138 y.set_str(rawtext)
1139 bod = y.find_good_parse_start(
1140 self.context_use_ps1,
1141 self._build_char_in_string_func(startatindex))
1142 if bod is not None or startat == 1:
1143 break
1144 y.set_lo(bod or 0)
1145 else:
1146 r = text.tag_prevrange("console", "insert")
1147 if r:
1148 startatindex = r[1]
1149 else:
1150 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001151 rawtext = text.get(startatindex, "insert")
1152 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001153 y.set_lo(0)
1154
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001155 c = y.get_continuation_type()
1156 if c != PyParse.C_NONE:
1157 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001158 if c == PyParse.C_STRING_FIRST_LINE:
1159 # after the first line of a string; do not indent at all
1160 pass
1161 elif c == PyParse.C_STRING_NEXT_LINES:
1162 # inside a string which started before this line;
1163 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001164 text.insert("insert", indent)
1165 elif c == PyParse.C_BRACKET:
1166 # line up with the first (if any) element of the
1167 # last open bracket structure; else indent one
1168 # level beyond the indent of the line with the
1169 # last open bracket
1170 self.reindent_to(y.compute_bracket_indent())
1171 elif c == PyParse.C_BACKSLASH:
1172 # if more than one line in this stmt already, just
1173 # mimic the current indent; else if initial line
1174 # has a start on an assignment stmt, indent to
1175 # beyond leftmost =; else to beyond first chunk of
1176 # non-whitespace on initial line
1177 if y.get_num_lines_in_stmt() > 1:
1178 text.insert("insert", indent)
1179 else:
1180 self.reindent_to(y.compute_backslash_indent())
1181 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001182 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001183 return "break"
1184
1185 # This line starts a brand new stmt; indent relative to
1186 # indentation of initial line of closest preceding
1187 # interesting stmt.
1188 indent = y.get_base_indent_string()
1189 text.insert("insert", indent)
1190 if y.is_block_opener():
1191 self.smart_indent_event(event)
1192 elif indent and y.is_block_closer():
1193 self.smart_backspace_event(event)
1194 return "break"
1195 finally:
1196 text.see("insert")
1197 text.undo_block_stop()
1198
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001199 # Our editwin provides a is_char_in_string function that works
1200 # with a Tk text index, but PyParse only knows about offsets into
1201 # a string. This builds a function for PyParse that accepts an
1202 # offset.
1203
1204 def _build_char_in_string_func(self, startindex):
1205 def inner(offset, _startindex=startindex,
1206 _icis=self.is_char_in_string):
1207 return _icis(_startindex + "+%dc" % offset)
1208 return inner
1209
1210 def indent_region_event(self, event):
1211 head, tail, chars, lines = self.get_region()
1212 for pos in range(len(lines)):
1213 line = lines[pos]
1214 if line:
1215 raw, effective = classifyws(line, self.tabwidth)
1216 effective = effective + self.indentwidth
1217 lines[pos] = self._make_blanks(effective) + line[raw:]
1218 self.set_region(head, tail, chars, lines)
1219 return "break"
1220
1221 def dedent_region_event(self, event):
1222 head, tail, chars, lines = self.get_region()
1223 for pos in range(len(lines)):
1224 line = lines[pos]
1225 if line:
1226 raw, effective = classifyws(line, self.tabwidth)
1227 effective = max(effective - self.indentwidth, 0)
1228 lines[pos] = self._make_blanks(effective) + line[raw:]
1229 self.set_region(head, tail, chars, lines)
1230 return "break"
1231
1232 def comment_region_event(self, event):
1233 head, tail, chars, lines = self.get_region()
1234 for pos in range(len(lines) - 1):
1235 line = lines[pos]
1236 lines[pos] = '##' + line
1237 self.set_region(head, tail, chars, lines)
1238
1239 def uncomment_region_event(self, event):
1240 head, tail, chars, lines = self.get_region()
1241 for pos in range(len(lines)):
1242 line = lines[pos]
1243 if not line:
1244 continue
1245 if line[:2] == '##':
1246 line = line[2:]
1247 elif line[:1] == '#':
1248 line = line[1:]
1249 lines[pos] = line
1250 self.set_region(head, tail, chars, lines)
1251
1252 def tabify_region_event(self, event):
1253 head, tail, chars, lines = self.get_region()
1254 tabwidth = self._asktabwidth()
1255 for pos in range(len(lines)):
1256 line = lines[pos]
1257 if line:
1258 raw, effective = classifyws(line, tabwidth)
1259 ntabs, nspaces = divmod(effective, tabwidth)
1260 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1261 self.set_region(head, tail, chars, lines)
1262
1263 def untabify_region_event(self, event):
1264 head, tail, chars, lines = self.get_region()
1265 tabwidth = self._asktabwidth()
1266 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001267 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001268 self.set_region(head, tail, chars, lines)
1269
1270 def toggle_tabs_event(self, event):
1271 if self.askyesno(
1272 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001273 "Turn tabs " + ("on", "off")[self.usetabs] +
1274 "?\nIndent width " +
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001275 ("will be", "remains at")[self.usetabs] + " 8." +
1276 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001277 parent=self.text):
1278 self.usetabs = not self.usetabs
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001279 # Try to prevent inconsistent indentation.
1280 # User must change indent width manually after using tabs.
1281 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001282 return "break"
1283
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001284 # XXX this isn't bound to anything -- see tabwidth comments
1285## def change_tabwidth_event(self, event):
1286## new = self._asktabwidth()
1287## if new != self.tabwidth:
1288## self.tabwidth = new
1289## self.set_indentation_params(0, guess=0)
1290## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001291
1292 def change_indentwidth_event(self, event):
1293 new = self.askinteger(
1294 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001295 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001296 parent=self.text,
1297 initialvalue=self.indentwidth,
1298 minvalue=2,
1299 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001300 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001301 self.indentwidth = new
1302 return "break"
1303
1304 def get_region(self):
1305 text = self.text
1306 first, last = self.get_selection_indices()
1307 if first and last:
1308 head = text.index(first + " linestart")
1309 tail = text.index(last + "-1c lineend +1c")
1310 else:
1311 head = text.index("insert linestart")
1312 tail = text.index("insert lineend +1c")
1313 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001314 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001315 return head, tail, chars, lines
1316
1317 def set_region(self, head, tail, chars, lines):
1318 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001319 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001320 if newchars == chars:
1321 text.bell()
1322 return
1323 text.tag_remove("sel", "1.0", "end")
1324 text.mark_set("insert", head)
1325 text.undo_block_start()
1326 text.delete(head, tail)
1327 text.insert(head, newchars)
1328 text.undo_block_stop()
1329 text.tag_add("sel", head, "insert")
1330
1331 # Make string that displays as n leading blanks.
1332
1333 def _make_blanks(self, n):
1334 if self.usetabs:
1335 ntabs, nspaces = divmod(n, self.tabwidth)
1336 return '\t' * ntabs + ' ' * nspaces
1337 else:
1338 return ' ' * n
1339
1340 # Delete from beginning of line to insert point, then reinsert
1341 # column logical (meaning use tabs if appropriate) spaces.
1342
1343 def reindent_to(self, column):
1344 text = self.text
1345 text.undo_block_start()
1346 if text.compare("insert linestart", "!=", "insert"):
1347 text.delete("insert linestart", "insert")
1348 if column:
1349 text.insert("insert", self._make_blanks(column))
1350 text.undo_block_stop()
1351
1352 def _asktabwidth(self):
1353 return self.askinteger(
1354 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001355 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001356 parent=self.text,
1357 initialvalue=self.indentwidth,
1358 minvalue=2,
1359 maxvalue=16) or self.tabwidth
1360
1361 # Guess indentwidth from text content.
1362 # Return guessed indentwidth. This should not be believed unless
1363 # it's in a reasonable range (e.g., it will be 0 if no indented
1364 # blocks are found).
1365
1366 def guess_indent(self):
1367 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1368 if opener and indented:
1369 raw, indentsmall = classifyws(opener, self.tabwidth)
1370 raw, indentlarge = classifyws(indented, self.tabwidth)
1371 else:
1372 indentsmall = indentlarge = 0
1373 return indentlarge - indentsmall
1374
1375# "line.col" -> line, as an int
1376def index2line(index):
1377 return int(float(index))
1378
1379# Look at the leading whitespace in s.
1380# Return pair (# of leading ws characters,
1381# effective # of leading blanks after expanding
1382# tabs to width tabwidth)
1383
1384def classifyws(s, tabwidth):
1385 raw = effective = 0
1386 for ch in s:
1387 if ch == ' ':
1388 raw = raw + 1
1389 effective = effective + 1
1390 elif ch == '\t':
1391 raw = raw + 1
1392 effective = (effective // tabwidth + 1) * tabwidth
1393 else:
1394 break
1395 return raw, effective
1396
1397import tokenize
1398_tokenize = tokenize
1399del tokenize
1400
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001401class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001402
1403 # .run() chews over the Text widget, looking for a block opener
1404 # and the stmt following it. Returns a pair,
1405 # (line containing block opener, line containing stmt)
1406 # Either or both may be None.
1407
1408 def __init__(self, text, tabwidth):
1409 self.text = text
1410 self.tabwidth = tabwidth
1411 self.i = self.finished = 0
1412 self.blkopenline = self.indentedline = None
1413
1414 def readline(self):
1415 if self.finished:
1416 return ""
1417 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001418 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001419 if self.text.compare(mark, ">=", "end"):
1420 return ""
1421 return self.text.get(mark, mark + " lineend+1c")
1422
1423 def tokeneater(self, type, token, start, end, line,
1424 INDENT=_tokenize.INDENT,
1425 NAME=_tokenize.NAME,
1426 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1427 if self.finished:
1428 pass
1429 elif type == NAME and token in OPENERS:
1430 self.blkopenline = line
1431 elif type == INDENT and self.blkopenline:
1432 self.indentedline = line
1433 self.finished = 1
1434
1435 def run(self):
1436 save_tabsize = _tokenize.tabsize
1437 _tokenize.tabsize = self.tabwidth
1438 try:
1439 try:
Trent Nelson428de652008-03-18 22:41:35 +00001440 tokens = _tokenize.generate_tokens(self.readline)
1441 for token in tokens:
1442 self.tokeneater(*token)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001443 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)
Guido van Rossum8ce8a782007-11-01 19:42:39 +00001504 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001505 root.mainloop()
1506 root.destroy()
1507
1508if __name__ == '__main__':
1509 test()