home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2011 June / maximum-cd-2011-06.iso / DiscContents / LibO_3.3.1_Win_x86_install_multi.exe / libreoffice1.cab / EditorWindow.py < prev    next >
Encoding:
Python Source  |  2011-02-15  |  57.5 KB  |  1,552 lines

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