paren matching extension.  warning: in current version of IDLE, can
not run this extension and CallTips extension at the same time.
diff --git a/Tools/idle/ParenMatch.py b/Tools/idle/ParenMatch.py
new file mode 100644
index 0000000..7500603
--- /dev/null
+++ b/Tools/idle/ParenMatch.py
@@ -0,0 +1,193 @@
+"""ParenMatch -- An IDLE extension for parenthesis matching.
+
+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 string
+
+import PyParse
+from AutoIndent import AutoIndent, index2line
+
+class ParenMatch:
+    """Highlight matching parentheses
+
+    There are three supported style of paren matching, based loosely
+    on the Emacs options.  The style is select based on the 
+    HILITE_STYLE attribute; it can be changed used the set_style
+    method.
+
+    The supported styles are:
+
+    default -- When a right paren is typed, highlight the matching
+        left paren for 1/2 sec.
+
+    expression -- When a right paren is typed, highlight the entire
+        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
+
+    Note: In Emacs, there are several styles of highlight where the
+    matching paren is highlighted whenever the cursor is immediately
+    to the right of a right paren.  I don't know how to do that in Tk,
+    so I haven't bothered.
+    """
+    
+    menudefs = []
+    
+    keydefs = {
+        '<<flash-open-paren>>' : ('<KeyRelease-parenright>',
+                                  '<KeyRelease-bracketright>',
+                                  '<KeyRelease-braceright>'),
+        '<<check-restore>>' : ('<KeyPress>',),
+    }
+
+    windows_keydefs = {}
+    unix_keydefs = {}
+
+    STYLE = "default" # or "expression"
+    FLASH_DELAY = 500
+    HILITE_CONFIG = {"foreground": "black",
+                     "background": "#43cd80", # SeaGreen3
+                     }
+    BELL = 1 # set to false for no bell
+
+    def __init__(self, editwin):
+        self.editwin = editwin
+        self.text = editwin.text
+        self.finder = LastOpenBracketFinder(editwin)
+        self.counter = 0
+        self._restore = None
+        self.set_style(self.STYLE)
+
+    def set_style(self, style):
+        self.STYLE = style
+        if style == "default":
+            self.create_tag = self.create_tag_default
+            self.set_timeout = self.set_timeout_last
+        elif style == "expression":
+            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:
+            self.warn_mismatched()
+            return
+        self._restore = 1
+        self.create_tag(index)
+        self.set_timeout()
+
+    def check_restore_event(self, event=None):
+        if self._restore:
+            self.text.tag_delete("paren")
+            self._restore = None
+
+    def handle_restore_timer(self, timer_count):
+        if timer_count + 1 == self.counter:
+            self.check_restore_event()
+
+    def warn_mismatched(self):
+        if self.BELL:
+            self.text.bell()
+
+    # any one of the create_tag_XXX methods can be used depending on
+    # the style
+
+    def create_tag_default(self, index):
+        """Highlight the single paren that matches"""
+        self.text.tag_add("paren", index)
+        self.text.tag_config("paren", self.HILITE_CONFIG)
+
+    def create_tag_expression(self, index):
+        """Highlight the entire expression"""
+        self.text.tag_add("paren", index, "insert")
+        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
+
+    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.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 = AutoIndent.num_context_lines
+    indentwidth = AutoIndent.indentwidth
+    tabwidth = AutoIndent.tabwidth
+    context_use_ps1 = AutoIndent.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 = `startat` + ".0"
+            # rawtext needs to contain everything up to the last
+            # character, which was the close paren.  also need to
+            # append "\n" to please the parser, which seems to expect
+            # a complete line
+            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:
+            return i
+        if 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
+