[Python-checkins] bpo-27099: IDLE - Convert built-in extensions to regular features (#2494)

Terry Jan Reedy webhook-mailer at python.org
Sun Sep 10 17:19:51 EDT 2017


https://github.com/python/cpython/commit/58fc71c447049d0efe4e11db1b55edc307f1bede
commit: 58fc71c447049d0efe4e11db1b55edc307f1bede
branch: master
author: wohlganger <charles.wohlganger at gmail.com>
committer: Terry Jan Reedy <tjreedy at udel.edu>
date: 2017-09-10T17:19:47-04:00
summary:

bpo-27099: IDLE - Convert built-in extensions to regular features (#2494)

About 10 IDLE features were implemented as supposedly optional
extensions.  Their different behavior could be confusing or worse for
users and not good for maintenance.  Hence the conversion.

The main difference for users is that user configurable key bindings
for builtin features are now handled uniformly.  Now, editing a binding
in a keyset only affects its value in the keyset.  All bindings are
defined together in the system-specific default keysets in config-
extensions.def.  All custom keysets are saved as a whole in config-
extension.cfg.  All take effect as soon as one clicks Apply or Ok.

The affected events are '<<force-open-completions>>', '<<expand-word>>',
'<<force-open-calltip>>', '<<flash-paren>>', '<<format-paragraph>>',
'<<run-module>>', '<<check-module>>', and '<<zoom-height>>'.  Any
(global) customizations made before 3.6.3 will not affect their keyset-
specific customization after 3.6.3. and vice versa.

Inital patch by Charles Wohlganger, revised by Terry Jan Reedy.

files:
A Lib/idlelib/zzdummy.py
A Misc/NEWS.d/next/IDLE/2017-08-24-13-48-16.bpo-27099.rENefC.rst
M Doc/library/idle.rst
M Lib/idlelib/autocomplete.py
M Lib/idlelib/autoexpand.py
M Lib/idlelib/calltips.py
M Lib/idlelib/codecontext.py
M Lib/idlelib/config-extensions.def
M Lib/idlelib/config-keys.def
M Lib/idlelib/config.py
M Lib/idlelib/configdialog.py
M Lib/idlelib/editor.py
M Lib/idlelib/idle_test/test_config.py
M Lib/idlelib/idle_test/test_configdialog.py
M Lib/idlelib/mainmenu.py
M Lib/idlelib/outwin.py
M Lib/idlelib/paragraph.py
M Lib/idlelib/parenmatch.py
M Lib/idlelib/rstrip.py
M Lib/idlelib/runscript.py
M Lib/idlelib/zoomheight.py

diff --git a/Doc/library/idle.rst b/Doc/library/idle.rst
index a945b6d7712..4f51f3f0ac2 100644
--- a/Doc/library/idle.rst
+++ b/Doc/library/idle.rst
@@ -672,23 +672,6 @@ Extensions
 
 IDLE contains an extension facility.  Preferences for extensions can be
 changed with Configure Extensions. See the beginning of config-extensions.def
-in the idlelib directory for further information.  The default extensions
-are currently:
-
-* FormatParagraph
-
-* AutoExpand
-
-* ZoomHeight
-
-* ScriptBinding
-
-* CallTips
-
-* ParenMatch
-
-* AutoComplete
-
-* CodeContext
-
-* RstripExtension
+in the idlelib directory for further information.  The only current default
+extension is zoomheight. It exists as an extension primarily to be an example
+and for testing purposes.
\ No newline at end of file
diff --git a/Lib/idlelib/autocomplete.py b/Lib/idlelib/autocomplete.py
index cd212ccb143..edf445f08b5 100644
--- a/Lib/idlelib/autocomplete.py
+++ b/Lib/idlelib/autocomplete.py
@@ -1,7 +1,7 @@
-"""autocomplete.py - An IDLE extension for automatically completing names.
+"""Complete either attribute names or file names.
 
-This extension can complete either attribute names or file names. It can pop
-a window with all available names, for the user to select from.
+Either on demand or after a user-selected delay after a key character,
+pop up a list of candidates.
 """
 import os
 import string
@@ -27,18 +27,9 @@
 
 class AutoComplete:
 
-    menudefs = [
-        ('edit', [
-            ("Show Completions", "<<force-open-completions>>"),
-        ])
-    ]
-
-    popupwait = idleConf.GetOption("extensions", "AutoComplete",
-                                   "popupwait", type="int", default=0)
-
     def __init__(self, editwin=None):
         self.editwin = editwin
-        if editwin is not None:  # not in subprocess or test
+        if editwin is not None:   # not in subprocess or test
             self.text = editwin.text
             self.autocompletewindow = None
             # id of delayed call, and the index of the text insert when
@@ -47,6 +38,11 @@ def __init__(self, editwin=None):
             self._delayed_completion_id = None
             self._delayed_completion_index = None
 
+    @classmethod
+    def reload(cls):
+        cls.popupwait = idleConf.GetOption(
+            "extensions", "AutoComplete", "popupwait", type="int", default=0)
+
     def _make_autocomplete_window(self):
         return autocomplete_w.AutoCompleteWindow(self.text)
 
@@ -228,6 +224,9 @@ def get_entity(self, name):
         return eval(name, namespace)
 
 
+AutoComplete.reload()
+
+
 if __name__ == '__main__':
     from unittest import main
     main('idlelib.idle_test.test_autocomplete', verbosity=2)
diff --git a/Lib/idlelib/autoexpand.py b/Lib/idlelib/autoexpand.py
index 6b46bee69c9..42e733a1a9e 100644
--- a/Lib/idlelib/autoexpand.py
+++ b/Lib/idlelib/autoexpand.py
@@ -10,23 +10,13 @@
 place before requesting the next selection causes AutoExpand to reset
 its state.
 
-This is an extension file and there is only one instance of AutoExpand.
+There is only one instance of Autoexpand.
 '''
 import re
 import string
 
-###$ event <<expand-word>>
-###$ win <Alt-slash>
-###$ unix <Alt-slash>
 
 class AutoExpand:
-
-    menudefs = [
-        ('edit', [
-            ('E_xpand Word', '<<expand-word>>'),
-         ]),
-    ]
-
     wordchars = string.ascii_letters + string.digits + "_"
 
     def __init__(self, editwin):
@@ -100,6 +90,7 @@ def getprevword(self):
             i = i-1
         return line[i:]
 
+
 if __name__ == '__main__':
     import unittest
     unittest.main('idlelib.idle_test.test_autoexpand', verbosity=2)
diff --git a/Lib/idlelib/calltips.py b/Lib/idlelib/calltips.py
index 49625eac158..ec8f6169895 100644
--- a/Lib/idlelib/calltips.py
+++ b/Lib/idlelib/calltips.py
@@ -1,9 +1,8 @@
-"""calltips.py - An IDLE Extension to Jog Your Memory
+"""Pop up a reminder of how to call a function.
 
 Call Tips are floating windows which display function, class, and method
 parameter and docstring information when you type an opening parenthesis, and
 which disappear when you type a closing parenthesis.
-
 """
 import inspect
 import re
@@ -15,13 +14,8 @@
 from idlelib.hyperparser import HyperParser
 import __main__
 
-class CallTips:
 
-    menudefs = [
-        ('edit', [
-            ("Show call tip", "<<force-open-calltip>>"),
-        ])
-    ]
+class CallTips:
 
     def __init__(self, editwin=None):
         if editwin is None:  # subprocess and test
@@ -103,6 +97,7 @@ def fetch_tip(self, expression):
         else:
             return get_argspec(get_entity(expression))
 
+
 def get_entity(expression):
     """Return the object corresponding to expression evaluated
     in a namespace spanning sys.modules and __main.dict__.
@@ -126,7 +121,6 @@ def get_entity(expression):
 _invalid_method = "invalid method signature"
 _argument_positional = "\n['/' marks preceding arguments as positional-only]\n"
 
-
 def get_argspec(ob):
     '''Return a string describing the signature of a callable object, or ''.
 
diff --git a/Lib/idlelib/codecontext.py b/Lib/idlelib/codecontext.py
index 09dc078d63f..779b5b88cf3 100644
--- a/Lib/idlelib/codecontext.py
+++ b/Lib/idlelib/codecontext.py
@@ -1,4 +1,4 @@
-"""codecontext - Extension to display the block context above the edit window
+"""codecontext - display the block context above the edit window
 
 Once code has scrolled off the top of a window, it can be difficult to
 determine which block you are in.  This extension implements a pane at the top
@@ -25,10 +25,8 @@
 getspacesfirstword =\
                    lambda s, c=re.compile(r"^(\s*)(\w*)"): c.match(s).groups()
 
+
 class CodeContext:
-    menudefs = [('options', [('!Code Conte_xt', '<<toggle-code-context>>')])]
-    context_depth = idleConf.GetOption("extensions", "CodeContext",
-                                       "numlines", type="int", default=3)
     bgcolor = idleConf.GetOption("extensions", "CodeContext",
                                  "bgcolor", type="str", default="LightGray")
     fgcolor = idleConf.GetOption("extensions", "CodeContext",
@@ -45,15 +43,20 @@ def __init__(self, editwin):
         # starts the toplevel 'block' of the module.
         self.info = [(0, -1, "", False)]
         self.topvisible = 1
-        visible = idleConf.GetOption("extensions", "CodeContext",
-                                     "visible", type="bool", default=False)
-        if visible:
-            self.toggle_code_context_event()
-            self.editwin.setvar('<<toggle-code-context>>', True)
+        self.reload()
         # Start two update cycles, one for context lines, one for font changes.
         self.text.after(UPDATEINTERVAL, self.timer_event)
         self.text.after(FONTUPDATEINTERVAL, self.font_timer_event)
 
+    @classmethod
+    def reload(cls):
+        cls.context_depth = idleConf.GetOption("extensions", "CodeContext",
+                                       "numlines", type="int", default=3)
+        cls.bgcolor = idleConf.GetOption("extensions", "CodeContext",
+                                     "bgcolor", type="str", default="LightGray")
+        cls.fgcolor = idleConf.GetOption("extensions", "CodeContext",
+                                     "fgcolor", type="str", default="Black")
+
     def toggle_code_context_event(self, event=None):
         if not self.label:
             # Calculate the border width and horizontal padding required to
@@ -86,7 +89,7 @@ def toggle_code_context_event(self, event=None):
         else:
             self.label.destroy()
             self.label = None
-        idleConf.SetOption("extensions", "CodeContext", "visible",
+        idleConf.SetOption("main", "Theme", "contexton",
                            str(self.label is not None))
         idleConf.SaveUserCfgFiles()
         return "break"
@@ -177,3 +180,6 @@ def font_timer_event(self):
             self.textfont = newtextfont
             self.label["font"] = self.textfont
         self.text.after(FONTUPDATEINTERVAL, self.font_timer_event)
+
+
+CodeContext.reload()
diff --git a/Lib/idlelib/config-extensions.def b/Lib/idlelib/config-extensions.def
index a24b8c9316b..e8d417bac0d 100644
--- a/Lib/idlelib/config-extensions.def
+++ b/Lib/idlelib/config-extensions.def
@@ -1,5 +1,25 @@
 # config-extensions.def
 #
+# The following sections are for features that are no longer extensions.
+# Their options values are left here for back-compatibility.
+
+[AutoComplete]
+popupwait= 2000
+
+[CodeContext]
+numlines= 3
+visible= False
+bgcolor= LightGray
+fgcolor= Black
+
+[FormatParagraph]
+max-width= 72
+
+[ParenMatch]
+style= expression
+flash-delay= 500
+bell= True
+
 # IDLE reads several config files to determine user preferences.  This
 # file is the default configuration file for IDLE extensions settings.
 #
@@ -19,7 +39,7 @@
 # extension that may be sensibly re-configured.
 #
 # If there are no keybindings for a menus' virtual events, include lines
-# like <<toggle-code-context>>=  (See [CodeContext], below.)
+# like <<toggle-code-context>>=.
 #
 # Currently it is necessary to manually modify this file to change
 # extension key bindings and default values. To customize, create
@@ -32,68 +52,14 @@
 # See config-keys.def for notes on specifying keys and extend.txt for
 # information on creating IDLE extensions.
 
-[AutoComplete]
-enable=True
-popupwait=2000
-[AutoComplete_cfgBindings]
-force-open-completions=<Control-Key-space>
-[AutoComplete_bindings]
-autocomplete=<Key-Tab>
-try-open-completions=<KeyRelease-period> <KeyRelease-slash> <KeyRelease-backslash>
-
-[AutoExpand]
-enable=True
-[AutoExpand_cfgBindings]
-expand-word=<Alt-Key-slash>
-
-[CallTips]
-enable=True
-[CallTips_cfgBindings]
-force-open-calltip=<Control-Key-backslash>
-[CallTips_bindings]
-try-open-calltip=<KeyRelease-parenleft>
-refresh-calltip=<KeyRelease-parenright> <KeyRelease-0>
-
-[CodeContext]
-enable=True
-enable_shell=False
-numlines=3
-visible=False
-bgcolor=LightGray
-fgcolor=Black
-[CodeContext_bindings]
-toggle-code-context=
-
-[FormatParagraph]
-enable=True
-max-width=72
-[FormatParagraph_cfgBindings]
-format-paragraph=<Alt-Key-q>
-
-[ParenMatch]
-enable=True
-style= expression
-flash-delay= 500
-bell=True
-[ParenMatch_cfgBindings]
-flash-paren=<Control-Key-0>
-[ParenMatch_bindings]
-paren-closed=<KeyRelease-parenright> <KeyRelease-bracketright> <KeyRelease-braceright>
-
-[RstripExtension]
-enable=True
-enable_shell=False
-enable_editor=True
-
-[ScriptBinding]
-enable=True
-enable_shell=False
-enable_editor=True
-[ScriptBinding_cfgBindings]
-run-module=<Key-F5>
-check-module=<Alt-Key-x>
-
-[ZoomHeight]
-enable=True
-[ZoomHeight_cfgBindings]
-zoom-height=<Alt-Key-2>
+# A fake extension for testing and example purposes.  When enabled and
+# invoked, inserts or deletes z-text at beginning of every line.
+[ZzDummy]
+enable= True
+enable_shell = False
+enable_editor = True
+z-text= Z
+[ZzDummy_cfgBindings]
+z-in= <Control-Shift-KeyRelease-Insert>
+[ZzDummy_bindings]
+z-out= <Control-Shift-KeyRelease-Delete>
diff --git a/Lib/idlelib/config-keys.def b/Lib/idlelib/config-keys.def
index 64788f9adf0..fd235194dfc 100644
--- a/Lib/idlelib/config-keys.def
+++ b/Lib/idlelib/config-keys.def
@@ -57,6 +57,14 @@ toggle-tabs=<Alt-Key-t> <Meta-Key-t> <Alt-Key-T> <Meta-Key-T>
 change-indentwidth=<Alt-Key-u> <Meta-Key-u> <Alt-Key-U> <Meta-Key-U>
 del-word-left=<Control-Key-BackSpace>
 del-word-right=<Control-Key-Delete>
+force-open-completions= <Control-Key-space>
+expand-word= <Alt-Key-slash>
+force-open-calltip= <Control-Key-backslash>
+format-paragraph= <Alt-Key-q>
+flash-paren= <Control-Key-0>
+run-module= <Key-F5>
+check-module= <Alt-Key-x>
+zoom-height= <Alt-Key-2>
 
 [IDLE Classic Unix]
 copy=<Alt-Key-w> <Meta-Key-w>
@@ -108,6 +116,14 @@ toggle-tabs=<Alt-Key-t>
 change-indentwidth=<Alt-Key-u>
 del-word-left=<Alt-Key-BackSpace>
 del-word-right=<Alt-Key-d>
+force-open-completions= <Control-Key-space>
+expand-word= <Alt-Key-slash>
+force-open-calltip= <Control-Key-backslash>
+format-paragraph= <Alt-Key-q>
+flash-paren= <Control-Key-0>
+run-module= <Key-F5>
+check-module= <Alt-Key-x>
+zoom-height= <Alt-Key-2>
 
 [IDLE Modern Unix]
 copy = <Control-Shift-Key-C> <Control-Key-Insert>
@@ -159,6 +175,14 @@ toggle-tabs = <Control-Key-T>
 change-indentwidth = <Alt-Key-u>
 del-word-left = <Control-Key-BackSpace>
 del-word-right = <Control-Key-Delete>
+force-open-completions= <Control-Key-space>
+expand-word= <Alt-Key-slash>
+force-open-calltip= <Control-Key-backslash>
+format-paragraph= <Alt-Key-q>
+flash-paren= <Control-Key-0>
+run-module= <Key-F5>
+check-module= <Alt-Key-x>
+zoom-height= <Alt-Key-2>
 
 [IDLE Classic Mac]
 copy=<Command-Key-c>
@@ -210,6 +234,14 @@ toggle-tabs=<Control-Key-t>
 change-indentwidth=<Control-Key-u>
 del-word-left=<Control-Key-BackSpace>
 del-word-right=<Control-Key-Delete>
+force-open-completions= <Control-Key-space>
+expand-word= <Option-Key-slash>
+force-open-calltip= <Control-Key-backslash>
+format-paragraph= <Option-Key-q>
+flash-paren= <Control-Key-0>
+run-module= <Key-F5>
+check-module= <Option-Key-x>
+zoom-height= <Option-Key-0>
 
 [IDLE Classic OSX]
 toggle-tabs = <Control-Key-t>
@@ -262,4 +294,11 @@ python-context-help = <Shift-Key-F1>
 save-copy-of-window-as-file = <Option-Command-Key-s>
 open-window-from-file = <Command-Key-o>
 python-docs = <Key-F1>
-
+force-open-completions= <Control-Key-space>
+expand-word= <Option-Key-slash>
+force-open-calltip= <Control-Key-backslash>
+format-paragraph= <Option-Key-q>
+flash-paren= <Control-Key-0>
+run-module= <Key-F5>
+check-module= <Option-Key-x>
+zoom-height= <Option-Key-0>
diff --git a/Lib/idlelib/config.py b/Lib/idlelib/config.py
index 63d9a442345..c3e57bc692d 100644
--- a/Lib/idlelib/config.py
+++ b/Lib/idlelib/config.py
@@ -359,7 +359,8 @@ def GetThemeDict(self, type, themeName):
                 'stderr-foreground':'#000000',
                 'stderr-background':'#ffffff',
                 'console-foreground':'#000000',
-                'console-background':'#ffffff' }
+                'console-background':'#ffffff',
+                }
         for element in theme:
             if not cfgParser.has_option(themeName, element):
                 # Print warning that will return a default color
@@ -443,6 +444,11 @@ def GetExtensions(self, active_only=True,
         for extn in userExtns:
             if extn not in extns: #user has added own extension
                 extns.append(extn)
+        for extn in ('AutoComplete','CodeContext',
+                     'FormatParagraph','ParenMatch'):
+            extns.remove(extn)
+            # specific exclusions because we are storing config for mainlined old
+            # extensions in config-extensions.def for backward compatibility
         if active_only:
             activeExtns = []
             for extn in extns:
@@ -594,7 +600,12 @@ def IsCoreBinding(self, virtualEvent):
         return ('<<'+virtualEvent+'>>') in self.GetCoreKeys()
 
 # TODO make keyBindins a file or class attribute used for test above
-# and copied in function below
+# and copied in function below.
+
+    former_extension_events = {  #  Those with user-configurable keys.
+        '<<force-open-completions>>', '<<expand-word>>',
+        '<<force-open-calltip>>', '<<flash-paren>>', '<<format-paragraph>>',
+         '<<run-module>>', '<<check-module>>', '<<zoom-height>>'}
 
     def GetCoreKeys(self, keySetName=None):
         """Return dict of core virtual-key keybindings for keySetName.
@@ -654,8 +665,17 @@ def GetCoreKeys(self, keySetName=None):
             '<<toggle-tabs>>': ['<Alt-Key-t>'],
             '<<change-indentwidth>>': ['<Alt-Key-u>'],
             '<<del-word-left>>': ['<Control-Key-BackSpace>'],
-            '<<del-word-right>>': ['<Control-Key-Delete>']
+            '<<del-word-right>>': ['<Control-Key-Delete>'],
+            '<<force-open-completions>>': ['<Control-Key-space>'],
+            '<<expand-word>>': ['<Alt-Key-slash>'],
+            '<<force-open-calltip>>': ['<Control-Key-backslash>'],
+            '<<flash-paren>>': ['<Control-Key-0>'],
+            '<<format-paragraph>>': ['<Alt-Key-q>'],
+            '<<run-module>>': ['<Key-F5>'],
+            '<<check-module>>': ['<Alt-Key-x>'],
+            '<<zoom-height>>': ['<Alt-Key-2>'],
             }
+
         if keySetName:
             if not (self.userCfg['keys'].has_section(keySetName) or
                     self.defaultCfg['keys'].has_section(keySetName)):
@@ -670,7 +690,8 @@ def GetCoreKeys(self, keySetName=None):
                     binding = self.GetKeyBinding(keySetName, event)
                     if binding:
                         keyBindings[event] = binding
-                    else: #we are going to return a default, print warning
+                    # Otherwise return default in keyBindings.
+                    elif event not in self.former_extension_events:
                         warning = (
                             '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
                             ' problem retrieving key binding for event %r\n'
diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py
index 4ef6197e714..604719f0453 100644
--- a/Lib/idlelib/configdialog.py
+++ b/Lib/idlelib/configdialog.py
@@ -15,7 +15,7 @@
                      NONE, BOTH, X, Y, W, E, EW, NS, NSEW, NW,
                      HORIZONTAL, VERTICAL, ANCHOR, ACTIVE, END)
 from tkinter.ttk import (Button, Checkbutton, Entry, Frame, Label, LabelFrame,
-                         Notebook, Radiobutton, Scrollbar, Style)
+                         OptionMenu, Notebook, Radiobutton, Scrollbar, Style)
 import tkinter.colorchooser as tkColorChooser
 import tkinter.font as tkFont
 from tkinter import messagebox
