PageRenderTime 2460ms CodeModel.GetById 647ms app.highlight 1562ms RepoModel.GetById 233ms app.codeStats 1ms

/Lib/idlelib/EditorWindow.py

http://unladen-swallow.googlecode.com/
Python | 1567 lines | 1300 code | 129 blank | 138 comment | 185 complexity | 7c01a03797facd6f0d3f9b66f279a62f MD5 | raw file

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file