blob: 2dce3372c19d5677f46dd7a20c025f6fcc620da2 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001# changes by dscherer@cmu.edu
2# - created format and run menus
3# - added silly advice dialog (apologies to Douglas Adams)
4# - made Python Documentation work on Windows (requires win32api to
5# do a ShellExecute(); other ways of starting a web browser are awkward)
6
7import sys
8import os
9import string
10import re
11import imp
12from Tkinter import *
13import tkSimpleDialog
14import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +000015
16import webbrowser
David Scherer7aced172000-08-15 01:13:23 +000017import idlever
18import WindowList
19from IdleConf import idleconf
20
21# The default tab setting for a Text widget, in average-width characters.
22TK_TABWIDTH_DEFAULT = 8
23
24# File menu
25
26#$ event <<open-module>>
27#$ win <Alt-m>
28#$ unix <Control-x><Control-m>
29
30#$ event <<open-class-browser>>
31#$ win <Alt-c>
32#$ unix <Control-x><Control-b>
33
34#$ event <<open-path-browser>>
35
36#$ event <<close-window>>
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +000037
David Scherer7aced172000-08-15 01:13:23 +000038#$ unix <Control-x><Control-0>
39#$ unix <Control-x><Key-0>
40#$ win <Alt-F4>
41
42# Edit menu
43
44#$ event <<Copy>>
45#$ win <Control-c>
46#$ unix <Alt-w>
47
48#$ event <<Cut>>
49#$ win <Control-x>
50#$ unix <Control-w>
51
52#$ event <<Paste>>
53#$ win <Control-v>
54#$ unix <Control-y>
55
56#$ event <<select-all>>
57#$ win <Alt-a>
58#$ unix <Alt-a>
59
60# Help menu
61
62#$ event <<help>>
63#$ win <F1>
64#$ unix <F1>
65
66#$ event <<about-idle>>
67
68# Events without menu entries
69
70#$ event <<remove-selection>>
71#$ win <Escape>
72
73#$ event <<center-insert>>
74#$ win <Control-l>
75#$ unix <Control-l>
76
77#$ event <<do-nothing>>
78#$ unix <Control-x>
79
80
Steven M. Gava4d712a42001-07-19 04:49:13 +000081about_title = "About IDLEfork"
David Scherer7aced172000-08-15 01:13:23 +000082about_text = """\
Steven M. Gava4d712a42001-07-19 04:49:13 +000083IDLEfork %s
David Scherer7aced172000-08-15 01:13:23 +000084
Steven M. Gava4d712a42001-07-19 04:49:13 +000085IDLE is an Integrated DeveLopment Environment for Python \
86by Guido van Rossum.
David Scherer7aced172000-08-15 01:13:23 +000087
Steven M. Gavaba247a62001-07-21 09:59:58 +000088IDLEfork is an official experimental development version of IDLE. \
Steven M. Gava4d712a42001-07-19 04:49:13 +000089Succesful new features in IDLEfork will be mereged back in to stable IDLE.
David Scherer7aced172000-08-15 01:13:23 +000090
Steven M. Gava4d712a42001-07-19 04:49:13 +000091This version of IDLEfork is based on the work in stable IDLE version %s, \
Steven M. Gavaba247a62001-07-21 09:59:58 +000092IDLE fork 0.7.1 released by David Scherer, and the VPython idle fork.
Steven M. Gava4d712a42001-07-19 04:49:13 +000093
Steven M. Gavaba247a62001-07-21 09:59:58 +000094See README.txt and NEWS.txt for more details on this verion of IDLEfork.
Steven M. Gava4d712a42001-07-19 04:49:13 +000095
96WARNING: IDLEfork is at this stage alpha quality software, expect things \
97to be broken.
98
99""" % (idlever.IDLEFORK_VERSION, idlever.IDLE_VERSION)
David Scherer7aced172000-08-15 01:13:23 +0000100
101class EditorWindow:
102
103 from Percolator import Percolator
104 from ColorDelegator import ColorDelegator
105 from UndoDelegator import UndoDelegator
106 from IOBinding import IOBinding
107 import Bindings
108 from Tkinter import Toplevel
109 from MultiStatusBar import MultiStatusBar
110
111 about_title = about_title
112 about_text = about_text
113
114 vars = {}
115
116 def __init__(self, flist=None, filename=None, key=None, root=None):
117 edconf = idleconf.getsection('EditorWindow')
118 coconf = idleconf.getsection('Colors')
119 self.flist = flist
120 root = root or flist.root
121 self.root = root
122 if flist:
123 self.vars = flist.vars
124 self.menubar = Menu(root)
125 self.top = top = self.Toplevel(root, menu=self.menubar)
126 self.vbar = vbar = Scrollbar(top, name='vbar')
127 self.text_frame = text_frame = Frame(top)
128 self.text = text = Text(text_frame, name='text', padx=5,
129 foreground=coconf.getdef('normal-foreground'),
130 background=coconf.getdef('normal-background'),
131 highlightcolor=coconf.getdef('hilite-foreground'),
132 highlightbackground=coconf.getdef('hilite-background'),
133 insertbackground=coconf.getdef('cursor-background'),
134 width=edconf.getint('width'),
135 height=edconf.getint('height'),
136 wrap="none")
137
138 self.createmenubar()
139 self.apply_bindings()
140
141 self.top.protocol("WM_DELETE_WINDOW", self.close)
142 self.top.bind("<<close-window>>", self.close_event)
143 text.bind("<<center-insert>>", self.center_insert_event)
144 text.bind("<<help>>", self.help_dialog)
145 text.bind("<<good-advice>>", self.good_advice)
146 text.bind("<<python-docs>>", self.python_docs)
147 text.bind("<<about-idle>>", self.about_dialog)
148 text.bind("<<open-module>>", self.open_module)
149 text.bind("<<do-nothing>>", lambda event: "break")
150 text.bind("<<select-all>>", self.select_all)
151 text.bind("<<remove-selection>>", self.remove_selection)
152 text.bind("<3>", self.right_menu_event)
153 if flist:
154 flist.inversedict[self] = key
155 if key:
156 flist.dict[key] = self
157 text.bind("<<open-new-window>>", self.flist.new_callback)
158 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
159 text.bind("<<open-class-browser>>", self.open_class_browser)
160 text.bind("<<open-path-browser>>", self.open_path_browser)
161
162 vbar['command'] = text.yview
163 vbar.pack(side=RIGHT, fill=Y)
164
165 text['yscrollcommand'] = vbar.set
166 text['font'] = edconf.get('font-name'), edconf.get('font-size')
167 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
168 text.pack(side=TOP, fill=BOTH, expand=1)
169 text.focus_set()
170
171 self.per = per = self.Percolator(text)
172 if self.ispythonsource(filename):
173 self.color = color = self.ColorDelegator(); per.insertfilter(color)
174 ##print "Initial colorizer"
175 else:
176 ##print "No initial colorizer"
177 self.color = None
178 self.undo = undo = self.UndoDelegator(); per.insertfilter(undo)
179 self.io = io = self.IOBinding(self)
180
181 text.undo_block_start = undo.undo_block_start
182 text.undo_block_stop = undo.undo_block_stop
183 undo.set_saved_change_hook(self.saved_change_hook)
184 io.set_filename_change_hook(self.filename_change_hook)
185
186 if filename:
187 if os.path.exists(filename):
188 io.loadfile(filename)
189 else:
190 io.set_filename(filename)
191
192 self.saved_change_hook()
193
194 self.load_extensions()
195
196 menu = self.menudict.get('windows')
197 if menu:
198 end = menu.index("end")
199 if end is None:
200 end = -1
201 if end >= 0:
202 menu.add_separator()
203 end = end + 1
204 self.wmenu_end = end
205 WindowList.register_callback(self.postwindowsmenu)
206
207 # Some abstractions so IDLE extensions are cross-IDE
208 self.askyesno = tkMessageBox.askyesno
209 self.askinteger = tkSimpleDialog.askinteger
210 self.showerror = tkMessageBox.showerror
211
212 if self.extensions.has_key('AutoIndent'):
213 self.extensions['AutoIndent'].set_indentation_params(
214 self.ispythonsource(filename))
215 self.set_status_bar()
216
217 def set_status_bar(self):
218 self.status_bar = self.MultiStatusBar(self.text_frame)
219 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
220 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
221 self.status_bar.pack(side=BOTTOM, fill=X)
222 self.text.bind('<KeyRelease>', self.set_line_and_column)
223 self.text.bind('<ButtonRelease>', self.set_line_and_column)
224 self.text.after_idle(self.set_line_and_column)
225
226 def set_line_and_column(self, event=None):
227 line, column = string.split(self.text.index(INSERT), '.')
228 self.status_bar.set_label('column', 'Col: %s' % column)
229 self.status_bar.set_label('line', 'Ln: %s' % line)
230
231 def wakeup(self):
232 if self.top.wm_state() == "iconic":
233 self.top.wm_deiconify()
234 else:
235 self.top.tkraise()
236 self.text.focus_set()
237
238 menu_specs = [
239 ("file", "_File"),
240 ("edit", "_Edit"),
241 ("format", "F_ormat"),
242 ("run", "_Run"),
243 ("windows", "_Windows"),
244 ("help", "_Help"),
245 ]
246
247 def createmenubar(self):
248 mbar = self.menubar
249 self.menudict = menudict = {}
250 for name, label in self.menu_specs:
251 underline, label = prepstr(label)
252 menudict[name] = menu = Menu(mbar, name=name)
253 mbar.add_cascade(label=label, menu=menu, underline=underline)
254 self.fill_menus()
255
256 def postwindowsmenu(self):
257 # Only called when Windows menu exists
258 # XXX Actually, this Just-In-Time updating interferes badly
259 # XXX with the tear-off feature. It would be better to update
260 # XXX all Windows menus whenever the list of windows changes.
261 menu = self.menudict['windows']
262 end = menu.index("end")
263 if end is None:
264 end = -1
265 if end > self.wmenu_end:
266 menu.delete(self.wmenu_end+1, end)
267 WindowList.add_windows_to_menu(menu)
268
269 rmenu = None
270
271 def right_menu_event(self, event):
272 self.text.tag_remove("sel", "1.0", "end")
273 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
274 if not self.rmenu:
275 self.make_rmenu()
276 rmenu = self.rmenu
277 self.event = event
278 iswin = sys.platform[:3] == 'win'
279 if iswin:
280 self.text.config(cursor="arrow")
281 rmenu.tk_popup(event.x_root, event.y_root)
282 if iswin:
283 self.text.config(cursor="ibeam")
284
285 rmenu_specs = [
286 # ("Label", "<<virtual-event>>"), ...
287 ("Close", "<<close-window>>"), # Example
288 ]
289
290 def make_rmenu(self):
291 rmenu = Menu(self.text, tearoff=0)
292 for label, eventname in self.rmenu_specs:
293 def command(text=self.text, eventname=eventname):
294 text.event_generate(eventname)
295 rmenu.add_command(label=label, command=command)
296 self.rmenu = rmenu
297
298 def about_dialog(self, event=None):
299 tkMessageBox.showinfo(self.about_title, self.about_text,
300 master=self.text)
301
302 helpfile = "help.txt"
303
304 def good_advice(self, event=None):
305 tkMessageBox.showinfo('Advice', "Don't Panic!", master=self.text)
306
307 def help_dialog(self, event=None):
308 try:
309 helpfile = os.path.join(os.path.dirname(__file__), self.helpfile)
310 except NameError:
311 helpfile = self.helpfile
312 if self.flist:
313 self.flist.open(helpfile)
314 else:
315 self.io.loadfile(helpfile)
316
David Scherer7aced172000-08-15 01:13:23 +0000317 help_url = "http://www.python.org/doc/current/"
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000318 if sys.platform[:3] == "win":
319 fn = os.path.dirname(__file__)
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +0000320 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000321 fn = os.path.normpath(fn)
322 if os.path.isfile(fn):
323 help_url = fn
324 del fn
David Scherer7aced172000-08-15 01:13:23 +0000325
326 def python_docs(self, event=None):
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000327 webbrowser.open(self.help_url)
David Scherer7aced172000-08-15 01:13:23 +0000328
329 def select_all(self, event=None):
330 self.text.tag_add("sel", "1.0", "end-1c")
331 self.text.mark_set("insert", "1.0")
332 self.text.see("insert")
333 return "break"
334
335 def remove_selection(self, event=None):
336 self.text.tag_remove("sel", "1.0", "end")
337 self.text.see("insert")
338
339 def open_module(self, event=None):
340 # XXX Shouldn't this be in IOBinding or in FileList?
341 try:
342 name = self.text.get("sel.first", "sel.last")
343 except TclError:
344 name = ""
345 else:
346 name = string.strip(name)
347 if not name:
348 name = tkSimpleDialog.askstring("Module",
349 "Enter the name of a Python module\n"
350 "to search on sys.path and open:",
351 parent=self.text)
352 if name:
353 name = string.strip(name)
354 if not name:
355 return
356 # XXX Ought to support package syntax
357 # XXX Ought to insert current file's directory in front of path
358 try:
359 (f, file, (suffix, mode, type)) = imp.find_module(name)
360 except (NameError, ImportError), msg:
361 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
362 return
363 if type != imp.PY_SOURCE:
364 tkMessageBox.showerror("Unsupported type",
365 "%s is not a source module" % name, parent=self.text)
366 return
367 if f:
368 f.close()
369 if self.flist:
370 self.flist.open(file)
371 else:
372 self.io.loadfile(file)
373
374 def open_class_browser(self, event=None):
375 filename = self.io.filename
376 if not filename:
377 tkMessageBox.showerror(
378 "No filename",
379 "This buffer has no associated filename",
380 master=self.text)
381 self.text.focus_set()
382 return None
383 head, tail = os.path.split(filename)
384 base, ext = os.path.splitext(tail)
385 import ClassBrowser
386 ClassBrowser.ClassBrowser(self.flist, base, [head])
387
388 def open_path_browser(self, event=None):
389 import PathBrowser
390 PathBrowser.PathBrowser(self.flist)
391
392 def gotoline(self, lineno):
393 if lineno is not None and lineno > 0:
394 self.text.mark_set("insert", "%d.0" % lineno)
395 self.text.tag_remove("sel", "1.0", "end")
396 self.text.tag_add("sel", "insert", "insert +1l")
397 self.center()
398
399 def ispythonsource(self, filename):
400 if not filename:
401 return 1
402 base, ext = os.path.splitext(os.path.basename(filename))
403 if os.path.normcase(ext) in (".py", ".pyw"):
404 return 1
405 try:
406 f = open(filename)
407 line = f.readline()
408 f.close()
409 except IOError:
410 return 0
411 return line[:2] == '#!' and string.find(line, 'python') >= 0
412
413 def close_hook(self):
414 if self.flist:
415 self.flist.close_edit(self)
416
417 def set_close_hook(self, close_hook):
418 self.close_hook = close_hook
419
420 def filename_change_hook(self):
421 if self.flist:
422 self.flist.filename_changed_edit(self)
423 self.saved_change_hook()
424 if self.ispythonsource(self.io.filename):
425 self.addcolorizer()
426 else:
427 self.rmcolorizer()
428
429 def addcolorizer(self):
430 if self.color:
431 return
432 ##print "Add colorizer"
433 self.per.removefilter(self.undo)
434 self.color = self.ColorDelegator()
435 self.per.insertfilter(self.color)
436 self.per.insertfilter(self.undo)
437
438 def rmcolorizer(self):
439 if not self.color:
440 return
441 ##print "Remove colorizer"
442 self.per.removefilter(self.undo)
443 self.per.removefilter(self.color)
444 self.color = None
445 self.per.insertfilter(self.undo)
446
447 def saved_change_hook(self):
448 short = self.short_title()
449 long = self.long_title()
450 if short and long:
451 title = short + " - " + long
452 elif short:
453 title = short
454 elif long:
455 title = long
456 else:
457 title = "Untitled"
458 icon = short or long or title
459 if not self.get_saved():
460 title = "*%s*" % title
461 icon = "*%s" % icon
462 self.top.wm_title(title)
463 self.top.wm_iconname(icon)
464
465 def get_saved(self):
466 return self.undo.get_saved()
467
468 def set_saved(self, flag):
469 self.undo.set_saved(flag)
470
471 def reset_undo(self):
472 self.undo.reset_undo()
473
474 def short_title(self):
475 filename = self.io.filename
476 if filename:
477 filename = os.path.basename(filename)
478 return filename
479
480 def long_title(self):
481 return self.io.filename or ""
482
483 def center_insert_event(self, event):
484 self.center()
485
486 def center(self, mark="insert"):
487 text = self.text
488 top, bot = self.getwindowlines()
489 lineno = self.getlineno(mark)
490 height = bot - top
491 newtop = max(1, lineno - height/2)
492 text.yview(float(newtop))
493
494 def getwindowlines(self):
495 text = self.text
496 top = self.getlineno("@0,0")
497 bot = self.getlineno("@0,65535")
498 if top == bot and text.winfo_height() == 1:
499 # Geometry manager hasn't run yet
500 height = int(text['height'])
501 bot = top + height - 1
502 return top, bot
503
504 def getlineno(self, mark="insert"):
505 text = self.text
506 return int(float(text.index(mark)))
507
508 def close_event(self, event):
509 self.close()
510
511 def maybesave(self):
512 if self.io:
513 return self.io.maybesave()
514
515 def close(self):
516 self.top.wm_deiconify()
517 self.top.tkraise()
518 reply = self.maybesave()
519 if reply != "cancel":
520 self._close()
521 return reply
522
523 def _close(self):
524 WindowList.unregister_callback(self.postwindowsmenu)
525 if self.close_hook:
526 self.close_hook()
527 self.flist = None
528 colorizing = 0
529 self.unload_extensions()
530 self.io.close(); self.io = None
531 self.undo = None # XXX
532 if self.color:
533 colorizing = self.color.colorizing
534 doh = colorizing and self.top
535 self.color.close(doh) # Cancel colorization
536 self.text = None
537 self.vars = None
538 self.per.close(); self.per = None
539 if not colorizing:
540 self.top.destroy()
541
542 def load_extensions(self):
543 self.extensions = {}
544 self.load_standard_extensions()
545
546 def unload_extensions(self):
547 for ins in self.extensions.values():
548 if hasattr(ins, "close"):
549 ins.close()
550 self.extensions = {}
551
552 def load_standard_extensions(self):
553 for name in self.get_standard_extension_names():
554 try:
555 self.load_extension(name)
556 except:
557 print "Failed to load extension", `name`
558 import traceback
559 traceback.print_exc()
560
561 def get_standard_extension_names(self):
562 return idleconf.getextensions()
563
564 def load_extension(self, name):
565 mod = __import__(name, globals(), locals(), [])
566 cls = getattr(mod, name)
567 ins = cls(self)
568 self.extensions[name] = ins
569 kdnames = ["keydefs"]
570 if sys.platform == 'win32':
571 kdnames.append("windows_keydefs")
572 elif sys.platform == 'mac':
573 kdnames.append("mac_keydefs")
574 else:
575 kdnames.append("unix_keydefs")
576 keydefs = {}
577 for kdname in kdnames:
578 if hasattr(ins, kdname):
579 keydefs.update(getattr(ins, kdname))
580 if keydefs:
581 self.apply_bindings(keydefs)
582 for vevent in keydefs.keys():
583 methodname = string.replace(vevent, "-", "_")
584 while methodname[:1] == '<':
585 methodname = methodname[1:]
586 while methodname[-1:] == '>':
587 methodname = methodname[:-1]
588 methodname = methodname + "_event"
589 if hasattr(ins, methodname):
590 self.text.bind(vevent, getattr(ins, methodname))
591 if hasattr(ins, "menudefs"):
592 self.fill_menus(ins.menudefs, keydefs)
593 return ins
594
595 def apply_bindings(self, keydefs=None):
596 if keydefs is None:
597 keydefs = self.Bindings.default_keydefs
598 text = self.text
599 text.keydefs = keydefs
600 for event, keylist in keydefs.items():
601 if keylist:
602 apply(text.event_add, (event,) + tuple(keylist))
603
604 def fill_menus(self, defs=None, keydefs=None):
605 # Fill the menus. Menus that are absent or None in
606 # self.menudict are ignored.
607 if defs is None:
608 defs = self.Bindings.menudefs
609 if keydefs is None:
610 keydefs = self.Bindings.default_keydefs
611 menudict = self.menudict
612 text = self.text
613 for mname, itemlist in defs:
614 menu = menudict.get(mname)
615 if not menu:
616 continue
617 for item in itemlist:
618 if not item:
619 menu.add_separator()
620 else:
621 label, event = item
622 checkbutton = (label[:1] == '!')
623 if checkbutton:
624 label = label[1:]
625 underline, label = prepstr(label)
626 accelerator = get_accelerator(keydefs, event)
627 def command(text=text, event=event):
628 text.event_generate(event)
629 if checkbutton:
630 var = self.getrawvar(event, BooleanVar)
631 menu.add_checkbutton(label=label, underline=underline,
632 command=command, accelerator=accelerator,
633 variable=var)
634 else:
635 menu.add_command(label=label, underline=underline,
636 command=command, accelerator=accelerator)
637
638 def getvar(self, name):
639 var = self.getrawvar(name)
640 if var:
641 return var.get()
642
643 def setvar(self, name, value, vartype=None):
644 var = self.getrawvar(name, vartype)
645 if var:
646 var.set(value)
647
648 def getrawvar(self, name, vartype=None):
649 var = self.vars.get(name)
650 if not var and vartype:
651 self.vars[name] = var = vartype(self.text)
652 return var
653
654 # Tk implementations of "virtual text methods" -- each platform
655 # reusing IDLE's support code needs to define these for its GUI's
656 # flavor of widget.
657
658 # Is character at text_index in a Python string? Return 0 for
659 # "guaranteed no", true for anything else. This info is expensive
660 # to compute ab initio, but is probably already known by the
661 # platform's colorizer.
662
663 def is_char_in_string(self, text_index):
664 if self.color:
665 # Return true iff colorizer hasn't (re)gotten this far
666 # yet, or the character is tagged as being in a string
667 return self.text.tag_prevrange("TODO", text_index) or \
668 "STRING" in self.text.tag_names(text_index)
669 else:
670 # The colorizer is missing: assume the worst
671 return 1
672
673 # If a selection is defined in the text widget, return (start,
674 # end) as Tkinter text indices, otherwise return (None, None)
675 def get_selection_indices(self):
676 try:
677 first = self.text.index("sel.first")
678 last = self.text.index("sel.last")
679 return first, last
680 except TclError:
681 return None, None
682
683 # Return the text widget's current view of what a tab stop means
684 # (equivalent width in spaces).
685
686 def get_tabwidth(self):
687 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
688 return int(current)
689
690 # Set the text widget's current view of what a tab stop means.
691
692 def set_tabwidth(self, newtabwidth):
693 text = self.text
694 if self.get_tabwidth() != newtabwidth:
695 pixels = text.tk.call("font", "measure", text["font"],
696 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000697 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000698 text.configure(tabs=pixels)
699
700def prepstr(s):
701 # Helper to extract the underscore from a string, e.g.
702 # prepstr("Co_py") returns (2, "Copy").
703 i = string.find(s, '_')
704 if i >= 0:
705 s = s[:i] + s[i+1:]
706 return i, s
707
708
709keynames = {
710 'bracketleft': '[',
711 'bracketright': ']',
712 'slash': '/',
713}
714
715def get_accelerator(keydefs, event):
716 keylist = keydefs.get(event)
717 if not keylist:
718 return ""
719 s = keylist[0]
720 s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
721 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
722 s = re.sub("Key-", "", s)
723 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
724 s = re.sub("Control-", "Ctrl-", s)
725 s = re.sub("-", "+", s)
726 s = re.sub("><", " ", s)
727 s = re.sub("<", "", s)
728 s = re.sub(">", "", s)
729 return s
730
731
732def fixwordbreaks(root):
733 # Make sure that Tk's double-click and next/previous word
734 # operations use our definition of a word (i.e. an identifier)
735 tk = root.tk
736 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
737 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
738 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
739
740
741def test():
742 root = Tk()
743 fixwordbreaks(root)
744 root.withdraw()
745 if sys.argv[1:]:
746 filename = sys.argv[1]
747 else:
748 filename = None
749 edit = EditorWindow(root=root, filename=filename)
750 edit.set_close_hook(root.quit)
751 root.mainloop()
752 root.destroy()
753
754if __name__ == '__main__':
755 test()