Checking in IDLEFORK exactly as it appears in the idlefork CVS.
On a branch, for now.
diff --git a/Tools/idle/AutoIndent.py b/Tools/idle/AutoIndent.py
deleted file mode 100644
index 7bc195b..0000000
--- a/Tools/idle/AutoIndent.py
+++ /dev/null
@@ -1,551 +0,0 @@
-#from Tkinter import TclError
-#import tkMessageBox
-#import tkSimpleDialog
-
-###$ event <<newline-and-indent>>
-###$ win <Key-Return>
-###$ win <KP_Enter>
-###$ unix <Key-Return>
-###$ unix <KP_Enter>
-
-###$ event <<indent-region>>
-###$ win <Control-bracketright>
-###$ unix <Alt-bracketright>
-###$ unix <Control-bracketright>
-
-###$ event <<dedent-region>>
-###$ win <Control-bracketleft>
-###$ unix <Alt-bracketleft>
-###$ unix <Control-bracketleft>
-
-###$ event <<comment-region>>
-###$ win <Alt-Key-3>
-###$ unix <Alt-Key-3>
-
-###$ event <<uncomment-region>>
-###$ win <Alt-Key-4>
-###$ unix <Alt-Key-4>
-
-###$ event <<tabify-region>>
-###$ win <Alt-Key-5>
-###$ unix <Alt-Key-5>
-
-###$ event <<untabify-region>>
-###$ win <Alt-Key-6>
-###$ unix <Alt-Key-6>
-
-import PyParse
-
-class AutoIndent:
-
-    menudefs = [
-        ('edit', [
-            None,
-            ('_Indent region', '<<indent-region>>'),
-            ('_Dedent region', '<<dedent-region>>'),
-            ('Comment _out region', '<<comment-region>>'),
-            ('U_ncomment region', '<<uncomment-region>>'),
-            ('Tabify region', '<<tabify-region>>'),
-            ('Untabify region', '<<untabify-region>>'),
-            ('Toggle tabs', '<<toggle-tabs>>'),
-            ('New indent width', '<<change-indentwidth>>'),
-        ]),
-    ]
-
-    keydefs = {
-        '<<smart-backspace>>': ['<Key-BackSpace>'],
-        '<<newline-and-indent>>': ['<Key-Return>', '<KP_Enter>'],
-        '<<smart-indent>>': ['<Key-Tab>']
-    }
-
-    windows_keydefs = {
-        '<<indent-region>>': ['<Control-bracketright>'],
-        '<<dedent-region>>': ['<Control-bracketleft>'],
-        '<<comment-region>>': ['<Alt-Key-3>'],
-        '<<uncomment-region>>': ['<Alt-Key-4>'],
-        '<<tabify-region>>': ['<Alt-Key-5>'],
-        '<<untabify-region>>': ['<Alt-Key-6>'],
-        '<<toggle-tabs>>': ['<Alt-Key-t>'],
-        '<<change-indentwidth>>': ['<Alt-Key-u>'],
-    }
-
-    unix_keydefs = {
-        '<<indent-region>>': ['<Alt-bracketright>',
-                              '<Meta-bracketright>',
-                              '<Control-bracketright>'],
-        '<<dedent-region>>': ['<Alt-bracketleft>',
-                              '<Meta-bracketleft>',
-                              '<Control-bracketleft>'],
-        '<<comment-region>>': ['<Alt-Key-3>', '<Meta-Key-3>'],
-        '<<uncomment-region>>': ['<Alt-Key-4>', '<Meta-Key-4>'],
-        '<<tabify-region>>': ['<Alt-Key-5>', '<Meta-Key-5>'],
-        '<<untabify-region>>': ['<Alt-Key-6>', '<Meta-Key-6>'],
-        '<<toggle-tabs>>': ['<Alt-Key-t>'],
-        '<<change-indentwidth>>': ['<Alt-Key-u>'],
-    }
-
-    # usetabs true  -> literal tab characters are used by indent and
-    #                  dedent cmds, possibly mixed with spaces if
-    #                  indentwidth is not a multiple of tabwidth
-    #         false -> tab characters are converted to spaces by indent
-    #                  and dedent cmds, and ditto TAB keystrokes
-    # indentwidth is the number of characters per logical indent level.
-    # tabwidth is the display width of a literal tab character.
-    # CAUTION:  telling Tk to use anything other than its default
-    # tab setting causes it to use an entirely different tabbing algorithm,
-    # treating tab stops as fixed distances from the left margin.
-    # Nobody expects this, so for now tabwidth should never be changed.
-    usetabs = 1
-    indentwidth = 4
-    tabwidth = 8    # for IDLE use, must remain 8 until Tk is fixed
-
-    # If context_use_ps1 is true, parsing searches back for a ps1 line;
-    # else searches for a popular (if, def, ...) Python stmt.
-    context_use_ps1 = 0
-
-    # When searching backwards for a reliable place to begin parsing,
-    # first start num_context_lines[0] lines back, then
-    # num_context_lines[1] lines back if that didn't work, and so on.
-    # The last value should be huge (larger than the # of lines in a
-    # conceivable file).
-    # Making the initial values larger slows things down more often.
-    num_context_lines = 50, 500, 5000000
-
-    def __init__(self, editwin):
-        self.editwin = editwin
-        self.text = editwin.text
-
-    def config(self, **options):
-        for key, value in options.items():
-            if key == 'usetabs':
-                self.usetabs = value
-            elif key == 'indentwidth':
-                self.indentwidth = value
-            elif key == 'tabwidth':
-                self.tabwidth = value
-            elif key == 'context_use_ps1':
-                self.context_use_ps1 = value
-            else:
-                raise KeyError, "bad option name: %s" % `key`
-
-    # If ispythonsource and guess are true, guess a good value for
-    # indentwidth based on file content (if possible), and if
-    # indentwidth != tabwidth set usetabs false.
-    # In any case, adjust the Text widget's view of what a tab
-    # character means.
-
-    def set_indentation_params(self, ispythonsource, guess=1):
-        if guess and ispythonsource:
-            i = self.guess_indent()
-            if 2 <= i <= 8:
-                self.indentwidth = i
-            if self.indentwidth != self.tabwidth:
-                self.usetabs = 0
-
-        self.editwin.set_tabwidth(self.tabwidth)
-
-    def smart_backspace_event(self, event):
-        text = self.text
-        first, last = self.editwin.get_selection_indices()
-        if first and last:
-            text.delete(first, last)
-            text.mark_set("insert", first)
-            return "break"
-        # Delete whitespace left, until hitting a real char or closest
-        # preceding virtual tab stop.
-        chars = text.get("insert linestart", "insert")
-        if chars == '':
-            if text.compare("insert", ">", "1.0"):
-                # easy: delete preceding newline
-                text.delete("insert-1c")
-            else:
-                text.bell()     # at start of buffer
-            return "break"
-        if  chars[-1] not in " \t":
-            # easy: delete preceding real char
-            text.delete("insert-1c")
-            return "break"
-        # Ick.  It may require *inserting* spaces if we back up over a
-        # tab character!  This is written to be clear, not fast.
-        tabwidth = self.tabwidth
-        have = len(chars.expandtabs(tabwidth))
-        assert have > 0
-        want = ((have - 1) // self.indentwidth) * self.indentwidth
-        ncharsdeleted = 0
-        while 1:
-            chars = chars[:-1]
-            ncharsdeleted = ncharsdeleted + 1
-            have = len(chars.expandtabs(tabwidth))
-            if have <= want or chars[-1] not in " \t":
-                break
-        text.undo_block_start()
-        text.delete("insert-%dc" % ncharsdeleted, "insert")
-        if have < want:
-            text.insert("insert", ' ' * (want - have))
-        text.undo_block_stop()
-        return "break"
-
-    def smart_indent_event(self, event):
-        # if intraline selection:
-        #     delete it
-        # elif multiline selection:
-        #     do indent-region & return
-        # indent one level
-        text = self.text
-        first, last = self.editwin.get_selection_indices()
-        text.undo_block_start()
-        try:
-            if first and last:
-                if index2line(first) != index2line(last):
-                    return self.indent_region_event(event)
-                text.delete(first, last)
-                text.mark_set("insert", first)
-            prefix = text.get("insert linestart", "insert")
-            raw, effective = classifyws(prefix, self.tabwidth)
-            if raw == len(prefix):
-                # only whitespace to the left
-                self.reindent_to(effective + self.indentwidth)
-            else:
-                if self.usetabs:
-                    pad = '\t'
-                else:
-                    effective = len(prefix.expandtabs(self.tabwidth))
-                    n = self.indentwidth
-                    pad = ' ' * (n - effective % n)
-                text.insert("insert", pad)
-            text.see("insert")
-            return "break"
-        finally:
-            text.undo_block_stop()
-
-    def newline_and_indent_event(self, event):
-        text = self.text
-        first, last = self.editwin.get_selection_indices()
-        text.undo_block_start()
-        try:
-            if first and last:
-                text.delete(first, last)
-                text.mark_set("insert", first)
-            line = text.get("insert linestart", "insert")
-            i, n = 0, len(line)
-            while i < n and line[i] in " \t":
-                i = i+1
-            if i == n:
-                # the cursor is in or at leading indentation; just inject
-                # an empty line at the start
-                text.insert("insert linestart", '\n')
-                return "break"
-            indent = line[:i]
-            # strip whitespace before insert point
-            i = 0
-            while line and line[-1] in " \t":
-                line = line[:-1]
-                i = i+1
-            if i:
-                text.delete("insert - %d chars" % i, "insert")
-            # strip whitespace after insert point
-            while text.get("insert") in " \t":
-                text.delete("insert")
-            # start new line
-            text.insert("insert", '\n')
-
-            # adjust indentation for continuations and block
-            # 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 = `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)
-            c = y.get_continuation_type()
-            if c != PyParse.C_NONE:
-                # The current stmt hasn't ended yet.
-                if c == PyParse.C_STRING:
-                    # inside a string; just mimic the current indent
-                    text.insert("insert", indent)
-                elif c == PyParse.C_BRACKET:
-                    # line up with the first (if any) element of the
-                    # last open bracket structure; else indent one
-                    # level beyond the indent of the line with the
-                    # last open bracket
-                    self.reindent_to(y.compute_bracket_indent())
-                elif c == PyParse.C_BACKSLASH:
-                    # if more than one line in this stmt already, just
-                    # mimic the current indent; else if initial line
-                    # has a start on an assignment stmt, indent to
-                    # beyond leftmost =; else to beyond first chunk of
-                    # non-whitespace on initial line
-                    if y.get_num_lines_in_stmt() > 1:
-                        text.insert("insert", indent)
-                    else:
-                        self.reindent_to(y.compute_backslash_indent())
-                else:
-                    assert 0, "bogus continuation type " + `c`
-                return "break"
-
-            # This line starts a brand new stmt; indent relative to
-            # indentation of initial line of closest preceding
-            # interesting stmt.
-            indent = y.get_base_indent_string()
-            text.insert("insert", indent)
-            if y.is_block_opener():
-                self.smart_indent_event(event)
-            elif indent and y.is_block_closer():
-                self.smart_backspace_event(event)
-            return "break"
-        finally:
-            text.see("insert")
-            text.undo_block_stop()
-
-    auto_indent = newline_and_indent_event
-
-    # Our editwin provides a is_char_in_string function that works
-    # with a Tk text index, but PyParse only knows about offsets into
-    # a string. This builds a function for PyParse that accepts an
-    # 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
-
-    def indent_region_event(self, event):
-        head, tail, chars, lines = self.get_region()
-        for pos in range(len(lines)):
-            line = lines[pos]
-            if line:
-                raw, effective = classifyws(line, self.tabwidth)
-                effective = effective + self.indentwidth
-                lines[pos] = self._make_blanks(effective) + line[raw:]
-        self.set_region(head, tail, chars, lines)
-        return "break"
-
-    def dedent_region_event(self, event):
-        head, tail, chars, lines = self.get_region()
-        for pos in range(len(lines)):
-            line = lines[pos]
-            if line:
-                raw, effective = classifyws(line, self.tabwidth)
-                effective = max(effective - self.indentwidth, 0)
-                lines[pos] = self._make_blanks(effective) + line[raw:]
-        self.set_region(head, tail, chars, lines)
-        return "break"
-
-    def comment_region_event(self, event):
-        head, tail, chars, lines = self.get_region()
-        for pos in range(len(lines) - 1):
-            line = lines[pos]
-            lines[pos] = '##' + line
-        self.set_region(head, tail, chars, lines)
-
-    def uncomment_region_event(self, event):
-        head, tail, chars, lines = self.get_region()
-        for pos in range(len(lines)):
-            line = lines[pos]
-            if not line:
-                continue
-            if line[:2] == '##':
-                line = line[2:]
-            elif line[:1] == '#':
-                line = line[1:]
-            lines[pos] = line
-        self.set_region(head, tail, chars, lines)
-
-    def tabify_region_event(self, event):
-        head, tail, chars, lines = self.get_region()
-        tabwidth = self._asktabwidth()
-        for pos in range(len(lines)):
-            line = lines[pos]
-            if line:
-                raw, effective = classifyws(line, tabwidth)
-                ntabs, nspaces = divmod(effective, tabwidth)
-                lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
-        self.set_region(head, tail, chars, lines)
-
-    def untabify_region_event(self, event):
-        head, tail, chars, lines = self.get_region()
-        tabwidth = self._asktabwidth()
-        for pos in range(len(lines)):
-            lines[pos] = lines[pos].expandtabs(tabwidth)
-        self.set_region(head, tail, chars, lines)
-
-    def toggle_tabs_event(self, event):
-        if self.editwin.askyesno(
-              "Toggle tabs",
-              "Turn tabs " + ("on", "off")[self.usetabs] + "?",
-              parent=self.text):
-            self.usetabs = not self.usetabs
-        return "break"
-
-    # XXX this isn't bound to anything -- see class tabwidth comments
-    def change_tabwidth_event(self, event):
-        new = self._asktabwidth()
-        if new != self.tabwidth:
-            self.tabwidth = new
-            self.set_indentation_params(0, guess=0)
-        return "break"
-
-    def change_indentwidth_event(self, event):
-        new = self.editwin.askinteger(
-                  "Indent width",
-                  "New indent width (1-16)",
-                  parent=self.text,
-                  initialvalue=self.indentwidth,
-                  minvalue=1,
-                  maxvalue=16)
-        if new and new != self.indentwidth:
-            self.indentwidth = new
-        return "break"
-
-    def get_region(self):
-        text = self.text
-        first, last = self.editwin.get_selection_indices()
-        if first and last:
-            head = text.index(first + " linestart")
-            tail = text.index(last + "-1c lineend +1c")
-        else:
-            head = text.index("insert linestart")
-            tail = text.index("insert lineend +1c")
-        chars = text.get(head, tail)
-        lines = chars.split("\n")
-        return head, tail, chars, lines
-
-    def set_region(self, head, tail, chars, lines):
-        text = self.text
-        newchars = "\n".join(lines)
-        if newchars == chars:
-            text.bell()
-            return
-        text.tag_remove("sel", "1.0", "end")
-        text.mark_set("insert", head)
-        text.undo_block_start()
-        text.delete(head, tail)
-        text.insert(head, newchars)
-        text.undo_block_stop()
-        text.tag_add("sel", head, "insert")
-
-    # Make string that displays as n leading blanks.
-
-    def _make_blanks(self, n):
-        if self.usetabs:
-            ntabs, nspaces = divmod(n, self.tabwidth)
-            return '\t' * ntabs + ' ' * nspaces
-        else:
-            return ' ' * n
-
-    # Delete from beginning of line to insert point, then reinsert
-    # column logical (meaning use tabs if appropriate) spaces.
-
-    def reindent_to(self, column):
-        text = self.text
-        text.undo_block_start()
-        if text.compare("insert linestart", "!=", "insert"):
-            text.delete("insert linestart", "insert")
-        if column:
-            text.insert("insert", self._make_blanks(column))
-        text.undo_block_stop()
-
-    def _asktabwidth(self):
-        return self.editwin.askinteger(
-            "Tab width",
-            "Spaces per tab?",
-            parent=self.text,
-            initialvalue=self.tabwidth,
-            minvalue=1,
-            maxvalue=16) or self.tabwidth
-
-    # Guess indentwidth from text content.
-    # Return guessed indentwidth.  This should not be believed unless
-    # it's in a reasonable range (e.g., it will be 0 if no indented
-    # blocks are found).
-
-    def guess_indent(self):
-        opener, indented = IndentSearcher(self.text, self.tabwidth).run()
-        if opener and indented:
-            raw, indentsmall = classifyws(opener, self.tabwidth)
-            raw, indentlarge = classifyws(indented, self.tabwidth)
-        else:
-            indentsmall = indentlarge = 0
-        return indentlarge - indentsmall
-
-# "line.col" -> line, as an int
-def index2line(index):
-    return int(float(index))
-
-# Look at the leading whitespace in s.
-# Return pair (# of leading ws characters,
-#              effective # of leading blanks after expanding
-#              tabs to width tabwidth)
-
-def classifyws(s, tabwidth):
-    raw = effective = 0
-    for ch in s:
-        if ch == ' ':
-            raw = raw + 1
-            effective = effective + 1
-        elif ch == '\t':
-            raw = raw + 1
-            effective = (effective // tabwidth + 1) * tabwidth
-        else:
-            break
-    return raw, effective
-
-import tokenize
-_tokenize = tokenize
-del tokenize
-
-class IndentSearcher:
-
-    # .run() chews over the Text widget, looking for a block opener
-    # and the stmt following it.  Returns a pair,
-    #     (line containing block opener, line containing stmt)
-    # Either or both may be None.
-
-    def __init__(self, text, tabwidth):
-        self.text = text
-        self.tabwidth = tabwidth
-        self.i = self.finished = 0
-        self.blkopenline = self.indentedline = None
-
-    def readline(self):
-        if self.finished:
-            return ""
-        i = self.i = self.i + 1
-        mark = `i` + ".0"
-        if self.text.compare(mark, ">=", "end"):
-            return ""
-        return self.text.get(mark, mark + " lineend+1c")
-
-    def tokeneater(self, type, token, start, end, line,
-                   INDENT=_tokenize.INDENT,
-                   NAME=_tokenize.NAME,
-                   OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
-        if self.finished:
-            pass
-        elif type == NAME and token in OPENERS:
-            self.blkopenline = line
-        elif type == INDENT and self.blkopenline:
-            self.indentedline = line
-            self.finished = 1
-
-    def run(self):
-        save_tabsize = _tokenize.tabsize
-        _tokenize.tabsize = self.tabwidth
-        try:
-            try:
-                _tokenize.tokenize(self.readline, self.tokeneater)
-            except _tokenize.TokenError:
-                # since we cut off the tokenizer early, we can trigger
-                # spurious errors
-                pass
-        finally:
-            _tokenize.tabsize = save_tabsize
-        return self.blkopenline, self.indentedline
diff --git a/Tools/idle/CREDITS.txt b/Tools/idle/CREDITS.txt
new file mode 100644
index 0000000..fd7af95
--- /dev/null
+++ b/Tools/idle/CREDITS.txt
@@ -0,0 +1,36 @@
+IDLEfork Credits
+==================
+
+Guido van Rossum, as well as being the creator of the Python language, is
+the original creator of IDLE.  He also developed the RPC code and Remote
+Debugger extension used in IDLEfork.
+
+The IDLEfork project was initiated and brought up to version 0.7.1 primarily
+by David Scherer, with help from Peter Schneider-Kamp and Nicholas Riley.
+Bruce Sherwood has contributed considerable time testing and suggesting
+improvements.
+
+Besides Guido, the main developers who have been active on IDLEfork version
+0.8.1 and later are Stephen M. Gava, who implemented the Configuration GUI, the
+new configuration system, and the new About menu, and Kurt B. Kaiser, who
+completed the integration of the RPC and remote debugger, and made a number of
+usability enhancements.
+
+Other contributors include Raymond Hettinger, Tony Lownds (Mac integration),
+Neal Norwitz (code check and clean-up), and Chui Tey (RPC integration, debugger
+integration and persistent breakpoints).
+
+Hernan Foffani, Christos Georgiou, Jason Orendorff, Josh Robb, and Bruce
+Sherwood have submitted useful patches.  Thanks, guys!
+
+There are others who should be included here, especially those who contributed
+to IDLE versions prior to 0.8, principally Mark Hammond, Jeremy Hylton,
+Tim Peters, and Moshe Zadka.  For additional details refer to NEWS.txt and
+Changelog.
+
+Please contact the IDLEfork maintainer to have yourself included here if you
+are one of those we missed! 
+
+Contact details at http://idlefork.sourceforge.net
+
+
diff --git a/Tools/idle/FrameViewer.py b/Tools/idle/FrameViewer.py
deleted file mode 100644
index 2ce0935..0000000
--- a/Tools/idle/FrameViewer.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from repr import Repr
-from Tkinter import *
-
-class FrameViewer:
-
-    def __init__(self, root, frame):
-        self.root = root
-        self.frame = frame
-        self.top = Toplevel(self.root)
-        self.repr = Repr()
-        self.repr.maxstring = 60
-        self.load_variables()
-
-    def load_variables(self):
-        row = 0
-        if self.frame.f_locals is not self.frame.f_globals:
-            l = Label(self.top, text="Local Variables",
-                      borderwidth=2, relief="raised")
-            l.grid(row=row, column=0, columnspan=2, sticky="ew")
-            row = self.load_names(self.frame.f_locals, row+1)
-        l = Label(self.top, text="Global Variables",
-                  borderwidth=2, relief="raised")
-        l.grid(row=row, column=0, columnspan=2, sticky="ew")
-        row = self.load_names(self.frame.f_globals, row+1)
-
-    def load_names(self, dict, row):
-        names = dict.keys()
-        names.sort()
-        for name in names:
-            value = dict[name]
-            svalue = self.repr.repr(value)
-            l = Label(self.top, text=name)
-            l.grid(row=row, column=0, sticky="w")
-            l = Entry(self.top, width=60, borderwidth=0)
-            l.insert(0, svalue)
-            l.grid(row=row, column=1, sticky="w")
-            row = row+1
-        return row
diff --git a/Tools/idle/HISTORY.txt b/Tools/idle/HISTORY.txt
new file mode 100644
index 0000000..9312f32
--- /dev/null
+++ b/Tools/idle/HISTORY.txt
@@ -0,0 +1,180 @@
+IDLE History
+============
+
+This file contains the release messages for previous IDLE releases.
+As you read on you go back to the dark ages of IDLE's history.
+
+
+IDLE 0.5 - February 2000 - Release Notes
+----------------------------------------
+
+This is an early release of IDLE, my own attempt at a Tkinter-based
+IDE for Python.
+
+(For a more detailed change log, see the file ChangeLog.)
+
+FEATURES
+
+IDLE has the following features:
+
+- coded in 100% pure Python, using the Tkinter GUI toolkit (i.e. Tcl/Tk)
+
+- cross-platform: works on Windows and Unix (on the Mac, there are
+currently problems with Tcl/Tk)
+
+- multi-window text editor with multiple undo, Python colorizing
+and many other features, e.g. smart indent and call tips
+
+- Python shell window (a.k.a. interactive interpreter)
+
+- debugger (not complete, but you can set breakpoints, view  and step)
+
+USAGE
+
+The main program is in the file "idle.py"; on Unix, you should be able
+to run it by typing "./idle.py" to your shell.  On Windows, you can
+run it by double-clicking it; you can use idle.pyw to avoid popping up
+a DOS console.  If you want to pass command line arguments on Windows,
+use the batch file idle.bat.
+
+Command line arguments: files passed on the command line are executed,
+not opened for editing, unless you give the -e command line option.
+Try "./idle.py -h" to see other command line options.
+
+IDLE requires Python 1.5.2, so it is currently only usable with a
+Python 1.5.2 distribution.  (An older version of IDLE is distributed
+with Python 1.5.2; you can drop this version on top of it.)
+
+COPYRIGHT
+
+IDLE is covered by the standard Python copyright notice
+(http://www.python.org/doc/Copyright.html).
+
+
+New in IDLE 0.5 (2/15/2000)
+---------------------------
+
+Tons of stuff, much of it contributed by Tim Peters and Mark Hammond:
+
+- Status bar, displaying current line/column (Moshe Zadka).
+
+- Better stack viewer, using tree widget.  (XXX Only used by Stack
+Viewer menu, not by the debugger.)
+
+- Format paragraph now recognizes Python block comments and reformats
+them correctly (MH)
+
+- New version of pyclbr.py parses top-level functions and understands
+much more of Python's syntax; this is reflected in the class and path
+browsers (TP)
+
+- Much better auto-indent; knows how to indent the insides of
+multi-line statements (TP)
+
+- Call tip window pops up when you type the name of a known function
+followed by an open parenthesis.  Hit ESC or click elsewhere in the
+window to close the tip window (MH)
+
+- Comment out region now inserts ## to make it stand out more (TP)
+
+- New path and class browsers based on a tree widget that looks
+familiar to Windows users
+
+- Reworked script running commands to be more intuitive: I/O now
+always goes to the *Python Shell* window, and raw_input() works
+correctly.  You use F5 to import/reload a module: this adds the module
+name to the __main__ namespace.  You use Control-F5 to run a script:
+this runs the script *in* the __main__ namespace.  The latter also
+sets sys.argv[] to the script name
+
+
+New in IDLE 0.4 (4/7/99)
+------------------------
+
+Most important change: a new menu entry "File -> Path browser", shows
+a 4-column hierarchical browser which lets you browse sys.path,
+directories, modules, and classes.  Yes, it's a superset of the Class
+browser menu entry.  There's also a new internal module,
+MultiScrolledLists.py, which provides the framework for this dialog.
+
+
+New in IDLE 0.3 (2/17/99)
+-------------------------
+
+Most important changes:
+
+- Enabled support for running a module, with or without the debugger.
+Output goes to a new window.  Pressing F5 in a module is effectively a
+reload of that module; Control-F5 loads it under the debugger.
+
+- Re-enable tearing off the Windows menu, and make a torn-off Windows
+menu update itself whenever a window is opened or closed.
+
+- Menu items can now be have a checkbox (when the menu label starts
+with "!"); use this for the Debugger and "Auto-open stack viewer"
+(was: JIT stack viewer) menu items.
+
+- Added a Quit button to the Debugger API.
+
+- The current directory is explicitly inserted into sys.path.
+
+- Fix the debugger (when using Python 1.5.2b2) to use canonical
+filenames for breakpoints, so these actually work.  (There's still a
+lot of work to be done to the management of breakpoints in the
+debugger though.)
+
+- Closing a window that is still colorizing now actually works.
+
+- Allow dragging of the separator between the two list boxes in the
+class browser.
+
+- Bind ESC to "close window" of the debugger, stack viewer and class
+browser.  It removes the selection highlighting in regular text
+windows.  (These are standard Windows conventions.)
+
+
+New in IDLE 0.2 (1/8/99)
+------------------------
+
+Lots of changes; here are the highlights:
+
+General:
+
+- You can now write and configure your own IDLE extension modules; see
+extend.txt.
+
+
+File menu:
+
+The command to open the Python shell window is now in the File menu.
+
+
+Edit menu:
+
+New Find dialog with more options; replace dialog; find in files dialog.
+
+Commands to tabify or untabify a region.
+
+Command to format a paragraph.
+
+
+Debug menu:
+
+JIT (Just-In-Time) stack viewer toggle -- if set, the stack viewer
+automaticall pops up when you get a traceback.
+
+Windows menu:
+
+Zoom height -- make the window full height.
+
+
+Help menu:
+
+The help text now show up in a regular window so you can search and
+even edit it if you like.
+
+
+
+IDLE 0.1 was distributed with the Python 1.5.2b1 release on 12/22/98.
+
+======================================================================
diff --git a/Tools/idle/INSTALL.txt b/Tools/idle/INSTALL.txt
new file mode 100644
index 0000000..ed7ac69
--- /dev/null
+++ b/Tools/idle/INSTALL.txt
@@ -0,0 +1,51 @@
+IDLEfork Installation Notes
+===========================
+
+IDLEfork requires Python Version 2.2 or later.
+
+There are several distribution files (where xx is the subversion):
+
+IDLEfork-0.9xx.win32.exe
+	This is a Windows installer which will install IDLEfork in 
+	..../site-packages/idleforklib/ and place the idefork startup script
+	at ..../scripts/idlefork.  Rename this to idlefork.pyw and
+	point your launcher icons at it.  Installation is as idlefork
+	to avoid conflict with the original Python IDLE.
+
+IDLEfork-0.9xx-1.noarch.rpm
+ 	This is an rpm which is designed to install as idleforklib in an
+	existing /usr/lib/python2.2 tree.  It installs as idlefork to avoid
+	conflict with Python IDLE.
+
+	Python rpms are available at http://www.python.org/2.2.2/rpms.html and
+	http://www.python.org/2.2.1/rpms.html.
+
+IDLEfork-0.9xx.tar.gz
+	This is a distutils sdist (source) tarfile which can be used to make 
+	installations on platforms not supported by the above files.
+	** It remains configured to install as idlelib, not idleforklib. **
+
+	Unpack in ..../Tools/, cd to the IDLEfork directory created, and
+	"python setup.py install" to install in ....site-packages/idlelib.
+	This will overwrite the Python IDLE installation.  
+
+        If you don't want to overwrite Python IDLE, it is also possible to
+	simply call "python idle.py" to run from the IDLEfork source directory
+	without making an installation.  In this case, IDLE will not be on
+	your PATH unless you are in the source directory.  Also, it is then
+	advisable to remove any Python IDLE installation by removing
+	..../site-packages/idlelib so the two identically named packages don't
+	conflict.
+
+	On Redhat Linux systems prior to 8.0, /usr/bin/python may be pointing
+	at python1.5.  If so, change the first line in the /usr/bin/idle 
+	script to read:
+	       !# /usr/bin/python2.2
+	       
+See README.txt for more details on this version of IDLEfork. 
+
+
+
+
+
+
diff --git a/Tools/idle/IdleConf.py b/Tools/idle/IdleConf.py
deleted file mode 100644
index 8eaa8e0..0000000
--- a/Tools/idle/IdleConf.py
+++ /dev/null
@@ -1,113 +0,0 @@
-"""Provides access to configuration information"""
-
-import os
-import sys
-from ConfigParser import ConfigParser, NoOptionError, NoSectionError
-
-class IdleConfParser(ConfigParser):
-
-    # these conf sections do not define extensions!
-    builtin_sections = {}
-    for section in ('EditorWindow', 'Colors'):
-        builtin_sections[section] = section
-
-    def getcolor(self, sec, name):
-        """Return a dictionary with foreground and background colors
-
-        The return value is appropriate for passing to Tkinter in, e.g.,
-        a tag_config call.
-        """
-        fore = self.getdef(sec, name + "-foreground")
-        back = self.getdef(sec, name + "-background")
-        return {"foreground": fore,
-                "background": back}
-
-    def getdef(self, sec, options, raw=0, vars=None, default=None):
-        """Get an option value for given section or return default"""
-        try:
-            return self.get(sec, options, raw, vars)
-        except (NoSectionError, NoOptionError):
-            return default
-
-    def getsection(self, section):
-        """Return a SectionConfigParser object"""
-        return SectionConfigParser(section, self)
-
-    def getextensions(self):
-        exts = []
-        for sec in self.sections():
-            if self.builtin_sections.has_key(sec):
-                continue
-            # enable is a bool, but it may not be defined
-            if self.getdef(sec, 'enable') != '0':
-                exts.append(sec)
-        return exts
-
-    def reload(self):
-        global idleconf
-        idleconf = IdleConfParser()
-        load(_dir) # _dir is a global holding the last directory loaded
-
-class SectionConfigParser:
-    """A ConfigParser object specialized for one section
-
-    This class has all the get methods that a regular ConfigParser does,
-    but without requiring a section argument.
-    """
-    def __init__(self, section, config):
-        self.section = section
-        self.config = config
-
-    def options(self):
-        return self.config.options(self.section)
-
-    def get(self, options, raw=0, vars=None):
-        return self.config.get(self.section, options, raw, vars)
-
-    def getdef(self, options, raw=0, vars=None, default=None):
-        return self.config.getdef(self.section, options, raw, vars, default)
-
-    def getint(self, option):
-        return self.config.getint(self.section, option)
-
-    def getfloat(self, option):
-        return self.config.getint(self.section, option)
-
-    def getboolean(self, option):
-        return self.config.getint(self.section, option)
-
-    def getcolor(self, option):
-        return self.config.getcolor(self.section, option)
-
-def load(dir):
-    """Load IDLE configuration files based on IDLE install in dir
-
-    Attempts to load two config files:
-    dir/config.txt
-    dir/config-[win/mac/unix].txt
-    dir/config-%(sys.platform)s.txt
-    ~/.idle
-    """
-    global _dir
-    _dir = dir
-
-    if sys.platform[:3] == 'win':
-        genplatfile = os.path.join(dir, "config-win.txt")
-    # XXX don't know what the platform string is on a Mac
-    elif sys.platform[:3] == 'mac':
-        genplatfile = os.path.join(dir, "config-mac.txt")
-    else:
-        genplatfile = os.path.join(dir, "config-unix.txt")
-
-    platfile = os.path.join(dir, "config-%s.txt" % sys.platform)
-
-    try:
-        homedir = os.environ['HOME']
-    except KeyError:
-        homedir = os.getcwd()
-
-    idleconf.read((os.path.join(dir, "config.txt"), genplatfile, platfile,
-                   os.path.join(homedir, ".idle")))
-
-idleconf = IdleConfParser()
-load(os.path.dirname(__file__))
diff --git a/Tools/idle/LICENSE.txt b/Tools/idle/LICENSE.txt
new file mode 100644
index 0000000..f7a8395
--- /dev/null
+++ b/Tools/idle/LICENSE.txt
@@ -0,0 +1,50 @@
+To apply this license to IDLE or IDLEfork, read 'IDLE' or 'IDLEfork'
+for every occurence of 'Python 2.1.1' in the text below.
+
+PSF LICENSE AGREEMENT
+---------------------
+
+1. This LICENSE AGREEMENT is between the Python Software Foundation
+("PSF"), and the Individual or Organization ("Licensee") accessing and
+otherwise using Python 2.1.1 software in source or binary form and its
+associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF
+hereby grants Licensee a nonexclusive, royalty-free, world-wide
+license to reproduce, analyze, test, perform and/or display publicly,
+prepare derivative works, distribute, and otherwise use Python 2.1.1
+alone or in any derivative version, provided, however, that PSF's
+License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
+2001 Python Software Foundation; All Rights Reserved" are retained in
+Python 2.1.1 alone or in any derivative version prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python 2.1.1 or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python 2.1.1.
+
+4. PSF is making Python 2.1.1 available to Licensee on an "AS IS"
+basis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 2.1.1 WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+2.1.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.1.1,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between PSF and
+Licensee.  This License Agreement does not grant permission to use PSF
+trademarks or trade name in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Python 2.1.1, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
diff --git a/Tools/idle/Makefile b/Tools/idle/Makefile
new file mode 100644
index 0000000..f118ebf
--- /dev/null
+++ b/Tools/idle/Makefile
@@ -0,0 +1,8 @@
+# Makefile to build the interrupt module, which is a C extension
+
+PYTHON=python2.3
+
+all: interrupt.so
+
+interrupt.so: interruptmodule.c
+	$(PYTHON) setup.py build_ext -i
diff --git a/Tools/idle/MultiScrolledLists.py b/Tools/idle/MultiScrolledLists.py
deleted file mode 100644
index 6398b86..0000000
--- a/Tools/idle/MultiScrolledLists.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# One or more ScrolledLists with HSeparators between them.
-# There is a hierarchical relationship between them:
-# the right list displays the substructure of the selected item
-# in the left list.
-
-from Tkinter import *
-from WindowList import ListedToplevel
-from Separator import HSeparator
-from ScrolledList import ScrolledList
-
-class MultiScrolledLists:
-
-    def __init__(self, root, nlists=2):
-        assert nlists >= 1
-        self.root = root
-        self.nlists = nlists
-        self.path = []
-        # create top
-        self.top = top = ListedToplevel(root)
-        top.protocol("WM_DELETE_WINDOW", self.close)
-        top.bind("<Escape>", self.close)
-        self.settitle()
-        # create frames and separators in between
-        self.frames = []
-        self.separators = []
-        last = top
-        for i in range(nlists-1):
-            sepa = HSeparator(last)
-            self.separators.append(sepa)
-            frame, last = sepa.parts()
-            self.frames.append(frame)
-        self.frames.append(last)
-        # create labels and lists
-        self.labels = []
-        self.lists = []
-        for i in range(nlists):
-            frame = self.frames[i]
-            label = Label(frame, text=self.subtitle(i),
-                relief="groove", borderwidth=2)
-            label.pack(fill="x")
-            self.labels.append(label)
-            list = ScrolledList(frame, width=self.width(i),
-                height=self.height(i))
-            self.lists.append(list)
-            list.on_select = \
-                lambda index, i=i, self=self: self.on_select(index, i)
-            list.on_double = \
-                lambda index, i=i, self=self: self.on_double(index, i)
-        # fill leftmost list (rest get filled on demand)
-        self.fill(0)
-        # XXX one after_idle isn't enough; two are...
-        top.after_idle(self.call_pack_propagate_1)
-
-    def call_pack_propagate_1(self):
-        self.top.after_idle(self.call_pack_propagate)
-
-    def call_pack_propagate(self):
-        for frame in self.frames:
-            frame.pack_propagate(0)
-
-    def close(self, event=None):
-        self.top.destroy()
-
-    def settitle(self):
-        short = self.shorttitle()
-        long = self.longtitle()
-        if short and long:
-            title = short + " - " + long
-        elif short:
-            title = short
-        elif long:
-            title = long
-        else:
-            title = "Untitled"
-        icon = short or long or title
-        self.top.wm_title(title)
-        self.top.wm_iconname(icon)
-
-    def longtitle(self):
-        # override this
-        return "Multi Scrolled Lists"
-
-    def shorttitle(self):
-        # override this
-        return None
-
-    def width(self, i):
-        # override this
-        return 20
-
-    def height(self, i):
-        # override this
-        return 10
-
-    def subtitle(self, i):
-        # override this
-        return "Column %d" % i
-
-    def fill(self, i):
-        for k in range(i, self.nlists):
-            self.lists[k].clear()
-            self.labels[k].configure(text=self.subtitle(k))
-        list = self.lists[i]
-        l = self.items(i)
-        for s in l:
-            list.append(s)
-
-    def on_select(self, index, i):
-        item = self.lists[i].get(index)
-        del self.path[i:]
-        self.path.append(item)
-        if i+1 < self.nlists:
-            self.fill(i+1)
-
-    def items(self, i):
-        # override this
-        l = []
-        for k in range(10):
-            s = str(k)
-            if i > 0:
-                s = self.path[i-1] + "." + s
-            l.append(s)
-        return l
-
-    def on_double(self, index, i):
-        pass
-
-
-def main():
-    root = Tk()
-    quit = Button(root, text="Exit", command=root.destroy)
-    quit.pack()
-    MultiScrolledLists(root, 4)
-    root.mainloop()
-
-if __name__ == "__main__":
-    main()
diff --git a/Tools/idle/OldStackViewer.py b/Tools/idle/OldStackViewer.py
deleted file mode 100644
index 4f295e8..0000000
--- a/Tools/idle/OldStackViewer.py
+++ /dev/null
@@ -1,275 +0,0 @@
-import sys
-import os
-from Tkinter import *
-import linecache
-from repr import Repr
-from WindowList import ListedToplevel
-
-from ScrolledList import ScrolledList
-
-
-class StackBrowser:
-
-    def __init__(self, root, flist, stack=None):
-        self.top = top = ListedToplevel(root)
-        top.protocol("WM_DELETE_WINDOW", self.close)
-        top.bind("<Key-Escape>", self.close)
-        top.wm_title("Stack viewer")
-        top.wm_iconname("Stack")
-        # Create help label
-        self.helplabel = Label(top,
-            text="Click once to view variables; twice for source",
-            borderwidth=2, relief="groove")
-        self.helplabel.pack(fill="x")
-        #
-        self.sv = StackViewer(top, flist, self)
-        if stack is None:
-            stack = get_stack()
-        self.sv.load_stack(stack)
-
-    def close(self, event=None):
-        self.top.destroy()
-
-    localsframe = None
-    localsviewer = None
-    localsdict = None
-    globalsframe = None
-    globalsviewer = None
-    globalsdict = None
-    curframe = None
-
-    def show_frame(self, (frame, lineno)):
-        if frame is self.curframe:
-            return
-        self.curframe = None
-        if frame.f_globals is not self.globalsdict:
-            self.show_globals(frame)
-        self.show_locals(frame)
-        self.curframe = frame
-
-    def show_globals(self, frame):
-        title = "Global Variables"
-        if frame.f_globals.has_key("__name__"):
-            try:
-                name = str(frame.f_globals["__name__"]) + ""
-            except:
-                name = ""
-            if name:
-                title = title + " in module " + name
-        self.globalsdict = None
-        if self.globalsviewer:
-            self.globalsviewer.close()
-        self.globalsviewer = None
-        if not self.globalsframe:
-            self.globalsframe = Frame(self.top)
-        self.globalsdict = frame.f_globals
-        self.globalsviewer = NamespaceViewer(
-            self.globalsframe,
-            title,
-            self.globalsdict)
-        self.globalsframe.pack(fill="both", side="bottom")
-
-    def show_locals(self, frame):
-        self.localsdict = None
-        if self.localsviewer:
-            self.localsviewer.close()
-        self.localsviewer = None
-        if frame.f_locals is not frame.f_globals:
-            title = "Local Variables"
-            code = frame.f_code
-            funcname = code.co_name
-            if funcname not in ("?", "", None):
-                title = title + " in " + funcname
-            if not self.localsframe:
-                self.localsframe = Frame(self.top)
-            self.localsdict = frame.f_locals
-            self.localsviewer = NamespaceViewer(
-                self.localsframe,
-                title,
-                self.localsdict)
-            self.localsframe.pack(fill="both", side="top")
-        else:
-            if self.localsframe:
-                self.localsframe.forget()
-
-
-class StackViewer(ScrolledList):
-
-    def __init__(self, master, flist, browser):
-        ScrolledList.__init__(self, master, width=80)
-        self.flist = flist
-        self.browser = browser
-        self.stack = []
-
-    def load_stack(self, stack, index=None):
-        self.stack = stack
-        self.clear()
-##        if len(stack) > 10:
-##            l["height"] = 10
-##            self.topframe.pack(expand=1)
-##        else:
-##            l["height"] = len(stack)
-##            self.topframe.pack(expand=0)
-        for i in range(len(stack)):
-            frame, lineno = stack[i]
-            try:
-                modname = frame.f_globals["__name__"]
-            except:
-                modname = "?"
-            code = frame.f_code
-            filename = code.co_filename
-            funcname = code.co_name
-            sourceline = linecache.getline(filename, lineno)
-            sourceline = sourceline.strip()
-            if funcname in ("?", "", None):
-                item = "%s, line %d: %s" % (modname, lineno, sourceline)
-            else:
-                item = "%s.%s(), line %d: %s" % (modname, funcname,
-                                                 lineno, sourceline)
-            if i == index:
-                item = "> " + item
-            self.append(item)
-        if index is not None:
-            self.select(index)
-
-    def popup_event(self, event):
-        if self.stack:
-            return ScrolledList.popup_event(self, event)
-
-    def fill_menu(self):
-        menu = self.menu
-        menu.add_command(label="Go to source line",
-                         command=self.goto_source_line)
-        menu.add_command(label="Show stack frame",
-                         command=self.show_stack_frame)
-
-    def on_select(self, index):
-        if 0 <= index < len(self.stack):
-            self.browser.show_frame(self.stack[index])
-
-    def on_double(self, index):
-        self.show_source(index)
-
-    def goto_source_line(self):
-        index = self.listbox.index("active")
-        self.show_source(index)
-
-    def show_stack_frame(self):
-        index = self.listbox.index("active")
-        if 0 <= index < len(self.stack):
-            self.browser.show_frame(self.stack[index])
-
-    def show_source(self, index):
-        if not (0 <= index < len(self.stack)):
-            return
-        frame, lineno = self.stack[index]
-        code = frame.f_code
-        filename = code.co_filename
-        if os.path.isfile(filename):
-            edit = self.flist.open(filename)
-            if edit:
-                edit.gotoline(lineno)
-
-
-def get_stack(t=None, f=None):
-    if t is None:
-        t = sys.last_traceback
-    stack = []
-    if t and t.tb_frame is f:
-        t = t.tb_next
-    while f is not None:
-        stack.append((f, f.f_lineno))
-        if f is self.botframe:
-            break
-        f = f.f_back
-    stack.reverse()
-    while t is not None:
-        stack.append((t.tb_frame, t.tb_lineno))
-        t = t.tb_next
-    return stack
-
-
-def getexception(type=None, value=None):
-    if type is None:
-        type = sys.last_type
-        value = sys.last_value
-    if hasattr(type, "__name__"):
-        type = type.__name__
-    s = str(type)
-    if value is not None:
-        s = s + ": " + str(value)
-    return s
-
-
-class NamespaceViewer:
-
-    def __init__(self, master, title, dict=None):
-        width = 0
-        height = 40
-        if dict:
-            height = 20*len(dict) # XXX 20 == observed height of Entry widget
-        self.master = master
-        self.title = title
-        self.repr = Repr()
-        self.repr.maxstring = 60
-        self.repr.maxother = 60
-        self.frame = frame = Frame(master)
-        self.frame.pack(expand=1, fill="both")
-        self.label = Label(frame, text=title, borderwidth=2, relief="groove")
-        self.label.pack(fill="x")
-        self.vbar = vbar = Scrollbar(frame, name="vbar")
-        vbar.pack(side="right", fill="y")
-        self.canvas = canvas = Canvas(frame,
-                                      height=min(300, max(40, height)),
-                                      scrollregion=(0, 0, width, height))
-        canvas.pack(side="left", fill="both", expand=1)
-        vbar["command"] = canvas.yview
-        canvas["yscrollcommand"] = vbar.set
-        self.subframe = subframe = Frame(canvas)
-        self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw")
-        self.load_dict(dict)
-
-    dict = -1
-
-    def load_dict(self, dict, force=0):
-        if dict is self.dict and not force:
-            return
-        subframe = self.subframe
-        frame = self.frame
-        for c in subframe.children.values():
-            c.destroy()
-        self.dict = None
-        if not dict:
-            l = Label(subframe, text="None")
-            l.grid(row=0, column=0)
-        else:
-            names = dict.keys()
-            names.sort()
-            row = 0
-            for name in names:
-                value = dict[name]
-                svalue = self.repr.repr(value) # repr(value)
-                l = Label(subframe, text=name)
-                l.grid(row=row, column=0, sticky="nw")
-    ##            l = Label(subframe, text=svalue, justify="l", wraplength=300)
-                l = Entry(subframe, width=0, borderwidth=0)
-                l.insert(0, svalue)
-    ##            l["state"] = "disabled"
-                l.grid(row=row, column=1, sticky="nw")
-                row = row+1
-        self.dict = dict
-        # XXX Could we use a <Configure> callback for the following?
-        subframe.update_idletasks() # Alas!
-        width = subframe.winfo_reqwidth()
-        height = subframe.winfo_reqheight()
-        canvas = self.canvas
-        self.canvas["scrollregion"] = (0, 0, width, height)
-        if height > 300:
-            canvas["height"] = 300
-            frame.pack(expand=1)
-        else:
-            canvas["height"] = height
-            frame.pack(expand=0)
-
-    def close(self):
-        self.frame.destroy()
diff --git a/Tools/idle/RemoteDebugger.py b/Tools/idle/RemoteDebugger.py
new file mode 100644
index 0000000..41f910f
--- /dev/null
+++ b/Tools/idle/RemoteDebugger.py
@@ -0,0 +1,381 @@
+"""Support for remote Python debugging.
+
+Some ASCII art to describe the structure:
+
+       IN PYTHON SUBPROCESS          #             IN IDLE PROCESS
+                                     #
+                                     #        oid='gui_adapter'
+                 +----------+        #       +------------+          +-----+
+                 | GUIProxy |--remote#call-->| GUIAdapter |--calls-->| GUI |
++-----+--calls-->+----------+        #       +------------+          +-----+
+| Idb |                               #                             /
++-----+<-calls--+------------+         #      +----------+<--calls-/
+                | IdbAdapter |<--remote#call--| IdbProxy |
+                +------------+         #      +----------+
+                oid='idb_adapter'      #
+
+The purpose of the Proxy and Adapter classes is to translate certain
+arguments and return values that cannot be transported through the RPC
+barrier, in particular frame and traceback objects.
+
+"""
+
+import sys
+import types
+import rpc
+import Debugger
+
+debugging = 0
+
+idb_adap_oid = "idb_adapter"
+gui_adap_oid = "gui_adapter"
+
+#=======================================
+#
+# In the PYTHON subprocess:
+
+frametable = {}
+dicttable = {}
+codetable = {}
+tracebacktable = {}
+
+def wrap_frame(frame):
+    fid = id(frame)
+    frametable[fid] = frame
+    return fid
+
+def wrap_info(info):
+    "replace info[2], a traceback instance, by its ID"
+    if info is None:
+        return None
+    else:
+        traceback = info[2]
+        assert isinstance(traceback, types.TracebackType)
+        traceback_id = id(traceback)
+        tracebacktable[traceback_id] = traceback
+        modified_info = (info[0], info[1], traceback_id)
+        return modified_info
+
+class GUIProxy:
+
+    def __init__(self, conn, gui_adap_oid):
+        self.conn = conn
+        self.oid = gui_adap_oid
+
+    def interaction(self, message, frame, info=None):
+        # calls rpc.SocketIO.remotecall() via run.MyHandler instance
+        # pass frame and traceback object IDs instead of the objects themselves
+        self.conn.remotecall(self.oid, "interaction",
+                             (message, wrap_frame(frame), wrap_info(info)),
+                             {})
+
+class IdbAdapter:
+
+    def __init__(self, idb):
+        self.idb = idb
+
+    #----------called by an IdbProxy----------
+
+    def set_step(self):
+        self.idb.set_step()
+
+    def set_quit(self):
+        self.idb.set_quit()
+
+    def set_continue(self):
+        self.idb.set_continue()
+
+    def set_next(self, fid):
+        frame = frametable[fid]
+        self.idb.set_next(frame)
+
+    def set_return(self, fid):
+        frame = frametable[fid]
+        self.idb.set_return(frame)
+
+    def get_stack(self, fid, tbid):
+        ##print >>sys.__stderr__, "get_stack(%s, %s)" % (`fid`, `tbid`)
+        frame = frametable[fid]
+        if tbid is None:
+            tb = None
+        else:
+            tb = tracebacktable[tbid]
+        stack, i = self.idb.get_stack(frame, tb)
+        ##print >>sys.__stderr__, "get_stack() ->", stack
+        stack = [(wrap_frame(frame), k) for frame, k in stack]
+        ##print >>sys.__stderr__, "get_stack() ->", stack
+        return stack, i
+
+    def run(self, cmd):
+        import __main__
+        self.idb.run(cmd, __main__.__dict__)
+
+    def set_break(self, filename, lineno):
+        msg = self.idb.set_break(filename, lineno)
+        return msg
+
+    def clear_break(self, filename, lineno):
+        msg = self.idb.clear_break(filename, lineno)
+        return msg
+
+    def clear_all_file_breaks(self, filename):
+        msg = self.idb.clear_all_file_breaks(filename)
+        return msg
+
+    #----------called by a FrameProxy----------
+
+    def frame_attr(self, fid, name):
+        frame = frametable[fid]
+        return getattr(frame, name)
+
+    def frame_globals(self, fid):
+        frame = frametable[fid]
+        dict = frame.f_globals
+        did = id(dict)
+        dicttable[did] = dict
+        return did
+
+    def frame_locals(self, fid):
+        frame = frametable[fid]
+        dict = frame.f_locals
+        did = id(dict)
+        dicttable[did] = dict
+        return did
+
+    def frame_code(self, fid):
+        frame = frametable[fid]
+        code = frame.f_code
+        cid = id(code)
+        codetable[cid] = code
+        return cid
+
+    #----------called by a CodeProxy----------
+
+    def code_name(self, cid):
+        code = codetable[cid]
+        return code.co_name
+
+    def code_filename(self, cid):
+        code = codetable[cid]
+        return code.co_filename
+
+    #----------called by a DictProxy----------
+
+    def dict_keys(self, did):
+        dict = dicttable[did]
+        return dict.keys()
+
+    def dict_item(self, did, key):
+        dict = dicttable[did]
+        value = dict[key]
+        value = repr(value)
+        return value
+
+#----------end class IdbAdapter----------
+
+
+def start_debugger(rpchandler, gui_adap_oid):
+    """Start the debugger and its RPC link in the Python subprocess
+
+    Start the subprocess side of the split debugger and set up that side of the
+    RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter
+    objects and linking them together.  Register the IdbAdapter with the
+    RPCServer to handle RPC requests from the split debugger GUI via the
+    IdbProxy.
+
+    """
+    gui_proxy = GUIProxy(rpchandler, gui_adap_oid)
+    idb = Debugger.Idb(gui_proxy)
+    idb_adap = IdbAdapter(idb)
+    rpchandler.register(idb_adap_oid, idb_adap)
+    return idb_adap_oid
+
+
+#=======================================
+#
+# In the IDLE process:
+
+
+class FrameProxy:
+
+    def __init__(self, conn, fid):
+        self._conn = conn
+        self._fid = fid
+        self._oid = "idb_adapter"
+        self._dictcache = {}
+
+    def __getattr__(self, name):
+        if name[:1] == "_":
+            raise AttributeError, name
+        if name == "f_code":
+            return self._get_f_code()
+        if name == "f_globals":
+            return self._get_f_globals()
+        if name == "f_locals":
+            return self._get_f_locals()
+        return self._conn.remotecall(self._oid, "frame_attr",
+                                     (self._fid, name), {})
+
+    def _get_f_code(self):
+        cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {})
+        return CodeProxy(self._conn, self._oid, cid)
+
+    def _get_f_globals(self):
+        did = self._conn.remotecall(self._oid, "frame_globals",
+                                    (self._fid,), {})
+        return self._get_dict_proxy(did)
+
+    def _get_f_locals(self):
+        did = self._conn.remotecall(self._oid, "frame_locals",
+                                    (self._fid,), {})
+        return self._get_dict_proxy(did)
+
+    def _get_dict_proxy(self, did):
+        if self._dictcache.has_key(did):
+            return self._dictcache[did]
+        dp = DictProxy(self._conn, self._oid, did)
+        self._dictcache[did] = dp
+        return dp
+
+
+class CodeProxy:
+
+    def __init__(self, conn, oid, cid):
+        self._conn = conn
+        self._oid = oid
+        self._cid = cid
+
+    def __getattr__(self, name):
+        if name == "co_name":
+            return self._conn.remotecall(self._oid, "code_name",
+                                         (self._cid,), {})
+        if name == "co_filename":
+            return self._conn.remotecall(self._oid, "code_filename",
+                                         (self._cid,), {})
+
+
+class DictProxy:
+
+    def __init__(self, conn, oid, did):
+        self._conn = conn
+        self._oid = oid
+        self._did = did
+
+    def keys(self):
+        return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {})
+
+    def __getitem__(self, key):
+        return self._conn.remotecall(self._oid, "dict_item",
+                                     (self._did, key), {})
+
+    def __getattr__(self, name):
+        ##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name
+        raise AttributeError, name
+
+
+class GUIAdapter:
+
+    def __init__(self, conn, gui):
+        self.conn = conn
+        self.gui = gui
+
+    def interaction(self, message, fid, modified_info):
+        ##print "interaction: (%s, %s, %s)" % (message, fid, modified_info)
+        frame = FrameProxy(self.conn, fid)
+        self.gui.interaction(message, frame, modified_info)
+
+
+class IdbProxy:
+
+    def __init__(self, conn, shell, oid):
+        self.oid = oid
+        self.conn = conn
+        self.shell = shell
+
+    def call(self, methodname, *args, **kwargs):
+        ##print "**IdbProxy.call %s %s %s" % (methodname, args, kwargs)
+        value = self.conn.remotecall(self.oid, methodname, args, kwargs)
+        ##print "**IdbProxy.call %s returns %s" % (methodname, `value`)
+        return value
+
+    def run(self, cmd, locals):
+        # Ignores locals on purpose!
+        seq = self.conn.asynccall(self.oid, "run", (cmd,), {})
+        self.shell.interp.active_seq = seq
+
+    def get_stack(self, frame, tbid):
+        # passing frame and traceback IDs, not the objects themselves
+        stack, i = self.call("get_stack", frame._fid, tbid)
+        stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack]
+        return stack, i
+
+    def set_continue(self):
+        self.call("set_continue")
+
+    def set_step(self):
+        self.call("set_step")
+
+    def set_next(self, frame):
+        self.call("set_next", frame._fid)
+
+    def set_return(self, frame):
+        self.call("set_return", frame._fid)
+
+    def set_quit(self):
+        self.call("set_quit")
+
+    def set_break(self, filename, lineno):
+        msg = self.call("set_break", filename, lineno)
+        return msg
+
+    def clear_break(self, filename, lineno):
+        msg = self.call("clear_break", filename, lineno)
+        return msg
+
+    def clear_all_file_breaks(self, filename):
+        msg = self.call("clear_all_file_breaks", filename)
+        return msg
+
+def start_remote_debugger(rpcclt, pyshell):
+    """Start the subprocess debugger, initialize the debugger GUI and RPC link
+
+    Request the RPCServer start the Python subprocess debugger and link.  Set
+    up the Idle side of the split debugger by instantiating the IdbProxy,
+    debugger GUI, and debugger GUIAdapter objects and linking them together.
+
+    Register the GUIAdapter with the RPCClient to handle debugger GUI
+    interaction requests coming from the subprocess debugger via the GUIProxy.
+
+    The IdbAdapter will pass execution and environment requests coming from the
+    Idle debugger GUI to the subprocess debugger via the IdbProxy.
+
+    """
+    global idb_adap_oid
+
+    idb_adap_oid = rpcclt.remotecall("exec", "start_the_debugger",\
+                                   (gui_adap_oid,), {})
+    idb_proxy = IdbProxy(rpcclt, pyshell, idb_adap_oid)
+    gui = Debugger.Debugger(pyshell, idb_proxy)
+    gui_adap = GUIAdapter(rpcclt, gui)
+    rpcclt.register(gui_adap_oid, gui_adap)
+    return gui
+
+def close_remote_debugger(rpcclt):
+    """Shut down subprocess debugger and Idle side of debugger RPC link
+
+    Request that the RPCServer shut down the subprocess debugger and link.
+    Unregister the GUIAdapter, which will cause a GC on the Idle process
+    debugger and RPC link objects.  (The second reference to the debugger GUI
+    is deleted in PyShell.close_remote_debugger().)
+
+    """
+    close_subprocess_debugger(rpcclt)
+    rpcclt.unregister(gui_adap_oid)
+
+def close_subprocess_debugger(rpcclt):
+    rpcclt.remotecall("exec", "stop_the_debugger", (idb_adap_oid,), {})
+
+def restart_subprocess_debugger(rpcclt):
+    idb_adap_oid_ret = rpcclt.remotecall("exec", "start_the_debugger",\
+                                         (gui_adap_oid,), {})
+    assert idb_adap_oid_ret == idb_adap_oid, 'Idb restarted with different oid'
diff --git a/Tools/idle/RemoteInterp.py b/Tools/idle/RemoteInterp.py
deleted file mode 100644
index e6f7671..0000000
--- a/Tools/idle/RemoteInterp.py
+++ /dev/null
@@ -1,341 +0,0 @@
-import select
-import socket
-import struct
-import sys
-import types
-
-VERBOSE = None
-
-class SocketProtocol:
-    """A simple protocol for sending strings across a socket"""
-    BUF_SIZE = 8192
-
-    def __init__(self, sock):
-        self.sock = sock
-        self._buffer = ''
-        self._closed = 0
-
-    def close(self):
-        self._closed = 1
-        self.sock.close()
-
-    def send(self, buf):
-        """Encode buf and write it on the socket"""
-        if VERBOSE:
-            VERBOSE.write('send %d:%s\n' % (len(buf), `buf`))
-        self.sock.send('%d:%s' % (len(buf), buf))
-
-    def receive(self, timeout=0):
-        """Get next complete string from socket or return None
-
-        Raise EOFError on EOF
-        """
-        buf = self._read_from_buffer()
-        if buf is not None:
-            return buf
-        recvbuf = self._read_from_socket(timeout)
-        if recvbuf is None:
-            return None
-        if recvbuf == '' and self._buffer == '':
-            raise EOFError
-        if VERBOSE:
-            VERBOSE.write('recv %s\n' % `recvbuf`)
-        self._buffer = self._buffer + recvbuf
-        r = self._read_from_buffer()
-        return r
-
-    def _read_from_socket(self, timeout):
-        """Does not block"""
-        if self._closed:
-            return ''
-        if timeout is not None:
-            r, w, x = select.select([self.sock], [], [], timeout)
-        if timeout is None or r:
-            return self.sock.recv(self.BUF_SIZE)
-        else:
-            return None
-
-    def _read_from_buffer(self):
-        buf = self._buffer
-        i = buf.find(':')
-        if i == -1:
-            return None
-        buflen = int(buf[:i])
-        enclen = i + 1 + buflen
-        if len(buf) >= enclen:
-            s = buf[i+1:enclen]
-            self._buffer = buf[enclen:]
-            return s
-        else:
-            self._buffer = buf
-        return None
-
-# helpers for registerHandler method below
-
-def get_methods(obj):
-    methods = []
-    for name in dir(obj):
-        attr = getattr(obj, name)
-        if callable(attr):
-            methods.append(name)
-    if type(obj) == types.InstanceType:
-        methods = methods + get_methods(obj.__class__)
-    if type(obj) == types.ClassType:
-        for super in obj.__bases__:
-            methods = methods + get_methods(super)
-    return methods
-
-class CommandProtocol:
-    def __init__(self, sockp):
-        self.sockp = sockp
-        self.seqno = 0
-        self.handlers = {}
-
-    def close(self):
-        self.sockp.close()
-        self.handlers.clear()
-
-    def registerHandler(self, handler):
-        """A Handler is an object with handle_XXX methods"""
-        for methname in get_methods(handler):
-            if methname[:7] == "handle_":
-                name = methname[7:]
-                self.handlers[name] = getattr(handler, methname)
-
-    def send(self, cmd, arg='', seqno=None):
-        if arg:
-            msg = "%s %s" % (cmd, arg)
-        else:
-            msg = cmd
-        if seqno is None:
-            seqno = self.get_seqno()
-        msgbuf = self.encode_seqno(seqno) + msg
-        self.sockp.send(msgbuf)
-        if cmd == "reply":
-            return
-        reply = self.sockp.receive(timeout=None)
-        r_cmd, r_arg, r_seqno = self._decode_msg(reply)
-        assert r_seqno == seqno and r_cmd == "reply", "bad reply"
-        return r_arg
-
-    def _decode_msg(self, msg):
-        seqno = self.decode_seqno(msg[:self.SEQNO_ENC_LEN])
-        msg = msg[self.SEQNO_ENC_LEN:]
-        parts = msg.split(" ", 2)
-        if len(parts) == 1:
-            cmd = msg
-            arg = ''
-        else:
-            cmd = parts[0]
-            arg = parts[1]
-        return cmd, arg, seqno
-
-    def dispatch(self):
-        msg = self.sockp.receive()
-        if msg is None:
-            return
-        cmd, arg, seqno = self._decode_msg(msg)
-        self._current_reply = seqno
-        h = self.handlers.get(cmd, self.default_handler)
-        try:
-            r = h(arg)
-        except TypeError, msg:
-            raise TypeError, "handle_%s: %s" % (cmd, msg)
-        if self._current_reply is None:
-            if r is not None:
-                sys.stderr.write("ignoring %s return value type %s\n" % \
-                                 (cmd, type(r).__name__))
-            return
-        if r is None:
-            r = ''
-        if type(r) != types.StringType:
-            raise ValueError, "invalid return type for %s" % cmd
-        self.send("reply", r, seqno=seqno)
-
-    def reply(self, arg=''):
-        """Send a reply immediately
-
-        otherwise reply will be sent when handler returns
-        """
-        self.send("reply", arg, self._current_reply)
-        self._current_reply = None
-
-    def default_handler(self, arg):
-        sys.stderr.write("WARNING: unhandled message %s\n" % arg)
-        return ''
-
-    SEQNO_ENC_LEN = 4
-
-    def get_seqno(self):
-        seqno = self.seqno
-        self.seqno = seqno + 1
-        return seqno
-
-    def encode_seqno(self, seqno):
-        return struct.pack("I", seqno)
-
-    def decode_seqno(self, buf):
-        return struct.unpack("I", buf)[0]
-
-
-class StdioRedirector:
-    """Redirect sys.std{in,out,err} to a set of file-like objects"""
-
-    def __init__(self, stdin, stdout, stderr):
-        self.stdin = stdin
-        self.stdout = stdout
-        self.stderr = stderr
-
-    def redirect(self):
-        self.save()
-        sys.stdin = self.stdin
-        sys.stdout = self.stdout
-        sys.stderr = self.stderr
-
-    def save(self):
-        self._stdin = sys.stdin
-        self._stdout = sys.stdout
-        self._stderr = sys.stderr
-
-    def restore(self):
-        sys.stdin = self._stdin
-        sys.stdout = self._stdout
-        sys.stderr = self._stderr
-
-class IOWrapper:
-    """Send output from a file-like object across a SocketProtocol
-
-    XXX Should this be more tightly integrated with the CommandProtocol?
-    """
-
-    def __init__(self, name, cmdp):
-        self.name = name
-        self.cmdp = cmdp
-        self.buffer = []
-
-class InputWrapper(IOWrapper):
-    def write(self, buf):
-        # XXX what should this do on Windows?
-        raise IOError, (9, '[Errno 9] Bad file descriptor')
-
-    def read(self, arg=None):
-        if arg is not None:
-            if arg <= 0:
-                return ''
-        else:
-            arg = 0
-        return self.cmdp.send(self.name, "read,%s" % arg)
-
-    def readline(self):
-        return self.cmdp.send(self.name, "readline")
-
-class OutputWrapper(IOWrapper):
-    def write(self, buf):
-        self.cmdp.send(self.name, buf)
-
-    def read(self, arg=None):
-        return ''
-
-class RemoteInterp:
-    def __init__(self, sock):
-        self._sock = SocketProtocol(sock)
-        self._cmd = CommandProtocol(self._sock)
-        self._cmd.registerHandler(self)
-
-    def run(self):
-        try:
-            while 1:
-                self._cmd.dispatch()
-        except EOFError:
-            pass
-
-    def handle_execfile(self, arg):
-        self._cmd.reply()
-        io = StdioRedirector(InputWrapper("stdin", self._cmd),
-                             OutputWrapper("stdout", self._cmd),
-                             OutputWrapper("stderr", self._cmd))
-        io.redirect()
-        execfile(arg, {'__name__':'__main__'})
-        io.restore()
-        self._cmd.send("terminated")
-
-    def handle_quit(self, arg):
-        self._cmd.reply()
-        self._cmd.close()
-
-def startRemoteInterp(id):
-    import os
-    # UNIX domain sockets are simpler for starters
-    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-    sock.bind("/var/tmp/ri.%s" % id)
-    try:
-        sock.listen(1)
-        cli, addr = sock.accept()
-        rinterp = RemoteInterp(cli)
-        rinterp.run()
-    finally:
-        os.unlink("/var/tmp/ri.%s" % id)
-
-class RIClient:
-    """Client of the remote interpreter"""
-    def __init__(self, sock):
-        self._sock = SocketProtocol(sock)
-        self._cmd = CommandProtocol(self._sock)
-        self._cmd.registerHandler(self)
-
-    def execfile(self, file):
-        self._cmd.send("execfile", file)
-
-    def run(self):
-        try:
-            while 1:
-                self._cmd.dispatch()
-        except EOFError:
-            pass
-
-    def handle_stdout(self, buf):
-        sys.stdout.write(buf)
-##        sys.stdout.flush()
-
-    def handle_stderr(self, buf):
-        sys.stderr.write(buf)
-
-    def handle_stdin(self, arg):
-        if arg == "readline":
-            return sys.stdin.readline()
-        i = arg.find(",") + 1
-        bytes = int(arg[i:])
-        if bytes == 0:
-            return sys.stdin.read()
-        else:
-            return sys.stdin.read(bytes)
-
-    def handle_terminated(self, arg):
-        self._cmd.reply()
-        self._cmd.send("quit")
-        self._cmd.close()
-
-def riExec(id, file):
-    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-    sock.connect("/var/tmp/ri.%s" % id)
-    cli = RIClient(sock)
-    cli.execfile(file)
-    cli.run()
-
-if __name__ == "__main__":
-    import getopt
-
-    SERVER = 1
-    opts, args = getopt.getopt(sys.argv[1:], 'cv')
-    for o, v in opts:
-        if o == '-c':
-            SERVER = 0
-        elif o == '-v':
-            VERBOSE = sys.stderr
-    id = args[0]
-
-    if SERVER:
-        startRemoteInterp(id)
-    else:
-        file = args[1]
-        riExec(id, file)
diff --git a/Tools/idle/RemoteObjectBrowser.py b/Tools/idle/RemoteObjectBrowser.py
new file mode 100644
index 0000000..6ba3391
--- /dev/null
+++ b/Tools/idle/RemoteObjectBrowser.py
@@ -0,0 +1,36 @@
+import rpc
+
+def remote_object_tree_item(item):
+    wrapper = WrappedObjectTreeItem(item)
+    oid = id(wrapper)
+    rpc.objecttable[oid] = wrapper
+    return oid
+
+class WrappedObjectTreeItem:
+    # Lives in PYTHON subprocess
+
+    def __init__(self, item):
+        self.__item = item
+
+    def __getattr__(self, name):
+        value = getattr(self.__item, name)
+        return value
+
+    def _GetSubList(self):
+        list = self.__item._GetSubList()
+        return map(remote_object_tree_item, list)
+
+class StubObjectTreeItem:
+    # Lives in IDLE process
+
+    def __init__(self, sockio, oid):
+        self.sockio = sockio
+        self.oid = oid
+
+    def __getattr__(self, name):
+        value = rpc.MethodProxy(self.sockio, self.oid, name)
+        return value
+
+    def _GetSubList(self):
+        list = self.sockio.remotecall(self.oid, "_GetSubList", (), {})
+        return [StubObjectTreeItem(self.sockio, oid) for oid in list]
diff --git a/Tools/idle/SearchBinding.py b/Tools/idle/SearchBinding.py
deleted file mode 100644
index 5943e3b..0000000
--- a/Tools/idle/SearchBinding.py
+++ /dev/null
@@ -1,97 +0,0 @@
-import tkSimpleDialog
-
-###$ event <<find>>
-###$ win <Control-f>
-###$ unix <Control-u><Control-u><Control-s>
-
-###$ event <<find-again>>
-###$ win <Control-g>
-###$ win <F3>
-###$ unix <Control-u><Control-s>
-
-###$ event <<find-selection>>
-###$ win <Control-F3>
-###$ unix <Control-s>
-
-###$ event <<find-in-files>>
-###$ win <Alt-F3>
-
-###$ event <<replace>>
-###$ win <Control-h>
-
-###$ event <<goto-line>>
-###$ win <Alt-g>
-###$ unix <Alt-g>
-
-class SearchBinding:
-
-    windows_keydefs = {
-        '<<find-again>>': ['<Control-g>', '<F3>'],
-        '<<find-in-files>>': ['<Alt-F3>'],
-        '<<find-selection>>': ['<Control-F3>'],
-        '<<find>>': ['<Control-f>'],
-        '<<replace>>': ['<Control-h>'],
-        '<<goto-line>>': ['<Alt-g>'],
-    }
-
-    unix_keydefs = {
-        '<<find-again>>': ['<Control-u><Control-s>'],
-        '<<find-in-files>>': ['<Alt-s>', '<Meta-s>'],
-        '<<find-selection>>': ['<Control-s>'],
-        '<<find>>': ['<Control-u><Control-u><Control-s>'],
-        '<<replace>>': ['<Control-r>'],
-        '<<goto-line>>': ['<Alt-g>', '<Meta-g>'],
-    }
-
-    menudefs = [
-        ('edit', [
-            None,
-            ('_Find...', '<<find>>'),
-            ('Find a_gain', '<<find-again>>'),
-            ('Find _selection', '<<find-selection>>'),
-            ('Find in Files...', '<<find-in-files>>'),
-            ('R_eplace...', '<<replace>>'),
-            ('Go to _line', '<<goto-line>>'),
-         ]),
-    ]
-
-    def __init__(self, editwin):
-        self.editwin = editwin
-
-    def find_event(self, event):
-        import SearchDialog
-        SearchDialog.find(self.editwin.text)
-        return "break"
-
-    def find_again_event(self, event):
-        import SearchDialog
-        SearchDialog.find_again(self.editwin.text)
-        return "break"
-
-    def find_selection_event(self, event):
-        import SearchDialog
-        SearchDialog.find_selection(self.editwin.text)
-        return "break"
-
-    def find_in_files_event(self, event):
-        import GrepDialog
-        GrepDialog.grep(self.editwin.text, self.editwin.io, self.editwin.flist)
-        return "break"
-
-    def replace_event(self, event):
-        import ReplaceDialog
-        ReplaceDialog.replace(self.editwin.text)
-        return "break"
-
-    def goto_line_event(self, event):
-        text = self.editwin.text
-        lineno = tkSimpleDialog.askinteger("Goto",
-                                           "Go to line number:",
-                                           parent=text)
-        if lineno is None:
-            return "break"
-        if lineno <= 0:
-            text.bell()
-            return "break"
-        text.mark_set("insert", "%d.0" % lineno)
-        text.see("insert")
diff --git a/Tools/idle/Separator.py b/Tools/idle/Separator.py
deleted file mode 100644
index 7145559..0000000
--- a/Tools/idle/Separator.py
+++ /dev/null
@@ -1,92 +0,0 @@
-from Tkinter import *
-
-class Separator:
-
-    def __init__(self, master, orient, min=10, thickness=5, bg=None):
-        self.min = max(1, min)
-        self.thickness = max(1, thickness)
-        if orient in ("h", "horizontal"):
-            self.side = "left"
-            self.dim = "width"
-            self.dir = "x"
-            self.cursor = "sb_h_double_arrow"
-        elif orient in ("v", "vertical"):
-            self.side = "top"
-            self.dim = "height"
-            self.dir = "y"
-            self.cursor = "sb_v_double_arrow"
-        else:
-            raise ValueError, "Separator: orient should be h or v"
-        self.winfo_dim = "winfo_" + self.dim
-        self.master = master = Frame(master)
-        master.pack(expand=1, fill="both")
-        self.f1 = Frame(master)
-        self.f1.pack(expand=1, fill="both", side=self.side)
-        self.div = Frame(master, cursor=self.cursor)
-        self.div[self.dim] = self.thickness
-        self.div.pack(fill="both", side=self.side)
-        self.f2 = Frame(master)
-        self.f2.pack(expand=1, fill="both", side=self.side)
-        self.div.bind("<ButtonPress-1>", self.divider_press)
-        if bg:
-            ##self.f1["bg"] = bg
-            ##self.f2["bg"] = bg
-            self.div["bg"] = bg
-
-    def parts(self):
-        return self.f1, self.f2
-
-    def divider_press(self, event):
-        self.press_event = event
-        self.f1.pack_propagate(0)
-        self.f2.pack_propagate(0)
-        for f in self.f1, self.f2:
-            for dim in "width", "height":
-                f[dim] = getattr(f, "winfo_"+dim)()
-        self.div.bind("<Motion>", self.div_motion)
-        self.div.bind("<ButtonRelease-1>", self.div_release)
-        self.div.grab_set()
-
-    def div_motion(self, event):
-        delta = getattr(event, self.dir) - getattr(self.press_event, self.dir)
-        if delta:
-            dim1 = getattr(self.f1, self.winfo_dim)()
-            dim2 = getattr(self.f2, self.winfo_dim)()
-            delta = max(delta, self.min-dim1)
-            delta = min(delta, dim2-self.min)
-            dim1 = dim1 + delta
-            dim2 = dim2 - delta
-            self.f1[self.dim] = dim1
-            self.f2[self.dim] = dim2
-
-    def div_release(self, event):
-        self.div_motion(event)
-        self.div.unbind("<Motion>")
-        self.div.grab_release()
-
-class VSeparator(Separator):
-
-    def __init__(self, master, min=10, thickness=5, bg=None):
-        Separator.__init__(self, master, "v", min, thickness, bg)
-
-class HSeparator(Separator):
-
-    def __init__(self, master, min=10, thickness=5, bg=None):
-        Separator.__init__(self, master, "h", min, thickness, bg)
-
-def main():
-    root = Tk()
-    tlist = []
-    outer = HSeparator(root, bg="red")
-    for part in outer.parts():
-        inner = VSeparator(part, bg="blue")
-        for f in inner.parts():
-            t = Text(f, width=40, height=10, borderwidth=0)
-            t.pack(fill="both", expand=1)
-            tlist.append(t)
-    tlist[0].insert("1.0", "Make your own Mondrian!")
-    tlist[1].insert("1.0", "Move the colored dividers...")
-    root.mainloop()
-
-if __name__ == '__main__':
-    main()
diff --git a/Tools/idle/aboutDialog.py b/Tools/idle/aboutDialog.py
new file mode 100644
index 0000000..2fc4a42
--- /dev/null
+++ b/Tools/idle/aboutDialog.py
@@ -0,0 +1,126 @@
+"""
+about box for idle
+"""
+
+from Tkinter import *
+import string, os
+import textView
+import idlever
+
+class AboutDialog(Toplevel):
+    """
+    modal about dialog for idle
+    """
+    def __init__(self,parent,title):
+        Toplevel.__init__(self, parent)
+        self.configure(borderwidth=5)
+        self.geometry("+%d+%d" % (parent.winfo_rootx()+30,
+                parent.winfo_rooty()+30))
+        self.bg="#707070"
+        self.fg="#ffffff"
+
+        self.CreateWidgets()
+        self.resizable(height=FALSE,width=FALSE)
+        self.title(title)
+        self.transient(parent)
+        self.grab_set()
+        self.protocol("WM_DELETE_WINDOW", self.Ok)
+        self.parent = parent
+        self.buttonOk.focus_set()
+        #key bindings for this dialog
+        self.bind('<Alt-c>',self.CreditsButtonBinding) #credits button
+        self.bind('<Alt-l>',self.LicenseButtonBinding) #license button
+        self.bind('<Return>',self.Ok) #dismiss dialog
+        self.bind('<Escape>',self.Ok) #dismiss dialog
+        self.wait_window()
+
+    def CreateWidgets(self):
+        frameMain = Frame(self,borderwidth=2,relief=SUNKEN)
+        frameButtons = Frame(self)
+        frameButtons.pack(side=BOTTOM,fill=X)
+        frameMain.pack(side=TOP,expand=TRUE,fill=BOTH)
+        self.buttonOk = Button(frameButtons,text='Ok',
+                command=self.Ok)#,default=ACTIVE
+        self.buttonOk.pack(padx=5,pady=5)
+        #self.picture = Image('photo',data=self.pictureData)
+        frameBg = Frame(frameMain,bg=self.bg)
+        frameBg.pack(expand=TRUE,fill=BOTH)
+        labelTitle = Label(frameBg,text='IDLEfork',fg=self.fg,bg=self.bg,
+                font=('courier', 24, 'bold'))
+        labelTitle.grid(row=0,column=0,sticky=W,padx=10,pady=10)
+        #labelPicture = Label(frameBg,text='[picture]')
+        #image=self.picture,bg=self.bg)
+        #labelPicture.grid(row=0,column=1,sticky=W,rowspan=2,padx=0,pady=3)
+        labelVersion = Label(frameBg,text='version  '+idlever.IDLE_VERSION,
+                fg=self.fg,bg=self.bg)
+        labelVersion.grid(row=1,column=0,sticky=W,padx=10,pady=5)
+        labelDesc = Label(frameBg,
+                text="A development version of Python's lightweight\n"+
+                'Integrated DeveLopment Environment, IDLE.',
+                justify=LEFT,fg=self.fg,bg=self.bg)
+        labelDesc.grid(row=2,column=0,sticky=W,columnspan=3,padx=10,pady=5)
+        labelCopyright = Label(frameBg,
+                text="Copyright (c) 2001 Python Software Foundation;\nAll Rights Reserved",
+                justify=LEFT,fg=self.fg,bg=self.bg)
+        labelCopyright.grid(row=3,column=0,sticky=W,columnspan=3,padx=10,pady=5)
+        labelLicense = Label(frameBg,
+                text='Released under the Python 2.1.1 PSF Licence',
+                justify=LEFT,fg=self.fg,bg=self.bg)
+        labelLicense.grid(row=4,column=0,sticky=W,columnspan=3,padx=10,pady=5)
+        Frame(frameBg,height=5,bg=self.bg).grid(row=5,column=0)
+        labelEmail = Label(frameBg,text='email:  idle-dev@python.org',
+                justify=LEFT,fg=self.fg,bg=self.bg)
+        labelEmail.grid(row=6,column=0,columnspan=2,sticky=W,padx=10,pady=0)
+        labelWWW = Label(frameBg,text='www:  http://idlefork.sourceforge.net',
+                justify=LEFT,fg=self.fg,bg=self.bg)
+        labelWWW.grid(row=7,column=0,columnspan=2,sticky=W,padx=10,pady=0)
+        Frame(frameBg,borderwidth=1,relief=SUNKEN,
+                height=2,bg=self.bg).grid(row=8,column=0,sticky=EW,
+                                          columnspan=3, padx=5, pady=5)
+        labelPythonVer = Label(frameBg,text='Python version:  '+
+                sys.version.split()[0],fg=self.fg,bg=self.bg)
+        labelPythonVer.grid(row=9,column=0,sticky=W,padx=10,pady=0)
+        #handle weird tk version num in windoze python >= 1.6 (?!?)
+        tkVer = `TkVersion`.split('.')
+        tkVer[len(tkVer)-1] = str('%.3g' % (float('.'+tkVer[len(tkVer)-1])))[2:]
+        if tkVer[len(tkVer)-1] == '':
+            tkVer[len(tkVer)-1] = '0'
+        tkVer = string.join(tkVer,'.')
+        labelTkVer = Label(frameBg,text='Tk version:  '+
+                tkVer,fg=self.fg,bg=self.bg)
+        labelTkVer.grid(row=9,column=1,sticky=W,padx=2,pady=0)
+
+        self.buttonLicense = Button(frameBg,text='View License',underline=5,
+                width=14,highlightbackground=self.bg,command=self.ShowLicense)#takefocus=FALSE
+        self.buttonLicense.grid(row=10,column=0,sticky=W,padx=10,pady=10)
+        self.buttonCredits = Button(frameBg,text='View Credits',underline=5,
+                width=14,highlightbackground=self.bg,command=self.ShowCredits)#takefocus=FALSE
+        self.buttonCredits.grid(row=10,column=1,columnspan=2,sticky=E,padx=10,pady=10)
+
+    def CreditsButtonBinding(self,event):
+        self.buttonCredits.invoke()
+
+    def LicenseButtonBinding(self,event):
+        self.buttonLicense.invoke()
+
+    def ShowLicense(self):
+        self.ViewFile('About - License','LICENSE.txt')
+
+    def ShowCredits(self):
+        self.ViewFile('About - Credits','CREDITS.txt')
+
+    def ViewFile(self,viewTitle,viewFile):
+        fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),viewFile)
+        textView.TextViewer(self,viewTitle,fn)
+
+    def Ok(self, event=None):
+        self.destroy()
+
+if __name__ == '__main__':
+    #test the dialog
+    root=Tk()
+    def run():
+        import aboutDialog
+        aboutDialog.AboutDialog(root,'About')
+    Button(root,text='Dialog',command=run).pack()
+    root.mainloop()
diff --git a/Tools/idle/boolcheck.py b/Tools/idle/boolcheck.py
new file mode 100644
index 0000000..f682232
--- /dev/null
+++ b/Tools/idle/boolcheck.py
@@ -0,0 +1,9 @@
+"boolcheck - import this module to ensure True, False, bool() builtins exist."
+try:
+    True
+except NameError:
+    import __builtin__
+    __builtin__.True = 1
+    __builtin__.False = 0
+    from operator import truth
+    __builtin__.bool = truth
diff --git a/Tools/idle/config-extensions.def b/Tools/idle/config-extensions.def
new file mode 100644
index 0000000..d4905e8
--- /dev/null
+++ b/Tools/idle/config-extensions.def
@@ -0,0 +1,54 @@
+# IDLE reads several config files to determine user preferences.  This 
+# file is the default config file for idle extensions settings.  
+#
+# Each extension must have at least one section, named after the extension
+# module. This section must contain an 'enable' item (=1 to enable the
+# extension, =0 to disable it) and also contains any other general
+# configuration items for the extension. Each extension may also define up to
+# two optional sections named ExtensionName_bindings and
+# ExtensionName_cfgBindings. If present, ExtensionName_bindings defines virtual
+# event bindings for the extension that are not sensibly re-configurable. If
+# present, ExtensionName_cfgBindings defines virtual event bindings for the
+# extension that may be sensibly re-configured.
+
+# See config-keys.def for notes on specifying keys.
+
+[FormatParagraph]
+enable=1
+[FormatParagraph_cfgBindings]
+format-paragraph=<Alt-Key-q>
+
+[AutoExpand]
+enable=1
+[AutoExpand_cfgBindings]
+expand-word=<Alt-Key-slash>
+
+[ZoomHeight]
+enable=1
+[ZoomHeight_cfgBindings]
+zoom-height=<Alt-Key-2>
+
+[ScriptBinding]
+enable=1
+[ScriptBinding_cfgBindings]
+run-module=<Key-F5>
+check-module=<Alt-Key-x>
+
+[CallTips]
+enable=1
+[CallTips_bindings]
+paren-open=<Key-parenleft>
+paren-close=<Key-parenright>
+check-calltip-cancel=<KeyRelease>
+calltip-cancel=<ButtonPress> <Key-Escape>
+
+[ParenMatch]
+enable=0
+style= expression
+flash-delay= 500
+bell= 1
+hilite-foreground= black
+hilite-background= #43cd80
+[ParenMatch_bindings]
+flash-open-paren=<KeyRelease-parenright> <KeyRelease-bracketright> <KeyRelease-braceright>
+check-restore=<KeyPress>
diff --git a/Tools/idle/config-highlight.def b/Tools/idle/config-highlight.def
new file mode 100644
index 0000000..2b7deab
--- /dev/null
+++ b/Tools/idle/config-highlight.def
@@ -0,0 +1,60 @@
+# IDLE reads several config files to determine user preferences.  This 
+# file is the default config file for idle highlight theme settings.  
+
+[IDLE Classic]
+normal-foreground= #000000
+normal-background= #ffffff
+keyword-foreground= #ff7700
+keyword-background= #ffffff
+comment-foreground= #dd0000
+comment-background= #ffffff
+string-foreground= #00aa00
+string-background= #ffffff
+definition-foreground= #0000ff
+definition-background= #ffffff
+hilite-foreground= #000000
+hilite-background= gray
+break-foreground= black
+break-background= #ffff55
+hit-foreground= #ffffff
+hit-background= #000000
+error-foreground= #000000
+error-background= #ff7777
+#cursor (only foreground can be set) 
+cursor-foreground= black
+#shell window
+stdout-foreground= blue
+stdout-background= #ffffff
+stderr-foreground= red
+stderr-background= #ffffff
+console-foreground= #770000
+console-background= #ffffff
+
+[IDLE New]
+normal-foreground= #000000
+normal-background= #ffffff
+keyword-foreground= #ff7700
+keyword-background= #ffffff
+comment-foreground= #dd0000
+comment-background= #ffffff
+string-foreground= #00aa00
+string-background= #ffffff
+definition-foreground= #0000ff
+definition-background= #ffffff
+hilite-foreground= #000000
+hilite-background= gray
+break-foreground= black
+break-background= #ffff55
+hit-foreground= #ffffff
+hit-background= #000000
+error-foreground= #000000
+error-background= #ff7777
+#cursor (only foreground can be set) 
+cursor-foreground= black
+#shell window
+stdout-foreground= blue
+stdout-background= #ffffff
+stderr-foreground= red
+stderr-background= #ffffff
+console-foreground= #770000
+console-background= #ffffff
diff --git a/Tools/idle/config-keys.def b/Tools/idle/config-keys.def
new file mode 100644
index 0000000..4f2be59
--- /dev/null
+++ b/Tools/idle/config-keys.def
@@ -0,0 +1,155 @@
+# IDLE reads several config files to determine user preferences.  This 
+# file is the default config file for idle key binding settings.  
+# Where multiple keys are specified for an action: if they are separated
+# by a space (eg. action=<key1> <key2>) then the keys are altenatives, if
+# there is no space (eg. action=<key1><key2>) then the keys comprise a
+# single 'emacs style' multi-keystoke binding. The tk event specifier 'Key'
+# is used in all cases, for consistency in auto key conflict checking in the
+# configuration gui.
+
+[IDLE Classic Windows]
+copy=<Control-Key-c>
+cut=<Control-Key-x>
+paste=<Control-Key-v>
+beginning-of-line= <Key-Home>
+center-insert=<Control-Key-l>
+close-all-windows=<Control-Key-q>
+close-window=<Alt-Key-F4> <Meta-Key-F4>
+do-nothing=<Control-Key-F12>
+end-of-file=<Control-Key-d>
+python-docs=<Key-F1>
+python-context-help=<Shift-Key-F1> 
+history-next=<Alt-Key-n> <Meta-Key-n>
+history-previous=<Alt-Key-p> <Meta-Key-p>
+interrupt-execution=<Control-Key-c>
+view-restart=<Key-F6>
+restart-shell=<Control-Key-F6>
+open-class-browser=<Alt-Key-c> <Meta-Key-c>
+open-module=<Alt-Key-m> <Meta-Key-m>
+open-new-window=<Control-Key-n>
+open-window-from-file=<Control-Key-o>
+plain-newline-and-indent=<Control-Key-j>
+print-window=<Control-Key-p>
+redo=<Control-Shift-Key-z>
+remove-selection=<Key-Escape>
+save-copy-of-window-as-file=<Alt-Shift-Key-s>
+save-window-as-file=<Control-Shift-Key-s>
+save-window=<Control-Key-s>
+select-all=<Control-Key-a>
+toggle-auto-coloring=<Control-Key-slash>
+undo=<Control-Key-z>
+find=<Control-Key-f>
+find-again=<Control-Key-g> <Key-F3>
+find-in-files=<Alt-Key-F3> <Meta-Key-F3>
+find-selection=<Control-Key-F3>
+replace=<Control-Key-h>
+goto-line=<Alt-Key-g> <Meta-Key-g>
+smart-backspace=<Key-BackSpace>
+newline-and-indent=<Key-Return> <Key-KP_Enter>
+smart-indent=<Key-Tab>
+indent-region=<Control-Key-bracketright>
+dedent-region=<Control-Key-bracketleft>
+comment-region=<Alt-Key-3> <Meta-Key-3>
+uncomment-region=<Alt-Key-4> <Meta-Key-4>
+tabify-region=<Alt-Key-5> <Meta-Key-5>
+untabify-region=<Alt-Key-6> <Meta-Key-6>
+toggle-tabs=<Alt-Key-t> <Meta-Key-t>
+change-indentwidth=<Alt-Key-u> <Meta-Key-u>
+
+[IDLE Classic Unix]
+copy=<Alt-Key-w> <Meta-Key-w>
+cut=<Control-Key-w>
+paste=<Control-Key-y>
+beginning-of-line=<Control-Key-a> <Key-Home>
+center-insert=<Control-Key-l>
+close-all-windows=<Control-Key-x><Control-Key-c>
+close-window=<Control-Key-x><Control-Key-0>
+do-nothing=<Control-Key-x>
+end-of-file=<Control-Key-d>
+history-next=<Alt-Key-n> <Meta-Key-n>
+history-previous=<Alt-Key-p> <Meta-Key-p>
+interrupt-execution=<Control-Key-c>
+view-restart=<Key-F6>
+restart-shell=<Control-Key-F6>
+open-class-browser=<Control-Key-x><Control-Key-b>
+open-module=<Control-Key-x><Control-Key-m>
+open-new-window=<Control-Key-x><Control-Key-n>
+open-window-from-file=<Control-Key-x><Control-Key-f>
+plain-newline-and-indent=<Control-Key-j>
+print-window=<Control-x><Control-Key-p>
+python-docs=<Control-Key-h> 
+python-context-help=<Control-Shift-Key-h> 
+redo=<Alt-Key-z> <Meta-Key-z>
+remove-selection=<Key-Escape>
+save-copy-of-window-as-file=<Control-Key-x><Control-Key-y>
+save-window-as-file=<Control-Key-x><Control-Key-w>
+save-window=<Control-Key-x><Control-Key-s>
+select-all=<Alt-Key-a> <Meta-Key-a>
+toggle-auto-coloring=<Control-Key-slash>
+undo=<Control-Key-z>
+find=<Control-Key-u><Control-Key-u><Control-Key-s>
+find-again=<Control-Key-u><Control-Key-s>
+find-in-files=<Alt-Key-s> <Meta-Key-s>
+find-selection=<Control-Key-s>
+replace=<Control-Key-r>
+goto-line=<Alt-Key-g> <Meta-Key-g>
+smart-backspace=<Key-BackSpace>
+newline-and-indent=<Key-Return> <Key-KP_Enter>
+smart-indent=<Key-Tab>
+indent-region=<Control-Key-bracketright>
+dedent-region=<Control-Key-bracketleft>
+comment-region=<Alt-Key-3>
+uncomment-region=<Alt-Key-4>
+tabify-region=<Alt-Key-5>
+untabify-region=<Alt-Key-6>
+toggle-tabs=<Alt-Key-t>
+change-indentwidth=<Alt-Key-u>
+
+[IDLE Classic Mac]
+copy=<Command-Key-c>
+cut=<Command-Key-x>
+paste=<Command-Key-v>
+beginning-of-line= <Key-Home>
+center-insert=<Control-Key-l>
+close-all-windows=<Command-Key-q>
+close-window=<Command-Key-w>
+do-nothing=<Control-Key-F12>
+end-of-file=<Control-Key-d>
+python-docs=<Key-F1>
+python-context-help=<Shift-Key-F1> 
+history-next=<Control-Key-n>
+history-previous=<Control-Key-p>
+interrupt-execution=<Control-Key-c>
+view-restart=<Key-F6>
+restart-shell=<Control-Key-F6>
+open-class-browser=<Command-Key-b>
+open-module=<Command-Key-m>
+open-new-window=<Command-Key-n>
+open-window-from-file=<Command-Key-o>
+plain-newline-and-indent=<Control-Key-j>
+print-window=<Command-Key-p>
+redo=<Shift-Command-Key-z> 
+remove-selection=<Key-Escape>
+save-window-as-file=<Shift-Command-Key-s>
+save-window=<Command-Key-s>
+save-copy-of-window-as-file=<Option-Command-Key-s>
+select-all=<Command-Key-a>
+toggle-auto-coloring=<Control-Key-slash>
+undo=<Command-Key-z>
+find=<Command-Key-f>
+find-again=<Command-Key-g> <Key-F3>
+find-in-files=<Command-Key-F3>
+find-selection=<Shift-Command-Key-F3>
+replace=<Command-Key-r>
+goto-line=<Command-Key-j>
+smart-backspace=<Key-BackSpace>
+newline-and-indent=<Key-Return> <Key-KP_Enter>
+smart-indent=<Key-Tab>
+indent-region=<Command-Key-bracketright>
+dedent-region=<Command-Key-bracketleft>
+comment-region=<Control-Key-3>
+uncomment-region=<Control-Key-4>
+tabify-region=<Control-Key-5>
+untabify-region=<Control-Key-6>
+toggle-tabs=<Control-Key-t>
+change-indentwidth=<Control-Key-u>
diff --git a/Tools/idle/config-mac.txt b/Tools/idle/config-mac.txt
deleted file mode 100644
index ee36e13..0000000
--- a/Tools/idle/config-mac.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-[EditorWindow]
-font-name= monaco
-font-size= 9
diff --git a/Tools/idle/config-main.def b/Tools/idle/config-main.def
new file mode 100644
index 0000000..9d520c1
--- /dev/null
+++ b/Tools/idle/config-main.def
@@ -0,0 +1,65 @@
+# IDLE reads several config files to determine user preferences.  This 
+# file is the default config file for general idle settings.
+#  
+# When IDLE starts, it will look in
+# the following two sets of files, in order:
+#
+#     default configuration
+#     ---------------------
+#     config-main.def         the default general config file
+#     config-extensions.def   the default extension config file
+#     config-highlight.def    the default highlighting config file
+#     config-keys.def         the default keybinding config file
+#
+#     user configuration
+#     -------------------
+#     ~/.idlerc/idle-main.cfg            the user general config file
+#     ~/.idlerc/idle-extensions.cfg      the user extension config file
+#     ~/.idlerc/idle-highlight.cfg       the user highlighting config file
+#     ~/.idlerc/idle-keys.cfg            the user keybinding config file
+#
+# Any options the user saves through the config dialog will be saved to
+# the relevant user config file. Reverting any general setting to the 
+# default causes that entry to be wiped from the user file and re-read 
+# from the default file. User highlighting themes or keybinding sets are
+# retained unless specifically deleted within the config dialog. Choosing
+# one of the default themes or keysets just applies the relevant settings 
+# from the default file. 
+#
+# Additional help sources are listed in the [HelpFiles] section and must be
+# viewable by a web browser (or the Windows Help viewer in the case of .chm
+# files). These sources will be listed on the Help menu.  The pattern is 
+# <sequence_number = menu item;/path/to/help/source> 
+# You can't use a semi-colon in a menu item or path.  The path will be platform
+# specific because of path separators, drive specs etc.
+#
+# It is best to use the Configuration GUI to set up additional help sources!
+# Example:
+#1 = My Extra Help Source;/usr/share/doc/foo/index.html
+#2 = Another Help Source;/path/to/another.pdf
+
+[General]
+editor-on-startup= 0
+print-command-posix=lpr %s
+print-command-win=start /min notepad /p %s
+
+[EditorWindow]
+width= 80
+height= 30
+font= courier
+font-size= 12
+font-bold= 0
+
+[Indent]
+use-spaces= 1
+num-spaces= 4
+
+[Theme]
+default= 1  
+name= IDLE Classic
+
+[Keys]
+default= 1  
+name= IDLE Classic Windows
+
+[HelpFiles]
diff --git a/Tools/idle/config-unix.txt b/Tools/idle/config-unix.txt
deleted file mode 100644
index 782965f..0000000
--- a/Tools/idle/config-unix.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-[EditorWindow]
-font-name= courier
-font-size= 10
-print-command=lpr %s
diff --git a/Tools/idle/config-win.txt b/Tools/idle/config-win.txt
deleted file mode 100644
index aeb6ab9..0000000
--- a/Tools/idle/config-win.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-[EditorWindow]
-font-name: courier new
-font-size: 10
-print-command=start /min notepad /p %s
diff --git a/Tools/idle/config.txt b/Tools/idle/config.txt
deleted file mode 100644
index 6f98a3e..0000000
--- a/Tools/idle/config.txt
+++ /dev/null
@@ -1,64 +0,0 @@
-# IDLE reads several config files to determine user preferences.  This 
-# file is the default config file.  When IDLE starts, it will look in
-# the following four files in order:
-#     config.txt                      the default config file
-#     config-[win/unix/mac].txt       the generic platform config file
-#     config-[sys.platform].txt       the specific platform config file
-#     ~/.idle                         the user config file
-# XXX what about Windows?
-#
-# The last definition of each option is used.  For example, you can
-# override the default window size (80x24) by defining width and
-# height options in the EditorWindow section of your ~/.idle file
-#
-# IDLE extensions can be enabled and disabled by adding them to one of
-# the config files.  To enable an extension, create a section with the
-# same name as the extension, e.g. the [ParenMatch] section below.  To
-# disable an extension, either remove the section or add the 'enable'
-# option with the value 0.  
-
-[EditorWindow]
-width= 80
-height= 24
-# fonts defined in config-[win/unix].txt
-
-[Colors]
-normal-foreground= black
-normal-background= white
-# These color types are not explicitly defined= sync, todo, stdin
-keyword-foreground= #ff7700
-comment-foreground= #dd0000
-string-foreground= #00aa00
-definition-foreground= #0000ff
-hilite-foreground= #000068
-hilite-background= #006868
-break-foreground= #ff7777
-hit-foreground= #ffffff
-hit-background= #000000
-stdout-foreground= blue
-stderr-foreground= red
-console-foreground= #770000
-error-background= #ff7777
-cursor-background= black
-
-[SearchBinding]
-
-[AutoIndent]
-
-[AutoExpand]
-
-[FormatParagraph]
-
-[ZoomHeight]
-
-[ScriptBinding]
-
-[CallTips]
-
-[ParenMatch]
-enable= 0
-style= expression
-flash-delay= 500
-bell= 1
-hilite-foreground= black
-hilite-background= #43cd80
diff --git a/Tools/idle/configDialog.py b/Tools/idle/configDialog.py
new file mode 100644
index 0000000..814689c
--- /dev/null
+++ b/Tools/idle/configDialog.py
@@ -0,0 +1,1134 @@
+"""IDLE Configuration Dialog: support user customization of IDLE by GUI
+
+Customize font faces, sizes, and colorization attributes.  Set indentation
+defaults.  Customize keybindings.  Colorization and keybindings can be
+saved as user defined sets.  Select startup options including shell/editor
+and default window size.  Define additional help sources.
+
+Note that tab width in IDLE is currently fixed at eight due to Tk issues.
+Refer to comment in EditorWindow autoindent code for details.
+
+"""
+from Tkinter import *
+import tkMessageBox, tkColorChooser, tkFont
+import string, copy
+
+from configHandler import idleConf
+from dynOptionMenuWidget import DynOptionMenu
+from tabpage import TabPageSet
+from keybindingDialog import GetKeysDialog
+from configSectionNameDialog import GetCfgSectionNameDialog
+from configHelpSourceEdit import GetHelpSourceDialog
+
+class ConfigDialog(Toplevel):
+    """
+    configuration dialog for idle
+    """
+    def __init__(self,parent,title):
+        Toplevel.__init__(self, parent)
+        self.configure(borderwidth=5)
+        self.geometry("+%d+%d" % (parent.winfo_rootx()+20,
+                parent.winfo_rooty()+30))
+        #Theme Elements. Each theme element key is it's display name.
+        #The first value of the tuple is the sample area tag name.
+        #The second value is the display name list sort index.
+        self.themeElements={'Normal Text':('normal','00'),
+            'Python Keywords':('keyword','01'),
+            'Python Definitions':('definition','02'),
+            'Python Comments':('comment','03'),
+            'Python Strings':('string','04'),
+            'Selected Text':('hilite','05'),
+            'Found Text':('hit','06'),
+            'Cursor':('cursor','07'),
+            'Error Text':('error','08'),
+            'Shell Normal Text':('console','09'),
+            'Shell Stdout Text':('stdout','10'),
+            'Shell Stderr Text':('stderr','11')}
+        self.ResetChangedItems() #load initial values in changed items dict
+        self.CreateWidgets()
+        self.resizable(height=FALSE,width=FALSE)
+        self.transient(parent)
+        self.grab_set()
+        self.protocol("WM_DELETE_WINDOW", self.Cancel)
+        self.parent = parent
+        self.tabPages.focus_set()
+        #key bindings for this dialog
+        #self.bind('<Escape>',self.Cancel) #dismiss dialog, no save
+        #self.bind('<Alt-a>',self.Apply) #apply changes, save
+        #self.bind('<F1>',self.Help) #context help
+        self.LoadConfigs()
+        self.AttachVarCallbacks() #avoid callbacks during LoadConfigs
+        self.wait_window()
+
+    def CreateWidgets(self):
+        self.tabPages = TabPageSet(self,
+                pageNames=['Fonts/Tabs','Highlighting','Keys','General'])
+        self.tabPages.ChangePage()#activates default (first) page
+        frameActionButtons = Frame(self)
+        #action buttons
+        self.buttonHelp = Button(frameActionButtons,text='Help',
+                command=self.Help,takefocus=FALSE)
+        self.buttonOk = Button(frameActionButtons,text='Ok',
+                command=self.Ok,takefocus=FALSE)
+        self.buttonApply = Button(frameActionButtons,text='Apply',
+                command=self.Apply,takefocus=FALSE)
+        self.buttonCancel = Button(frameActionButtons,text='Cancel',
+                command=self.Cancel,takefocus=FALSE)
+        self.CreatePageFontTab()
+        self.CreatePageHighlight()
+        self.CreatePageKeys()
+        self.CreatePageGeneral()
+        self.buttonHelp.pack(side=RIGHT,padx=5,pady=5)
+        self.buttonOk.pack(side=LEFT,padx=5,pady=5)
+        self.buttonApply.pack(side=LEFT,padx=5,pady=5)
+        self.buttonCancel.pack(side=LEFT,padx=5,pady=5)
+        frameActionButtons.pack(side=BOTTOM)
+        self.tabPages.pack(side=TOP,expand=TRUE,fill=BOTH)
+
+    def CreatePageFontTab(self):
+        #tkVars
+        self.fontSize=StringVar(self)
+        self.fontBold=BooleanVar(self)
+        self.fontName=StringVar(self)
+        self.spaceNum=IntVar(self)
+        #self.tabCols=IntVar(self)
+        self.indentBySpaces=BooleanVar(self)
+        self.editFont=tkFont.Font(self,('courier',12,'normal'))
+        ##widget creation
+        #body frame
+        frame=self.tabPages.pages['Fonts/Tabs']['page']
+        #body section frames
+        frameFont=Frame(frame,borderwidth=2,relief=GROOVE)
+        frameIndent=Frame(frame,borderwidth=2,relief=GROOVE)
+        #frameFont
+        labelFontTitle=Label(frameFont,text='Set Base Editor Font')
+        frameFontName=Frame(frameFont)
+        frameFontParam=Frame(frameFont)
+        labelFontNameTitle=Label(frameFontName,justify=LEFT,
+                text='Font :')
+        self.listFontName=Listbox(frameFontName,height=5,takefocus=FALSE,
+                exportselection=FALSE)
+        self.listFontName.bind('<ButtonRelease-1>',self.OnListFontButtonRelease)
+        scrollFont=Scrollbar(frameFontName)
+        scrollFont.config(command=self.listFontName.yview)
+        self.listFontName.config(yscrollcommand=scrollFont.set)
+        labelFontSizeTitle=Label(frameFontParam,text='Size :')
+        self.optMenuFontSize=DynOptionMenu(frameFontParam,self.fontSize,None,
+            command=self.SetFontSample)
+        checkFontBold=Checkbutton(frameFontParam,variable=self.fontBold,
+            onvalue=1,offvalue=0,text='Bold',command=self.SetFontSample)
+        frameFontSample=Frame(frameFont,relief=SOLID,borderwidth=1)
+        self.labelFontSample=Label(frameFontSample,
+                text='AaBbCcDdEe\nFfGgHhIiJjK\n1234567890\n#:+=(){}[]',
+                justify=LEFT,font=self.editFont)
+        #frameIndent
+        labelIndentTitle=Label(frameIndent,text='Set Indentation Defaults')
+        frameIndentType=Frame(frameIndent)
+        frameIndentSize=Frame(frameIndent)
+        labelIndentTypeTitle=Label(frameIndentType,
+                text='Choose indentation type :')
+        radioUseSpaces=Radiobutton(frameIndentType,variable=self.indentBySpaces,
+            value=1,text='Tab key inserts spaces')
+        radioUseTabs=Radiobutton(frameIndentType,variable=self.indentBySpaces,
+            value=0,text='Tab key inserts tabs')
+        labelIndentSizeTitle=Label(frameIndentSize,
+                text='Choose indentation size :')
+        labelSpaceNumTitle=Label(frameIndentSize,justify=LEFT,
+                text='indent width')
+        self.scaleSpaceNum=Scale(frameIndentSize,variable=self.spaceNum,
+                orient='horizontal',tickinterval=2,from_=2,to=16)
+        #labeltabColsTitle=Label(frameIndentSize,justify=LEFT,
+        #        text='when tab key inserts tabs,\ncolumns per tab')
+        #self.scaleTabCols=Scale(frameIndentSize,variable=self.tabCols,
+        #        orient='horizontal',tickinterval=2,from_=2,to=8)
+        #widget packing
+        #body
+        frameFont.pack(side=LEFT,padx=5,pady=10,expand=TRUE,fill=BOTH)
+        frameIndent.pack(side=LEFT,padx=5,pady=10,fill=Y)
+        #frameFont
+        labelFontTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
+        frameFontName.pack(side=TOP,padx=5,pady=5,fill=X)
+        frameFontParam.pack(side=TOP,padx=5,pady=5,fill=X)
+        labelFontNameTitle.pack(side=TOP,anchor=W)
+        self.listFontName.pack(side=LEFT,expand=TRUE,fill=X)
+        scrollFont.pack(side=LEFT,fill=Y)
+        labelFontSizeTitle.pack(side=LEFT,anchor=W)
+        self.optMenuFontSize.pack(side=LEFT,anchor=W)
+        checkFontBold.pack(side=LEFT,anchor=W,padx=20)
+        frameFontSample.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH)
+        self.labelFontSample.pack(expand=TRUE,fill=BOTH)
+        #frameIndent
+        labelIndentTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
+        frameIndentType.pack(side=TOP,padx=5,fill=X)
+        frameIndentSize.pack(side=TOP,padx=5,pady=5,fill=BOTH)
+        labelIndentTypeTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
+        radioUseSpaces.pack(side=TOP,anchor=W,padx=5)
+        radioUseTabs.pack(side=TOP,anchor=W,padx=5)
+        labelIndentSizeTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
+        labelSpaceNumTitle.pack(side=TOP,anchor=W,padx=5)
+        self.scaleSpaceNum.pack(side=TOP,padx=5,fill=X)
+        #labeltabColsTitle.pack(side=TOP,anchor=W,padx=5)
+        #self.scaleTabCols.pack(side=TOP,padx=5,fill=X)
+        return frame
+
+    def CreatePageHighlight(self):
+        self.builtinTheme=StringVar(self)
+        self.customTheme=StringVar(self)
+        self.fgHilite=BooleanVar(self)
+        self.colour=StringVar(self)
+        self.fontName=StringVar(self)
+        self.themeIsBuiltin=BooleanVar(self)
+        self.highlightTarget=StringVar(self)
+        ##widget creation
+        #body frame
+        frame=self.tabPages.pages['Highlighting']['page']
+        #body section frames
+        frameCustom=Frame(frame,borderwidth=2,relief=GROOVE)
+        frameTheme=Frame(frame,borderwidth=2,relief=GROOVE)
+        #frameCustom
+        self.textHighlightSample=Text(frameCustom,relief=SOLID,borderwidth=1,
+            font=('courier',12,''),cursor='hand2',width=21,height=10,
+            takefocus=FALSE,highlightthickness=0,wrap=NONE)
+        text=self.textHighlightSample
+        text.bind('<Double-Button-1>',lambda e: 'break')
+        text.bind('<B1-Motion>',lambda e: 'break')
+        textAndTags=(('#you can click here','comment'),('\n','normal'),
+            ('#to choose items','comment'),('\n','normal'),('def','keyword'),
+            (' ','normal'),('func','definition'),('(param):','normal'),
+            ('\n  ','normal'),('"""string"""','string'),('\n  var0 = ','normal'),
+            ("'string'",'string'),('\n  var1 = ','normal'),("'selected'",'hilite'),
+            ('\n  var2 = ','normal'),("'found'",'hit'),('\n\n','normal'),
+            (' error ','error'),(' ','normal'),('cursor |','cursor'),
+            ('\n ','normal'),('shell','console'),(' ','normal'),('stdout','stdout'),
+            (' ','normal'),('stderr','stderr'),('\n','normal'))
+        for txTa in textAndTags:
+            text.insert(END,txTa[0],txTa[1])
+        for element in self.themeElements.keys():
+            text.tag_bind(self.themeElements[element][0],'<ButtonPress-1>',
+                lambda event,elem=element: event.widget.winfo_toplevel()
+                .highlightTarget.set(elem))
+        text.config(state=DISABLED)
+        self.frameColourSet=Frame(frameCustom,relief=SOLID,borderwidth=1)
+        frameFgBg=Frame(frameCustom)
+        labelCustomTitle=Label(frameCustom,text='Set Custom Highlighting')
+        buttonSetColour=Button(self.frameColourSet,text='Choose Colour for :',
+            command=self.GetColour,highlightthickness=0)
+        self.optMenuHighlightTarget=DynOptionMenu(self.frameColourSet,
+            self.highlightTarget,None,highlightthickness=0)#,command=self.SetHighlightTargetBinding
+        self.radioFg=Radiobutton(frameFgBg,variable=self.fgHilite,
+            value=1,text='Foreground',command=self.SetColourSampleBinding)
+        self.radioBg=Radiobutton(frameFgBg,variable=self.fgHilite,
+            value=0,text='Background',command=self.SetColourSampleBinding)
+        self.fgHilite.set(1)
+        buttonSaveCustomTheme=Button(frameCustom,
+            text='Save as New Custom Theme',command=self.SaveAsNewTheme)
+        #frameTheme
+        labelThemeTitle=Label(frameTheme,text='Select a Highlighting Theme')
+        labelTypeTitle=Label(frameTheme,text='Select : ')
+        self.radioThemeBuiltin=Radiobutton(frameTheme,variable=self.themeIsBuiltin,
+            value=1,command=self.SetThemeType,text='a Built-in Theme')
+        self.radioThemeCustom=Radiobutton(frameTheme,variable=self.themeIsBuiltin,
+            value=0,command=self.SetThemeType,text='a Custom Theme')
+        self.optMenuThemeBuiltin=DynOptionMenu(frameTheme,
+            self.builtinTheme,None,command=None)
+        self.optMenuThemeCustom=DynOptionMenu(frameTheme,
+            self.customTheme,None,command=None)
+        self.buttonDeleteCustomTheme=Button(frameTheme,text='Delete Custom Theme',
+                command=self.DeleteCustomTheme)
+        ##widget packing
+        #body
+        frameCustom.pack(side=LEFT,padx=5,pady=10,expand=TRUE,fill=BOTH)
+        frameTheme.pack(side=LEFT,padx=5,pady=10,fill=Y)
+        #frameCustom
+        labelCustomTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
+        self.frameColourSet.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=X)
+        frameFgBg.pack(side=TOP,padx=5,pady=0)
+        self.textHighlightSample.pack(side=TOP,padx=5,pady=5,expand=TRUE,
+            fill=BOTH)
+        buttonSetColour.pack(side=TOP,expand=TRUE,fill=X,padx=8,pady=4)
+        self.optMenuHighlightTarget.pack(side=TOP,expand=TRUE,fill=X,padx=8,pady=3)
+        self.radioFg.pack(side=LEFT,anchor=E)
+        self.radioBg.pack(side=RIGHT,anchor=W)
+        buttonSaveCustomTheme.pack(side=BOTTOM,fill=X,padx=5,pady=5)
+        #frameTheme
+        labelThemeTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
+        labelTypeTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
+        self.radioThemeBuiltin.pack(side=TOP,anchor=W,padx=5)
+        self.radioThemeCustom.pack(side=TOP,anchor=W,padx=5,pady=2)
+        self.optMenuThemeBuiltin.pack(side=TOP,fill=X,padx=5,pady=5)
+        self.optMenuThemeCustom.pack(side=TOP,fill=X,anchor=W,padx=5,pady=5)
+        self.buttonDeleteCustomTheme.pack(side=TOP,fill=X,padx=5,pady=5)
+        return frame
+
+    def CreatePageKeys(self):
+        #tkVars
+        self.bindingTarget=StringVar(self)
+        self.builtinKeys=StringVar(self)
+        self.customKeys=StringVar(self)
+        self.keysAreBuiltin=BooleanVar(self)
+        self.keyBinding=StringVar(self)
+        ##widget creation
+        #body frame
+        frame=self.tabPages.pages['Keys']['page']
+        #body section frames
+        frameCustom=Frame(frame,borderwidth=2,relief=GROOVE)
+        frameKeySets=Frame(frame,borderwidth=2,relief=GROOVE)
+        #frameCustom
+        frameTarget=Frame(frameCustom)
+        labelCustomTitle=Label(frameCustom,text='Set Custom Key Bindings')
+        labelTargetTitle=Label(frameTarget,text='Action - Key(s)')
+        scrollTargetY=Scrollbar(frameTarget)
+        scrollTargetX=Scrollbar(frameTarget,orient=HORIZONTAL)
+        self.listBindings=Listbox(frameTarget,takefocus=FALSE,
+                exportselection=FALSE)
+        self.listBindings.bind('<ButtonRelease-1>',self.KeyBindingSelected)
+        scrollTargetY.config(command=self.listBindings.yview)
+        scrollTargetX.config(command=self.listBindings.xview)
+        self.listBindings.config(yscrollcommand=scrollTargetY.set)
+        self.listBindings.config(xscrollcommand=scrollTargetX.set)
+        self.buttonNewKeys=Button(frameCustom,text='Get New Keys for Selection',
+            command=self.GetNewKeys,state=DISABLED)
+        buttonSaveCustomKeys=Button(frameCustom,
+                text='Save as New Custom Key Set',command=self.SaveAsNewKeySet)
+        #frameKeySets
+        labelKeysTitle=Label(frameKeySets,text='Select a Key Set')
+        labelTypeTitle=Label(frameKeySets,text='Select : ')
+        self.radioKeysBuiltin=Radiobutton(frameKeySets,variable=self.keysAreBuiltin,
+            value=1,command=self.SetKeysType,text='a Built-in Key Set')
+        self.radioKeysCustom=Radiobutton(frameKeySets,variable=self.keysAreBuiltin,
+            value=0,command=self.SetKeysType,text='a Custom Key Set')
+        self.optMenuKeysBuiltin=DynOptionMenu(frameKeySets,
+            self.builtinKeys,None,command=None)
+        self.optMenuKeysCustom=DynOptionMenu(frameKeySets,
+            self.customKeys,None,command=None)
+        self.buttonDeleteCustomKeys=Button(frameKeySets,text='Delete Custom Key Set',
+                command=self.DeleteCustomKeys)
+        ##widget packing
+        #body
+        frameCustom.pack(side=LEFT,padx=5,pady=5,expand=TRUE,fill=BOTH)
+        frameKeySets.pack(side=LEFT,padx=5,pady=5,fill=Y)
+        #frameCustom
+        labelCustomTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
+        buttonSaveCustomKeys.pack(side=BOTTOM,fill=X,padx=5,pady=5)
+        self.buttonNewKeys.pack(side=BOTTOM,fill=X,padx=5,pady=5)
+        frameTarget.pack(side=LEFT,padx=5,pady=5,expand=TRUE,fill=BOTH)
+        #frame target
+        frameTarget.columnconfigure(0,weight=1)
+        frameTarget.rowconfigure(1,weight=1)
+        labelTargetTitle.grid(row=0,column=0,columnspan=2,sticky=W)
+        self.listBindings.grid(row=1,column=0,sticky=NSEW)
+        scrollTargetY.grid(row=1,column=1,sticky=NS)
+        scrollTargetX.grid(row=2,column=0,sticky=EW)
+        #frameKeySets
+        labelKeysTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
+        labelTypeTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
+        self.radioKeysBuiltin.pack(side=TOP,anchor=W,padx=5)
+        self.radioKeysCustom.pack(side=TOP,anchor=W,padx=5,pady=2)
+        self.optMenuKeysBuiltin.pack(side=TOP,fill=X,padx=5,pady=5)
+        self.optMenuKeysCustom.pack(side=TOP,fill=X,anchor=W,padx=5,pady=5)
+        self.buttonDeleteCustomKeys.pack(side=TOP,fill=X,padx=5,pady=5)
+        return frame
+
+    def CreatePageGeneral(self):
+        #tkVars
+        self.winWidth=StringVar(self)
+        self.winHeight=StringVar(self)
+        self.startupEdit=IntVar(self)
+        self.userHelpBrowser=BooleanVar(self)
+        self.helpBrowser=StringVar(self)
+        #widget creation
+        #body
+        frame=self.tabPages.pages['General']['page']
+        #body section frames
+        frameRun=Frame(frame,borderwidth=2,relief=GROOVE)
+        frameWinSize=Frame(frame,borderwidth=2,relief=GROOVE)
+        frameHelp=Frame(frame,borderwidth=2,relief=GROOVE)
+        #frameRun
+        labelRunTitle=Label(frameRun,text='Startup Preferences')
+        labelRunChoiceTitle=Label(frameRun,text='On Startup : ')
+        radioStartupEdit=Radiobutton(frameRun,variable=self.startupEdit,
+            value=1,command=self.SetKeysType,text="Open Edit Window")
+        radioStartupShell=Radiobutton(frameRun,variable=self.startupEdit,
+            value=0,command=self.SetKeysType,text='Open Shell Window')
+        #frameWinSize
+        labelWinSizeTitle=Label(frameWinSize,text='Initial Window Size'+
+                '  (in characters)')
+        labelWinWidthTitle=Label(frameWinSize,text='Width')
+        entryWinWidth=Entry(frameWinSize,textvariable=self.winWidth,
+                width=3)
+        labelWinHeightTitle=Label(frameWinSize,text='Height')
+        entryWinHeight=Entry(frameWinSize,textvariable=self.winHeight,
+                width=3)
+        #frameHelp
+        labelHelpTitle=Label(frameHelp,text='Help Options')
+        frameHelpList=Frame(frameHelp)
+        frameHelpListButtons=Frame(frameHelpList)
+        labelHelpListTitle=Label(frameHelpList,text='Additional Help Sources:')
+        scrollHelpList=Scrollbar(frameHelpList)
+        self.listHelp=Listbox(frameHelpList,height=5,takefocus=FALSE,
+                exportselection=FALSE)
+        scrollHelpList.config(command=self.listHelp.yview)
+        self.listHelp.config(yscrollcommand=scrollHelpList.set)
+        self.listHelp.bind('<ButtonRelease-1>',self.HelpSourceSelected)
+        self.buttonHelpListEdit=Button(frameHelpListButtons,text='Edit',
+                state=DISABLED,width=8,command=self.HelpListItemEdit)
+        self.buttonHelpListAdd=Button(frameHelpListButtons,text='Add',
+                width=8,command=self.HelpListItemAdd)
+        self.buttonHelpListRemove=Button(frameHelpListButtons,text='Remove',
+                state=DISABLED,width=8,command=self.HelpListItemRemove)
+        # the following is better handled by the BROWSER environment
+        # variable under unix/linux
+        #checkHelpBrowser=Checkbutton(frameHelp,variable=self.userHelpBrowser,
+        #    onvalue=1,offvalue=0,text='user specified (html) help browser:',
+        #    command=self.OnCheckUserHelpBrowser)
+        #self.entryHelpBrowser=Entry(frameHelp,textvariable=self.helpBrowser,
+        #        width=40)
+        #widget packing
+        #body
+        frameRun.pack(side=TOP,padx=5,pady=5,fill=X)
+        frameWinSize.pack(side=TOP,padx=5,pady=5,fill=X)
+        frameHelp.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH)
+        #frameRun
+        labelRunTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
+        labelRunChoiceTitle.pack(side=LEFT,anchor=W,padx=5,pady=5)
+        radioStartupEdit.pack(side=LEFT,anchor=W,padx=5,pady=5)
+        radioStartupShell.pack(side=LEFT,anchor=W,padx=5,pady=5)
+        #frameWinSize
+        labelWinSizeTitle.pack(side=LEFT,anchor=W,padx=5,pady=5)
+        entryWinHeight.pack(side=RIGHT,anchor=E,padx=10,pady=5)
+        labelWinHeightTitle.pack(side=RIGHT,anchor=E,pady=5)
+        entryWinWidth.pack(side=RIGHT,anchor=E,padx=10,pady=5)
+        labelWinWidthTitle.pack(side=RIGHT,anchor=E,pady=5)
+        #frameHelp
+        labelHelpTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
+        frameHelpListButtons.pack(side=RIGHT,padx=5,pady=5,fill=Y)
+        frameHelpList.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH)
+        labelHelpListTitle.pack(side=TOP,anchor=W)
+        scrollHelpList.pack(side=RIGHT,anchor=W,fill=Y)
+        self.listHelp.pack(side=LEFT,anchor=E,expand=TRUE,fill=BOTH)
+        self.buttonHelpListEdit.pack(side=TOP,anchor=W,pady=5)
+        self.buttonHelpListAdd.pack(side=TOP,anchor=W)
+        self.buttonHelpListRemove.pack(side=TOP,anchor=W,pady=5)
+        #checkHelpBrowser.pack(side=TOP,anchor=W,padx=5)
+        #self.entryHelpBrowser.pack(side=TOP,anchor=W,padx=5,pady=5)
+        return frame
+
+    def AttachVarCallbacks(self):
+        self.fontSize.trace_variable('w',self.VarChanged_fontSize)
+        self.fontName.trace_variable('w',self.VarChanged_fontName)
+        self.fontBold.trace_variable('w',self.VarChanged_fontBold)
+        self.spaceNum.trace_variable('w',self.VarChanged_spaceNum)
+        #self.tabCols.trace_variable('w',self.VarChanged_tabCols)
+        self.indentBySpaces.trace_variable('w',self.VarChanged_indentBySpaces)
+        self.colour.trace_variable('w',self.VarChanged_colour)
+        self.builtinTheme.trace_variable('w',self.VarChanged_builtinTheme)
+        self.customTheme.trace_variable('w',self.VarChanged_customTheme)
+        self.themeIsBuiltin.trace_variable('w',self.VarChanged_themeIsBuiltin)
+        self.highlightTarget.trace_variable('w',self.VarChanged_highlightTarget)
+        self.keyBinding.trace_variable('w',self.VarChanged_keyBinding)
+        self.builtinKeys.trace_variable('w',self.VarChanged_builtinKeys)
+        self.customKeys.trace_variable('w',self.VarChanged_customKeys)
+        self.keysAreBuiltin.trace_variable('w',self.VarChanged_keysAreBuiltin)
+        self.winWidth.trace_variable('w',self.VarChanged_winWidth)
+        self.winHeight.trace_variable('w',self.VarChanged_winHeight)
+        self.startupEdit.trace_variable('w',self.VarChanged_startupEdit)
+
+    def VarChanged_fontSize(self,*params):
+        value=self.fontSize.get()
+        self.AddChangedItem('main','EditorWindow','font-size',value)
+
+    def VarChanged_fontName(self,*params):
+        value=self.fontName.get()
+        self.AddChangedItem('main','EditorWindow','font',value)
+
+    def VarChanged_fontBold(self,*params):
+        value=self.fontBold.get()
+        self.AddChangedItem('main','EditorWindow','font-bold',value)
+
+    def VarChanged_indentBySpaces(self,*params):
+        value=self.indentBySpaces.get()
+        self.AddChangedItem('main','Indent','use-spaces',value)
+
+    def VarChanged_spaceNum(self,*params):
+        value=self.spaceNum.get()
+        self.AddChangedItem('main','Indent','num-spaces',value)
+
+    #def VarChanged_tabCols(self,*params):
+    #    value=self.tabCols.get()
+    #    self.AddChangedItem('main','Indent','tab-cols',value)
+
+    def VarChanged_colour(self,*params):
+        self.OnNewColourSet()
+
+    def VarChanged_builtinTheme(self,*params):
+        value=self.builtinTheme.get()
+        self.AddChangedItem('main','Theme','name',value)
+        self.PaintThemeSample()
+
+    def VarChanged_customTheme(self,*params):
+        value=self.customTheme.get()
+        if value != '- no custom themes -':
+            self.AddChangedItem('main','Theme','name',value)
+            self.PaintThemeSample()
+
+    def VarChanged_themeIsBuiltin(self,*params):
+        value=self.themeIsBuiltin.get()
+        self.AddChangedItem('main','Theme','default',value)
+        if value:
+            self.VarChanged_builtinTheme()
+        else:
+            self.VarChanged_customTheme()
+
+    def VarChanged_highlightTarget(self,*params):
+        self.SetHighlightTarget()
+
+    def VarChanged_keyBinding(self,*params):
+        value=self.keyBinding.get()
+        keySet=self.customKeys.get()
+        event=self.listBindings.get(ANCHOR).split()[0]
+        if idleConf.IsCoreBinding(event):
+            #this is a core keybinding
+            self.AddChangedItem('keys',keySet,event,value)
+        else: #this is an extension key binding
+            extName=idleConf.GetExtnNameForEvent(event)
+            extKeybindSection=extName+'_cfgBindings'
+            self.AddChangedItem('extensions',extKeybindSection,event,value)
+
+    def VarChanged_builtinKeys(self,*params):
+        value=self.builtinKeys.get()
+        self.AddChangedItem('main','Keys','name',value)
+        self.LoadKeysList(value)
+
+    def VarChanged_customKeys(self,*params):
+        value=self.customKeys.get()
+        if value != '- no custom keys -':
+            self.AddChangedItem('main','Keys','name',value)
+            self.LoadKeysList(value)
+
+    def VarChanged_keysAreBuiltin(self,*params):
+        value=self.keysAreBuiltin.get()
+        self.AddChangedItem('main','Keys','default',value)
+        if value:
+            self.VarChanged_builtinKeys()
+        else:
+            self.VarChanged_customKeys()
+
+    def VarChanged_winWidth(self,*params):
+        value=self.winWidth.get()
+        self.AddChangedItem('main','EditorWindow','width',value)
+
+    def VarChanged_winHeight(self,*params):
+        value=self.winHeight.get()
+        self.AddChangedItem('main','EditorWindow','height',value)
+
+    def VarChanged_startupEdit(self,*params):
+        value=self.startupEdit.get()
+        self.AddChangedItem('main','General','editor-on-startup',value)
+
+    def ResetChangedItems(self):
+        #When any config item is changed in this dialog, an entry
+        #should be made in the relevant section (config type) of this
+        #dictionary. The key should be the config file section name and the
+        #value a dictionary, whose key:value pairs are item=value pairs for
+        #that config file section.
+        self.changedItems={'main':{},'highlight':{},'keys':{},'extensions':{}}
+
+    def AddChangedItem(self,type,section,item,value):
+        value=str(value) #make sure we use a string
+        if not self.changedItems[type].has_key(section):
+            self.changedItems[type][section]={}
+        self.changedItems[type][section][item]=value
+
+    def GetDefaultItems(self):
+        dItems={'main':{},'highlight':{},'keys':{},'extensions':{}}
+        for configType in dItems.keys():
+            sections=idleConf.GetSectionList('default',configType)
+            for section in sections:
+                dItems[configType][section]={}
+                options=idleConf.defaultCfg[configType].GetOptionList(section)
+                for option in options:
+                    dItems[configType][section][option]=(
+                            idleConf.defaultCfg[configType].Get(section,option))
+        return dItems
+
+    def SetThemeType(self):
+        if self.themeIsBuiltin.get():
+            self.optMenuThemeBuiltin.config(state=NORMAL)
+            self.optMenuThemeCustom.config(state=DISABLED)
+            self.buttonDeleteCustomTheme.config(state=DISABLED)
+        else:
+            self.optMenuThemeBuiltin.config(state=DISABLED)
+            self.radioThemeCustom.config(state=NORMAL)
+            self.optMenuThemeCustom.config(state=NORMAL)
+            self.buttonDeleteCustomTheme.config(state=NORMAL)
+
+    def SetKeysType(self):
+        if self.keysAreBuiltin.get():
+            self.optMenuKeysBuiltin.config(state=NORMAL)
+            self.optMenuKeysCustom.config(state=DISABLED)
+            self.buttonDeleteCustomKeys.config(state=DISABLED)
+        else:
+            self.optMenuKeysBuiltin.config(state=DISABLED)
+            self.radioKeysCustom.config(state=NORMAL)
+            self.optMenuKeysCustom.config(state=NORMAL)
+            self.buttonDeleteCustomKeys.config(state=NORMAL)
+
+    def GetNewKeys(self):
+        listIndex=self.listBindings.index(ANCHOR)
+        binding=self.listBindings.get(listIndex)
+        bindName=binding.split()[0] #first part, up to first space
+        if self.keysAreBuiltin.get():
+            currentKeySetName=self.builtinKeys.get()
+        else:
+            currentKeySetName=self.customKeys.get()
+        currentBindings=idleConf.GetCurrentKeySet()
+        if currentKeySetName in self.changedItems['keys'].keys(): #unsaved changes
+            keySetChanges=self.changedItems['keys'][currentKeySetName]
+            for event in keySetChanges.keys():
+                currentBindings[event]=keySetChanges[event].split()
+        currentKeySequences=currentBindings.values()
+        newKeys=GetKeysDialog(self,'Get New Keys',bindName,
+                currentKeySequences).result
+        if newKeys: #new keys were specified
+            if self.keysAreBuiltin.get(): #current key set is a built-in
+                message=('Your changes will be saved as a new Custom Key Set. '+
+                        'Enter a name for your new Custom Key Set below.')
+                newKeySet=self.GetNewKeysName(message)
+                if not newKeySet: #user cancelled custom key set creation
+                    self.listBindings.select_set(listIndex)
+                    self.listBindings.select_anchor(listIndex)
+                    return
+                else: #create new custom key set based on previously active key set
+                    self.CreateNewKeySet(newKeySet)
+            self.listBindings.delete(listIndex)
+            self.listBindings.insert(listIndex,bindName+' - '+newKeys)
+            self.listBindings.select_set(listIndex)
+            self.listBindings.select_anchor(listIndex)
+            self.keyBinding.set(newKeys)
+        else:
+            self.listBindings.select_set(listIndex)
+            self.listBindings.select_anchor(listIndex)
+
+    def GetNewKeysName(self,message):
+        usedNames=(idleConf.GetSectionList('user','keys')+
+                idleConf.GetSectionList('default','keys'))
+        newKeySet=GetCfgSectionNameDialog(self,'New Custom Key Set',
+                message,usedNames).result
+        return newKeySet
+
+    def SaveAsNewKeySet(self):
+        newKeysName=self.GetNewKeysName('New Key Set Name:')
+        if newKeysName:
+            self.CreateNewKeySet(newKeysName)
+
+    def KeyBindingSelected(self,event):
+        self.buttonNewKeys.config(state=NORMAL)
+
+    def CreateNewKeySet(self,newKeySetName):
+        #creates new custom key set based on the previously active key set,
+        #and makes the new key set active
+        if self.keysAreBuiltin.get():
+            prevKeySetName=self.builtinKeys.get()
+        else:
+            prevKeySetName=self.customKeys.get()
+        prevKeys=idleConf.GetCoreKeys(prevKeySetName)
+        newKeys={}
+        for event in prevKeys.keys(): #add key set to changed items
+            eventName=event[2:-2] #trim off the angle brackets
+            binding=string.join(prevKeys[event])
+            newKeys[eventName]=binding
+        #handle any unsaved changes to prev key set
+        if prevKeySetName in self.changedItems['keys'].keys():
+            keySetChanges=self.changedItems['keys'][prevKeySetName]
+            for event in keySetChanges.keys():
+                newKeys[event]=keySetChanges[event]
+        #save the new theme
+        self.SaveNewKeySet(newKeySetName,newKeys)
+        #change gui over to the new key set
+        customKeyList=idleConf.GetSectionList('user','keys')
+        customKeyList.sort()
+        self.optMenuKeysCustom.SetMenu(customKeyList,newKeySetName)
+        self.keysAreBuiltin.set(0)
+        self.SetKeysType()
+
+    def LoadKeysList(self,keySetName):
+        reselect=0
+        newKeySet=0
+        if self.listBindings.curselection():
+            reselect=1
+            listIndex=self.listBindings.index(ANCHOR)
+        keySet=idleConf.GetKeySet(keySetName)
+        bindNames=keySet.keys()
+        bindNames.sort()
+        self.listBindings.delete(0,END)
+        for bindName in bindNames:
+            key=string.join(keySet[bindName]) #make key(s) into a string
+            bindName=bindName[2:-2] #trim off the angle brackets
+            if keySetName in self.changedItems['keys'].keys():
+                #handle any unsaved changes to this key set
+                if bindName in self.changedItems['keys'][keySetName].keys():
+                    key=self.changedItems['keys'][keySetName][bindName]
+            self.listBindings.insert(END, bindName+' - '+key)
+        if reselect:
+            self.listBindings.see(listIndex)
+            self.listBindings.select_set(listIndex)
+            self.listBindings.select_anchor(listIndex)
+
+    def DeleteCustomKeys(self):
+        keySetName=self.customKeys.get()
+        if not tkMessageBox.askyesno('Delete Key Set','Are you sure you wish '+
+                                     'to delete the key set '+`keySetName`+' ?',
+                                     parent=self):
+            return
+        #remove key set from config
+        idleConf.userCfg['keys'].remove_section(keySetName)
+        if self.changedItems['keys'].has_key(keySetName):
+            del(self.changedItems['keys'][keySetName])
+        #write changes
+        idleConf.userCfg['keys'].Save()
+        #reload user key set list
+        itemList=idleConf.GetSectionList('user','keys')
+        itemList.sort()
+        if not itemList:
+            self.radioKeysCustom.config(state=DISABLED)
+            self.optMenuKeysCustom.SetMenu(itemList,'- no custom keys -')
+        else:
+            self.optMenuKeysCustom.SetMenu(itemList,itemList[0])
+        #revert to default key set
+        self.keysAreBuiltin.set(idleConf.defaultCfg['main'].Get('Keys','default'))
+        self.builtinKeys.set(idleConf.defaultCfg['main'].Get('Keys','name'))
+        #user can't back out of these changes, they must be applied now
+        self.Apply()
+        self.SetKeysType()
+
+    def DeleteCustomTheme(self):
+        themeName=self.customTheme.get()
+        if not tkMessageBox.askyesno('Delete Theme','Are you sure you wish '+
+                                     'to delete the theme '+`themeName`+' ?',
+                                     parent=self):
+            return
+        #remove theme from config
+        idleConf.userCfg['highlight'].remove_section(themeName)
+        if self.changedItems['highlight'].has_key(themeName):
+            del(self.changedItems['highlight'][themeName])
+        #write changes
+        idleConf.userCfg['highlight'].Save()
+        #reload user theme list
+        itemList=idleConf.GetSectionList('user','highlight')
+        itemList.sort()
+        if not itemList:
+            self.radioThemeCustom.config(state=DISABLED)
+            self.optMenuThemeCustom.SetMenu(itemList,'- no custom themes -')
+        else:
+            self.optMenuThemeCustom.SetMenu(itemList,itemList[0])
+        #revert to default theme
+        self.themeIsBuiltin.set(idleConf.defaultCfg['main'].Get('Theme','default'))
+        self.builtinTheme.set(idleConf.defaultCfg['main'].Get('Theme','name'))
+        #user can't back out of these changes, they must be applied now
+        self.Apply()
+        self.SetThemeType()
+
+    def GetColour(self):
+        target=self.highlightTarget.get()
+        prevColour=self.frameColourSet.cget('bg')
+        rgbTuplet, colourString = tkColorChooser.askcolor(parent=self,
+            title='Pick new colour for : '+target,initialcolor=prevColour)
+        if colourString and (colourString!=prevColour):
+            #user didn't cancel, and they chose a new colour
+            if self.themeIsBuiltin.get(): #current theme is a built-in
+                message=('Your changes will be saved as a new Custom Theme. '+
+                        'Enter a name for your new Custom Theme below.')
+                newTheme=self.GetNewThemeName(message)
+                if not newTheme: #user cancelled custom theme creation
+                    return
+                else: #create new custom theme based on previously active theme
+                    self.CreateNewTheme(newTheme)
+                    self.colour.set(colourString)
+            else: #current theme is user defined
+                self.colour.set(colourString)
+
+    def OnNewColourSet(self):
+        newColour=self.colour.get()
+        self.frameColourSet.config(bg=newColour)#set sample
+        if self.fgHilite.get(): plane='foreground'
+        else: plane='background'
+        sampleElement=self.themeElements[self.highlightTarget.get()][0]
+        apply(self.textHighlightSample.tag_config,
+                (sampleElement,),{plane:newColour})
+        theme=self.customTheme.get()
+        themeElement=sampleElement+'-'+plane
+        self.AddChangedItem('highlight',theme,themeElement,newColour)
+
+    def GetNewThemeName(self,message):
+        usedNames=(idleConf.GetSectionList('user','highlight')+
+                idleConf.GetSectionList('default','highlight'))
+        newTheme=GetCfgSectionNameDialog(self,'New Custom Theme',
+                message,usedNames).result
+        return newTheme
+
+    def SaveAsNewTheme(self):
+        newThemeName=self.GetNewThemeName('New Theme Name:')
+        if newThemeName:
+            self.CreateNewTheme(newThemeName)
+
+    def CreateNewTheme(self,newThemeName):
+        #creates new custom theme based on the previously active theme,
+        #and makes the new theme active
+        if self.themeIsBuiltin.get():
+            themeType='default'
+            themeName=self.builtinTheme.get()
+        else:
+            themeType='user'
+            themeName=self.customTheme.get()
+        newTheme=idleConf.GetThemeDict(themeType,themeName)
+        #apply any of the old theme's unsaved changes to the new theme
+        if themeName in self.changedItems['highlight'].keys():
+            themeChanges=self.changedItems['highlight'][themeName]
+            for element in themeChanges.keys():
+                newTheme[element]=themeChanges[element]
+        #save the new theme
+        self.SaveNewTheme(newThemeName,newTheme)
+        #change gui over to the new theme
+        customThemeList=idleConf.GetSectionList('user','highlight')
+        customThemeList.sort()
+        self.optMenuThemeCustom.SetMenu(customThemeList,newThemeName)
+        self.themeIsBuiltin.set(0)
+        self.SetThemeType()
+
+    def OnListFontButtonRelease(self,event):
+        self.fontName.set(self.listFontName.get(ANCHOR))
+        self.SetFontSample()
+
+    def SetFontSample(self,event=None):
+        fontName=self.fontName.get()
+        if self.fontBold.get():
+            fontWeight=tkFont.BOLD
+        else:
+            fontWeight=tkFont.NORMAL
+        self.editFont.config(size=self.fontSize.get(),
+                weight=fontWeight,family=fontName)
+
+    def SetHighlightTarget(self):
+        if self.highlightTarget.get()=='Cursor': #bg not possible
+            self.radioFg.config(state=DISABLED)
+            self.radioBg.config(state=DISABLED)
+            self.fgHilite.set(1)
+        else: #both fg and bg can be set
+            self.radioFg.config(state=NORMAL)
+            self.radioBg.config(state=NORMAL)
+            self.fgHilite.set(1)
+        self.SetColourSample()
+
+    def SetColourSampleBinding(self,*args):
+        self.SetColourSample()
+
+    def SetColourSample(self):
+        #set the colour smaple area
+        tag=self.themeElements[self.highlightTarget.get()][0]
+        if self.fgHilite.get(): plane='foreground'
+        else: plane='background'
+        colour=self.textHighlightSample.tag_cget(tag,plane)
+        self.frameColourSet.config(bg=colour)
+
+    def PaintThemeSample(self):
+        if self.themeIsBuiltin.get(): #a default theme
+            theme=self.builtinTheme.get()
+        else: #a user theme
+            theme=self.customTheme.get()
+        for elementTitle in self.themeElements.keys():
+            element=self.themeElements[elementTitle][0]
+            colours=idleConf.GetHighlight(theme,element)
+            if element=='cursor': #cursor sample needs special painting
+                colours['background']=idleConf.GetHighlight(theme,
+                        'normal', fgBg='bg')
+            #handle any unsaved changes to this theme
+            if theme in self.changedItems['highlight'].keys():
+                themeDict=self.changedItems['highlight'][theme]
+                if themeDict.has_key(element+'-foreground'):
+                    colours['foreground']=themeDict[element+'-foreground']
+                if themeDict.has_key(element+'-background'):
+                    colours['background']=themeDict[element+'-background']
+            apply(self.textHighlightSample.tag_config,(element,),colours)
+        self.SetColourSample()
+
+##     def OnCheckUserHelpBrowser(self):
+##         if self.userHelpBrowser.get():
+##             self.entryHelpBrowser.config(state=NORMAL)
+##         else:
+##             self.entryHelpBrowser.config(state=DISABLED)
+
+    def HelpSourceSelected(self,event):
+        self.SetHelpListButtonStates()
+
+    def SetHelpListButtonStates(self):
+        if self.listHelp.size()<1: #no entries in list
+            self.buttonHelpListEdit.config(state=DISABLED)
+            self.buttonHelpListRemove.config(state=DISABLED)
+        else: #there are some entries
+            if self.listHelp.curselection(): #there currently is a selection
+                self.buttonHelpListEdit.config(state=NORMAL)
+                self.buttonHelpListRemove.config(state=NORMAL)
+            else:  #there currently is not a selection
+                self.buttonHelpListEdit.config(state=DISABLED)
+                self.buttonHelpListRemove.config(state=DISABLED)
+
+    def HelpListItemAdd(self):
+        helpSource=GetHelpSourceDialog(self,'New Help Source').result
+        if helpSource:
+            self.userHelpList.append( (helpSource[0],helpSource[1]) )
+            self.listHelp.insert(END,helpSource[0])
+            self.UpdateUserHelpChangedItems()
+        self.SetHelpListButtonStates()
+
+    def HelpListItemEdit(self):
+        itemIndex=self.listHelp.index(ANCHOR)
+        helpSource=self.userHelpList[itemIndex]
+        newHelpSource=GetHelpSourceDialog(self,'Edit Help Source',
+                menuItem=helpSource[0],filePath=helpSource[1]).result
+        if (not newHelpSource) or (newHelpSource==helpSource):
+            return #no changes
+        self.userHelpList[itemIndex]=newHelpSource
+        self.listHelp.delete(itemIndex)
+        self.listHelp.insert(itemIndex,newHelpSource[0])
+        self.UpdateUserHelpChangedItems()
+        self.SetHelpListButtonStates()
+
+    def HelpListItemRemove(self):
+        itemIndex=self.listHelp.index(ANCHOR)
+        del(self.userHelpList[itemIndex])
+        self.listHelp.delete(itemIndex)
+        self.UpdateUserHelpChangedItems()
+        self.SetHelpListButtonStates()
+
+    def UpdateUserHelpChangedItems(self):
+        "Clear and rebuild the HelpFiles section in self.changedItems"
+        self.changedItems['main']['HelpFiles'] = {}
+        for num in range(1,len(self.userHelpList)+1):
+            self.AddChangedItem('main','HelpFiles',str(num),
+                    string.join(self.userHelpList[num-1][:2],';'))
+
+    def LoadFontCfg(self):
+        ##base editor font selection list
+        fonts=list(tkFont.families(self))
+        fonts.sort()
+        for font in fonts:
+            self.listFontName.insert(END,font)
+        configuredFont=idleConf.GetOption('main','EditorWindow','font',
+                default='courier')
+        self.fontName.set(configuredFont)
+        if configuredFont in fonts:
+            currentFontIndex=fonts.index(configuredFont)
+            self.listFontName.see(currentFontIndex)
+            self.listFontName.select_set(currentFontIndex)
+            self.listFontName.select_anchor(currentFontIndex)
+        ##font size dropdown
+        fontSize=idleConf.GetOption('main','EditorWindow','font-size',
+                default='12')
+        self.optMenuFontSize.SetMenu(('7','8','9','10','11','12','13','14',
+                '16','18','20','22'),fontSize )
+        ##fontWeight
+        self.fontBold.set(idleConf.GetOption('main','EditorWindow',
+                'font-bold',default=0,type='bool'))
+        ##font sample
+        self.SetFontSample()
+
+    def LoadTabCfg(self):
+        ##indent type radiobuttons
+        spaceIndent=idleConf.GetOption('main','Indent','use-spaces',
+                default=1,type='bool')
+        self.indentBySpaces.set(spaceIndent)
+        ##indent sizes
+        spaceNum=idleConf.GetOption('main','Indent','num-spaces',
+                default=4,type='int')
+        #tabCols=idleConf.GetOption('main','Indent','tab-cols',
+        #        default=4,type='int')
+        self.spaceNum.set(spaceNum)
+        #self.tabCols.set(tabCols)
+
+    def LoadThemeCfg(self):
+        ##current theme type radiobutton
+        self.themeIsBuiltin.set(idleConf.GetOption('main','Theme','default',
+            type='bool',default=1))
+        ##currently set theme
+        currentOption=idleConf.CurrentTheme()
+        ##load available theme option menus
+        if self.themeIsBuiltin.get(): #default theme selected
+            itemList=idleConf.GetSectionList('default','highlight')
+            itemList.sort()
+            self.optMenuThemeBuiltin.SetMenu(itemList,currentOption)
+            itemList=idleConf.GetSectionList('user','highlight')
+            itemList.sort()
+            if not itemList:
+                self.radioThemeCustom.config(state=DISABLED)
+                self.customTheme.set('- no custom themes -')
+            else:
+                self.optMenuThemeCustom.SetMenu(itemList,itemList[0])
+        else: #user theme selected
+            itemList=idleConf.GetSectionList('user','highlight')
+            itemList.sort()
+            self.optMenuThemeCustom.SetMenu(itemList,currentOption)
+            itemList=idleConf.GetSectionList('default','highlight')
+            itemList.sort()
+            self.optMenuThemeBuiltin.SetMenu(itemList,itemList[0])
+        self.SetThemeType()
+        ##load theme element option menu
+        themeNames=self.themeElements.keys()
+        themeNames.sort(self.__ThemeNameIndexCompare)
+        self.optMenuHighlightTarget.SetMenu(themeNames,themeNames[0])
+        self.PaintThemeSample()
+        self.SetHighlightTarget()
+
+    def __ThemeNameIndexCompare(self,a,b):
+        if self.themeElements[a][1]<self.themeElements[b][1]: return -1
+        elif self.themeElements[a][1]==self.themeElements[b][1]: return 0
+        else: return 1
+
+    def LoadKeyCfg(self):
+        ##current keys type radiobutton
+        self.keysAreBuiltin.set(idleConf.GetOption('main','Keys','default',
+            type='bool',default=1))
+        ##currently set keys
+        currentOption=idleConf.CurrentKeys()
+        ##load available keyset option menus
+        if self.keysAreBuiltin.get(): #default theme selected
+            itemList=idleConf.GetSectionList('default','keys')
+            itemList.sort()
+            self.optMenuKeysBuiltin.SetMenu(itemList,currentOption)
+            itemList=idleConf.GetSectionList('user','keys')
+            itemList.sort()
+            if not itemList:
+                self.radioKeysCustom.config(state=DISABLED)
+                self.customKeys.set('- no custom keys -')
+            else:
+                self.optMenuKeysCustom.SetMenu(itemList,itemList[0])
+        else: #user key set selected
+            itemList=idleConf.GetSectionList('user','keys')
+            itemList.sort()
+            self.optMenuKeysCustom.SetMenu(itemList,currentOption)
+            itemList=idleConf.GetSectionList('default','keys')
+            itemList.sort()
+            self.optMenuKeysBuiltin.SetMenu(itemList,itemList[0])
+        self.SetKeysType()
+        ##load keyset element list
+        keySetName=idleConf.CurrentKeys()
+        self.LoadKeysList(keySetName)
+
+    def LoadGeneralCfg(self):
+        #startup state
+        self.startupEdit.set(idleConf.GetOption('main','General',
+                'editor-on-startup',default=1,type='bool'))
+        #initial window size
+        self.winWidth.set(idleConf.GetOption('main','EditorWindow','width'))
+        self.winHeight.set(idleConf.GetOption('main','EditorWindow','height'))
+        # additional help sources
+        self.userHelpList = idleConf.GetAllExtraHelpSourcesList()
+        for helpItem in self.userHelpList:
+            self.listHelp.insert(END,helpItem[0])
+        self.SetHelpListButtonStates()
+        #self.userHelpBrowser.set(idleConf.GetOption('main','General',
+        #        'user-help-browser',default=0,type='bool'))
+        #self.helpBrowser.set(idleConf.GetOption('main','General',
+        #        'user-help-browser-command',default=''))
+        #self.OnCheckUserHelpBrowser()
+
+    def LoadConfigs(self):
+        """
+        load configuration from default and user config files and populate
+        the widgets on the config dialog pages.
+        """
+        ### fonts / tabs page
+        self.LoadFontCfg()
+        self.LoadTabCfg()
+        ### highlighting page
+        self.LoadThemeCfg()
+        ### keys page
+        self.LoadKeyCfg()
+        ### general page
+        self.LoadGeneralCfg()
+
+    def SaveNewKeySet(self,keySetName,keySet):
+        """
+        save a newly created core key set.
+        keySetName - string, the name of the new key set
+        keySet - dictionary containing the new key set
+        """
+        if not idleConf.userCfg['keys'].has_section(keySetName):
+            idleConf.userCfg['keys'].add_section(keySetName)
+        for event in keySet.keys():
+            value=keySet[event]
+            idleConf.userCfg['keys'].SetOption(keySetName,event,value)
+
+    def SaveNewTheme(self,themeName,theme):
+        """
+        save a newly created theme.
+        themeName - string, the name of the new theme
+        theme - dictionary containing the new theme
+        """
+        if not idleConf.userCfg['highlight'].has_section(themeName):
+            idleConf.userCfg['highlight'].add_section(themeName)
+        for element in theme.keys():
+            value=theme[element]
+            idleConf.userCfg['highlight'].SetOption(themeName,element,value)
+
+    def SetUserValue(self,configType,section,item,value):
+        if idleConf.defaultCfg[configType].has_option(section,item):
+            if idleConf.defaultCfg[configType].Get(section,item)==value:
+                #the setting equals a default setting, remove it from user cfg
+                return idleConf.userCfg[configType].RemoveOption(section,item)
+        #if we got here set the option
+        return idleConf.userCfg[configType].SetOption(section,item,value)
+
+    def SaveAllChangedConfigs(self):
+        "Save configuration changes to the user config file."
+        idleConf.userCfg['main'].Save()
+        for configType in self.changedItems.keys():
+            cfgTypeHasChanges = False
+            for section in self.changedItems[configType].keys():
+                if section == 'HelpFiles':
+                    #this section gets completely replaced
+                    idleConf.userCfg['main'].remove_section('HelpFiles')
+                    cfgTypeHasChanges = True
+                for item in self.changedItems[configType][section].keys():
+                    value = self.changedItems[configType][section][item]
+                    if self.SetUserValue(configType,section,item,value):
+                        cfgTypeHasChanges = True
+            if cfgTypeHasChanges:
+                idleConf.userCfg[configType].Save()
+        self.ResetChangedItems() #clear the changed items dict
+
+    def ActivateConfigChanges(self):
+        #things that need to be done to make
+        #applied config changes dynamic:
+        #update editor/shell font and repaint
+        #dynamically update indentation setttings
+        #update theme and repaint
+        #update keybindings and re-bind
+        #update user help sources menu
+        winInstances=self.parent.instanceDict.keys()
+        for instance in winInstances:
+            instance.ResetColorizer()
+            instance.ResetFont()
+            instance.ResetKeybindings()
+            instance.reset_help_menu_entries()
+
+    def Cancel(self):
+        self.destroy()
+
+    def Ok(self):
+        self.Apply()
+        self.destroy()
+
+    def Apply(self):
+        self.SaveAllChangedConfigs()
+        self.ActivateConfigChanges()
+
+    def Help(self):
+        pass
+
+if __name__ == '__main__':
+    #test the dialog
+    root=Tk()
+    Button(root,text='Dialog',
+            command=lambda:ConfigDialog(root,'Settings')).pack()
+    root.instanceDict={}
+    root.mainloop()
diff --git a/Tools/idle/configHandler.py b/Tools/idle/configHandler.py
new file mode 100644
index 0000000..fd9cbc4
--- /dev/null
+++ b/Tools/idle/configHandler.py
@@ -0,0 +1,655 @@
+"""Provides access to stored IDLE configuration information.
+
+Refer to the comments at the beginning of config-main.def for a description of
+the available configuration files and the design implemented to update user
+configuration information.  In particular, user configuration choices which
+duplicate the defaults will be removed from the user's configuration files,
+and if a file becomes empty, it will be deleted.
+
+The contents of the user files may be altered using the Options/Configure IDLE
+menu to access the configuration GUI (configDialog.py), or manually.
+
+Throughout this module there is an emphasis on returning useable defaults
+when a problem occurs in returning a requested configuration value back to
+idle. This is to allow IDLE to continue to function in spite of errors in
+the retrieval of config information. When a default is returned instead of
+a requested config value, a message is printed to stderr to aid in
+configuration problem notification and resolution.
+
+"""
+import os
+import sys
+import string
+from ConfigParser import ConfigParser, NoOptionError, NoSectionError
+
+class InvalidConfigType(Exception): pass
+class InvalidConfigSet(Exception): pass
+class InvalidFgBg(Exception): pass
+class InvalidTheme(Exception): pass
+
+class IdleConfParser(ConfigParser):
+    """
+    A ConfigParser specialised for idle configuration file handling
+    """
+    def __init__(self, cfgFile, cfgDefaults=None):
+        """
+        cfgFile - string, fully specified configuration file name
+        """
+        self.file=cfgFile
+        ConfigParser.__init__(self,defaults=cfgDefaults)
+
+    def Get(self, section, option, type=None, default=None):
+        """
+        Get an option value for given section/option or return default.
+        If type is specified, return as type.
+        """
+        if type=='bool':
+            getVal=self.getboolean
+        elif type=='int':
+            getVal=self.getint
+        else:
+            getVal=self.get
+        if self.has_option(section,option):
+            #return getVal(section, option, raw, vars, default)
+            return getVal(section, option)
+        else:
+            return default
+
+    def GetOptionList(self,section):
+        """
+        Get an option list for given section
+        """
+        if self.has_section(section):
+            return self.options(section)
+        else:  #return a default value
+            return []
+
+    def Load(self):
+        """
+        Load the configuration file from disk
+        """
+        self.read(self.file)
+
+class IdleUserConfParser(IdleConfParser):
+    """
+    IdleConfigParser specialised for user configuration handling.
+    """
+
+    def AddSection(self,section):
+        """
+        if section doesn't exist, add it
+        """
+        if not self.has_section(section):
+            self.add_section(section)
+
+    def RemoveEmptySections(self):
+        """
+        remove any sections that have no options
+        """
+        for section in self.sections():
+            if not self.GetOptionList(section):
+                self.remove_section(section)
+
+    def IsEmpty(self):
+        """
+        Remove empty sections and then return 1 if parser has no sections
+        left, else return 0.
+        """
+        self.RemoveEmptySections()
+        if self.sections():
+            return 0
+        else:
+            return 1
+
+    def RemoveOption(self,section,option):
+        """
+        If section/option exists, remove it.
+        Returns 1 if option was removed, 0 otherwise.
+        """
+        if self.has_section(section):
+            return self.remove_option(section,option)
+
+    def SetOption(self,section,option,value):
+        """
+        Sets option to value, adding section if required.
+        Returns 1 if option was added or changed, otherwise 0.
+        """
+        if self.has_option(section,option):
+            if self.get(section,option)==value:
+                return 0
+            else:
+                self.set(section,option,value)
+                return 1
+        else:
+            if not self.has_section(section):
+                self.add_section(section)
+            self.set(section,option,value)
+            return 1
+
+    def RemoveFile(self):
+        """
+        Removes the user config file from disk if it exists.
+        """
+        if os.path.exists(self.file):
+            os.remove(self.file)
+
+    def Save(self):
+        """Update user configuration file.
+
+        Remove empty sections. If resulting config isn't empty, write the file
+        to disk. If config is empty, remove the file from disk if it exists.
+
+        """
+        if not self.IsEmpty():
+            cfgFile=open(self.file,'w')
+            self.write(cfgFile)
+        else:
+            self.RemoveFile()
+
+class IdleConf:
+    """
+    holds config parsers for all idle config files:
+    default config files
+        (idle install dir)/config-main.def
+        (idle install dir)/config-extensions.def
+        (idle install dir)/config-highlight.def
+        (idle install dir)/config-keys.def
+    user config  files
+        (user home dir)/.idlerc/config-main.cfg
+        (user home dir)/.idlerc/config-extensions.cfg
+        (user home dir)/.idlerc/config-highlight.cfg
+        (user home dir)/.idlerc/config-keys.cfg
+    """
+    def __init__(self):
+        self.defaultCfg={}
+        self.userCfg={}
+        self.cfg={}
+        self.CreateConfigHandlers()
+        self.LoadCfgFiles()
+        #self.LoadCfg()
+
+    def CreateConfigHandlers(self):
+        """
+        set up a dictionary of config parsers for default and user
+        configurations respectively
+        """
+        #build idle install path
+        if __name__ != '__main__': # we were imported
+            idleDir=os.path.dirname(__file__)
+        else: # we were exec'ed (for testing only)
+            idleDir=os.path.abspath(sys.path[0])
+        userDir=self.GetUserCfgDir()
+        configTypes=('main','extensions','highlight','keys')
+        defCfgFiles={}
+        usrCfgFiles={}
+        for cfgType in configTypes: #build config file names
+            defCfgFiles[cfgType]=os.path.join(idleDir,'config-'+cfgType+'.def')
+            usrCfgFiles[cfgType]=os.path.join(userDir,'config-'+cfgType+'.cfg')
+        for cfgType in configTypes: #create config parsers
+            self.defaultCfg[cfgType]=IdleConfParser(defCfgFiles[cfgType])
+            self.userCfg[cfgType]=IdleUserConfParser(usrCfgFiles[cfgType])
+
+    def GetUserCfgDir(self):
+        """
+        Creates (if required) and returns a filesystem directory for storing
+        user config files.
+        """
+        cfgDir='.idlerc'
+        userDir=os.path.expanduser('~')
+        if userDir != '~': #'HOME' exists as a key in os.environ
+            if not os.path.exists(userDir):
+                warn=('\n Warning: HOME environment variable points to\n '+
+                        userDir+'\n but the path does not exist.\n')
+                sys.stderr.write(warn)
+                userDir='~'
+        if userDir=='~': #we still don't have a home directory
+            #traditionally idle has defaulted to os.getcwd(), is this adeqate?
+            userDir = os.getcwd() #hack for no real homedir
+        userDir=os.path.join(userDir,cfgDir)
+        if not os.path.exists(userDir):
+            try: #make the config dir if it doesn't exist yet
+                os.mkdir(userDir)
+            except IOError:
+                warn=('\n Warning: unable to create user config directory\n '+
+                        userDir+'\n')
+                sys.stderr.write(warn)
+        return userDir
+
+    def GetOption(self, configType, section, option, default=None, type=None):
+        """
+        Get an option value for given config type and given general
+        configuration section/option or return a default. If type is specified,
+        return as type. Firstly the user configuration is checked, with a
+        fallback to the default configuration, and a final 'catch all'
+        fallback to a useable passed-in default if the option isn't present in
+        either the user or the default configuration.
+        configType must be one of ('main','extensions','highlight','keys')
+        If a default is returned a warning is printed to stderr.
+        """
+        if self.userCfg[configType].has_option(section,option):
+            return self.userCfg[configType].Get(section, option, type=type)
+        elif self.defaultCfg[configType].has_option(section,option):
+            return self.defaultCfg[configType].Get(section, option, type=type)
+        else: #returning default, print warning
+            warning=('\n Warning: configHandler.py - IdleConf.GetOption -\n'+
+                       ' problem retrieving configration option '+`option`+'\n'+
+                       ' from section '+`section`+'.\n'+
+                       ' returning default value: '+`default`+'\n')
+            sys.stderr.write(warning)
+            return default
+
+    def GetSectionList(self, configSet, configType):
+        """
+        Get a list of sections from either the user or default config for
+        the given config type.
+        configSet must be either 'user' or 'default'
+        configType must be one of ('main','extensions','highlight','keys')
+        """
+        if not (configType in ('main','extensions','highlight','keys')):
+            raise InvalidConfigType, 'Invalid configType specified'
+        if configSet == 'user':
+            cfgParser=self.userCfg[configType]
+        elif configSet == 'default':
+            cfgParser=self.defaultCfg[configType]
+        else:
+            raise InvalidConfigSet, 'Invalid configSet specified'
+        return cfgParser.sections()
+
+    def GetHighlight(self, theme, element, fgBg=None):
+        """
+        return individual highlighting theme elements.
+        fgBg - string ('fg'or'bg') or None, if None return a dictionary
+        containing fg and bg colours (appropriate for passing to Tkinter in,
+        e.g., a tag_config call), otherwise fg or bg colour only as specified.
+        """
+        if self.defaultCfg['highlight'].has_section(theme):
+            themeDict=self.GetThemeDict('default',theme)
+        else:
+            themeDict=self.GetThemeDict('user',theme)
+        fore=themeDict[element+'-foreground']
+        if element=='cursor': #there is no config value for cursor bg
+            back=themeDict['normal-background']
+        else:
+            back=themeDict[element+'-background']
+        highlight={"foreground": fore,"background": back}
+        if not fgBg: #return dict of both colours
+            return highlight
+        else: #return specified colour only
+            if fgBg == 'fg':
+                return highlight["foreground"]
+            if fgBg == 'bg':
+                return highlight["background"]
+            else:
+                raise InvalidFgBg, 'Invalid fgBg specified'
+
+    def GetThemeDict(self,type,themeName):
+        """
+        type - string, 'default' or 'user' theme type
+        themeName - string, theme name
+        Returns a dictionary which holds {option:value} for each element
+        in the specified theme. Values are loaded over a set of ultimate last
+        fallback defaults to guarantee that all theme elements are present in
+        a newly created theme.
+        """
+        if type == 'user':
+            cfgParser=self.userCfg['highlight']
+        elif type == 'default':
+            cfgParser=self.defaultCfg['highlight']
+        else:
+            raise InvalidTheme, 'Invalid theme type specified'
+        #foreground and background values are provded for each theme element
+        #(apart from cursor) even though all these values are not yet used
+        #by idle, to allow for their use in the future. Default values are
+        #generally black and white.
+        theme={ 'normal-foreground':'#000000',
+                'normal-background':'#ffffff',
+                'keyword-foreground':'#000000',
+                'keyword-background':'#ffffff',
+                'comment-foreground':'#000000',
+                'comment-background':'#ffffff',
+                'string-foreground':'#000000',
+                'string-background':'#ffffff',
+                'definition-foreground':'#000000',
+                'definition-background':'#ffffff',
+                'hilite-foreground':'#000000',
+                'hilite-background':'gray',
+                'break-foreground':'#ffffff',
+                'break-background':'#000000',
+                'hit-foreground':'#ffffff',
+                'hit-background':'#000000',
+                'error-foreground':'#ffffff',
+                'error-background':'#000000',
+                #cursor (only foreground can be set)
+                'cursor-foreground':'#000000',
+                #shell window
+                'stdout-foreground':'#000000',
+                'stdout-background':'#ffffff',
+                'stderr-foreground':'#000000',
+                'stderr-background':'#ffffff',
+                'console-foreground':'#000000',
+                'console-background':'#ffffff' }
+        for element in theme.keys():
+            if not cfgParser.has_option(themeName,element):
+                #we are going to return a default, print warning
+                warning=('\n Warning: configHandler.py - IdleConf.GetThemeDict'+
+                           ' -\n problem retrieving theme element '+`element`+
+                           '\n from theme '+`themeName`+'.\n'+
+                           ' returning default value: '+`theme[element]`+'\n')
+                sys.stderr.write(warning)
+            colour=cfgParser.Get(themeName,element,default=theme[element])
+            theme[element]=colour
+        return theme
+
+    def CurrentTheme(self):
+        """
+        Returns the name of the currently active theme
+        """
+        return self.GetOption('main','Theme','name',default='')
+
+    def CurrentKeys(self):
+        """
+        Returns the name of the currently active key set
+        """
+        return self.GetOption('main','Keys','name',default='')
+
+    def GetExtensions(self, activeOnly=1):
+        """
+        Gets a list of all idle extensions declared in the config files.
+        activeOnly - boolean, if true only return active (enabled) extensions
+        """
+        extns=self.RemoveKeyBindNames(
+                self.GetSectionList('default','extensions'))
+        userExtns=self.RemoveKeyBindNames(
+                self.GetSectionList('user','extensions'))
+        for extn in userExtns:
+            if extn not in extns: #user has added own extension
+                extns.append(extn)
+        if activeOnly:
+            activeExtns=[]
+            for extn in extns:
+                if self.GetOption('extensions',extn,'enable',default=1,
+                    type='bool'):
+                    #the extension is enabled
+                    activeExtns.append(extn)
+            return activeExtns
+        else:
+            return extns
+
+    def RemoveKeyBindNames(self,extnNameList):
+        #get rid of keybinding section names
+        names=extnNameList
+        kbNameIndicies=[]
+        for name in names:
+            if name.endswith('_bindings') or name.endswith('_cfgBindings'):
+                kbNameIndicies.append(names.index(name))
+        kbNameIndicies.sort()
+        kbNameIndicies.reverse()
+        for index in kbNameIndicies: #delete each keybinding section name
+            del(names[index])
+        return names
+
+    def GetExtnNameForEvent(self,virtualEvent):
+        """
+        Returns the name of the extension that virtualEvent is bound in, or
+        None if not bound in any extension.
+        virtualEvent - string, name of the virtual event to test for, without
+                       the enclosing '<< >>'
+        """
+        extName=None
+        vEvent='<<'+virtualEvent+'>>'
+        for extn in self.GetExtensions(activeOnly=0):
+            for event in self.GetExtensionKeys(extn).keys():
+                if event == vEvent:
+                    extName=extn
+        return extName
+
+    def GetExtensionKeys(self,extensionName):
+        """
+        returns a dictionary of the configurable keybindings for a particular
+        extension,as they exist in the dictionary returned by GetCurrentKeySet;
+        that is, where previously used bindings are disabled.
+        """
+        keysName=extensionName+'_cfgBindings'
+        activeKeys=self.GetCurrentKeySet()
+        extKeys={}
+        if self.defaultCfg['extensions'].has_section(keysName):
+            eventNames=self.defaultCfg['extensions'].GetOptionList(keysName)
+            for eventName in eventNames:
+                event='<<'+eventName+'>>'
+                binding=activeKeys[event]
+                extKeys[event]=binding
+        return extKeys
+
+    def __GetRawExtensionKeys(self,extensionName):
+        """
+        returns a dictionary of the configurable keybindings for a particular
+        extension, as defined in the configuration files, or an empty dictionary
+        if no bindings are found
+        """
+        keysName=extensionName+'_cfgBindings'
+        extKeys={}
+        if self.defaultCfg['extensions'].has_section(keysName):
+            eventNames=self.defaultCfg['extensions'].GetOptionList(keysName)
+            for eventName in eventNames:
+                binding=self.GetOption('extensions',keysName,
+                        eventName,default='').split()
+                event='<<'+eventName+'>>'
+                extKeys[event]=binding
+        return extKeys
+
+    def GetExtensionBindings(self,extensionName):
+        """
+        Returns a dictionary of all the event bindings for a particular
+        extension. The configurable keybindings are returned as they exist in
+        the dictionary returned by GetCurrentKeySet; that is, where re-used
+        keybindings are disabled.
+        """
+        bindsName=extensionName+'_bindings'
+        extBinds=self.GetExtensionKeys(extensionName)
+        #add the non-configurable bindings
+        if self.defaultCfg['extensions'].has_section(bindsName):
+            eventNames=self.defaultCfg['extensions'].GetOptionList(bindsName)
+            for eventName in eventNames:
+                binding=self.GetOption('extensions',bindsName,
+                        eventName,default='').split()
+                event='<<'+eventName+'>>'
+                extBinds[event]=binding
+
+        return extBinds
+
+    def GetKeyBinding(self, keySetName, eventStr):
+        """
+        returns the keybinding for a specific event.
+        keySetName - string, name of key binding set
+        eventStr - string, the virtual event we want the binding for,
+                   represented as a string, eg. '<<event>>'
+        """
+        eventName=eventStr[2:-2] #trim off the angle brackets
+        binding=self.GetOption('keys',keySetName,eventName,default='').split()
+        return binding
+
+    def GetCurrentKeySet(self):
+        return self.GetKeySet(self.CurrentKeys())
+
+    def GetKeySet(self,keySetName):
+        """
+        Returns a dictionary of: all requested core keybindings, plus the
+        keybindings for all currently active extensions. If a binding defined
+        in an extension is already in use, that binding is disabled.
+        """
+        keySet=self.GetCoreKeys(keySetName)
+        activeExtns=self.GetExtensions(activeOnly=1)
+        for extn in activeExtns:
+            extKeys=self.__GetRawExtensionKeys(extn)
+            if extKeys: #the extension defines keybindings
+                for event in extKeys.keys():
+                    if extKeys[event] in keySet.values():
+                        #the binding is already in use
+                        extKeys[event]='' #disable this binding
+                    keySet[event]=extKeys[event] #add binding
+        return keySet
+
+    def IsCoreBinding(self,virtualEvent):
+        """
+        returns true if the virtual event is bound in the core idle keybindings.
+        virtualEvent - string, name of the virtual event to test for, without
+                       the enclosing '<< >>'
+        """
+        return ('<<'+virtualEvent+'>>') in self.GetCoreKeys().keys()
+
+    def GetCoreKeys(self, keySetName=None):
+        """
+        returns the requested set of core keybindings, with fallbacks if
+        required.
+        Keybindings loaded from the config file(s) are loaded _over_ these
+        defaults, so if there is a problem getting any core binding there will
+        be an 'ultimate last resort fallback' to the CUA-ish bindings
+        defined here.
+        """
+        keyBindings={
+            '<<copy>>': ['<Control-c>', '<Control-C>'],
+            '<<cut>>': ['<Control-x>', '<Control-X>'],
+            '<<paste>>': ['<Control-v>', '<Control-V>'],
+            '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
+            '<<center-insert>>': ['<Control-l>'],
+            '<<close-all-windows>>': ['<Control-q>'],
+            '<<close-window>>': ['<Alt-F4>'],
+            '<<do-nothing>>': ['<Control-x>'],
+            '<<end-of-file>>': ['<Control-d>'],
+            '<<python-docs>>': ['<F1>'],
+            '<<python-context-help>>': ['<Shift-F1>'],
+            '<<history-next>>': ['<Alt-n>'],
+            '<<history-previous>>': ['<Alt-p>'],
+            '<<interrupt-execution>>': ['<Control-c>'],
+            '<<view-restart>>': ['<F6>'],
+            '<<restart-shell>>': ['<Control-F6>'],
+            '<<open-class-browser>>': ['<Alt-c>'],
+            '<<open-module>>': ['<Alt-m>'],
+            '<<open-new-window>>': ['<Control-n>'],
+            '<<open-window-from-file>>': ['<Control-o>'],
+            '<<plain-newline-and-indent>>': ['<Control-j>'],
+            '<<print-window>>': ['<Control-p>'],
+            '<<redo>>': ['<Control-y>'],
+            '<<remove-selection>>': ['<Escape>'],
+            '<<save-copy-of-window-as-file>>': ['<Alt-Shift-s>'],
+            '<<save-window-as-file>>': ['<Alt-s>'],
+            '<<save-window>>': ['<Control-s>'],
+            '<<select-all>>': ['<Alt-a>'],
+            '<<toggle-auto-coloring>>': ['<Control-slash>'],
+            '<<undo>>': ['<Control-z>'],
+            '<<find-again>>': ['<Control-g>', '<F3>'],
+            '<<find-in-files>>': ['<Alt-F3>'],
+            '<<find-selection>>': ['<Control-F3>'],
+            '<<find>>': ['<Control-f>'],
+            '<<replace>>': ['<Control-h>'],
+            '<<goto-line>>': ['<Alt-g>'],
+            '<<smart-backspace>>': ['<Key-BackSpace>'],
+            '<<newline-and-indent>>': ['<Key-Return> <Key-KP_Enter>'],
+            '<<smart-indent>>': ['<Key-Tab>'],
+            '<<indent-region>>': ['<Control-Key-bracketright>'],
+            '<<dedent-region>>': ['<Control-Key-bracketleft>'],
+            '<<comment-region>>': ['<Alt-Key-3>'],
+            '<<uncomment-region>>': ['<Alt-Key-4>'],
+            '<<tabify-region>>': ['<Alt-Key-5>'],
+            '<<untabify-region>>': ['<Alt-Key-6>'],
+            '<<toggle-tabs>>': ['<Alt-Key-t>'],
+            '<<change-indentwidth>>': ['<Alt-Key-u>']
+            }
+        if keySetName:
+            for event in keyBindings.keys():
+                binding=self.GetKeyBinding(keySetName,event)
+                if binding:
+                    keyBindings[event]=binding
+                else: #we are going to return a default, print warning
+                    warning=('\n Warning: configHandler.py - IdleConf.GetCoreKeys'+
+                               ' -\n problem retrieving key binding for event '+
+                               `event`+'\n from key set '+`keySetName`+'.\n'+
+                               ' returning default value: '+`keyBindings[event]`+'\n')
+                    sys.stderr.write(warning)
+        return keyBindings
+
+    def GetExtraHelpSourceList(self,configSet):
+        """Fetch list of extra help sources from a given configSet.
+
+        Valid configSets are 'user' or 'default'.  Return a list of tuples of
+        the form (menu_item , path_to_help_file , option), or return the empty
+        list.  'option' is the sequence number of the help resource.  'option'
+        values determine the position of the menu items on the Help menu,
+        therefore the returned list must be sorted by 'option'.
+
+        """
+        helpSources=[]
+        if configSet=='user':
+            cfgParser=self.userCfg['main']
+        elif configSet=='default':
+            cfgParser=self.defaultCfg['main']
+        else:
+            raise InvalidConfigSet, 'Invalid configSet specified'
+        options=cfgParser.GetOptionList('HelpFiles')
+        for option in options:
+            value=cfgParser.Get('HelpFiles',option,default=';')
+            if value.find(';')==-1: #malformed config entry with no ';'
+                menuItem='' #make these empty
+                helpPath='' #so value won't be added to list
+            else: #config entry contains ';' as expected
+                value=string.split(value,';')
+                menuItem=value[0].strip()
+                helpPath=value[1].strip()
+            if menuItem and helpPath: #neither are empty strings
+                helpSources.append( (menuItem,helpPath,option) )
+        helpSources.sort(self.__helpsort)
+        return helpSources
+
+    def __helpsort(self, h1, h2):
+        if int(h1[2]) < int(h2[2]):
+            return -1
+        elif int(h1[2]) > int(h2[2]):
+            return 1
+        else:
+            return 0
+
+    def GetAllExtraHelpSourcesList(self):
+        """
+        Returns a list of tuples containing the details of all additional help
+        sources configured, or an empty list if there are none. Tuples are of
+        the format returned by GetExtraHelpSourceList.
+        """
+        allHelpSources=( self.GetExtraHelpSourceList('default')+
+                self.GetExtraHelpSourceList('user') )
+        return allHelpSources
+
+    def LoadCfgFiles(self):
+        """
+        load all configuration files.
+        """
+        for key in self.defaultCfg.keys():
+            self.defaultCfg[key].Load()
+            self.userCfg[key].Load() #same keys
+
+    def SaveUserCfgFiles(self):
+        """
+        write all loaded user configuration files back to disk
+        """
+        for key in self.userCfg.keys():
+            self.userCfg[key].Save()
+
+idleConf=IdleConf()
+
+### module test
+if __name__ == '__main__':
+    def dumpCfg(cfg):
+        print '\n',cfg,'\n'
+        for key in cfg.keys():
+            sections=cfg[key].sections()
+            print key
+            print sections
+            for section in sections:
+                options=cfg[key].options(section)
+                print section
+                print options
+                for option in options:
+                    print option, '=', cfg[key].Get(section,option)
+    dumpCfg(idleConf.defaultCfg)
+    dumpCfg(idleConf.userCfg)
+    print idleConf.userCfg['main'].Get('Theme','name')
+    #print idleConf.userCfg['highlight'].GetDefHighlight('Foo','normal')
diff --git a/Tools/idle/configHelpSourceEdit.py b/Tools/idle/configHelpSourceEdit.py
new file mode 100644
index 0000000..b781884
--- /dev/null
+++ b/Tools/idle/configHelpSourceEdit.py
@@ -0,0 +1,157 @@
+"Dialog to specify or edit the parameters for a user configured help source."
+
+import os
+
+from Tkinter import *
+import tkMessageBox
+import tkFileDialog
+
+class GetHelpSourceDialog(Toplevel):
+    def __init__(self, parent, title, menuItem='', filePath=''):
+        """Get menu entry and url/ local file location for Additional Help
+
+        User selects a name for the Help resource and provides a web url
+        or a local file as its source.  The user can enter a url or browse
+        for the file.
+
+        """
+        Toplevel.__init__(self, parent)
+        self.configure(borderwidth=5)
+        self.resizable(height=FALSE, width=FALSE)
+        self.title(title)
+        self.transient(parent)
+        self.grab_set()
+        self.protocol("WM_DELETE_WINDOW", self.Cancel)
+        self.parent = parent
+        self.result = None
+        self.CreateWidgets()
+        self.menu.set(menuItem)
+        self.path.set(filePath)
+        self.withdraw() #hide while setting geometry
+        #needs to be done here so that the winfo_reqwidth is valid
+        self.update_idletasks()
+        #centre dialog over parent:
+        self.geometry("+%d+%d" %
+                      ((parent.winfo_rootx() + ((parent.winfo_width()/2)
+                                                -(self.winfo_reqwidth()/2)),
+                        parent.winfo_rooty() + ((parent.winfo_height()/2)
+                                                -(self.winfo_reqheight()/2)))))
+        self.deiconify() #geometry set, unhide
+        self.bind('<Return>', self.Ok)
+        self.wait_window()
+
+    def CreateWidgets(self):
+        self.menu = StringVar(self)
+        self.path = StringVar(self)
+        self.fontSize = StringVar(self)
+        self.frameMain = Frame(self, borderwidth=2, relief=GROOVE)
+        self.frameMain.pack(side=TOP, expand=TRUE, fill=BOTH)
+        labelMenu = Label(self.frameMain, anchor=W, justify=LEFT,
+                          text='Menu Item:')
+        self.entryMenu = Entry(self.frameMain, textvariable=self.menu,
+                               width=30)
+        self.entryMenu.focus_set()
+        labelPath = Label(self.frameMain, anchor=W, justify=LEFT,
+                          text='Help File Path: Enter URL or browse for file')
+        self.entryPath = Entry(self.frameMain, textvariable=self.path,
+                               width=40)
+        self.entryMenu.focus_set()
+        labelMenu.pack(anchor=W, padx=5, pady=3)
+        self.entryMenu.pack(anchor=W, padx=5, pady=3)
+        labelPath.pack(anchor=W, padx=5, pady=3)
+        self.entryPath.pack(anchor=W, padx=5, pady=3)
+        browseButton = Button(self.frameMain, text='Browse', width=8,
+                              command=self.browseFile)
+        browseButton.pack(pady=3)
+        frameButtons = Frame(self)
+        frameButtons.pack(side=BOTTOM, fill=X)
+        self.buttonOk = Button(frameButtons, text='OK',
+                               width=8, default=ACTIVE,  command=self.Ok)
+        self.buttonOk.grid(row=0, column=0, padx=5,pady=5)
+        self.buttonCancel = Button(frameButtons, text='Cancel',
+                                   width=8, command=self.Cancel)
+        self.buttonCancel.grid(row=0, column=1, padx=5, pady=5)
+
+    def browseFile(self):
+        filetypes = [
+            ("HTML Files", "*.htm *.html", "TEXT"),
+            ("PDF Files", "*.pdf", "TEXT"),
+            ("Windows Help Files", "*.chm"),
+            ("Text Files", "*.txt", "TEXT"),
+            ("All Files", "*")]
+        path = self.path.get()
+        if path:
+            dir, base = os.path.split(path)
+        else:
+            base = None
+            if sys.platform.count('win') or sys.platform.count('nt'):
+                dir = os.path.join(os.path.dirname(sys.executable), 'Doc')
+                if not os.path.isdir(dir):
+                    dir = os.getcwd()
+            else:
+                dir = os.getcwd()
+        opendialog = tkFileDialog.Open(parent=self, filetypes=filetypes)
+        file = opendialog.show(initialdir=dir, initialfile=base)
+        if file:
+            self.path.set(file)
+
+    def MenuOk(self):
+        "Simple validity check for a sensible menu item name"
+        menuOk = True
+        menu = self.menu.get()
+        menu.strip()
+        if not menu:
+            tkMessageBox.showerror(title='Menu Item Error',
+                                   message='No menu item specified',
+                                   parent=self)
+            self.entryMenu.focus_set()
+            menuOk = False
+        elif len(menu) > 30:
+            tkMessageBox.showerror(title='Menu Item Error',
+                                   message='Menu item too long:'
+                                           '\nLimit 30 characters.',
+                                   parent=self)
+            self.entryMenu.focus_set()
+            menuOk = False
+        return menuOk
+
+    def PathOk(self):
+        "Simple validity check for menu file path"
+        pathOk = True
+        path = self.path.get()
+        path.strip()
+        if not path: #no path specified
+            tkMessageBox.showerror(title='File Path Error',
+                                   message='No help file path specified.',
+                                   parent=self)
+            self.entryPath.focus_set()
+            pathOk = False
+        elif path.startswith('www.') or path.startswith('http'):
+            pathOk = True
+        elif not os.path.exists(path):
+            tkMessageBox.showerror(title='File Path Error',
+                                   message='Help file path does not exist.',
+                                   parent=self)
+            self.entryPath.focus_set()
+            pathOk = False
+        return pathOk
+
+    def Ok(self, event=None):
+        if self.MenuOk() and self.PathOk():
+            self.result = (self.menu.get().strip(),
+                           self.path.get().strip())
+            self.destroy()
+
+    def Cancel(self, event=None):
+        self.result = None
+        self.destroy()
+
+if __name__ == '__main__':
+    #test the dialog
+    root = Tk()
+    def run():
+        keySeq = ''
+        dlg = GetHelpSourceDialog(root, 'Get Help Source')
+        print dlg.result
+    Button(root,text='Dialog', command=run).pack()
+    root.mainloop()
diff --git a/Tools/idle/configSectionNameDialog.py b/Tools/idle/configSectionNameDialog.py
new file mode 100644
index 0000000..4f1b002
--- /dev/null
+++ b/Tools/idle/configSectionNameDialog.py
@@ -0,0 +1,97 @@
+"""
+Dialog that allows user to specify a new config file section name.
+Used to get new highlight theme and keybinding set names.
+"""
+from Tkinter import *
+import tkMessageBox
+
+class GetCfgSectionNameDialog(Toplevel):
+    def __init__(self,parent,title,message,usedNames):
+        """
+        message - string, informational message to display
+        usedNames - list, list of names already in use for validity check
+        """
+        Toplevel.__init__(self, parent)
+        self.configure(borderwidth=5)
+        self.resizable(height=FALSE,width=FALSE)
+        self.title(title)
+        self.transient(parent)
+        self.grab_set()
+        self.protocol("WM_DELETE_WINDOW", self.Cancel)
+        self.parent = parent
+        self.message=message
+        self.usedNames=usedNames
+        self.result=''
+        self.CreateWidgets()
+        self.withdraw() #hide while setting geometry
+        self.update_idletasks()
+        #needs to be done here so that the winfo_reqwidth is valid
+        self.messageInfo.config(width=self.frameMain.winfo_reqwidth())
+        self.geometry("+%d+%d" %
+            ((parent.winfo_rootx()+((parent.winfo_width()/2)
+                -(self.winfo_reqwidth()/2)),
+              parent.winfo_rooty()+((parent.winfo_height()/2)
+                -(self.winfo_reqheight()/2)) )) ) #centre dialog over parent
+        self.deiconify() #geometry set, unhide
+        self.wait_window()
+
+    def CreateWidgets(self):
+        self.name=StringVar(self)
+        self.fontSize=StringVar(self)
+        self.frameMain = Frame(self,borderwidth=2,relief=SUNKEN)
+        self.frameMain.pack(side=TOP,expand=TRUE,fill=BOTH)
+        self.messageInfo=Message(self.frameMain,anchor=W,justify=LEFT,padx=5,pady=5,
+                text=self.message)#,aspect=200)
+        entryName=Entry(self.frameMain,textvariable=self.name,width=30)
+        entryName.focus_set()
+        self.messageInfo.pack(padx=5,pady=5)#,expand=TRUE,fill=BOTH)
+        entryName.pack(padx=5,pady=5)
+        frameButtons=Frame(self)
+        frameButtons.pack(side=BOTTOM,fill=X)
+        self.buttonOk = Button(frameButtons,text='Ok',
+                width=8,command=self.Ok)
+        self.buttonOk.grid(row=0,column=0,padx=5,pady=5)
+        self.buttonCancel = Button(frameButtons,text='Cancel',
+                width=8,command=self.Cancel)
+        self.buttonCancel.grid(row=0,column=1,padx=5,pady=5)
+
+    def NameOk(self):
+        #simple validity check for a sensible
+        #ConfigParser file section name
+        nameOk=1
+        name=self.name.get()
+        name.strip()
+        if not name: #no name specified
+            tkMessageBox.showerror(title='Name Error',
+                    message='No name specified.', parent=self)
+            nameOk=0
+        elif len(name)>30: #name too long
+            tkMessageBox.showerror(title='Name Error',
+                    message='Name too long. It should be no more than '+
+                    '30 characters.', parent=self)
+            nameOk=0
+        elif name in self.usedNames:
+            tkMessageBox.showerror(title='Name Error',
+                    message='This name is already in use.', parent=self)
+            nameOk=0
+        return nameOk
+
+    def Ok(self, event=None):
+        if self.NameOk():
+            self.result=self.name.get().strip()
+            self.destroy()
+
+    def Cancel(self, event=None):
+        self.result=''
+        self.destroy()
+
+if __name__ == '__main__':
+    #test the dialog
+    root=Tk()
+    def run():
+        keySeq=''
+        dlg=GetCfgSectionNameDialog(root,'Get Name',
+                'The information here should need to be word wrapped. Test.')
+        print dlg.result
+    Button(root,text='Dialog',command=run).pack()
+    root.mainloop()
diff --git a/Tools/idle/dynOptionMenuWidget.py b/Tools/idle/dynOptionMenuWidget.py
new file mode 100644
index 0000000..e81f7ba
--- /dev/null
+++ b/Tools/idle/dynOptionMenuWidget.py
@@ -0,0 +1,35 @@
+"""
+OptionMenu widget modified to allow dynamic menu reconfiguration
+and setting of highlightthickness
+"""
+from Tkinter import OptionMenu
+from Tkinter import _setit
+import copy
+
+class DynOptionMenu(OptionMenu):
+    """
+    unlike OptionMenu, our kwargs can include highlightthickness
+    """
+    def __init__(self, master, variable, value, *values, **kwargs):
+        #get a copy of kwargs before OptionMenu.__init__ munges them
+        kwargsCopy=copy.copy(kwargs)
+        if 'highlightthickness' in kwargs.keys():
+            del(kwargs['highlightthickness'])
+        OptionMenu.__init__(self, master, variable, value, *values, **kwargs)
+        self.config(highlightthickness=kwargsCopy.get('highlightthickness'))
+        #self.menu=self['menu']
+        self.variable=variable
+        self.command=kwargs.get('command')
+
+    def SetMenu(self,valueList,value=None):
+        """
+        clear and reload the menu with a new set of options.
+        valueList - list of new options
+        value - initial value to set the optionmenu's menubutton to
+        """
+        self['menu'].delete(0,'end')
+        for item in valueList:
+            self['menu'].add_command(label=item,
+                    command=_setit(self.variable,item,self.command))
+        if value:
+            self.variable.set(value)
diff --git a/Tools/idle/eventparse.py b/Tools/idle/eventparse.py
deleted file mode 100644
index f253b2a..0000000
--- a/Tools/idle/eventparse.py
+++ /dev/null
@@ -1,89 +0,0 @@
-#! /usr/bin/env python
-
-"""Parse event definitions out of comments in source files."""
-
-import sys
-import glob
-import fileinput
-import pprint
-
-def main():
-    hits = []
-    sublist = []
-    args = sys.argv[1:]
-    if not args:
-        args = filter(lambda s: 'A' <= s[0] <= 'Z', glob.glob("*.py"))
-        if not args:
-            print "No arguments, no [A-Z]*.py files."
-            return 1
-    for line in fileinput.input(args):
-        if line[:2] == '#$':
-            if not sublist:
-                sublist.append('file %s' % fileinput.filename())
-                sublist.append('line %d' % fileinput.lineno())
-            sublist.append(line[2:-1].strip())
-        else:
-            if sublist:
-                hits.append(sublist)
-                sublist = []
-    if sublist:
-        hits.append(sublist)
-        sublist = []
-    dd = {}
-    for sublist in hits:
-        d = {}
-        for line in sublist:
-            words = line.split(None, 1)
-            if len(words) != 2:
-                continue
-            tag = words[0]
-            l = d.get(tag, [])
-            l.append(words[1])
-            d[tag] = l
-        if d.has_key('event'):
-            keys = d['event']
-            if len(keys) != 1:
-                print "Multiple event keys in", d
-                print 'File "%s", line %d' % (d['file'], d['line'])
-            key = keys[0]
-            if dd.has_key(key):
-                print "Duplicate event in", d
-                print 'File "%s", line %d' % (d['file'], d['line'])
-                return
-            dd[key] = d
-        else:
-            print "No event key in", d
-            print 'File "%s", line %d' % (d['file'], d['line'])
-    winevents = getevents(dd, "win")
-    unixevents = getevents(dd, "unix")
-    save = sys.stdout
-    f = open("keydefs.py", "w")
-    try:
-        sys.stdout = f
-        print "windows_keydefs = \\"
-        pprint.pprint(winevents)
-        print
-        print "unix_keydefs = \\"
-        pprint.pprint(unixevents)
-    finally:
-        sys.stdout = save
-    f.close()
-
-def getevents(dd, key):
-    res = {}
-    events = dd.keys()
-    events.sort()
-    for e in events:
-        d = dd[e]
-        if d.has_key(key) or d.has_key("all"):
-            list = []
-            for x in d.get(key, []) + d.get("all", []):
-                list.append(x)
-                if key == "unix" and x[:5] == "<Alt-":
-                    x = "<Meta-" + x[5:]
-                    list.append(x)
-            res[e] = list
-    return res
-
-if __name__ == '__main__':
-    sys.exit(main())
diff --git a/Tools/idle/interruptmodule.c b/Tools/idle/interruptmodule.c
new file mode 100644
index 0000000..8e18d5a
--- /dev/null
+++ b/Tools/idle/interruptmodule.c
@@ -0,0 +1,49 @@
+/***********************************************************************
+ *  interruptmodule.c
+ *
+ *  Python extension implementing the interrupt module.
+ *  
+ **********************************************************************/
+
+#include "Python.h"
+
+#ifndef PyDoc_STR
+#define PyDoc_VAR(name) static char name[]
+#define PyDoc_STR(str) str
+#define PyDoc_STRVAR(name,str) PyDoc_VAR(name) = PyDoc_STR(str)
+#endif
+
+/* module documentation */
+
+PyDoc_STRVAR(module_doc,
+"Provide a way to interrupt the main thread from a subthread.\n\n\
+In threaded Python code the KeyboardInterrupt is always directed to\n\
+the thread which raised it.  This extension provides a method,\n\
+interrupt_main, which a subthread can use to raise a KeyboardInterrupt\n\
+in the main thread.");
+
+/* module functions */
+
+static PyObject *
+setinterrupt(PyObject * self, PyObject * args)
+{
+	PyErr_SetInterrupt();
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+/* registration table */
+
+static struct PyMethodDef methods[] = {
+	{"interrupt_main", setinterrupt, METH_VARARGS,
+	 PyDoc_STR("Interrupt the main thread")},
+	{NULL, NULL}
+};
+
+/* module initialization */
+
+void
+initinterrupt(void)
+{
+	(void) Py_InitModule3("interrupt", methods, module_doc);
+}
diff --git a/Tools/idle/keybindingDialog.py b/Tools/idle/keybindingDialog.py
new file mode 100644
index 0000000..df024e7
--- /dev/null
+++ b/Tools/idle/keybindingDialog.py
@@ -0,0 +1,262 @@
+"""
+dialog for building tkinter accelerator key bindings
+"""
+from Tkinter import *
+import tkMessageBox
+import string, os
+
+class GetKeysDialog(Toplevel):
+    def __init__(self,parent,title,action,currentKeySequences):
+        """
+        action - string, the name of the virtual event these keys will be
+                 mapped to
+        currentKeys - list, a list of all key sequence lists currently mapped
+                 to virtual events, for overlap checking
+        """
+        Toplevel.__init__(self, parent)
+        self.configure(borderwidth=5)
+        self.resizable(height=FALSE,width=FALSE)
+        self.title(title)
+        self.transient(parent)
+        self.grab_set()
+        self.protocol("WM_DELETE_WINDOW", self.Cancel)
+        self.parent = parent
+        self.action=action
+        self.currentKeySequences=currentKeySequences
+        self.result=''
+        self.keyString=StringVar(self)
+        self.keyString.set('')
+        self.SetModifiersForPlatform()
+        self.modifier_vars = []
+        for modifier in self.modifiers:
+            variable = StringVar(self)
+            variable.set('')
+            self.modifier_vars.append(variable)
+        self.CreateWidgets()
+        self.LoadFinalKeyList()
+        self.withdraw() #hide while setting geometry
+        self.update_idletasks()
+        self.geometry("+%d+%d" %
+            ((parent.winfo_rootx()+((parent.winfo_width()/2)
+                -(self.winfo_reqwidth()/2)),
+              parent.winfo_rooty()+((parent.winfo_height()/2)
+                -(self.winfo_reqheight()/2)) )) ) #centre dialog over parent
+        self.deiconify() #geometry set, unhide
+        self.wait_window()
+
+    def CreateWidgets(self):
+        frameMain = Frame(self,borderwidth=2,relief=SUNKEN)
+        frameMain.pack(side=TOP,expand=TRUE,fill=BOTH)
+        frameButtons=Frame(self)
+        frameButtons.pack(side=BOTTOM,fill=X)
+        self.buttonOk = Button(frameButtons,text='Ok',
+                width=8,command=self.Ok)
+        self.buttonOk.grid(row=0,column=0,padx=5,pady=5)
+        self.buttonCancel = Button(frameButtons,text='Cancel',
+                width=8,command=self.Cancel)
+        self.buttonCancel.grid(row=0,column=1,padx=5,pady=5)
+        self.frameKeySeqBasic = Frame(frameMain)
+        self.frameKeySeqAdvanced = Frame(frameMain)
+        self.frameControlsBasic = Frame(frameMain)
+        self.frameHelpAdvanced = Frame(frameMain)
+        self.frameKeySeqAdvanced.grid(row=0,column=0,sticky=NSEW,padx=5,pady=5)
+        self.frameKeySeqBasic.grid(row=0,column=0,sticky=NSEW,padx=5,pady=5)
+        self.frameKeySeqBasic.lift()
+        self.frameHelpAdvanced.grid(row=1,column=0,sticky=NSEW,padx=5)
+        self.frameControlsBasic.grid(row=1,column=0,sticky=NSEW,padx=5)
+        self.frameControlsBasic.lift()
+        self.buttonLevel = Button(frameMain,command=self.ToggleLevel,
+                text='Advanced Key Binding Entry >>')
+        self.buttonLevel.grid(row=2,column=0,stick=EW,padx=5,pady=5)
+        labelTitleBasic = Label(self.frameKeySeqBasic,
+                text="New keys for  '"+self.action+"' :")
+        labelTitleBasic.pack(anchor=W)
+        labelKeysBasic = Label(self.frameKeySeqBasic,justify=LEFT,
+                textvariable=self.keyString,relief=GROOVE,borderwidth=2)
+        labelKeysBasic.pack(ipadx=5,ipady=5,fill=X)
+        self.modifier_checkbuttons = {}
+        column = 0
+        for modifier, variable in zip(self.modifiers, self.modifier_vars):
+            label = self.modifier_label.get(modifier, modifier)
+            check=Checkbutton(self.frameControlsBasic,
+                command=self.BuildKeyString,
+                text=label,variable=variable,onvalue=modifier,offvalue='')
+            check.grid(row=0,column=column,padx=2,sticky=W)
+            self.modifier_checkbuttons[modifier] = check
+            column += 1
+        labelFnAdvice=Label(self.frameControlsBasic,justify=LEFT,
+                text="Select the desired modifier\n"+
+                     "keys above, and final key\n"+
+                     "from the list on the right.")
+        labelFnAdvice.grid(row=1,column=0,columnspan=4,padx=2,sticky=W)
+        self.listKeysFinal=Listbox(self.frameControlsBasic,width=15,height=10,
+                selectmode=SINGLE)
+        self.listKeysFinal.bind('<ButtonRelease-1>',self.FinalKeySelected)
+        self.listKeysFinal.grid(row=0,column=4,rowspan=4,sticky=NS)
+        scrollKeysFinal=Scrollbar(self.frameControlsBasic,orient=VERTICAL,
+                command=self.listKeysFinal.yview)
+        self.listKeysFinal.config(yscrollcommand=scrollKeysFinal.set)
+        scrollKeysFinal.grid(row=0,column=5,rowspan=4,sticky=NS)
+        self.buttonClear=Button(self.frameControlsBasic,
+                text='Clear Keys',command=self.ClearKeySeq)
+        self.buttonClear.grid(row=2,column=0,columnspan=4)
+        labelTitleAdvanced = Label(self.frameKeySeqAdvanced,justify=LEFT,
+                text="Enter new binding(s) for  '"+self.action+"' :\n"+
+                "(will not be checked for validity)")
+        labelTitleAdvanced.pack(anchor=W)
+        self.entryKeysAdvanced=Entry(self.frameKeySeqAdvanced,
+                textvariable=self.keyString)
+        self.entryKeysAdvanced.pack(fill=X)
+        labelHelpAdvanced=Label(self.frameHelpAdvanced,justify=LEFT,
+            text="Key bindings are specified using tkinter key id's as\n"+
+                 "in these samples: <Control-f>, <Shift-F2>, <F12>,\n"
+                 "<Control-space>, <Meta-less>, <Control-Alt-Shift-x>.\n\n"+
+                 "'Emacs style' multi-keystroke bindings are specified as\n"+
+                 "follows: <Control-x><Control-y> or <Meta-f><Meta-g>.\n\n"+
+                 "Multiple separate bindings for one action should be\n"+
+                 "separated by a space, eg., <Alt-v> <Meta-v>." )
+        labelHelpAdvanced.grid(row=0,column=0,sticky=NSEW)
+
+    def SetModifiersForPlatform(self):
+        """Determine list of names of key modifiers for this platform.
+
+        The names are used to build Tk bindings -- it doesn't matter if the
+        keyboard has these keys, it matters if Tk understands them. The
+        order is also important: key binding equality depends on it, so
+        config-keys.def must use the same ordering.
+        """
+        import sys
+        if sys.platform == 'darwin' and sys.executable.count('.app'):
+            self.modifiers = ['Shift', 'Control', 'Option', 'Command']
+        else:
+            self.modifiers = ['Control', 'Alt', 'Shift']
+        self.modifier_label = {'Control': 'Ctrl'}
+
+    def ToggleLevel(self):
+        if  self.buttonLevel.cget('text')[:8]=='Advanced':
+            self.ClearKeySeq()
+            self.buttonLevel.config(text='<< Basic Key Binding Entry')
+            self.frameKeySeqAdvanced.lift()
+            self.frameHelpAdvanced.lift()
+            self.entryKeysAdvanced.focus_set()
+        else:
+            self.ClearKeySeq()
+            self.buttonLevel.config(text='Advanced Key Binding Entry >>')
+            self.frameKeySeqBasic.lift()
+            self.frameControlsBasic.lift()
+
+    def FinalKeySelected(self,event):
+        self.BuildKeyString()
+
+    def BuildKeyString(self):
+        keyList=[]
+        modifiers=self.GetModifiers()
+        finalKey=self.listKeysFinal.get(ANCHOR)
+        if modifiers: modifiers[0]='<'+modifiers[0]
+        keyList=keyList+modifiers
+        if finalKey:
+            if (not modifiers) and (finalKey not
+                    in self.alphanumKeys+self.punctuationKeys):
+                finalKey='<'+self.TranslateKey(finalKey)
+            else:
+                finalKey=self.TranslateKey(finalKey)
+            keyList.append(finalKey+'>')
+        keyStr=string.join(keyList,'-')
+        self.keyString.set(keyStr)
+
+    def GetModifiers(self):
+        modList = [variable.get() for variable in self.modifier_vars]
+        return filter(None, modList)
+
+    def ClearKeySeq(self):
+        self.listKeysFinal.select_clear(0,END)
+        self.listKeysFinal.yview(MOVETO, '0.0')
+        for variable in self.modifier_vars:
+            variable.set('')
+        self.keyString.set('')
+
+    def LoadFinalKeyList(self):
+        #these tuples are also available for use in validity checks
+        self.functionKeys=('F1','F2','F2','F4','F5','F6','F7','F8','F9',
+                'F10','F11','F12')
+        self.alphanumKeys=tuple(string.ascii_lowercase+string.digits)
+        self.punctuationKeys=tuple('~!@#%^&*()_-+={}[]|;:,.<>/?')
+        self.whitespaceKeys=('Tab','Space','Return')
+        self.editKeys=('BackSpace','Delete','Insert')
+        self.moveKeys=('Home','End','Page Up','Page Down','Left Arrow',
+                'Right Arrow','Up Arrow','Down Arrow')
+        #make a tuple of most of the useful common 'final' keys
+        keys=(self.alphanumKeys+self.punctuationKeys+self.functionKeys+
+                self.whitespaceKeys+self.editKeys+self.moveKeys)
+        apply(self.listKeysFinal.insert,
+            (END,)+keys)
+
+    def TranslateKey(self,key):
+        #translate from key list value to tkinter key-id
+        translateDict={'~':'asciitilde','!':'exclam','@':'at','#':'numbersign',
+                '%':'percent','^':'asciicircum','&':'ampersand','*':'asterisk',
+                '(':'parenleft',')':'parenright','_':'underscore','-':'minus',
+                '+':'plus','=':'equal','{':'braceleft','}':'braceright',
+                '[':'bracketleft',']':'bracketright','|':'bar',';':'semicolon',
+                ':':'colon',',':'comma','.':'period','<':'less','>':'greater',
+                '/':'slash','?':'question','Page Up':'Prior','Page Down':'Next',
+                'Left Arrow':'Left','Right Arrow':'Right','Up Arrow':'Up',
+                'Down Arrow': 'Down'}
+        if key in translateDict.keys():
+            key=translateDict[key]
+        key='Key-'+key
+        return key
+
+    def Ok(self, event=None):
+        if self.KeysOk():
+            self.result=self.keyString.get()
+            self.destroy()
+
+    def Cancel(self, event=None):
+        self.result=''
+        self.destroy()
+
+    def KeysOk(self):
+        #simple validity check
+        keysOk=1
+        keys=self.keyString.get()
+        keys.strip()
+        finalKey=self.listKeysFinal.get(ANCHOR)
+        modifiers=self.GetModifiers()
+        keySequence=keys.split()#make into a key sequence list for overlap check
+        if not keys: #no keys specified
+            tkMessageBox.showerror(title='Key Sequence Error',
+                    message='No keys specified.')
+            keysOk=0
+        elif not keys.endswith('>'): #no final key specified
+            tkMessageBox.showerror(title='Key Sequence Error',
+                    message='No final key specified.')
+            keysOk=0
+        elif (not modifiers) and (finalKey in
+                self.alphanumKeys+self.punctuationKeys):
+            #modifier required
+            tkMessageBox.showerror(title='Key Sequence Error',
+                    message='No modifier key(s) specified.')
+            keysOk=0
+        elif (modifiers==['Shift']) and (finalKey not
+                in self.functionKeys+('Tab',)):
+            #shift alone is only a useful modifier with a function key
+            tkMessageBox.showerror(title='Key Sequence Error',
+                    message='Shift alone is not a useful modifier '+
+                            'when used with this final key key.')
+            keysOk=0
+        elif keySequence in self.currentKeySequences: #keys combo already in use
+            tkMessageBox.showerror(title='Key Sequence Error',
+                    message='This key combination is already in use.')
+            keysOk=0
+        return keysOk
+
+if __name__ == '__main__':
+    #test the dialog
+    root=Tk()
+    def run():
+        keySeq=''
+        dlg=GetKeysDialog(root,'Get Keys','find-again',[])
+        print dlg.result
+    Button(root,text='Dialog',command=run).pack()
+    root.mainloop()
diff --git a/Tools/idle/keydefs.py b/Tools/idle/keydefs.py
deleted file mode 100644
index 9761258..0000000
--- a/Tools/idle/keydefs.py
+++ /dev/null
@@ -1,57 +0,0 @@
-windows_keydefs = \
-{'<<Copy>>': ['<Control-c>', '<Control-C>'],
- '<<Cut>>': ['<Control-x>', '<Control-X>'],
- '<<Paste>>': ['<Control-v>', '<Control-V>'],
- '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
- '<<center-insert>>': ['<Control-l>'],
- '<<close-all-windows>>': ['<Control-q>'],
- '<<close-window>>': ['<Alt-F4>'],
- '<<dump-undo-state>>': ['<Control-backslash>'],
- '<<end-of-file>>': ['<Control-d>'],
- '<<help>>': ['<F1>'],
- '<<history-next>>': ['<Alt-n>'],
- '<<history-previous>>': ['<Alt-p>'],
- '<<interrupt-execution>>': ['<Control-c>'],
- '<<open-class-browser>>': ['<Alt-c>'],
- '<<open-module>>': ['<Alt-m>'],
- '<<open-new-window>>': ['<Control-n>'],
- '<<open-window-from-file>>': ['<Control-o>'],
- '<<plain-newline-and-indent>>': ['<Control-j>'],
- '<<print-window>>': ['<Control-p>'],
- '<<redo>>': ['<Control-y>'],
- '<<remove-selection>>': ['<Escape>'],
- '<<save-copy-of-window-as-file>>': ['<Alt-Shift-s>'],
- '<<save-window-as-file>>': ['<Alt-s>'],
- '<<save-window>>': ['<Control-s>'],
- '<<select-all>>': ['<Control-a>'],
- '<<toggle-auto-coloring>>': ['<Control-slash>'],
- '<<undo>>': ['<Control-z>']}
-
-unix_keydefs = \
-{'<<Copy>>': ['<Alt-w>', '<Meta-w>'],
- '<<Cut>>': ['<Control-w>'],
- '<<Paste>>': ['<Control-y>'],
- '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
- '<<center-insert>>': ['<Control-l>'],
- '<<close-all-windows>>': ['<Control-x><Control-c>'],
- '<<close-window>>': ['<Control-x><Control-0>', '<Control-x><Key-0>'],
- '<<do-nothing>>': ['<Control-x>'],
- '<<dump-undo-state>>': ['<Control-backslash>'],
- '<<end-of-file>>': ['<Control-d>'],
- '<<help>>': ['<F1>'],
- '<<history-next>>': ['<Alt-n>', '<Meta-n>'],
- '<<history-previous>>': ['<Alt-p>', '<Meta-p>'],
- '<<interrupt-execution>>': ['<Control-c>'],
- '<<open-class-browser>>': ['<Control-x><Control-b>'],
- '<<open-module>>': ['<Control-x><Control-m>'],
- '<<open-new-window>>': ['<Control-x><Control-n>'],
- '<<open-window-from-file>>': ['<Control-x><Control-f>'],
- '<<plain-newline-and-indent>>': ['<Control-j>'],
- '<<print-window>>': ['<Control-x><Control-p>'],
- '<<redo>>': ['<Alt-z>', '<Meta-z>'],
- '<<save-copy-of-window-as-file>>': ['<Control-x><w>'],
- '<<save-window-as-file>>': ['<Control-x><Control-w>'],
- '<<save-window>>': ['<Control-x><Control-s>'],
- '<<select-all>>': ['<Alt-a>', '<Meta-a>'],
- '<<toggle-auto-coloring>>': ['<Control-slash>'],
- '<<undo>>': ['<Control-z>']}
diff --git a/Tools/idle/macosx_main.py b/Tools/idle/macosx_main.py
new file mode 100644
index 0000000..bc91a0b
--- /dev/null
+++ b/Tools/idle/macosx_main.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env pythonw
+# IDLE.app
+#
+# Installation:
+#   see the install_IDLE target in python/dist/src/Mac/OSX/Makefile
+#
+# Usage:
+#
+# 1. Double clicking IDLE icon will open IDLE.
+# 2. Dropping file on IDLE icon will open that file in IDLE.
+# 3. Launch from command line with files with this command-line:
+#
+#     /Applications/Python/IDLE.app/Contents/MacOS/python file1 file2 file3
+#
+#
+
+# Add IDLE.app/Contents/Resources/idlelib to path.
+# __file__ refers to this file when it is used as a module, sys.argv[0]
+# refers to this file when it is used as a script (pythonw macosx_main.py)
+import sys
+
+from os.path import split, join, isdir
+try:
+    __file__
+except NameError:
+    __file__ = sys.argv[0]
+idlelib = join(split(__file__)[0], 'idlelib')
+if isdir(idlelib):
+    sys.path.append(idlelib)
+
+# see if we are being asked to execute the subprocess code
+if '-p' in sys.argv:
+    # run expects only the port number in sys.argv
+    sys.argv.remove('-p')
+
+    # this module will become the namespace used by the interactive
+    # interpreter; remove all variables we have defined.
+    del sys, __file__, split, join, isdir, idlelib
+    __import__('run').main()
+else:
+    # Load idlelib/idle.py which starts the application.
+    import idle
diff --git a/Tools/idle/rpc.py b/Tools/idle/rpc.py
new file mode 100644
index 0000000..15946a6
--- /dev/null
+++ b/Tools/idle/rpc.py
@@ -0,0 +1,580 @@
+"""RPC Implemention, originally written for the Python Idle IDE
+
+For security reasons, GvR requested that Idle's Python execution server process
+connect to the Idle process, which listens for the connection.  Since Idle has
+has only one client per server, this was not a limitation.
+
+   +---------------------------------+ +-------------+
+   | SocketServer.BaseRequestHandler | | SocketIO    |
+   +---------------------------------+ +-------------+
+                   ^                   | register()  |
+                   |                   | unregister()|
+                   |                   +-------------+
+                   |                      ^  ^
+                   |                      |  |
+                   | + -------------------+  |
+                   | |                       |
+   +-------------------------+        +-----------------+
+   | RPCHandler              |        | RPCClient       |
+   | [attribute of RPCServer]|        |                 |
+   +-------------------------+        +-----------------+
+
+The RPCServer handler class is expected to provide register/unregister methods.
+RPCHandler inherits the mix-in class SocketIO, which provides these methods.
+
+See the Idle run.main() docstring for further information on how this was
+accomplished in Idle.
+
+"""
+
+import sys
+import socket
+import select
+import SocketServer
+import struct
+import cPickle as pickle
+import threading
+import traceback
+import copy_reg
+import types
+import marshal
+
+def unpickle_code(ms):
+    co = marshal.loads(ms)
+    assert isinstance(co, types.CodeType)
+    return co
+
+def pickle_code(co):
+    assert isinstance(co, types.CodeType)
+    ms = marshal.dumps(co)
+    return unpickle_code, (ms,)
+
+# XXX KBK 24Aug02 function pickling capability not used in Idle
+#  def unpickle_function(ms):
+#      return ms
+
+#  def pickle_function(fn):
+#      assert isinstance(fn, type.FunctionType)
+#      return `fn`
+
+copy_reg.pickle(types.CodeType, pickle_code, unpickle_code)
+# copy_reg.pickle(types.FunctionType, pickle_function, unpickle_function)
+
+BUFSIZE = 8*1024
+
+class RPCServer(SocketServer.TCPServer):
+
+    def __init__(self, addr, handlerclass=None):
+        if handlerclass is None:
+            handlerclass = RPCHandler
+        SocketServer.TCPServer.__init__(self, addr, handlerclass)
+
+    def server_bind(self):
+        "Override TCPServer method, no bind() phase for connecting entity"
+        pass
+
+    def server_activate(self):
+        """Override TCPServer method, connect() instead of listen()
+
+        Due to the reversed connection, self.server_address is actually the
+        address of the Idle Client to which we are connecting.
+
+        """
+        self.socket.connect(self.server_address)
+
+    def get_request(self):
+        "Override TCPServer method, return already connected socket"
+        return self.socket, self.server_address
+
+    def handle_error(self, request, client_address):
+        """Override TCPServer method
+
+        Error message goes to __stderr__.  No error message if exiting
+        normally or socket raised EOF.  Other exceptions not handled in
+        server code will cause os._exit.
+
+        """
+        try:
+            raise
+        except SystemExit:
+            raise
+        except EOFError:
+            pass
+        except:
+            erf = sys.__stderr__
+            print>>erf, '\n' + '-'*40
+            print>>erf, 'Unhandled server exception!'
+            print>>erf, 'Thread: %s' % threading.currentThread().getName()
+            print>>erf, 'Client Address: ', client_address
+            print>>erf, 'Request: ', repr(request)
+            traceback.print_exc(file=erf)
+            print>>erf, '\n*** Unrecoverable, server exiting!'
+            print>>erf, '-'*40
+            import os
+            os._exit(0)
+
+
+objecttable = {}
+
+class SocketIO:
+
+    nextseq = 0
+
+    def __init__(self, sock, objtable=None, debugging=None):
+        self.mainthread = threading.currentThread()
+        if debugging is not None:
+            self.debugging = debugging
+        self.sock = sock
+        if objtable is None:
+            objtable = objecttable
+        self.objtable = objtable
+        self.cvar = threading.Condition()
+        self.responses = {}
+        self.cvars = {}
+        self.interrupted = False
+
+    def close(self):
+        sock = self.sock
+        self.sock = None
+        if sock is not None:
+            sock.close()
+
+    def debug(self, *args):
+        if not self.debugging:
+            return
+        s = self.location + " " + str(threading.currentThread().getName())
+        for a in args:
+            s = s + " " + str(a)
+        print>>sys.__stderr__, s
+
+    def register(self, oid, object):
+        self.objtable[oid] = object
+
+    def unregister(self, oid):
+        try:
+            del self.objtable[oid]
+        except KeyError:
+            pass
+
+    def localcall(self, request):
+        self.debug("localcall:", request)
+        try:
+            how, (oid, methodname, args, kwargs) = request
+        except TypeError:
+            return ("ERROR", "Bad request format")
+        assert how == "call"
+        if not self.objtable.has_key(oid):
+            return ("ERROR", "Unknown object id: %s" % `oid`)
+        obj = self.objtable[oid]
+        if methodname == "__methods__":
+            methods = {}
+            _getmethods(obj, methods)
+            return ("OK", methods)
+        if methodname == "__attributes__":
+            attributes = {}
+            _getattributes(obj, attributes)
+            return ("OK", attributes)
+        if not hasattr(obj, methodname):
+            return ("ERROR", "Unsupported method name: %s" % `methodname`)
+        method = getattr(obj, methodname)
+        try:
+            ret = method(*args, **kwargs)
+            if isinstance(ret, RemoteObject):
+                ret = remoteref(ret)
+            return ("OK", ret)
+        except SystemExit:
+            raise
+        except socket.error:
+            pass
+        except:
+            self.debug("localcall:EXCEPTION")
+            traceback.print_exc(file=sys.__stderr__)
+            return ("EXCEPTION", None)
+
+    def remotecall(self, oid, methodname, args, kwargs):
+        self.debug("remotecall:asynccall: ", oid, methodname)
+        # XXX KBK 06Feb03 self.interrupted logic may not be necessary if
+        #                 subprocess is threaded.
+        if self.interrupted:
+            self.interrupted = False
+            raise KeyboardInterrupt
+        seq = self.asynccall(oid, methodname, args, kwargs)
+        return self.asyncreturn(seq)
+
+    def asynccall(self, oid, methodname, args, kwargs):
+        request = ("call", (oid, methodname, args, kwargs))
+        seq = self.newseq()
+        self.debug(("asynccall:%d:" % seq), oid, methodname, args, kwargs)
+        self.putmessage((seq, request))
+        return seq
+
+    def asyncreturn(self, seq):
+        self.debug("asyncreturn:%d:call getresponse(): " % seq)
+        response = self.getresponse(seq, wait=None)
+        self.debug(("asyncreturn:%d:response: " % seq), response)
+        return self.decoderesponse(response)
+
+    def decoderesponse(self, response):
+        how, what = response
+        if how == "OK":
+            return what
+        if how == "EXCEPTION":
+            self.debug("decoderesponse: EXCEPTION")
+            return None
+        if how == "ERROR":
+            self.debug("decoderesponse: Internal ERROR:", what)
+            raise RuntimeError, what
+        raise SystemError, (how, what)
+
+    def mainloop(self):
+        """Listen on socket until I/O not ready or EOF
+
+        Main thread pollresponse() will loop looking for seq number None, which
+        never comes, and exit on EOFError.
+
+        """
+        try:
+            self.getresponse(myseq=None, wait=None)
+        except EOFError:
+            pass
+
+    def getresponse(self, myseq, wait):
+        response = self._getresponse(myseq, wait)
+        if response is not None:
+            how, what = response
+            if how == "OK":
+                response = how, self._proxify(what)
+        return response
+
+    def _proxify(self, obj):
+        if isinstance(obj, RemoteProxy):
+            return RPCProxy(self, obj.oid)
+        if isinstance(obj, types.ListType):
+            return map(self._proxify, obj)
+        # XXX Check for other types -- not currently needed
+        return obj
+
+    def _getresponse(self, myseq, wait):
+        self.debug("_getresponse:myseq:", myseq)
+        if threading.currentThread() is self.mainthread:
+            # Main thread: does all reading of requests or responses
+            # Loop here, blocking each time until socket is ready.
+            while 1:
+                response = self.pollresponse(myseq, wait)
+                if response is not None:
+                    return response
+        else:
+            # Auxiliary thread: wait for notification from main thread
+            self.cvar.acquire()
+            self.cvars[myseq] = self.cvar
+            while not self.responses.has_key(myseq):
+                self.cvar.wait()
+            response = self.responses[myseq]
+            del self.responses[myseq]
+            del self.cvars[myseq]
+            self.cvar.release()
+            return response
+
+    def newseq(self):
+        self.nextseq = seq = self.nextseq + 2
+        return seq
+
+    def putmessage(self, message):
+        self.debug("putmessage:%d:" % message[0])
+        try:
+            s = pickle.dumps(message)
+        except:
+            print >>sys.__stderr__, "Cannot pickle:", `message`
+            raise
+        s = struct.pack("<i", len(s)) + s
+        while len(s) > 0:
+            try:
+                n = self.sock.send(s)
+            except AttributeError:
+                # socket was closed
+                raise IOError
+            else:
+                s = s[n:]
+
+    def ioready(self, wait=0.0):
+        r, w, x = select.select([self.sock.fileno()], [], [], wait)
+        return len(r)
+
+    buffer = ""
+    bufneed = 4
+    bufstate = 0 # meaning: 0 => reading count; 1 => reading data
+
+    def pollpacket(self, wait=0.0):
+        self._stage0()
+        if len(self.buffer) < self.bufneed:
+            if not self.ioready(wait):
+                return None
+            try:
+                s = self.sock.recv(BUFSIZE)
+            except socket.error:
+                raise EOFError
+            if len(s) == 0:
+                raise EOFError
+            self.buffer += s
+            self._stage0()
+        return self._stage1()
+
+    def _stage0(self):
+        if self.bufstate == 0 and len(self.buffer) >= 4:
+            s = self.buffer[:4]
+            self.buffer = self.buffer[4:]
+            self.bufneed = struct.unpack("<i", s)[0]
+            self.bufstate = 1
+
+    def _stage1(self):
+        if self.bufstate == 1 and len(self.buffer) >= self.bufneed:
+            packet = self.buffer[:self.bufneed]
+            self.buffer = self.buffer[self.bufneed:]
+            self.bufneed = 4
+            self.bufstate = 0
+            return packet
+
+    def pollmessage(self, wait=0.0):
+        packet = self.pollpacket(wait)
+        if packet is None:
+            return None
+        try:
+            message = pickle.loads(packet)
+        except:
+            print >>sys.__stderr__, "-----------------------"
+            print >>sys.__stderr__, "cannot unpickle packet:", `packet`
+            traceback.print_stack(file=sys.__stderr__)
+            print >>sys.__stderr__, "-----------------------"
+            raise
+        return message
+
+    def pollresponse(self, myseq, wait=0.0):
+        """Handle messages received on the socket.
+
+        Some messages received may be asynchronous 'call' commands, and
+        some may be responses intended for other threads.
+
+        Loop until message with myseq sequence number is received.  Save others
+        in self.responses and notify the owning thread, except that 'call'
+        commands are handed off to localcall() and the response sent back
+        across the link with the appropriate sequence number.
+
+        """
+        while 1:
+            message = self.pollmessage(wait)
+            if message is None:  # socket not ready
+                return None
+            #wait = 0.0  # poll on subsequent passes instead of blocking
+            seq, resq = message
+            self.debug("pollresponse:%d:myseq:%s" % (seq, myseq))
+            if resq[0] == "call":
+                self.debug("pollresponse:%d:localcall:call:" % seq)
+                response = self.localcall(resq)
+                self.debug("pollresponse:%d:localcall:response:%s"
+                           % (seq, response))
+                self.putmessage((seq, response))
+                continue
+            elif seq == myseq:
+                return resq
+            else:
+                self.cvar.acquire()
+                cv = self.cvars.get(seq)
+                # response involving unknown sequence number is discarded,
+                # probably intended for prior incarnation
+                if cv is not None:
+                    self.responses[seq] = resq
+                    cv.notify()
+                self.cvar.release()
+                continue
+
+#----------------- end class SocketIO --------------------
+
+class RemoteObject:
+    # Token mix-in class
+    pass
+
+def remoteref(obj):
+    oid = id(obj)
+    objecttable[oid] = obj
+    return RemoteProxy(oid)
+
+class RemoteProxy:
+
+    def __init__(self, oid):
+        self.oid = oid
+
+class RPCHandler(SocketServer.BaseRequestHandler, SocketIO):
+
+    debugging = False
+    location = "#S"  # Server
+
+    def __init__(self, sock, addr, svr):
+        svr.current_handler = self ## cgt xxx
+        SocketIO.__init__(self, sock)
+        SocketServer.BaseRequestHandler.__init__(self, sock, addr, svr)
+
+    def handle(self):
+        "handle() method required by SocketServer"
+        self.mainloop()
+
+    def get_remote_proxy(self, oid):
+        return RPCProxy(self, oid)
+
+class RPCClient(SocketIO):
+
+    debugging = False
+    location = "#C"  # Client
+
+    nextseq = 1 # Requests coming from the client are odd numbered
+
+    def __init__(self, address, family=socket.AF_INET, type=socket.SOCK_STREAM):
+        self.listening_sock = socket.socket(family, type)
+        self.listening_sock.setsockopt(socket.SOL_SOCKET,
+                                       socket.SO_REUSEADDR, 1)
+        self.listening_sock.bind(address)
+        self.listening_sock.listen(1)
+
+    def accept(self):
+        working_sock, address = self.listening_sock.accept()
+        if self.debugging:
+            print>>sys.__stderr__, "****** Connection request from ", address
+        if address[0] == '127.0.0.1':
+            SocketIO.__init__(self, working_sock)
+        else:
+            print>>sys.__stderr__, "** Invalid host: ", address
+            raise socket.error
+
+    def get_remote_proxy(self, oid):
+        return RPCProxy(self, oid)
+
+class RPCProxy:
+
+    __methods = None
+    __attributes = None
+
+    def __init__(self, sockio, oid):
+        self.sockio = sockio
+        self.oid = oid
+
+    def __getattr__(self, name):
+        if self.__methods is None:
+            self.__getmethods()
+        if self.__methods.get(name):
+            return MethodProxy(self.sockio, self.oid, name)
+        if self.__attributes is None:
+            self.__getattributes()
+        if not self.__attributes.has_key(name):
+            raise AttributeError, name
+    __getattr__.DebuggerStepThrough=1
+
+    def __getattributes(self):
+        self.__attributes = self.sockio.remotecall(self.oid,
+                                                "__attributes__", (), {})
+
+    def __getmethods(self):
+        self.__methods = self.sockio.remotecall(self.oid,
+                                                "__methods__", (), {})
+
+def _getmethods(obj, methods):
+    # Helper to get a list of methods from an object
+    # Adds names to dictionary argument 'methods'
+    for name in dir(obj):
+        attr = getattr(obj, name)
+        if callable(attr):
+            methods[name] = 1
+    if type(obj) == types.InstanceType:
+        _getmethods(obj.__class__, methods)
+    if type(obj) == types.ClassType:
+        for super in obj.__bases__:
+            _getmethods(super, methods)
+
+def _getattributes(obj, attributes):
+    for name in dir(obj):
+        attr = getattr(obj, name)
+        if not callable(attr):
+            attributes[name] = 1
+
+class MethodProxy:
+
+    def __init__(self, sockio, oid, name):
+        self.sockio = sockio
+        self.oid = oid
+        self.name = name
+
+    def __call__(self, *args, **kwargs):
+        value = self.sockio.remotecall(self.oid, self.name, args, kwargs)
+        return value
+
+#
+# Self Test
+#
+
+def testServer(addr):
+    # XXX 25 Jul 02 KBK needs update to use rpc.py register/unregister methods
+    class RemotePerson:
+        def __init__(self,name):
+            self.name = name
+        def greet(self, name):
+            print "(someone called greet)"
+            print "Hello %s, I am %s." % (name, self.name)
+            print
+        def getName(self):
+            print "(someone called getName)"
+            print
+            return self.name
+        def greet_this_guy(self, name):
+            print "(someone called greet_this_guy)"
+            print "About to greet %s ..." % name
+            remote_guy = self.server.current_handler.get_remote_proxy(name)
+            remote_guy.greet("Thomas Edison")
+            print "Done."
+            print
+
+    person = RemotePerson("Thomas Edison")
+    svr = RPCServer(addr)
+    svr.register('thomas', person)
+    person.server = svr # only required if callbacks are used
+
+    # svr.serve_forever()
+    svr.handle_request()  # process once only
+
+def testClient(addr):
+    "demonstrates RPC Client"
+    # XXX 25 Jul 02 KBK needs update to use rpc.py register/unregister methods
+    import time
+    clt=RPCClient(addr)
+    thomas = clt.get_remote_proxy("thomas")
+    print "The remote person's name is ..."
+    print thomas.getName()
+    # print clt.remotecall("thomas", "getName", (), {})
+    print
+    time.sleep(1)
+    print "Getting remote thomas to say hi..."
+    thomas.greet("Alexander Bell")
+    #clt.remotecall("thomas","greet",("Alexander Bell",), {})
+    print "Done."
+    print
+    time.sleep(2)
+    # demonstrates remote server calling local instance
+    class LocalPerson:
+        def __init__(self,name):
+            self.name = name
+        def greet(self, name):
+            print "You've greeted me!"
+        def getName(self):
+            return self.name
+    person = LocalPerson("Alexander Bell")
+    clt.register("alexander",person)
+    thomas.greet_this_guy("alexander")
+    # clt.remotecall("thomas","greet_this_guy",("alexander",), {})
+
+def test():
+    addr=("localhost",8833)
+    if len(sys.argv) == 2:
+        if sys.argv[1]=='-server':
+            testServer(addr)
+            return
+    testClient(addr)
+
+if __name__ == '__main__':
+    test()
diff --git a/Tools/idle/run.py b/Tools/idle/run.py
new file mode 100644
index 0000000..497cbbd
--- /dev/null
+++ b/Tools/idle/run.py
@@ -0,0 +1,216 @@
+import sys
+import time
+import socket
+import traceback
+import threading
+import Queue
+
+import boolcheck
+
+import CallTips
+import RemoteDebugger
+import RemoteObjectBrowser
+import StackViewer
+import rpc
+import interrupt
+
+import __main__
+
+# Thread shared globals: Establish a queue between a subthread (which handles
+# the socket) and the main thread (which runs user code), plus global
+# completion and exit flags:
+
+server = None                # RPCServer instance
+queue = Queue.Queue(0)
+execution_finished = False
+exit_requested = False
+
+
+def main():
+    """Start the Python execution server in a subprocess
+
+    In the Python subprocess, RPCServer is instantiated with handlerclass
+    MyHandler, which inherits register/unregister methods from RPCHandler via
+    the mix-in class SocketIO.
+
+    When the RPCServer 'server' is instantiated, the TCPServer initialization
+    creates an instance of run.MyHandler and calls its handle() method.
+    handle() instantiates a run.Executive object, passing it a reference to the
+    MyHandler object.  That reference is saved as attribute rpchandler of the
+    Executive instance.  The Executive methods have access to the reference and
+    can pass it on to entities that they command
+    (e.g. RemoteDebugger.Debugger.start_debugger()).  The latter, in turn, can
+    call MyHandler(SocketIO) register/unregister methods via the reference to
+    register and unregister themselves.
+
+    """
+    global queue, execution_finished, exit_requested
+
+    port = 8833
+    if sys.argv[1:]:
+        port = int(sys.argv[1])
+    sys.argv[:] = [""]
+    sockthread = threading.Thread(target=manage_socket,
+                                  name='SockThread',
+                                  args=(('localhost', port),))
+    sockthread.setDaemon(True)
+    sockthread.start()
+    while 1:
+        try:
+            if exit_requested:
+                sys.exit()
+            # XXX KBK 22Mar03 eventually check queue here!
+            pass
+            time.sleep(0.05)
+        except KeyboardInterrupt:
+            ##execution_finished = True
+            continue
+
+def manage_socket(address):
+    global server, exit_requested
+
+    for i in range(6):
+        time.sleep(i)
+        try:
+            server = rpc.RPCServer(address, MyHandler)
+            break
+        except socket.error, err:
+            if i < 3:
+                print>>sys.__stderr__, ".. ",
+            else:
+                print>>sys.__stderr__,"\nPython subprocess socket error: "\
+                                              + err[1] + ", retrying...."
+    else:
+        print>>sys.__stderr__, "\nConnection to Idle failed, exiting."
+        exit_requested = True
+    server.handle_request() # A single request only
+
+
+class MyHandler(rpc.RPCHandler):
+
+    def handle(self):
+        """Override base method"""
+        executive = Executive(self)
+        self.register("exec", executive)
+        sys.stdin = self.get_remote_proxy("stdin")
+        sys.stdout = self.get_remote_proxy("stdout")
+        sys.stderr = self.get_remote_proxy("stderr")
+        rpc.RPCHandler.getresponse(self, myseq=None, wait=0.5)
+
+
+class Executive:
+
+    def __init__(self, rpchandler):
+        self.rpchandler = rpchandler
+        self.locals = __main__.__dict__
+        self.calltip = CallTips.CallTips()
+
+    def runcode(self, code):
+        global queue, execution_finished
+
+        execution_finished = False
+        queue.put(code)
+        # dequeue and run in subthread
+        self.runcode_from_queue()
+        while not execution_finished:
+            time.sleep(0.05)
+
+    def runcode_from_queue(self):
+        global queue, execution_finished
+
+        # poll until queue has code object, using threads, just block?
+        while True:
+            try:
+                code = queue.get(0)
+                break
+            except Queue.Empty:
+                time.sleep(0.05)
+        try:
+            exec code in self.locals
+        except:
+            self.flush_stdout()
+            efile = sys.stderr
+            typ, val, tb = info = sys.exc_info()
+            sys.last_type, sys.last_value, sys.last_traceback = info
+            tbe = traceback.extract_tb(tb)
+            print >>efile, 'Traceback (most recent call last):'
+            exclude = ("run.py", "rpc.py", "RemoteDebugger.py", "bdb.py")
+            self.cleanup_traceback(tbe, exclude)
+            traceback.print_list(tbe, file=efile)
+            lines = traceback.format_exception_only(typ, val)
+            for line in lines:
+                print>>efile, line,
+            execution_finished = True
+        else:
+            self.flush_stdout()
+            execution_finished = True
+
+    def flush_stdout(self):
+        try:
+            if sys.stdout.softspace:
+                sys.stdout.softspace = 0
+                sys.stdout.write("\n")
+        except (AttributeError, EOFError):
+            pass
+
+    def cleanup_traceback(self, tb, exclude):
+        "Remove excluded traces from beginning/end of tb; get cached lines"
+        orig_tb = tb[:]
+        while tb:
+            for rpcfile in exclude:
+                if tb[0][0].count(rpcfile):
+                    break    # found an exclude, break for: and delete tb[0]
+            else:
+                break        # no excludes, have left RPC code, break while:
+            del tb[0]
+        while tb:
+            for rpcfile in exclude:
+                if tb[-1][0].count(rpcfile):
+                    break
+            else:
+                break
+            del tb[-1]
+        if len(tb) == 0:
+            # exception was in IDLE internals, don't prune!
+            tb[:] = orig_tb[:]
+            print>>sys.stderr, "** IDLE Internal Exception: "
+        for i in range(len(tb)):
+            fn, ln, nm, line = tb[i]
+            if nm == '?':
+                nm = "-toplevel-"
+            if not line and fn.startswith("<pyshell#"):
+                line = self.rpchandler.remotecall('linecache', 'getline',
+                                                  (fn, ln), {})
+            tb[i] = fn, ln, nm, line
+
+    def interrupt_the_server(self):
+        self.rpchandler.interrupted = True
+        ##print>>sys.__stderr__, "** Interrupt main!"
+        interrupt.interrupt_main()
+
+    def shutdown_the_server(self):
+        global exit_requested
+
+        exit_requested = True
+
+    def start_the_debugger(self, gui_adap_oid):
+        return RemoteDebugger.start_debugger(self.rpchandler, gui_adap_oid)
+
+    def stop_the_debugger(self, idb_adap_oid):
+        "Unregister the Idb Adapter.  Link objects and Idb then subject to GC"
+        self.rpchandler.unregister(idb_adap_oid)
+
+    def get_the_calltip(self, name):
+        return self.calltip.fetch_tip(name)
+
+    def stackviewer(self, flist_oid=None):
+        if not hasattr(sys, "last_traceback"):
+            return None
+        flist = None
+        if flist_oid is not None:
+            flist = self.rpchandler.get_remote_proxy(flist_oid)
+        tb = sys.last_traceback
+        while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]:
+            tb = tb.tb_next
+        item = StackViewer.StackTreeItem(flist, tb)
+        return RemoteObjectBrowser.remote_object_tree_item(item)
diff --git a/Tools/idle/setup.cfg b/Tools/idle/setup.cfg
new file mode 100644
index 0000000..c4b5729
--- /dev/null
+++ b/Tools/idle/setup.cfg
@@ -0,0 +1,4 @@
+[bdist_rpm]
+release = 1
+packager = Kurt B. Kaiser <kbk@shore.net>
+
diff --git a/Tools/idle/tabpage.py b/Tools/idle/tabpage.py
new file mode 100644
index 0000000..12f8929
--- /dev/null
+++ b/Tools/idle/tabpage.py
@@ -0,0 +1,113 @@
+"""
+a couple of classes for implementing partial tabbed-page like behaviour
+"""
+
+from Tkinter import *
+
+class InvalidTabPage(Exception): pass
+class AlreadyExists(Exception): pass
+
+class PageTab(Frame):
+    """
+    a 'page tab' like framed button
+    """
+    def __init__(self,parent):
+        Frame.__init__(self, parent,borderwidth=2,relief=RIDGE)
+        self.button=Radiobutton(self,padx=5,pady=5,takefocus=FALSE,
+                indicatoron=FALSE,highlightthickness=0,
+                borderwidth=0,selectcolor=self.cget('bg'))
+        self.button.pack()
+
+class TabPageSet(Frame):
+    """
+    a set of 'pages' with TabButtons for controlling their display
+    """
+    def __init__(self,parent,pageNames=[],**kw):
+        """
+        pageNames - a list of strings, each string will be the dictionary key
+        to a page's data, and the name displayed on the page's tab. Should be
+        specified in desired page order. The first page will be the default
+        and first active page.
+        """
+        Frame.__init__(self, parent, kw)
+        self.grid_location(0,0)
+        self.columnconfigure(0,weight=1)
+        self.rowconfigure(1,weight=1)
+        self.tabBar=Frame(self)
+        self.tabBar.grid(row=0,column=0,sticky=EW)
+        self.activePage=StringVar(self)
+        self.defaultPage=''
+        self.pages={}
+        for name in pageNames:
+            self.AddPage(name)
+
+    def ChangePage(self,pageName=None):
+        if pageName:
+            if pageName in self.pages.keys():
+                self.activePage.set(pageName)
+            else:
+                raise InvalidTabPage, 'Invalid TabPage Name'
+        ## pop up the active 'tab' only
+        for page in self.pages.keys():
+            self.pages[page]['tab'].config(relief=RIDGE)
+        self.pages[self.GetActivePage()]['tab'].config(relief=RAISED)
+        ## switch page
+        self.pages[self.GetActivePage()]['page'].lift()
+
+    def GetActivePage(self):
+        return self.activePage.get()
+
+    def AddPage(self,pageName):
+        if pageName in self.pages.keys():
+            raise AlreadyExists, 'TabPage Name Already Exists'
+        self.pages[pageName]={'tab':PageTab(self.tabBar),
+                'page':Frame(self,borderwidth=2,relief=RAISED)}
+        self.pages[pageName]['tab'].button.config(text=pageName,
+                command=self.ChangePage,variable=self.activePage,
+                value=pageName)
+        self.pages[pageName]['tab'].pack(side=LEFT)
+        self.pages[pageName]['page'].grid(row=1,column=0,sticky=NSEW)
+        if len(self.pages)==1: # adding first page
+            self.defaultPage=pageName
+            self.activePage.set(self.defaultPage)
+            self.ChangePage()
+
+    def RemovePage(self,pageName):
+        if not pageName in self.pages.keys():
+            raise InvalidTabPage, 'Invalid TabPage Name'
+        self.pages[pageName]['tab'].pack_forget()
+        self.pages[pageName]['page'].grid_forget()
+        self.pages[pageName]['tab'].destroy()
+        self.pages[pageName]['page'].destroy()
+        del(self.pages[pageName])
+        # handle removing last remaining, or default, or active page
+        if not self.pages: # removed last remaining page
+            self.defaultPage=''
+            return
+        if pageName==self.defaultPage: # set a new default page
+            self.defaultPage=\
+                self.tabBar.winfo_children()[0].button.cget('text')
+        if pageName==self.GetActivePage(): # set a new active page
+            self.activePage.set(self.defaultPage)
+        self.ChangePage()
+
+if __name__ == '__main__':
+    #test dialog
+    root=Tk()
+    tabPage=TabPageSet(root,pageNames=['Foobar','Baz'])
+    tabPage.pack(expand=TRUE,fill=BOTH)
+    Label(tabPage.pages['Foobar']['page'],text='Foo',pady=20).pack()
+    Label(tabPage.pages['Foobar']['page'],text='Bar',pady=20).pack()
+    Label(tabPage.pages['Baz']['page'],text='Baz').pack()
+    entryPgName=Entry(root)
+    buttonAdd=Button(root,text='Add Page',
+            command=lambda:tabPage.AddPage(entryPgName.get()))
+    buttonRemove=Button(root,text='Remove Page',
+            command=lambda:tabPage.RemovePage(entryPgName.get()))
+    labelPgName=Label(root,text='name of page to add/remove:')
+    buttonAdd.pack(padx=5,pady=5)
+    buttonRemove.pack(padx=5,pady=5)
+    labelPgName.pack(padx=5)
+    entryPgName.pack(padx=5)
+    tabPage.ChangePage()
+    root.mainloop()
diff --git a/Tools/idle/textView.py b/Tools/idle/textView.py
new file mode 100644
index 0000000..23e8bed
--- /dev/null
+++ b/Tools/idle/textView.py
@@ -0,0 +1,77 @@
+##---------------------------------------------------------------------------##
+##
+## idle - simple text view dialog
+## elguavas
+##
+##---------------------------------------------------------------------------##
+"""
+simple text browser for idle
+"""
+from Tkinter import *
+import tkMessageBox
+
+class TextViewer(Toplevel):
+    """
+    simple text viewer dialog for idle
+    """
+    def __init__(self,parent,title,fileName):
+        """
+        fileName - string,should be an absoulute filename
+        """
+        Toplevel.__init__(self, parent)
+        self.configure(borderwidth=5)
+        self.geometry("+%d+%d" % (parent.winfo_rootx()+10,
+                parent.winfo_rooty()+10))
+        #elguavas - config placeholders til config stuff completed
+        self.bg=None
+        self.fg=None
+
+        self.CreateWidgets()
+        self.title(title)
+        self.transient(parent)
+        self.grab_set()
+        self.protocol("WM_DELETE_WINDOW", self.Ok)
+        self.parent = parent
+        self.textView.focus_set()
+        #key bindings for this dialog
+        self.bind('<Return>',self.Ok) #dismiss dialog
+        self.bind('<Escape>',self.Ok) #dismiss dialog
+        self.LoadTextFile(fileName)
+        self.textView.config(state=DISABLED)
+        self.wait_window()
+
+    def LoadTextFile(self, fileName):
+        textFile = None
+        try:
+            textFile = open(fileName, 'r')
+        except IOError:
+            tkMessageBox.showerror(title='File Load Error',
+                    message='Unable to load file '+`fileName`+' .')
+        else:
+            self.textView.insert(0.0,textFile.read())
+
+    def CreateWidgets(self):
+        frameText = Frame(self)
+        frameButtons = Frame(self)
+        self.buttonOk = Button(frameButtons,text='Ok',
+                command=self.Ok,takefocus=FALSE,default=ACTIVE)
+        self.scrollbarView = Scrollbar(frameText,orient=VERTICAL,
+                takefocus=FALSE,highlightthickness=0)
+        self.textView = Text(frameText,wrap=WORD,highlightthickness=0)
+        self.scrollbarView.config(command=self.textView.yview)
+        self.textView.config(yscrollcommand=self.scrollbarView.set)
+        self.buttonOk.pack(padx=5,pady=5)
+        self.scrollbarView.pack(side=RIGHT,fill=Y)
+        self.textView.pack(side=LEFT,expand=TRUE,fill=BOTH)
+        frameButtons.pack(side=BOTTOM,fill=X)
+        frameText.pack(side=TOP,expand=TRUE,fill=BOTH)
+
+    def Ok(self, event=None):
+        self.destroy()
+
+if __name__ == '__main__':
+    #test the dialog
+    root=Tk()
+    Button(root,text='View',
+            command=lambda:TextViewer(root,'Text','./textView.py')).pack()
+    root.mainloop()