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