Merge IDLE-syntax-branch r39668:41449 into trunk

A    idlelib/AutoCompleteWindow.py
A    idlelib/AutoComplete.py
A    idlelib/HyperParser.py
M    idlelib/PyShell.py
M    idlelib/ParenMatch.py
M    idlelib/configDialog.py
M    idlelib/EditorWindow.py
M    idlelib/PyParse.py
M    idlelib/CallTips.py
M    idlelib/CallTipWindow.py
M    idlelib/run.py
M    idlelib/config-extensions.def
A    idlelib/MultiCall.py
diff --git a/Lib/idlelib/AutoComplete.py b/Lib/idlelib/AutoComplete.py
new file mode 100644
index 0000000..7085386
--- /dev/null
+++ b/Lib/idlelib/AutoComplete.py
@@ -0,0 +1,226 @@
+"""AutoComplete.py - An IDLE extension for automatically completing names.
+
+This extension can complete either attribute names of file names. It can pop
+a window with all available names, for the user to select from.
+"""
+import os
+import sys
+import string
+
+from configHandler import idleConf
+
+import AutoCompleteWindow
+from HyperParser import HyperParser
+
+import __main__
+
+# This string includes all chars that may be in a file name (without a path
+# separator)
+FILENAME_CHARS = string.ascii_letters + string.digits + os.curdir + "._~#$:-"
+# This string includes all chars that may be in an identifier
+ID_CHARS = string.ascii_letters + string.digits + "_"
+
+# These constants represent the two different types of completions
+COMPLETE_ATTRIBUTES, COMPLETE_FILES = range(1, 2+1)
+
+class AutoComplete:
+
+    menudefs = [
+        ('edit', [
+            ("Show completions", "<<force-open-completions>>"),
+        ])
+    ]
+
+    popupwait = idleConf.GetOption("extensions", "AutoComplete",
+                                   "popupwait", type="int", default=0)
+
+    def __init__(self, editwin=None):
+        if editwin == None:  # subprocess and test
+            self.editwin = None
+            return
+        self.editwin = editwin
+        self.text = editwin.text
+        self.autocompletewindow = None
+
+        # id of delayed call, and the index of the text insert when the delayed
+        # call was issued. If _delayed_completion_id is None, there is no
+        # delayed call.
+        self._delayed_completion_id = None
+        self._delayed_completion_index = None
+
+    def _make_autocomplete_window(self):
+        return AutoCompleteWindow.AutoCompleteWindow(self.text)
+
+    def _remove_autocomplete_window(self, event=None):
+        if self.autocompletewindow:
+            self.autocompletewindow.hide_window()
+            self.autocompletewindow = None
+
+    def force_open_completions_event(self, event):
+        """Happens when the user really wants to open a completion list, even
+        if a function call is needed.
+        """
+        self.open_completions(True, False, True)
+
+    def try_open_completions_event(self, event):
+        """Happens when it would be nice to open a completion list, but not
+        really neccesary, for example after an dot, so function
+        calls won't be made.
+        """
+        lastchar = self.text.get("insert-1c")
+        if lastchar == ".":
+            self._open_completions_later(False, False, False,
+                                         COMPLETE_ATTRIBUTES)
+        elif lastchar == os.sep:
+            self._open_completions_later(False, False, False,
+                                         COMPLETE_FILES)
+
+    def autocomplete_event(self, event):
+        """Happens when the user wants to complete his word, and if neccesary,
+        open a completion list after that (if there is more than one
+        completion)
+        """
+        if hasattr(event, "mc_state") and event.mc_state:
+            # A modifier was pressed along with the tab, continue as usual.
+            return
+        if self.autocompletewindow and self.autocompletewindow.is_active():
+            self.autocompletewindow.complete()
+            return "break"
+        else:
+            opened = self.open_completions(False, True, True)
+            if opened:
+                return "break"
+
+    def _open_completions_later(self, *args):
+        self._delayed_completion_index = self.text.index("insert")
+        if self._delayed_completion_id is not None:
+            self.text.after_cancel(self._delayed_completion_id)
+        self._delayed_completion_id = \
+            self.text.after(self.popupwait, self._delayed_open_completions,
+                            *args)
+
+    def _delayed_open_completions(self, *args):
+        self._delayed_completion_id = None
+        if self.text.index("insert") != self._delayed_completion_index:
+            return
+        self.open_completions(*args)
+
+    def open_completions(self, evalfuncs, complete, userWantsWin, mode=None):
+        """Find the completions and create the AutoCompleteWindow.
+        Return True if successful (no syntax error or so found).
+        if complete is True, then if there's nothing to complete and no
+        start of completion, won't open completions and return False.
+        If mode is given, will open a completion list only in this mode.
+        """
+        # Cancel another delayed call, if it exists.
+        if self._delayed_completion_id is not None:
+            self.text.after_cancel(self._delayed_completion_id)
+            self._delayed_completion_id = None
+
+        hp = HyperParser(self.editwin, "insert")
+        curline = self.text.get("insert linestart", "insert")
+        i = j = len(curline)
+        if hp.is_in_string() and (not mode or mode==COMPLETE_FILES):
+            self._remove_autocomplete_window()
+            mode = COMPLETE_FILES
+            while i and curline[i-1] in FILENAME_CHARS:
+                i -= 1
+            comp_start = curline[i:j]
+            j = i
+            while i and curline[i-1] in FILENAME_CHARS+os.sep:
+                i -= 1
+            comp_what = curline[i:j]
+        elif hp.is_in_code() and (not mode or mode==COMPLETE_ATTRIBUTES):
+            self._remove_autocomplete_window()
+            mode = COMPLETE_ATTRIBUTES
+            while i and curline[i-1] in ID_CHARS:
+                i -= 1
+            comp_start = curline[i:j]
+            if i and curline[i-1] == '.':
+                hp.set_index("insert-%dc" % (len(curline)-(i-1)))
+                comp_what = hp.get_expression()
+                if not comp_what or \
+                   (not evalfuncs and comp_what.find('(') != -1):
+                    return
+            else:
+                comp_what = ""
+        else:
+            return
+
+        if complete and not comp_what and not comp_start:
+            return
+        comp_lists = self.fetch_completions(comp_what, mode)
+        if not comp_lists[0]:
+            return
+        self.autocompletewindow = self._make_autocomplete_window()
+        self.autocompletewindow.show_window(comp_lists,
+                                            "insert-%dc" % len(comp_start),
+                                            complete,
+                                            mode,
+                                            userWantsWin)
+        return True
+
+    def fetch_completions(self, what, mode):
+        """Return a pair of lists of completions for something. The first list
+        is a sublist of the second. Both are sorted.
+
+        If there is a Python subprocess, get the comp. list there.  Otherwise,
+        either fetch_completions() is running in the subprocess itself or it
+        was called in an IDLE EditorWindow before any script had been run.
+
+        The subprocess environment is that of the most recently run script.  If
+        two unrelated modules are being edited some calltips in the current
+        module may be inoperative if the module was not the last to run.
+        """
+        try:
+            rpcclt = self.editwin.flist.pyshell.interp.rpcclt
+        except:
+            rpcclt = None
+        if rpcclt:
+            return rpcclt.remotecall("exec", "get_the_completion_list",
+                                     (what, mode), {})
+        else:
+            if mode == COMPLETE_ATTRIBUTES:
+                if what == "":
+                    namespace = __main__.__dict__.copy()
+                    namespace.update(__main__.__builtins__.__dict__)
+                    bigl = eval("dir()", namespace)
+                    bigl.sort()
+                    if "__all__" in bigl:
+                        smalll = eval("__all__", namespace)
+                        smalll.sort()
+                    else:
+                        smalll = filter(lambda s: s[:1] != '_', bigl)
+                else:
+                    try:
+                        entity = self.get_entity(what)
+                        bigl = dir(entity)
+                        bigl.sort()
+                        if "__all__" in bigl:
+                            smalll = entity.__all__
+                            smalll.sort()
+                        else:
+                            smalll = filter(lambda s: s[:1] != '_', bigl)
+                    except:
+                        return [], []
+
+            elif mode == COMPLETE_FILES:
+                if what == "":
+                    what = "."
+                try:
+                    expandedpath = os.path.expanduser(what)
+                    bigl = os.listdir(expandedpath)
+                    bigl.sort()
+                    smalll = filter(lambda s: s[:1] != '.', bigl)
+                except OSError:
+                    return [], []
+
+            if not smalll:
+                smalll = bigl
+            return smalll, bigl
+
+    def get_entity(self, name):
+        """Lookup name in a namespace spanning sys.modules and __main.dict__"""
+        namespace = sys.modules.copy()
+        namespace.update(__main__.__dict__)
+        return eval(name, namespace)
diff --git a/Lib/idlelib/AutoCompleteWindow.py b/Lib/idlelib/AutoCompleteWindow.py
new file mode 100644
index 0000000..d8bbff4
--- /dev/null
+++ b/Lib/idlelib/AutoCompleteWindow.py
@@ -0,0 +1,393 @@
+"""
+An auto-completion window for IDLE, used by the AutoComplete extension
+"""
+from Tkinter import *
+from MultiCall import MC_SHIFT
+import AutoComplete
+
+HIDE_VIRTUAL_EVENT_NAME = "<<autocompletewindow-hide>>"
+HIDE_SEQUENCES = ("<FocusOut>", "<ButtonPress>")
+KEYPRESS_VIRTUAL_EVENT_NAME = "<<autocompletewindow-keypress>>"
+# We need to bind event beyond <Key> so that the function will be called
+# before the default specific IDLE function
+KEYPRESS_SEQUENCES = ("<Key>", "<Key-BackSpace>", "<Key-Return>",
+                      "<Key-Up>", "<Key-Down>", "<Key-Home>", "<Key-End>")
+KEYRELEASE_VIRTUAL_EVENT_NAME = "<<autocompletewindow-keyrelease>>"
+KEYRELEASE_SEQUENCE = "<KeyRelease>"
+LISTUPDATE_SEQUENCE = "<ButtonRelease>"
+WINCONFIG_SEQUENCE = "<Configure>"
+DOUBLECLICK_SEQUENCE = "<Double-ButtonRelease>"
+
+class AutoCompleteWindow:
+
+    def __init__(self, widget):
+        # The widget (Text) on which we place the AutoCompleteWindow
+        self.widget = widget
+        # The widgets we create
+        self.autocompletewindow = self.listbox = self.scrollbar = None
+        # The default foreground and background of a selection. Saved because
+        # they are changed to the regular colors of list items when the
+        # completion start is not a prefix of the selected completion
+        self.origselforeground = self.origselbackground = None
+        # The list of completions
+        self.completions = None
+        # A list with more completions, or None
+        self.morecompletions = None
+        # The completion mode. Either AutoComplete.COMPLETE_ATTRIBUTES or
+        # AutoComplete.COMPLETE_FILES
+        self.mode = None
+        # The current completion start, on the text box (a string)
+        self.start = None
+        # The index of the start of the completion
+        self.startindex = None
+        # The last typed start, used so that when the selection changes,
+        # the new start will be as close as possible to the last typed one.
+        self.lasttypedstart = None
+        # Do we have an indication that the user wants the completion window
+        # (for example, he clicked the list)
+        self.userwantswindow = None
+        # event ids
+        self.hideid = self.keypressid = self.listupdateid = self.winconfigid \
+        = self.keyreleaseid = self.doubleclickid                         = None
+
+    def _change_start(self, newstart):
+        i = 0
+        while i < len(self.start) and i < len(newstart) and \
+              self.start[i] == newstart[i]:
+            i += 1
+        if i < len(self.start):
+            self.widget.delete("%s+%dc" % (self.startindex, i),
+                               "%s+%dc" % (self.startindex, len(self.start)))
+        if i < len(newstart):
+            self.widget.insert("%s+%dc" % (self.startindex, i),
+                               newstart[i:])
+        self.start = newstart
+
+    def _binary_search(self, s):
+        """Find the first index in self.completions where completions[i] is
+        greater or equal to s, or the last index if there is no such
+        one."""
+        i = 0; j = len(self.completions)
+        while j > i:
+            m = (i + j) // 2
+            if self.completions[m] >= s:
+                j = m
+            else:
+                i = m + 1
+        return min(i, len(self.completions)-1)
+
+    def _complete_string(self, s):
+        """Assuming that s is the prefix of a string in self.completions,
+        return the longest string which is a prefix of all the strings which
+        s is a prefix of them. If s is not a prefix of a string, return s."""
+        first = self._binary_search(s)
+        if self.completions[first][:len(s)] != s:
+            # There is not even one completion which s is a prefix of.
+            return s
+        # Find the end of the range of completions where s is a prefix of.
+        i = first + 1
+        j = len(self.completions)
+        while j > i:
+            m = (i + j) // 2
+            if self.completions[m][:len(s)] != s:
+                j = m
+            else:
+                i = m + 1
+        last = i-1
+
+        # We should return the maximum prefix of first and last
+        i = len(s)
+        while len(self.completions[first]) > i and \
+              len(self.completions[last]) > i and \
+              self.completions[first][i] == self.completions[last][i]:
+            i += 1
+        return self.completions[first][:i]
+
+    def _selection_changed(self):
+        """Should be called when the selection of the Listbox has changed.
+        Updates the Listbox display and calls _change_start."""
+        cursel = int(self.listbox.curselection()[0])
+
+        self.listbox.see(cursel)
+
+        lts = self.lasttypedstart
+        selstart = self.completions[cursel]
+        if self._binary_search(lts) == cursel:
+            newstart = lts
+        else:
+            i = 0
+            while i < len(lts) and i < len(selstart) and lts[i] == selstart[i]:
+                i += 1
+            while cursel > 0 and selstart[:i] <= self.completions[cursel-1]:
+                i += 1
+            newstart = selstart[:i]
+        self._change_start(newstart)
+
+        if self.completions[cursel][:len(self.start)] == self.start:
+            # start is a prefix of the selected completion
+            self.listbox.configure(selectbackground=self.origselbackground,
+                                   selectforeground=self.origselforeground)
+        else:
+            self.listbox.configure(selectbackground=self.listbox.cget("bg"),
+                                   selectforeground=self.listbox.cget("fg"))
+            # If there are more completions, show them, and call me again.
+            if self.morecompletions:
+                self.completions = self.morecompletions
+                self.morecompletions = None
+                self.listbox.delete(0, END)
+                for item in self.completions:
+                    self.listbox.insert(END, item)
+                self.listbox.select_set(self._binary_search(self.start))
+                self._selection_changed()
+
+    def show_window(self, comp_lists, index, complete, mode, userWantsWin):
+        """Show the autocomplete list, bind events.
+        If complete is True, complete the text, and if there is exactly one
+        matching completion, don't open a list."""
+        # Handle the start we already have
+        self.completions, self.morecompletions = comp_lists
+        self.mode = mode
+        self.startindex = self.widget.index(index)
+        self.start = self.widget.get(self.startindex, "insert")
+        if complete:
+            completed = self._complete_string(self.start)
+            self._change_start(completed)
+            i = self._binary_search(completed)
+            if self.completions[i] == completed and \
+               (i == len(self.completions)-1 or
+                self.completions[i+1][:len(completed)] != completed):
+                # There is exactly one matching completion
+                return
+        self.userwantswindow = userWantsWin
+        self.lasttypedstart = self.start
+
+        # Put widgets in place
+        self.autocompletewindow = acw = Toplevel(self.widget)
+        # Put it in a position so that it is not seen.
+        acw.wm_geometry("+10000+10000")
+        # Make it float
+        acw.wm_overrideredirect(1)
+        try:
+            # This command is only needed and available on Tk >= 8.4.0 for OSX
+            # Without it, call tips intrude on the typing process by grabbing
+            # the focus.
+            acw.tk.call("::tk::unsupported::MacWindowStyle", "style", acw._w,
+                        "help", "noActivates")
+        except TclError:
+            pass
+        self.scrollbar = scrollbar = Scrollbar(acw, orient=VERTICAL)
+        self.listbox = listbox = Listbox(acw, yscrollcommand=scrollbar.set,
+                                         exportselection=False, bg="white")
+        for item in self.completions:
+            listbox.insert(END, item)
+        self.origselforeground = listbox.cget("selectforeground")
+        self.origselbackground = listbox.cget("selectbackground")
+        scrollbar.config(command=listbox.yview)
+        scrollbar.pack(side=RIGHT, fill=Y)
+        listbox.pack(side=LEFT, fill=BOTH, expand=True)
+
+        # Initialize the listbox selection
+        self.listbox.select_set(self._binary_search(self.start))
+        self._selection_changed()
+
+        # bind events
+        self.hideid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME,
+                                       self.hide_event)
+        for seq in HIDE_SEQUENCES:
+            self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq)
+        self.keypressid = self.widget.bind(KEYPRESS_VIRTUAL_EVENT_NAME,
+                                           self.keypress_event)
+        for seq in KEYPRESS_SEQUENCES:
+            self.widget.event_add(KEYPRESS_VIRTUAL_EVENT_NAME, seq)
+        self.keyreleaseid = self.widget.bind(KEYRELEASE_VIRTUAL_EVENT_NAME,
+                                             self.keyrelease_event)
+        self.widget.event_add(KEYRELEASE_VIRTUAL_EVENT_NAME,KEYRELEASE_SEQUENCE)
+        self.listupdateid = listbox.bind(LISTUPDATE_SEQUENCE,
+                                         self.listupdate_event)
+        self.winconfigid = acw.bind(WINCONFIG_SEQUENCE, self.winconfig_event)
+        self.doubleclickid = listbox.bind(DOUBLECLICK_SEQUENCE,
+                                          self.doubleclick_event)
+
+    def winconfig_event(self, event):
+        if not self.is_active():
+            return
+        # Position the completion list window
+        acw = self.autocompletewindow
+        self.widget.see(self.startindex)
+        x, y, cx, cy = self.widget.bbox(self.startindex)
+        acw.wm_geometry("+%d+%d" % (x + self.widget.winfo_rootx(),
+                                    y + self.widget.winfo_rooty() \
+                                    -acw.winfo_height()))
+
+
+    def hide_event(self, event):
+        if not self.is_active():
+            return
+        self.hide_window()
+
+    def listupdate_event(self, event):
+        if not self.is_active():
+            return
+        self.userwantswindow = True
+        self._selection_changed()
+
+    def doubleclick_event(self, event):
+        # Put the selected completion in the text, and close the list
+        cursel = int(self.listbox.curselection()[0])
+        self._change_start(self.completions[cursel])
+        self.hide_window()
+
+    def keypress_event(self, event):
+        if not self.is_active():
+            return
+        keysym = event.keysym
+        if hasattr(event, "mc_state"):
+            state = event.mc_state
+        else:
+            state = 0
+
+        if (len(keysym) == 1 or keysym in ("underscore", "BackSpace")
+            or (self.mode==AutoComplete.COMPLETE_FILES and keysym in
+                ("period", "minus"))) \
+           and not (state & ~MC_SHIFT):
+            # Normal editing of text
+            if len(keysym) == 1:
+                self._change_start(self.start + keysym)
+            elif keysym == "underscore":
+                self._change_start(self.start + '_')
+            elif keysym == "period":
+                self._change_start(self.start + '.')
+            elif keysym == "minus":
+                self._change_start(self.start + '-')
+            else:
+                # keysym == "BackSpace"
+                if len(self.start) == 0:
+                    self.hide_window()
+                    return
+                self._change_start(self.start[:-1])
+            self.lasttypedstart = self.start
+            self.listbox.select_clear(0, int(self.listbox.curselection()[0]))
+            self.listbox.select_set(self._binary_search(self.start))
+            self._selection_changed()
+            return "break"
+
+        elif keysym == "Return" and not state:
+            # If start is a prefix of the selection, or there was an indication
+            # that the user used the completion window, put the selected
+            # completion in the text, and close the list.
+            # Otherwise, close the window and let the event through.
+            cursel = int(self.listbox.curselection()[0])
+            if self.completions[cursel][:len(self.start)] == self.start or \
+               self.userwantswindow:
+                self._change_start(self.completions[cursel])
+                self.hide_window()
+                return "break"
+            else:
+                self.hide_window()
+                return
+
+        elif (self.mode == AutoComplete.COMPLETE_ATTRIBUTES and keysym in
+              ("period", "space", "parenleft", "parenright", "bracketleft",
+               "bracketright")) or \
+             (self.mode == AutoComplete.COMPLETE_FILES and keysym in
+              ("slash", "backslash", "quotedbl", "apostrophe")) \
+             and not (state & ~MC_SHIFT):
+            # If start is a prefix of the selection, but is not '' when
+            # completing file names, put the whole
+            # selected completion. Anyway, close the list.
+            cursel = int(self.listbox.curselection()[0])
+            if self.completions[cursel][:len(self.start)] == self.start \
+               and (self.mode==AutoComplete.COMPLETE_ATTRIBUTES or self.start):
+                self._change_start(self.completions[cursel])
+            self.hide_window()
+            return
+
+        elif keysym in ("Home", "End", "Prior", "Next", "Up", "Down") and \
+             not state:
+            # Move the selection in the listbox
+            self.userwantswindow = True
+            cursel = int(self.listbox.curselection()[0])
+            if keysym == "Home":
+                newsel = 0
+            elif keysym == "End":
+                newsel = len(self.completions)-1
+            elif keysym in ("Prior", "Next"):
+                jump = self.listbox.nearest(self.listbox.winfo_height()) - \
+                       self.listbox.nearest(0)
+                if keysym == "Prior":
+                    newsel = max(0, cursel-jump)
+                else:
+                    assert keysym == "Next"
+                    newsel = min(len(self.completions)-1, cursel+jump)
+            elif keysym == "Up":
+                newsel = max(0, cursel-1)
+            else:
+                assert keysym == "Down"
+                newsel = min(len(self.completions)-1, cursel+1)
+            self.listbox.select_clear(cursel)
+            self.listbox.select_set(newsel)
+            self._selection_changed()
+            return "break"
+
+        elif (keysym == "Tab" and not state):
+            # The user wants a completion, but it is handled by AutoComplete
+            # (not AutoCompleteWindow), so ignore.
+            self.userwantswindow = True
+            return
+
+        elif reduce(lambda x, y: x or y,
+                    [keysym.find(s) != -1 for s in ("Shift", "Control", "Alt",
+                                                    "Meta", "Command", "Option")
+                     ]):
+            # A modifier key, so ignore
+            return
+
+        else:
+            # Unknown event, close the window and let it through.
+            self.hide_window()
+            return
+
+    def keyrelease_event(self, event):
+        if not self.is_active():
+            return
+        if self.widget.index("insert") != \
+           self.widget.index("%s+%dc" % (self.startindex, len(self.start))):
+            # If we didn't catch an event which moved the insert, close window
+            self.hide_window()
+
+    def is_active(self):
+        return self.autocompletewindow is not None
+
+    def complete(self):
+        self._change_start(self._complete_string(self.start))
+        # The selection doesn't change.
+
+    def hide_window(self):
+        if not self.is_active():
+            return
+
+        # unbind events
+        for seq in HIDE_SEQUENCES:
+            self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq)
+        self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideid)
+        self.hideid = None
+        for seq in KEYPRESS_SEQUENCES:
+            self.widget.event_delete(KEYPRESS_VIRTUAL_EVENT_NAME, seq)
+        self.widget.unbind(KEYPRESS_VIRTUAL_EVENT_NAME, self.keypressid)
+        self.keypressid = None
+        self.widget.event_delete(KEYRELEASE_VIRTUAL_EVENT_NAME,
+                                 KEYRELEASE_SEQUENCE)
+        self.widget.unbind(KEYRELEASE_VIRTUAL_EVENT_NAME, self.keyreleaseid)
+        self.keyreleaseid = None
+        self.listbox.unbind(LISTUPDATE_SEQUENCE, self.listupdateid)
+        self.listupdateid = None
+        self.autocompletewindow.unbind(WINCONFIG_SEQUENCE, self.winconfigid)
+        self.winconfigid = None
+
+        # destroy widgets
+        self.scrollbar.destroy()
+        self.scrollbar = None
+        self.listbox.destroy()
+        self.listbox = None
+        self.autocompletewindow.destroy()
+        self.autocompletewindow = None
diff --git a/Lib/idlelib/CallTipWindow.py b/Lib/idlelib/CallTipWindow.py
index 990d96e..09d6414 100644
--- a/Lib/idlelib/CallTipWindow.py
+++ b/Lib/idlelib/CallTipWindow.py
@@ -6,33 +6,65 @@
 """
 from Tkinter import *
 
+HIDE_VIRTUAL_EVENT_NAME = "<<caltipwindow-hide>>"
+HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>")
+CHECKHIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-checkhide>>"
+CHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>")
+CHECKHIDE_TIME = 100 # miliseconds
+
+MARK_RIGHT = "calltipwindowregion_right"
+
 class CallTip:
 
     def __init__(self, widget):
         self.widget = widget
-        self.tipwindow = None
-        self.id = None
-        self.x = self.y = 0
+        self.tipwindow = self.label = None
+        self.parenline = self.parencol = None
+        self.lastline = None
+        self.hideid = self.checkhideid = None
 
-    def showtip(self, text):
-        " Display text in calltip window"
+    def position_window(self):
+        """Check if needs to reposition the window, and if so - do it."""
+        curline = int(self.widget.index("insert").split('.')[0])
+        if curline == self.lastline:
+            return
+        self.lastline = curline
+        self.widget.see("insert")
+        if curline == self.parenline:
+            box = self.widget.bbox("%d.%d" % (self.parenline,
+                                              self.parencol))
+        else:
+            box = self.widget.bbox("%d.0" % curline)
+        if not box:
+            box = list(self.widget.bbox("insert"))
+            # align to left of window
+            box[0] = 0
+            box[2] = 0
+        x = box[0] + self.widget.winfo_rootx() + 2
+        y = box[1] + box[3] + self.widget.winfo_rooty()
+        self.tipwindow.wm_geometry("+%d+%d" % (x, y))
+
+    def showtip(self, text, parenleft, parenright):
+        """Show the calltip, bind events which will close it and reposition it.
+        """
         # truncate overly long calltip
         if len(text) >= 79:
             text = text[:75] + ' ...'
         self.text = text
         if self.tipwindow or not self.text:
             return
-        self.widget.see("insert")
-        x, y, cx, cy = self.widget.bbox("insert")
-        x = x + self.widget.winfo_rootx() + 2
-        y = y + cy + self.widget.winfo_rooty()
+
+        self.widget.mark_set(MARK_RIGHT, parenright)
+        self.parenline, self.parencol = map(
+            int, self.widget.index(parenleft).split("."))
+
         self.tipwindow = tw = Toplevel(self.widget)
+        self.position_window()
         # XXX 12 Dec 2002 KBK The following command has two effects: It removes
         #     the calltip window border (good) but also causes (at least on
         #     Linux) the calltip to show as a top level window, burning through
         #     any other window dragged over it.  Also, shows on all viewports!
         tw.wm_overrideredirect(1)
-        tw.wm_geometry("+%d+%d" % (x, y))
         try:
             # This command is only needed and available on Tk >= 8.4.0 for OSX
             # Without it, call tips intrude on the typing process by grabbing
@@ -41,16 +73,66 @@
                        "help", "noActivates")
         except TclError:
             pass
-        label = Label(tw, text=self.text, justify=LEFT,
-                      background="#ffffe0", relief=SOLID, borderwidth=1,
-                      font = self.widget['font'])
-        label.pack()
+        self.label = Label(tw, text=self.text, justify=LEFT,
+                           background="#ffffe0", relief=SOLID, borderwidth=1,
+                           font = self.widget['font'])
+        self.label.pack()
+
+        self.checkhideid = self.widget.bind(CHECKHIDE_VIRTUAL_EVENT_NAME,
+                                            self.checkhide_event)
+        for seq in CHECKHIDE_SEQUENCES:
+            self.widget.event_add(CHECKHIDE_VIRTUAL_EVENT_NAME, seq)
+        self.widget.after(CHECKHIDE_TIME, self.checkhide_event)
+        self.hideid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME,
+                                       self.hide_event)
+        for seq in HIDE_SEQUENCES:
+            self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq)
+
+    def checkhide_event(self, event=None):
+        if not self.tipwindow:
+            # If the event was triggered by the same event that unbinded
+            # this function, the function will be called nevertheless,
+            # so do nothing in this case.
+            return
+        curline, curcol = map(int, self.widget.index("insert").split('.'))
+        if curline < self.parenline or \
+           (curline == self.parenline and curcol <= self.parencol) or \
+           self.widget.compare("insert", ">", MARK_RIGHT):
+            self.hidetip()
+        else:
+            self.position_window()
+            self.widget.after(CHECKHIDE_TIME, self.checkhide_event)
+
+    def hide_event(self, event):
+        if not self.tipwindow:
+            # See the explanation in checkhide_event.
+            return
+        self.hidetip()
 
     def hidetip(self):
-        tw = self.tipwindow
+        if not self.tipwindow:
+            return
+
+        for seq in CHECKHIDE_SEQUENCES:
+            self.widget.event_delete(CHECKHIDE_VIRTUAL_EVENT_NAME, seq)
+        self.widget.unbind(CHECKHIDE_VIRTUAL_EVENT_NAME, self.checkhideid)
+        self.checkhideid = None
+        for seq in HIDE_SEQUENCES:
+            self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq)
+        self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideid)
+        self.hideid = None
+
+        self.label.destroy()
+        self.label = None
+        self.tipwindow.destroy()
         self.tipwindow = None
-        if tw:
-            tw.destroy()
+
+        self.widget.mark_unset(MARK_RIGHT)
+        self.parenline = self.parencol = self.lastline = None
+
+    def is_active(self):
+        return bool(self.tipwindow)
+
 
 
 ###############################
diff --git a/Lib/idlelib/CallTips.py b/Lib/idlelib/CallTips.py
index 97d9746..47a1d55 100644
--- a/Lib/idlelib/CallTips.py
+++ b/Lib/idlelib/CallTips.py
@@ -3,21 +3,21 @@
 Call Tips are floating windows which display function, class, and method
 parameter and docstring information when you type an opening parenthesis, and
 which disappear when you type a closing parenthesis.
-
-Future plans include extending the functionality to include class attributes.
-
 """
 import sys
