home *** CD-ROM | disk | FTP | other *** search
/ Freelog 33 / Freelog033.iso / Progr / Python-2.2.1.exe / EDITORWINDOW.PY < prev    next >
Encoding:
Python Source  |  2001-04-18  |  23.4 KB  |  732 lines

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