@@ -198,7 +198,6 @@ def help(self):
 
     def deactivate_current_config(self):
         """Remove current key bindings.
-
         Iterate over window instances defined in parent and remove
         the keybindings.
         """
@@ -288,6 +287,7 @@ def load_extensions(self):
         "Fill self.extensions with data from the default and user configs."
         self.extensions = {}
         for ext_name in idleConf.GetExtensions(active_only=False):
+            # Former built-in extensions are already filtered out.
             self.extensions[ext_name] = []
 
         for ext_name in self.extensions:
@@ -796,7 +796,8 @@ def create_page_highlight(self):
                 takefocus=FALSE, highlightthickness=0, wrap=NONE)
         text.bind('<Double-Button-1>', lambda e: 'break')
         text.bind('<B1-Motion>', lambda e: 'break')
-        text_and_tags=(('\n', 'normal'),
+        text_and_tags=(
+            ('\n', 'normal'),
             ('#you can click here', 'comment'), ('\n', 'normal'),
             ('#to choose items', 'comment'), ('\n', 'normal'),
             ('def', 'keyword'), (' ', 'normal'),
@@ -858,11 +859,10 @@ def tem(event, elem=element):
                 frame_theme, text='Delete Custom Theme',
                 command=self.delete_custom)
         self.theme_message = Label(frame_theme, borderwidth=2)
-
         # Pack widgets:
         # body.
         frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
-        frame_theme.pack(side=LEFT, padx=5, pady=5, fill=Y)
+        frame_theme.pack(side=TOP, padx=5, pady=5, fill=X)
         # frame_custom.
         self.frame_color_set.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=X)
         frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
