Add new extension to reformat a (text) paragraph.
diff --git a/Tools/idle/FormatParagraph.py b/Tools/idle/FormatParagraph.py
new file mode 100644
index 0000000..729ec93
--- /dev/null
+++ b/Tools/idle/FormatParagraph.py
@@ -0,0 +1,101 @@
+# Extension to format a paragraph
+
+import string
+import re
+
+class FormatParagraph:
+
+    menudefs = [
+        ('edit', [
+            ('Format Paragraph', '<<format-paragraph>>'),
+         ])
+    ]
+
+    keydefs = {
+        '<<format-paragraph>>': ['<Alt-q>'],
+    }
+
+    def __init__(self, editwin):
+        self.editwin = editwin
+
+    def format_paragraph_event(self, event):
+        text = self.editwin.text
+        try:
+            first = text.index("sel.first")
+            last = text.index("sel.last")
+        except TclError:
+            first = last = None
+        if first and last:
+            data = text.get(first, last)
+        else:
+            first, last, data = find_paragraph(text, text.index("insert"))
+        newdata = reformat_paragraph(data)
+        text.tag_remove("sel", "1.0", "end")
+        if newdata != data:
+            text.mark_set("insert", first)
+            text.delete(first, last)
+            text.insert(first, newdata)
+        else:
+            text.mark_set("insert", last)
+        text.see("insert")
+
+def find_paragraph(text, mark):
+    lineno, col = map(int, string.split(mark, "."))
+    line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
+    while is_all_white(line):
+        lineno = lineno + 1
+        line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
+    first_lineno = lineno
+    while not is_all_white(line):
+        lineno = lineno + 1
+        line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
+    last = "%d.0" % lineno
+    # Search back to beginning of paragraph
+    lineno = first_lineno - 1
+    line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
+    while not is_all_white(line):
+        lineno = lineno - 1
+        line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
+    first = "%d.0" % (lineno+1)
+    return first, last, text.get(first, last)
+
+def reformat_paragraph(data, limit=72):
+    lines = string.split(data, "\n")
+    i = 0
+    n = len(lines)
+    while i < n and is_all_white(lines[i]):
+        i = i+1
+    if i >= n:
+        return data
+    indent1 = get_indent(lines[i])
+    if i+1 < n and not is_all_white(lines[i+1]):
+        indent2 = get_indent(lines[i+1])
+    else:
+        indent2 = indent1
+    new = lines[:i]
+    partial = indent1
+    while i < n and not is_all_white(lines[i]):
+        # XXX Should take double space after period (etc.) into account
+        words = re.split("(\s+)", lines[i])
+        for j in range(0, len(words), 2):
+            word = words[j]
+            if not word:
+                continue # Can happen when line ends in whitespace
+            if len(string.expandtabs(partial + word)) > limit and \
+               partial != indent1:
+                new.append(string.rstrip(partial))
+                partial = indent2
+            partial = partial + word + " "
+            if j+1 < len(words) and words[j+1] != " ":
+                partial = partial + " "
+        i = i+1
+    new.append(string.rstrip(partial))
+    # XXX Should reformat remaining paragraphs as well
+    new.extend(lines[i:])
+    return string.join(new, "\n")
+
+def is_all_white(line):
+    return re.match(r"^\s*$", line) is not None
+
+def get_indent(line):
+    return re.match(r"^(\s*)", line).group()
diff --git a/Tools/idle/extend.py b/Tools/idle/extend.py
index 7d8117d..58bc95f 100644
--- a/Tools/idle/extend.py
+++ b/Tools/idle/extend.py
@@ -5,5 +5,6 @@
     "SearchBinding",
     "AutoIndent",
     "AutoExpand",
+    "FormatParagraph",
     "ZoomHeight",
 ]