home *** CD-ROM | disk | FTP | other *** search
/ PC World 2005 June / PCWorld_2005-06_cd.bin / software / vyzkuste / firewally / firewally.exe / framework-2.3.exe / EditorWindow.py < prev    next >
Text File  |  2003-12-30  |  52KB  |  1,426 lines

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