blob: 1cd496a5a63a118e3d89ed9ef6c469b94960b43f [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. Kaiserb1754452005-11-18 22:05:48 +00009from MultiCall import MultiCallCreator
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000010
11import webbrowser
David Scherer7aced172000-08-15 01:13:23 +000012import idlever
13import WindowList
Steven M. Gavac5976402002-01-04 03:06:08 +000014import SearchDialog
15import GrepDialog
16import ReplaceDialog
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +000017import PyParse
Steven M. Gavadc72f482002-01-03 11:51:07 +000018from configHandler import idleConf
Steven M. Gava3b55a892001-11-21 05:56:26 +000019import aboutDialog, textView, configDialog
David Scherer7aced172000-08-15 01:13:23 +000020
21# The default tab setting for a Text widget, in average-width characters.
22TK_TABWIDTH_DEFAULT = 8
23
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000024def _find_module(fullname, path=None):
25 """Version of imp.find_module() that handles hierarchical module names"""
26
27 file = None
28 for tgt in fullname.split('.'):
29 if file is not None:
30 file.close() # close intermediate files
31 (file, filename, descr) = imp.find_module(tgt, path)
32 if descr[2] == imp.PY_SOURCE:
33 break # find but not load the source file
34 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000035 try:
36 path = module.__path__
37 except AttributeError:
38 raise ImportError, 'No source for module ' + module.__name__
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000039 return file, filename, descr
40
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000041class EditorWindow(object):
David Scherer7aced172000-08-15 01:13:23 +000042 from Percolator import Percolator
43 from ColorDelegator import ColorDelegator
44 from UndoDelegator import UndoDelegator
45 from IOBinding import IOBinding
46 import Bindings
47 from Tkinter import Toplevel
48 from MultiStatusBar import MultiStatusBar
49
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000050 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000051
52 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000053 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000054 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000055 if sys.platform.count('linux'):
56 # look for html docs in a couple of standard places
57 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
58 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
59 dochome = '/var/www/html/python/index.html'
60 else:
61 basepath = '/usr/share/doc/' # standard location
62 dochome = os.path.join(basepath, pyver,
63 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000064 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +000065 chmfile = os.path.join(sys.prefix, 'Doc',
66 'Python%d%d.chm' % sys.version_info[:2])
Thomas Heller84ef1532003-09-23 20:53:10 +000067 if os.path.isfile(chmfile):
68 dochome = chmfile
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000069 dochome = os.path.normpath(dochome)
70 if os.path.isfile(dochome):
71 EditorWindow.help_url = dochome
72 else:
73 EditorWindow.help_url = "http://www.python.org/doc/current"
Steven M. Gavadc72f482002-01-03 11:51:07 +000074 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000075 self.flist = flist
76 root = root or flist.root
77 self.root = root
David Scherer7aced172000-08-15 01:13:23 +000078 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +000079 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000080 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +000081 self.tkinter_vars = flist.vars
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +000082 #self.top.instance_dict makes flist.inversedict avalable to
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000083 #configDialog.py so it can access all EditorWindow instaces
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +000084 self.top.instance_dict=flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +000085 else:
86 self.tkinter_vars = {} # keys: Tkinter event names
87 # values: Tkinter variable instances
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +000088 self.recent_files_path=os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +000089 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +000090 self.vbar = vbar = Scrollbar(top, name='vbar')
91 self.text_frame = text_frame = Frame(top)
Kurt B. Kaiser1061e722003-01-04 01:43:53 +000092 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000093 self.text = text = MultiCallCreator(Text)(
94 text_frame, name='text', padx=5, wrap='none',
Steven M. Gavadc72f482002-01-03 11:51:07 +000095 foreground=idleConf.GetHighlight(currentTheme,
96 'normal',fgBg='fg'),
97 background=idleConf.GetHighlight(currentTheme,
98 'normal',fgBg='bg'),
99 highlightcolor=idleConf.GetHighlight(currentTheme,
100 'hilite',fgBg='fg'),
101 highlightbackground=idleConf.GetHighlight(currentTheme,
102 'hilite',fgBg='bg'),
103 insertbackground=idleConf.GetHighlight(currentTheme,
104 'cursor',fgBg='fg'),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000105 width=self.width,
Steven M. Gavadc72f482002-01-03 11:51:07 +0000106 height=idleConf.GetOption('main','EditorWindow','height') )
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000107 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000108
109 self.createmenubar()
110 self.apply_bindings()
111
112 self.top.protocol("WM_DELETE_WINDOW", self.close)
113 self.top.bind("<<close-window>>", self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000114 text.bind("<<cut>>", self.cut)
115 text.bind("<<copy>>", self.copy)
116 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000117 text.bind("<<center-insert>>", self.center_insert_event)
118 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000119 text.bind("<<python-docs>>", self.python_docs)
120 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000121 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000122 text.bind("<<open-module>>", self.open_module)
123 text.bind("<<do-nothing>>", lambda event: "break")
124 text.bind("<<select-all>>", self.select_all)
125 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000126 text.bind("<<find>>", self.find_event)
127 text.bind("<<find-again>>", self.find_again_event)
128 text.bind("<<find-in-files>>", self.find_in_files_event)
129 text.bind("<<find-selection>>", self.find_selection_event)
130 text.bind("<<replace>>", self.replace_event)
131 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000132 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000133 text.bind("<<smart-backspace>>",self.smart_backspace_event)
134 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
135 text.bind("<<smart-indent>>",self.smart_indent_event)
136 text.bind("<<indent-region>>",self.indent_region_event)
137 text.bind("<<dedent-region>>",self.dedent_region_event)
138 text.bind("<<comment-region>>",self.comment_region_event)
139 text.bind("<<uncomment-region>>",self.uncomment_region_event)
140 text.bind("<<tabify-region>>",self.tabify_region_event)
141 text.bind("<<untabify-region>>",self.untabify_region_event)
142 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
143 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000144 text.bind("<Left>", self.move_at_edge_if_selection(0))
145 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000146 text.bind("<<del-word-left>>", self.del_word_left)
147 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000148
David Scherer7aced172000-08-15 01:13:23 +0000149 if flist:
150 flist.inversedict[self] = key
151 if key:
152 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000153 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000154 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
155 text.bind("<<open-class-browser>>", self.open_class_browser)
156 text.bind("<<open-path-browser>>", self.open_path_browser)
157
Steven M. Gava898a3652001-10-07 11:10:44 +0000158 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000159 vbar['command'] = text.yview
160 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000161 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000162 fontWeight = 'normal'
163 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000164 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000165 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
166 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
167 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000168 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
169 text.pack(side=TOP, fill=BOTH, expand=1)
170 text.focus_set()
171
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000172 # usetabs true -> literal tab characters are used by indent and
173 # dedent cmds, possibly mixed with spaces if
174 # indentwidth is not a multiple of tabwidth,
175 # which will cause Tabnanny to nag!
176 # false -> tab characters are converted to spaces by indent
177 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000178 # Although use-spaces=0 can be configured manually in config-main.def,
179 # configuration of tabs v. spaces is not supported in the configuration
180 # dialog. IDLE promotes the preferred Python indentation: use spaces!
181 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
182 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000183
184 # tabwidth is the display width of a literal tab character.
185 # CAUTION: telling Tk to use anything other than its default
186 # tab setting causes it to use an entirely different tabbing algorithm,
187 # treating tab stops as fixed distances from the left margin.
188 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000189 self.tabwidth = 8 # must remain 8 until Tk is fixed.
190
191 # indentwidth is the number of screen characters per indent level.
192 # The recommended Python indentation is four spaces.
193 self.indentwidth = self.tabwidth
194 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000195
196 # If context_use_ps1 is true, parsing searches back for a ps1 line;
197 # else searches for a popular (if, def, ...) Python stmt.
198 self.context_use_ps1 = False
199
200 # When searching backwards for a reliable place to begin parsing,
201 # first start num_context_lines[0] lines back, then
202 # num_context_lines[1] lines back if that didn't work, and so on.
203 # The last value should be huge (larger than the # of lines in a
204 # conceivable file).
205 # Making the initial values larger slows things down more often.
206 self.num_context_lines = 50, 500, 5000000
207
David Scherer7aced172000-08-15 01:13:23 +0000208 self.per = per = self.Percolator(text)
209 if self.ispythonsource(filename):
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000210 self.color = color = self.ColorDelegator()
211 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000212 else:
David Scherer7aced172000-08-15 01:13:23 +0000213 self.color = None
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000214
215 self.undo = undo = self.UndoDelegator()
216 per.insertfilter(undo)
217 text.undo_block_start = undo.undo_block_start
218 text.undo_block_stop = undo.undo_block_stop
219 undo.set_saved_change_hook(self.saved_change_hook)
220
221 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000222 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000223 io.set_filename_change_hook(self.filename_change_hook)
224
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000225 # Create the recent files submenu
226 self.recent_files_menu = Menu(self.menubar)
227 self.menudict['file'].insert_cascade(3, label='Recent Files',
228 underline=0,
229 menu=self.recent_files_menu)
230 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000231
David Scherer7aced172000-08-15 01:13:23 +0000232 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000233 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000234 io.loadfile(filename)
235 else:
236 io.set_filename(filename)
David Scherer7aced172000-08-15 01:13:23 +0000237 self.saved_change_hook()
238
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000239 self.set_indentation_params(self.ispythonsource(filename))
240
David Scherer7aced172000-08-15 01:13:23 +0000241 self.load_extensions()
242
243 menu = self.menudict.get('windows')
244 if menu:
245 end = menu.index("end")
246 if end is None:
247 end = -1
248 if end >= 0:
249 menu.add_separator()
250 end = end + 1
251 self.wmenu_end = end
252 WindowList.register_callback(self.postwindowsmenu)
253
254 # Some abstractions so IDLE extensions are cross-IDE
255 self.askyesno = tkMessageBox.askyesno
256 self.askinteger = tkSimpleDialog.askinteger
257 self.showerror = tkMessageBox.showerror
258
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000259 def new_callback(self, event):
260 dirname, basename = self.io.defaultfilename()
261 self.flist.new(dirname)
262 return "break"
263
David Scherer7aced172000-08-15 01:13:23 +0000264 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000265 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000266 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
267 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
268 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000269 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
270 self.text.event_add("<<set-line-and-column>>",
271 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000272 self.text.after_idle(self.set_line_and_column)
273
274 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000275 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000276 self.status_bar.set_label('column', 'Col: %s' % column)
277 self.status_bar.set_label('line', 'Ln: %s' % line)
278
David Scherer7aced172000-08-15 01:13:23 +0000279 menu_specs = [
280 ("file", "_File"),
281 ("edit", "_Edit"),
282 ("format", "F_ormat"),
283 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000284 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000285 ("windows", "_Windows"),
286 ("help", "_Help"),
287 ]
288
289 def createmenubar(self):
290 mbar = self.menubar
291 self.menudict = menudict = {}
292 for name, label in self.menu_specs:
293 underline, label = prepstr(label)
294 menudict[name] = menu = Menu(mbar, name=name)
295 mbar.add_cascade(label=label, menu=menu, underline=underline)
296 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000297 self.base_helpmenu_length = self.menudict['help'].index(END)
298 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000299
300 def postwindowsmenu(self):
301 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000302 menu = self.menudict['windows']
303 end = menu.index("end")
304 if end is None:
305 end = -1
306 if end > self.wmenu_end:
307 menu.delete(self.wmenu_end+1, end)
308 WindowList.add_windows_to_menu(menu)
309
310 rmenu = None
311
312 def right_menu_event(self, event):
313 self.text.tag_remove("sel", "1.0", "end")
314 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
315 if not self.rmenu:
316 self.make_rmenu()
317 rmenu = self.rmenu
318 self.event = event
319 iswin = sys.platform[:3] == 'win'
320 if iswin:
321 self.text.config(cursor="arrow")
322 rmenu.tk_popup(event.x_root, event.y_root)
323 if iswin:
324 self.text.config(cursor="ibeam")
325
326 rmenu_specs = [
327 # ("Label", "<<virtual-event>>"), ...
328 ("Close", "<<close-window>>"), # Example
329 ]
330
331 def make_rmenu(self):
332 rmenu = Menu(self.text, tearoff=0)
333 for label, eventname in self.rmenu_specs:
334 def command(text=self.text, eventname=eventname):
335 text.event_generate(eventname)
336 rmenu.add_command(label=label, command=command)
337 self.rmenu = rmenu
338
339 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000340 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000341
Steven M. Gava3b55a892001-11-21 05:56:26 +0000342 def config_dialog(self, event=None):
343 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000344
David Scherer7aced172000-08-15 01:13:23 +0000345 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000346 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000347 textView.TextViewer(self.top,'Help',fn)
348
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000349 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000350 if sys.platform[:3] == 'win':
Steven M. Gava931625d2002-04-22 00:38:26 +0000351 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000352 else:
353 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000354 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000355
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000356 def cut(self,event):
357 self.text.event_generate("<<Cut>>")
358 return "break"
359
360 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000361 if not self.text.tag_ranges("sel"):
362 # There is no selection, so do nothing and maybe interrupt.
363 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000364 self.text.event_generate("<<Copy>>")
365 return "break"
366
367 def paste(self,event):
368 self.text.event_generate("<<Paste>>")
369 return "break"
370
David Scherer7aced172000-08-15 01:13:23 +0000371 def select_all(self, event=None):
372 self.text.tag_add("sel", "1.0", "end-1c")
373 self.text.mark_set("insert", "1.0")
374 self.text.see("insert")
375 return "break"
376
377 def remove_selection(self, event=None):
378 self.text.tag_remove("sel", "1.0", "end")
379 self.text.see("insert")
380
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000381 def move_at_edge_if_selection(self, edge_index):
382 """Cursor move begins at start or end of selection
383
384 When a left/right cursor key is pressed create and return to Tkinter a
385 function which causes a cursor move from the associated edge of the
386 selection.
387
388 """
389 self_text_index = self.text.index
390 self_text_mark_set = self.text.mark_set
391 edges_table = ("sel.first+1c", "sel.last-1c")
392 def move_at_edge(event):
393 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
394 try:
395 self_text_index("sel.first")
396 self_text_mark_set("insert", edges_table[edge_index])
397 except TclError:
398 pass
399 return move_at_edge
400
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000401 def del_word_left(self, event):
402 self.text.event_generate('<Meta-Delete>')
403 return "break"
404
405 def del_word_right(self, event):
406 self.text.event_generate('<Meta-d>')
407 return "break"
408
Steven M. Gavac5976402002-01-04 03:06:08 +0000409 def find_event(self, event):
410 SearchDialog.find(self.text)
411 return "break"
412
413 def find_again_event(self, event):
414 SearchDialog.find_again(self.text)
415 return "break"
416
417 def find_selection_event(self, event):
418 SearchDialog.find_selection(self.text)
419 return "break"
420
421 def find_in_files_event(self, event):
422 GrepDialog.grep(self.text, self.io, self.flist)
423 return "break"
424
425 def replace_event(self, event):
426 ReplaceDialog.replace(self.text)
427 return "break"
428
429 def goto_line_event(self, event):
430 text = self.text
431 lineno = tkSimpleDialog.askinteger("Goto",
432 "Go to line number:",parent=text)
433 if lineno is None:
434 return "break"
435 if lineno <= 0:
436 text.bell()
437 return "break"
438 text.mark_set("insert", "%d.0" % lineno)
439 text.see("insert")
440
David Scherer7aced172000-08-15 01:13:23 +0000441 def open_module(self, event=None):
442 # XXX Shouldn't this be in IOBinding or in FileList?
443 try:
444 name = self.text.get("sel.first", "sel.last")
445 except TclError:
446 name = ""
447 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000448 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000449 name = tkSimpleDialog.askstring("Module",
450 "Enter the name of a Python module\n"
451 "to search on sys.path and open:",
452 parent=self.text, initialvalue=name)
453 if name:
454 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000455 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000456 return
David Scherer7aced172000-08-15 01:13:23 +0000457 # XXX Ought to insert current file's directory in front of path
458 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000459 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000460 except (NameError, ImportError), msg:
461 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
462 return
463 if type != imp.PY_SOURCE:
464 tkMessageBox.showerror("Unsupported type",
465 "%s is not a source module" % name, parent=self.text)
466 return
467 if f:
468 f.close()
469 if self.flist:
470 self.flist.open(file)
471 else:
472 self.io.loadfile(file)
473
474 def open_class_browser(self, event=None):
475 filename = self.io.filename
476 if not filename:
477 tkMessageBox.showerror(
478 "No filename",
479 "This buffer has no associated filename",
480 master=self.text)
481 self.text.focus_set()
482 return None
483 head, tail = os.path.split(filename)
484 base, ext = os.path.splitext(tail)
485 import ClassBrowser
486 ClassBrowser.ClassBrowser(self.flist, base, [head])
487
488 def open_path_browser(self, event=None):
489 import PathBrowser
490 PathBrowser.PathBrowser(self.flist)
491
492 def gotoline(self, lineno):
493 if lineno is not None and lineno > 0:
494 self.text.mark_set("insert", "%d.0" % lineno)
495 self.text.tag_remove("sel", "1.0", "end")
496 self.text.tag_add("sel", "insert", "insert +1l")
497 self.center()
498
499 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000500 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000501 return True
David Scherer7aced172000-08-15 01:13:23 +0000502 base, ext = os.path.splitext(os.path.basename(filename))
503 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000504 return True
David Scherer7aced172000-08-15 01:13:23 +0000505 try:
506 f = open(filename)
507 line = f.readline()
508 f.close()
509 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000510 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000511 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000512
513 def close_hook(self):
514 if self.flist:
515 self.flist.close_edit(self)
516
517 def set_close_hook(self, close_hook):
518 self.close_hook = close_hook
519
520 def filename_change_hook(self):
521 if self.flist:
522 self.flist.filename_changed_edit(self)
523 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000524 self.top.update_windowlist_registry(self)
David Scherer7aced172000-08-15 01:13:23 +0000525 if self.ispythonsource(self.io.filename):
526 self.addcolorizer()
527 else:
528 self.rmcolorizer()
529
530 def addcolorizer(self):
531 if self.color:
532 return
David Scherer7aced172000-08-15 01:13:23 +0000533 self.per.removefilter(self.undo)
534 self.color = self.ColorDelegator()
535 self.per.insertfilter(self.color)
536 self.per.insertfilter(self.undo)
537
538 def rmcolorizer(self):
539 if not self.color:
540 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000541 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000542 self.per.removefilter(self.undo)
543 self.per.removefilter(self.color)
544 self.color = None
545 self.per.insertfilter(self.undo)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000546
Steven M. Gavab77d3432002-03-02 07:16:21 +0000547 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000548 "Update the colour theme if it is changed"
549 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000550 if self.color:
551 self.color = self.ColorDelegator()
552 self.per.insertfilter(self.color)
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000553 theme = idleConf.GetOption('main','Theme','name')
554 self.text.config(idleConf.GetHighlight(theme, "normal"))
David Scherer7aced172000-08-15 01:13:23 +0000555
Steven M. Gavab1585412002-03-12 00:21:56 +0000556 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000557 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000558 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000559 fontWeight='normal'
560 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
561 fontWeight='bold'
562 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
563 idleConf.GetOption('main','EditorWindow','font-size'),
564 fontWeight))
565
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000566 def RemoveKeybindings(self):
567 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000568 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000569 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
570 keydefs = self.Bindings.default_keydefs
571 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000572 self.text.event_delete(event, *keylist)
573 for extensionName in self.get_standard_extension_names():
574 keydefs = idleConf.GetExtensionBindings(extensionName)
575 if keydefs:
576 for event, keylist in keydefs.items():
577 self.text.event_delete(event, *keylist)
578
579 def ApplyKeybindings(self):
580 "Update the keybindings after they are changed"
581 # Called from configDialog.py
582 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000583 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000584 for extensionName in self.get_standard_extension_names():
585 keydefs = idleConf.GetExtensionBindings(extensionName)
586 if keydefs:
587 self.apply_bindings(keydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000588 #update menu accelerators
589 menuEventDict={}
590 for menu in self.Bindings.menudefs:
591 menuEventDict[menu[0]]={}
592 for item in menu[1]:
593 if item:
594 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
595 for menubarItem in self.menudict.keys():
596 menu=self.menudict[menubarItem]
597 end=menu.index(END)+1
598 for index in range(0,end):
599 if menu.type(index)=='command':
600 accel=menu.entrycget(index,'accelerator')
601 if accel:
602 itemName=menu.entrycget(index,'label')
603 event=''
604 if menuEventDict.has_key(menubarItem):
605 if menuEventDict[menubarItem].has_key(itemName):
606 event=menuEventDict[menubarItem][itemName]
607 if event:
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000608 accel=get_accelerator(keydefs, event)
609 menu.entryconfig(index,accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000610
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000611 def set_notabs_indentwidth(self):
612 "Update the indentwidth if changed and not using tabs in this window"
613 # Called from configDialog.py
614 if not self.usetabs:
615 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
616 type='int')
617
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000618 def reset_help_menu_entries(self):
619 "Update the additional help entries on the Help menu"
620 help_list = idleConf.GetAllExtraHelpSourcesList()
621 helpmenu = self.menudict['help']
622 # first delete the extra help entries, if any
623 helpmenu_length = helpmenu.index(END)
624 if helpmenu_length > self.base_helpmenu_length:
625 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
626 # then rebuild them
627 if help_list:
628 helpmenu.add_separator()
629 for entry in help_list:
630 cmd = self.__extra_help_callback(entry[1])
631 helpmenu.add_command(label=entry[0], command=cmd)
632 # and update the menu dictionary
633 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000634
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000635 def __extra_help_callback(self, helpfile):
636 "Create a callback with the helpfile value frozen at definition time"
637 def display_extra_help(helpfile=helpfile):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000638 if not (helpfile.startswith('www') or helpfile.startswith('http')):
639 url = os.path.normpath(helpfile)
640 if sys.platform[:3] == 'win':
641 os.startfile(helpfile)
642 else:
643 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000644 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000645
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000646 def update_recent_files_list(self, new_file=None):
647 "Load and update the recent files list and menus"
648 rf_list = []
649 if os.path.exists(self.recent_files_path):
650 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000651 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000652 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000653 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000654 rf_list_file.close()
655 if new_file:
656 new_file = os.path.abspath(new_file) + '\n'
657 if new_file in rf_list:
658 rf_list.remove(new_file) # move to top
659 rf_list.insert(0, new_file)
660 # clean and save the recent files list
661 bad_paths = []
662 for path in rf_list:
663 if '\0' in path or not os.path.exists(path[0:-1]):
664 bad_paths.append(path)
665 rf_list = [path for path in rf_list if path not in bad_paths]
666 ulchars = "1234567890ABCDEFGHIJK"
667 rf_list = rf_list[0:len(ulchars)]
668 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000669 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000670 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000671 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000672 rf_file.close()
673 # for each edit window instance, construct the recent files menu
674 for instance in self.top.instance_dict.keys():
675 menu = instance.recent_files_menu
676 menu.delete(1, END) # clear, and rebuild:
677 for i, file in zip(count(), rf_list):
678 file_name = file[0:-1] # zap \n
679 callback = instance.__recent_file_callback(file_name)
680 menu.add_command(label=ulchars[i] + " " + file_name,
681 command=callback,
682 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000683
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000684 def __recent_file_callback(self, file_name):
685 def open_recent_file(fn_closure=file_name):
686 self.io.open(editFile=fn_closure)
687 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000688
David Scherer7aced172000-08-15 01:13:23 +0000689 def saved_change_hook(self):
690 short = self.short_title()
691 long = self.long_title()
692 if short and long:
693 title = short + " - " + long
694 elif short:
695 title = short
696 elif long:
697 title = long
698 else:
699 title = "Untitled"
700 icon = short or long or title
701 if not self.get_saved():
702 title = "*%s*" % title
703 icon = "*%s" % icon
704 self.top.wm_title(title)
705 self.top.wm_iconname(icon)
706
707 def get_saved(self):
708 return self.undo.get_saved()
709
710 def set_saved(self, flag):
711 self.undo.set_saved(flag)
712
713 def reset_undo(self):
714 self.undo.reset_undo()
715
716 def short_title(self):
717 filename = self.io.filename
718 if filename:
719 filename = os.path.basename(filename)
720 return filename
721
722 def long_title(self):
723 return self.io.filename or ""
724
725 def center_insert_event(self, event):
726 self.center()
727
728 def center(self, mark="insert"):
729 text = self.text
730 top, bot = self.getwindowlines()
731 lineno = self.getlineno(mark)
732 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000733 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000734 text.yview(float(newtop))
735
736 def getwindowlines(self):
737 text = self.text
738 top = self.getlineno("@0,0")
739 bot = self.getlineno("@0,65535")
740 if top == bot and text.winfo_height() == 1:
741 # Geometry manager hasn't run yet
742 height = int(text['height'])
743 bot = top + height - 1
744 return top, bot
745
746 def getlineno(self, mark="insert"):
747 text = self.text
748 return int(float(text.index(mark)))
749
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000750 def get_geometry(self):
751 "Return (width, height, x, y)"
752 geom = self.top.wm_geometry()
753 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
754 tuple = (map(int, m.groups()))
755 return tuple
756
David Scherer7aced172000-08-15 01:13:23 +0000757 def close_event(self, event):
758 self.close()
759
760 def maybesave(self):
761 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000762 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000763 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000764 self.top.deiconify()
765 self.top.lower()
766 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000767 return self.io.maybesave()
768
769 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000770 reply = self.maybesave()
771 if reply != "cancel":
772 self._close()
773 return reply
774
775 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000776 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000777 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000778 WindowList.unregister_callback(self.postwindowsmenu)
779 if self.close_hook:
780 self.close_hook()
781 self.flist = None
782 colorizing = 0
783 self.unload_extensions()
784 self.io.close(); self.io = None
785 self.undo = None # XXX
786 if self.color:
787 colorizing = self.color.colorizing
788 doh = colorizing and self.top
789 self.color.close(doh) # Cancel colorization
790 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000791 self.tkinter_vars = None
David Scherer7aced172000-08-15 01:13:23 +0000792 self.per.close(); self.per = None
793 if not colorizing:
794 self.top.destroy()
795
796 def load_extensions(self):
797 self.extensions = {}
798 self.load_standard_extensions()
799
800 def unload_extensions(self):
801 for ins in self.extensions.values():
802 if hasattr(ins, "close"):
803 ins.close()
804 self.extensions = {}
805
806 def load_standard_extensions(self):
807 for name in self.get_standard_extension_names():
808 try:
809 self.load_extension(name)
810 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000811 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +0000812 import traceback
813 traceback.print_exc()
814
815 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000816 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000817
818 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000819 try:
820 mod = __import__(name, globals(), locals(), [])
821 except ImportError:
822 print "\nFailed to import extension: ", name
823 return
David Scherer7aced172000-08-15 01:13:23 +0000824 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000825 keydefs = idleConf.GetExtensionBindings(name)
826 if hasattr(cls, "menudefs"):
827 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000828 ins = cls(self)
829 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000830 if keydefs:
831 self.apply_bindings(keydefs)
832 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000833 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000834 while methodname[:1] == '<':
835 methodname = methodname[1:]
836 while methodname[-1:] == '>':
837 methodname = methodname[:-1]
838 methodname = methodname + "_event"
839 if hasattr(ins, methodname):
840 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000841
842 def apply_bindings(self, keydefs=None):
843 if keydefs is None:
844 keydefs = self.Bindings.default_keydefs
845 text = self.text
846 text.keydefs = keydefs
847 for event, keylist in keydefs.items():
848 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000849 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000850
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000851 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000852 """Add appropriate entries to the menus and submenus
853
854 Menus that are absent or None in self.menudict are ignored.
855 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000856 if menudefs is None:
857 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000858 if keydefs is None:
859 keydefs = self.Bindings.default_keydefs
860 menudict = self.menudict
861 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000862 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000863 menu = menudict.get(mname)
864 if not menu:
865 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000866 for entry in entrylist:
867 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000868 menu.add_separator()
869 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000870 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000871 checkbutton = (label[:1] == '!')
872 if checkbutton:
873 label = label[1:]
874 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000875 accelerator = get_accelerator(keydefs, eventname)
876 def command(text=text, eventname=eventname):
877 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000878 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000879 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000880 menu.add_checkbutton(label=label, underline=underline,
881 command=command, accelerator=accelerator,
882 variable=var)
883 else:
884 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000885 command=command,
886 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000887
888 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000889 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +0000890 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000891 value = var.get()
892 return value
893 else:
894 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +0000895
896 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000897 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +0000898 if var:
899 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000900 else:
901 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +0000902
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000903 def get_var_obj(self, name, vartype=None):
904 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +0000905 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000906 # create a Tkinter variable object with self.text as master:
907 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000908 return var
909
910 # Tk implementations of "virtual text methods" -- each platform
911 # reusing IDLE's support code needs to define these for its GUI's
912 # flavor of widget.
913
914 # Is character at text_index in a Python string? Return 0 for
915 # "guaranteed no", true for anything else. This info is expensive
916 # to compute ab initio, but is probably already known by the
917 # platform's colorizer.
918
919 def is_char_in_string(self, text_index):
920 if self.color:
921 # Return true iff colorizer hasn't (re)gotten this far
922 # yet, or the character is tagged as being in a string
923 return self.text.tag_prevrange("TODO", text_index) or \
924 "STRING" in self.text.tag_names(text_index)
925 else:
926 # The colorizer is missing: assume the worst
927 return 1
928
929 # If a selection is defined in the text widget, return (start,
930 # end) as Tkinter text indices, otherwise return (None, None)
931 def get_selection_indices(self):
932 try:
933 first = self.text.index("sel.first")
934 last = self.text.index("sel.last")
935 return first, last
936 except TclError:
937 return None, None
938
939 # Return the text widget's current view of what a tab stop means
940 # (equivalent width in spaces).
941
942 def get_tabwidth(self):
943 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
944 return int(current)
945
946 # Set the text widget's current view of what a tab stop means.
947
948 def set_tabwidth(self, newtabwidth):
949 text = self.text
950 if self.get_tabwidth() != newtabwidth:
951 pixels = text.tk.call("font", "measure", text["font"],
952 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000953 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000954 text.configure(tabs=pixels)
955
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000956 # If ispythonsource and guess are true, guess a good value for
957 # indentwidth based on file content (if possible), and if
958 # indentwidth != tabwidth set usetabs false.
959 # In any case, adjust the Text widget's view of what a tab
960 # character means.
961
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000962 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000963 if guess and ispythonsource:
964 i = self.guess_indent()
965 if 2 <= i <= 8:
966 self.indentwidth = i
967 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000968 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000969 self.set_tabwidth(self.tabwidth)
970
971 def smart_backspace_event(self, event):
972 text = self.text
973 first, last = self.get_selection_indices()
974 if first and last:
975 text.delete(first, last)
976 text.mark_set("insert", first)
977 return "break"
978 # Delete whitespace left, until hitting a real char or closest
979 # preceding virtual tab stop.
980 chars = text.get("insert linestart", "insert")
981 if chars == '':
982 if text.compare("insert", ">", "1.0"):
983 # easy: delete preceding newline
984 text.delete("insert-1c")
985 else:
986 text.bell() # at start of buffer
987 return "break"
988 if chars[-1] not in " \t":
989 # easy: delete preceding real char
990 text.delete("insert-1c")
991 return "break"
992 # Ick. It may require *inserting* spaces if we back up over a
993 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000994 tabwidth = self.tabwidth
995 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000996 assert have > 0
997 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000998 # Debug prompt is multilined....
999 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001000 ncharsdeleted = 0
1001 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001002 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001003 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001004 chars = chars[:-1]
1005 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001006 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001007 if have <= want or chars[-1] not in " \t":
1008 break
1009 text.undo_block_start()
1010 text.delete("insert-%dc" % ncharsdeleted, "insert")
1011 if have < want:
1012 text.insert("insert", ' ' * (want - have))
1013 text.undo_block_stop()
1014 return "break"
1015
1016 def smart_indent_event(self, event):
1017 # if intraline selection:
1018 # delete it
1019 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001020 # do indent-region
1021 # else:
1022 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001023 text = self.text
1024 first, last = self.get_selection_indices()
1025 text.undo_block_start()
1026 try:
1027 if first and last:
1028 if index2line(first) != index2line(last):
1029 return self.indent_region_event(event)
1030 text.delete(first, last)
1031 text.mark_set("insert", first)
1032 prefix = text.get("insert linestart", "insert")
1033 raw, effective = classifyws(prefix, self.tabwidth)
1034 if raw == len(prefix):
1035 # only whitespace to the left
1036 self.reindent_to(effective + self.indentwidth)
1037 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001038 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001039 if self.usetabs:
1040 pad = '\t'
1041 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001042 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001043 n = self.indentwidth
1044 pad = ' ' * (n - effective % n)
1045 text.insert("insert", pad)
1046 text.see("insert")
1047 return "break"
1048 finally:
1049 text.undo_block_stop()
1050
1051 def newline_and_indent_event(self, event):
1052 text = self.text
1053 first, last = self.get_selection_indices()
1054 text.undo_block_start()
1055 try:
1056 if first and last:
1057 text.delete(first, last)
1058 text.mark_set("insert", first)
1059 line = text.get("insert linestart", "insert")
1060 i, n = 0, len(line)
1061 while i < n and line[i] in " \t":
1062 i = i+1
1063 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001064 # the cursor is in or at leading indentation in a continuation
1065 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001066 text.insert("insert linestart", '\n')
1067 return "break"
1068 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001069 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001070 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001071 last_line_of_prompt = sys.ps1.split('\n')[-1]
1072 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001073 line = line[:-1]
1074 i = i+1
1075 if i:
1076 text.delete("insert - %d chars" % i, "insert")
1077 # strip whitespace after insert point
1078 while text.get("insert") in " \t":
1079 text.delete("insert")
1080 # start new line
1081 text.insert("insert", '\n')
1082
1083 # adjust indentation for continuations and block
1084 # open/close first need to find the last stmt
1085 lno = index2line(text.index('insert'))
1086 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001087 if not self.context_use_ps1:
1088 for context in self.num_context_lines:
1089 startat = max(lno - context, 1)
1090 startatindex = `startat` + ".0"
1091 rawtext = text.get(startatindex, "insert")
1092 y.set_str(rawtext)
1093 bod = y.find_good_parse_start(
1094 self.context_use_ps1,
1095 self._build_char_in_string_func(startatindex))
1096 if bod is not None or startat == 1:
1097 break
1098 y.set_lo(bod or 0)
1099 else:
1100 r = text.tag_prevrange("console", "insert")
1101 if r:
1102 startatindex = r[1]
1103 else:
1104 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001105 rawtext = text.get(startatindex, "insert")
1106 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001107 y.set_lo(0)
1108
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001109 c = y.get_continuation_type()
1110 if c != PyParse.C_NONE:
1111 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001112 if c == PyParse.C_STRING_FIRST_LINE:
1113 # after the first line of a string; do not indent at all
1114 pass
1115 elif c == PyParse.C_STRING_NEXT_LINES:
1116 # inside a string which started before this line;
1117 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001118 text.insert("insert", indent)
1119 elif c == PyParse.C_BRACKET:
1120 # line up with the first (if any) element of the
1121 # last open bracket structure; else indent one
1122 # level beyond the indent of the line with the
1123 # last open bracket
1124 self.reindent_to(y.compute_bracket_indent())
1125 elif c == PyParse.C_BACKSLASH:
1126 # if more than one line in this stmt already, just
1127 # mimic the current indent; else if initial line
1128 # has a start on an assignment stmt, indent to
1129 # beyond leftmost =; else to beyond first chunk of
1130 # non-whitespace on initial line
1131 if y.get_num_lines_in_stmt() > 1:
1132 text.insert("insert", indent)
1133 else:
1134 self.reindent_to(y.compute_backslash_indent())
1135 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001136 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001137 return "break"
1138
1139 # This line starts a brand new stmt; indent relative to
1140 # indentation of initial line of closest preceding
1141 # interesting stmt.
1142 indent = y.get_base_indent_string()
1143 text.insert("insert", indent)
1144 if y.is_block_opener():
1145 self.smart_indent_event(event)
1146 elif indent and y.is_block_closer():
1147 self.smart_backspace_event(event)
1148 return "break"
1149 finally:
1150 text.see("insert")
1151 text.undo_block_stop()
1152
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001153 # Our editwin provides a is_char_in_string function that works
1154 # with a Tk text index, but PyParse only knows about offsets into
1155 # a string. This builds a function for PyParse that accepts an
1156 # offset.
1157
1158 def _build_char_in_string_func(self, startindex):
1159 def inner(offset, _startindex=startindex,
1160 _icis=self.is_char_in_string):
1161 return _icis(_startindex + "+%dc" % offset)
1162 return inner
1163
1164 def indent_region_event(self, event):
1165 head, tail, chars, lines = self.get_region()
1166 for pos in range(len(lines)):
1167 line = lines[pos]
1168 if line:
1169 raw, effective = classifyws(line, self.tabwidth)
1170 effective = effective + self.indentwidth
1171 lines[pos] = self._make_blanks(effective) + line[raw:]
1172 self.set_region(head, tail, chars, lines)
1173 return "break"
1174
1175 def dedent_region_event(self, event):
1176 head, tail, chars, lines = self.get_region()
1177 for pos in range(len(lines)):
1178 line = lines[pos]
1179 if line:
1180 raw, effective = classifyws(line, self.tabwidth)
1181 effective = max(effective - self.indentwidth, 0)
1182 lines[pos] = self._make_blanks(effective) + line[raw:]
1183 self.set_region(head, tail, chars, lines)
1184 return "break"
1185
1186 def comment_region_event(self, event):
1187 head, tail, chars, lines = self.get_region()
1188 for pos in range(len(lines) - 1):
1189 line = lines[pos]
1190 lines[pos] = '##' + line
1191 self.set_region(head, tail, chars, lines)
1192
1193 def uncomment_region_event(self, event):
1194 head, tail, chars, lines = self.get_region()
1195 for pos in range(len(lines)):
1196 line = lines[pos]
1197 if not line:
1198 continue
1199 if line[:2] == '##':
1200 line = line[2:]
1201 elif line[:1] == '#':
1202 line = line[1:]
1203 lines[pos] = line
1204 self.set_region(head, tail, chars, lines)
1205
1206 def tabify_region_event(self, event):
1207 head, tail, chars, lines = self.get_region()
1208 tabwidth = self._asktabwidth()
1209 for pos in range(len(lines)):
1210 line = lines[pos]
1211 if line:
1212 raw, effective = classifyws(line, tabwidth)
1213 ntabs, nspaces = divmod(effective, tabwidth)
1214 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1215 self.set_region(head, tail, chars, lines)
1216
1217 def untabify_region_event(self, event):
1218 head, tail, chars, lines = self.get_region()
1219 tabwidth = self._asktabwidth()
1220 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001221 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001222 self.set_region(head, tail, chars, lines)
1223
1224 def toggle_tabs_event(self, event):
1225 if self.askyesno(
1226 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001227 "Turn tabs " + ("on", "off")[self.usetabs] +
1228 "?\nIndent width " +
1229 ("will be", "remains at")[self.usetabs] + " 8.",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001230 parent=self.text):
1231 self.usetabs = not self.usetabs
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001232 # Try to prevent mixed tabs/spaces.
1233 # User must reset indent width manually after using tabs
1234 # if he insists on getting into trouble.
1235 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001236 return "break"
1237
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001238 # XXX this isn't bound to anything -- see tabwidth comments
1239## def change_tabwidth_event(self, event):
1240## new = self._asktabwidth()
1241## if new != self.tabwidth:
1242## self.tabwidth = new
1243## self.set_indentation_params(0, guess=0)
1244## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001245
1246 def change_indentwidth_event(self, event):
1247 new = self.askinteger(
1248 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001249 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001250 parent=self.text,
1251 initialvalue=self.indentwidth,
1252 minvalue=2,
1253 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001254 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001255 self.indentwidth = new
1256 return "break"
1257
1258 def get_region(self):
1259 text = self.text
1260 first, last = self.get_selection_indices()
1261 if first and last:
1262 head = text.index(first + " linestart")
1263 tail = text.index(last + "-1c lineend +1c")
1264 else:
1265 head = text.index("insert linestart")
1266 tail = text.index("insert lineend +1c")
1267 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001268 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001269 return head, tail, chars, lines
1270
1271 def set_region(self, head, tail, chars, lines):
1272 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001273 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001274 if newchars == chars:
1275 text.bell()
1276 return
1277 text.tag_remove("sel", "1.0", "end")
1278 text.mark_set("insert", head)
1279 text.undo_block_start()
1280 text.delete(head, tail)
1281 text.insert(head, newchars)
1282 text.undo_block_stop()
1283 text.tag_add("sel", head, "insert")
1284
1285 # Make string that displays as n leading blanks.
1286
1287 def _make_blanks(self, n):
1288 if self.usetabs:
1289 ntabs, nspaces = divmod(n, self.tabwidth)
1290 return '\t' * ntabs + ' ' * nspaces
1291 else:
1292 return ' ' * n
1293
1294 # Delete from beginning of line to insert point, then reinsert
1295 # column logical (meaning use tabs if appropriate) spaces.
1296
1297 def reindent_to(self, column):
1298 text = self.text
1299 text.undo_block_start()
1300 if text.compare("insert linestart", "!=", "insert"):
1301 text.delete("insert linestart", "insert")
1302 if column:
1303 text.insert("insert", self._make_blanks(column))
1304 text.undo_block_stop()
1305
1306 def _asktabwidth(self):
1307 return self.askinteger(
1308 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001309 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001310 parent=self.text,
1311 initialvalue=self.indentwidth,
1312 minvalue=2,
1313 maxvalue=16) or self.tabwidth
1314
1315 # Guess indentwidth from text content.
1316 # Return guessed indentwidth. This should not be believed unless
1317 # it's in a reasonable range (e.g., it will be 0 if no indented
1318 # blocks are found).
1319
1320 def guess_indent(self):
1321 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1322 if opener and indented:
1323 raw, indentsmall = classifyws(opener, self.tabwidth)
1324 raw, indentlarge = classifyws(indented, self.tabwidth)
1325 else:
1326 indentsmall = indentlarge = 0
1327 return indentlarge - indentsmall
1328
1329# "line.col" -> line, as an int
1330def index2line(index):
1331 return int(float(index))
1332
1333# Look at the leading whitespace in s.
1334# Return pair (# of leading ws characters,
1335# effective # of leading blanks after expanding
1336# tabs to width tabwidth)
1337
1338def classifyws(s, tabwidth):
1339 raw = effective = 0
1340 for ch in s:
1341 if ch == ' ':
1342 raw = raw + 1
1343 effective = effective + 1
1344 elif ch == '\t':
1345 raw = raw + 1
1346 effective = (effective // tabwidth + 1) * tabwidth
1347 else:
1348 break
1349 return raw, effective
1350
1351import tokenize
1352_tokenize = tokenize
1353del tokenize
1354
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001355class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001356
1357 # .run() chews over the Text widget, looking for a block opener
1358 # and the stmt following it. Returns a pair,
1359 # (line containing block opener, line containing stmt)
1360 # Either or both may be None.
1361
1362 def __init__(self, text, tabwidth):
1363 self.text = text
1364 self.tabwidth = tabwidth
1365 self.i = self.finished = 0
1366 self.blkopenline = self.indentedline = None
1367
1368 def readline(self):
1369 if self.finished:
1370 return ""
1371 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001372 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001373 if self.text.compare(mark, ">=", "end"):
1374 return ""
1375 return self.text.get(mark, mark + " lineend+1c")
1376
1377 def tokeneater(self, type, token, start, end, line,
1378 INDENT=_tokenize.INDENT,
1379 NAME=_tokenize.NAME,
1380 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1381 if self.finished:
1382 pass
1383 elif type == NAME and token in OPENERS:
1384 self.blkopenline = line
1385 elif type == INDENT and self.blkopenline:
1386 self.indentedline = line
1387 self.finished = 1
1388
1389 def run(self):
1390 save_tabsize = _tokenize.tabsize
1391 _tokenize.tabsize = self.tabwidth
1392 try:
1393 try:
1394 _tokenize.tokenize(self.readline, self.tokeneater)
1395 except _tokenize.TokenError:
1396 # since we cut off the tokenizer early, we can trigger
1397 # spurious errors
1398 pass
1399 finally:
1400 _tokenize.tabsize = save_tabsize
1401 return self.blkopenline, self.indentedline
1402
1403### end autoindent code ###
1404
David Scherer7aced172000-08-15 01:13:23 +00001405def prepstr(s):
1406 # Helper to extract the underscore from a string, e.g.
1407 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001408 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001409 if i >= 0:
1410 s = s[:i] + s[i+1:]
1411 return i, s
1412
1413
1414keynames = {
1415 'bracketleft': '[',
1416 'bracketright': ']',
1417 'slash': '/',
1418}
1419
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001420def get_accelerator(keydefs, eventname):
1421 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001422 if not keylist:
1423 return ""
1424 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001425 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001426 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1427 s = re.sub("Key-", "", s)
1428 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1429 s = re.sub("Control-", "Ctrl-", s)
1430 s = re.sub("-", "+", s)
1431 s = re.sub("><", " ", s)
1432 s = re.sub("<", "", s)
1433 s = re.sub(">", "", s)
1434 return s
1435
1436
1437def fixwordbreaks(root):
1438 # Make sure that Tk's double-click and next/previous word
1439 # operations use our definition of a word (i.e. an identifier)
1440 tk = root.tk
1441 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1442 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1443 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1444
1445
1446def test():
1447 root = Tk()
1448 fixwordbreaks(root)
1449 root.withdraw()
1450 if sys.argv[1:]:
1451 filename = sys.argv[1]
1452 else:
1453 filename = None
1454 edit = EditorWindow(root=root, filename=filename)
1455 edit.set_close_hook(root.quit)
1456 root.mainloop()
1457 root.destroy()
1458
1459if __name__ == '__main__':
1460 test()