blob: c63823aa708dc25e9f64925e5fe0efb258786b60 [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
5from Tkinter import *
6import tkSimpleDialog
7import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00008
9import webbrowser
David Scherer7aced172000-08-15 01:13:23 +000010import idlever
11import WindowList
Steven M. Gavac5976402002-01-04 03:06:08 +000012import SearchDialog
13import GrepDialog
14import ReplaceDialog
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +000015import PyParse
Steven M. Gavadc72f482002-01-03 11:51:07 +000016from configHandler import idleConf
Steven M. Gava3b55a892001-11-21 05:56:26 +000017import aboutDialog, textView, configDialog
David Scherer7aced172000-08-15 01:13:23 +000018
19# The default tab setting for a Text widget, in average-width characters.
20TK_TABWIDTH_DEFAULT = 8
21
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000022def _find_module(fullname, path=None):
23 """Version of imp.find_module() that handles hierarchical module names"""
24
25 file = None
26 for tgt in fullname.split('.'):
27 if file is not None:
28 file.close() # close intermediate files
29 (file, filename, descr) = imp.find_module(tgt, path)
30 if descr[2] == imp.PY_SOURCE:
31 break # find but not load the source file
32 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000033 try:
34 path = module.__path__
35 except AttributeError:
36 raise ImportError, 'No source for module ' + module.__name__
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000037 return file, filename, descr
38
David Scherer7aced172000-08-15 01:13:23 +000039class EditorWindow:
David Scherer7aced172000-08-15 01:13:23 +000040 from Percolator import Percolator
41 from ColorDelegator import ColorDelegator
42 from UndoDelegator import UndoDelegator
43 from IOBinding import IOBinding
44 import Bindings
45 from Tkinter import Toplevel
46 from MultiStatusBar import MultiStatusBar
47
David Scherer7aced172000-08-15 01:13:23 +000048 vars = {}
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:
53 if sys.platform.count('linux'):
54 # look for html docs in a couple of standard places
55 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
56 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
57 dochome = '/var/www/html/python/index.html'
58 else:
59 basepath = '/usr/share/doc/' # standard location
60 dochome = os.path.join(basepath, pyver,
61 'Doc', 'index.html')
62 else:
63 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
64 dochome = os.path.normpath(dochome)
65 if os.path.isfile(dochome):
66 EditorWindow.help_url = dochome
67 else:
68 EditorWindow.help_url = "http://www.python.org/doc/current"
Steven M. Gavadc72f482002-01-03 11:51:07 +000069 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000070 self.flist = flist
71 root = root or flist.root
72 self.root = root
David Scherer7aced172000-08-15 01:13:23 +000073 self.menubar = Menu(root)
74 self.top = top = self.Toplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000075 if flist:
76 self.vars = flist.vars
77 #self.top.instanceDict makes flist.inversedict avalable to
78 #configDialog.py so it can access all EditorWindow instaces
79 self.top.instanceDict=flist.inversedict
Steven M. Gava1d46e402002-03-27 08:40:46 +000080 self.recentFilesPath=os.path.join(idleConf.GetUserCfgDir(),
81 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +000082 self.vbar = vbar = Scrollbar(top, name='vbar')
83 self.text_frame = text_frame = Frame(top)
Kurt B. Kaiser1061e722003-01-04 01:43:53 +000084 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaisera1dee062002-10-04 21:33:57 +000085 self.text = text = Text(text_frame, name='text', padx=5, wrap='none',
Steven M. Gavadc72f482002-01-03 11:51:07 +000086 foreground=idleConf.GetHighlight(currentTheme,
87 'normal',fgBg='fg'),
88 background=idleConf.GetHighlight(currentTheme,
89 'normal',fgBg='bg'),
90 highlightcolor=idleConf.GetHighlight(currentTheme,
91 'hilite',fgBg='fg'),
92 highlightbackground=idleConf.GetHighlight(currentTheme,
93 'hilite',fgBg='bg'),
94 insertbackground=idleConf.GetHighlight(currentTheme,
95 'cursor',fgBg='fg'),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +000096 width=self.width,
Steven M. Gavadc72f482002-01-03 11:51:07 +000097 height=idleConf.GetOption('main','EditorWindow','height') )
David Scherer7aced172000-08-15 01:13:23 +000098
99 self.createmenubar()
100 self.apply_bindings()
101
102 self.top.protocol("WM_DELETE_WINDOW", self.close)
103 self.top.bind("<<close-window>>", self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000104 text.bind("<<cut>>", self.cut)
105 text.bind("<<copy>>", self.copy)
106 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000107 text.bind("<<center-insert>>", self.center_insert_event)
108 text.bind("<<help>>", self.help_dialog)
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000109 text.bind("<<view-readme>>", self.view_readme)
David Scherer7aced172000-08-15 01:13:23 +0000110 text.bind("<<python-docs>>", self.python_docs)
111 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000112 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000113 text.bind("<<open-module>>", self.open_module)
114 text.bind("<<do-nothing>>", lambda event: "break")
115 text.bind("<<select-all>>", self.select_all)
116 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000117 text.bind("<<find>>", self.find_event)
118 text.bind("<<find-again>>", self.find_again_event)
119 text.bind("<<find-in-files>>", self.find_in_files_event)
120 text.bind("<<find-selection>>", self.find_selection_event)
121 text.bind("<<replace>>", self.replace_event)
122 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000123 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000124 text.bind("<<smart-backspace>>",self.smart_backspace_event)
125 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
126 text.bind("<<smart-indent>>",self.smart_indent_event)
127 text.bind("<<indent-region>>",self.indent_region_event)
128 text.bind("<<dedent-region>>",self.dedent_region_event)
129 text.bind("<<comment-region>>",self.comment_region_event)
130 text.bind("<<uncomment-region>>",self.uncomment_region_event)
131 text.bind("<<tabify-region>>",self.tabify_region_event)
132 text.bind("<<untabify-region>>",self.untabify_region_event)
133 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
134 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000135 text.bind("<Left>", self.move_at_edge_if_selection(0))
136 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000137
David Scherer7aced172000-08-15 01:13:23 +0000138 if flist:
139 flist.inversedict[self] = key
140 if key:
141 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000142 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000143 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
144 text.bind("<<open-class-browser>>", self.open_class_browser)
145 text.bind("<<open-path-browser>>", self.open_path_browser)
146
Steven M. Gava898a3652001-10-07 11:10:44 +0000147 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000148 vbar['command'] = text.yview
149 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000150 text['yscrollcommand'] = vbar.set
Steven M. Gavab1585412002-03-12 00:21:56 +0000151 fontWeight='normal'
152 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
153 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000154 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000155 idleConf.GetOption('main','EditorWindow','font-size'),
156 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000157 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
158 text.pack(side=TOP, fill=BOTH, expand=1)
159 text.focus_set()
160
161 self.per = per = self.Percolator(text)
162 if self.ispythonsource(filename):
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000163 self.color = color = self.ColorDelegator()
164 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000165 else:
David Scherer7aced172000-08-15 01:13:23 +0000166 self.color = None
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000167
168 self.undo = undo = self.UndoDelegator()
169 per.insertfilter(undo)
170 text.undo_block_start = undo.undo_block_start
171 text.undo_block_stop = undo.undo_block_stop
172 undo.set_saved_change_hook(self.saved_change_hook)
173
174 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000175 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000176 io.set_filename_change_hook(self.filename_change_hook)
177
Steven M. Gava1d46e402002-03-27 08:40:46 +0000178 #create the Recent Files submenu
179 self.menuRecentFiles=Menu(self.menubar)
180 self.menudict['file'].insert_cascade(3,label='Recent Files',
181 underline=0,menu=self.menuRecentFiles)
182 self.UpdateRecentFilesList()
David Scherer7aced172000-08-15 01:13:23 +0000183
David Scherer7aced172000-08-15 01:13:23 +0000184 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000185 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000186 io.loadfile(filename)
187 else:
188 io.set_filename(filename)
David Scherer7aced172000-08-15 01:13:23 +0000189 self.saved_change_hook()
190
191 self.load_extensions()
192
193 menu = self.menudict.get('windows')
194 if menu:
195 end = menu.index("end")
196 if end is None:
197 end = -1
198 if end >= 0:
199 menu.add_separator()
200 end = end + 1
201 self.wmenu_end = end
202 WindowList.register_callback(self.postwindowsmenu)
203
204 # Some abstractions so IDLE extensions are cross-IDE
205 self.askyesno = tkMessageBox.askyesno
206 self.askinteger = tkSimpleDialog.askinteger
207 self.showerror = tkMessageBox.showerror
208
209 if self.extensions.has_key('AutoIndent'):
210 self.extensions['AutoIndent'].set_indentation_params(
211 self.ispythonsource(filename))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000212
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000213 def new_callback(self, event):
214 dirname, basename = self.io.defaultfilename()
215 self.flist.new(dirname)
216 return "break"
217
David Scherer7aced172000-08-15 01:13:23 +0000218 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000219 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000220 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
221 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
222 self.status_bar.pack(side=BOTTOM, fill=X)
223 self.text.bind('<KeyRelease>', self.set_line_and_column)
224 self.text.bind('<ButtonRelease>', self.set_line_and_column)
225 self.text.after_idle(self.set_line_and_column)
226
227 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000228 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000229 self.status_bar.set_label('column', 'Col: %s' % column)
230 self.status_bar.set_label('line', 'Ln: %s' % line)
231
232 def wakeup(self):
233 if self.top.wm_state() == "iconic":
234 self.top.wm_deiconify()
235 else:
236 self.top.tkraise()
237 self.text.focus_set()
238
239 menu_specs = [
240 ("file", "_File"),
241 ("edit", "_Edit"),
242 ("format", "F_ormat"),
243 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000244 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000245 ("windows", "_Windows"),
246 ("help", "_Help"),
247 ]
248
249 def createmenubar(self):
250 mbar = self.menubar
251 self.menudict = menudict = {}
252 for name, label in self.menu_specs:
253 underline, label = prepstr(label)
254 menudict[name] = menu = Menu(mbar, name=name)
255 mbar.add_cascade(label=label, menu=menu, underline=underline)
256 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000257 self.base_helpmenu_length = self.menudict['help'].index(END)
258 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000259
260 def postwindowsmenu(self):
261 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000262 menu = self.menudict['windows']
263 end = menu.index("end")
264 if end is None:
265 end = -1
266 if end > self.wmenu_end:
267 menu.delete(self.wmenu_end+1, end)
268 WindowList.add_windows_to_menu(menu)
269
270 rmenu = None
271
272 def right_menu_event(self, event):
273 self.text.tag_remove("sel", "1.0", "end")
274 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
275 if not self.rmenu:
276 self.make_rmenu()
277 rmenu = self.rmenu
278 self.event = event
279 iswin = sys.platform[:3] == 'win'
280 if iswin:
281 self.text.config(cursor="arrow")
282 rmenu.tk_popup(event.x_root, event.y_root)
283 if iswin:
284 self.text.config(cursor="ibeam")
285
286 rmenu_specs = [
287 # ("Label", "<<virtual-event>>"), ...
288 ("Close", "<<close-window>>"), # Example
289 ]
290
291 def make_rmenu(self):
292 rmenu = Menu(self.text, tearoff=0)
293 for label, eventname in self.rmenu_specs:
294 def command(text=self.text, eventname=eventname):
295 text.event_generate(eventname)
296 rmenu.add_command(label=label, command=command)
297 self.rmenu = rmenu
298
299 def about_dialog(self, event=None):
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000300 aboutDialog.AboutDialog(self.top,'About IDLEfork')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000301
Steven M. Gava3b55a892001-11-21 05:56:26 +0000302 def config_dialog(self, event=None):
303 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000304
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000305 def view_readme(self, event=None):
306 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000307 textView.TextViewer(self.top,'IDLEfork - README',fn)
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000308
David Scherer7aced172000-08-15 01:13:23 +0000309 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000310 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000311 textView.TextViewer(self.top,'Help',fn)
312
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000313 def python_docs(self, event=None):
314 if sys.platform.count('win') or sys.platform.count('nt'):
Steven M. Gava931625d2002-04-22 00:38:26 +0000315 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000316 return "break"
317 else:
318 webbrowser.open(self.help_url)
319 return "break"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000320
321 def display_docs(self, url):
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000322 if not (url.startswith('www') or url.startswith('http')):
323 url = os.path.normpath(url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000324 if sys.platform.count('win') or sys.platform.count('nt'):
325 os.startfile(url)
326 else:
327 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000328
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000329 def cut(self,event):
330 self.text.event_generate("<<Cut>>")
331 return "break"
332
333 def copy(self,event):
334 self.text.event_generate("<<Copy>>")
335 return "break"
336
337 def paste(self,event):
338 self.text.event_generate("<<Paste>>")
339 return "break"
340
David Scherer7aced172000-08-15 01:13:23 +0000341 def select_all(self, event=None):
342 self.text.tag_add("sel", "1.0", "end-1c")
343 self.text.mark_set("insert", "1.0")
344 self.text.see("insert")
345 return "break"
346
347 def remove_selection(self, event=None):
348 self.text.tag_remove("sel", "1.0", "end")
349 self.text.see("insert")
350
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000351 def move_at_edge_if_selection(self, edge_index):
352 """Cursor move begins at start or end of selection
353
354 When a left/right cursor key is pressed create and return to Tkinter a
355 function which causes a cursor move from the associated edge of the
356 selection.
357
358 """
359 self_text_index = self.text.index
360 self_text_mark_set = self.text.mark_set
361 edges_table = ("sel.first+1c", "sel.last-1c")
362 def move_at_edge(event):
363 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
364 try:
365 self_text_index("sel.first")
366 self_text_mark_set("insert", edges_table[edge_index])
367 except TclError:
368 pass
369 return move_at_edge
370
Steven M. Gavac5976402002-01-04 03:06:08 +0000371 def find_event(self, event):
372 SearchDialog.find(self.text)
373 return "break"
374
375 def find_again_event(self, event):
376 SearchDialog.find_again(self.text)
377 return "break"
378
379 def find_selection_event(self, event):
380 SearchDialog.find_selection(self.text)
381 return "break"
382
383 def find_in_files_event(self, event):
384 GrepDialog.grep(self.text, self.io, self.flist)
385 return "break"
386
387 def replace_event(self, event):
388 ReplaceDialog.replace(self.text)
389 return "break"
390
391 def goto_line_event(self, event):
392 text = self.text
393 lineno = tkSimpleDialog.askinteger("Goto",
394 "Go to line number:",parent=text)
395 if lineno is None:
396 return "break"
397 if lineno <= 0:
398 text.bell()
399 return "break"
400 text.mark_set("insert", "%d.0" % lineno)
401 text.see("insert")
402
David Scherer7aced172000-08-15 01:13:23 +0000403 def open_module(self, event=None):
404 # XXX Shouldn't this be in IOBinding or in FileList?
405 try:
406 name = self.text.get("sel.first", "sel.last")
407 except TclError:
408 name = ""
409 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000410 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000411 name = tkSimpleDialog.askstring("Module",
412 "Enter the name of a Python module\n"
413 "to search on sys.path and open:",
414 parent=self.text, initialvalue=name)
415 if name:
416 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000417 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000418 return
David Scherer7aced172000-08-15 01:13:23 +0000419 # XXX Ought to insert current file's directory in front of path
420 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000421 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000422 except (NameError, ImportError), msg:
423 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
424 return
425 if type != imp.PY_SOURCE:
426 tkMessageBox.showerror("Unsupported type",
427 "%s is not a source module" % name, parent=self.text)
428 return
429 if f:
430 f.close()
431 if self.flist:
432 self.flist.open(file)
433 else:
434 self.io.loadfile(file)
435
436 def open_class_browser(self, event=None):
437 filename = self.io.filename
438 if not filename:
439 tkMessageBox.showerror(
440 "No filename",
441 "This buffer has no associated filename",
442 master=self.text)
443 self.text.focus_set()
444 return None
445 head, tail = os.path.split(filename)
446 base, ext = os.path.splitext(tail)
447 import ClassBrowser
448 ClassBrowser.ClassBrowser(self.flist, base, [head])
449
450 def open_path_browser(self, event=None):
451 import PathBrowser
452 PathBrowser.PathBrowser(self.flist)
453
454 def gotoline(self, lineno):
455 if lineno is not None and lineno > 0:
456 self.text.mark_set("insert", "%d.0" % lineno)
457 self.text.tag_remove("sel", "1.0", "end")
458 self.text.tag_add("sel", "insert", "insert +1l")
459 self.center()
460
461 def ispythonsource(self, filename):
462 if not filename:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000463 return True
David Scherer7aced172000-08-15 01:13:23 +0000464 base, ext = os.path.splitext(os.path.basename(filename))
465 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000466 return True
David Scherer7aced172000-08-15 01:13:23 +0000467 try:
468 f = open(filename)
469 line = f.readline()
470 f.close()
471 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000472 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000473 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000474
475 def close_hook(self):
476 if self.flist:
477 self.flist.close_edit(self)
478
479 def set_close_hook(self, close_hook):
480 self.close_hook = close_hook
481
482 def filename_change_hook(self):
483 if self.flist:
484 self.flist.filename_changed_edit(self)
485 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000486 self.top.update_windowlist_registry(self)
David Scherer7aced172000-08-15 01:13:23 +0000487 if self.ispythonsource(self.io.filename):
488 self.addcolorizer()
489 else:
490 self.rmcolorizer()
491
492 def addcolorizer(self):
493 if self.color:
494 return
David Scherer7aced172000-08-15 01:13:23 +0000495 self.per.removefilter(self.undo)
496 self.color = self.ColorDelegator()
497 self.per.insertfilter(self.color)
498 self.per.insertfilter(self.undo)
499
500 def rmcolorizer(self):
501 if not self.color:
502 return
David Scherer7aced172000-08-15 01:13:23 +0000503 self.per.removefilter(self.undo)
504 self.per.removefilter(self.color)
505 self.color = None
506 self.per.insertfilter(self.undo)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000507
Steven M. Gavab77d3432002-03-02 07:16:21 +0000508 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000509 "Update the colour theme if it is changed"
510 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000511 if self.color:
512 self.color = self.ColorDelegator()
513 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000514
Steven M. Gavab1585412002-03-12 00:21:56 +0000515 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000516 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000517 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000518 fontWeight='normal'
519 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
520 fontWeight='bold'
521 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
522 idleConf.GetOption('main','EditorWindow','font-size'),
523 fontWeight))
524
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000525 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000526 "Update the keybindings if they are changed"
527 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000528 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
529 keydefs = self.Bindings.default_keydefs
530 for event, keylist in keydefs.items():
531 self.text.event_delete(event)
532 self.apply_bindings()
533 #update menu accelerators
534 menuEventDict={}
535 for menu in self.Bindings.menudefs:
536 menuEventDict[menu[0]]={}
537 for item in menu[1]:
538 if item:
539 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
540 for menubarItem in self.menudict.keys():
541 menu=self.menudict[menubarItem]
542 end=menu.index(END)+1
543 for index in range(0,end):
544 if menu.type(index)=='command':
545 accel=menu.entrycget(index,'accelerator')
546 if accel:
547 itemName=menu.entrycget(index,'label')
548 event=''
549 if menuEventDict.has_key(menubarItem):
550 if menuEventDict[menubarItem].has_key(itemName):
551 event=menuEventDict[menubarItem][itemName]
552 if event:
553 #print 'accel was:',accel
554 accel=get_accelerator(keydefs, event)
555 menu.entryconfig(index,accelerator=accel)
556 #print 'accel now:',accel,'\n'
557
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000558 def reset_help_menu_entries(self):
559 "Update the additional help entries on the Help menu"
560 help_list = idleConf.GetAllExtraHelpSourcesList()
561 helpmenu = self.menudict['help']
562 # first delete the extra help entries, if any
563 helpmenu_length = helpmenu.index(END)
564 if helpmenu_length > self.base_helpmenu_length:
565 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
566 # then rebuild them
567 if help_list:
568 helpmenu.add_separator()
569 for entry in help_list:
570 cmd = self.__extra_help_callback(entry[1])
571 helpmenu.add_command(label=entry[0], command=cmd)
572 # and update the menu dictionary
573 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000574
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000575 def __extra_help_callback(self, helpfile):
576 "Create a callback with the helpfile value frozen at definition time"
577 def display_extra_help(helpfile=helpfile):
578 self.display_docs(helpfile)
579 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000580
Steven M. Gava1d46e402002-03-27 08:40:46 +0000581 def UpdateRecentFilesList(self,newFile=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000582 "Load or update the recent files list, and menu if required"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000583 rfList=[]
584 if os.path.exists(self.recentFilesPath):
585 RFfile=open(self.recentFilesPath,'r')
586 try:
587 rfList=RFfile.readlines()
588 finally:
589 RFfile.close()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000590 if newFile:
Steven M. Gava1d46e402002-03-27 08:40:46 +0000591 newFile=os.path.abspath(newFile)+'\n'
592 if newFile in rfList:
593 rfList.remove(newFile)
594 rfList.insert(0,newFile)
595 rfList=self.__CleanRecentFiles(rfList)
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000596 #print self.flist.inversedict
597 #print self.top.instanceDict
598 #print self
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000599 ullist = "1234567890ABCDEFGHIJ"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000600 if rfList:
601 for instance in self.top.instanceDict.keys():
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000602 menu = instance.menuRecentFiles
603 menu.delete(1,END)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000604 i = 0 ; ul = 0; ullen = len(ullist)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000605 for file in rfList:
606 fileName=file[0:-1]
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000607 callback = instance.__RecentFileCallback(fileName)
608 if i > ullen: # don't underline menuitems
609 ul=None
610 menu.add_command(label=ullist[i] + " " + fileName,
611 command=callback,
612 underline=ul)
613 i += 1
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000614
Steven M. Gava1d46e402002-03-27 08:40:46 +0000615 def __CleanRecentFiles(self,rfList):
616 origRfList=rfList[:]
617 count=0
618 nonFiles=[]
619 for path in rfList:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000620 if not os.path.exists(path[0:-1]):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000621 nonFiles.append(count)
622 count=count+1
623 if nonFiles:
624 nonFiles.reverse()
625 for index in nonFiles:
626 del(rfList[index])
627 if len(rfList)>19:
628 rfList=rfList[0:19]
629 #if rfList != origRfList:
630 RFfile=open(self.recentFilesPath,'w')
631 try:
632 RFfile.writelines(rfList)
633 finally:
634 RFfile.close()
635 return rfList
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000636
Steven M. Gava1d46e402002-03-27 08:40:46 +0000637 def __RecentFileCallback(self,fileName):
638 def OpenRecentFile(fileName=fileName):
639 self.io.open(editFile=fileName)
640 return OpenRecentFile
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000641
David Scherer7aced172000-08-15 01:13:23 +0000642 def saved_change_hook(self):
643 short = self.short_title()
644 long = self.long_title()
645 if short and long:
646 title = short + " - " + long
647 elif short:
648 title = short
649 elif long:
650 title = long
651 else:
652 title = "Untitled"
653 icon = short or long or title
654 if not self.get_saved():
655 title = "*%s*" % title
656 icon = "*%s" % icon
657 self.top.wm_title(title)
658 self.top.wm_iconname(icon)
659
660 def get_saved(self):
661 return self.undo.get_saved()
662
663 def set_saved(self, flag):
664 self.undo.set_saved(flag)
665
666 def reset_undo(self):
667 self.undo.reset_undo()
668
669 def short_title(self):
670 filename = self.io.filename
671 if filename:
672 filename = os.path.basename(filename)
673 return filename
674
675 def long_title(self):
676 return self.io.filename or ""
677
678 def center_insert_event(self, event):
679 self.center()
680
681 def center(self, mark="insert"):
682 text = self.text
683 top, bot = self.getwindowlines()
684 lineno = self.getlineno(mark)
685 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000686 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000687 text.yview(float(newtop))
688
689 def getwindowlines(self):
690 text = self.text
691 top = self.getlineno("@0,0")
692 bot = self.getlineno("@0,65535")
693 if top == bot and text.winfo_height() == 1:
694 # Geometry manager hasn't run yet
695 height = int(text['height'])
696 bot = top + height - 1
697 return top, bot
698
699 def getlineno(self, mark="insert"):
700 text = self.text
701 return int(float(text.index(mark)))
702
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000703 def get_geometry(self):
704 "Return (width, height, x, y)"
705 geom = self.top.wm_geometry()
706 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
707 tuple = (map(int, m.groups()))
708 return tuple
709
David Scherer7aced172000-08-15 01:13:23 +0000710 def close_event(self, event):
711 self.close()
712
713 def maybesave(self):
714 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000715 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000716 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000717 self.top.deiconify()
718 self.top.lower()
719 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000720 return self.io.maybesave()
721
722 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000723 reply = self.maybesave()
724 if reply != "cancel":
725 self._close()
726 return reply
727
728 def _close(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000729 #print self.io.filename
Steven M. Gava1d46e402002-03-27 08:40:46 +0000730 if self.io.filename:
731 self.UpdateRecentFilesList(newFile=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000732 WindowList.unregister_callback(self.postwindowsmenu)
733 if self.close_hook:
734 self.close_hook()
735 self.flist = None
736 colorizing = 0
737 self.unload_extensions()
738 self.io.close(); self.io = None
739 self.undo = None # XXX
740 if self.color:
741 colorizing = self.color.colorizing
742 doh = colorizing and self.top
743 self.color.close(doh) # Cancel colorization
744 self.text = None
745 self.vars = None
746 self.per.close(); self.per = None
747 if not colorizing:
748 self.top.destroy()
749
750 def load_extensions(self):
751 self.extensions = {}
752 self.load_standard_extensions()
753
754 def unload_extensions(self):
755 for ins in self.extensions.values():
756 if hasattr(ins, "close"):
757 ins.close()
758 self.extensions = {}
759
760 def load_standard_extensions(self):
761 for name in self.get_standard_extension_names():
762 try:
763 self.load_extension(name)
764 except:
765 print "Failed to load extension", `name`
766 import traceback
767 traceback.print_exc()
768
769 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000770 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000771
772 def load_extension(self, name):
773 mod = __import__(name, globals(), locals(), [])
774 cls = getattr(mod, name)
775 ins = cls(self)
776 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000777 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000778 if keydefs:
779 self.apply_bindings(keydefs)
780 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000781 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000782 while methodname[:1] == '<':
783 methodname = methodname[1:]
784 while methodname[-1:] == '>':
785 methodname = methodname[:-1]
786 methodname = methodname + "_event"
787 if hasattr(ins, methodname):
788 self.text.bind(vevent, getattr(ins, methodname))
789 if hasattr(ins, "menudefs"):
790 self.fill_menus(ins.menudefs, keydefs)
791 return ins
792
793 def apply_bindings(self, keydefs=None):
794 if keydefs is None:
795 keydefs = self.Bindings.default_keydefs
796 text = self.text
797 text.keydefs = keydefs
798 for event, keylist in keydefs.items():
799 if keylist:
800 apply(text.event_add, (event,) + tuple(keylist))
801
802 def fill_menus(self, defs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000803 """Add appropriate entries to the menus and submenus
804
805 Menus that are absent or None in self.menudict are ignored.
806 """
David Scherer7aced172000-08-15 01:13:23 +0000807 if defs is None:
808 defs = self.Bindings.menudefs
809 if keydefs is None:
810 keydefs = self.Bindings.default_keydefs
811 menudict = self.menudict
812 text = self.text
813 for mname, itemlist in defs:
814 menu = menudict.get(mname)
815 if not menu:
816 continue
817 for item in itemlist:
818 if not item:
819 menu.add_separator()
820 else:
821 label, event = item
822 checkbutton = (label[:1] == '!')
823 if checkbutton:
824 label = label[1:]
825 underline, label = prepstr(label)
826 accelerator = get_accelerator(keydefs, event)
827 def command(text=text, event=event):
828 text.event_generate(event)
829 if checkbutton:
830 var = self.getrawvar(event, BooleanVar)
831 menu.add_checkbutton(label=label, underline=underline,
832 command=command, accelerator=accelerator,
833 variable=var)
834 else:
835 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000836 command=command,
837 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000838
839 def getvar(self, name):
840 var = self.getrawvar(name)
841 if var:
842 return var.get()
843
844 def setvar(self, name, value, vartype=None):
845 var = self.getrawvar(name, vartype)
846 if var:
847 var.set(value)
848
849 def getrawvar(self, name, vartype=None):
850 var = self.vars.get(name)
851 if not var and vartype:
852 self.vars[name] = var = vartype(self.text)
853 return var
854
855 # Tk implementations of "virtual text methods" -- each platform
856 # reusing IDLE's support code needs to define these for its GUI's
857 # flavor of widget.
858
859 # Is character at text_index in a Python string? Return 0 for
860 # "guaranteed no", true for anything else. This info is expensive
861 # to compute ab initio, but is probably already known by the
862 # platform's colorizer.
863
864 def is_char_in_string(self, text_index):
865 if self.color:
866 # Return true iff colorizer hasn't (re)gotten this far
867 # yet, or the character is tagged as being in a string
868 return self.text.tag_prevrange("TODO", text_index) or \
869 "STRING" in self.text.tag_names(text_index)
870 else:
871 # The colorizer is missing: assume the worst
872 return 1
873
874 # If a selection is defined in the text widget, return (start,
875 # end) as Tkinter text indices, otherwise return (None, None)
876 def get_selection_indices(self):
877 try:
878 first = self.text.index("sel.first")
879 last = self.text.index("sel.last")
880 return first, last
881 except TclError:
882 return None, None
883
884 # Return the text widget's current view of what a tab stop means
885 # (equivalent width in spaces).
886
887 def get_tabwidth(self):
888 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
889 return int(current)
890
891 # Set the text widget's current view of what a tab stop means.
892
893 def set_tabwidth(self, newtabwidth):
894 text = self.text
895 if self.get_tabwidth() != newtabwidth:
896 pixels = text.tk.call("font", "measure", text["font"],
897 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000898 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000899 text.configure(tabs=pixels)
900
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000901### begin autoindent code ###
902
903 # usetabs true -> literal tab characters are used by indent and
904 # dedent cmds, possibly mixed with spaces if
905 # indentwidth is not a multiple of tabwidth
906 # false -> tab characters are converted to spaces by indent
907 # and dedent cmds, and ditto TAB keystrokes
908 # indentwidth is the number of characters per logical indent level.
909 # tabwidth is the display width of a literal tab character.
910 # CAUTION: telling Tk to use anything other than its default
911 # tab setting causes it to use an entirely different tabbing algorithm,
912 # treating tab stops as fixed distances from the left margin.
913 # Nobody expects this, so for now tabwidth should never be changed.
914 usetabs = 0
915 indentwidth = 4
916 tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
917
918 # If context_use_ps1 is true, parsing searches back for a ps1 line;
919 # else searches for a popular (if, def, ...) Python stmt.
920 context_use_ps1 = 0
921
922 # When searching backwards for a reliable place to begin parsing,
923 # first start num_context_lines[0] lines back, then
924 # num_context_lines[1] lines back if that didn't work, and so on.
925 # The last value should be huge (larger than the # of lines in a
926 # conceivable file).
927 # Making the initial values larger slows things down more often.
928 num_context_lines = 50, 500, 5000000
929
930 def config(self, **options):
931 for key, value in options.items():
932 if key == 'usetabs':
933 self.usetabs = value
934 elif key == 'indentwidth':
935 self.indentwidth = value
936 elif key == 'tabwidth':
937 self.tabwidth = value
938 elif key == 'context_use_ps1':
939 self.context_use_ps1 = value
940 else:
941 raise KeyError, "bad option name: %s" % `key`
942
943 # If ispythonsource and guess are true, guess a good value for
944 # indentwidth based on file content (if possible), and if
945 # indentwidth != tabwidth set usetabs false.
946 # In any case, adjust the Text widget's view of what a tab
947 # character means.
948
949 def set_indentation_params(self, ispythonsource, guess=1):
950 if guess and ispythonsource:
951 i = self.guess_indent()
952 if 2 <= i <= 8:
953 self.indentwidth = i
954 if self.indentwidth != self.tabwidth:
955 self.usetabs = 0
956
957 self.set_tabwidth(self.tabwidth)
958
959 def smart_backspace_event(self, event):
960 text = self.text
961 first, last = self.get_selection_indices()
962 if first and last:
963 text.delete(first, last)
964 text.mark_set("insert", first)
965 return "break"
966 # Delete whitespace left, until hitting a real char or closest
967 # preceding virtual tab stop.
968 chars = text.get("insert linestart", "insert")
969 if chars == '':
970 if text.compare("insert", ">", "1.0"):
971 # easy: delete preceding newline
972 text.delete("insert-1c")
973 else:
974 text.bell() # at start of buffer
975 return "break"
976 if chars[-1] not in " \t":
977 # easy: delete preceding real char
978 text.delete("insert-1c")
979 return "break"
980 # Ick. It may require *inserting* spaces if we back up over a
981 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000982 tabwidth = self.tabwidth
983 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000984 assert have > 0
985 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000986 # Debug prompt is multilined....
987 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000988 ncharsdeleted = 0
989 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000990 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +0000991 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000992 chars = chars[:-1]
993 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000994 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000995 if have <= want or chars[-1] not in " \t":
996 break
997 text.undo_block_start()
998 text.delete("insert-%dc" % ncharsdeleted, "insert")
999 if have < want:
1000 text.insert("insert", ' ' * (want - have))
1001 text.undo_block_stop()
1002 return "break"
1003
1004 def smart_indent_event(self, event):
1005 # if intraline selection:
1006 # delete it
1007 # elif multiline selection:
1008 # do indent-region & return
1009 # indent one level
1010 text = self.text
1011 first, last = self.get_selection_indices()
1012 text.undo_block_start()
1013 try:
1014 if first and last:
1015 if index2line(first) != index2line(last):
1016 return self.indent_region_event(event)
1017 text.delete(first, last)
1018 text.mark_set("insert", first)
1019 prefix = text.get("insert linestart", "insert")
1020 raw, effective = classifyws(prefix, self.tabwidth)
1021 if raw == len(prefix):
1022 # only whitespace to the left
1023 self.reindent_to(effective + self.indentwidth)
1024 else:
1025 if self.usetabs:
1026 pad = '\t'
1027 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001028 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001029 n = self.indentwidth
1030 pad = ' ' * (n - effective % n)
1031 text.insert("insert", pad)
1032 text.see("insert")
1033 return "break"
1034 finally:
1035 text.undo_block_stop()
1036
1037 def newline_and_indent_event(self, event):
1038 text = self.text
1039 first, last = self.get_selection_indices()
1040 text.undo_block_start()
1041 try:
1042 if first and last:
1043 text.delete(first, last)
1044 text.mark_set("insert", first)
1045 line = text.get("insert linestart", "insert")
1046 i, n = 0, len(line)
1047 while i < n and line[i] in " \t":
1048 i = i+1
1049 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001050 # the cursor is in or at leading indentation in a continuation
1051 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001052 text.insert("insert linestart", '\n')
1053 return "break"
1054 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001055 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001056 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001057 last_line_of_prompt = sys.ps1.split('\n')[-1]
1058 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001059 line = line[:-1]
1060 i = i+1
1061 if i:
1062 text.delete("insert - %d chars" % i, "insert")
1063 # strip whitespace after insert point
1064 while text.get("insert") in " \t":
1065 text.delete("insert")
1066 # start new line
1067 text.insert("insert", '\n')
1068
1069 # adjust indentation for continuations and block
1070 # open/close first need to find the last stmt
1071 lno = index2line(text.index('insert'))
1072 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1073 for context in self.num_context_lines:
1074 startat = max(lno - context, 1)
1075 startatindex = `startat` + ".0"
1076 rawtext = text.get(startatindex, "insert")
1077 y.set_str(rawtext)
1078 bod = y.find_good_parse_start(
1079 self.context_use_ps1,
1080 self._build_char_in_string_func(startatindex))
1081 if bod is not None or startat == 1:
1082 break
1083 y.set_lo(bod or 0)
1084 c = y.get_continuation_type()
1085 if c != PyParse.C_NONE:
1086 # The current stmt hasn't ended yet.
1087 if c == PyParse.C_STRING:
1088 # inside a string; just mimic the current indent
1089 text.insert("insert", indent)
1090 elif c == PyParse.C_BRACKET:
1091 # line up with the first (if any) element of the
1092 # last open bracket structure; else indent one
1093 # level beyond the indent of the line with the
1094 # last open bracket
1095 self.reindent_to(y.compute_bracket_indent())
1096 elif c == PyParse.C_BACKSLASH:
1097 # if more than one line in this stmt already, just
1098 # mimic the current indent; else if initial line
1099 # has a start on an assignment stmt, indent to
1100 # beyond leftmost =; else to beyond first chunk of
1101 # non-whitespace on initial line
1102 if y.get_num_lines_in_stmt() > 1:
1103 text.insert("insert", indent)
1104 else:
1105 self.reindent_to(y.compute_backslash_indent())
1106 else:
1107 assert 0, "bogus continuation type " + `c`
1108 return "break"
1109
1110 # This line starts a brand new stmt; indent relative to
1111 # indentation of initial line of closest preceding
1112 # interesting stmt.
1113 indent = y.get_base_indent_string()
1114 text.insert("insert", indent)
1115 if y.is_block_opener():
1116 self.smart_indent_event(event)
1117 elif indent and y.is_block_closer():
1118 self.smart_backspace_event(event)
1119 return "break"
1120 finally:
1121 text.see("insert")
1122 text.undo_block_stop()
1123
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001124 # Our editwin provides a is_char_in_string function that works
1125 # with a Tk text index, but PyParse only knows about offsets into
1126 # a string. This builds a function for PyParse that accepts an
1127 # offset.
1128
1129 def _build_char_in_string_func(self, startindex):
1130 def inner(offset, _startindex=startindex,
1131 _icis=self.is_char_in_string):
1132 return _icis(_startindex + "+%dc" % offset)
1133 return inner
1134
1135 def indent_region_event(self, event):
1136 head, tail, chars, lines = self.get_region()
1137 for pos in range(len(lines)):
1138 line = lines[pos]
1139 if line:
1140 raw, effective = classifyws(line, self.tabwidth)
1141 effective = effective + self.indentwidth
1142 lines[pos] = self._make_blanks(effective) + line[raw:]
1143 self.set_region(head, tail, chars, lines)
1144 return "break"
1145
1146 def dedent_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 line:
1151 raw, effective = classifyws(line, self.tabwidth)
1152 effective = max(effective - self.indentwidth, 0)
1153 lines[pos] = self._make_blanks(effective) + line[raw:]
1154 self.set_region(head, tail, chars, lines)
1155 return "break"
1156
1157 def comment_region_event(self, event):
1158 head, tail, chars, lines = self.get_region()
1159 for pos in range(len(lines) - 1):
1160 line = lines[pos]
1161 lines[pos] = '##' + line
1162 self.set_region(head, tail, chars, lines)
1163
1164 def uncomment_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 not line:
1169 continue
1170 if line[:2] == '##':
1171 line = line[2:]
1172 elif line[:1] == '#':
1173 line = line[1:]
1174 lines[pos] = line
1175 self.set_region(head, tail, chars, lines)
1176
1177 def tabify_region_event(self, event):
1178 head, tail, chars, lines = self.get_region()
1179 tabwidth = self._asktabwidth()
1180 for pos in range(len(lines)):
1181 line = lines[pos]
1182 if line:
1183 raw, effective = classifyws(line, tabwidth)
1184 ntabs, nspaces = divmod(effective, tabwidth)
1185 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1186 self.set_region(head, tail, chars, lines)
1187
1188 def untabify_region_event(self, event):
1189 head, tail, chars, lines = self.get_region()
1190 tabwidth = self._asktabwidth()
1191 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001192 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001193 self.set_region(head, tail, chars, lines)
1194
1195 def toggle_tabs_event(self, event):
1196 if self.askyesno(
1197 "Toggle tabs",
1198 "Turn tabs " + ("on", "off")[self.usetabs] + "?",
1199 parent=self.text):
1200 self.usetabs = not self.usetabs
1201 return "break"
1202
1203 # XXX this isn't bound to anything -- see class tabwidth comments
1204 def change_tabwidth_event(self, event):
1205 new = self._asktabwidth()
1206 if new != self.tabwidth:
1207 self.tabwidth = new
1208 self.set_indentation_params(0, guess=0)
1209 return "break"
1210
1211 def change_indentwidth_event(self, event):
1212 new = self.askinteger(
1213 "Indent width",
1214 "New indent width (2-16)",
1215 parent=self.text,
1216 initialvalue=self.indentwidth,
1217 minvalue=2,
1218 maxvalue=16)
1219 if new and new != self.indentwidth:
1220 self.indentwidth = new
1221 return "break"
1222
1223 def get_region(self):
1224 text = self.text
1225 first, last = self.get_selection_indices()
1226 if first and last:
1227 head = text.index(first + " linestart")
1228 tail = text.index(last + "-1c lineend +1c")
1229 else:
1230 head = text.index("insert linestart")
1231 tail = text.index("insert lineend +1c")
1232 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001233 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001234 return head, tail, chars, lines
1235
1236 def set_region(self, head, tail, chars, lines):
1237 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001238 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001239 if newchars == chars:
1240 text.bell()
1241 return
1242 text.tag_remove("sel", "1.0", "end")
1243 text.mark_set("insert", head)
1244 text.undo_block_start()
1245 text.delete(head, tail)
1246 text.insert(head, newchars)
1247 text.undo_block_stop()
1248 text.tag_add("sel", head, "insert")
1249
1250 # Make string that displays as n leading blanks.
1251
1252 def _make_blanks(self, n):
1253 if self.usetabs:
1254 ntabs, nspaces = divmod(n, self.tabwidth)
1255 return '\t' * ntabs + ' ' * nspaces
1256 else:
1257 return ' ' * n
1258
1259 # Delete from beginning of line to insert point, then reinsert
1260 # column logical (meaning use tabs if appropriate) spaces.
1261
1262 def reindent_to(self, column):
1263 text = self.text
1264 text.undo_block_start()
1265 if text.compare("insert linestart", "!=", "insert"):
1266 text.delete("insert linestart", "insert")
1267 if column:
1268 text.insert("insert", self._make_blanks(column))
1269 text.undo_block_stop()
1270
1271 def _asktabwidth(self):
1272 return self.askinteger(
1273 "Tab width",
1274 "Spaces per tab? (2-16)",
1275 parent=self.text,
1276 initialvalue=self.indentwidth,
1277 minvalue=2,
1278 maxvalue=16) or self.tabwidth
1279
1280 # Guess indentwidth from text content.
1281 # Return guessed indentwidth. This should not be believed unless
1282 # it's in a reasonable range (e.g., it will be 0 if no indented
1283 # blocks are found).
1284
1285 def guess_indent(self):
1286 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1287 if opener and indented:
1288 raw, indentsmall = classifyws(opener, self.tabwidth)
1289 raw, indentlarge = classifyws(indented, self.tabwidth)
1290 else:
1291 indentsmall = indentlarge = 0
1292 return indentlarge - indentsmall
1293
1294# "line.col" -> line, as an int
1295def index2line(index):
1296 return int(float(index))
1297
1298# Look at the leading whitespace in s.
1299# Return pair (# of leading ws characters,
1300# effective # of leading blanks after expanding
1301# tabs to width tabwidth)
1302
1303def classifyws(s, tabwidth):
1304 raw = effective = 0
1305 for ch in s:
1306 if ch == ' ':
1307 raw = raw + 1
1308 effective = effective + 1
1309 elif ch == '\t':
1310 raw = raw + 1
1311 effective = (effective // tabwidth + 1) * tabwidth
1312 else:
1313 break
1314 return raw, effective
1315
1316import tokenize
1317_tokenize = tokenize
1318del tokenize
1319
1320class IndentSearcher:
1321
1322 # .run() chews over the Text widget, looking for a block opener
1323 # and the stmt following it. Returns a pair,
1324 # (line containing block opener, line containing stmt)
1325 # Either or both may be None.
1326
1327 def __init__(self, text, tabwidth):
1328 self.text = text
1329 self.tabwidth = tabwidth
1330 self.i = self.finished = 0
1331 self.blkopenline = self.indentedline = None
1332
1333 def readline(self):
1334 if self.finished:
1335 return ""
1336 i = self.i = self.i + 1
1337 mark = `i` + ".0"
1338 if self.text.compare(mark, ">=", "end"):
1339 return ""
1340 return self.text.get(mark, mark + " lineend+1c")
1341
1342 def tokeneater(self, type, token, start, end, line,
1343 INDENT=_tokenize.INDENT,
1344 NAME=_tokenize.NAME,
1345 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1346 if self.finished:
1347 pass
1348 elif type == NAME and token in OPENERS:
1349 self.blkopenline = line
1350 elif type == INDENT and self.blkopenline:
1351 self.indentedline = line
1352 self.finished = 1
1353
1354 def run(self):
1355 save_tabsize = _tokenize.tabsize
1356 _tokenize.tabsize = self.tabwidth
1357 try:
1358 try:
1359 _tokenize.tokenize(self.readline, self.tokeneater)
1360 except _tokenize.TokenError:
1361 # since we cut off the tokenizer early, we can trigger
1362 # spurious errors
1363 pass
1364 finally:
1365 _tokenize.tabsize = save_tabsize
1366 return self.blkopenline, self.indentedline
1367
1368### end autoindent code ###
1369
David Scherer7aced172000-08-15 01:13:23 +00001370def prepstr(s):
1371 # Helper to extract the underscore from a string, e.g.
1372 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001373 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001374 if i >= 0:
1375 s = s[:i] + s[i+1:]
1376 return i, s
1377
1378
1379keynames = {
1380 'bracketleft': '[',
1381 'bracketright': ']',
1382 'slash': '/',
1383}
1384
1385def get_accelerator(keydefs, event):
1386 keylist = keydefs.get(event)
1387 if not keylist:
1388 return ""
1389 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001390 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001391 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1392 s = re.sub("Key-", "", s)
1393 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1394 s = re.sub("Control-", "Ctrl-", s)
1395 s = re.sub("-", "+", s)
1396 s = re.sub("><", " ", s)
1397 s = re.sub("<", "", s)
1398 s = re.sub(">", "", s)
1399 return s
1400
1401
1402def fixwordbreaks(root):
1403 # Make sure that Tk's double-click and next/previous word
1404 # operations use our definition of a word (i.e. an identifier)
1405 tk = root.tk
1406 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1407 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1408 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1409
1410
1411def test():
1412 root = Tk()
1413 fixwordbreaks(root)
1414 root.withdraw()
1415 if sys.argv[1:]:
1416 filename = sys.argv[1]
1417 else:
1418 filename = None
1419 edit = EditorWindow(root=root, filename=filename)
1420 edit.set_close_hook(root.quit)
1421 root.mainloop()
1422 root.destroy()
1423
1424if __name__ == '__main__':
1425 test()