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