[Python-checkins] cpython: Issue #27173: Add 'IDLE Modern Unix' to the built-in key sets.

terry.reedy python-checkins at python.org
Sun Jul 10 13:46:45 EDT 2016


https://hg.python.org/cpython/rev/e6e6c71776b0
changeset:   102300:e6e6c71776b0
user:        Terry Jan Reedy <tjreedy at udel.edu>
date:        Sun Jul 10 13:46:34 2016 -0400
summary:
  Issue #27173: Add 'IDLE Modern Unix' to the built-in key sets.
Make the default key set depend on the platform.
Add tests for changes to the config module.

files:
  Lib/idlelib/config-keys.def          |   51 +++++
  Lib/idlelib/config-main.def          |    4 +-
  Lib/idlelib/config.py                |  130 +++++++++-----
  Lib/idlelib/configdialog.py          |   44 ++++-
  Lib/idlelib/idle_test/test_config.py |   98 +++++++++++
  5 files changed, 270 insertions(+), 57 deletions(-)


diff --git a/Lib/idlelib/config-keys.def b/Lib/idlelib/config-keys.def
--- a/Lib/idlelib/config-keys.def
+++ b/Lib/idlelib/config-keys.def
@@ -109,6 +109,57 @@
 del-word-left=<Alt-Key-BackSpace>
 del-word-right=<Alt-Key-d>
 
+[IDLE Modern Unix]
+copy = <Control-Shift-Key-C> <Control-Key-Insert>
+cut = <Control-Key-x> <Shift-Key-Delete>
+paste = <Control-Key-v> <Shift-Key-Insert>
+beginning-of-line = <Key-Home>
+center-insert = <Control-Key-l>
+close-all-windows = <Control-Key-q>
+close-window = <Control-Key-w> <Control-Shift-Key-W>
+do-nothing = <Control-Key-F12>
+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-b>
+open-module = <Control-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>
+python-context-help = <Shift-Key-F1>
+python-docs = <Key-F1>
+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 = <Key-F3>
+find-in-files = <Control-Shift-Key-f>
+find-selection = <Control-Key-h>
+replace = <Control-Key-r>
+goto-line = <Control-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 = <Control-Key-d>
+uncomment-region = <Control-Shift-Key-D>
+tabify-region = <Alt-Key-5>
+untabify-region = <Alt-Key-6>
+toggle-tabs = <Control-Key-T>
+change-indentwidth = <Alt-Key-u>
+del-word-left = <Control-Key-BackSpace>
+del-word-right = <Control-Key-Delete>
+
 [IDLE Classic Mac]
 copy=<Command-Key-c>
 cut=<Command-Key-x>
diff --git a/Lib/idlelib/config-main.def b/Lib/idlelib/config-main.def
--- a/Lib/idlelib/config-main.def
+++ b/Lib/idlelib/config-main.def
@@ -70,7 +70,9 @@
 
 [Keys]
 default= 1
-name= IDLE Classic Windows
+name=
+name2=
+# name2 set in user config-main.cfg for keys added after 2016 July 1
 
 [History]
 cyclic=1
diff --git a/Lib/idlelib/config.py b/Lib/idlelib/config.py
--- a/Lib/idlelib/config.py
+++ b/Lib/idlelib/config.py
@@ -234,10 +234,7 @@
                        ' from section %r: %r' %
                        (type, option, section,
                        self.userCfg[configType].Get(section, option, raw=raw)))
-            try:
-                print(warning, file=sys.stderr)
-            except OSError:
-                pass
+            _warn(warning, configType, section, option)
         try:
             if self.defaultCfg[configType].has_option(section,option):
                 return self.defaultCfg[configType].Get(
@@ -251,10 +248,7 @@
                        ' from section %r.\n'
                        ' returning default value: %r' %
                        (option, section, default))
-            try:
-                print(warning, file=sys.stderr)
-            except OSError:
-                pass
+            _warn(warning, configType, section, option)
         return default
 
     def SetOption(self, configType, section, option, value):
@@ -362,47 +356,68 @@
                            '\n from theme %r.\n'
                            ' returning default color: %r' %
                            (element, themeName, theme[element]))
-                try:
-                    print(warning, file=sys.stderr)
-                except OSError:
-                    pass
+                _warn(warning, 'highlight', themeName, element)
             theme[element] = cfgParser.Get(
                     themeName, element, default=theme[element])
         return theme
 
     def CurrentTheme(self):
