Tim Peters smart.patch:

EditorWindow.py:

+ Added get_tabwidth & set_tabwidth "virtual text" methods, that get/set the
widget's view of what a tab means.

+ Moved TK_TABWIDTH_DEFAULT here from AutoIndent.

+ Renamed Mark's get_selection_index to get_selection_indices (sorry, Mark,
but the name was plain wrong <wink>).

FormatParagraph.py:  renamed use of get_selection_index.

AutoIndent.py:

+ Moved TK_TABWIDTH_DEFAULT to EditorWindow.

+ Rewrote set_indentation_params to use new VTW get/set_tabwidth methods.

+ Changed smart_backspace_event to delete whitespace back to closest
preceding virtual tab stop or real character (note that this may require
inserting characters if backspacing over a tab!).

+ Nuked almost references to the selection tag, in favor of using
get_selection_indices.  The sole exception is in set_region, for which no
"set_selection" abstraction has yet been agreed upon.

+ Had too much fun using the spiffy new features of the format-paragraph
cmd.
diff --git a/Tools/idle/AutoIndent.py b/Tools/idle/AutoIndent.py
index 7784254..66a0aec 100644
--- a/Tools/idle/AutoIndent.py
+++ b/Tools/idle/AutoIndent.py
@@ -3,9 +3,6 @@
 #import tkMessageBox
 #import tkSimpleDialog
 
-# The default tab setting for a Text widget, in average-width characters.
-TK_TABWIDTH_DEFAULT = 8
-
 ###$ event <<newline-and-indent>>
 ###$ win <Key-Return>
 ###$ win <KP_Enter>
@@ -101,7 +98,7 @@
     # Nobody expects this, so for now tabwidth should never be changed.
     usetabs = 1
     indentwidth = 4
-    tabwidth = TK_TABWIDTH_DEFAULT
+    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.
@@ -139,8 +136,6 @@
     # character means.
 
     def set_indentation_params(self, ispythonsource, guess=1):
-        text = self.text
-
         if guess and ispythonsource:
             i = self.guess_indent()
             if 2 <= i <= 8:
@@ -148,39 +143,47 @@
             if self.indentwidth != self.tabwidth:
                 self.usetabs = 0
 
-        current_tabs = text['tabs']
-        if current_tabs == "" and self.tabwidth == TK_TABWIDTH_DEFAULT:
-            pass
-        else:
-            # Reconfigure the Text widget by measuring the width
-            # of a tabwidth-length string in pixels, forcing the
-            # widget's tab stops to that.
-            need_tabs = text.tk.call("font", "measure", text['font'],
-                                     "-displayof", text.master,
-                                     "n" * self.tabwidth)
-            if current_tabs != need_tabs:
-                text.configure(tabs=need_tabs)
+        self.editwin.set_tabwidth(self.tabwidth)
 
     def smart_backspace_event(self, event):
         text = self.text
-        try:
-            first = text.index("sel.first")
-            last = text.index("sel.last")
-        except: # Was catching TclError, but this doesnt work for
-            first = last = None
+        first, last = self.editwin.get_selection_indices()
         if first and last:
             text.delete(first, last)
             text.mark_set("insert", first)
             return "break"
-        # If we're at the end of leading whitespace, nuke one indent
-        # level, else one character.
+        # Delete whitespace left, until hitting a real char or closest
+        # preceding virtual tab stop.
         chars = text.get("insert linestart", "insert")
