blob: 01deba03d622c690ddbaa22a62135d45d140304c [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
3import string
4import re
5import imp
6from Tkinter import *
7import tkSimpleDialog
8import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00009
10import webbrowser
David Scherer7aced172000-08-15 01:13:23 +000011import idlever
12import WindowList
Steven M. Gavac5976402002-01-04 03:06:08 +000013import SearchDialog
14import GrepDialog
15import ReplaceDialog
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +000016import PyParse
Steven M. Gavadc72f482002-01-03 11:51:07 +000017from configHandler import idleConf
Steven M. Gava3b55a892001-11-21 05:56:26 +000018import aboutDialog, textView, configDialog
David Scherer7aced172000-08-15 01:13:23 +000019
20# The default tab setting for a Text widget, in average-width characters.
21TK_TABWIDTH_DEFAULT = 8
22
David Scherer7aced172000-08-15 01:13:23 +000023class EditorWindow:
David Scherer7aced172000-08-15 01:13:23 +000024 from Percolator import Percolator
25 from ColorDelegator import ColorDelegator
26 from UndoDelegator import UndoDelegator
27 from IOBinding import IOBinding
28 import Bindings
29 from Tkinter import Toplevel
30 from MultiStatusBar import MultiStatusBar
31
David Scherer7aced172000-08-15 01:13:23 +000032 vars = {}
33
34 def __init__(self, flist=None, filename=None, key=None, root=None):
Steven M. Gavadc72f482002-01-03 11:51:07 +000035 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000036 self.flist = flist
37 root = root or flist.root
38 self.root = root
David Scherer7aced172000-08-15 01:13:23 +000039 self.menubar = Menu(root)
40 self.top = top = self.Toplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000041 if flist:
42 self.vars = flist.vars
43 #self.top.instanceDict makes flist.inversedict avalable to
44 #configDialog.py so it can access all EditorWindow instaces
45 self.top.instanceDict=flist.inversedict
Steven M. Gava1d46e402002-03-27 08:40:46 +000046 self.recentFilesPath=os.path.join(idleConf.GetUserCfgDir(),
47 'recent-files.lst')
Kurt B. Kaiser889f8bf2002-07-06 04:22:25 +000048 self.break_set = False
David Scherer7aced172000-08-15 01:13:23 +000049 self.vbar = vbar = Scrollbar(top, name='vbar')
50 self.text_frame = text_frame = Frame(top)
Steven M. Gavadc72f482002-01-03 11:51:07 +000051 self.text = text = Text(text_frame, name='text', padx=5, wrap=None,
52 foreground=idleConf.GetHighlight(currentTheme,
53 'normal',fgBg='fg'),
54 background=idleConf.GetHighlight(currentTheme,
55 'normal',fgBg='bg'),
56 highlightcolor=idleConf.GetHighlight(currentTheme,
57 'hilite',fgBg='fg'),
58 highlightbackground=idleConf.GetHighlight(currentTheme,
59 'hilite',fgBg='bg'),
60 insertbackground=idleConf.GetHighlight(currentTheme,
61 'cursor',fgBg='fg'),
62 width=idleConf.GetOption('main','EditorWindow','width'),
63 height=idleConf.GetOption('main','EditorWindow','height') )
David Scherer7aced172000-08-15 01:13:23 +000064
65 self.createmenubar()
66 self.apply_bindings()
67
68 self.top.protocol("WM_DELETE_WINDOW", self.close)
69 self.top.bind("<<close-window>>", self.close_event)
70 text.bind("<<center-insert>>", self.center_insert_event)
71 text.bind("<<help>>", self.help_dialog)
72 text.bind("<<good-advice>>", self.good_advice)
Steven M. Gavaabdfc412001-08-11 07:46:26 +000073 text.bind("<<view-readme>>", self.view_readme)
David Scherer7aced172000-08-15 01:13:23 +000074 text.bind("<<python-docs>>", self.python_docs)
75 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +000076 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +000077 text.bind("<<open-module>>", self.open_module)
78 text.bind("<<do-nothing>>", lambda event: "break")
79 text.bind("<<select-all>>", self.select_all)
80 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +000081 text.bind("<<find>>", self.find_event)
82 text.bind("<<find-again>>", self.find_again_event)
83 text.bind("<<find-in-files>>", self.find_in_files_event)
84 text.bind("<<find-selection>>", self.find_selection_event)
85 text.bind("<<replace>>", self.replace_event)
86 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +000087 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +000088 text.bind("<<smart-backspace>>",self.smart_backspace_event)
89 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
90 text.bind("<<smart-indent>>",self.smart_indent_event)
91 text.bind("<<indent-region>>",self.indent_region_event)
92 text.bind("<<dedent-region>>",self.dedent_region_event)
93 text.bind("<<comment-region>>",self.comment_region_event)
94 text.bind("<<uncomment-region>>",self.uncomment_region_event)
95 text.bind("<<tabify-region>>",self.tabify_region_event)
96 text.bind("<<untabify-region>>",self.untabify_region_event)
97 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
98 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
99
David Scherer7aced172000-08-15 01:13:23 +0000100 if flist:
101 flist.inversedict[self] = key
102 if key:
103 flist.dict[key] = self
104 text.bind("<<open-new-window>>", self.flist.new_callback)
105 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
106 text.bind("<<open-class-browser>>", self.open_class_browser)
107 text.bind("<<open-path-browser>>", self.open_path_browser)
108
Steven M. Gava898a3652001-10-07 11:10:44 +0000109 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000110 vbar['command'] = text.yview
111 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000112 text['yscrollcommand'] = vbar.set
Steven M. Gavab1585412002-03-12 00:21:56 +0000113 fontWeight='normal'
114 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
115 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000116 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000117 idleConf.GetOption('main','EditorWindow','font-size'),
118 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000119 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
120 text.pack(side=TOP, fill=BOTH, expand=1)
121 text.focus_set()
122
123 self.per = per = self.Percolator(text)
124 if self.ispythonsource(filename):
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000125 self.color = color = self.ColorDelegator()
126 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000127 ##print "Initial colorizer"
128 else:
129 ##print "No initial colorizer"
130 self.color = None
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000131
132 self.undo = undo = self.UndoDelegator()
133 per.insertfilter(undo)
134 text.undo_block_start = undo.undo_block_start
135 text.undo_block_stop = undo.undo_block_stop
136 undo.set_saved_change_hook(self.saved_change_hook)
137
138 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000139 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000140 io.set_filename_change_hook(self.filename_change_hook)
141
Steven M. Gava1d46e402002-03-27 08:40:46 +0000142 #create the Recent Files submenu
143 self.menuRecentFiles=Menu(self.menubar)
144 self.menudict['file'].insert_cascade(3,label='Recent Files',
145 underline=0,menu=self.menuRecentFiles)
146 self.UpdateRecentFilesList()
David Scherer7aced172000-08-15 01:13:23 +0000147
David Scherer7aced172000-08-15 01:13:23 +0000148 if filename:
149 if os.path.exists(filename):
150 io.loadfile(filename)
151 else:
152 io.set_filename(filename)
David Scherer7aced172000-08-15 01:13:23 +0000153 self.saved_change_hook()
154
155 self.load_extensions()
156
157 menu = self.menudict.get('windows')
158 if menu:
159 end = menu.index("end")
160 if end is None:
161 end = -1
162 if end >= 0:
163 menu.add_separator()
164 end = end + 1
165 self.wmenu_end = end
166 WindowList.register_callback(self.postwindowsmenu)
167
168 # Some abstractions so IDLE extensions are cross-IDE
169 self.askyesno = tkMessageBox.askyesno
170 self.askinteger = tkSimpleDialog.askinteger
171 self.showerror = tkMessageBox.showerror
172
173 if self.extensions.has_key('AutoIndent'):
174 self.extensions['AutoIndent'].set_indentation_params(
175 self.ispythonsource(filename))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000176
David Scherer7aced172000-08-15 01:13:23 +0000177 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000178 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000179 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
180 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
181 self.status_bar.pack(side=BOTTOM, fill=X)
182 self.text.bind('<KeyRelease>', self.set_line_and_column)
183 self.text.bind('<ButtonRelease>', self.set_line_and_column)
184 self.text.after_idle(self.set_line_and_column)
185
186 def set_line_and_column(self, event=None):
187 line, column = string.split(self.text.index(INSERT), '.')
188 self.status_bar.set_label('column', 'Col: %s' % column)
189 self.status_bar.set_label('line', 'Ln: %s' % line)
190
191 def wakeup(self):
192 if self.top.wm_state() == "iconic":
193 self.top.wm_deiconify()
194 else:
195 self.top.tkraise()
196 self.text.focus_set()
197
198 menu_specs = [
199 ("file", "_File"),
200 ("edit", "_Edit"),
201 ("format", "F_ormat"),
202 ("run", "_Run"),
Steven M. Gava82c66822002-02-18 01:45:43 +0000203 ("settings", "_Settings"),
David Scherer7aced172000-08-15 01:13:23 +0000204 ("windows", "_Windows"),
205 ("help", "_Help"),
206 ]
207
208 def createmenubar(self):
209 mbar = self.menubar
210 self.menudict = menudict = {}
211 for name, label in self.menu_specs:
212 underline, label = prepstr(label)
213 menudict[name] = menu = Menu(mbar, name=name)
214 mbar.add_cascade(label=label, menu=menu, underline=underline)
215 self.fill_menus()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000216 #create the ExtraHelp menu, if required
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000217 self.ResetExtraHelpMenu()
David Scherer7aced172000-08-15 01:13:23 +0000218
219 def postwindowsmenu(self):
220 # Only called when Windows menu exists
221 # XXX Actually, this Just-In-Time updating interferes badly
222 # XXX with the tear-off feature. It would be better to update
223 # XXX all Windows menus whenever the list of windows changes.
224 menu = self.menudict['windows']
225 end = menu.index("end")
226 if end is None:
227 end = -1
228 if end > self.wmenu_end:
229 menu.delete(self.wmenu_end+1, end)
230 WindowList.add_windows_to_menu(menu)
231
232 rmenu = None
233
234 def right_menu_event(self, event):
235 self.text.tag_remove("sel", "1.0", "end")
236 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
237 if not self.rmenu:
238 self.make_rmenu()
239 rmenu = self.rmenu
240 self.event = event
241 iswin = sys.platform[:3] == 'win'
242 if iswin:
243 self.text.config(cursor="arrow")
244 rmenu.tk_popup(event.x_root, event.y_root)
245 if iswin:
246 self.text.config(cursor="ibeam")
247
248 rmenu_specs = [
249 # ("Label", "<<virtual-event>>"), ...
250 ("Close", "<<close-window>>"), # Example
251 ]
252
253 def make_rmenu(self):
254 rmenu = Menu(self.text, tearoff=0)
255 for label, eventname in self.rmenu_specs:
256 def command(text=self.text, eventname=eventname):
257 text.event_generate(eventname)
258 rmenu.add_command(label=label, command=command)
259 self.rmenu = rmenu
260
261 def about_dialog(self, event=None):
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000262 aboutDialog.AboutDialog(self.top,'About IDLEfork')
263
Steven M. Gava3b55a892001-11-21 05:56:26 +0000264 def config_dialog(self, event=None):
265 configDialog.ConfigDialog(self.top,'Settings')
266
David Scherer7aced172000-08-15 01:13:23 +0000267 def good_advice(self, event=None):
268 tkMessageBox.showinfo('Advice', "Don't Panic!", master=self.text)
269
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000270 def view_readme(self, event=None):
271 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
272 textView.TextViewer(self.top,'IDLEfork - README',fn)
273
David Scherer7aced172000-08-15 01:13:23 +0000274 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000275 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
276 textView.TextViewer(self.top,'Help',fn)
277
David Scherer7aced172000-08-15 01:13:23 +0000278 help_url = "http://www.python.org/doc/current/"
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000279 if sys.platform[:3] == "win":
280 fn = os.path.dirname(__file__)
Steven M. Gava931625d2002-04-22 00:38:26 +0000281 fn = os.path.join(fn, os.pardir, os.pardir, "pythlp.chm")
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000282 fn = os.path.normpath(fn)
283 if os.path.isfile(fn):
284 help_url = fn
Steven M. Gava931625d2002-04-22 00:38:26 +0000285 else:
286 fn = os.path.dirname(__file__)
287 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
288 fn = os.path.normpath(fn)
289 if os.path.isfile(fn):
290 help_url = fn
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000291 del fn
Steven M. Gava931625d2002-04-22 00:38:26 +0000292 def python_docs(self, event=None):
293 os.startfile(self.help_url)
294 else:
295 def python_docs(self, event=None):
296 self.display_docs(self.help_url)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000297
298 def display_docs(self, url):
299 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000300
301 def select_all(self, event=None):
302 self.text.tag_add("sel", "1.0", "end-1c")
303 self.text.mark_set("insert", "1.0")
304 self.text.see("insert")
305 return "break"
306
307 def remove_selection(self, event=None):
308 self.text.tag_remove("sel", "1.0", "end")
309 self.text.see("insert")
310
Steven M. Gavac5976402002-01-04 03:06:08 +0000311 def find_event(self, event):
312 SearchDialog.find(self.text)
313 return "break"
314
315 def find_again_event(self, event):
316 SearchDialog.find_again(self.text)
317 return "break"
318
319 def find_selection_event(self, event):
320 SearchDialog.find_selection(self.text)
321 return "break"
322
323 def find_in_files_event(self, event):
324 GrepDialog.grep(self.text, self.io, self.flist)
325 return "break"
326
327 def replace_event(self, event):
328 ReplaceDialog.replace(self.text)
329 return "break"
330
331 def goto_line_event(self, event):
332 text = self.text
333 lineno = tkSimpleDialog.askinteger("Goto",
334 "Go to line number:",parent=text)
335 if lineno is None:
336 return "break"
337 if lineno <= 0:
338 text.bell()
339 return "break"
340 text.mark_set("insert", "%d.0" % lineno)
341 text.see("insert")
342
David Scherer7aced172000-08-15 01:13:23 +0000343 def open_module(self, event=None):
344 # XXX Shouldn't this be in IOBinding or in FileList?
345 try:
346 name = self.text.get("sel.first", "sel.last")
347 except TclError:
348 name = ""
349 else:
350 name = string.strip(name)
351 if not name:
352 name = tkSimpleDialog.askstring("Module",
353 "Enter the name of a Python module\n"
354 "to search on sys.path and open:",
355 parent=self.text)
356 if name:
357 name = string.strip(name)
358 if not name:
359 return
360 # XXX Ought to support package syntax
361 # XXX Ought to insert current file's directory in front of path
362 try:
363 (f, file, (suffix, mode, type)) = imp.find_module(name)
364 except (NameError, ImportError), msg:
365 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
366 return
367 if type != imp.PY_SOURCE:
368 tkMessageBox.showerror("Unsupported type",
369 "%s is not a source module" % name, parent=self.text)
370 return
371 if f:
372 f.close()
373 if self.flist:
374 self.flist.open(file)
375 else:
376 self.io.loadfile(file)
377
378 def open_class_browser(self, event=None):
379 filename = self.io.filename
380 if not filename:
381 tkMessageBox.showerror(
382 "No filename",
383 "This buffer has no associated filename",
384 master=self.text)
385 self.text.focus_set()
386 return None
387 head, tail = os.path.split(filename)
388 base, ext = os.path.splitext(tail)
389 import ClassBrowser
390 ClassBrowser.ClassBrowser(self.flist, base, [head])
391
392 def open_path_browser(self, event=None):
393 import PathBrowser
394 PathBrowser.PathBrowser(self.flist)
395
396 def gotoline(self, lineno):
397 if lineno is not None and lineno > 0:
398 self.text.mark_set("insert", "%d.0" % lineno)
399 self.text.tag_remove("sel", "1.0", "end")
400 self.text.tag_add("sel", "insert", "insert +1l")
401 self.center()
402
403 def ispythonsource(self, filename):
404 if not filename:
405 return 1
406 base, ext = os.path.splitext(os.path.basename(filename))
407 if os.path.normcase(ext) in (".py", ".pyw"):
408 return 1
409 try:
410 f = open(filename)
411 line = f.readline()
412 f.close()
413 except IOError:
414 return 0
415 return line[:2] == '#!' and string.find(line, 'python') >= 0
416
417 def close_hook(self):
418 if self.flist:
419 self.flist.close_edit(self)
420
421 def set_close_hook(self, close_hook):
422 self.close_hook = close_hook
423
424 def filename_change_hook(self):
425 if self.flist:
426 self.flist.filename_changed_edit(self)
427 self.saved_change_hook()
428 if self.ispythonsource(self.io.filename):
429 self.addcolorizer()
430 else:
431 self.rmcolorizer()
432
433 def addcolorizer(self):
434 if self.color:
435 return
436 ##print "Add colorizer"
437 self.per.removefilter(self.undo)
438 self.color = self.ColorDelegator()
439 self.per.insertfilter(self.color)
440 self.per.insertfilter(self.undo)
441
442 def rmcolorizer(self):
443 if not self.color:
444 return
445 ##print "Remove colorizer"
446 self.per.removefilter(self.undo)
447 self.per.removefilter(self.color)
448 self.color = None
449 self.per.insertfilter(self.undo)
Steven M. Gavab77d3432002-03-02 07:16:21 +0000450
451 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000452 "Update the colour theme if it is changed"
453 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000454 if self.color:
455 self.color = self.ColorDelegator()
456 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000457
Steven M. Gavab1585412002-03-12 00:21:56 +0000458 def ResetFont(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000459 "Update the text widgets' font if it is changed"
460 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000461 fontWeight='normal'
462 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
463 fontWeight='bold'
464 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
465 idleConf.GetOption('main','EditorWindow','font-size'),
466 fontWeight))
467
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000468 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000469 "Update the keybindings if they are changed"
470 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000471 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
472 keydefs = self.Bindings.default_keydefs
473 for event, keylist in keydefs.items():
474 self.text.event_delete(event)
475 self.apply_bindings()
476 #update menu accelerators
477 menuEventDict={}
478 for menu in self.Bindings.menudefs:
479 menuEventDict[menu[0]]={}
480 for item in menu[1]:
481 if item:
482 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
483 for menubarItem in self.menudict.keys():
484 menu=self.menudict[menubarItem]
485 end=menu.index(END)+1
486 for index in range(0,end):
487 if menu.type(index)=='command':
488 accel=menu.entrycget(index,'accelerator')
489 if accel:
490 itemName=menu.entrycget(index,'label')
491 event=''
492 if menuEventDict.has_key(menubarItem):
493 if menuEventDict[menubarItem].has_key(itemName):
494 event=menuEventDict[menubarItem][itemName]
495 if event:
496 #print 'accel was:',accel
497 accel=get_accelerator(keydefs, event)
498 menu.entryconfig(index,accelerator=accel)
499 #print 'accel now:',accel,'\n'
500
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000501 def ResetExtraHelpMenu(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000502 "Load or update the Extra Help menu if required"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000503 menuList=idleConf.GetAllExtraHelpSourcesList()
504 helpMenu=self.menudict['help']
505 cascadeIndex=helpMenu.index(END)-1
506 if menuList:
507 if not hasattr(self,'menuExtraHelp'):
508 self.menuExtraHelp=Menu(self.menubar)
509 helpMenu.insert_cascade(cascadeIndex,label='Extra Help',
510 underline=1,menu=self.menuExtraHelp)
511 self.menuExtraHelp.delete(1,END)
512 for menuItem in menuList:
513 self.menuExtraHelp.add_command(label=menuItem[0],
Steven M. Gava1d46e402002-03-27 08:40:46 +0000514 command=self.__DisplayExtraHelpCallback(menuItem[1]))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000515 else: #no extra help items
516 if hasattr(self,'menuExtraHelp'):
517 helpMenu.delete(cascadeIndex-1)
518 del(self.menuExtraHelp)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000519
520 def __DisplayExtraHelpCallback(self,helpFile):
521 def DisplayExtraHelp(helpFile=helpFile):
522 self.display_docs(helpFile)
523 return DisplayExtraHelp
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000524
Steven M. Gava1d46e402002-03-27 08:40:46 +0000525 def UpdateRecentFilesList(self,newFile=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000526 "Load or update the recent files list, and menu if required"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000527 rfList=[]
528 if os.path.exists(self.recentFilesPath):
529 RFfile=open(self.recentFilesPath,'r')
530 try:
531 rfList=RFfile.readlines()
532 finally:
533 RFfile.close()
534 if newFile:
535 newFile=os.path.abspath(newFile)+'\n'
536 if newFile in rfList:
537 rfList.remove(newFile)
538 rfList.insert(0,newFile)
539 rfList=self.__CleanRecentFiles(rfList)
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000540 #print self.flist.inversedict
541 #print self.top.instanceDict
542 #print self
Steven M. Gava1d46e402002-03-27 08:40:46 +0000543 if rfList:
544 for instance in self.top.instanceDict.keys():
545 instance.menuRecentFiles.delete(1,END)
546 for file in rfList:
547 fileName=file[0:-1]
548 instance.menuRecentFiles.add_command(label=fileName,
549 command=instance.__RecentFileCallback(fileName))
550
551 def __CleanRecentFiles(self,rfList):
552 origRfList=rfList[:]
553 count=0
554 nonFiles=[]
555 for path in rfList:
556 if not os.path.exists(path[0:-1]):
557 nonFiles.append(count)
558 count=count+1
559 if nonFiles:
560 nonFiles.reverse()
561 for index in nonFiles:
562 del(rfList[index])
563 if len(rfList)>19:
564 rfList=rfList[0:19]
565 #if rfList != origRfList:
566 RFfile=open(self.recentFilesPath,'w')
567 try:
568 RFfile.writelines(rfList)
569 finally:
570 RFfile.close()
571 return rfList
572
573 def __RecentFileCallback(self,fileName):
574 def OpenRecentFile(fileName=fileName):
575 self.io.open(editFile=fileName)
576 return OpenRecentFile
577
David Scherer7aced172000-08-15 01:13:23 +0000578 def saved_change_hook(self):
579 short = self.short_title()
580 long = self.long_title()
581 if short and long:
582 title = short + " - " + long
583 elif short:
584 title = short
585 elif long:
586 title = long
587 else:
588 title = "Untitled"
589 icon = short or long or title
590 if not self.get_saved():
591 title = "*%s*" % title
592 icon = "*%s" % icon
Kurt B. Kaiser889f8bf2002-07-06 04:22:25 +0000593 if self.break_set:
594 shell = self.flist.pyshell
595 shell.interp.debugger.clear_file_breaks(self)
David Scherer7aced172000-08-15 01:13:23 +0000596 self.top.wm_title(title)
597 self.top.wm_iconname(icon)
598
599 def get_saved(self):
600 return self.undo.get_saved()
601
602 def set_saved(self, flag):
603 self.undo.set_saved(flag)
604
605 def reset_undo(self):
606 self.undo.reset_undo()
607
608 def short_title(self):
609 filename = self.io.filename
610 if filename:
611 filename = os.path.basename(filename)
612 return filename
613
614 def long_title(self):
615 return self.io.filename or ""
616
617 def center_insert_event(self, event):
618 self.center()
619
620 def center(self, mark="insert"):
621 text = self.text
622 top, bot = self.getwindowlines()
623 lineno = self.getlineno(mark)
624 height = bot - top
625 newtop = max(1, lineno - height/2)
626 text.yview(float(newtop))
627
628 def getwindowlines(self):
629 text = self.text
630 top = self.getlineno("@0,0")
631 bot = self.getlineno("@0,65535")
632 if top == bot and text.winfo_height() == 1:
633 # Geometry manager hasn't run yet
634 height = int(text['height'])
635 bot = top + height - 1
636 return top, bot
637
638 def getlineno(self, mark="insert"):
639 text = self.text
640 return int(float(text.index(mark)))
641
642 def close_event(self, event):
643 self.close()
644
645 def maybesave(self):
646 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000647 if not self.get_saved():
648 if self.top.state()!='normal':
649 self.top.deiconify()
650 self.top.lower()
651 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000652 return self.io.maybesave()
653
654 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000655 reply = self.maybesave()
656 if reply != "cancel":
657 self._close()
658 return reply
659
660 def _close(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000661 #print self.io.filename
Steven M. Gava1d46e402002-03-27 08:40:46 +0000662 if self.io.filename:
663 self.UpdateRecentFilesList(newFile=self.io.filename)
Kurt B. Kaiser889f8bf2002-07-06 04:22:25 +0000664 if self.break_set:
665 shell = self.flist.pyshell
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000666 shell.interp.debugger.clear_file_breaks(self)
David Scherer7aced172000-08-15 01:13:23 +0000667 WindowList.unregister_callback(self.postwindowsmenu)
668 if self.close_hook:
669 self.close_hook()
670 self.flist = None
671 colorizing = 0
672 self.unload_extensions()
673 self.io.close(); self.io = None
674 self.undo = None # XXX
675 if self.color:
676 colorizing = self.color.colorizing
677 doh = colorizing and self.top
678 self.color.close(doh) # Cancel colorization
679 self.text = None
680 self.vars = None
681 self.per.close(); self.per = None
682 if not colorizing:
683 self.top.destroy()
684
685 def load_extensions(self):
686 self.extensions = {}
687 self.load_standard_extensions()
688
689 def unload_extensions(self):
690 for ins in self.extensions.values():
691 if hasattr(ins, "close"):
692 ins.close()
693 self.extensions = {}
694
695 def load_standard_extensions(self):
696 for name in self.get_standard_extension_names():
697 try:
698 self.load_extension(name)
699 except:
700 print "Failed to load extension", `name`
701 import traceback
702 traceback.print_exc()
703
704 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000705 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000706
707 def load_extension(self, name):
708 mod = __import__(name, globals(), locals(), [])
709 cls = getattr(mod, name)
710 ins = cls(self)
711 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000712 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000713 if keydefs:
714 self.apply_bindings(keydefs)
715 for vevent in keydefs.keys():
716 methodname = string.replace(vevent, "-", "_")
717 while methodname[:1] == '<':
718 methodname = methodname[1:]
719 while methodname[-1:] == '>':
720 methodname = methodname[:-1]
721 methodname = methodname + "_event"
722 if hasattr(ins, methodname):
723 self.text.bind(vevent, getattr(ins, methodname))
724 if hasattr(ins, "menudefs"):
725 self.fill_menus(ins.menudefs, keydefs)
726 return ins
727
728 def apply_bindings(self, keydefs=None):
729 if keydefs is None:
730 keydefs = self.Bindings.default_keydefs
731 text = self.text
732 text.keydefs = keydefs
733 for event, keylist in keydefs.items():
734 if keylist:
735 apply(text.event_add, (event,) + tuple(keylist))
736
737 def fill_menus(self, defs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000738 """Add appropriate entries to the menus and submenus
739
740 Menus that are absent or None in self.menudict are ignored.
741 """
David Scherer7aced172000-08-15 01:13:23 +0000742 if defs is None:
743 defs = self.Bindings.menudefs
744 if keydefs is None:
745 keydefs = self.Bindings.default_keydefs
746 menudict = self.menudict
747 text = self.text
748 for mname, itemlist in defs:
749 menu = menudict.get(mname)
750 if not menu:
751 continue
752 for item in itemlist:
753 if not item:
754 menu.add_separator()
755 else:
756 label, event = item
757 checkbutton = (label[:1] == '!')
758 if checkbutton:
759 label = label[1:]
760 underline, label = prepstr(label)
761 accelerator = get_accelerator(keydefs, event)
762 def command(text=text, event=event):
763 text.event_generate(event)
764 if checkbutton:
765 var = self.getrawvar(event, BooleanVar)
766 menu.add_checkbutton(label=label, underline=underline,
767 command=command, accelerator=accelerator,
768 variable=var)
769 else:
770 menu.add_command(label=label, underline=underline,
771 command=command, accelerator=accelerator)
772
773 def getvar(self, name):
774 var = self.getrawvar(name)
775 if var:
776 return var.get()
777
778 def setvar(self, name, value, vartype=None):
779 var = self.getrawvar(name, vartype)
780 if var:
781 var.set(value)
782
783 def getrawvar(self, name, vartype=None):
784 var = self.vars.get(name)
785 if not var and vartype:
786 self.vars[name] = var = vartype(self.text)
787 return var
788
789 # Tk implementations of "virtual text methods" -- each platform
790 # reusing IDLE's support code needs to define these for its GUI's
791 # flavor of widget.
792
793 # Is character at text_index in a Python string? Return 0 for
794 # "guaranteed no", true for anything else. This info is expensive
795 # to compute ab initio, but is probably already known by the
796 # platform's colorizer.
797
798 def is_char_in_string(self, text_index):
799 if self.color:
800 # Return true iff colorizer hasn't (re)gotten this far
801 # yet, or the character is tagged as being in a string
802 return self.text.tag_prevrange("TODO", text_index) or \
803 "STRING" in self.text.tag_names(text_index)
804 else:
805 # The colorizer is missing: assume the worst
806 return 1
807
808 # If a selection is defined in the text widget, return (start,
809 # end) as Tkinter text indices, otherwise return (None, None)
810 def get_selection_indices(self):
811 try:
812 first = self.text.index("sel.first")
813 last = self.text.index("sel.last")
814 return first, last
815 except TclError:
816 return None, None
817
818 # Return the text widget's current view of what a tab stop means
819 # (equivalent width in spaces).
820
821 def get_tabwidth(self):
822 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
823 return int(current)
824
825 # Set the text widget's current view of what a tab stop means.
826
827 def set_tabwidth(self, newtabwidth):
828 text = self.text
829 if self.get_tabwidth() != newtabwidth:
830 pixels = text.tk.call("font", "measure", text["font"],
831 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000832 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000833 text.configure(tabs=pixels)
834
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000835### begin autoindent code ###
836
837 # usetabs true -> literal tab characters are used by indent and
838 # dedent cmds, possibly mixed with spaces if
839 # indentwidth is not a multiple of tabwidth
840 # false -> tab characters are converted to spaces by indent
841 # and dedent cmds, and ditto TAB keystrokes
842 # indentwidth is the number of characters per logical indent level.
843 # tabwidth is the display width of a literal tab character.
844 # CAUTION: telling Tk to use anything other than its default
845 # tab setting causes it to use an entirely different tabbing algorithm,
846 # treating tab stops as fixed distances from the left margin.
847 # Nobody expects this, so for now tabwidth should never be changed.
848 usetabs = 0
849 indentwidth = 4
850 tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
851
852 # If context_use_ps1 is true, parsing searches back for a ps1 line;
853 # else searches for a popular (if, def, ...) Python stmt.
854 context_use_ps1 = 0
855
856 # When searching backwards for a reliable place to begin parsing,
857 # first start num_context_lines[0] lines back, then
858 # num_context_lines[1] lines back if that didn't work, and so on.
859 # The last value should be huge (larger than the # of lines in a
860 # conceivable file).
861 # Making the initial values larger slows things down more often.
862 num_context_lines = 50, 500, 5000000
863
864 def config(self, **options):
865 for key, value in options.items():
866 if key == 'usetabs':
867 self.usetabs = value
868 elif key == 'indentwidth':
869 self.indentwidth = value
870 elif key == 'tabwidth':
871 self.tabwidth = value
872 elif key == 'context_use_ps1':
873 self.context_use_ps1 = value
874 else:
875 raise KeyError, "bad option name: %s" % `key`
876
877 # If ispythonsource and guess are true, guess a good value for
878 # indentwidth based on file content (if possible), and if
879 # indentwidth != tabwidth set usetabs false.
880 # In any case, adjust the Text widget's view of what a tab
881 # character means.
882
883 def set_indentation_params(self, ispythonsource, guess=1):
884 if guess and ispythonsource:
885 i = self.guess_indent()
886 if 2 <= i <= 8:
887 self.indentwidth = i
888 if self.indentwidth != self.tabwidth:
889 self.usetabs = 0
890
891 self.set_tabwidth(self.tabwidth)
892
893 def smart_backspace_event(self, event):
894 text = self.text
895 first, last = self.get_selection_indices()
896 if first and last:
897 text.delete(first, last)
898 text.mark_set("insert", first)
899 return "break"
900 # Delete whitespace left, until hitting a real char or closest
901 # preceding virtual tab stop.
902 chars = text.get("insert linestart", "insert")
903 if chars == '':
904 if text.compare("insert", ">", "1.0"):
905 # easy: delete preceding newline
906 text.delete("insert-1c")
907 else:
908 text.bell() # at start of buffer
909 return "break"
910 if chars[-1] not in " \t":
911 # easy: delete preceding real char
912 text.delete("insert-1c")
913 return "break"
914 # Ick. It may require *inserting* spaces if we back up over a
915 # tab character! This is written to be clear, not fast.
916 expand, tabwidth = string.expandtabs, self.tabwidth
917 have = len(expand(chars, tabwidth))
918 assert have > 0
919 want = ((have - 1) // self.indentwidth) * self.indentwidth
920 ncharsdeleted = 0
921 while 1:
922 chars = chars[:-1]
923 ncharsdeleted = ncharsdeleted + 1
924 have = len(expand(chars, tabwidth))
925 if have <= want or chars[-1] not in " \t":
926 break
927 text.undo_block_start()
928 text.delete("insert-%dc" % ncharsdeleted, "insert")
929 if have < want:
930 text.insert("insert", ' ' * (want - have))
931 text.undo_block_stop()
932 return "break"
933
934 def smart_indent_event(self, event):
935 # if intraline selection:
936 # delete it
937 # elif multiline selection:
938 # do indent-region & return
939 # indent one level
940 text = self.text
941 first, last = self.get_selection_indices()
942 text.undo_block_start()
943 try:
944 if first and last:
945 if index2line(first) != index2line(last):
946 return self.indent_region_event(event)
947 text.delete(first, last)
948 text.mark_set("insert", first)
949 prefix = text.get("insert linestart", "insert")
950 raw, effective = classifyws(prefix, self.tabwidth)
951 if raw == len(prefix):
952 # only whitespace to the left
953 self.reindent_to(effective + self.indentwidth)
954 else:
955 if self.usetabs:
956 pad = '\t'
957 else:
958 effective = len(string.expandtabs(prefix,
959 self.tabwidth))
960 n = self.indentwidth
961 pad = ' ' * (n - effective % n)
962 text.insert("insert", pad)
963 text.see("insert")
964 return "break"
965 finally:
966 text.undo_block_stop()
967
968 def newline_and_indent_event(self, event):
969 text = self.text
970 first, last = self.get_selection_indices()
971 text.undo_block_start()
972 try:
973 if first and last:
974 text.delete(first, last)
975 text.mark_set("insert", first)
976 line = text.get("insert linestart", "insert")
977 i, n = 0, len(line)
978 while i < n and line[i] in " \t":
979 i = i+1
980 if i == n:
981 # the cursor is in or at leading indentation; just inject
982 # an empty line at the start
983 text.insert("insert linestart", '\n')
984 return "break"
985 indent = line[:i]
986 # strip whitespace before insert point
987 i = 0
988 while line and line[-1] in " \t":
989 line = line[:-1]
990 i = i+1
991 if i:
992 text.delete("insert - %d chars" % i, "insert")
993 # strip whitespace after insert point
994 while text.get("insert") in " \t":
995 text.delete("insert")
996 # start new line
997 text.insert("insert", '\n')
998
999 # adjust indentation for continuations and block
1000 # open/close first need to find the last stmt
1001 lno = index2line(text.index('insert'))
1002 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1003 for context in self.num_context_lines:
1004 startat = max(lno - context, 1)
1005 startatindex = `startat` + ".0"
1006 rawtext = text.get(startatindex, "insert")
1007 y.set_str(rawtext)
1008 bod = y.find_good_parse_start(
1009 self.context_use_ps1,
1010 self._build_char_in_string_func(startatindex))
1011 if bod is not None or startat == 1:
1012 break
1013 y.set_lo(bod or 0)
1014 c = y.get_continuation_type()
1015 if c != PyParse.C_NONE:
1016 # The current stmt hasn't ended yet.
1017 if c == PyParse.C_STRING:
1018 # inside a string; just mimic the current indent
1019 text.insert("insert", indent)
1020 elif c == PyParse.C_BRACKET:
1021 # line up with the first (if any) element of the
1022 # last open bracket structure; else indent one
1023 # level beyond the indent of the line with the
1024 # last open bracket
1025 self.reindent_to(y.compute_bracket_indent())
1026 elif c == PyParse.C_BACKSLASH:
1027 # if more than one line in this stmt already, just
1028 # mimic the current indent; else if initial line
1029 # has a start on an assignment stmt, indent to
1030 # beyond leftmost =; else to beyond first chunk of
1031 # non-whitespace on initial line
1032 if y.get_num_lines_in_stmt() > 1:
1033 text.insert("insert", indent)
1034 else:
1035 self.reindent_to(y.compute_backslash_indent())
1036 else:
1037 assert 0, "bogus continuation type " + `c`
1038 return "break"
1039
1040 # This line starts a brand new stmt; indent relative to
1041 # indentation of initial line of closest preceding
1042 # interesting stmt.
1043 indent = y.get_base_indent_string()
1044 text.insert("insert", indent)
1045 if y.is_block_opener():
1046 self.smart_indent_event(event)
1047 elif indent and y.is_block_closer():
1048 self.smart_backspace_event(event)
1049 return "break"
1050 finally:
1051 text.see("insert")
1052 text.undo_block_stop()
1053
1054 auto_indent = newline_and_indent_event
1055
1056 # Our editwin provides a is_char_in_string function that works
1057 # with a Tk text index, but PyParse only knows about offsets into
1058 # a string. This builds a function for PyParse that accepts an
1059 # offset.
1060
1061 def _build_char_in_string_func(self, startindex):
1062 def inner(offset, _startindex=startindex,
1063 _icis=self.is_char_in_string):
1064 return _icis(_startindex + "+%dc" % offset)
1065 return inner
1066
1067 def indent_region_event(self, event):
1068 head, tail, chars, lines = self.get_region()
1069 for pos in range(len(lines)):
1070 line = lines[pos]
1071 if line:
1072 raw, effective = classifyws(line, self.tabwidth)
1073 effective = effective + self.indentwidth
1074 lines[pos] = self._make_blanks(effective) + line[raw:]
1075 self.set_region(head, tail, chars, lines)
1076 return "break"
1077
1078 def dedent_region_event(self, event):
1079 head, tail, chars, lines = self.get_region()
1080 for pos in range(len(lines)):
1081 line = lines[pos]
1082 if line:
1083 raw, effective = classifyws(line, self.tabwidth)
1084 effective = max(effective - self.indentwidth, 0)
1085 lines[pos] = self._make_blanks(effective) + line[raw:]
1086 self.set_region(head, tail, chars, lines)
1087 return "break"
1088
1089 def comment_region_event(self, event):
1090 head, tail, chars, lines = self.get_region()
1091 for pos in range(len(lines) - 1):
1092 line = lines[pos]
1093 lines[pos] = '##' + line
1094 self.set_region(head, tail, chars, lines)
1095
1096 def uncomment_region_event(self, event):
1097 head, tail, chars, lines = self.get_region()
1098 for pos in range(len(lines)):
1099 line = lines[pos]
1100 if not line:
1101 continue
1102 if line[:2] == '##':
1103 line = line[2:]
1104 elif line[:1] == '#':
1105 line = line[1:]
1106 lines[pos] = line
1107 self.set_region(head, tail, chars, lines)
1108
1109 def tabify_region_event(self, event):
1110 head, tail, chars, lines = self.get_region()
1111 tabwidth = self._asktabwidth()
1112 for pos in range(len(lines)):
1113 line = lines[pos]
1114 if line:
1115 raw, effective = classifyws(line, tabwidth)
1116 ntabs, nspaces = divmod(effective, tabwidth)
1117 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1118 self.set_region(head, tail, chars, lines)
1119
1120 def untabify_region_event(self, event):
1121 head, tail, chars, lines = self.get_region()
1122 tabwidth = self._asktabwidth()
1123 for pos in range(len(lines)):
1124 lines[pos] = string.expandtabs(lines[pos], tabwidth)
1125 self.set_region(head, tail, chars, lines)
1126
1127 def toggle_tabs_event(self, event):
1128 if self.askyesno(
1129 "Toggle tabs",
1130 "Turn tabs " + ("on", "off")[self.usetabs] + "?",
1131 parent=self.text):
1132 self.usetabs = not self.usetabs
1133 return "break"
1134
1135 # XXX this isn't bound to anything -- see class tabwidth comments
1136 def change_tabwidth_event(self, event):
1137 new = self._asktabwidth()
1138 if new != self.tabwidth:
1139 self.tabwidth = new
1140 self.set_indentation_params(0, guess=0)
1141 return "break"
1142
1143 def change_indentwidth_event(self, event):
1144 new = self.askinteger(
1145 "Indent width",
1146 "New indent width (2-16)",
1147 parent=self.text,
1148 initialvalue=self.indentwidth,
1149 minvalue=2,
1150 maxvalue=16)
1151 if new and new != self.indentwidth:
1152 self.indentwidth = new
1153 return "break"
1154
1155 def get_region(self):
1156 text = self.text
1157 first, last = self.get_selection_indices()
1158 if first and last:
1159 head = text.index(first + " linestart")
1160 tail = text.index(last + "-1c lineend +1c")
1161 else:
1162 head = text.index("insert linestart")
1163 tail = text.index("insert lineend +1c")
1164 chars = text.get(head, tail)
1165 lines = string.split(chars, "\n")
1166 return head, tail, chars, lines
1167
1168 def set_region(self, head, tail, chars, lines):
1169 text = self.text
1170 newchars = string.join(lines, "\n")
1171 if newchars == chars:
1172 text.bell()
1173 return
1174 text.tag_remove("sel", "1.0", "end")
1175 text.mark_set("insert", head)
1176 text.undo_block_start()
1177 text.delete(head, tail)
1178 text.insert(head, newchars)
1179 text.undo_block_stop()
1180 text.tag_add("sel", head, "insert")
1181
1182 # Make string that displays as n leading blanks.
1183
1184 def _make_blanks(self, n):
1185 if self.usetabs:
1186 ntabs, nspaces = divmod(n, self.tabwidth)
1187 return '\t' * ntabs + ' ' * nspaces
1188 else:
1189 return ' ' * n
1190
1191 # Delete from beginning of line to insert point, then reinsert
1192 # column logical (meaning use tabs if appropriate) spaces.
1193
1194 def reindent_to(self, column):
1195 text = self.text
1196 text.undo_block_start()
1197 if text.compare("insert linestart", "!=", "insert"):
1198 text.delete("insert linestart", "insert")
1199 if column:
1200 text.insert("insert", self._make_blanks(column))
1201 text.undo_block_stop()
1202
1203 def _asktabwidth(self):
1204 return self.askinteger(
1205 "Tab width",
1206 "Spaces per tab? (2-16)",
1207 parent=self.text,
1208 initialvalue=self.indentwidth,
1209 minvalue=2,
1210 maxvalue=16) or self.tabwidth
1211
1212 # Guess indentwidth from text content.
1213 # Return guessed indentwidth. This should not be believed unless
1214 # it's in a reasonable range (e.g., it will be 0 if no indented
1215 # blocks are found).
1216
1217 def guess_indent(self):
1218 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1219 if opener and indented:
1220 raw, indentsmall = classifyws(opener, self.tabwidth)
1221 raw, indentlarge = classifyws(indented, self.tabwidth)
1222 else:
1223 indentsmall = indentlarge = 0
1224 return indentlarge - indentsmall
1225
1226# "line.col" -> line, as an int
1227def index2line(index):
1228 return int(float(index))
1229
1230# Look at the leading whitespace in s.
1231# Return pair (# of leading ws characters,
1232# effective # of leading blanks after expanding
1233# tabs to width tabwidth)
1234
1235def classifyws(s, tabwidth):
1236 raw = effective = 0
1237 for ch in s:
1238 if ch == ' ':
1239 raw = raw + 1
1240 effective = effective + 1
1241 elif ch == '\t':
1242 raw = raw + 1
1243 effective = (effective // tabwidth + 1) * tabwidth
1244 else:
1245 break
1246 return raw, effective
1247
1248import tokenize
1249_tokenize = tokenize
1250del tokenize
1251
1252class IndentSearcher:
1253
1254 # .run() chews over the Text widget, looking for a block opener
1255 # and the stmt following it. Returns a pair,
1256 # (line containing block opener, line containing stmt)
1257 # Either or both may be None.
1258
1259 def __init__(self, text, tabwidth):
1260 self.text = text
1261 self.tabwidth = tabwidth
1262 self.i = self.finished = 0
1263 self.blkopenline = self.indentedline = None
1264
1265 def readline(self):
1266 if self.finished:
1267 return ""
1268 i = self.i = self.i + 1
1269 mark = `i` + ".0"
1270 if self.text.compare(mark, ">=", "end"):
1271 return ""
1272 return self.text.get(mark, mark + " lineend+1c")
1273
1274 def tokeneater(self, type, token, start, end, line,
1275 INDENT=_tokenize.INDENT,
1276 NAME=_tokenize.NAME,
1277 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1278 if self.finished:
1279 pass
1280 elif type == NAME and token in OPENERS:
1281 self.blkopenline = line
1282 elif type == INDENT and self.blkopenline:
1283 self.indentedline = line
1284 self.finished = 1
1285
1286 def run(self):
1287 save_tabsize = _tokenize.tabsize
1288 _tokenize.tabsize = self.tabwidth
1289 try:
1290 try:
1291 _tokenize.tokenize(self.readline, self.tokeneater)
1292 except _tokenize.TokenError:
1293 # since we cut off the tokenizer early, we can trigger
1294 # spurious errors
1295 pass
1296 finally:
1297 _tokenize.tabsize = save_tabsize
1298 return self.blkopenline, self.indentedline
1299
1300### end autoindent code ###
1301
David Scherer7aced172000-08-15 01:13:23 +00001302def prepstr(s):
1303 # Helper to extract the underscore from a string, e.g.
1304 # prepstr("Co_py") returns (2, "Copy").
1305 i = string.find(s, '_')
1306 if i >= 0:
1307 s = s[:i] + s[i+1:]
1308 return i, s
1309
1310
1311keynames = {
1312 'bracketleft': '[',
1313 'bracketright': ']',
1314 'slash': '/',
1315}
1316
1317def get_accelerator(keydefs, event):
1318 keylist = keydefs.get(event)
1319 if not keylist:
1320 return ""
1321 s = keylist[0]
1322 s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
1323 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1324 s = re.sub("Key-", "", s)
1325 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1326 s = re.sub("Control-", "Ctrl-", s)
1327 s = re.sub("-", "+", s)
1328 s = re.sub("><", " ", s)
1329 s = re.sub("<", "", s)
1330 s = re.sub(">", "", s)
1331 return s
1332
1333
1334def fixwordbreaks(root):
1335 # Make sure that Tk's double-click and next/previous word
1336 # operations use our definition of a word (i.e. an identifier)
1337 tk = root.tk
1338 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1339 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1340 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1341
1342
1343def test():
1344 root = Tk()
1345 fixwordbreaks(root)
1346 root.withdraw()
1347 if sys.argv[1:]:
1348 filename = sys.argv[1]
1349 else:
1350 filename = None
1351 edit = EditorWindow(root=root, filename=filename)
1352 edit.set_close_hook(root.quit)
1353 root.mainloop()
1354 root.destroy()
1355
1356if __name__ == '__main__':
1357 test()