blob: 31f400c0ad641a80cb15b079200bf5dc9dab42cb [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
Steven M. Gavab1585412002-03-12 00:21:56 +0000160 fontWeight='normal'
161 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
162 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000163 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000164 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
176 self.usetabs = False
177
178 # indentwidth is the number of characters per logical indent level.
179 # Recommended Python default indent is four spaces.
180 self.indentwidth = 4
181
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.
187 self.tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed.
188 # indentwidth should be 8 when usetabs is True.
189
190 # If context_use_ps1 is true, parsing searches back for a ps1 line;
191 # else searches for a popular (if, def, ...) Python stmt.
192 self.context_use_ps1 = False
193
194 # When searching backwards for a reliable place to begin parsing,
195 # first start num_context_lines[0] lines back, then
196 # num_context_lines[1] lines back if that didn't work, and so on.
197 # The last value should be huge (larger than the # of lines in a
198 # conceivable file).
199 # Making the initial values larger slows things down more often.
200 self.num_context_lines = 50, 500, 5000000
201
David Scherer7aced172000-08-15 01:13:23 +0000202 self.per = per = self.Percolator(text)
203 if self.ispythonsource(filename):
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000204 self.color = color = self.ColorDelegator()
205 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000206 else:
David Scherer7aced172000-08-15 01:13:23 +0000207 self.color = None
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000208
209 self.undo = undo = self.UndoDelegator()
210 per.insertfilter(undo)
211 text.undo_block_start = undo.undo_block_start
212 text.undo_block_stop = undo.undo_block_stop
213 undo.set_saved_change_hook(self.saved_change_hook)
214
215 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000216 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000217 io.set_filename_change_hook(self.filename_change_hook)
218
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000219 # Create the recent files submenu
220 self.recent_files_menu = Menu(self.menubar)
221 self.menudict['file'].insert_cascade(3, label='Recent Files',
222 underline=0,
223 menu=self.recent_files_menu)
224 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000225
David Scherer7aced172000-08-15 01:13:23 +0000226 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000227 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000228 io.loadfile(filename)
229 else:
230 io.set_filename(filename)
David Scherer7aced172000-08-15 01:13:23 +0000231 self.saved_change_hook()
232
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000233 self.set_indentation_params(self.ispythonsource(filename))
234
David Scherer7aced172000-08-15 01:13:23 +0000235 self.load_extensions()
236
237 menu = self.menudict.get('windows')
238 if menu:
239 end = menu.index("end")
240 if end is None:
241 end = -1
242 if end >= 0:
243 menu.add_separator()
244 end = end + 1
245 self.wmenu_end = end
246 WindowList.register_callback(self.postwindowsmenu)
247
248 # Some abstractions so IDLE extensions are cross-IDE
249 self.askyesno = tkMessageBox.askyesno
250 self.askinteger = tkSimpleDialog.askinteger
251 self.showerror = tkMessageBox.showerror
252
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000253 def new_callback(self, event):
254 dirname, basename = self.io.defaultfilename()
255 self.flist.new(dirname)
256 return "break"
257
David Scherer7aced172000-08-15 01:13:23 +0000258 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000259 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000260 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
261 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
262 self.status_bar.pack(side=BOTTOM, fill=X)
263 self.text.bind('<KeyRelease>', self.set_line_and_column)
264 self.text.bind('<ButtonRelease>', self.set_line_and_column)
265 self.text.after_idle(self.set_line_and_column)
266
267 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000268 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000269 self.status_bar.set_label('column', 'Col: %s' % column)
270 self.status_bar.set_label('line', 'Ln: %s' % line)
271
David Scherer7aced172000-08-15 01:13:23 +0000272 menu_specs = [
273 ("file", "_File"),
274 ("edit", "_Edit"),
275 ("format", "F_ormat"),
276 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000277 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000278 ("windows", "_Windows"),
279 ("help", "_Help"),
280 ]
281
282 def createmenubar(self):
283 mbar = self.menubar
284 self.menudict = menudict = {}
285 for name, label in self.menu_specs:
286 underline, label = prepstr(label)
287 menudict[name] = menu = Menu(mbar, name=name)
288 mbar.add_cascade(label=label, menu=menu, underline=underline)
289 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000290 self.base_helpmenu_length = self.menudict['help'].index(END)
291 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000292
293 def postwindowsmenu(self):
294 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000295 menu = self.menudict['windows']
296 end = menu.index("end")
297 if end is None:
298 end = -1
299 if end > self.wmenu_end:
300 menu.delete(self.wmenu_end+1, end)
301 WindowList.add_windows_to_menu(menu)
302
303 rmenu = None
304
305 def right_menu_event(self, event):
306 self.text.tag_remove("sel", "1.0", "end")
307 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
308 if not self.rmenu:
309 self.make_rmenu()
310 rmenu = self.rmenu
311 self.event = event
312 iswin = sys.platform[:3] == 'win'
313 if iswin:
314 self.text.config(cursor="arrow")
315 rmenu.tk_popup(event.x_root, event.y_root)
316 if iswin:
317 self.text.config(cursor="ibeam")
318
319 rmenu_specs = [
320 # ("Label", "<<virtual-event>>"), ...
321 ("Close", "<<close-window>>"), # Example
322 ]
323
324 def make_rmenu(self):
325 rmenu = Menu(self.text, tearoff=0)
326 for label, eventname in self.rmenu_specs:
327 def command(text=self.text, eventname=eventname):
328 text.event_generate(eventname)
329 rmenu.add_command(label=label, command=command)
330 self.rmenu = rmenu
331
332 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000333 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000334
Steven M. Gava3b55a892001-11-21 05:56:26 +0000335 def config_dialog(self, event=None):
336 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000337
David Scherer7aced172000-08-15 01:13:23 +0000338 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000339 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000340 textView.TextViewer(self.top,'Help',fn)
341
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000342 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000343 if sys.platform[:3] == 'win':
Steven M. Gava931625d2002-04-22 00:38:26 +0000344 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000345 else:
346 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000347 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000348
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000349 def cut(self,event):
350 self.text.event_generate("<<Cut>>")
351 return "break"
352
353 def copy(self,event):
354 self.text.event_generate("<<Copy>>")
355 return "break"
356
357 def paste(self,event):
358 self.text.event_generate("<<Paste>>")
359 return "break"
360
David Scherer7aced172000-08-15 01:13:23 +0000361 def select_all(self, event=None):
362 self.text.tag_add("sel", "1.0", "end-1c")
363 self.text.mark_set("insert", "1.0")
364 self.text.see("insert")
365 return "break"
366
367 def remove_selection(self, event=None):
368 self.text.tag_remove("sel", "1.0", "end")
369 self.text.see("insert")
370
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000371 def move_at_edge_if_selection(self, edge_index):
372 """Cursor move begins at start or end of selection
373
374 When a left/right cursor key is pressed create and return to Tkinter a
375 function which causes a cursor move from the associated edge of the
376 selection.
377
378 """
379 self_text_index = self.text.index
380 self_text_mark_set = self.text.mark_set
381 edges_table = ("sel.first+1c", "sel.last-1c")
382 def move_at_edge(event):
383 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
384 try:
385 self_text_index("sel.first")
386 self_text_mark_set("insert", edges_table[edge_index])
387 except TclError:
388 pass
389 return move_at_edge
390
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000391 def del_word_left(self, event):
392 self.text.event_generate('<Meta-Delete>')
393 return "break"
394
395 def del_word_right(self, event):
396 self.text.event_generate('<Meta-d>')
397 return "break"
398
Steven M. Gavac5976402002-01-04 03:06:08 +0000399 def find_event(self, event):
400 SearchDialog.find(self.text)
401 return "break"
402
403 def find_again_event(self, event):
404 SearchDialog.find_again(self.text)
405 return "break"
406
407 def find_selection_event(self, event):
408 SearchDialog.find_selection(self.text)
409 return "break"
410
411 def find_in_files_event(self, event):
412 GrepDialog.grep(self.text, self.io, self.flist)
413 return "break"
414
415 def replace_event(self, event):
416 ReplaceDialog.replace(self.text)
417 return "break"
418
419 def goto_line_event(self, event):
420 text = self.text
421 lineno = tkSimpleDialog.askinteger("Goto",
422 "Go to line number:",parent=text)
423 if lineno is None:
424 return "break"
425 if lineno <= 0:
426 text.bell()
427 return "break"
428 text.mark_set("insert", "%d.0" % lineno)
429 text.see("insert")
430
David Scherer7aced172000-08-15 01:13:23 +0000431 def open_module(self, event=None):
432 # XXX Shouldn't this be in IOBinding or in FileList?
433 try:
434 name = self.text.get("sel.first", "sel.last")
435 except TclError:
436 name = ""
437 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000438 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000439 name = tkSimpleDialog.askstring("Module",
440 "Enter the name of a Python module\n"
441 "to search on sys.path and open:",
442 parent=self.text, initialvalue=name)
443 if name:
444 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000445 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000446 return
David Scherer7aced172000-08-15 01:13:23 +0000447 # XXX Ought to insert current file's directory in front of path
448 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000449 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000450 except (NameError, ImportError), msg:
451 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
452 return
453 if type != imp.PY_SOURCE:
454 tkMessageBox.showerror("Unsupported type",
455 "%s is not a source module" % name, parent=self.text)
456 return
457 if f:
458 f.close()
459 if self.flist:
460 self.flist.open(file)
461 else:
462 self.io.loadfile(file)
463
464 def open_class_browser(self, event=None):
465 filename = self.io.filename
466 if not filename:
467 tkMessageBox.showerror(
468 "No filename",
469 "This buffer has no associated filename",
470 master=self.text)
471 self.text.focus_set()
472 return None
473 head, tail = os.path.split(filename)
474 base, ext = os.path.splitext(tail)
475 import ClassBrowser
476 ClassBrowser.ClassBrowser(self.flist, base, [head])
477
478 def open_path_browser(self, event=None):
479 import PathBrowser
480 PathBrowser.PathBrowser(self.flist)
481
482 def gotoline(self, lineno):
483 if lineno is not None and lineno > 0:
484 self.text.mark_set("insert", "%d.0" % lineno)
485 self.text.tag_remove("sel", "1.0", "end")
486 self.text.tag_add("sel", "insert", "insert +1l")
487 self.center()
488
489 def ispythonsource(self, filename):
490 if not filename:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000491 return True
David Scherer7aced172000-08-15 01:13:23 +0000492 base, ext = os.path.splitext(os.path.basename(filename))
493 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000494 return True
David Scherer7aced172000-08-15 01:13:23 +0000495 try:
496 f = open(filename)
497 line = f.readline()
498 f.close()
499 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000500 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000501 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000502
503 def close_hook(self):
504 if self.flist:
505 self.flist.close_edit(self)
506
507 def set_close_hook(self, close_hook):
508 self.close_hook = close_hook
509
510 def filename_change_hook(self):
511 if self.flist:
512 self.flist.filename_changed_edit(self)
513 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000514 self.top.update_windowlist_registry(self)
David Scherer7aced172000-08-15 01:13:23 +0000515 if self.ispythonsource(self.io.filename):
516 self.addcolorizer()
517 else:
518 self.rmcolorizer()
519
520 def addcolorizer(self):
521 if self.color:
522 return
David Scherer7aced172000-08-15 01:13:23 +0000523 self.per.removefilter(self.undo)
524 self.color = self.ColorDelegator()
525 self.per.insertfilter(self.color)
526 self.per.insertfilter(self.undo)
527
528 def rmcolorizer(self):
529 if not self.color:
530 return
David Scherer7aced172000-08-15 01:13:23 +0000531 self.per.removefilter(self.undo)
532 self.per.removefilter(self.color)
533 self.color = None
534 self.per.insertfilter(self.undo)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000535
Steven M. Gavab77d3432002-03-02 07:16:21 +0000536 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000537 "Update the colour theme if it is changed"
538 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000539 if self.color:
540 self.color = self.ColorDelegator()
541 self.per.insertfilter(self.color)
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000542 theme = idleConf.GetOption('main','Theme','name')
543 self.text.config(idleConf.GetHighlight(theme, "normal"))
David Scherer7aced172000-08-15 01:13:23 +0000544
Steven M. Gavab1585412002-03-12 00:21:56 +0000545 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000546 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000547 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000548 fontWeight='normal'
549 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
550 fontWeight='bold'
551 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
552 idleConf.GetOption('main','EditorWindow','font-size'),
553 fontWeight))
554
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000555 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000556 "Update the keybindings if they are changed"
557 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000558 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
559 keydefs = self.Bindings.default_keydefs
560 for event, keylist in keydefs.items():
561 self.text.event_delete(event)
562 self.apply_bindings()
563 #update menu accelerators
564 menuEventDict={}
565 for menu in self.Bindings.menudefs:
566 menuEventDict[menu[0]]={}
567 for item in menu[1]:
568 if item:
569 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
570 for menubarItem in self.menudict.keys():
571 menu=self.menudict[menubarItem]
572 end=menu.index(END)+1
573 for index in range(0,end):
574 if menu.type(index)=='command':
575 accel=menu.entrycget(index,'accelerator')
576 if accel:
577 itemName=menu.entrycget(index,'label')
578 event=''
579 if menuEventDict.has_key(menubarItem):
580 if menuEventDict[menubarItem].has_key(itemName):
581 event=menuEventDict[menubarItem][itemName]
582 if event:
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000583 accel=get_accelerator(keydefs, event)
584 menu.entryconfig(index,accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000585
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000586 def reset_help_menu_entries(self):
587 "Update the additional help entries on the Help menu"
588 help_list = idleConf.GetAllExtraHelpSourcesList()
589 helpmenu = self.menudict['help']
590 # first delete the extra help entries, if any
591 helpmenu_length = helpmenu.index(END)
592 if helpmenu_length > self.base_helpmenu_length:
593 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
594 # then rebuild them
595 if help_list:
596 helpmenu.add_separator()
597 for entry in help_list:
598 cmd = self.__extra_help_callback(entry[1])
599 helpmenu.add_command(label=entry[0], command=cmd)
600 # and update the menu dictionary
601 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000602
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000603 def __extra_help_callback(self, helpfile):
604 "Create a callback with the helpfile value frozen at definition time"
605 def display_extra_help(helpfile=helpfile):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000606 if not (helpfile.startswith('www') or helpfile.startswith('http')):
607 url = os.path.normpath(helpfile)
608 if sys.platform[:3] == 'win':
609 os.startfile(helpfile)
610 else:
611 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000612 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000613
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000614 def update_recent_files_list(self, new_file=None):
615 "Load and update the recent files list and menus"
616 rf_list = []
617 if os.path.exists(self.recent_files_path):
618 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000619 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000620 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000621 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000622 rf_list_file.close()
623 if new_file:
624 new_file = os.path.abspath(new_file) + '\n'
625 if new_file in rf_list:
626 rf_list.remove(new_file) # move to top
627 rf_list.insert(0, new_file)
628 # clean and save the recent files list
629 bad_paths = []
630 for path in rf_list:
631 if '\0' in path or not os.path.exists(path[0:-1]):
632 bad_paths.append(path)
633 rf_list = [path for path in rf_list if path not in bad_paths]
634 ulchars = "1234567890ABCDEFGHIJK"
635 rf_list = rf_list[0:len(ulchars)]
636 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000637 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000638 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000639 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000640 rf_file.close()
641 # for each edit window instance, construct the recent files menu
642 for instance in self.top.instance_dict.keys():
643 menu = instance.recent_files_menu
644 menu.delete(1, END) # clear, and rebuild:
645 for i, file in zip(count(), rf_list):
646 file_name = file[0:-1] # zap \n
647 callback = instance.__recent_file_callback(file_name)
648 menu.add_command(label=ulchars[i] + " " + file_name,
649 command=callback,
650 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000651
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000652 def __recent_file_callback(self, file_name):
653 def open_recent_file(fn_closure=file_name):
654 self.io.open(editFile=fn_closure)
655 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000656
David Scherer7aced172000-08-15 01:13:23 +0000657 def saved_change_hook(self):
658 short = self.short_title()
659 long = self.long_title()
660 if short and long:
661 title = short + " - " + long
662 elif short:
663 title = short
664 elif long:
665 title = long
666 else:
667 title = "Untitled"
668 icon = short or long or title
669 if not self.get_saved():
670 title = "*%s*" % title
671 icon = "*%s" % icon
672 self.top.wm_title(title)
673 self.top.wm_iconname(icon)
674
675 def get_saved(self):
676 return self.undo.get_saved()
677
678 def set_saved(self, flag):
679 self.undo.set_saved(flag)
680
681 def reset_undo(self):
682 self.undo.reset_undo()
683
684 def short_title(self):
685 filename = self.io.filename
686 if filename:
687 filename = os.path.basename(filename)
688 return filename
689
690 def long_title(self):
691 return self.io.filename or ""
692
693 def center_insert_event(self, event):
694 self.center()
695
696 def center(self, mark="insert"):
697 text = self.text
698 top, bot = self.getwindowlines()
699 lineno = self.getlineno(mark)
700 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000701 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000702 text.yview(float(newtop))
703
704 def getwindowlines(self):
705 text = self.text
706 top = self.getlineno("@0,0")
707 bot = self.getlineno("@0,65535")
708 if top == bot and text.winfo_height() == 1:
709 # Geometry manager hasn't run yet
710 height = int(text['height'])
711 bot = top + height - 1
712 return top, bot
713
714 def getlineno(self, mark="insert"):
715 text = self.text
716 return int(float(text.index(mark)))
717
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000718 def get_geometry(self):
719 "Return (width, height, x, y)"
720 geom = self.top.wm_geometry()
721 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
722 tuple = (map(int, m.groups()))
723 return tuple
724
David Scherer7aced172000-08-15 01:13:23 +0000725 def close_event(self, event):
726 self.close()
727
728 def maybesave(self):
729 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000730 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000731 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000732 self.top.deiconify()
733 self.top.lower()
734 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000735 return self.io.maybesave()
736
737 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000738 reply = self.maybesave()
739 if reply != "cancel":
740 self._close()
741 return reply
742
743 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000744 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000745 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000746 WindowList.unregister_callback(self.postwindowsmenu)
747 if self.close_hook:
748 self.close_hook()
749 self.flist = None
750 colorizing = 0
751 self.unload_extensions()
752 self.io.close(); self.io = None
753 self.undo = None # XXX
754 if self.color:
755 colorizing = self.color.colorizing
756 doh = colorizing and self.top
757 self.color.close(doh) # Cancel colorization
758 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000759 self.tkinter_vars = None
David Scherer7aced172000-08-15 01:13:23 +0000760 self.per.close(); self.per = None
761 if not colorizing:
762 self.top.destroy()
763
764 def load_extensions(self):
765 self.extensions = {}
766 self.load_standard_extensions()
767
768 def unload_extensions(self):
769 for ins in self.extensions.values():
770 if hasattr(ins, "close"):
771 ins.close()
772 self.extensions = {}
773
774 def load_standard_extensions(self):
775 for name in self.get_standard_extension_names():
776 try:
777 self.load_extension(name)
778 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000779 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +0000780 import traceback
781 traceback.print_exc()
782
783 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000784 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000785
786 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000787 try:
788 mod = __import__(name, globals(), locals(), [])
789 except ImportError:
790 print "\nFailed to import extension: ", name
791 return
David Scherer7aced172000-08-15 01:13:23 +0000792 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000793 keydefs = idleConf.GetExtensionBindings(name)
794 if hasattr(cls, "menudefs"):
795 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000796 ins = cls(self)
797 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000798 if keydefs:
799 self.apply_bindings(keydefs)
800 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000801 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000802 while methodname[:1] == '<':
803 methodname = methodname[1:]
804 while methodname[-1:] == '>':
805 methodname = methodname[:-1]
806 methodname = methodname + "_event"
807 if hasattr(ins, methodname):
808 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000809
810 def apply_bindings(self, keydefs=None):
811 if keydefs is None:
812 keydefs = self.Bindings.default_keydefs
813 text = self.text
814 text.keydefs = keydefs
815 for event, keylist in keydefs.items():
816 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000817 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000818
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000819 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000820 """Add appropriate entries to the menus and submenus
821
822 Menus that are absent or None in self.menudict are ignored.
823 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000824 if menudefs is None:
825 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000826 if keydefs is None:
827 keydefs = self.Bindings.default_keydefs
828 menudict = self.menudict
829 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000830 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000831 menu = menudict.get(mname)
832 if not menu:
833 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000834 for entry in entrylist:
835 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000836 menu.add_separator()
837 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000838 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000839 checkbutton = (label[:1] == '!')
840 if checkbutton:
841 label = label[1:]
842 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000843 accelerator = get_accelerator(keydefs, eventname)
844 def command(text=text, eventname=eventname):
845 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000846 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000847 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000848 menu.add_checkbutton(label=label, underline=underline,
849 command=command, accelerator=accelerator,
850 variable=var)
851 else:
852 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000853 command=command,
854 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000855
856 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000857 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +0000858 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000859 value = var.get()
860 return value
861 else:
862 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +0000863
864 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000865 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +0000866 if var:
867 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000868 else:
869 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +0000870
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000871 def get_var_obj(self, name, vartype=None):
872 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +0000873 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000874 # create a Tkinter variable object with self.text as master:
875 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +0000876 return var
877
878 # Tk implementations of "virtual text methods" -- each platform
879 # reusing IDLE's support code needs to define these for its GUI's
880 # flavor of widget.
881
882 # Is character at text_index in a Python string? Return 0 for
883 # "guaranteed no", true for anything else. This info is expensive
884 # to compute ab initio, but is probably already known by the
885 # platform's colorizer.
886
887 def is_char_in_string(self, text_index):
888 if self.color:
889 # Return true iff colorizer hasn't (re)gotten this far
890 # yet, or the character is tagged as being in a string
891 return self.text.tag_prevrange("TODO", text_index) or \
892 "STRING" in self.text.tag_names(text_index)
893 else:
894 # The colorizer is missing: assume the worst
895 return 1
896
897 # If a selection is defined in the text widget, return (start,
898 # end) as Tkinter text indices, otherwise return (None, None)
899 def get_selection_indices(self):
900 try:
901 first = self.text.index("sel.first")
902 last = self.text.index("sel.last")
903 return first, last
904 except TclError:
905 return None, None
906
907 # Return the text widget's current view of what a tab stop means
908 # (equivalent width in spaces).
909
910 def get_tabwidth(self):
911 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
912 return int(current)
913
914 # Set the text widget's current view of what a tab stop means.
915
916 def set_tabwidth(self, newtabwidth):
917 text = self.text
918 if self.get_tabwidth() != newtabwidth:
919 pixels = text.tk.call("font", "measure", text["font"],
920 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000921 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000922 text.configure(tabs=pixels)
923
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000924 # If ispythonsource and guess are true, guess a good value for
925 # indentwidth based on file content (if possible), and if
926 # indentwidth != tabwidth set usetabs false.
927 # In any case, adjust the Text widget's view of what a tab
928 # character means.
929
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000930 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000931 if guess and ispythonsource:
932 i = self.guess_indent()
933 if 2 <= i <= 8:
934 self.indentwidth = i
935 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000936 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000937 self.set_tabwidth(self.tabwidth)
938
939 def smart_backspace_event(self, event):
940 text = self.text
941 first, last = self.get_selection_indices()
942 if first and last:
943 text.delete(first, last)
944 text.mark_set("insert", first)
945 return "break"
946 # Delete whitespace left, until hitting a real char or closest
947 # preceding virtual tab stop.
948 chars = text.get("insert linestart", "insert")
949 if chars == '':
950 if text.compare("insert", ">", "1.0"):
951 # easy: delete preceding newline
952 text.delete("insert-1c")
953 else:
954 text.bell() # at start of buffer
955 return "break"
956 if chars[-1] not in " \t":
957 # easy: delete preceding real char
958 text.delete("insert-1c")
959 return "break"
960 # Ick. It may require *inserting* spaces if we back up over a
961 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000962 tabwidth = self.tabwidth
963 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000964 assert have > 0
965 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000966 # Debug prompt is multilined....
967 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000968 ncharsdeleted = 0
969 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000970 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +0000971 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000972 chars = chars[:-1]
973 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000974 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000975 if have <= want or chars[-1] not in " \t":
976 break
977 text.undo_block_start()
978 text.delete("insert-%dc" % ncharsdeleted, "insert")
979 if have < want:
980 text.insert("insert", ' ' * (want - have))
981 text.undo_block_stop()
982 return "break"
983
984 def smart_indent_event(self, event):
985 # if intraline selection:
986 # delete it
987 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000988 # do indent-region
989 # else:
990 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000991 text = self.text
992 first, last = self.get_selection_indices()
993 text.undo_block_start()
994 try:
995 if first and last:
996 if index2line(first) != index2line(last):
997 return self.indent_region_event(event)
998 text.delete(first, last)
999 text.mark_set("insert", first)
1000 prefix = text.get("insert linestart", "insert")
1001 raw, effective = classifyws(prefix, self.tabwidth)
1002 if raw == len(prefix):
1003 # only whitespace to the left
1004 self.reindent_to(effective + self.indentwidth)
1005 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001006 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001007 if self.usetabs:
1008 pad = '\t'
1009 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001010 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001011 n = self.indentwidth
1012 pad = ' ' * (n - effective % n)
1013 text.insert("insert", pad)
1014 text.see("insert")
1015 return "break"
1016 finally:
1017 text.undo_block_stop()
1018
1019 def newline_and_indent_event(self, event):
1020 text = self.text
1021 first, last = self.get_selection_indices()
1022 text.undo_block_start()
1023 try:
1024 if first and last:
1025 text.delete(first, last)
1026 text.mark_set("insert", first)
1027 line = text.get("insert linestart", "insert")
1028 i, n = 0, len(line)
1029 while i < n and line[i] in " \t":
1030 i = i+1
1031 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001032 # the cursor is in or at leading indentation in a continuation
1033 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001034 text.insert("insert linestart", '\n')
1035 return "break"
1036 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001037 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001038 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001039 last_line_of_prompt = sys.ps1.split('\n')[-1]
1040 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001041 line = line[:-1]
1042 i = i+1
1043 if i:
1044 text.delete("insert - %d chars" % i, "insert")
1045 # strip whitespace after insert point
1046 while text.get("insert") in " \t":
1047 text.delete("insert")
1048 # start new line
1049 text.insert("insert", '\n')
1050
1051 # adjust indentation for continuations and block
1052 # open/close first need to find the last stmt
1053 lno = index2line(text.index('insert'))
1054 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1055 for context in self.num_context_lines:
1056 startat = max(lno - context, 1)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001057 startatindex = repr(startat) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001058 rawtext = text.get(startatindex, "insert")
1059 y.set_str(rawtext)
1060 bod = y.find_good_parse_start(
1061 self.context_use_ps1,
1062 self._build_char_in_string_func(startatindex))
1063 if bod is not None or startat == 1:
1064 break
1065 y.set_lo(bod or 0)
1066 c = y.get_continuation_type()
1067 if c != PyParse.C_NONE:
1068 # The current stmt hasn't ended yet.
1069 if c == PyParse.C_STRING:
1070 # inside a string; just mimic the current indent
1071 text.insert("insert", indent)
1072 elif c == PyParse.C_BRACKET:
1073 # line up with the first (if any) element of the
1074 # last open bracket structure; else indent one
1075 # level beyond the indent of the line with the
1076 # last open bracket
1077 self.reindent_to(y.compute_bracket_indent())
1078 elif c == PyParse.C_BACKSLASH:
1079 # if more than one line in this stmt already, just
1080 # mimic the current indent; else if initial line
1081 # has a start on an assignment stmt, indent to
1082 # beyond leftmost =; else to beyond first chunk of
1083 # non-whitespace on initial line
1084 if y.get_num_lines_in_stmt() > 1:
1085 text.insert("insert", indent)
1086 else:
1087 self.reindent_to(y.compute_backslash_indent())
1088 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001089 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001090 return "break"
1091
1092 # This line starts a brand new stmt; indent relative to
1093 # indentation of initial line of closest preceding
1094 # interesting stmt.
1095 indent = y.get_base_indent_string()
1096 text.insert("insert", indent)
1097 if y.is_block_opener():
1098 self.smart_indent_event(event)
1099 elif indent and y.is_block_closer():
1100 self.smart_backspace_event(event)
1101 return "break"
1102 finally:
1103 text.see("insert")
1104 text.undo_block_stop()
1105
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001106 # Our editwin provides a is_char_in_string function that works
1107 # with a Tk text index, but PyParse only knows about offsets into
1108 # a string. This builds a function for PyParse that accepts an
1109 # offset.
1110
1111 def _build_char_in_string_func(self, startindex):
1112 def inner(offset, _startindex=startindex,
1113 _icis=self.is_char_in_string):
1114 return _icis(_startindex + "+%dc" % offset)
1115 return inner
1116
1117 def indent_region_event(self, event):
1118 head, tail, chars, lines = self.get_region()
1119 for pos in range(len(lines)):
1120 line = lines[pos]
1121 if line:
1122 raw, effective = classifyws(line, self.tabwidth)
1123 effective = effective + self.indentwidth
1124 lines[pos] = self._make_blanks(effective) + line[raw:]
1125 self.set_region(head, tail, chars, lines)
1126 return "break"
1127
1128 def dedent_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 = max(effective - self.indentwidth, 0)
1135 lines[pos] = self._make_blanks(effective) + line[raw:]
1136 self.set_region(head, tail, chars, lines)
1137 return "break"
1138
1139 def comment_region_event(self, event):
1140 head, tail, chars, lines = self.get_region()
1141 for pos in range(len(lines) - 1):
1142 line = lines[pos]
1143 lines[pos] = '##' + line
1144 self.set_region(head, tail, chars, lines)
1145
1146 def uncomment_region_event(self, event):
1147 head, tail, chars, lines = self.get_region()
1148 for pos in range(len(lines)):
1149 line = lines[pos]
1150 if not line:
1151 continue
1152 if line[:2] == '##':
1153 line = line[2:]
1154 elif line[:1] == '#':
1155 line = line[1:]
1156 lines[pos] = line
1157 self.set_region(head, tail, chars, lines)
1158
1159 def tabify_region_event(self, event):
1160 head, tail, chars, lines = self.get_region()
1161 tabwidth = self._asktabwidth()
1162 for pos in range(len(lines)):
1163 line = lines[pos]
1164 if line:
1165 raw, effective = classifyws(line, tabwidth)
1166 ntabs, nspaces = divmod(effective, tabwidth)
1167 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1168 self.set_region(head, tail, chars, lines)
1169
1170 def untabify_region_event(self, event):
1171 head, tail, chars, lines = self.get_region()
1172 tabwidth = self._asktabwidth()
1173 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001174 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001175 self.set_region(head, tail, chars, lines)
1176
1177 def toggle_tabs_event(self, event):
1178 if self.askyesno(
1179 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001180 "Turn tabs " + ("on", "off")[self.usetabs] +
1181 "?\nIndent width " +
1182 ("will be", "remains at")[self.usetabs] + " 8.",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001183 parent=self.text):
1184 self.usetabs = not self.usetabs
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001185 # Try to prevent mixed tabs/spaces.
1186 # User must reset indent width manually after using tabs
1187 # if he insists on getting into trouble.
1188 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001189 return "break"
1190
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001191 # XXX this isn't bound to anything -- see tabwidth comments
1192## def change_tabwidth_event(self, event):
1193## new = self._asktabwidth()
1194## if new != self.tabwidth:
1195## self.tabwidth = new
1196## self.set_indentation_params(0, guess=0)
1197## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001198
1199 def change_indentwidth_event(self, event):
1200 new = self.askinteger(
1201 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001202 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001203 parent=self.text,
1204 initialvalue=self.indentwidth,
1205 minvalue=2,
1206 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001207 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001208 self.indentwidth = new
1209 return "break"
1210
1211 def get_region(self):
1212 text = self.text
1213 first, last = self.get_selection_indices()
1214 if first and last:
1215 head = text.index(first + " linestart")
1216 tail = text.index(last + "-1c lineend +1c")
1217 else:
1218 head = text.index("insert linestart")
1219 tail = text.index("insert lineend +1c")
1220 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001221 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001222 return head, tail, chars, lines
1223
1224 def set_region(self, head, tail, chars, lines):
1225 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001226 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001227 if newchars == chars:
1228 text.bell()
1229 return
1230 text.tag_remove("sel", "1.0", "end")
1231 text.mark_set("insert", head)
1232 text.undo_block_start()
1233 text.delete(head, tail)
1234 text.insert(head, newchars)
1235 text.undo_block_stop()
1236 text.tag_add("sel", head, "insert")
1237
1238 # Make string that displays as n leading blanks.
1239
1240 def _make_blanks(self, n):
1241 if self.usetabs:
1242 ntabs, nspaces = divmod(n, self.tabwidth)
1243 return '\t' * ntabs + ' ' * nspaces
1244 else:
1245 return ' ' * n
1246
1247 # Delete from beginning of line to insert point, then reinsert
1248 # column logical (meaning use tabs if appropriate) spaces.
1249
1250 def reindent_to(self, column):
1251 text = self.text
1252 text.undo_block_start()
1253 if text.compare("insert linestart", "!=", "insert"):
1254 text.delete("insert linestart", "insert")
1255 if column:
1256 text.insert("insert", self._make_blanks(column))
1257 text.undo_block_stop()
1258
1259 def _asktabwidth(self):
1260 return self.askinteger(
1261 "Tab width",
1262 "Spaces per tab? (2-16)",
1263 parent=self.text,
1264 initialvalue=self.indentwidth,
1265 minvalue=2,
1266 maxvalue=16) or self.tabwidth
1267
1268 # Guess indentwidth from text content.
1269 # Return guessed indentwidth. This should not be believed unless
1270 # it's in a reasonable range (e.g., it will be 0 if no indented
1271 # blocks are found).
1272
1273 def guess_indent(self):
1274 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1275 if opener and indented:
1276 raw, indentsmall = classifyws(opener, self.tabwidth)
1277 raw, indentlarge = classifyws(indented, self.tabwidth)
1278 else:
1279 indentsmall = indentlarge = 0
1280 return indentlarge - indentsmall
1281
1282# "line.col" -> line, as an int
1283def index2line(index):
1284 return int(float(index))
1285
1286# Look at the leading whitespace in s.
1287# Return pair (# of leading ws characters,
1288# effective # of leading blanks after expanding
1289# tabs to width tabwidth)
1290
1291def classifyws(s, tabwidth):
1292 raw = effective = 0
1293 for ch in s:
1294 if ch == ' ':
1295 raw = raw + 1
1296 effective = effective + 1
1297 elif ch == '\t':
1298 raw = raw + 1
1299 effective = (effective // tabwidth + 1) * tabwidth
1300 else:
1301 break
1302 return raw, effective
1303
1304import tokenize
1305_tokenize = tokenize
1306del tokenize
1307
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001308class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001309
1310 # .run() chews over the Text widget, looking for a block opener
1311 # and the stmt following it. Returns a pair,
1312 # (line containing block opener, line containing stmt)
1313 # Either or both may be None.
1314
1315 def __init__(self, text, tabwidth):
1316 self.text = text
1317 self.tabwidth = tabwidth
1318 self.i = self.finished = 0
1319 self.blkopenline = self.indentedline = None
1320
1321 def readline(self):
1322 if self.finished:
1323 return ""
1324 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001325 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001326 if self.text.compare(mark, ">=", "end"):
1327 return ""
1328 return self.text.get(mark, mark + " lineend+1c")
1329
1330 def tokeneater(self, type, token, start, end, line,
1331 INDENT=_tokenize.INDENT,
1332 NAME=_tokenize.NAME,
1333 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1334 if self.finished:
1335 pass
1336 elif type == NAME and token in OPENERS:
1337 self.blkopenline = line
1338 elif type == INDENT and self.blkopenline:
1339 self.indentedline = line
1340 self.finished = 1
1341
1342 def run(self):
1343 save_tabsize = _tokenize.tabsize
1344 _tokenize.tabsize = self.tabwidth
1345 try:
1346 try:
1347 _tokenize.tokenize(self.readline, self.tokeneater)
1348 except _tokenize.TokenError:
1349 # since we cut off the tokenizer early, we can trigger
1350 # spurious errors
1351 pass
1352 finally:
1353 _tokenize.tabsize = save_tabsize
1354 return self.blkopenline, self.indentedline
1355
1356### end autoindent code ###
1357
David Scherer7aced172000-08-15 01:13:23 +00001358def prepstr(s):
1359 # Helper to extract the underscore from a string, e.g.
1360 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001361 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001362 if i >= 0:
1363 s = s[:i] + s[i+1:]
1364 return i, s
1365
1366
1367keynames = {
1368 'bracketleft': '[',
1369 'bracketright': ']',
1370 'slash': '/',
1371}
1372
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001373def get_accelerator(keydefs, eventname):
1374 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001375 if not keylist:
1376 return ""
1377 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001378 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001379 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1380 s = re.sub("Key-", "", s)
1381 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1382 s = re.sub("Control-", "Ctrl-", s)
1383 s = re.sub("-", "+", s)
1384 s = re.sub("><", " ", s)
1385 s = re.sub("<", "", s)
1386 s = re.sub(">", "", s)
1387 return s
1388
1389
1390def fixwordbreaks(root):
1391 # Make sure that Tk's double-click and next/previous word
1392 # operations use our definition of a word (i.e. an identifier)
1393 tk = root.tk
1394 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1395 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1396 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1397
1398
1399def test():
1400 root = Tk()
1401 fixwordbreaks(root)
1402 root.withdraw()
1403 if sys.argv[1:]:
1404 filename = sys.argv[1]
1405 else:
1406 filename = None
1407 edit = EditorWindow(root=root, filename=filename)
1408 edit.set_close_hook(root.quit)
1409 root.mainloop()
1410 root.destroy()
1411
1412if __name__ == '__main__':
1413 test()