-        raw, effective = classifyws(chars, self.tabwidth)
-        if 0 < raw == len(chars):
-            if effective >= self.indentwidth:
-                self.reindent_to(effective - self.indentwidth)
-                return "break"
-        text.delete("insert-1c")
+        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.
+        expand, tabwidth = string.expandtabs, self.tabwidth
+        have = len(expand(chars, tabwidth))
+        assert have > 0
+        want = int((have - 1) / self.indentwidth) * self.indentwidth
+        ncharsdeleted = 0
+        while 1:
+            chars = chars[:-1]
+            ncharsdeleted = ncharsdeleted + 1
+            have = len(expand(chars, 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):
@@ -190,11 +193,7 @@
         #     do indent-region & return
         # indent one level
         text = self.text
-        try:
-            first = text.index("sel.first")
-            last = text.index("sel.last")
-        except: # Was catching TclError, but this doesnt work for
-            first = last = None
+        first, last = self.editwin.get_selection_indices()
         text.undo_block_start()
         try:
             if first and last:
@@ -223,11 +222,7 @@
 
     def newline_and_indent_event(self, event):
         text = self.text
-        try:
-            first = text.index("sel.first")
-            last = text.index("sel.last")
-        except: # Was catching TclError, but this doesnt work for
-            first = last = None
+        first, last = self.editwin.get_selection_indices()
         text.undo_block_start()
         try:
             if first and last:
@@ -256,8 +251,8 @@
             # start new line
             text.insert("insert", '\n')
 
-            # adjust indentation for continuations and block open/close
-            # first need to find the last stmt
+            # 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:
@@ -280,15 +275,15 @@
                 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
+                    # 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
+                    # 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:
@@ -298,8 +293,8 @@
                 return "break"
 
             # This line starts a brand new stmt; indent relative to
-            # indentation of initial line of closest preceding interesting
-            # stmt.
+            # indentation of initial line of closest preceding
+            # interesting stmt.
             indent = y.get_base_indent_string()
             text.insert("insert", indent)
             if y.is_block_opener():
@@ -313,9 +308,10 @@
 
     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.
+    # 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,
@@ -413,9 +409,11 @@
 
     def get_region(self):
         text = self.text
-        head = text.index("sel.first linestart")
-        tail = text.index("sel.last -1c lineend +1c")
-        if not (head and tail):
+        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)
diff --git a/Tools/idle/EditorWindow.py b/Tools/idle/EditorWindow.py
index bc4edde..0f23b15 100644
--- a/Tools/idle/EditorWindow.py
+++ b/Tools/idle/EditorWindow.py
@@ -9,6 +9,9 @@
 import idlever
 import WindowList
 
+# The default tab setting for a Text widget, in average-width characters.
+TK_TABWIDTH_DEFAULT = 8
+
 # File menu
 
 #$ event <<open-module>>
@@ -599,7 +602,7 @@
 
     # If a selection is defined in the text widget, return (start,
     # end) as Tkinter text indices, otherwise return (None, None)
-    def get_selection_index(self):
+    def get_selection_indices(self):
         try:
             first = self.text.index("sel.first")
             last = self.text.index("sel.last")
@@ -607,6 +610,23 @@
         except TclError:
             return None, None
 
+    # Return the text widget's current view of what a tab stop means
+    # (equivalent width in spaces).
+
+    def get_tabwidth(self):
+        current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
+        return int(current)
+
+    # Set the text widget's current view of what a tab stop means.
+
+    def set_tabwidth(self, newtabwidth):
+        text = self.text
+        if self.get_tabwidth() != newtabwidth:
+            pixels = text.tk.call("font", "measure", text["font"],
+                                  "-displayof", text.master,
+                                  "n" * newtabwith)
+            text.configure(tabs=pixels)
+
 def prepstr(s):
     # Helper to extract the underscore from a string, e.g.
     # prepstr("Co_py") returns (2, "Copy").
diff --git a/Tools/idle/FormatParagraph.py b/Tools/idle/FormatParagraph.py
index 6d2d475..671b303 100644
--- a/Tools/idle/FormatParagraph.py
+++ b/Tools/idle/FormatParagraph.py
@@ -38,7 +38,7 @@
 
     def format_paragraph_event(self, event):
         text = self.editwin.text
-        first, last = self.editwin.get_selection_index()
+        first, last = self.editwin.get_selection_indices()
         if first and last:
             data = text.get(first, last)
             comment_header = ''