-import string
 import types
 
 import CallTipWindow
+from HyperParser import HyperParser
 
 import __main__
 
 class CallTips:
 
     menudefs = [
+        ('edit', [
+            ("Show call tip", "<<force-open-calltip>>"),
+        ])
     ]
 
     def __init__(self, editwin=None):
@@ -36,51 +36,47 @@
         # See __init__ for usage
         return CallTipWindow.CallTip(self.text)
 
-    def _remove_calltip_window(self):
+    def _remove_calltip_window(self, event=None):
         if self.calltip:
             self.calltip.hidetip()
             self.calltip = None
 
-    def paren_open_event(self, event):
+    def force_open_calltip_event(self, event):
+        """Happens when the user really wants to open a CallTip, even if a
+        function call is needed.
+        """
+        self.open_calltip(True)
+
+    def try_open_calltip_event(self, event):
+        """Happens when it would be nice to open a CallTip, but not really
+        neccesary, for example after an opening bracket, so function calls
+        won't be made.
+        """
+        self.open_calltip(False)
+
+    def refresh_calltip_event(self, event):
+        """If there is already a calltip window, check if it is still needed,
+        and if so, reload it.
+        """
+        if self.calltip and self.calltip.is_active():
+            self.open_calltip(False)
+
+    def open_calltip(self, evalfuncs):
         self._remove_calltip_window()
