blob: 2303537dfe16c13b818ada30a7c64e260d3143b5 [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
262 # XXX Actually, this Just-In-Time updating interferes badly
263 # XXX with the tear-off feature. It would be better to update
264 # XXX all Windows menus whenever the list of windows changes.
265 menu = self.menudict['windows']
266 end = menu.index("end")
267 if end is None:
268 end = -1
269 if end > self.wmenu_end:
270 menu.delete(self.wmenu_end+1, end)
271 WindowList.add_windows_to_menu(menu)
272
273 rmenu = None
274
275 def right_menu_event(self, event):
276 self.text.tag_remove("sel", "1.0", "end")
277 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
278 if not self.rmenu:
279 self.make_rmenu()
280 rmenu = self.rmenu
281 self.event = event
282 iswin = sys.platform[:3] == 'win'
283 if iswin:
284 self.text.config(cursor="arrow")
285 rmenu.tk_popup(event.x_root, event.y_root)
286 if iswin:
287 self.text.config(cursor="ibeam")
288
289 rmenu_specs = [
290 # ("Label", "<<virtual-event>>"), ...
291 ("Close", "<<close-window>>"), # Example
292 ]
293
294 def make_rmenu(self):
295 rmenu = Menu(self.text, tearoff=0)
296 for label, eventname in self.rmenu_specs:
297 def command(text=self.text, eventname=eventname):
298 text.event_generate(eventname)
299 rmenu.add_command(label=label, command=command)
300 self.rmenu = rmenu
301
302 def about_dialog(self, event=None):
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000303 aboutDialog.AboutDialog(self.top,'About IDLEfork')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000304
Steven M. Gava3b55a892001-11-21 05:56:26 +0000305 def config_dialog(self, event=None):
306 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000307
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000308 def view_readme(self, event=None):
309 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000310 textView.TextViewer(self.top,'IDLEfork - README',fn)
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000311
David Scherer7aced172000-08-15 01:13:23 +0000312 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000313 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000314 textView.TextViewer(self.top,'Help',fn)
315
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000316 def python_docs(self, event=None):
317 if sys.platform.count('win') or sys.platform.count('nt'):
Steven M. Gava931625d2002-04-22 00:38:26 +0000318 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000319 return "break"
320 else:
321 webbrowser.open(self.help_url)
322 return "break"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000323
324 def display_docs(self, url):
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000325 if not (url.startswith('www') or url.startswith('http')):
326 url = os.path.normpath(url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000327 if sys.platform.count('win') or sys.platform.count('nt'):
328 os.startfile(url)
329 else:
330 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000331
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000332 def cut(self,event):
333 self.text.event_generate("<<Cut>>")
334 return "break"
335
336 def copy(self,event):
337 self.text.event_generate("<<Copy>>")
338 return "break"
339
340 def paste(self,event):
341 self.text.event_generate("<<Paste>>")
342 return "break"
343
David Scherer7aced172000-08-15 01:13:23 +0000344 def select_all(self, event=None):
345 self.text.tag_add("sel", "1.0", "end-1c")
346 self.text.mark_set("insert", "1.0")
347 self.text.see("insert")
348 return "break"
349
350 def remove_selection(self, event=None):
351 self.text.tag_remove("sel", "1.0", "end")
352 self.text.see("insert")
353
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000354 def move_at_edge_if_selection(self, edge_index):
355 """Cursor move begins at start or end of selection
356
357 When a left/right cursor key is pressed create and return to Tkinter a
358 function which causes a cursor move from the associated edge of the
359 selection.
360
361 """
362 self_text_index = self.text.index
363 self_text_mark_set = self.text.mark_set
364 edges_table = ("sel.first+1c", "sel.last-1c")
365 def move_at_edge(event):
366 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
367 try:
368 self_text_index("sel.first")
369 self_text_mark_set("insert", edges_table[edge_index])
370 except TclError:
371 pass
372 return move_at_edge
373
Steven M. Gavac5976402002-01-04 03:06:08 +0000374 def find_event(self, event):
375 SearchDialog.find(self.text)
376 return "break"
377
378 def find_again_event(self, event):
379 SearchDialog.find_again(self.text)
380 return "break"
381
382 def find_selection_event(self, event):
383 SearchDialog.find_selection(self.text)
384 return "break"
385
386 def find_in_files_event(self, event):
387 GrepDialog.grep(self.text, self.io, self.flist)
388 return "break"
389
390 def replace_event(self, event):
391 ReplaceDialog.replace(self.text)
392 return "break"
393
394 def goto_line_event(self, event):
395 text = self.text
396 lineno = tkSimpleDialog.askinteger("Goto",
397 "Go to line number:",parent=text)
398 if lineno is None:
399 return "break"
400 if lineno <= 0:
401 text.bell()
402 return "break"
403 text.mark_set("insert", "%d.0" % lineno)
404 text.see("insert")
405
David Scherer7aced172000-08-15 01:13:23 +0000406 def open_module(self, event=None):
407 # XXX Shouldn't this be in IOBinding or in FileList?
408 try:
409 name = self.text.get("sel.first", "sel.last")
410 except TclError:
411 name = ""
412 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000413 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000414 if not name:
415 name = tkSimpleDialog.askstring("Module",
416 "Enter the name of a Python module\n"
417 "to search on sys.path and open:",
418 parent=self.text)
419 if name:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000420 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000421 if not name:
422 return
David Scherer7aced172000-08-15 01:13:23 +0000423 # XXX Ought to insert current file's directory in front of path
424 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000425 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000426 except (NameError, ImportError), msg:
427 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
428 return
429 if type != imp.PY_SOURCE:
430 tkMessageBox.showerror("Unsupported type",
431 "%s is not a source module" % name, parent=self.text)
432 return
433 if f:
434 f.close()
435 if self.flist:
436 self.flist.open(file)
437 else:
438 self.io.loadfile(file)
439
440 def open_class_browser(self, event=None):
441 filename = self.io.filename
442 if not filename:
443 tkMessageBox.showerror(
444 "No filename",
445 "This buffer has no associated filename",
446 master=self.text)
447 self.text.focus_set()
448 return None
449 head, tail = os.path.split(filename)
450 base, ext = os.path.splitext(tail)
451 import ClassBrowser
452 ClassBrowser.ClassBrowser(self.flist, base, [head])
453
454 def open_path_browser(self, event=None):
455 import PathBrowser
456 PathBrowser.PathBrowser(self.flist)
457
458 def gotoline(self, lineno):
459 if lineno is not None and lineno > 0:
460 self.text.mark_set("insert", "%d.0" % lineno)
461 self.text.tag_remove("sel", "1.0", "end")
462 self.text.tag_add("sel", "insert", "insert +1l")
463 self.center()
464
465 def ispythonsource(self, filename):
466 if not filename:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000467 return True
David Scherer7aced172000-08-15 01:13:23 +0000468 base, ext = os.path.splitext(os.path.basename(filename))
469 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000470 return True
David Scherer7aced172000-08-15 01:13:23 +0000471 try:
472 f = open(filename)
473 line = f.readline()
474 f.close()
475 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000476 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000477 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000478
479 def close_hook(self):
480 if self.flist:
481 self.flist.close_edit(self)
482
483 def set_close_hook(self, close_hook):
484 self.close_hook = close_hook
485
486 def filename_change_hook(self):
487 if self.flist:
488 self.flist.filename_changed_edit(self)
489 self.saved_change_hook()
490 if self.ispythonsource(self.io.filename):
491 self.addcolorizer()
492 else:
493 self.rmcolorizer()
494
495 def addcolorizer(self):
496 if self.color:
497 return
David Scherer7aced172000-08-15 01:13:23 +0000498 self.per.removefilter(self.undo)
499 self.color = self.ColorDelegator()
500 self.per.insertfilter(self.color)
501 self.per.insertfilter(self.undo)
502
503 def rmcolorizer(self):
504 if not self.color:
505 return
David Scherer7aced172000-08-15 01:13:23 +0000506 self.per.removefilter(self.undo)
507 self.per.removefilter(self.color)
508 self.color = None
509 self.per.insertfilter(self.undo)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000510
Steven M. Gavab77d3432002-03-02 07:16:21 +0000511 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000512 "Update the colour theme if it is changed"
513 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000514 if self.color:
515 self.color = self.ColorDelegator()
516 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000517
Steven M. Gavab1585412002-03-12 00:21:56 +0000518 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000519 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000520 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000521 fontWeight='normal'
522 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
523 fontWeight='bold'
524 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
525 idleConf.GetOption('main','EditorWindow','font-size'),
526 fontWeight))
527
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000528 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000529 "Update the keybindings if they are changed"
530 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000531 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
532 keydefs = self.Bindings.default_keydefs
533 for event, keylist in keydefs.items():
534 self.text.event_delete(event)
535 self.apply_bindings()
536 #update menu accelerators
537 menuEventDict={}
538 for menu in self.Bindings.menudefs:
539 menuEventDict[menu[0]]={}
540 for item in menu[1]:
541 if item:
542 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
543 for menubarItem in self.menudict.keys():
544 menu=self.menudict[menubarItem]
545 end=menu.index(END)+1
546 for index in range(0,end):
547 if menu.type(index)=='command':
548 accel=menu.entrycget(index,'accelerator')
549 if accel:
550 itemName=menu.entrycget(index,'label')
551 event=''
552 if menuEventDict.has_key(menubarItem):
553 if menuEventDict[menubarItem].has_key(itemName):
554 event=menuEventDict[menubarItem][itemName]
555 if event:
556 #print 'accel was:',accel
557 accel=get_accelerator(keydefs, event)
558 menu.entryconfig(index,accelerator=accel)
559 #print 'accel now:',accel,'\n'
560
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000561 def reset_help_menu_entries(self):
562 "Update the additional help entries on the Help menu"
563 help_list = idleConf.GetAllExtraHelpSourcesList()
564 helpmenu = self.menudict['help']
565 # first delete the extra help entries, if any
566 helpmenu_length = helpmenu.index(END)
567 if helpmenu_length > self.base_helpmenu_length:
568 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
569 # then rebuild them
570 if help_list:
571 helpmenu.add_separator()
572 for entry in help_list:
573 cmd = self.__extra_help_callback(entry[1])
574 helpmenu.add_command(label=entry[0], command=cmd)
575 # and update the menu dictionary
576 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000577
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000578 def __extra_help_callback(self, helpfile):
579 "Create a callback with the helpfile value frozen at definition time"
580 def display_extra_help(helpfile=helpfile):
581 self.display_docs(helpfile)
582 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000583
Steven M. Gava1d46e402002-03-27 08:40:46 +0000584 def UpdateRecentFilesList(self,newFile=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000585 "Load or update the recent files list, and menu if required"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000586 rfList=[]
587 if os.path.exists(self.recentFilesPath):
588 RFfile=open(self.recentFilesPath,'r')
589 try:
590 rfList=RFfile.readlines()
591 finally:
592 RFfile.close()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000593 if newFile:
Steven M. Gava1d46e402002-03-27 08:40:46 +0000594 newFile=os.path.abspath(newFile)+'\n'
595 if newFile in rfList:
596 rfList.remove(newFile)
597 rfList.insert(0,newFile)
598 rfList=self.__CleanRecentFiles(rfList)
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000599 #print self.flist.inversedict
600 #print self.top.instanceDict
601 #print self
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000602 ullist = "1234567890ABCDEFGHIJ"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000603 if rfList:
604 for instance in self.top.instanceDict.keys():
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000605 menu = instance.menuRecentFiles
606 menu.delete(1,END)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000607 i = 0 ; ul = 0; ullen = len(ullist)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000608 for file in rfList:
609 fileName=file[0:-1]
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000610 callback = instance.__RecentFileCallback(fileName)
611 if i > ullen: # don't underline menuitems
612 ul=None
613 menu.add_command(label=ullist[i] + " " + fileName,
614 command=callback,
615 underline=ul)
616 i += 1
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000617
Steven M. Gava1d46e402002-03-27 08:40:46 +0000618 def __CleanRecentFiles(self,rfList):
619 origRfList=rfList[:]
620 count=0
621 nonFiles=[]
622 for path in rfList:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000623 if not os.path.exists(path[0:-1]):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000624 nonFiles.append(count)
625 count=count+1
626 if nonFiles:
627 nonFiles.reverse()
628 for index in nonFiles:
629 del(rfList[index])
630 if len(rfList)>19:
631 rfList=rfList[0:19]
632 #if rfList != origRfList:
633 RFfile=open(self.recentFilesPath,'w')
634 try:
635 RFfile.writelines(rfList)
636 finally:
637 RFfile.close()
638 return rfList
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000639
Steven M. Gava1d46e402002-03-27 08:40:46 +0000640 def __RecentFileCallback(self,fileName):
641 def OpenRecentFile(fileName=fileName):
642 self.io.open(editFile=fileName)
643 return OpenRecentFile
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000644
David Scherer7aced172000-08-15 01:13:23 +0000645 def saved_change_hook(self):
646 short = self.short_title()
647 long = self.long_title()
648 if short and long:
649 title = short + " - " + long
650 elif short:
651 title = short
652 elif long:
653 title = long
654 else:
655 title = "Untitled"
656 icon = short or long or title
657 if not self.get_saved():
658 title = "*%s*" % title
659 icon = "*%s" % icon
660 self.top.wm_title(title)
661 self.top.wm_iconname(icon)
662
663 def get_saved(self):
664 return self.undo.get_saved()
665
666 def set_saved(self, flag):
667 self.undo.set_saved(flag)
668
669 def reset_undo(self):
670 self.undo.reset_undo()
671
672 def short_title(self):
673 filename = self.io.filename
674 if filename:
675 filename = os.path.basename(filename)
676 return filename
677
678 def long_title(self):
679 return self.io.filename or ""
680
681 def center_insert_event(self, event):
682 self.center()
683
684 def center(self, mark="insert"):
685 text = self.text
686 top, bot = self.getwindowlines()
687 lineno = self.getlineno(mark)
688 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000689 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000690 text.yview(float(newtop))
691
692 def getwindowlines(self):
693 text = self.text
694 top = self.getlineno("@0,0")
695 bot = self.getlineno("@0,65535")
696 if top == bot and text.winfo_height() == 1:
697 # Geometry manager hasn't run yet
698 height = int(text['height'])
699 bot = top + height - 1
700 return top, bot
701
702 def getlineno(self, mark="insert"):
703 text = self.text
704 return int(float(text.index(mark)))
705
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000706 def get_geometry(self):
707 "Return (width, height, x, y)"
708 geom = self.top.wm_geometry()
709 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
710 tuple = (map(int, m.groups()))
711 return tuple
712
David Scherer7aced172000-08-15 01:13:23 +0000713 def close_event(self, event):
714 self.close()
715
716 def maybesave(self):
717 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000718 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000719 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000720 self.top.deiconify()
721 self.top.lower()
722 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000723 return self.io.maybesave()
724
725 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000726 reply = self.maybesave()
727 if reply != "cancel":
728 self._close()
729 return reply
730
731 def _close(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000732 #print self.io.filename
Steven M. Gava1d46e402002-03-27 08:40:46 +0000733 if self.io.filename:
734 self.UpdateRecentFilesList(newFile=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000735 WindowList.unregister_callback(self.postwindowsmenu)
736 if self.close_hook:
737 self.close_hook()
738 self.flist = None
739 colorizing = 0
740 self.unload_extensions()
741 self.io.close(); self.io = None
742 self.undo = None # XXX
743 if self.color:
744 colorizing = self.color.colorizing
745 doh = colorizing and self.top
746 self.color.close(doh) # Cancel colorization
747 self.text = None
748 self.vars = None
749 self.per.close(); self.per = None
750 if not colorizing:
751 self.top.destroy()
752
753 def load_extensions(self):
754 self.extensions = {}
755 self.load_standard_extensions()
756
757 def unload_extensions(self):
758 for ins in self.extensions.values():
759 if hasattr(ins, "close"):
760 ins.close()
761 self.extensions = {}
762
763 def load_standard_extensions(self):
764 for name in self.get_standard_extension_names():
765 try:
766 self.load_extension(name)
767 except:
768 print "Failed to load extension", `name`
769 import traceback
770 traceback.print_exc()
771
772 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000773 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000774
775 def load_extension(self, name):
776 mod = __import__(name, globals(), locals(), [])
777 cls = getattr(mod, name)
778 ins = cls(self)
779 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000780 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000781 if keydefs:
782 self.apply_bindings(keydefs)
783 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000784 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000785 while methodname[:1] == '<':
786 methodname = methodname[1:]
787 while methodname[-1:] == '>':
788 methodname = methodname[:-1]
789 methodname = methodname + "_event"
790 if hasattr(ins, methodname):
791 self.text.bind(vevent, getattr(ins, methodname))
792 if hasattr(ins, "menudefs"):
793 self.fill_menus(ins.menudefs, keydefs)
794 return ins
795
796 def apply_bindings(self, keydefs=None):
797 if keydefs is None:
798 keydefs = self.Bindings.default_keydefs
799 text = self.text
800 text.keydefs = keydefs
801 for event, keylist in keydefs.items():
802 if keylist:
803 apply(text.event_add, (event,) + tuple(keylist))
804
805 def fill_menus(self, defs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000806 """Add appropriate entries to the menus and submenus
807
808 Menus that are absent or None in self.menudict are ignored.
809 """
David Scherer7aced172000-08-15 01:13:23 +0000810 if defs is None:
811 defs = self.Bindings.menudefs
812 if keydefs is None:
813 keydefs = self.Bindings.default_keydefs
814 menudict = self.menudict
815 text = self.text
816 for mname, itemlist in defs:
817 menu = menudict.get(mname)
818 if not menu:
819 continue
820 for item in itemlist:
821 if not item:
822 menu.add_separator()
823 else:
824 label, event = item
825 checkbutton = (label[:1] == '!')
826 if checkbutton:
827 label = label[1:]
828 underline, label = prepstr(label)
829 accelerator = get_accelerator(keydefs, event)
830 def command(text=text, event=event):
831 text.event_generate(event)
832 if checkbutton:
833 var = self.getrawvar(event, BooleanVar)
834 menu.add_checkbutton(label=label, underline=underline,
835 command=command, accelerator=accelerator,
836 variable=var)
837 else:
838 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000839 command=command,
840 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000841
842 def getvar(self, name):
843 var = self.getrawvar(name)
844 if var:
845 return var.get()
846
847 def setvar(self, name, value, vartype=None):
848 var = self.getrawvar(name, vartype)
849 if var:
850 var.set(value)
851
852 def getrawvar(self, name, vartype=None):
853 var = self.vars.get(name)
854 if not var and vartype:
855 self.vars[name] = var = vartype(self.text)
856 return var
857
858 # Tk implementations of "virtual text methods" -- each platform
859 # reusing IDLE's support code needs to define these for its GUI's
860 # flavor of widget.
861
862 # Is character at text_index in a Python string? Return 0 for
863 # "guaranteed no", true for anything else. This info is expensive
864 # to compute ab initio, but is probably already known by the
865 # platform's colorizer.
866
867 def is_char_in_string(self, text_index):
868 if self.color:
869 # Return true iff colorizer hasn't (re)gotten this far
870 # yet, or the character is tagged as being in a string
871 return self.text.tag_prevrange("TODO", text_index) or \
872 "STRING" in self.text.tag_names(text_index)
873 else:
874 # The colorizer is missing: assume the worst
875 return 1
876
877 # If a selection is defined in the text widget, return (start,
878 # end) as Tkinter text indices, otherwise return (None, None)
879 def get_selection_indices(self):
880 try:
881 first = self.text.index("sel.first")
882 last = self.text.index("sel.last")
883 return first, last
884 except TclError:
885 return None, None
886
887 # Return the text widget's current view of what a tab stop means
888 # (equivalent width in spaces).
889
890 def get_tabwidth(self):
891 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
892 return int(current)
893
894 # Set the text widget's current view of what a tab stop means.
895
896 def set_tabwidth(self, newtabwidth):
897 text = self.text
898 if self.get_tabwidth() != newtabwidth:
899 pixels = text.tk.call("font", "measure", text["font"],
900 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000901 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000902 text.configure(tabs=pixels)
903
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000904### begin autoindent code ###
905
906 # usetabs true -> literal tab characters are used by indent and
907 # dedent cmds, possibly mixed with spaces if
908 # indentwidth is not a multiple of tabwidth
909 # false -> tab characters are converted to spaces by indent
910 # and dedent cmds, and ditto TAB keystrokes
911 # indentwidth is the number of characters per logical indent level.
912 # tabwidth is the display width of a literal tab character.
913 # CAUTION: telling Tk to use anything other than its default
914 # tab setting causes it to use an entirely different tabbing algorithm,
915 # treating tab stops as fixed distances from the left margin.
916 # Nobody expects this, so for now tabwidth should never be changed.
917 usetabs = 0
918 indentwidth = 4
919 tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
920
921 # If context_use_ps1 is true, parsing searches back for a ps1 line;
922 # else searches for a popular (if, def, ...) Python stmt.
923 context_use_ps1 = 0
924
925 # When searching backwards for a reliable place to begin parsing,
926 # first start num_context_lines[0] lines back, then
927 # num_context_lines[1] lines back if that didn't work, and so on.
928 # The last value should be huge (larger than the # of lines in a
929 # conceivable file).
930 # Making the initial values larger slows things down more often.
931 num_context_lines = 50, 500, 5000000
932
933 def config(self, **options):
934 for key, value in options.items():
935 if key == 'usetabs':
936 self.usetabs = value
937 elif key == 'indentwidth':
938 self.indentwidth = value
939 elif key == 'tabwidth':
940 self.tabwidth = value
941 elif key == 'context_use_ps1':
942 self.context_use_ps1 = value
943 else:
944 raise KeyError, "bad option name: %s" % `key`
945
946 # If ispythonsource and guess are true, guess a good value for
947 # indentwidth based on file content (if possible), and if
948 # indentwidth != tabwidth set usetabs false.
949 # In any case, adjust the Text widget's view of what a tab
950 # character means.
951
952 def set_indentation_params(self, ispythonsource, guess=1):
953 if guess and ispythonsource:
954 i = self.guess_indent()
955 if 2 <= i <= 8:
956 self.indentwidth = i
957 if self.indentwidth != self.tabwidth:
958 self.usetabs = 0
959
960 self.set_tabwidth(self.tabwidth)
961
962 def smart_backspace_event(self, event):
963 text = self.text
964 first, last = self.get_selection_indices()
965 if first and last:
966 text.delete(first, last)
967 text.mark_set("insert", first)
968 return "break"
969 # Delete whitespace left, until hitting a real char or closest
970 # preceding virtual tab stop.
971 chars = text.get("insert linestart", "insert")
972 if chars == '':
973 if text.compare("insert", ">", "1.0"):
974 # easy: delete preceding newline
975 text.delete("insert-1c")
976 else:
977 text.bell() # at start of buffer
978 return "break"
979 if chars[-1] not in " \t":
980 # easy: delete preceding real char
981 text.delete("insert-1c")
982 return "break"
983 # Ick. It may require *inserting* spaces if we back up over a
984 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000985 tabwidth = self.tabwidth
986 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000987 assert have > 0
988 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000989 # Debug prompt is multilined....
990 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000991 ncharsdeleted = 0
992 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000993 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +0000994 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000995 chars = chars[:-1]
996 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000997 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000998 if have <= want or chars[-1] not in " \t":
999 break
1000 text.undo_block_start()
1001 text.delete("insert-%dc" % ncharsdeleted, "insert")
1002 if have < want:
1003 text.insert("insert", ' ' * (want - have))
1004 text.undo_block_stop()
1005 return "break"
1006
1007 def smart_indent_event(self, event):
1008 # if intraline selection:
1009 # delete it
1010 # elif multiline selection:
1011 # do indent-region & return
1012 # indent one level
1013 text = self.text
1014 first, last = self.get_selection_indices()
1015 text.undo_block_start()
1016 try:
1017 if first and last:
1018 if index2line(first) != index2line(last):
1019 return self.indent_region_event(event)
1020 text.delete(first, last)
1021 text.mark_set("insert", first)
1022 prefix = text.get("insert linestart", "insert")
1023 raw, effective = classifyws(prefix, self.tabwidth)
1024 if raw == len(prefix):
1025 # only whitespace to the left
1026 self.reindent_to(effective + self.indentwidth)
1027 else:
1028 if self.usetabs:
1029 pad = '\t'
1030 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001031 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001032 n = self.indentwidth
1033 pad = ' ' * (n - effective % n)
1034 text.insert("insert", pad)
1035 text.see("insert")
1036 return "break"
1037 finally:
1038 text.undo_block_stop()
1039
1040 def newline_and_indent_event(self, event):
1041 text = self.text
1042 first, last = self.get_selection_indices()
1043 text.undo_block_start()
1044 try:
1045 if first and last:
1046 text.delete(first, last)
1047 text.mark_set("insert", first)
1048 line = text.get("insert linestart", "insert")
1049 i, n = 0, len(line)
1050 while i < n and line[i] in " \t":
1051 i = i+1
1052 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001053 # the cursor is in or at leading indentation in a continuation
1054 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001055 text.insert("insert linestart", '\n')
1056 return "break"
1057 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001058 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001059 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001060 last_line_of_prompt = sys.ps1.split('\n')[-1]
1061 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001062 line = line[:-1]
1063 i = i+1
1064 if i:
1065 text.delete("insert - %d chars" % i, "insert")
1066 # strip whitespace after insert point
1067 while text.get("insert") in " \t":
1068 text.delete("insert")
1069 # start new line
1070 text.insert("insert", '\n')
1071
1072 # adjust indentation for continuations and block
1073 # open/close first need to find the last stmt
1074 lno = index2line(text.index('insert'))
1075 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1076 for context in self.num_context_lines:
1077 startat = max(lno - context, 1)
1078 startatindex = `startat` + ".0"
1079 rawtext = text.get(startatindex, "insert")
1080 y.set_str(rawtext)
1081 bod = y.find_good_parse_start(
1082 self.context_use_ps1,
1083 self._build_char_in_string_func(startatindex))
1084 if bod is not None or startat == 1:
1085 break
1086 y.set_lo(bod or 0)
1087 c = y.get_continuation_type()
1088 if c != PyParse.C_NONE:
1089 # The current stmt hasn't ended yet.
1090 if c == PyParse.C_STRING:
1091 # inside a string; just mimic the current indent
1092 text.insert("insert", indent)
1093 elif c == PyParse.C_BRACKET:
1094 # line up with the first (if any) element of the
1095 # last open bracket structure; else indent one
1096 # level beyond the indent of the line with the
1097 # last open bracket
1098 self.reindent_to(y.compute_bracket_indent())
1099 elif c == PyParse.C_BACKSLASH:
1100 # if more than one line in this stmt already, just
1101 # mimic the current indent; else if initial line
1102 # has a start on an assignment stmt, indent to
1103 # beyond leftmost =; else to beyond first chunk of
1104 # non-whitespace on initial line
1105 if y.get_num_lines_in_stmt() > 1:
1106 text.insert("insert", indent)
1107 else:
1108 self.reindent_to(y.compute_backslash_indent())
1109 else:
1110 assert 0, "bogus continuation type " + `c`
1111 return "break"
1112
1113 # This line starts a brand new stmt; indent relative to
1114 # indentation of initial line of closest preceding
1115 # interesting stmt.
1116 indent = y.get_base_indent_string()
1117 text.insert("insert", indent)
1118 if y.is_block_opener():
1119 self.smart_indent_event(event)
1120 elif indent and y.is_block_closer():
1121 self.smart_backspace_event(event)
1122 return "break"
1123 finally:
1124 text.see("insert")
1125 text.undo_block_stop()
1126
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001127 # Our editwin provides a is_char_in_string function that works
1128 # with a Tk text index, but PyParse only knows about offsets into
1129 # a string. This builds a function for PyParse that accepts an
1130 # offset.
1131
1132 def _build_char_in_string_func(self, startindex):
1133 def inner(offset, _startindex=startindex,
1134 _icis=self.is_char_in_string):
1135 return _icis(_startindex + "+%dc" % offset)
1136 return inner
1137
1138 def indent_region_event(self, event):
1139 head, tail, chars, lines = self.get_region()
1140 for pos in range(len(lines)):
1141 line = lines[pos]
1142 if line:
1143 raw, effective = classifyws(line, self.tabwidth)
1144 effective = effective + self.indentwidth
1145 lines[pos] = self._make_blanks(effective) + line[raw:]
1146 self.set_region(head, tail, chars, lines)
1147 return "break"
1148
1149 def dedent_region_event(self, event):
1150 head, tail, chars, lines = self.get_region()
1151 for pos in range(len(lines)):
1152 line = lines[pos]
1153 if line:
1154 raw, effective = classifyws(line, self.tabwidth)
1155 effective = max(effective - self.indentwidth, 0)
1156 lines[pos] = self._make_blanks(effective) + line[raw:]
1157 self.set_region(head, tail, chars, lines)
1158 return "break"
1159
1160 def comment_region_event(self, event):
1161 head, tail, chars, lines = self.get_region()
1162 for pos in range(len(lines) - 1):
1163 line = lines[pos]
1164 lines[pos] = '##' + line
1165 self.set_region(head, tail, chars, lines)
1166
1167 def uncomment_region_event(self, event):
1168 head, tail, chars, lines = self.get_region()
1169 for pos in range(len(lines)):
1170 line = lines[pos]
1171 if not line:
1172 continue
1173 if line[:2] == '##':
1174 line = line[2:]
1175 elif line[:1] == '#':
1176 line = line[1:]
1177 lines[pos] = line
1178 self.set_region(head, tail, chars, lines)
1179
1180 def tabify_region_event(self, event):
1181 head, tail, chars, lines = self.get_region()
1182 tabwidth = self._asktabwidth()
1183 for pos in range(len(lines)):
1184 line = lines[pos]
1185 if line:
1186 raw, effective = classifyws(line, tabwidth)
1187 ntabs, nspaces = divmod(effective, tabwidth)
1188 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1189 self.set_region(head, tail, chars, lines)
1190
1191 def untabify_region_event(self, event):
1192 head, tail, chars, lines = self.get_region()
1193 tabwidth = self._asktabwidth()
1194 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001195 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001196 self.set_region(head, tail, chars, lines)
1197
1198 def toggle_tabs_event(self, event):
1199 if self.askyesno(
1200 "Toggle tabs",
1201 "Turn tabs " + ("on", "off")[self.usetabs] + "?",
1202 parent=self.text):
1203 self.usetabs = not self.usetabs
1204 return "break"
1205
1206 # XXX this isn't bound to anything -- see class tabwidth comments
1207 def change_tabwidth_event(self, event):
1208 new = self._asktabwidth()
1209 if new != self.tabwidth:
1210 self.tabwidth = new
1211 self.set_indentation_params(0, guess=0)
1212 return "break"
1213
1214 def change_indentwidth_event(self, event):
1215 new = self.askinteger(
1216 "Indent width",
1217 "New indent width (2-16)",
1218 parent=self.text,
1219 initialvalue=self.indentwidth,
1220 minvalue=2,
1221 maxvalue=16)
1222 if new and new != self.indentwidth:
1223 self.indentwidth = new
1224 return "break"
1225
1226 def get_region(self):
1227 text = self.text
1228 first, last = self.get_selection_indices()
1229 if first and last:
1230 head = text.index(first + " linestart")
1231 tail = text.index(last + "-1c lineend +1c")
1232 else:
1233 head = text.index("insert linestart")
1234 tail = text.index("insert lineend +1c")
1235 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001236 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001237 return head, tail, chars, lines
1238
1239 def set_region(self, head, tail, chars, lines):
1240 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001241 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001242 if newchars == chars:
1243 text.bell()
1244 return
1245 text.tag_remove("sel", "1.0", "end")
1246 text.mark_set("insert", head)
1247 text.undo_block_start()
1248 text.delete(head, tail)
1249 text.insert(head, newchars)
1250 text.undo_block_stop()
1251 text.tag_add("sel", head, "insert")
1252
1253 # Make string that displays as n leading blanks.
1254
1255 def _make_blanks(self, n):
1256 if self.usetabs:
1257 ntabs, nspaces = divmod(n, self.tabwidth)
1258 return '\t' * ntabs + ' ' * nspaces
1259 else:
1260 return ' ' * n
1261
1262 # Delete from beginning of line to insert point, then reinsert
1263 # column logical (meaning use tabs if appropriate) spaces.
1264
1265 def reindent_to(self, column):
1266 text = self.text
1267 text.undo_block_start()
1268 if text.compare("insert linestart", "!=", "insert"):
1269 text.delete("insert linestart", "insert")
1270 if column:
1271 text.insert("insert", self._make_blanks(column))
1272 text.undo_block_stop()
1273
1274 def _asktabwidth(self):
1275 return self.askinteger(
1276 "Tab width",
1277 "Spaces per tab? (2-16)",
1278 parent=self.text,
1279 initialvalue=self.indentwidth,
1280 minvalue=2,
1281 maxvalue=16) or self.tabwidth
1282
1283 # Guess indentwidth from text content.
1284 # Return guessed indentwidth. This should not be believed unless
1285 # it's in a reasonable range (e.g., it will be 0 if no indented
1286 # blocks are found).
1287
1288 def guess_indent(self):
1289 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1290 if opener and indented:
1291 raw, indentsmall = classifyws(opener, self.tabwidth)
1292 raw, indentlarge = classifyws(indented, self.tabwidth)
1293 else:
1294 indentsmall = indentlarge = 0
1295 return indentlarge - indentsmall
1296
1297# "line.col" -> line, as an int
1298def index2line(index):
1299 return int(float(index))
1300
1301# Look at the leading whitespace in s.
1302# Return pair (# of leading ws characters,
1303# effective # of leading blanks after expanding
1304# tabs to width tabwidth)
1305
1306def classifyws(s, tabwidth):
1307 raw = effective = 0
1308 for ch in s:
1309 if ch == ' ':
1310 raw = raw + 1
1311 effective = effective + 1
1312 elif ch == '\t':
1313 raw = raw + 1
1314 effective = (effective // tabwidth + 1) * tabwidth
1315 else:
1316 break
1317 return raw, effective
1318
1319import tokenize
1320_tokenize = tokenize
1321del tokenize
1322
1323class IndentSearcher:
1324
1325 # .run() chews over the Text widget, looking for a block opener
1326 # and the stmt following it. Returns a pair,
1327 # (line containing block opener, line containing stmt)
1328 # Either or both may be None.
1329
1330 def __init__(self, text, tabwidth):
1331 self.text = text
1332 self.tabwidth = tabwidth
1333 self.i = self.finished = 0
1334 self.blkopenline = self.indentedline = None
1335
1336 def readline(self):
1337 if self.finished:
1338 return ""
1339 i = self.i = self.i + 1
1340 mark = `i` + ".0"
1341 if self.text.compare(mark, ">=", "end"):
1342 return ""
1343 return self.text.get(mark, mark + " lineend+1c")
1344
1345 def tokeneater(self, type, token, start, end, line,
1346 INDENT=_tokenize.INDENT,
1347 NAME=_tokenize.NAME,
1348 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1349 if self.finished:
1350 pass
1351 elif type == NAME and token in OPENERS:
1352 self.blkopenline = line
1353 elif type == INDENT and self.blkopenline:
1354 self.indentedline = line
1355 self.finished = 1
1356
1357 def run(self):
1358 save_tabsize = _tokenize.tabsize
1359 _tokenize.tabsize = self.tabwidth
1360 try:
1361 try:
1362 _tokenize.tokenize(self.readline, self.tokeneater)
1363 except _tokenize.TokenError:
1364 # since we cut off the tokenizer early, we can trigger
1365 # spurious errors
1366 pass
1367 finally:
1368 _tokenize.tabsize = save_tabsize
1369 return self.blkopenline, self.indentedline
1370
1371### end autoindent code ###
1372
David Scherer7aced172000-08-15 01:13:23 +00001373def prepstr(s):
1374 # Helper to extract the underscore from a string, e.g.
1375 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001376 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001377 if i >= 0:
1378 s = s[:i] + s[i+1:]
1379 return i, s
1380
1381
1382keynames = {
1383 'bracketleft': '[',
1384 'bracketright': ']',
1385 'slash': '/',
1386}
1387
1388def get_accelerator(keydefs, event):
1389 keylist = keydefs.get(event)
1390 if not keylist:
1391 return ""
1392 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001393 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001394 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1395 s = re.sub("Key-", "", s)
1396 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1397 s = re.sub("Control-", "Ctrl-", s)
1398 s = re.sub("-", "+", s)
1399 s = re.sub("><", " ", s)
1400 s = re.sub("<", "", s)
1401 s = re.sub(">", "", s)
1402 return s
1403
1404
1405def fixwordbreaks(root):
1406 # Make sure that Tk's double-click and next/previous word
1407 # operations use our definition of a word (i.e. an identifier)
1408 tk = root.tk
1409 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1410 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1411 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1412
1413
1414def test():
1415 root = Tk()
1416 fixwordbreaks(root)
1417 root.withdraw()
1418 if sys.argv[1:]:
1419 filename = sys.argv[1]
1420 else:
1421 filename = None
1422 edit = EditorWindow(root=root, filename=filename)
1423 edit.set_close_hook(root.quit)
1424 root.mainloop()
1425 root.destroy()
1426
1427if __name__ == '__main__':
1428 test()