@@ -1764,15 +1764,30 @@ def create_page_general(self):
                     (*)helplist: ListBox
                     scroll_helplist: Scrollbar
         """
+        # Integer values need StringVar because int('') raises.
         self.startup_edit = tracers.add(
                 IntVar(self), ('main', 'General', 'editor-on-startup'))
-        self.autosave = tracers.add(
-                IntVar(self), ('main', 'General', 'autosave'))
         self.win_width = tracers.add(
                 StringVar(self), ('main', 'EditorWindow', 'width'))
         self.win_height = tracers.add(
                 StringVar(self), ('main', 'EditorWindow', 'height'))
+        self.autocomplete_wait = tracers.add(
+                StringVar(self), ('extensions', 'AutoComplete', 'popupwait'))
+        self.paren_style = tracers.add(
+                StringVar(self), ('extensions', 'ParenMatch', 'style'))
+        self.flash_delay = tracers.add(
+                StringVar(self), ('extensions', 'ParenMatch', 'flash-delay'))
+        self.paren_bell = tracers.add(
+                BooleanVar(self), ('extensions', 'ParenMatch', 'bell'))
+
+        self.autosave = tracers.add(
+                IntVar(self), ('main', 'General', 'autosave'))
+        self.format_width = tracers.add(
+                StringVar(self), ('extensions', 'FormatParagraph', 'max-width'))
+        self.context_lines = tracers.add(
+                StringVar(self), ('extensions', 'CodeContext', 'numlines'))
 
+        # Create widgets:
         # Section frames.
         frame_window = LabelFrame(self, borderwidth=2, relief=GROOVE,
                                   text=' Window Preferences')
@@ -1790,7 +1805,7 @@ def create_page_general(self):
                 frame_run, variable=self.startup_edit, value=0,
                 text='Open Shell Window')
 
-        frame_win_size = Frame(frame_window, borderwidth=0,)
+        frame_win_size = Frame(frame_window, borderwidth=0)
         win_size_title = Label(
                 frame_win_size, text='Initial Window Size  (in characters)')
         win_width_title = Label(frame_win_size, text='Width')
@@ -1800,6 +1815,26 @@ def create_page_general(self):
         self.win_height_int = Entry(
                 frame_win_size, textvariable=self.win_height, width=3)
 
+        frame_autocomplete = Frame(frame_window, borderwidth=0,)
+        auto_wait_title = Label(frame_autocomplete,
+                               text='Completions Popup Wait (milliseconds)')
+        self.auto_wait_int = Entry(frame_autocomplete, width=6,
+                                   textvariable=self.autocomplete_wait)
+
+        frame_paren1 = Frame(frame_window, borderwidth=0)
+        paren_style_title = Label(frame_paren1, text='Paren Match Style')
+        self.paren_style_type = OptionMenu(
+                frame_paren1, self.paren_style, 'expression',
+                "opener","parens","expression")
+        frame_paren2 = Frame(frame_window, borderwidth=0)
+        paren_time_title = Label(
+                frame_paren2, text='Time Match Displayed (milliseconds)\n'
+                                  '(0 is until next input)')
+        self.paren_flash_time = Entry(
+                frame_paren2, textvariable=self.flash_delay, width=6)
+        self.bell_on = Checkbutton(
+                frame_paren2, text="Bell on Mismatch", variable=self.paren_bell)
+
         # Frame_editor.
         frame_save = Frame(frame_editor, borderwidth=0)
         run_save_title = Label(frame_save, text='At Start of Run (F5)  ')
@@ -1810,6 +1845,18 @@ def create_page_general(self):
                 frame_save, variable=self.autosave, value=1,
                 text='No Prompt')
 
+        frame_format = Frame(frame_editor, borderwidth=0)
+        format_width_title = Label(frame_format,
+                                   text='Format Paragraph Max Width')
+        self.format_width_int = Entry(
+                frame_format, textvariable=self.format_width, width=4)
+
+        frame_context = Frame(frame_editor, borderwidth=0)
+        context_title = Label(frame_context, text='Context Lines :')
+        self.context_int = Entry(
+                frame_context, textvariable=self.context_lines, width=3)
+
+
         # frame_help.
         frame_helplist = Frame(frame_help)
         frame_helplist_buttons = Frame(frame_helplist)
@@ -1847,11 +1894,33 @@ def create_page_general(self):
         win_height_title.pack(side=RIGHT, anchor=E, pady=5)
         self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
         win_width_title.pack(side=RIGHT, anchor=E, pady=5)
+        # frame_autocomplete.
+        frame_autocomplete.pack(side=TOP, padx=5, pady=0, fill=X)
+        auto_wait_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+        self.auto_wait_int.pack(side=TOP, padx=10, pady=5)
+        # frame_paren.
+        frame_paren1.pack(side=TOP, padx=5, pady=0, fill=X)
+        paren_style_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+        self.paren_style_type.pack(side=TOP, padx=10, pady=5)
+        frame_paren2.pack(side=TOP, padx=5, pady=0, fill=X)
+        paren_time_title.pack(side=LEFT, anchor=W, padx=5)
+        self.bell_on.pack(side=RIGHT, anchor=E, padx=15, pady=5)
+        self.paren_flash_time.pack(side=TOP, anchor=W, padx=15, pady=5)
+
         # frame_save.
         frame_save.pack(side=TOP, padx=5, pady=0, fill=X)
         run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
         self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
         self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+        # frame_format.
+        frame_format.pack(side=TOP, padx=5, pady=0, fill=X)
+        format_width_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+        self.format_width_int.pack(side=TOP, padx=10, pady=5)
+        # frame_context.
+        frame_context.pack(side=TOP, padx=5, pady=0, fill=X)
+        context_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+        self.context_int.pack(side=TOP, padx=5, pady=5)
+
         # frame_help.
         frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
         frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
@@ -1863,17 +1932,30 @@ def create_page_general(self):
 
     def load_general_cfg(self):
         "Load current configuration settings for the general options."
-        # Set startup state.
+        # Set variables for all windows.
         self.startup_edit.set(idleConf.GetOption(
-                'main', 'General', 'editor-on-startup', default=0, type='bool'))
-        # Set autosave state.
-        self.autosave.set(idleConf.GetOption(
-                'main', 'General', 'autosave', default=0, type='bool'))
-        # Set initial window size.
+                'main', 'General', 'editor-on-startup', type='bool'))
         self.win_width.set(idleConf.GetOption(
                 'main', 'EditorWindow', 'width', type='int'))
         self.win_height.set(idleConf.GetOption(
                 'main', 'EditorWindow', 'height', type='int'))
+        self.autocomplete_wait.set(idleConf.GetOption(
+                'extensions', 'AutoComplete', 'popupwait', type='int'))
+        self.paren_style.set(idleConf.GetOption(
+                'extensions', 'ParenMatch', 'style'))
+        self.flash_delay.set(idleConf.GetOption(
+                'extensions', 'ParenMatch', 'flash-delay', type='int'))
+        self.paren_bell.set(idleConf.GetOption(
+                'extensions', 'ParenMatch', 'bell'))
+
+        # Set variables for editor windows.
+        self.autosave.set(idleConf.GetOption(
+                'main', 'General', 'autosave', default=0, type='bool'))
+        self.format_width.set(idleConf.GetOption(
+                'extensions', 'FormatParagraph', 'max-width', type='int'))
+        self.context_lines.set(idleConf.GetOption(
+                'extensions', 'CodeContext', 'numlines', type='int'))
+
         # Set additional help sources.
         self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
         self.helplist.delete(0, 'end')
@@ -2034,10 +2116,10 @@ def detach(self):
 be used with older IDLE releases if it is saved as a custom
 key set, with a different name.
 ''',
-    'Extensions': '''
-Extensions:
+     'General': '''
+General:
 
-Autocomplete: Popupwait is milleseconds to wait after key char, without
+AutoComplete: Popupwait is milleseconds to wait after key char, without
 cursor movement, before popping up completion box.  Key char is '.' after
 identifier or a '/' (or '\\' on Windows) within a string.
 
diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py
index 43b105f7265..855d3750556 100644
--- a/Lib/idlelib/editor.py
+++ b/Lib/idlelib/editor.py
@@ -31,7 +31,6 @@
 TK_TABWIDTH_DEFAULT = 8
 _py_version = ' (%s)' % platform.python_version()
 
-
 def _sphinx_version():
     "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
     major, minor, micro, level, serial = sys.version_info
@@ -52,11 +51,22 @@ class EditorWindow(object):
     from idlelib import mainmenu
     from tkinter import Toplevel
     from idlelib.statusbar import MultiStatusBar
+    from idlelib.autocomplete import AutoComplete
+    from idlelib.autoexpand import AutoExpand
+    from idlelib.calltips import CallTips
+    from idlelib.codecontext import CodeContext
+    from idlelib.paragraph import FormatParagraph
+    from idlelib.parenmatch import ParenMatch
+    from idlelib.rstrip import RstripExtension
+    from idlelib.zoomheight import ZoomHeight
 
     filesystemencoding = sys.getfilesystemencoding()  # for file names
     help_url = None
 
     def __init__(self, flist=None, filename=None, key=None, root=None):
+        # Delay import: runscript imports pyshell imports EditorWindow.
+        from idlelib.runscript import ScriptBinding
+
         if EditorWindow.help_url is None:
             dochome =  os.path.join(sys.base_prefix, 'Doc', 'index.html')
             if sys.platform.count('linux'):
@@ -84,7 +94,8 @@ def __init__(self, flist=None, filename=None, key=None, root=None):
                     # Safari requires real file:-URLs
                     EditorWindow.help_url = 'file://' + EditorWindow.help_url
             else:
-                EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
+                EditorWindow.help_url = ("https://docs.python.org/%d.%d/"
+                                         % sys.version_info[:2])
         self.flist = flist
         root = root or flist.root
         self.root = root
@@ -270,6 +281,43 @@ def __init__(self, flist=None, filename=None, key=None, root=None):
         self.askinteger = tkSimpleDialog.askinteger
         self.showerror = tkMessageBox.showerror
 
+        # Add pseudoevents for former extension fixed keys.
+        # (This probably needs to be done once in the process.)
+        text.event_add('<<autocomplete>>', '<Key-Tab>')
+        text.event_add('<<try-open-completions>>', '<KeyRelease-period>',
+                       '<KeyRelease-slash>', '<KeyRelease-backslash>')
+        text.event_add('<<try-open-calltip>>', '<KeyRelease-parenleft>')
+        text.event_add('<<refresh-calltip>>', '<KeyRelease-parenright>')
+        text.event_add('<<paren-closed>>', '<KeyRelease-parenright>',
+                       '<KeyRelease-bracketright>', '<KeyRelease-braceright>')
+
+        # Former extension bindings depends on frame.text being packed
+        # (called from self.ResetColorizer()).
+        autocomplete = self.AutoComplete(self)
+        text.bind("<<autocomplete>>", autocomplete.autocomplete_event)
+        text.bind("<<try-open-completions>>",
+                  autocomplete.try_open_completions_event)
+        text.bind("<<force-open-completions>>",
+                  autocomplete.force_open_completions_event)
+        text.bind("<<expand-word>>", self.AutoExpand(self).expand_word_event)
+        text.bind("<<format-paragraph>>",
+                  self.FormatParagraph(self).format_paragraph_event)
+        parenmatch = self.ParenMatch(self)
+        text.bind("<<flash-paren>>", parenmatch.flash_paren_event)
+        text.bind("<<paren-closed>>", parenmatch.paren_closed_event)
+        scriptbinding = ScriptBinding(self)
+        text.bind("<<check-module>>", scriptbinding.check_module_event)
+        text.bind("<<run-module>>", scriptbinding.run_module_event)
+        text.bind("<<do-rstrip>>", self.RstripExtension(self).do_rstrip)
+        calltips = self.CallTips(self)
+        text.bind("<<try-open-calltip>>", calltips.try_open_calltip_event)
+        #refresh-calltips must come after paren-closed to work right
+        text.bind("<<refresh-calltip>>", calltips.refresh_calltip_event)
+        text.bind("<<force-open-calltip>>", calltips.force_open_calltip_event)
+        text.bind("<<zoom-height>>", self.ZoomHeight(self).zoom_height_event)
+        text.bind("<<toggle-code-context>>",
+                  self.CodeContext(self).toggle_code_context_event)
+
     def _filename_to_unicode(self, filename):
         """Return filename as BMP unicode so diplayable in Tk."""
         # Decode bytes to unicode.
@@ -981,16 +1029,8 @@ def load_standard_extensions(self):
     def get_standard_extension_names(self):
         return idleConf.GetExtensions(editor_only=True)
 
-    extfiles = {  # map config-extension section names to new file names
-        'AutoComplete': 'autocomplete',
-        'AutoExpand': 'autoexpand',
-        'CallTips': 'calltips',
-        'CodeContext': 'codecontext',
-        'FormatParagraph': 'paragraph',
-        'ParenMatch': 'parenmatch',
-        'RstripExtension': 'rstrip',
-        'ScriptBinding': 'runscript',
-        'ZoomHeight': 'zoomheight',
+    extfiles = {  # Map built-in config-extension section names to file names.
+        'ZzDummy': 'zzdummy',
         }
 
     def load_extension(self, name):
diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py
index bbf06504fc2..84b45a63767 100644
--- a/Lib/idlelib/idle_test/test_config.py
+++ b/Lib/idlelib/idle_test/test_config.py
@@ -437,78 +437,57 @@ def test_get_extensions(self):
 
         eq = self.assertEqual
         eq(conf.GetExtensions(),
-           ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext',
-            'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding',
-            'ZoomHeight'])
+           ['ZzDummy'])
         eq(conf.GetExtensions(active_only=False),
-            ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext',
-             'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding',
-             'ZoomHeight', 'DISABLE'])
+            ['ZzDummy', 'DISABLE'])
         eq(conf.GetExtensions(editor_only=True),
-           ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext',
-            'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding',
-            'ZoomHeight'])
+           ['ZzDummy'])
         eq(conf.GetExtensions(shell_only=True),
-           ['AutoComplete', 'AutoExpand', 'CallTips', 'FormatParagraph',
-            'ParenMatch', 'ZoomHeight'])
+           [])
         eq(conf.GetExtensions(active_only=False, editor_only=True),
-           ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext',
-            'FormatParagraph', 'ParenMatch', 'RstripExtension',
-            'ScriptBinding', 'ZoomHeight', 'DISABLE'])
-        eq(conf.GetExtensions(active_only=False, shell_only=True),
-           ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext',
-            'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding',
-            'ZoomHeight', 'DISABLE'])
+           ['ZzDummy', 'DISABLE'])
 
         # Add user extensions
         conf.SetOption('extensions', 'Foobar', 'enable', 'True')
         eq(conf.GetExtensions(),
-           ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext',
-            'FormatParagraph', 'ParenMatch', 'RstripExtension',
-            'ScriptBinding', 'ZoomHeight', 'Foobar'])  # User extensions didn't sort
+           ['ZzDummy', 'Foobar'])  # User extensions didn't sort
         eq(conf.GetExtensions(active_only=False),
-           ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext',
-            'FormatParagraph', 'ParenMatch', 'RstripExtension',
-            'ScriptBinding', 'ZoomHeight', 'DISABLE', 'Foobar'])
+           ['ZzDummy', 'DISABLE', 'Foobar'])
 
     def test_remove_key_bind_names(self):
         conf = self.mock_config()
 
         self.assertCountEqual(
             conf.RemoveKeyBindNames(conf.GetSectionList('default', 'extensions')),
-            ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext',
-             'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding',
-             'ZoomHeight'])
+            ['AutoComplete', 'CodeContext', 'FormatParagraph', 'ParenMatch','ZzDummy'])
 
     def test_get_extn_name_for_event(self):
         conf = self.mock_config()
 
         eq = self.assertEqual
-        eq(conf.GetExtnNameForEvent('force-open-completions'), 'AutoComplete')
-        eq(conf.GetExtnNameForEvent('expand-word'), 'AutoExpand')
-        eq(conf.GetExtnNameForEvent('force-open-calltip'), 'CallTips')
-        eq(conf.GetExtnNameForEvent('zoom-height'), 'ZoomHeight')
+        eq(conf.GetExtnNameForEvent('z-in'), 'ZzDummy')
+        eq(conf.GetExtnNameForEvent('z-out'), None)
 
     def test_get_extension_keys(self):
         conf = self.mock_config()
 
         eq = self.assertEqual
-        eq(conf.GetExtensionKeys('AutoComplete'),
-           {'<<force-open-completions>>': ['<Control-Key-space>']})
-        eq(conf.GetExtensionKeys('ParenMatch'),
-           {'<<flash-paren>>': ['<Control-Key-0>']})
-
-        key = ['<Option-Key-2>'] if sys.platform == 'darwin' else ['<Alt-Key-2>']
-        eq(conf.GetExtensionKeys('ZoomHeight'), {'<<zoom-height>>': key})
+        eq(conf.GetExtensionKeys('ZzDummy'),
+           {'<<z-in>>': ['<Control-Shift-KeyRelease-Insert>']})
+# need option key test
+##        key = ['<Option-Key-2>'] if sys.platform == 'darwin' else ['<Alt-Key-2>']
+##        eq(conf.GetExtensionKeys('ZoomHeight'), {'<<zoom-height>>': key})
 
     def test_get_extension_bindings(self):
         conf = self.mock_config()
 
         self.assertEqual(conf.GetExtensionBindings('NotExists'), {})
 
-        key = ['<Option-Key-2>'] if sys.platform == 'darwin' else ['<Alt-Key-2>']
+        #key = ['<Option-Key-2>'] if sys.platform == 'darwin' else ['<Alt-Key-2>']
+        expect = {'<<z-in>>': ['<Control-Shift-KeyRelease-Insert>'],
+                  '<<z-out>>': ['<Control-Shift-KeyRelease-Delete>']}
         self.assertEqual(
-            conf.GetExtensionBindings('ZoomHeight'), {'<<zoom-height>>': key})
+            conf.GetExtensionBindings('ZzDummy'), expect)
 
         # Add non-configuarable bindings
         conf.defaultCfg['extensions'].add_section('Foobar')
@@ -542,9 +521,11 @@ def test_get_current_keyset(self):
         sys.platform = 'some-linux'
         self.assertEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys()))
 
-        # This should not be the same, sicne replace <Alt- to <Option-
-        sys.platform = 'darwin'
-        self.assertNotEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys()))
+        # This should not be the same, since replace <Alt- to <Option-.
+        # Above depended on config-extensions.def having Alt keys,
+        # which is no longer true.
+        # sys.platform = 'darwin'
+        # self.assertNotEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys()))
 
         # Restore platform
         sys.platform = current_platform
diff --git a/Lib/idlelib/idle_test/test_configdialog.py b/Lib/idlelib/idle_test/test_configdialog.py
index f116538faca..3d6a858e6fd 100644
--- a/Lib/idlelib/idle_test/test_configdialog.py
+++ b/Lib/idlelib/idle_test/test_configdialog.py
@@ -824,19 +824,20 @@ def test_keybinding(self):
         d.custom_name.set('my custom keys')
         d.bindingslist.delete(0, 'end')
         d.bindingslist.insert(0, 'copy')
-        d.bindingslist.insert(1, 'expand-word')
+        d.bindingslist.insert(1, 'z-in')
         d.bindingslist.selection_set(0)
         d.bindingslist.selection_anchor(0)
         # Core binding - adds to keys.
         d.keybinding.set('<Key-F11>')
         self.assertEqual(keyspage,
                          {'my custom keys': {'copy': '<Key-F11>'}})
+
         # Not a core binding - adds to extensions.
         d.bindingslist.selection_set(1)
         d.bindingslist.selection_anchor(1)
         d.keybinding.set('<Key-F11>')
         self.assertEqual(extpage,
-                         {'AutoExpand_cfgBindings': {'expand-word': '<Key-F11>'}})
+                         {'ZzDummy_cfgBindings': {'z-in': '<Key-F11>'}})
 
     def test_set_keys_type(self):
         eq = self.assertEqual
@@ -1125,13 +1126,6 @@ def test_startup(self):
         self.assertEqual(mainpage,
                          {'General': {'editor-on-startup': '0'}})
 
-    def test_autosave(self):
-        d = self.page
-        d.save_auto_on.invoke()
-        self.assertEqual(mainpage, {'General': {'autosave': '1'}})
-        d.save_ask_on.invoke()
-        self.assertEqual(mainpage, {'General': {'autosave': '0'}})
-
     def test_editor_size(self):
         d = self.page
         d.win_height_int.insert(0, '1')
@@ -1140,6 +1134,37 @@ def test_editor_size(self):
         d.win_width_int.insert(0, '1')
         self.assertEqual(mainpage, {'EditorWindow': {'width': '180'}})
 
+    def test_autocomplete_wait(self):
+        self.page.auto_wait_int.insert(0, '1')
+        self.assertEqual(extpage, {'AutoComplete': {'popupwait': '12000'}})
+
+    def test_parenmatch(self):
+        d = self.page
+        eq = self.assertEqual
+        d.paren_style_type['menu'].invoke(0)
+        eq(extpage, {'ParenMatch': {'style': 'opener'}})
+        changes.clear()
+        d.paren_flash_time.insert(0, '2')
+        eq(extpage, {'ParenMatch': {'flash-delay': '2500'}})
+        changes.clear()
+        d.bell_on.invoke()
+        eq(extpage, {'ParenMatch': {'bell': 'False'}})
+
+    def test_autosave(self):
+        d = self.page
+        d.save_auto_on.invoke()
+        self.assertEqual(mainpage, {'General': {'autosave': '1'}})
+        d.save_ask_on.invoke()
+        self.assertEqual(mainpage, {'General': {'autosave': '0'}})
+
+    def test_paragraph(self):
+        self.page.format_width_int.insert(0, '1')
+        self.assertEqual(extpage, {'FormatParagraph': {'max-width': '172'}})
+
+    def test_context(self):
+        self.page.context_int.insert(0, '1')
+        self.assertEqual(extpage, {'CodeContext': {'numlines': '13'}})
+
     def test_source_selected(self):
         d = self.page
         d.set = d.set_add_delete_state
diff --git a/Lib/idlelib/mainmenu.py b/Lib/idlelib/mainmenu.py
index 65345cd1f16..d1dcb83d932 100644
--- a/Lib/idlelib/mainmenu.py
+++ b/Lib/idlelib/mainmenu.py
@@ -52,6 +52,11 @@
    ('Find in Files...', '<<find-in-files>>'),
    ('R_eplace...', '<<replace>>'),
    ('Go to _Line', '<<goto-line>>'),
+   ('S_how Completions', '<<force-open-completions>>'),
+   ('E_xpand Word', '<<expand-word>>'),
+   ('Show C_all Tip', '<<force-open-calltip>>'),
+   ('Show Surrounding P_arens', '<<flash-paren>>'),
+
   ]),
 ('format', [
    ('_Indent Region', '<<indent-region>>'),
@@ -62,9 +67,13 @@
    ('Untabify Region', '<<untabify-region>>'),
    ('Toggle Tabs', '<<toggle-tabs>>'),
    ('New Indent Width', '<<change-indentwidth>>'),
+   ('F_ormat Paragraph', '<<format-paragraph>>'),
+   ('S_trip Trailing Whitespace', '<<do-rstrip>>'),
    ]),
  ('run', [
    ('Python Shell', '<<open-python-shell>>'),
+   ('C_heck Module', '<<check-module>>'),
+   ('R_un Module', '<<run-module>>'),
    ]),
  ('shell', [
    ('_View Last Restart', '<<view-restart>>'),
@@ -80,7 +89,10 @@
    ]),
  ('options', [
    ('Configure _IDLE', '<<open-config-dialog>>'),
-   None,
+   ('_Code Context', '<<toggle-code-context>>'),
+   ]),
+ ('windows', [
+   ('Zoom Height', '<<zoom-height>>'),
    ]),
  ('help', [
    ('_About IDLE', '<<about-idle>>'),
diff --git a/Lib/idlelib/outwin.py b/Lib/idlelib/outwin.py
index 5f7c09fb92c..6c2a792d86b 100644
--- a/Lib/idlelib/outwin.py
+++ b/Lib/idlelib/outwin.py
@@ -77,6 +77,7 @@ class OutputWindow(EditorWindow):
     def __init__(self, *args):
         EditorWindow.__init__(self, *args)
         self.text.bind("<<goto-file-line>>", self.goto_file_line)
+        self.text.unbind("<<toggle-code-context>>")
 
     # Customize EditorWindow
     def ispythonsource(self, filename):
diff --git a/Lib/idlelib/paragraph.py b/Lib/idlelib/paragraph.py
index f11bdaeb77a..cf8dfdb641f 100644
--- a/Lib/idlelib/paragraph.py
+++ b/Lib/idlelib/paragraph.py
@@ -1,4 +1,4 @@
-"""Extension to format a paragraph or selection to a max width.
+"""Format a paragraph, comment block, or selection to a max width.
 
 Does basic, standard text formatting, and also understands Python
 comment blocks. Thus, for editing Python source code, this
@@ -21,15 +21,14 @@
 
 class FormatParagraph:
 
-    menudefs = [
-        ('format', [   # /s/edit/format   dscherer at cmu.edu
-            ('Format Paragraph', '<<format-paragraph>>'),
-         ])
-    ]
-
     def __init__(self, editwin):
         self.editwin = editwin
 
+    @classmethod
+    def reload(cls):
+        cls.max_width = idleConf.GetOption('extensions', 'FormatParagraph',
+                                           'max-width', type='int', default=72)
+
     def close(self):
         self.editwin = None
 
@@ -45,11 +44,7 @@ def format_paragraph_event(self, event, limit=None):
 
         The length limit parameter is for testing with a known value.
         """
