symbian-qemu-0.9.1-12/python-2.6.1/Lib/idlelib/EditorWindow.py
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     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()