blob: ed6b1a19fdf8dee171b517ed85eddcafdd939c8e [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
David Scherer7aced172000-08-15 01:13:23 +00003import re
4import imp
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00005from itertools import count
David Scherer7aced172000-08-15 01:13:23 +00006from Tkinter import *
7import tkSimpleDialog
8import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00009
10import webbrowser
David Scherer7aced172000-08-15 01:13:23 +000011import idlever
12import WindowList
Steven M. Gavac5976402002-01-04 03:06:08 +000013import SearchDialog
14import GrepDialog
15import ReplaceDialog
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +000016import PyParse
Steven M. Gavadc72f482002-01-03 11:51:07 +000017from configHandler import idleConf
Steven M. Gava3b55a892001-11-21 05:56:26 +000018import aboutDialog, textView, configDialog
David Scherer7aced172000-08-15 01:13:23 +000019
20# The default tab setting for a Text widget, in average-width characters.
21TK_TABWIDTH_DEFAULT = 8
22
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000023def _find_module(fullname, path=None):
24 """Version of imp.find_module() that handles hierarchical module names"""
25
26 file = None
27 for tgt in fullname.split('.'):
28 if file is not None:
29 file.close() # close intermediate files
30 (file, filename, descr) = imp.find_module(tgt, path)
31 if descr[2] == imp.PY_SOURCE:
32 break # find but not load the source file
33 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000034 try:
35 path = module.__path__
36 except AttributeError:
37 raise ImportError, 'No source for module ' + module.__name__
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000038 return file, filename, descr
39
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000040class EditorWindow(object):
David Scherer7aced172000-08-15 01:13:23 +000041 from Percolator import Percolator
42 from ColorDelegator import ColorDelegator
43 from UndoDelegator import UndoDelegator
44 from IOBinding import IOBinding
45 import Bindings
46 from Tkinter import Toplevel
47 from MultiStatusBar import MultiStatusBar
48
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000049 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000050
51 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000052 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000053 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000054 if sys.platform.count('linux'):
55 # look for html docs in a couple of standard places
56 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
57 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
58 dochome = '/var/www/html/python/index.html'
59 else:
60 basepath = '/usr/share/doc/' # standard location
61 dochome = os.path.join(basepath, pyver,
62 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000063 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +000064 chmfile = os.path.join(sys.prefix, 'Doc',
65 'Python%d%d.chm' % sys.version_info[:2])
Thomas Heller84ef1532003-09-23 20:53:10 +000066 if os.path.isfile(chmfile):
67 dochome = chmfile
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000068 dochome = os.path.normpath(dochome)
69 if os.path.isfile(dochome):
70 EditorWindow.help_url = dochome
71 else:
72 EditorWindow.help_url = "http://www.python.org/doc/current"
Steven M. Gavadc72f482002-01-03 11:51:07 +000073 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000074 self.flist = flist
75 root = root or flist.root
76 self.root = root
David Scherer7aced172000-08-15 01:13:23 +000077 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +000078 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000079 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +000080 self.tkinter_vars = flist.vars
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +000081 #self.top.instance_dict makes flist.inversedict avalable to
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000082 #configDialog.py so it can access all EditorWindow instaces
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +000083 self.top.instance_dict=flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +000084 else:
85 self.tkinter_vars = {} # keys: Tkinter event names
86 # values: Tkinter variable instances
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +000087 self.recent_files_path=os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +000088 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +000089 self.vbar = vbar = Scrollbar(top, name='vbar')
90 self.text_frame = text_frame = Frame(top)
Kurt B. Kaiser1061e722003-01-04 01:43:53 +000091 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaisera1dee062002-10-04 21:33:57 +000092 self.text = text = Text(text_frame, name='text', padx=5, wrap='none',
Steven M. Gavadc72f482002-01-03 11:51:07 +000093 foreground=idleConf.GetHighlight(currentTheme,
94 'normal',fgBg='fg'),
95 background=idleConf.GetHighlight(currentTheme,
96 'normal',fgBg='bg'),
97 highlightcolor=idleConf.GetHighlight(currentTheme,
98 'hilite',fgBg='fg'),
99 highlightbackground=idleConf.GetHighlight(currentTheme,
100 'hilite',fgBg='bg'),
101 insertbackground=idleConf.GetHighlight(currentTheme,
102 'cursor',fgBg='fg'),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000103 width=self.width,
Steven M. Gavadc72f482002-01-03 11:51:07 +0000104 height=idleConf.GetOption('main','EditorWindow','height') )
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000105 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000106
107 self.createmenubar()
108 self.apply_bindings()
109
110 self.top.protocol("WM_DELETE_WINDOW", self.close)
111 self.top.bind("<<close-window>>", self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000112 text.bind("<<cut>>", self.cut)
113 text.bind("<<copy>>", self.copy)
114 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000115 text.bind("<<center-insert>>", self.center_insert_event)
116 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000117 text.bind("<<python-docs>>", self.python_docs)
118 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000119 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000120 text.bind("<<open-module>>", self.open_module)
121 text.bind("<<do-nothing>>", lambda event: "break")
122 text.bind("<<select-all>>", self.select_all)
123 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000124 text.bind("<<find>>", self.find_event)
125 text.bind("<<find-again>>", self.find_again_event)
126 text.bind("<<find-in-files>>", self.find_in_files_event)
127 text.bind("<<find-selection>>", self.find_selection_event)
128 text.bind("<<replace>>", self.replace_event)
129 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000130 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000131 text.bind("<<smart-backspace>>",self.smart_backspace_event)
132 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
133 text.bind("<<smart-indent>>",self.smart_indent_event)
134 text.bind("<<indent-region>>",self.indent_region_event)
135 text.bind("<<dedent-region>>",self.dedent_region_event)
136 text.bind("<<comment-region>>",self.comment_region_event)
137 text.bind("<<uncomment-region>>",self.uncomment_region_event)
138 text.bind("<<tabify-region>>",self.tabify_region_event)
139 text.bind("<<untabify-region>>",self.untabify_region_event)
140 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
141 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000142 text.bind("<Left>", self.move_at_edge_if_selection(0))
143 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000144 text.bind("<<del-word-left>>", self.del_word_left)
145 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000146
David Scherer7aced172000-08-15 01:13:23 +0000147 if flist:
148 flist.inversedict[self] = key
149 if key:
150 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000151 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000152 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
153 text.bind("<<open-class-browser>>", self.open_class_browser)
154 text.bind("<<open-path-browser>>", self.open_path_browser)
155
Steven M. Gava898a3652001-10-07 11:10:44 +0000156 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000157 vbar['command'] = text.yview
158 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000159 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000160 fontWeight = 'normal'
161 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000162 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000163 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
164 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
165 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000166 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
167 text.pack(side=TOP, fill=BOTH, expand=1)
168 text.focus_set()
169
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000170 # usetabs true -> literal tab characters are used by indent and
171 # dedent cmds, possibly mixed with spaces if
172 # indentwidth is not a multiple of tabwidth,
173 # which will cause Tabnanny to nag!
174 # false -> tab characters are converted to spaces by indent
175 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000176 # Although use-spaces=0 can be configured manually in config-main.def,
177 # configuration of tabs v. spaces is not supported in the configuration
178 # dialog. IDLE promotes the preferred Python indentation: use spaces!
179 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
180 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000181
182 # tabwidth is the display width of a literal tab character.
183 # CAUTION: telling Tk to use anything other than its default
184 # tab setting causes it to use an entirely different tabbing algorithm,
185 # treating tab stops as fixed distances from the left margin.
186 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000187 self.tabwidth = 8 # must remain 8 until Tk is fixed.
188
189 # indentwidth is the number of screen characters per indent level.
190 # The recommended Python indentation is four spaces.
191 self.indentwidth = self.tabwidth
192 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000193
194 # If context_use_ps1 is true, parsing searches back for a ps1 line;
195 # else searches for a popular (if, def, ...) Python stmt.
196 self.context_use_ps1 = False
197
198 # When searching backwards for a reliable place to begin parsing,
199 # first start num_context_lines[0] lines back, then
200 # num_context_lines[1] lines back if that didn't work, and so on.
201 # The last value should be huge (larger than the # of lines in a
202 # conceivable file).
203 # Making the initial values larger slows things down more often.
204 self.num_context_lines = 50, 500, 5000000
205
David Scherer7aced172000-08-15 01:13:23 +0000206 self.per = per = self.Percolator(text)
207 if self.ispythonsource(filename):
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000208 self.color = color = self.ColorDelegator()
209 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000210 else:
David Scherer7aced172000-08-15 01:13:23 +0000211 self.color = None
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000212
213 self.undo = undo = self.UndoDelegator()
214 per.insertfilter(undo)
215 text.undo_block_start = undo.undo_block_start
216 text.undo_block_stop = undo.undo_block_stop
217 undo.set_saved_change_hook(self.saved_change_hook)
218
219 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000220 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000221 io.set_filename_change_hook(self.filename_change_hook)
222
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000223 # Create the recent files submenu
224 self.recent_files_menu = Menu(self.menubar)
225 self.menudict['file'].insert_cascade(3, label='Recent Files',
226 underline=0,
227 menu=self.recent_files_menu)
228 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000229
David Scherer7aced172000-08-15 01:13:23 +0000230 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000231 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000232 io.loadfile(filename)
233 else:
234 io.set_filename(filename)
David Scherer7aced172000-08-15 01:13:23 +0000235 self.saved_change_hook()
236
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000237 self.set_indentation_params(self.ispythonsource(filename))
238
David Scherer7aced172000-08-15 01:13:23 +0000239 self.load_extensions()
240
241 menu = self.menudict.get('windows')
242 if menu:
243 end = menu.index("end")
244 if end is None:
245 end = -1
246 if end >= 0:
247 menu.add_separator()
248 end = end + 1
249 self.wmenu_end = end
250 WindowList.register_callback(self.postwindowsmenu)
251
252 # Some abstractions so IDLE extensions are cross-IDE
253 self.askyesno = tkMessageBox.askyesno
254 self.askinteger = tkSimpleDialog.askinteger
255 self.showerror = tkMessageBox.showerror
256
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000257 def new_callback(self, event):
258 dirname, basename = self.io.defaultfilename()
259 self.flist.new(dirname)
260 return "break"
261
David Scherer7aced172000-08-15 01:13:23 +0000262 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000263 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000264 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
265 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
266 self.status_bar.pack(side=BOTTOM, fill=X)
267 self.text.bind('<KeyRelease>', self.set_line_and_column)
268 self.text.bind('<ButtonRelease>', self.set_line_and_column)
269 self.text.after_idle(self.set_line_and_column)
270
271 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000272 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000273 self.status_bar.set_label('column', 'Col: %s' % column)
274 self.status_bar.set_label('line', 'Ln: %s' % line)
275
David Scherer7aced172000-08-15 01:13:23 +0000276 menu_specs = [
277 ("file", "_File"),
278 ("edit", "_Edit"),
279 ("format", "F_ormat"),
280 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000281 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000282 ("windows", "_Windows"),
283 ("help", "_Help"),
284 ]
285
286 def createmenubar(self):
287 mbar = self.menubar
288 self.menudict = menudict = {}
289 for name, label in self.menu_specs:
290 underline, label = prepstr(label)
291 menudict[name] = menu = Menu(mbar, name=name)
292 mbar.add_cascade(label=label, menu=menu, underline=underline)
293 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000294 self.base_helpmenu_length = self.menudict['help'].index(END)
295 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000296
297 def postwindowsmenu(self):
298 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000299 menu = self.menudict['windows']
300 end = menu.index("end")
301 if end is None:
302 end = -1
303 if end > self.wmenu_end:
304 menu.delete(self.wmenu_end+1, end)
305 WindowList.add_windows_to_menu(menu)
306
307 rmenu = None
308
309 def right_menu_event(self, event):
310 self.text.tag_remove("sel", "1.0", "end")
311 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
312 if not self.rmenu:
313 self.make_rmenu()
314 rmenu = self.rmenu
315 self.event = event
316 iswin = sys.platform[:3] == 'win'
317 if iswin:
318 self.text.config(cursor="arrow")
319 rmenu.tk_popup(event.x_root, event.y_root)
320 if iswin:
321 self.text.config(cursor="ibeam")
322
323 rmenu_specs = [
324 # ("Label", "<<virtual-event>>"), ...
325 ("Close", "<<close-window>>"), # Example
326 ]
327
328 def make_rmenu(self):
329 rmenu = Menu(self.text, tearoff=0)
330 for label, eventname in self.rmenu_specs:
331 def command(text=self.text, eventname=eventname):
332 text.event_generate(eventname)
333 rmenu.add_command(label=label, command=command)
334 self.rmenu = rmenu
335
336 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000337 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000338
Steven M. Gava3b55a892001-11-21 05:56:26 +0000339 def config_dialog(self, event=None):
340 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000341
David Scherer7aced172000-08-15 01:13:23 +0000342 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000343 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000344 textView.TextViewer(self.top,'Help',fn)
345
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000346 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000347 if sys.platform[:3] == 'win':
Steven M. Gava931625d2002-04-22 00:38:26 +0000348 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000349 else:
350 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000351 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000352
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000353 def cut(self,event):
354 self.text.event_generate("<<Cut>>")
355 return "break"
356
357 def copy(self,event):
358 self.text.event_generate("<<Copy>>")
359 return "break"
360
361 def paste(self,event):
362 self.text.event_generate("<<Paste>>")
363 return "break"
364
David Scherer7aced172000-08-15 01:13:23 +0000365 def select_all(self, event=None):
366 self.text.tag_add("sel", "1.0", "end-1c")
367 self.text.mark_set("insert", "1.0")
368 self.text.see("insert")
369 return "break"
370
371 def remove_selection(self, event=None):
372 self.text.tag_remove("sel", "1.0", "end")
373 self.text.see("insert")
374
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000375 def move_at_edge_if_selection(self, edge_index):
376 """Cursor move begins at start or end of selection
377
378 When a left/right cursor key is pressed create and return to Tkinter a
379 function which causes a cursor move from the associated edge of the
380 selection.
381
382 """
383 self_text_index = self.text.index
384 self_text_mark_set = self.text.mark_set
385 edges_table = ("sel.first+1c", "sel.last-1c")
386 def move_at_edge(event):
387 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
388 try:
389 self_text_index("sel.first")
390 self_text_mark_set("insert", edges_table[edge_index])
391 except TclError:
392 pass
393 return move_at_edge
394
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000395 def del_word_left(self, event):
396 self.text.event_generate('<Meta-Delete>')
397 return "break"
398
399 def del_word_right(self, event):
400 self.text.event_generate('<Meta-d>')
401 return "break"
402
Steven M. Gavac5976402002-01-04 03:06:08 +0000403 def find_event(self, event):
404 SearchDialog.find(self.text)
405 return "break"
406
407 def find_again_event(self, event):
408 SearchDialog.find_again(self.text)
409 return "break"
410
411 def find_selection_event(self, event):
412 SearchDialog.find_selection(self.text)
413 return "break"
414
415 def find_in_files_event(self, event):
416 GrepDialog.grep(self.text, self.io, self.flist)
417 return "break"
418
419 def replace_event(self, event):
420 ReplaceDialog.replace(self.text)
421 return "break"
422
423 def goto_line_event(self, event):
424 text = self.text
425 lineno = tkSimpleDialog.askinteger("Goto",
426 "Go to line number:",parent=text)
427 if lineno is None:
428 return "break"
429 if lineno <= 0:
430 text.bell()
431 return "break"
432 text.mark_set("insert", "%d.0" % lineno)
433 text.see("insert")
434
David Scherer7aced172000-08-15 01:13:23 +0000435 def open_module(self, event=None):
436 # XXX Shouldn't this be in IOBinding or in FileList?
437 try:
438 name = self.text.get("sel.first", "sel.last")
439 except TclError:
440 name = ""
441 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000442 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000443 name = tkSimpleDialog.askstring("Module",
444 "Enter the name of a Python module\n"
445 "to search on sys.path and open:",
446 parent=self.text, initialvalue=name)
447 if name:
448 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000449 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000450 return
David Scherer7aced172000-08-15 01:13:23 +0000451 # XXX Ought to insert current file's directory in front of path
452 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000453 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000454 except (NameError, ImportError), msg:
455 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
456 return
457 if type != imp.PY_SOURCE:
458 tkMessageBox.showerror("Unsupported type",
459 "%s is not a source module" % name, parent=self.text)
460 return
461 if f:
462 f.close()
463 if self.flist:
464 self.flist.open(file)
465 else:
466 self.io.loadfile(file)
467
468 def open_class_browser(self, event=None):
469 filename = self.io.filename
470 if not filename:
471 tkMessageBox.showerror(
472 "No filename",
473 "This buffer has no associated filename",
474 master=self.text)
475 self.text.focus_set()
476 return None
477 head, tail = os.path.split(filename)
478 base, ext = os.path.splitext(tail)
479 import ClassBrowser
480 ClassBrowser.ClassBrowser(self.flist, base, [head])
481
482 def open_path_browser(self, event=None):
483 import PathBrowser
484 PathBrowser.PathBrowser(self.flist)
485
486 def gotoline(self, lineno):
487 if lineno is not None and lineno > 0:
488 self.text.mark_set("insert", "%d.0" % lineno)
489 self.text.tag_remove("sel", "1.0", "end")
490 self.text.tag_add("sel", "insert", "insert +1l")
491 self.center()
492
493 def ispythonsource(self, filename):
494 if not filename:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000495 return True
David Scherer7aced172000-08-15 01:13:23 +0000496 base, ext = os.path.splitext(os.path.basename(filename))
497 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000498 return True
David Scherer7aced172000-08-15 01:13:23 +0000499 try:
500 f = open(filename)
501 line = f.readline()
502 f.close()
503 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000504 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000505 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000506
507 def close_hook(self):
508 if self.flist:
509 self.flist.close_edit(self)
510
511 def set_close_hook(self, close_hook):
512 self.close_hook = close_hook
513
514 def filename_change_hook(self):
515 if self.flist:
516 self.flist.filename_changed_edit(self)
517 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000518 self.top.update_windowlist_registry(self)
David Scherer7aced172000-08-15 01:13:23 +0000519 if self.ispythonsource(self.io.filename):
520 self.addcolorizer()
521 else:
522 self.rmcolorizer()
523
524 def addcolorizer(self):
525 if self.color:
526 return
David Scherer7aced172000-08-15 01:13:23 +0000527 self.per.removefilter(self.undo)
528 self.color = self.ColorDelegator()
529 self.per.insertfilter(self.color)
530 self.per.insertfilter(self.undo)
531
532 def rmcolorizer(self):
533 if not self.color:
534 return
David Scherer7aced172000-08-15 01:13:23 +0000535 self.per.removefilter(self.undo)
536 self.per.removefilter(self.color)
537 self.color = None
538 self.per.insertfilter(self.undo)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000539
Steven M. Gavab77d3432002-03-02 07:16:21 +0000540 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000541 "Update the colour theme if it is changed"
542 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000543 if self.color:
544 self.color = self.ColorDelegator()
545 self.per.insertfilter(self.color)
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000546 theme = idleConf.GetOption('main','Theme','name')
547 self.text.config(idleConf.GetHighlight(theme, "normal"))
David Scherer7aced172000-08-15 01:13:23 +0000548
Steven M. Gavab1585412002-03-12 00:21:56 +0000549 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000550 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000551 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000552 fontWeight='normal'
553 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
554 fontWeight='bold'
555 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
556 idleConf.GetOption('main','EditorWindow','font-size'),
557 fontWeight))
558
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000559 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000560 "Update the keybindings if they are changed"
561 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000562 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
563 keydefs = self.Bindings.default_keydefs
564 for event, keylist in keydefs.items():
565 self.text.event_delete(event)
566 self.apply_bindings()
567 #update menu accelerators
568 menuEventDict={}
569 for menu in self.Bindings.menudefs:
570 menuEventDict[menu[0]]={}
571 for item in menu[1]:
572 if item:
573 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
574 for menubarItem in self.menudict.keys():
575 menu=self.menudict[menubarItem]
576 end=menu.index(END)+1
577 for index in range(0,end):
578 if menu.type(index)=='command':
579 accel=menu.entrycget(index,'accelerator')
580 if accel:
581 itemName=menu.entrycget(index,'label')
582 event=''
583 if menuEventDict.has_key(menubarItem):
584 if menuEventDict[menubarItem].has_key(itemName):
585 event=menuEventDict[menubarItem][itemName]
586 if event:
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000587 accel=get_accelerator(keydefs, event)
588 menu.entryconfig(index,accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000589
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000590 def set_notabs_indentwidth(self):
591 "Update the indentwidth if changed and not using tabs in this window"
592 # Called from configDialog.py
593 if not self.usetabs:
594 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
595 type='int')
596
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000597 def reset_help_menu_entries(self):
598 "Update the additional help entries on the Help menu"
599 help_list = idleConf.GetAllExtraHelpSourcesList()
600 helpmenu = self.menudict['help']
601 # first delete the extra help entries, if any
602 helpmenu_length = helpmenu.index(END)
603 if helpmenu_length > self.base_helpmenu_length:
604 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
605 # then rebuild them
606 if help_list:
607 helpmenu.add_separator()
608 for entry in help_list:
609 cmd = self.__extra_help_callback(entry[1])
610 helpmenu.add_command(label=entry[0], command=cmd)
611 # and update the menu dictionary
612 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000613
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000614 def __extra_help_callback(self, helpfile):
615 "Create a callback with the helpfile value frozen at definition time"
616 def display_extra_help(helpfile=helpfile):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000617 if not (helpfile.startswith('www') or helpfile.startswith('http')):
618 url = os.path.normpath(helpfile)
619 if sys.platform[:3] == 'win':
620 os.startfile(helpfile)
621 else:
622 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000623 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000624
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000625 def update_recent_files_list(self, new_file=None):
626 "Load and update the recent files list and menus"
627 rf_list = []
628 if os.path.exists(self.recent_files_path):
629 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000630 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000631 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000632 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000633 rf_list_file.close()
634 if new_file:
635 new_file = os.path.abspath(new_file) + '\n'
636 if new_file in rf_list:
637 rf_list.remove(new_file) # move to top
638 rf_list.insert(0, new_file)
639 # clean and save the recent files list
640 bad_paths = []
641 for path in rf_list:
642 if '\0' in path or not os.path.exists(path[0:-1]):
643 bad_paths.append(path)
644 rf_list = [path for path in rf_list if path not in bad_paths]
645 ulchars = "1234567890ABCDEFGHIJK"
646 rf_list = rf_list[0:len(ulchars)]
647 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000648 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000649 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000650 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000651 rf_file.close()
652 # for each edit window instance, construct the recent files menu
653 for instance in self.top.instance_dict.keys():
654 menu = instance.recent_files_menu
655 menu.delete(1, END) # clear, and rebuild:
656 for i, file in zip(count(), rf_list):
657 file_name = file[0:-1] # zap \n
658 callback = instance.__recent_file_callback(file_name)
659 menu.add_command(label=ulchars[i] + " " + file_name,
660 command=callback,
661 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000662
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000663 def __recent_file_callback(self, file_name):
664 def open_recent_file(fn_closure=file_name):
665 self.io.open(editFile=fn_closure)
666 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000667
David Scherer7aced172000-08-15 01:13:23 +0000668 def saved_change_hook(self):
669 short = self.short_title()
670 long = self.long_title()
671 if short and long:
672 title = short + " - " + long
673 elif short:
674 title = short
675 elif long:
676 title = long
677 else:
678 title = "Untitled"
679 icon = short or long or title
680 if not self.get_saved():
681 title = "*%s*" % title
682 icon = "*%s" % icon
683 self.top.wm_title(title)
684 self.top.wm_iconname(icon)
685
686 def get_saved(self):
687 return self.undo.get_saved()
688
689 def set_saved(self, flag):
690 self.undo.set_saved(flag)
691
692 def reset_undo(self):
693 self.undo.reset_undo()
694
695 def short_title(self):
696 filename = self.io.filename
697 if filename:
698 filename = os.path.basename(filename)
699 return filename
700
701 def long_title(self):
702 return self.io.filename or ""
703
704 def center_insert_event(self, event):
705 self.center()
706
707 def center(self, mark="insert"):
708 text = self.text
709 top, bot = self.getwindowlines()
710 lineno = self.getlineno(mark)
711 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000712 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000713 text.yview(float(newtop))
714
715 def getwindowlines(self):
716 text = self.text
717 top = self.getlineno("@0,0")
718 bot = self.getlineno("@0,65535")
719 if top == bot and text.winfo_height() == 1:
720 # Geometry manager hasn't run yet
721 height = int(text['height'])
722 bot = top + height - 1
723 return top, bot
724
725 def getlineno(self, mark="insert"):
726 text = self.text
727 return int(float(text.index(mark)))
728
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000729 def get_geometry(self):
730 "Return (width, height, x, y)"
731 geom = self.top.wm_geometry()
732 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
733 tuple = (map(int, m.groups()))
734 return tuple
735
David Scherer7aced172000-08-15 01:13:23 +0000736 def close_event(self, event):
737 self.close()
738
739 def maybesave(self):
740 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000741 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000742 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000743 self.top.deiconify()
744 self.top.lower()
745 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000746 return self.io.maybesave()
747
748 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000749 reply = self.maybesave()
750 if reply != "cancel":
751 self._close()
752 return reply
753
754 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000755 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000756 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000757 WindowList.unregister_callback(self.postwindowsmenu)
758 if self.close_hook:
759 self.close_hook()
760 self.flist = None
761 colorizing = 0
762 self.unload_extensions()
763 self.io.close(); self.io = None
764 self.undo = None # XXX
765 if self.color:
766 colorizing = self.color.colorizing
767 doh = colorizing and self.top
768 self.color.close(doh) # Cancel colorization
769 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000770 self.tkinter_vars = None
David Scherer7aced172000-08-15 01:13:23 +0000771 self.per.close(); self.per = None
772 if not colorizing:
773 self.top.destroy()
774
775 def load_extensions(self):
776 self.extensions = {}
777 self.load_standard_extensions()
778
779 def unload_extensions(self):
780 for ins in self.extensions.values():
781 if hasattr(ins, "close"):
782 ins.close()
783 self.extensions = {}
784
785 def load_standard_extensions(self):
786 for name in self.get_standard_extension_names():
787 try:
788 self.load_extension(name)
789 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000790 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +0000791 import traceback
792 traceback.print_exc()
793
794 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000795 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000796
797 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000798 try:
799 mod = __import__(name, globals(), locals(), [])
800 except ImportError:
801 print "\nFailed to import extension: ", name
802 return
David Scherer7aced172000-08-15 01:13:23 +0000803 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000804 keydefs = idleConf.GetExtensionBindings(name)
805 if hasattr(cls, "menudefs"):
806 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000807 ins = cls(self)
808 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000809 if keydefs:
810 self.apply_bindings(keydefs)
811 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000812 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000813 while methodname[:1] == '<':
814 methodname = methodname[1:]
815 while methodname[-1:] == '>':
816 methodname = methodname[:-1]
817 methodname = methodname + "_event"
818 if hasattr(ins, methodname):
819 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000820
821 def apply_bindings(self, keydefs=None):
822 if keydefs is None:
823 keydefs = self.Bindings.default_keydefs
824 text = self.text
825 text.keydefs = keydefs
826 for event, keylist in keydefs.items():
827 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000828 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000829
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000830 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000831 """Add appropriate entries to the menus and submenus
832
833 Menus that are absent or None in self.menudict are ignored.
834 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000835 if menudefs is None:
836 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000837 if keydefs is None:
838 keydefs = self.Bindings.default_keydefs
839 menudict = self.menudict
840 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000841 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000842 menu = menudict.get(mname)
843 if not menu:
844 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000845 for entry in entrylist:
846 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000847 menu.add_separator()
848 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000849 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000850 checkbutton = (label[:1] == '!')
851 if checkbutton:
852 label = label[1:]
853 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000854 accelerator = get_accelerator(keydefs, eventname)
855 def command(text=text, eventname=eventname):
856 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000857 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000858 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000859 menu.add_checkbutton(label=label, underline=underline,
860 command=command, accelerator=accelerator,
861 variable=var)
862 else:
863 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000864 command=command,
865 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000866
867 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000868 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +0000869 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000870 value = var.get()
871 return value
872 else:
873 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +0000874
875 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000876 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +0000877 if var:
878 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000879 else:
880 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +0000881
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000882 def get_var_obj(self, name, vartype=None):
883 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +0000884 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000885 # create a Tkinter variable object with self.text as master:
886 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000887 return var
888
889 # Tk implementations of "virtual text methods" -- each platform
890 # reusing IDLE's support code needs to define these for its GUI's
891 # flavor of widget.
892
893 # Is character at text_index in a Python string? Return 0 for
894 # "guaranteed no", true for anything else. This info is expensive
895 # to compute ab initio, but is probably already known by the
896 # platform's colorizer.
897
898 def is_char_in_string(self, text_index):
899 if self.color:
900 # Return true iff colorizer hasn't (re)gotten this far
901 # yet, or the character is tagged as being in a string
902 return self.text.tag_prevrange("TODO", text_index) or \
903 "STRING" in self.text.tag_names(text_index)
904 else:
905 # The colorizer is missing: assume the worst
906 return 1
907
908 # If a selection is defined in the text widget, return (start,
909 # end) as Tkinter text indices, otherwise return (None, None)
910 def get_selection_indices(self):
911 try:
912 first = self.text.index("sel.first")
913 last = self.text.index("sel.last")
914 return first, last
915 except TclError:
916 return None, None
917
918 # Return the text widget's current view of what a tab stop means
919 # (equivalent width in spaces).
920
921 def get_tabwidth(self):
922 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
923 return int(current)
924
925 # Set the text widget's current view of what a tab stop means.
926
927 def set_tabwidth(self, newtabwidth):
928 text = self.text
929 if self.get_tabwidth() != newtabwidth:
930 pixels = text.tk.call("font", "measure", text["font"],
931 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000932 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000933 text.configure(tabs=pixels)
934
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000935 # If ispythonsource and guess are true, guess a good value for
936 # indentwidth based on file content (if possible), and if
937 # indentwidth != tabwidth set usetabs false.
938 # In any case, adjust the Text widget's view of what a tab
939 # character means.
940
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000941 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000942 if guess and ispythonsource:
943 i = self.guess_indent()
944 if 2 <= i <= 8:
945 self.indentwidth = i
946 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000947 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000948 self.set_tabwidth(self.tabwidth)
949
950 def smart_backspace_event(self, event):
951 text = self.text
952 first, last = self.get_selection_indices()
953 if first and last:
954 text.delete(first, last)
955 text.mark_set("insert", first)
956 return "break"
957 # Delete whitespace left, until hitting a real char or closest
958 # preceding virtual tab stop.
959 chars = text.get("insert linestart", "insert")
960 if chars == '':
961 if text.compare("insert", ">", "1.0"):
962 # easy: delete preceding newline
963 text.delete("insert-1c")
964 else:
965 text.bell() # at start of buffer
966 return "break"
967 if chars[-1] not in " \t":
968 # easy: delete preceding real char
969 text.delete("insert-1c")
970 return "break"
971 # Ick. It may require *inserting* spaces if we back up over a
972 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000973 tabwidth = self.tabwidth
974 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000975 assert have > 0
976 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000977 # Debug prompt is multilined....
978 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000979 ncharsdeleted = 0
980 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000981 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +0000982 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000983 chars = chars[:-1]
984 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000985 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000986 if have <= want or chars[-1] not in " \t":
987 break
988 text.undo_block_start()
989 text.delete("insert-%dc" % ncharsdeleted, "insert")
990 if have < want:
991 text.insert("insert", ' ' * (want - have))
992 text.undo_block_stop()
993 return "break"
994
995 def smart_indent_event(self, event):
996 # if intraline selection:
997 # delete it
998 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000999 # do indent-region
1000 # else:
1001 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001002 text = self.text
1003 first, last = self.get_selection_indices()
1004 text.undo_block_start()
1005 try:
1006 if first and last:
1007 if index2line(first) != index2line(last):
1008 return self.indent_region_event(event)
1009 text.delete(first, last)
1010 text.mark_set("insert", first)
1011 prefix = text.get("insert linestart", "insert")
1012 raw, effective = classifyws(prefix, self.tabwidth)
1013 if raw == len(prefix):
1014 # only whitespace to the left
1015 self.reindent_to(effective + self.indentwidth)
1016 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001017 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001018 if self.usetabs:
1019 pad = '\t'
1020 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001021 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001022 n = self.indentwidth
1023 pad = ' ' * (n - effective % n)
1024 text.insert("insert", pad)
1025 text.see("insert")
1026 return "break"
1027 finally:
1028 text.undo_block_stop()
1029
1030 def newline_and_indent_event(self, event):
1031 text = self.text
1032 first, last = self.get_selection_indices()
1033 text.undo_block_start()
1034 try:
1035 if first and last:
1036 text.delete(first, last)
1037 text.mark_set("insert", first)
1038 line = text.get("insert linestart", "insert")
1039 i, n = 0, len(line)
1040 while i < n and line[i] in " \t":
1041 i = i+1
1042 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001043 # the cursor is in or at leading indentation in a continuation
1044 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001045 text.insert("insert linestart", '\n')
1046 return "break"
1047 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001048 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001049 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001050 last_line_of_prompt = sys.ps1.split('\n')[-1]
1051 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001052 line = line[:-1]
1053 i = i+1
1054 if i:
1055 text.delete("insert - %d chars" % i, "insert")
1056 # strip whitespace after insert point
1057 while text.get("insert") in " \t":
1058 text.delete("insert")
1059 # start new line
1060 text.insert("insert", '\n')
1061
1062 # adjust indentation for continuations and block
1063 # open/close first need to find the last stmt
1064 lno = index2line(text.index('insert'))
1065 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1066 for context in self.num_context_lines:
1067 startat = max(lno - context, 1)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001068 startatindex = repr(startat) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001069 rawtext = text.get(startatindex, "insert")
1070 y.set_str(rawtext)
1071 bod = y.find_good_parse_start(
1072 self.context_use_ps1,
1073 self._build_char_in_string_func(startatindex))
1074 if bod is not None or startat == 1:
1075 break
1076 y.set_lo(bod or 0)
1077 c = y.get_continuation_type()
1078 if c != PyParse.C_NONE:
1079 # The current stmt hasn't ended yet.
1080 if c == PyParse.C_STRING:
1081 # inside a string; just mimic the current indent
1082 text.insert("insert", indent)
1083 elif c == PyParse.C_BRACKET:
1084 # line up with the first (if any) element of the
1085 # last open bracket structure; else indent one
1086 # level beyond the indent of the line with the
1087 # last open bracket
1088 self.reindent_to(y.compute_bracket_indent())
1089 elif c == PyParse.C_BACKSLASH:
1090 # if more than one line in this stmt already, just
1091 # mimic the current indent; else if initial line
1092 # has a start on an assignment stmt, indent to
1093 # beyond leftmost =; else to beyond first chunk of
1094 # non-whitespace on initial line
1095 if y.get_num_lines_in_stmt() > 1:
1096 text.insert("insert", indent)
1097 else:
1098 self.reindent_to(y.compute_backslash_indent())
1099 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001100 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001101 return "break"
1102
1103 # This line starts a brand new stmt; indent relative to
1104 # indentation of initial line of closest preceding
1105 # interesting stmt.
1106 indent = y.get_base_indent_string()
1107 text.insert("insert", indent)
1108 if y.is_block_opener():
1109 self.smart_indent_event(event)
1110 elif indent and y.is_block_closer():
1111 self.smart_backspace_event(event)
1112 return "break"
1113 finally:
1114 text.see("insert")
1115 text.undo_block_stop()
1116
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001117 # Our editwin provides a is_char_in_string function that works
1118 # with a Tk text index, but PyParse only knows about offsets into
1119 # a string. This builds a function for PyParse that accepts an
1120 # offset.
1121
1122 def _build_char_in_string_func(self, startindex):
1123 def inner(offset, _startindex=startindex,
1124 _icis=self.is_char_in_string):
1125 return _icis(_startindex + "+%dc" % offset)
1126 return inner
1127
1128 def indent_region_event(self, event):
1129 head, tail, chars, lines = self.get_region()
1130 for pos in range(len(lines)):
1131 line = lines[pos]
1132 if line:
1133 raw, effective = classifyws(line, self.tabwidth)
1134 effective = effective + self.indentwidth
1135 lines[pos] = self._make_blanks(effective) + line[raw:]
1136 self.set_region(head, tail, chars, lines)
1137 return "break"
1138
1139 def dedent_region_event(self, event):
1140 head, tail, chars, lines = self.get_region()
1141 for pos in range(len(lines)):
1142 line = lines[pos]
1143 if line:
1144 raw, effective = classifyws(line, self.tabwidth)
1145 effective = max(effective - self.indentwidth, 0)
1146 lines[pos] = self._make_blanks(effective) + line[raw:]
1147 self.set_region(head, tail, chars, lines)
1148 return "break"
1149
1150 def comment_region_event(self, event):
1151 head, tail, chars, lines = self.get_region()
1152 for pos in range(len(lines) - 1):
1153 line = lines[pos]
1154 lines[pos] = '##' + line
1155 self.set_region(head, tail, chars, lines)
1156
1157 def uncomment_region_event(self, event):
1158 head, tail, chars, lines = self.get_region()
1159 for pos in range(len(lines)):
1160 line = lines[pos]
1161 if not line:
1162 continue
1163 if line[:2] == '##':
1164 line = line[2:]
1165 elif line[:1] == '#':
1166 line = line[1:]
1167 lines[pos] = line
1168 self.set_region(head, tail, chars, lines)
1169
1170 def tabify_region_event(self, event):
1171 head, tail, chars, lines = self.get_region()
1172 tabwidth = self._asktabwidth()
1173 for pos in range(len(lines)):
1174 line = lines[pos]
1175 if line:
1176 raw, effective = classifyws(line, tabwidth)
1177 ntabs, nspaces = divmod(effective, tabwidth)
1178 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1179 self.set_region(head, tail, chars, lines)
1180
1181 def untabify_region_event(self, event):
1182 head, tail, chars, lines = self.get_region()
1183 tabwidth = self._asktabwidth()
1184 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001185 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001186 self.set_region(head, tail, chars, lines)
1187
1188 def toggle_tabs_event(self, event):
1189 if self.askyesno(
1190 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001191 "Turn tabs " + ("on", "off")[self.usetabs] +
1192 "?\nIndent width " +
1193 ("will be", "remains at")[self.usetabs] + " 8.",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001194 parent=self.text):
1195 self.usetabs = not self.usetabs
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001196 # Try to prevent mixed tabs/spaces.
1197 # User must reset indent width manually after using tabs
1198 # if he insists on getting into trouble.
1199 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001200 return "break"
1201
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001202 # XXX this isn't bound to anything -- see tabwidth comments
1203## def change_tabwidth_event(self, event):
1204## new = self._asktabwidth()
1205## if new != self.tabwidth:
1206## self.tabwidth = new
1207## self.set_indentation_params(0, guess=0)
1208## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001209
1210 def change_indentwidth_event(self, event):
1211 new = self.askinteger(
1212 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001213 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001214 parent=self.text,
1215 initialvalue=self.indentwidth,
1216 minvalue=2,
1217 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001218 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001219 self.indentwidth = new
1220 return "break"
1221
1222 def get_region(self):
1223 text = self.text
1224 first, last = self.get_selection_indices()
1225 if first and last:
1226 head = text.index(first + " linestart")
1227 tail = text.index(last + "-1c lineend +1c")
1228 else:
1229 head = text.index("insert linestart")
1230 tail = text.index("insert lineend +1c")
1231 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001232 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001233 return head, tail, chars, lines
1234
1235 def set_region(self, head, tail, chars, lines):
1236 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001237 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001238 if newchars == chars:
1239 text.bell()
1240 return
1241 text.tag_remove("sel", "1.0", "end")
1242 text.mark_set("insert", head)
1243 text.undo_block_start()
1244 text.delete(head, tail)
1245 text.insert(head, newchars)
1246 text.undo_block_stop()
1247 text.tag_add("sel", head, "insert")
1248
1249 # Make string that displays as n leading blanks.
1250
1251 def _make_blanks(self, n):
1252 if self.usetabs:
1253 ntabs, nspaces = divmod(n, self.tabwidth)
1254 return '\t' * ntabs + ' ' * nspaces
1255 else:
1256 return ' ' * n
1257
1258 # Delete from beginning of line to insert point, then reinsert
1259 # column logical (meaning use tabs if appropriate) spaces.
1260
1261 def reindent_to(self, column):
1262 text = self.text
1263 text.undo_block_start()
1264 if text.compare("insert linestart", "!=", "insert"):
1265 text.delete("insert linestart", "insert")
1266 if column:
1267 text.insert("insert", self._make_blanks(column))
1268 text.undo_block_stop()
1269
1270 def _asktabwidth(self):
1271 return self.askinteger(
1272 "Tab width",
1273 "Spaces per tab? (2-16)",
1274 parent=self.text,
1275 initialvalue=self.indentwidth,
1276 minvalue=2,
1277 maxvalue=16) or self.tabwidth
1278
1279 # Guess indentwidth from text content.
1280 # Return guessed indentwidth. This should not be believed unless
1281 # it's in a reasonable range (e.g., it will be 0 if no indented
1282 # blocks are found).
1283
1284 def guess_indent(self):
1285 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1286 if opener and indented:
1287 raw, indentsmall = classifyws(opener, self.tabwidth)
1288 raw, indentlarge = classifyws(indented, self.tabwidth)
1289 else:
1290 indentsmall = indentlarge = 0
1291 return indentlarge - indentsmall
1292
1293# "line.col" -> line, as an int
1294def index2line(index):
1295 return int(float(index))
1296
1297# Look at the leading whitespace in s.
1298# Return pair (# of leading ws characters,
1299# effective # of leading blanks after expanding
1300# tabs to width tabwidth)
1301
1302def classifyws(s, tabwidth):
1303 raw = effective = 0
1304 for ch in s:
1305 if ch == ' ':
1306 raw = raw + 1
1307 effective = effective + 1
1308 elif ch == '\t':
1309 raw = raw + 1
1310 effective = (effective // tabwidth + 1) * tabwidth
1311 else:
1312 break
1313 return raw, effective
1314
1315import tokenize
1316_tokenize = tokenize
1317del tokenize
1318
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001319class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001320
1321 # .run() chews over the Text widget, looking for a block opener
1322 # and the stmt following it. Returns a pair,
1323 # (line containing block opener, line containing stmt)
1324 # Either or both may be None.
1325
1326 def __init__(self, text, tabwidth):
1327 self.text = text
1328 self.tabwidth = tabwidth
1329 self.i = self.finished = 0
1330 self.blkopenline = self.indentedline = None
1331
1332 def readline(self):
1333 if self.finished:
1334 return ""
1335 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001336 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001337 if self.text.compare(mark, ">=", "end"):
1338 return ""
1339 return self.text.get(mark, mark + " lineend+1c")
1340
1341 def tokeneater(self, type, token, start, end, line,
1342 INDENT=_tokenize.INDENT,
1343 NAME=_tokenize.NAME,
1344 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1345 if self.finished:
1346 pass
1347 elif type == NAME and token in OPENERS:
1348 self.blkopenline = line
1349 elif type == INDENT and self.blkopenline:
1350 self.indentedline = line
1351 self.finished = 1
1352
1353 def run(self):
1354 save_tabsize = _tokenize.tabsize
1355 _tokenize.tabsize = self.tabwidth
1356 try:
1357 try:
1358 _tokenize.tokenize(self.readline, self.tokeneater)
1359 except _tokenize.TokenError:
1360 # since we cut off the tokenizer early, we can trigger
1361 # spurious errors
1362 pass
1363 finally:
1364 _tokenize.tabsize = save_tabsize
1365 return self.blkopenline, self.indentedline
1366
1367### end autoindent code ###
1368
David Scherer7aced172000-08-15 01:13:23 +00001369def prepstr(s):
1370 # Helper to extract the underscore from a string, e.g.
1371 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001372 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001373 if i >= 0:
1374 s = s[:i] + s[i+1:]
1375 return i, s
1376
1377
1378keynames = {
1379 'bracketleft': '[',
1380 'bracketright': ']',
1381 'slash': '/',
1382}
1383
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001384def get_accelerator(keydefs, eventname):
1385 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001386 if not keylist:
1387 return ""
1388 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001389 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001390 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1391 s = re.sub("Key-", "", s)
1392 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1393 s = re.sub("Control-", "Ctrl-", s)
1394 s = re.sub("-", "+", s)
1395 s = re.sub("><", " ", s)
1396 s = re.sub("<", "", s)
1397 s = re.sub(">", "", s)
1398 return s
1399
1400
1401def fixwordbreaks(root):
1402 # Make sure that Tk's double-click and next/previous word
1403 # operations use our definition of a word (i.e. an identifier)
1404 tk = root.tk
1405 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1406 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1407 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1408
1409
1410def test():
1411 root = Tk()
1412 fixwordbreaks(root)
1413 root.withdraw()
1414 if sys.argv[1:]:
1415 filename = sys.argv[1]
1416 else:
1417 filename = None
1418 edit = EditorWindow(root=root, filename=filename)
1419 edit.set_close_hook(root.quit)
1420 root.mainloop()
1421 root.destroy()
1422
1423if __name__ == '__main__':
1424 test()