-        if limit is None:
-            # The default length limit is that defined by pep8
-            limit = idleConf.GetOption(
-                'extensions', 'FormatParagraph', 'max-width',
-                type='int', default=72)
+        limit = self.max_width if limit is None else limit
         text = self.editwin.text
         first, last = self.editwin.get_selection_indices()
         if first and last:
@@ -75,6 +70,9 @@ def format_paragraph_event(self, event, limit=None):
         text.see("insert")
         return "break"
 
+
+FormatParagraph.reload()
+
 def find_paragraph(text, mark):
     """Returns the start/stop indices enclosing the paragraph that mark is in.
 
diff --git a/Lib/idlelib/parenmatch.py b/Lib/idlelib/parenmatch.py
index 7f25880ae5e..12212150c6d 100644
--- a/Lib/idlelib/parenmatch.py
+++ b/Lib/idlelib/parenmatch.py
@@ -1,4 +1,4 @@
-"""ParenMatch -- An IDLE extension for parenthesis matching.
+"""ParenMatch -- for parenthesis matching.
 
 When you hit a right paren, the cursor should move briefly to the left
 paren.  Paren here is used generically; the matching applies to
@@ -30,18 +30,6 @@ class ParenMatch:
     - Highlight when cursor is moved to the right of a closer.
       This might be too expensive to check.
     """