-        """Return the name of the currently active text color theme.
+        "Return the name of the currently active text color theme."
+        return self.current_colors_and_keys('Theme')
 
-        idlelib.config-main.def includes this section
+    def CurrentKeys(self):
+        """Return the name of the currently active key set."""
+        return self.current_colors_and_keys('Keys')
+
+    def current_colors_and_keys(self, section):
+        """Return the currently active name for Theme or Keys section.
+
+        idlelib.config-main.def ('default') includes these sections
+
         [Theme]
         default= 1
         name= IDLE Classic
         name2=
-        # name2 set in user config-main.cfg for themes added after 2015 Oct 1
 
-        Item name2 is needed because setting name to a new builtin
-        causes older IDLEs to display multiple error messages or quit.
+        [Keys]
+        default= 1
+        name=
+        name2=
+
+        Item 'name2', is used for built-in ('default') themes and keys
+        added after 2015 Oct 1 and 2016 July 1.  This kludge is needed
+        because setting 'name' to a builtin not defined in older IDLEs
+        to display multiple error messages or quit.
         See https://bugs.python.org/issue25313.
-        When default = True, name2 takes precedence over name,
-        while older IDLEs will just use name.
+        When default = True, 'name2' takes precedence over 'name',
+        while older IDLEs will just use name.  When default = False,
+        'name2' may still be set, but it is ignored.
         """
+        cfgname = 'highlight' if section == 'Theme' else 'keys'
         default = self.GetOption('main', 'Theme', 'default',
                                  type='bool', default=True)
+        name = ''
         if default:
-            theme = self.GetOption('main', 'Theme', 'name2', default='')
-        if default and not theme or not default:
-            theme = self.GetOption('main', 'Theme', 'name', default='')
-        source = self.defaultCfg if default else self.userCfg
-        if source['highlight'].has_section(theme):
-            return theme
+            name = self.GetOption('main', section, 'name2', default='')
+        if not name:
+            name = self.GetOption('main', section, 'name', default='')
+        if name:
+            source = self.defaultCfg if default else self.userCfg
+            if source[cfgname].has_section(name):
+                return name
+        return "IDLE Classic" if section == 'Theme' else self.default_keys()
+
+    @staticmethod
+    def default_keys():
+        if sys.platform[:3] == 'win':
+            return 'IDLE Classic Windows'
+        elif sys.platform == 'darwin':
+            return 'IDLE Classic OSX'
         else:
-            return "IDLE Classic"
+            return 'IDLE Modern Unix'
 
-    def CurrentKeys(self):
-        "Return the name of the currently active key set."
-        return self.GetOption('main', 'Keys', 'name', default='')
-
-    def GetExtensions(self, active_only=True, editor_only=False, shell_only=False):
+    def GetExtensions(self, active_only=True,
+                      editor_only=False, shell_only=False):
         """Return extensions in default and user config-extensions files.
 
         If active_only True, only return active (enabled) extensions
@@ -422,7 +437,7 @@
                 if self.GetOption('extensions', extn, 'enable', default=True,
                                   type='bool'):
                     #the extension is enabled
-                    if editor_only or shell_only:  # TODO if both, contradictory
+                    if editor_only or shell_only:  # TODO both True contradict
                         if editor_only:
                             option = "enable_editor"
                         else:
@@ -527,7 +542,8 @@
         eventStr - virtual event, including brackets, as in '<<event>>'.
         """
         eventName = eventStr[2:-2] #trim off the angle brackets
-        binding = self.GetOption('keys', keySetName, eventName, default='').split()
+        binding = self.GetOption('keys', keySetName, eventName, default='',
+                                 warn_on_default=False).split()
         return binding
 
     def GetCurrentKeySet(self):
@@ -638,20 +654,28 @@
             '<<del-word-right>>': ['<Control-Key-Delete>']
             }
         if keySetName:
-            for event in keyBindings:
-                binding = self.GetKeyBinding(keySetName, event)
-                if binding:
-                    keyBindings[event] = binding
-                else: #we are going to return a default, print warning
-                    warning=('\n Warning: config.py - IdleConf.GetCoreKeys'
-                               ' -\n problem retrieving key binding for event %r'
-                               '\n from key set %r.\n'
-                               ' returning default value: %r' %
-                               (event, keySetName, keyBindings[event]))
-                    try:
-                        print(warning, file=sys.stderr)
-                    except OSError:
-                        pass
+            if not (self.userCfg['keys'].has_section(keySetName) or
+                    self.defaultCfg['keys'].has_section(keySetName)):
+                warning = (
+                    '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
+                    ' key set %r is not defined, using default bindings.' %
+                    (keySetName,)
+                )
+                _warn(warning, 'keys', keySetName)
+            else:
+                for event in keyBindings:
+                    binding = self.GetKeyBinding(keySetName, event)
+                    if binding:
+                        keyBindings[event] = binding
+                    else: #we are going to return a default, print warning
+                        warning = (
+                            '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
+                            ' problem retrieving key binding for event %r\n'
+                            ' from key set %r.\n'
+                            ' returning default value: %r' %
+                            (event, keySetName, keyBindings[event])
+                        )
+                        _warn(warning, 'keys', keySetName, event)
         return keyBindings
 
     def GetExtraHelpSourceList(self, configSet):
@@ -735,6 +759,18 @@
 
 idleConf = IdleConf()
 
+
+_warned = set()
+def _warn(msg, *key):
+    key = (msg,) + key
+    if key not in _warned:
+        try:
+            print(msg, file=sys.stderr)
+        except OSError:
+            pass
+        _warned.add(key)
+
+
 # TODO Revise test output, write expanded unittest
 #
 if __name__ == '__main__':
diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py
--- a/Lib/idlelib/configdialog.py
+++ b/Lib/idlelib/configdialog.py
@@ -341,6 +341,7 @@
         buttonSaveCustomKeys = Button(
                 frames[1], text='Save as New Custom Key Set',
                 command=self.SaveAsNewKeySet)
+        self.new_custom_keys = Label(frames[0], bd=2)
 
         ##widget packing
         #body
@@ -361,6 +362,7 @@
         self.radioKeysCustom.grid(row=1, column=0, sticky=W+NS)
         self.optMenuKeysBuiltin.grid(row=0, column=1, sticky=NSEW)
         self.optMenuKeysCustom.grid(row=1, column=1, sticky=NSEW)
+        self.new_custom_keys.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
         self.buttonDeleteCustomKeys.pack(side=LEFT, fill=X, expand=True, padx=2)
         buttonSaveCustomKeys.pack(side=LEFT, fill=X, expand=True, padx=2)
         frames[0].pack(side=TOP, fill=BOTH, expand=True)
@@ -514,10 +516,11 @@
         self.OnNewColourSet()
 
     def VarChanged_builtinTheme(self, *params):
+        oldthemes = ('IDLE Classic', 'IDLE New')
         value = self.builtinTheme.get()
-        if value == 'IDLE Dark':
-            if idleConf.GetOption('main', 'Theme', 'name') != 'IDLE New':
-                self.AddChangedItem('main', 'Theme', 'name', 'IDLE Classic')
+        if value not in oldthemes:
+            if idleConf.GetOption('main', 'Theme', 'name') not in oldthemes:
+                self.AddChangedItem('main', 'Theme', 'name', oldthemes[0])
             self.AddChangedItem('main', 'Theme', 'name2', value)
             self.new_custom_theme.config(text='New theme, see Help',
                                          fg='#500000')
@@ -557,8 +560,23 @@
             self.AddChangedItem('extensions', extKeybindSection, event, value)
 
     def VarChanged_builtinKeys(self, *params):
+        oldkeys = (
+            'IDLE Classic Windows',
+            'IDLE Classic Unix',
+            'IDLE Classic Mac',
+            'IDLE Classic OSX',
+        )
         value = self.builtinKeys.get()
-        self.AddChangedItem('main', 'Keys', 'name', value)
+        if value not in oldkeys:
+            if idleConf.GetOption('main', 'Keys', 'name') not in oldkeys:
+                self.AddChangedItem('main', 'Keys', 'name', oldkeys[0])
+            self.AddChangedItem('main', 'Keys', 'name2', value)
+            self.new_custom_keys.config(text='New key set, see Help',
+                                        fg='#500000')
+        else:
+            self.AddChangedItem('main', 'Keys', 'name', value)
+            self.AddChangedItem('main', 'Keys', 'name2', '')
+            self.new_custom_keys.config(text='', fg='black')
         self.LoadKeysList(value)
 
     def VarChanged_customKeys(self, *params):
@@ -767,8 +785,10 @@
         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'))
+        self.keysAreBuiltin.set(idleConf.defaultCfg['main']
+                                .Get('Keys', 'default'))
+        self.builtinKeys.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
+                             or idleConf.default_keys())
         #user can't back out of these changes, they must be applied now
         self.SaveAllChangedConfigs()
         self.ActivateConfigChanges()
@@ -1067,7 +1087,7 @@
             self.optMenuKeysCustom.SetMenu(itemList, currentOption)
             itemList = idleConf.GetSectionList('default', 'keys')
             itemList.sort()
-            self.optMenuKeysBuiltin.SetMenu(itemList, itemList[0])
+            self.optMenuKeysBuiltin.SetMenu(itemList, idleConf.default_keys())
         self.SetKeysType()
         ##load keyset element list
         keySetName = idleConf.CurrentKeys()
@@ -1369,12 +1389,18 @@
 [Cancel] only cancels changes made since the last save.
 '''
 help_pages = {
-    'Highlighting':'''
+    'Highlighting': '''
 Highlighting:
 The IDLE Dark color theme is new in October 2015.  It can only
 be used with older IDLE releases if it is saved as a custom
 theme, with a different name.
-'''
+''',
+    'Keys': '''
+Keys:
+The IDLE Modern Unix key set is new in June 2016.  It can only
+be used with older IDLE releases if it is saved as a custom
+key set, with a different name.
+''',
 }
 
 
diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py
new file mode 100644
--- /dev/null
+++ b/Lib/idlelib/idle_test/test_config.py
@@ -0,0 +1,98 @@
+'''Test idlelib.config.
+
+Much is tested by opening config dialog live or in test_configdialog.
+Coverage: 27%
+'''
+from sys import modules
+from test.support import captured_stderr
+from tkinter import Tk
+import unittest
+from idlelib import config
+
+# Tests should not depend on fortuitous user configurations.
+# They must not affect actual user .cfg files.
+# Replace user parsers with empty parsers that cannot be saved.
+
+idleConf = config.idleConf
+usercfg = idleConf.userCfg
+testcfg = {}
+usermain = testcfg['main'] = config.IdleUserConfParser('')  # filename
+userhigh = testcfg['highlight'] = config.IdleUserConfParser('')
+userkeys = testcfg['keys'] = config.IdleUserConfParser('')
+
+def setUpModule():
+    idleConf.userCfg = testcfg
+
+def tearDownModule():
+    idleConf.userCfg = testcfg
+
+
+class CurrentColorKeysTest(unittest.TestCase):
+    """Test correct scenarios for colorkeys and wrap functions.
+
+        The 5 correct patterns are possible results of config dialog.
+    """
+    colorkeys = idleConf.current_colors_and_keys
+
+    def test_old_default(self):
+        # name2 must be blank
+        usermain.read_string('''
+            [Theme]
+            default= 1
+            ''')
+        self.assertEqual(self.colorkeys('Theme'), 'IDLE Classic')
+        usermain['Theme']['name'] = 'IDLE New'
+        self.assertEqual(self.colorkeys('Theme'), 'IDLE New')
+        usermain['Theme']['name'] = 'non-default'  # error
+        self.assertEqual(self.colorkeys('Theme'), 'IDLE Classic')
+        usermain.remove_section('Theme')
+
+    def test_new_default(self):
+        # name2 overrides name
+        usermain.read_string('''
+            [Theme]
+            default= 1
+            name= IDLE New
+            name2= IDLE Dark
+            ''')
+        self.assertEqual(self.colorkeys('Theme'), 'IDLE Dark')
+        usermain['Theme']['name2'] = 'non-default'  # error
+        self.assertEqual(self.colorkeys('Theme'), 'IDLE Classic')
+        usermain.remove_section('Theme')
+
+    def test_user_override(self):
+        # name2 does not matter
+        usermain.read_string('''
+            [Theme]
+            default= 0
+            name= Custom Dark
+            ''')  # error until set userhigh
+        self.assertEqual(self.colorkeys('Theme'), 'IDLE Classic')
+        userhigh.read_string('[Custom Dark]\na=b')
+        self.assertEqual(self.colorkeys('Theme'), 'Custom Dark')
+        usermain['Theme']['name2'] = 'IDLE Dark'
+        self.assertEqual(self.colorkeys('Theme'), 'Custom Dark')
+        usermain.remove_section('Theme')
+        userhigh.remove_section('Custom Dark')
+
+
+class WarningTest(unittest.TestCase):
+
+    def test_warn(self):
+        Equal = self.assertEqual
+        config._warned = set()
+        with captured_stderr() as stderr:
+            config._warn('warning', 'key')
+        Equal(config._warned, {('warning','key')})
+        Equal(stderr.getvalue(), 'warning'+'\n')
+        with captured_stderr() as stderr:
+            config._warn('warning', 'key')
+        Equal(stderr.getvalue(), '')
+        with captured_stderr() as stderr:
+            config._warn('warn2', 'yek')
+        Equal(config._warned, {('warning','key'), ('warn2','yek')})
+        Equal(stderr.getvalue(), 'warn2'+'\n')
+
+
+if __name__ == '__main__':
+    unittest.main(verbosity=2)

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list