-        name = self.get_name_at_cursor()
+
+        hp = HyperParser(self.editwin, "insert")
+        sur_paren = hp.get_surrounding_brackets('(')
+        if not sur_paren:
+            return
+        hp.set_index(sur_paren[0])
+        name = hp.get_expression()
+        if not name or (not evalfuncs and name.find('(') != -1):
+            return
         arg_text = self.fetch_tip(name)
-        if arg_text:
-            self.calltip_start = self.text.index("insert")
-            self.calltip = self._make_calltip_window()
-            self.calltip.showtip(arg_text)
-        return "" #so the event is handled normally.
-
-    def paren_close_event(self, event):
-        # Now just hides, but later we should check if other
-        # paren'd expressions remain open.
-        self._remove_calltip_window()
-        return "" #so the event is handled normally.
-
-    def check_calltip_cancel_event(self, event):
-        if self.calltip:
-            # If we have moved before the start of the calltip,
-            # or off the calltip line, then cancel the tip.
-            # (Later need to be smarter about multi-line, etc)
-            if self.text.compare("insert", "<=", self.calltip_start) or \
-               self.text.compare("insert", ">", self.calltip_start
-                                 + " lineend"):
-                self._remove_calltip_window()
-        return "" #so the event is handled normally.
-
-    def calltip_cancel_event(self, event):
-        self._remove_calltip_window()
-        return "" #so the event is handled normally.
-
-    __IDCHARS = "._" + string.ascii_letters + string.digits
-
-    def get_name_at_cursor(self):
-        idchars = self.__IDCHARS
-        str = self.text.get("insert linestart", "insert")
-        i = len(str)
-        while i and str[i-1] in idchars:
-            i -= 1
-        return str[i:]
+        if not arg_text:
+            return
+        self.calltip = self._make_calltip_window()
+        self.calltip.showtip(arg_text, sur_paren[0], sur_paren[1])
 
     def fetch_tip(self, name):
         """Return the argument list and docstring of a function or class
@@ -127,7 +123,7 @@
     return None
 
 def get_arg_text(ob):
-    "Get a string describing the arguments for the given object"
+    """Get a string describing the arguments for the given object"""
     argText = ""
     if ob is not None:
         argOffset = 0
@@ -150,7 +146,7 @@
             try:
                 realArgs = fob.func_code.co_varnames[argOffset:fob.func_code.co_argcount]
                 defaults = fob.func_defaults or []
-                defaults = list(map(lambda name: "=%s" % name, defaults))
+                defaults = list(map(lambda name: "=%s" % repr(name), defaults))
                 defaults = [""] * (len(realArgs)-len(defaults)) + defaults
                 items = map(lambda arg, dflt: arg+dflt, realArgs, defaults)
                 if fob.func_code.co_flags & 0x4:
diff --git a/Lib/idlelib/EditorWindow.py b/Lib/idlelib/EditorWindow.py
index cc38122..1cd496a 100644
--- a/Lib/idlelib/EditorWindow.py
+++ b/Lib/idlelib/EditorWindow.py
@@ -6,6 +6,7 @@
 from Tkinter import *
 import tkSimpleDialog
 import tkMessageBox
+from MultiCall import MultiCallCreator
 
 import webbrowser
 import idlever
@@ -89,7 +90,8 @@
         self.vbar = vbar = Scrollbar(top, name='vbar')
         self.text_frame = text_frame = Frame(top)
         self.width = idleConf.GetOption('main','EditorWindow','width')
-        self.text = text = Text(text_frame, name='text', padx=5, wrap='none',
+        self.text = text = MultiCallCreator(Text)(
+                text_frame, name='text', padx=5, wrap='none',
                 foreground=idleConf.GetHighlight(currentTheme,
                         'normal',fgBg='fg'),
                 background=idleConf.GetHighlight(currentTheme,
@@ -264,8 +266,9 @@
         self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
         self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
         self.status_bar.pack(side=BOTTOM, fill=X)
-        self.text.bind('<KeyRelease>', self.set_line_and_column)
-        self.text.bind('<ButtonRelease>', self.set_line_and_column)
+        self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
+        self.text.event_add("<<set-line-and-column>>",
+                            "<KeyRelease>", "<ButtonRelease>")
         self.text.after_idle(self.set_line_and_column)
 
     def set_line_and_column(self, event=None):
@@ -355,6 +358,9 @@
         return "break"
 
     def copy(self,event):
+        if not self.text.tag_ranges("sel"):
+            # There is no selection, so do nothing and maybe interrupt.
+            return
         self.text.event_generate("<<Copy>>")
         return "break"
 
@@ -557,14 +563,28 @@
                 idleConf.GetOption('main','EditorWindow','font-size'),
                 fontWeight))
 
-    def ResetKeybindings(self):
-        "Update the keybindings if they are changed"
+    def RemoveKeybindings(self):
+        "Remove the keybindings before they are changed."
         # Called from configDialog.py
         self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
         keydefs = self.Bindings.default_keydefs
         for event, keylist in keydefs.items():
-            self.text.event_delete(event)
+            self.text.event_delete(event, *keylist)
+        for extensionName in self.get_standard_extension_names():
+            keydefs = idleConf.GetExtensionBindings(extensionName)
+            if keydefs:
+                for event, keylist in keydefs.items():
+                    self.text.event_delete(event, *keylist)
+
+    def ApplyKeybindings(self):
+        "Update the keybindings after they are changed"
+        # Called from configDialog.py
+        self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
         self.apply_bindings()
+        for extensionName in self.get_standard_extension_names():
+            keydefs = idleConf.GetExtensionBindings(extensionName)
+            if keydefs:
+                self.apply_bindings(keydefs)
         #update menu accelerators
         menuEventDict={}
         for menu in self.Bindings.menudefs:
@@ -1064,17 +1084,28 @@
             # open/close first need to find the last stmt
             lno = index2line(text.index('insert'))
             y = PyParse.Parser(self.indentwidth, self.tabwidth)
-            for context in self.num_context_lines:
-                startat = max(lno - context, 1)
-                startatindex = repr(startat) + ".0"
+            if not self.context_use_ps1:
+                for context in self.num_context_lines:
+                    startat = max(lno - context, 1)
+                    startatindex = `startat` + ".0"
+                    rawtext = text.get(startatindex, "insert")
+                    y.set_str(rawtext)
+                    bod = y.find_good_parse_start(
+                              self.context_use_ps1,
+                              self._build_char_in_string_func(startatindex))
+                    if bod is not None or startat == 1:
+                        break
+                y.set_lo(bod or 0)
+            else:
+                r = text.tag_prevrange("console", "insert")
+                if r:
+                    startatindex = r[1]
+                else:
+                    startatindex = "1.0"
                 rawtext = text.get(startatindex, "insert")
                 y.set_str(rawtext)
-                bod = y.find_good_parse_start(
-                          self.context_use_ps1,
-                          self._build_char_in_string_func(startatindex))
-                if bod is not None or startat == 1:
-                    break
-            y.set_lo(bod or 0)
+                y.set_lo(0)
+
             c = y.get_continuation_type()
             if c != PyParse.C_NONE:
                 # The current stmt hasn't ended yet.
diff --git a/Lib/idlelib/HyperParser.py b/Lib/idlelib/HyperParser.py
new file mode 100644
index 0000000..519de74
--- /dev/null
+++ b/Lib/idlelib/HyperParser.py
@@ -0,0 +1,241 @@
+"""
+HyperParser
+===========
+This module defines the HyperParser class, which provides advanced parsing
+abilities for the ParenMatch and other extensions.
+The HyperParser uses PyParser. PyParser is intended mostly to give information
+on the proper indentation of code. HyperParser gives some information on the
+structure of code, used by extensions to help the user.
+"""
+
+import string
+import keyword
+import PyParse
+
+class HyperParser:
+
+    def __init__(self, editwin, index):
+        """Initialize the HyperParser to analyze the surroundings of the given
+        index.
+        """
+
+        self.editwin = editwin
+        self.text = text = editwin.text
+
+        parser = PyParse.Parser(editwin.indentwidth, editwin.tabwidth)
+
+        def index2line(index):
+            return int(float(index))
+        lno = index2line(text.index(index))
+
+        if not editwin.context_use_ps1:
+            for context in editwin.num_context_lines:
+                startat = max(lno - context, 1)
+                startatindex = `startat` + ".0"
+                stopatindex = "%d.end" % lno
+                # We add the newline because PyParse requires a newline at end.
+                # We add a space so that index won't be at end of line, so that
+                # its status will be the same as the char before it, if should.
+                parser.set_str(text.get(startatindex, stopatindex)+' \n')
+                bod = parser.find_good_parse_start(
+                          editwin._build_char_in_string_func(startatindex))
+                if bod is not None or startat == 1:
+                    break
+            parser.set_lo(bod or 0)
+        else:
+            r = text.tag_prevrange("console", index)
+            if r:
+                startatindex = r[1]
+            else:
+                startatindex = "1.0"
+            stopatindex = "%d.end" % lno
+            # We add the newline because PyParse requires a newline at end.
+            # We add a space so that index won't be at end of line, so that
+            # its status will be the same as the char before it, if should.
+            parser.set_str(text.get(startatindex, stopatindex)+' \n')
+            parser.set_lo(0)
+
+        # We want what the parser has, except for the last newline and space.
+        self.rawtext = parser.str[:-2]
+        # As far as I can see, parser.str preserves the statement we are in,
+        # so that stopatindex can be used to synchronize the string with the
+        # text box indices.
+        self.stopatindex = stopatindex
+        self.bracketing = parser.get_last_stmt_bracketing()
+        # find which pairs of bracketing are openers. These always correspond
+        # to a character of rawtext.
+        self.isopener = [i>0 and self.bracketing[i][1] > self.bracketing[i-1][1]
+                         for i in range(len(self.bracketing))]
+
+        self.set_index(index)
+
+    def set_index(self, index):
+        """Set the index to which the functions relate. Note that it must be
+        in the same statement.
+        """
+        indexinrawtext = \
+            len(self.rawtext) - len(self.text.get(index, self.stopatindex))
+        if indexinrawtext < 0:
+            raise ValueError("The index given is before the analyzed statement")
+        self.indexinrawtext = indexinrawtext
+        # find the rightmost bracket to which index belongs
+        self.indexbracket = 0
+        while self.indexbracket < len(self.bracketing)-1 and \
+              self.bracketing[self.indexbracket+1][0] < self.indexinrawtext:
+            self.indexbracket += 1
+        if self.indexbracket < len(self.bracketing)-1 and \
+           self.bracketing[self.indexbracket+1][0] == self.indexinrawtext and \
+           not self.isopener[self.indexbracket+1]:
+            self.indexbracket += 1
+
+    def is_in_string(self):
+        """Is the index given to the HyperParser is in a string?"""
+        # The bracket to which we belong should be an opener.
+        # If it's an opener, it has to have a character.
+        return self.isopener[self.indexbracket] and \
+               self.rawtext[self.bracketing[self.indexbracket][0]] in ('"', "'")
+
+    def is_in_code(self):
+        """Is the index given to the HyperParser is in a normal code?"""
+        return not self.isopener[self.indexbracket] or \
+               self.rawtext[self.bracketing[self.indexbracket][0]] not in \
+                                                                ('#', '"', "'")
+
+    def get_surrounding_brackets(self, openers='([{', mustclose=False):
+        """If the index given to the HyperParser is surrounded by a bracket
+        defined in openers (or at least has one before it), return the
+        indices of the opening bracket and the closing bracket (or the
+        end of line, whichever comes first).
+        If it is not surrounded by brackets, or the end of line comes before
+        the closing bracket and mustclose is True, returns None.
+        """
+        bracketinglevel = self.bracketing[self.indexbracket][1]
+        before = self.indexbracket
+        while not self.isopener[before] or \
+              self.rawtext[self.bracketing[before][0]] not in openers or \
+              self.bracketing[before][1] > bracketinglevel:
+            before -= 1
+            if before < 0:
+                return None
+            bracketinglevel = min(bracketinglevel, self.bracketing[before][1])
+        after = self.indexbracket + 1
+        while after < len(self.bracketing) and \
+              self.bracketing[after][1] >= bracketinglevel:
+            after += 1
+
+        beforeindex = self.text.index("%s-%dc" %
+            (self.stopatindex, len(self.rawtext)-self.bracketing[before][0]))
+        if after >= len(self.bracketing) or \
+           self.bracketing[after][0] > len(self.rawtext):
+            if mustclose:
+                return None
+            afterindex = self.stopatindex
+        else:
+            # We are after a real char, so it is a ')' and we give the index
+            # before it.
+            afterindex = self.text.index("%s-%dc" %
+                (self.stopatindex,
+                 len(self.rawtext)-(self.bracketing[after][0]-1)))
+
+        return beforeindex, afterindex
+
+    # This string includes all chars that may be in a white space
+    _whitespace_chars = " \t\n\\"
+    # This string includes all chars that may be in an identifier
+    _id_chars = string.ascii_letters + string.digits + "_"
+    # This string includes all chars that may be the first char of an identifier
+    _id_first_chars = string.ascii_letters + "_"
+
+    # Given a string and pos, return the number of chars in the identifier
+    # which ends at pos, or 0 if there is no such one. Saved words are not
+    # identifiers.
+    def _eat_identifier(self, str, limit, pos):
+        i = pos
+        while i > limit and str[i-1] in self._id_chars:
+            i -= 1
+        if i < pos and (str[i] not in self._id_first_chars or \
+                        keyword.iskeyword(str[i:pos])):
+            i = pos
+        return pos - i
+
+    def get_expression(self):
+        """Return a string with the Python expression which ends at the given
+        index, which is empty if there is no real one.
+        """
+        if not self.is_in_code():
+            raise ValueError("get_expression should only be called if index "\
+                             "is inside a code.")
+
+        rawtext = self.rawtext
+        bracketing = self.bracketing
+
+        brck_index = self.indexbracket
+        brck_limit = bracketing[brck_index][0]
+        pos = self.indexinrawtext
+
+        last_identifier_pos = pos
+        postdot_phase = True
+
+        while 1:
+            # Eat whitespaces, comments, and if postdot_phase is False - one dot
+            while 1:
+                if pos>brck_limit and rawtext[pos-1] in self._whitespace_chars:
+                    # Eat a whitespace
+                    pos -= 1
+                elif not postdot_phase and \
+                     pos > brck_limit and rawtext[pos-1] == '.':
+                    # Eat a dot
+                    pos -= 1
+                    postdot_phase = True
+                # The next line will fail if we are *inside* a comment, but we
+                # shouldn't be.
+                elif pos == brck_limit and brck_index > 0 and \
+                     rawtext[bracketing[brck_index-1][0]] == '#':
+                    # Eat a comment
+                    brck_index -= 2
+                    brck_limit = bracketing[brck_index][0]
+                    pos = bracketing[brck_index+1][0]
+                else:
+                    # If we didn't eat anything, quit.
+                    break
+
+            if not postdot_phase:
+                # We didn't find a dot, so the expression end at the last
+                # identifier pos.
+                break
+
+            ret = self._eat_identifier(rawtext, brck_limit, pos)
+            if ret:
+                # There is an identifier to eat
+                pos = pos - ret
+                last_identifier_pos = pos
+                # Now, in order to continue the search, we must find a dot.
+                postdot_phase = False
+                # (the loop continues now)
+
+            elif pos == brck_limit:
+                # We are at a bracketing limit. If it is a closing bracket,
+                # eat the bracket, otherwise, stop the search.
+                level = bracketing[brck_index][1]
+                while brck_index > 0 and bracketing[brck_index-1][1] > level:
+                    brck_index -= 1
+                if bracketing[brck_index][0] == brck_limit:
+                    # We were not at the end of a closing bracket
+                    break
+                pos = bracketing[brck_index][0]
+                brck_index -= 1
+                brck_limit = bracketing[brck_index][0]
+                last_identifier_pos = pos
+                if rawtext[pos] in "([":
+                    # [] and () may be used after an identifier, so we
+                    # continue. postdot_phase is True, so we don't allow a dot.
+                    pass
+                else:
+                    # We can't continue after other types of brackets
+                    break
+
+            else:
+                # We've found an operator or something.
+                break
+
+        return rawtext[last_identifier_pos:self.indexinrawtext]
diff --git a/Lib/idlelib/MultiCall.py b/Lib/idlelib/MultiCall.py
new file mode 100644
index 0000000..ea8b140
--- /dev/null
+++ b/Lib/idlelib/MultiCall.py
@@ -0,0 +1,404 @@
+"""
+MultiCall - a class which inherits its methods from a Tkinter widget (Text, for
+example), but enables multiple calls of functions per virtual event - all
+matching events will be called, not only the most specific one. This is done
+by wrapping the event functions - event_add, event_delete and event_info.
+MultiCall recognizes only a subset of legal event sequences. Sequences which
+are not recognized are treated by the original Tk handling mechanism. A
+more-specific event will be called before a less-specific event.
+
+The recognized sequences are complete one-event sequences (no emacs-style
+Ctrl-X Ctrl-C, no shortcuts like <3>), for all types of events.
+Key/Button Press/Release events can have modifiers.
+The recognized modifiers are Shift, Control, Option and Command for Mac, and
+Control, Alt, Shift, Meta/M for other platforms.
+
+For all events which were handled by MultiCall, a new member is added to the
+event instance passed to the binded functions - mc_type. This is one of the
+event type constants defined in this module (such as MC_KEYPRESS).
+For Key/Button events (which are handled by MultiCall and may receive
+modifiers), another member is added - mc_state. This member gives the state
+of the recognized modifiers, as a combination of the modifier constants
+also defined in this module (for example, MC_SHIFT).
+Using these members is absolutely portable.
+
+The order by which events are called is defined by these rules:
+1. A more-specific event will be called before a less-specific event.
+2. A recently-binded event will be called before a previously-binded event,
+   unless this conflicts with the first rule.
+Each function will be called at most once for each event.
+"""
+
+import sys
+import os
+import string
+import re
+import Tkinter
+
+# the event type constants, which define the meaning of mc_type
+MC_KEYPRESS=0; MC_KEYRELEASE=1; MC_BUTTONPRESS=2; MC_BUTTONRELEASE=3;
+MC_ACTIVATE=4; MC_CIRCULATE=5; MC_COLORMAP=6; MC_CONFIGURE=7;
+MC_DEACTIVATE=8; MC_DESTROY=9; MC_ENTER=10; MC_EXPOSE=11; MC_FOCUSIN=12;
+MC_FOCUSOUT=13; MC_GRAVITY=14; MC_LEAVE=15; MC_MAP=16; MC_MOTION=17;
+MC_MOUSEWHEEL=18; MC_PROPERTY=19; MC_REPARENT=20; MC_UNMAP=21; MC_VISIBILITY=22;
+# the modifier state constants, which define the meaning of mc_state
+MC_SHIFT = 1<<0; MC_CONTROL = 1<<2; MC_ALT = 1<<3; MC_META = 1<<5
+MC_OPTION = 1<<6; MC_COMMAND = 1<<7
+
+# define the list of modifiers, to be used in complex event types.
+if sys.platform == "darwin" and sys.executable.count(".app"):
+    _modifiers = (("Shift",), ("Control",), ("Option",), ("Command",))
+    _modifier_masks = (MC_SHIFT, MC_CONTROL, MC_OPTION, MC_COMMAND)
+else:
+    _modifiers = (("Control",), ("Alt",), ("Shift",), ("Meta", "M"))
+    _modifier_masks = (MC_CONTROL, MC_ALT, MC_SHIFT, MC_META)
+
+# a dictionary to map a modifier name into its number
+_modifier_names = dict([(name, number)
+                         for number in range(len(_modifiers))
+                         for name in _modifiers[number]])
+
+# A binder is a class which binds functions to one type of event. It has two
+# methods: bind and unbind, which get a function and a parsed sequence, as
+# returned by _parse_sequence(). There are two types of binders:
+# _SimpleBinder handles event types with no modifiers and no detail.
+# No Python functions are called when no events are binded.
+# _ComplexBinder handles event types with modifiers and a detail.
+# A Python function is called each time an event is generated.
+
+class _SimpleBinder:
+    def __init__(self, type, widget, widgetinst):
+        self.type = type
+        self.sequence = '<'+_types[type][0]+'>'
+        self.widget = widget
+        self.widgetinst = widgetinst
+        self.bindedfuncs = []
+        self.handlerid = None
+
+    def bind(self, triplet, func):
+        if not self.handlerid:
+            def handler(event, l = self.bindedfuncs, mc_type = self.type):
+                event.mc_type = mc_type
+                wascalled = {}
+                for i in range(len(l)-1, -1, -1):
+                    func = l[i]
+                    if func not in wascalled:
+                        wascalled[func] = True
+                        r = func(event)
+                        if r:
+                            return r
+            self.handlerid = self.widget.bind(self.widgetinst,
+                                              self.sequence, handler)
+        self.bindedfuncs.append(func)
+
+    def unbind(self, triplet, func):
+        self.bindedfuncs.remove(func)
+        if not self.bindedfuncs:
+            self.widget.unbind(self.widgetinst, self.sequence, self.handlerid)
+            self.handlerid = None
+
+    def __del__(self):
+        if self.handlerid:
+            self.widget.unbind(self.widgetinst, self.sequence, self.handlerid)
+
+# An int in range(1 << len(_modifiers)) represents a combination of modifiers
+# (if the least significent bit is on, _modifiers[0] is on, and so on).
+# _state_subsets gives for each combination of modifiers, or *state*,
+# a list of the states which are a subset of it. This list is ordered by the
+# number of modifiers is the state - the most specific state comes first.
+_states = range(1 << len(_modifiers))
+_state_names = [reduce(lambda x, y: x + y,
+                       [_modifiers[i][0]+'-' for i in range(len(_modifiers))
+                        if (1 << i) & s],
+                       "")
+                for s in _states]
+_state_subsets = map(lambda i: filter(lambda j: not (j & (~i)), _states),
+                      _states)
+for l in _state_subsets:
+    l.sort(lambda a, b, nummod = lambda x: len(filter(lambda i: (1<<i) & x,
+                                                      range(len(_modifiers)))):
+           nummod(b) - nummod(a))
+# _state_codes gives for each state, the portable code to be passed as mc_state
+_state_codes = [reduce(lambda x, y: x | y,
+                       [_modifier_masks[i] for i in range(len(_modifiers))
+                        if (1 << i) & s],
+                       0)
+                for s in _states]
+
+class _ComplexBinder:
+    # This class binds many functions, and only unbinds them when it is deleted.
+    # self.handlerids is the list of seqs and ids of binded handler functions.
+    # The binded functions sit in a dictionary of lists of lists, which maps
+    # a detail (or None) and a state into a list of functions.
+    # When a new detail is discovered, handlers for all the possible states
+    # are binded.
+
+    def __create_handler(self, lists, mc_type, mc_state):
+        def handler(event, lists = lists,
+                    mc_type = mc_type, mc_state = mc_state,
+                    ishandlerrunning = self.ishandlerrunning,
+                    doafterhandler = self.doafterhandler):
+            ishandlerrunning[:] = [True]
+            event.mc_type = mc_type
+            event.mc_state = mc_state
+            wascalled = {}
+            r = None
+            for l in lists:
+                for i in range(len(l)-1, -1, -1):
+                    func = l[i]
+                    if func not in wascalled:
+                        wascalled[func] = True
+                        r = l[i](event)
+                        if r:
+                            break
+                if r:
+                    break
+            ishandlerrunning[:] = []
+            # Call all functions in doafterhandler and remove them from list
+            while doafterhandler:
+                doafterhandler.pop()()
+            if r:
+                return r
+        return handler
+
+    def __init__(self, type, widget, widgetinst):
+        self.type = type
+        self.typename = _types[type][0]
+        self.widget = widget
+        self.widgetinst = widgetinst
+        self.bindedfuncs = {None: [[] for s in _states]}
+        self.handlerids = []
+        # we don't want to change the lists of functions while a handler is
+        # running - it will mess up the loop and anyway, we usually want the
+        # change to happen from the next event. So we have a list of functions
+        # for the handler to run after it finishes calling the binded functions.
+        # It calls them only once.
+        # ishandlerrunning is a list. An empty one means no, otherwise - yes.
+        # this is done so that it would be mutable.
+        self.ishandlerrunning = []
+        self.doafterhandler = []
+        for s in _states:
+            lists = [self.bindedfuncs[None][i] for i in _state_subsets[s]]
+            handler = self.__create_handler(lists, type, _state_codes[s])
+            seq = '<'+_state_names[s]+self.typename+'>'
+            self.handlerids.append((seq, self.widget.bind(self.widgetinst,
+                                                          seq, handler)))
+
+    def bind(self, triplet, func):
+        if not self.bindedfuncs.has_key(triplet[2]):
+            self.bindedfuncs[triplet[2]] = [[] for s in _states]
+            for s in _states:
+                lists = [ self.bindedfuncs[detail][i]
+                          for detail in (triplet[2], None)
+                          for i in _state_subsets[s]       ]
+                handler = self.__create_handler(lists, self.type,
+                                                _state_codes[s])
+                seq = "<%s%s-%s>"% (_state_names[s], self.typename, triplet[2])
+                self.handlerids.append((seq, self.widget.bind(self.widgetinst,
+                                                              seq, handler)))
+        doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].append(func)
+        if not self.ishandlerrunning:
+            doit()
+        else:
+            self.doafterhandler.append(doit)
+
+    def unbind(self, triplet, func):
+        doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].remove(func)
+        if not self.ishandlerrunning:
+            doit()
+        else:
+            self.doafterhandler.append(doit)
+
+    def __del__(self):
+        for seq, id in self.handlerids:
+            self.widget.unbind(self.widgetinst, seq, id)
+
+# define the list of event types to be handled by MultiEvent. the order is
+# compatible with the definition of event type constants.
+_types = (
+    ("KeyPress", "Key"), ("KeyRelease",), ("ButtonPress", "Button"),
+    ("ButtonRelease",), ("Activate",), ("Circulate",), ("Colormap",),
+    ("Configure",), ("Deactivate",), ("Destroy",), ("Enter",), ("Expose",),
+    ("FocusIn",), ("FocusOut",), ("Gravity",), ("Leave",), ("Map",),
+    ("Motion",), ("MouseWheel",), ("Property",), ("Reparent",), ("Unmap",),
+    ("Visibility",),
+)
+
+# which binder should be used for every event type?
+_binder_classes = (_ComplexBinder,) * 4 + (_SimpleBinder,) * (len(_types)-4)
+
+# A dictionary to map a type name into its number
+_type_names = dict([(name, number)
+                     for number in range(len(_types))
+                     for name in _types[number]])
+
+_keysym_re = re.compile(r"^\w+$")
+_button_re = re.compile(r"^[1-5]$")
+def _parse_sequence(sequence):
+    """Get a string which should describe an event sequence. If it is
+    successfully parsed as one, return a tuple containing the state (as an int),
+    the event type (as an index of _types), and the detail - None if none, or a
+    string if there is one. If the parsing is unsuccessful, return None.
+    """
+    if not sequence or sequence[0] != '<' or sequence[-1] != '>':
+        return None
+    words = string.split(sequence[1:-1], '-')
+
+    modifiers = 0
+    while words and words[0] in _modifier_names:
+        modifiers |= 1 << _modifier_names[words[0]]
+        del words[0]
+
+    if words and words[0] in _type_names:
+        type = _type_names[words[0]]
+        del words[0]
+    else:
+        return None
+
+    if _binder_classes[type] is _SimpleBinder:
+        if modifiers or words:
+            return None
+        else:
+            detail = None
+    else:
+        # _ComplexBinder
+        if type in [_type_names[s] for s in ("KeyPress", "KeyRelease")]:
+            type_re = _keysym_re
+        else:
+            type_re = _button_re
+
+        if not words:
+            detail = None
+        elif len(words) == 1 and type_re.match(words[0]):
+            detail = words[0]
+        else:
+            return None
+
+    return modifiers, type, detail
+
+def _triplet_to_sequence(triplet):
+    if triplet[2]:
+        return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'-'+ \
+               triplet[2]+'>'
+    else:
+        return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'>'
+
+_multicall_dict = {}
+def MultiCallCreator(widget):
+    """Return a MultiCall class which inherits its methods from the
+    given widget class (for example, Tkinter.Text). This is used
+    instead of a templating mechanism.
+    """
+    if widget in _multicall_dict:
+        return _multicall_dict[widget]
+
+    class MultiCall (widget):
+        assert issubclass(widget, Tkinter.Misc)
+
+        def __init__(self, *args, **kwargs):
+            apply(widget.__init__, (self,)+args, kwargs)
+            # a dictionary which maps a virtual event to a tuple with:
+            #  0. the function binded
+            #  1. a list of triplets - the sequences it is binded to
+            self.__eventinfo = {}
+            self.__binders = [_binder_classes[i](i, widget, self)
+                              for i in range(len(_types))]
+
+        def bind(self, sequence=None, func=None, add=None):
+            #print "bind(%s, %s, %s) called." % (sequence, func, add)
+            if type(sequence) is str and len(sequence) > 2 and \
+               sequence[:2] == "<<" and sequence[-2:] == ">>":
+                if sequence in self.__eventinfo:
+                    ei = self.__eventinfo[sequence]
+                    if ei[0] is not None:
+                        for triplet in ei[1]:
+                            self.__binders[triplet[1]].unbind(triplet, ei[0])
+                    ei[0] = func
+                    if ei[0] is not None:
+                        for triplet in ei[1]:
+                            self.__binders[triplet[1]].bind(triplet, func)
+                else:
+                    self.__eventinfo[sequence] = [func, []]
+            return widget.bind(self, sequence, func, add)
+
+        def unbind(self, sequence, funcid=None):
+            if type(sequence) is str and len(sequence) > 2 and \
+               sequence[:2] == "<<" and sequence[-2:] == ">>" and \
+               sequence in self.__eventinfo:
+                func, triplets = self.__eventinfo[sequence]
+                if func is not None:
+                    for triplet in triplets:
+                        self.__binders[triplet[1]].unbind(triplet, func)
+                    self.__eventinfo[sequence][0] = None
+            return widget.unbind(self, sequence, funcid)
+
+        def event_add(self, virtual, *sequences):
+            #print "event_add(%s,%s) was called"%(repr(virtual),repr(sequences))
+            if virtual not in self.__eventinfo:
+                self.__eventinfo[virtual] = [None, []]
+
+            func, triplets = self.__eventinfo[virtual]
+            for seq in sequences:
+                triplet = _parse_sequence(seq)
+                if triplet is None:
+                    #print >> sys.stderr, "Seq. %s was added by Tkinter."%seq
+                    widget.event_add(self, virtual, seq)
+                else:
+                    if func is not None:
+                        self.__binders[triplet[1]].bind(triplet, func)
+                    triplets.append(triplet)
+
+        def event_delete(self, virtual, *sequences):
+            func, triplets = self.__eventinfo[virtual]
+            for seq in sequences:
+                triplet = _parse_sequence(seq)
+                if triplet is None:
+                    #print >> sys.stderr, "Seq. %s was deleted by Tkinter."%seq
+                    widget.event_delete(self, virtual, seq)
+                else:
+                    if func is not None:
+                        self.__binders[triplet[1]].unbind(triplet, func)
+                    triplets.remove(triplet)
+
+        def event_info(self, virtual=None):
+            if virtual is None or virtual not in self.__eventinfo:
+                return widget.event_info(self, virtual)
+            else:
+                return tuple(map(_triplet_to_sequence,
+                                 self.__eventinfo[virtual][1])) + \
+                       widget.event_info(self, virtual)
+
+        def __del__(self):
+            for virtual in self.__eventinfo:
+                func, triplets = self.__eventinfo[virtual]
+                if func:
+                    for triplet in triplets:
+                        self.__binders[triplet[1]].unbind(triplet, func)
+
+
+    _multicall_dict[widget] = MultiCall
+    return MultiCall
+
+if __name__ == "__main__":
+    # Test
+    root = Tkinter.Tk()
+    text = MultiCallCreator(Tkinter.Text)(root)
+    text.pack()
+    def bindseq(seq, n=[0]):
+        def handler(event):
+            print seq
+        text.bind("<<handler%d>>"%n[0], handler)
+        text.event_add("<<handler%d>>"%n[0], seq)
+        n[0] += 1
+    bindseq("<Key>")
+    bindseq("<Control-Key>")
+    bindseq("<Alt-Key-a>")
+    bindseq("<Control-Key-a>")
+    bindseq("<Alt-Control-Key-a>")
+    bindseq("<Key-b>")
+    bindseq("<Control-Button-1>")
+    bindseq("<Alt-Button-1>")
+    bindseq("<FocusOut>")
+    bindseq("<Enter>")
+    bindseq("<Leave>")
+    root.mainloop()
diff --git a/Lib/idlelib/ParenMatch.py b/Lib/idlelib/ParenMatch.py
index 407f468..673aee2 100644
--- a/Lib/idlelib/ParenMatch.py
+++ b/Lib/idlelib/ParenMatch.py
@@ -3,17 +3,14 @@
 When you hit a right paren, the cursor should move briefly to the left
 paren.  Paren here is used generically; the matching applies to
 parentheses, square brackets, and curly braces.
-
-WARNING: This extension will fight with the CallTips extension,
-because they both are interested in the KeyRelease-parenright event.
-We'll have to fix IDLE to do something reasonable when two or more
-extensions what to capture the same event.
 """
 