-    menudefs = [
-        ('edit', [
-            ("Show surrounding parens", "<<flash-paren>>"),
-        ])
-    ]
-    STYLE = idleConf.GetOption(
-            'extensions','ParenMatch','style', default='expression')
-    FLASH_DELAY = idleConf.GetOption(
-            'extensions','ParenMatch','flash-delay', type='int',default=500)
-    BELL = idleConf.GetOption(
-            'extensions','ParenMatch','bell', type='bool',default=1)
-    HILITE_CONFIG = idleConf.GetHighlight(idleConf.CurrentTheme(),'hilite')
 
     RESTORE_VIRTUAL_EVENT_NAME = "<<parenmatch-check-restore>>"
     # We want the restore event be called before the usual return and
@@ -62,6 +50,17 @@ def __init__(self, editwin):
         self.is_restore_active = 0
         self.set_style(self.STYLE)
 
+    @classmethod
+    def reload(cls):
+        cls.STYLE = idleConf.GetOption(
+            'extensions','ParenMatch','style', default='opener')
+        cls.FLASH_DELAY = idleConf.GetOption(
+                'extensions','ParenMatch','flash-delay', type='int',default=500)
+        cls.BELL = idleConf.GetOption(
+                'extensions','ParenMatch','bell', type='bool', default=1)
+        cls.HILITE_CONFIG = idleConf.GetHighlight(idleConf.CurrentTheme(),
+                                                  'hilite')
+
     def activate_restore(self):
         "Activate mechanism to restore text from highlighting."
         if not self.is_restore_active:
@@ -181,6 +180,9 @@ def set_timeout_last(self):
             lambda self=self, c=self.counter: self.handle_restore_timer(c))
 
 
+ParenMatch.reload()
+
+
 if __name__ == '__main__':
     import unittest
     unittest.main('idlelib.idle_test.test_parenmatch', verbosity=2)
diff --git a/Lib/idlelib/rstrip.py b/Lib/idlelib/rstrip.py
index 2ce3c7eafe5..18c86f9b2c8 100644
--- a/Lib/idlelib/rstrip.py
+++ b/Lib/idlelib/rstrip.py
@@ -2,12 +2,8 @@
 
 class RstripExtension:
 
-    menudefs = [
-        ('format', [None, ('Strip trailing whitespace', '<<do-rstrip>>'), ] ), ]
-
     def __init__(self, editwin):
         self.editwin = editwin
-        self.editwin.text.bind("<<do-rstrip>>", self.do_rstrip)
 
     def do_rstrip(self, event=None):
 
diff --git a/Lib/idlelib/runscript.py b/Lib/idlelib/runscript.py
index 3355f17d90b..45bf5634582 100644
--- a/Lib/idlelib/runscript.py
+++ b/Lib/idlelib/runscript.py
@@ -1,22 +1,14 @@
-"""Extension to execute code outside the Python shell window.
+"""Execute code from an editor.
 