-import PyParse
-from EditorWindow import EditorWindow, index2line
+from HyperParser import HyperParser
 from configHandler import idleConf
 
+keysym_opener = {"parenright":'(', "bracketright":'[', "braceright":'{'}
+CHECK_DELAY = 100 # miliseconds
+
 class ParenMatch:
     """Highlight matching parentheses
 
@@ -31,7 +28,6 @@
         expression from the left paren to the right paren.
 
     TODO:
-        - fix interaction with CallTips
         - extend IDLE with configuration dialog to change options
         - implement rest of Emacs highlight styles (see below)
         - print mismatch warning in IDLE status window
@@ -41,7 +37,11 @@
     to the right of a right paren.  I don't know how to do that in Tk,
     so I haven't bothered.
     """
-    menudefs = []
+    menudefs = [
+        ('edit', [
+            ("Show surrounding parens", "<<flash-paren>>"),
+        ])
+    ]
     STYLE = idleConf.GetOption('extensions','ParenMatch','style',
             default='expression')
     FLASH_DELAY = idleConf.GetOption('extensions','ParenMatch','flash-delay',
@@ -50,14 +50,36 @@
     BELL = idleConf.GetOption('extensions','ParenMatch','bell',
             type='bool',default=1)
 
+    RESTORE_VIRTUAL_EVENT_NAME = "<<parenmatch-check-restore>>"
+    # We want the restore event be called before the usual return and
+    # backspace events.
+    RESTORE_SEQUENCES = ("<KeyPress>", "<ButtonPress>",
+                         "<Key-Return>", "<Key-BackSpace>")
+
     def __init__(self, editwin):
         self.editwin = editwin
         self.text = editwin.text
-        self.finder = LastOpenBracketFinder(editwin)
+        # Bind the check-restore event to the function restore_event,
+        # so that we can then use activate_restore (which calls event_add)
+        # and deactivate_restore (which calls event_delete).
+        editwin.text.bind(self.RESTORE_VIRTUAL_EVENT_NAME,
+                          self.restore_event)
         self.counter = 0
-        self._restore = None
+        self.is_restore_active = 0
         self.set_style(self.STYLE)
 
+    def activate_restore(self):
+        if not self.is_restore_active:
+            for seq in self.RESTORE_SEQUENCES:
+                self.text.event_add(self.RESTORE_VIRTUAL_EVENT_NAME, seq)
+            self.is_restore_active = True
+
+    def deactivate_restore(self):
+        if self.is_restore_active:
+            for seq in self.RESTORE_SEQUENCES:
+                self.text.event_delete(self.RESTORE_VIRTUAL_EVENT_NAME, seq)
+            self.is_restore_active = False
+
     def set_style(self, style):
         self.STYLE = style
         if style == "default":
@@ -67,23 +89,38 @@
             self.create_tag = self.create_tag_expression
             self.set_timeout = self.set_timeout_none
 
-    def flash_open_paren_event(self, event):
-        index = self.finder.find(keysym_type(event.keysym))
-        if index is None:
+    def flash_paren_event(self, event):
+        indices = HyperParser(self.editwin, "insert").get_surrounding_brackets()
+        if indices is None:
             self.warn_mismatched()
             return
-        self._restore = 1
-        self.create_tag(index)
+        self.activate_restore()
+        self.create_tag(indices)
+        self.set_timeout_last()
+
+    def paren_closed_event(self, event):
+        # If it was a shortcut and not really a closing paren, quit.
+        if self.text.get("insert-1c") not in (')',']','}'):
+            return
+        hp = HyperParser(self.editwin, "insert-1c")
+        if not hp.is_in_code():
+            return
+        indices = hp.get_surrounding_brackets(keysym_opener[event.keysym], True)
+        if indices is None:
+            self.warn_mismatched()
+            return
+        self.activate_restore()
+        self.create_tag(indices)
         self.set_timeout()
 
-    def check_restore_event(self, event=None):
-        if self._restore:
-            self.text.tag_delete("paren")
-            self._restore = None
+    def restore_event(self, event=None):
+        self.text.tag_delete("paren")
+        self.deactivate_restore()
+        self.counter += 1   # disable the last timer, if there is one.
 
     def handle_restore_timer(self, timer_count):
-        if timer_count + 1 == self.counter:
-            self.check_restore_event()
+        if timer_count == self.counter:
+            self.restore_event()
 
     def warn_mismatched(self):
         if self.BELL:
@@ -92,87 +129,43 @@
     # any one of the create_tag_XXX methods can be used depending on
     # the style
 
-    def create_tag_default(self, index):
+    def create_tag_default(self, indices):
         """Highlight the single paren that matches"""
-        self.text.tag_add("paren", index)
+        self.text.tag_add("paren", indices[0])
         self.text.tag_config("paren", self.HILITE_CONFIG)
 
-    def create_tag_expression(self, index):
+    def create_tag_expression(self, indices):
         """Highlight the entire expression"""
-        self.text.tag_add("paren", index, "insert")
+        if self.text.get(indices[1]) in (')', ']', '}'):
+            rightindex = indices[1]+"+1c"
+        else:
+            rightindex = indices[1]
+        self.text.tag_add("paren", indices[0], rightindex)
         self.text.tag_config("paren", self.HILITE_CONFIG)
 
     # any one of the set_timeout_XXX methods can be used depending on
     # the style
 
     def set_timeout_none(self):
-        """Highlight will remain until user input turns it off"""
-        pass
+        """Highlight will remain until user input turns it off
+        or the insert has moved"""
+        # After CHECK_DELAY, call a function which disables the "paren" tag
+        # if the event is for the most recent timer and the insert has changed,
+        # or schedules another call for itself.
+        self.counter += 1
+        def callme(callme, self=self, c=self.counter,
+                   index=self.text.index("insert")):
+            if index != self.text.index("insert"):
+                self.handle_restore_timer(c)
+            else:
+                self.editwin.text_frame.after(CHECK_DELAY, callme, callme)
+        self.editwin.text_frame.after(CHECK_DELAY, callme, callme)
 
     def set_timeout_last(self):
         """The last highlight created will be removed after .5 sec"""
         # associate a counter with an event; only disable the "paren"
         # tag if the event is for the most recent timer.
+        self.counter += 1
         self.editwin.text_frame.after(self.FLASH_DELAY,
                                       lambda self=self, c=self.counter: \
                                       self.handle_restore_timer(c))
-        self.counter = self.counter + 1
-
-def keysym_type(ks):
-    # Not all possible chars or keysyms are checked because of the
-    # limited context in which the function is used.
-    if ks == "parenright" or ks == "(":
-        return "paren"
-    if ks == "bracketright" or ks == "[":
-        return "bracket"
-    if ks == "braceright" or ks == "{":
-        return "brace"
-
-class LastOpenBracketFinder:
-    num_context_lines = EditorWindow.num_context_lines
-    indentwidth = EditorWindow.indentwidth
-    tabwidth = EditorWindow.tabwidth
-    context_use_ps1 = EditorWindow.context_use_ps1
-
-    def __init__(self, editwin):
-        self.editwin = editwin
-        self.text = editwin.text
-
-    def _find_offset_in_buf(self, lno):
-        y = PyParse.Parser(self.indentwidth, self.tabwidth)
-        for context in self.num_context_lines:
-            startat = max(lno - context, 1)
-            startatindex = repr(startat) + ".0"
-            # rawtext needs to contain everything up to the last
-            # character, which was the close paren.  the parser also
-            # requires that the last line ends with "\n"
-            rawtext = self.text.get(startatindex, "insert")[:-1] + "\n"
-            y.set_str(rawtext)
-            bod = y.find_good_parse_start(
-                        self.context_use_ps1,
-                        self._build_char_in_string_func(startatindex))
-            if bod is not None or startat == 1:
-                break
-        y.set_lo(bod or 0)
-        i = y.get_last_open_bracket_pos()
-        return i, y.str
-
-    def find(self, right_keysym_type):
-        """Return the location of the last open paren"""
-        lno = index2line(self.text.index("insert"))
-        i, buf = self._find_offset_in_buf(lno)
-        if i is None \
-           or keysym_type(buf[i]) != right_keysym_type:
-            return None
-        lines_back = buf[i:].count("\n") - 1
-        # subtract one for the "\n" added to please the parser
-        upto_open = buf[:i]
-        j = upto_open.rfind("\n") + 1 # offset of column 0 of line
-        offset = i - j
-        return "%d.%d" % (lno - lines_back, offset)
-
-    def _build_char_in_string_func(self, startindex):
-        def inner(offset, startindex=startindex,
-                  icis=self.editwin.is_char_in_string):
-            return icis(startindex + "%dc" % offset)
-        return inner
diff --git a/Lib/idlelib/PyParse.py b/Lib/idlelib/PyParse.py
index 1bf4919..1a9db67 100644
--- a/Lib/idlelib/PyParse.py
+++ b/Lib/idlelib/PyParse.py
@@ -14,9 +14,7 @@
 _synchre = re.compile(r"""
     ^
     [ \t]*
-    (?: if
-    |   for
-    |   while
+    (?: while
     |   else
     |   def
     |   return
@@ -145,29 +143,11 @@
     # This will be reliable iff given a reliable is_char_in_string
     # function, meaning that when it says "no", it's absolutely
     # guaranteed that the char is not in a string.
-    #
-    # Ack, hack: in the shell window this kills us, because there's
-    # no way to tell the differences between output, >>> etc and
-    # user input.  Indeed, IDLE's first output line makes the rest
-    # look like it's in an unclosed paren!:
-    # Python 1.5.2 (#0, Apr 13 1999, ...
 
-    def find_good_parse_start(self, use_ps1, is_char_in_string=None,
+    def find_good_parse_start(self, is_char_in_string=None,
                               _synchre=_synchre):
         str, pos = self.str, None
-        if use_ps1:
-            # shell window
-            ps1 = '\n' + sys.ps1
-            i = str.rfind(ps1)
-            if i >= 0:
-                pos = i + len(ps1)
-                # make it look like there's a newline instead
-                # of ps1 at the start -- hacking here once avoids
-                # repeated hackery later
-                self.str = str[:pos-1] + '\n' + str[pos:]
-            return pos
 
-        # File window -- real work.
         if not is_char_in_string:
             # no clue -- make the caller pass everything
             return None
@@ -363,6 +343,11 @@
     # Creates:
     #     self.stmt_start, stmt_end
     #         slice indices of last interesting stmt
+    #     self.stmt_bracketing
+    #         the bracketing structure of the last interesting stmt;
+    #         for example, for the statement "say(boo) or die", stmt_bracketing
+    #         will be [(0, 0), (3, 1), (8, 0)]. Strings and comments are
+    #         treated as brackets, for the matter.
     #     self.lastch
     #         last non-whitespace character before optional trailing
     #         comment
@@ -404,6 +389,7 @@
         lastch = ""
         stack = []  # stack of open bracket indices
         push_stack = stack.append
+        bracketing = [(p, 0)]
         while p < q:
             # suck up all except ()[]{}'"#\\
             m = _chew_ordinaryre(str, p, q)
@@ -424,6 +410,7 @@
 
             if ch in "([{":
                 push_stack(p)
+                bracketing.append((p, len(stack)))
                 lastch = ch
                 p = p+1
                 continue
@@ -433,6 +420,7 @@
                     del stack[-1]
                 lastch = ch
                 p = p+1
+                bracketing.append((p, len(stack)))
                 continue
 
             if ch == '"' or ch == "'":
@@ -443,14 +431,18 @@
                 # strings to a couple of characters per line.  study1
                 # also needed to keep track of newlines, and we don't
                 # have to.
+                bracketing.append((p, len(stack)+1))
                 lastch = ch
                 p = _match_stringre(str, p, q).end()
+                bracketing.append((p, len(stack)))
                 continue
 
             if ch == '#':
                 # consume comment and trailing newline
+                bracketing.append((p, len(stack)+1))
                 p = str.find('\n', p, q) + 1
                 assert p > 0
+                bracketing.append((p, len(stack)))
                 continue
 
             assert ch == '\\'
@@ -466,6 +458,7 @@
         self.lastch = lastch
         if stack:
             self.lastopenbracketpos = stack[-1]
+        self.stmt_bracketing = tuple(bracketing)
 
     # Assuming continuation is C_BRACKET, return the number
     # of spaces the next line should be indented.
@@ -590,3 +583,12 @@
     def get_last_open_bracket_pos(self):
         self._study2()
         return self.lastopenbracketpos
+
+    # the structure of the bracketing of the last interesting statement,
+    # in the format defined in _study2, or None if the text didn't contain
+    # anything
+    stmt_bracketing = None
+
+    def get_last_stmt_bracketing(self):
+        self._study2()
+        return self.stmt_bracketing
diff --git a/Lib/idlelib/PyShell.py b/Lib/idlelib/PyShell.py
index 5034417..f81091b 100644
--- a/Lib/idlelib/PyShell.py
+++ b/Lib/idlelib/PyShell.py
@@ -1091,11 +1091,12 @@
                 self.recall(self.text.get(next[0], next[1]), event)
                 return "break"
             # No stdin mark -- just get the current line, less any prompt
-            line = self.text.get("insert linestart", "insert lineend")
-            last_line_of_prompt = sys.ps1.split('\n')[-1]
-            if line.startswith(last_line_of_prompt):
-                line = line[len(last_line_of_prompt):]
-            self.recall(line, event)
+            indices = self.text.tag_nextrange("console", "insert linestart")
+            if indices and \
+               self.text.compare(indices[0], "<=", "insert linestart"):
+                self.recall(self.text.get(indices[1], "insert lineend"), event)
+            else:
+                self.recall(self.text.get("insert linestart", "insert lineend"), event)
             return "break"
         # If we're between the beginning of the line and the iomark, i.e.
         # in the prompt area, move to the end of the prompt
diff --git a/Lib/idlelib/config-extensions.def b/Lib/idlelib/config-extensions.def
index 4a4055f..8b3f90c 100644
--- a/Lib/idlelib/config-extensions.def
+++ b/Lib/idlelib/config-extensions.def
@@ -52,22 +52,30 @@
 
 [CallTips]
 enable=1
+[CallTips_cfgBindings]
+force-open-calltip=<Control-Key-backslash>
 [CallTips_bindings]
-paren-open=<Key-parenleft>
-paren-close=<Key-parenright>
-check-calltip-cancel=<KeyRelease>
-calltip-cancel=<ButtonPress> <Key-Escape>
+try-open-calltip=<KeyRelease-parenleft>
+refresh-calltip=<KeyRelease-parenright> <KeyRelease-0>
 
 [ParenMatch]
-enable=0
+enable=1
 style= expression
 flash-delay= 500
 bell= 1
-hilite-foreground= black
-hilite-background= #43cd80
+[ParenMatch_cfgBindings]
+flash-paren=<Control-Key-0>
 [ParenMatch_bindings]
-flash-open-paren=<KeyRelease-parenright> <KeyRelease-bracketright> <KeyRelease-braceright>
-check-restore=<KeyPress>
+paren-closed=<KeyRelease-parenright> <KeyRelease-bracketright> <KeyRelease-braceright>
+
+[AutoComplete]
+enable=1
+popupwait=0
+[AutoComplete_cfgBindings]
+force-open-completions=<Control-Key-space>
+[AutoComplete_bindings]
+autocomplete=<Key-Tab>
+try-open-completions=<KeyRelease-period> <KeyRelease-slash> <KeyRelease-backslash>
 
 [CodeContext]
 enable=1
diff --git a/Lib/idlelib/configDialog.py b/Lib/idlelib/configDialog.py
index 63bcae2..2d8835c 100644
--- a/Lib/idlelib/configDialog.py
+++ b/Lib/idlelib/configDialog.py
@@ -1106,6 +1106,13 @@
             idleConf.userCfg[configType].Save()
         self.ResetChangedItems() #clear the changed items dict
 
+    def DeactivateCurrentConfig(self):
+        #Before a config is saved, some cleanup of current
+        #config must be done - remove the previous keybindings
+        winInstances=self.parent.instance_dict.keys()
+        for instance in winInstances:
+            instance.RemoveKeybindings()
+
     def ActivateConfigChanges(self):
         "Dynamically apply configuration changes"
         winInstances=self.parent.instance_dict.keys()
@@ -1113,7 +1120,7 @@
             instance.ResetColorizer()
             instance.ResetFont()
             instance.set_notabs_indentwidth()
-            instance.ResetKeybindings()
+            instance.ApplyKeybindings()
             instance.reset_help_menu_entries()
 
     def Cancel(self):
@@ -1124,6 +1131,7 @@
         self.destroy()
 
     def Apply(self):
+        self.DeactivateCurrentConfig()
         self.SaveAllChangedConfigs()
         self.ActivateConfigChanges()
 
diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py
index 8adb6f1..ae810c4 100644
--- a/Lib/idlelib/run.py
+++ b/Lib/idlelib/run.py
@@ -9,6 +9,8 @@
 import Queue
 
 import CallTips
+import AutoComplete
+
 import RemoteDebugger
 import RemoteObjectBrowser
 import StackViewer
@@ -275,6 +277,7 @@
         self.rpchandler = rpchandler
         self.locals = __main__.__dict__
         self.calltip = CallTips.CallTips()
+        self.autocomplete = AutoComplete.AutoComplete()
 
     def runcode(self, code):
         try:
@@ -305,6 +308,9 @@
     def get_the_calltip(self, name):
         return self.calltip.fetch_tip(name)
 
+    def get_the_completion_list(self, what, mode):
+        return self.autocomplete.fetch_completions(what, mode)
+
     def stackviewer(self, flist_oid=None):
         if self.usr_exc_info:
             typ, val, tb = self.usr_exc_info