-This adds the following commands:
+Check module: do a full syntax check of the current module.
+Also run the tabnanny to catch any inconsistent tabs.
 
-- Check module does a full syntax check of the current module.
-  It also runs the tabnanny to catch any inconsistent tabs.
-
-- Run module executes the module's code in the __main__ namespace.  The window
-  must have been saved previously. The module is added to sys.modules, and is
-  also added to the __main__ namespace.
-
-XXX GvR Redesign this interface (yet again) as follows:
-
-- Present a dialog box for ``Run Module''
-
-- Allow specify command line arguments in the dialog box
+Run module: also execute the module's code in the __main__ namespace.
+The window must have been saved previously. The module is added to
+sys.modules, and is also added to the __main__ namespace.
 
+TODO: Specify command line arguments in a dialog box.
 """
-
 import os
 import tabnanny
 import tokenize
@@ -40,11 +32,6 @@
 
 class ScriptBinding:
 
-    menudefs = [
-        ('run', [None,
-                 ('Check Module', '<<check-module>>'),
-                 ('Run Module', '<<run-module>>'), ]), ]
-
     def __init__(self, editwin):
         self.editwin = editwin
         # Provide instance variables referenced by debugger
diff --git a/Lib/idlelib/zoomheight.py b/Lib/idlelib/zoomheight.py
index d01c9e964aa..74fbc888a80 100644
--- a/Lib/idlelib/zoomheight.py
+++ b/Lib/idlelib/zoomheight.py
@@ -1,4 +1,4 @@
-# Sample extension: zoom a window to maximum height
+"Zoom a window to maximum height."
 
 import re
 import sys
@@ -8,12 +8,6 @@
 
 class ZoomHeight:
 
-    menudefs = [
-        ('windows', [
-            ('_Zoom Height', '<<zoom-height>>'),
-         ])
-    ]
-
     def __init__(self, editwin):
         self.editwin = editwin
 
diff --git a/Lib/idlelib/zzdummy.py b/Lib/idlelib/zzdummy.py
new file mode 100644
index 00000000000..80844996466
--- /dev/null
+++ b/Lib/idlelib/zzdummy.py
@@ -0,0 +1,42 @@
+"Example extension, also used for testing."
+
+from idlelib.config import idleConf
+
+ztext = idleConf.GetOption('extensions', 'ZzDummy', 'z-text')
+
+
+class ZzDummy:
+
+##    menudefs = [
+##        ('format', [
+##            ('Z in', '<<z-in>>'),
+##            ('Z out', '<<z-out>>'),
+##        ] )
+##    ]
+
+    def __init__(self, editwin):
+        self.text = editwin.text
+        z_in = False
+
+    @classmethod
+    def reload(cls):
+        cls.ztext = idleConf.GetOption('extensions', 'ZzDummy', 'z-text')
+
+    def z_in_event(self, event):
+        """
+        """
+        text = self.text
+        text.undo_block_start()
+        for line in range(1, text.index('end')):
+            text.insert('%d.0', ztest)
+        text.undo_block_stop()
+        return "break"
+
+    def z_out_event(self, event): pass
+
+ZzDummy.reload()
+
+##if __name__ == "__main__":
+##    import unittest
+##    unittest.main('idlelib.idle_test.test_zzdummy',
+##            verbosity=2, exit=False)
diff --git a/Misc/NEWS.d/next/IDLE/2017-08-24-13-48-16.bpo-27099.rENefC.rst b/Misc/NEWS.d/next/IDLE/2017-08-24-13-48-16.bpo-27099.rENefC.rst
new file mode 100644
index 00000000000..9b59fbabe53
--- /dev/null
+++ b/Misc/NEWS.d/next/IDLE/2017-08-24-13-48-16.bpo-27099.rENefC.rst
@@ -0,0 +1,20 @@
+Convert IDLE's built-in 'extensions' to regular features.
+
+About 10 IDLE features were implemented as supposedly optional
+extensions.  Their different behavior could be confusing or worse for
+users and not good for maintenance.  Hence the conversion.
+
+The main difference for users is that user configurable key bindings
+for builtin features are now handled uniformly.  Now, editing a binding
+in a keyset only affects its value in the keyset.  All bindings are
+defined together in the system-specific default keysets in config-
+extensions.def.  All custom keysets are saved as a whole in config-
+extension.cfg.  All take effect as soon as one clicks Apply or Ok.
+
+The affected events are '<<force-open-completions>>', '<<expand-word>>',
+'<<force-open-calltip>>', '<<flash-paren>>', '<<format-paragraph>>',
+'<<run-module>>', '<<check-module>>', and '<<zoom-height>>'.  Any
+(global) customizations made before 3.6.3 will not affect their keyset-
+specific customization after 3.6.3. and vice versa.
+
+Inital patch by Charles Wohlganger.



More information about the Python-checkins mailing list