diff options
-rw-r--r-- | Lib/idlelib/config-keys.def | 51 | ||||
-rw-r--r-- | Lib/idlelib/config-main.def | 4 | ||||
-rw-r--r-- | Lib/idlelib/config.py | 130 | ||||
-rw-r--r-- | Lib/idlelib/configdialog.py | 44 | ||||
-rw-r--r-- | 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 index 3bfcb69..64788f9 100644 --- a/Lib/idlelib/config-keys.def +++ b/Lib/idlelib/config-keys.def @@ -109,6 +109,57 @@ change-indentwidth=<Alt-Key-u> 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 index 8ebbc1b..a61bba7 100644 --- a/Lib/idlelib/config-main.def +++ b/Lib/idlelib/config-main.def @@ -70,7 +70,9 @@ name2= [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 index 51ef21b..f2437a8 100644 --- a/Lib/idlelib/config.py +++ b/Lib/idlelib/config.py @@ -234,10 +234,7 @@ class IdleConf: ' 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 @@ class IdleConf: ' 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 @@ class IdleConf: '\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') + + 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 - idlelib.config-main.def includes this section [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 @@ class IdleConf: 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 @@ class IdleConf: 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 @@ class IdleConf: '<<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 @@ class IdleConf: 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 index 388b48f..fda655f 100644 --- a/Lib/idlelib/configdialog.py +++ b/Lib/idlelib/configdialog.py @@ -341,6 +341,7 @@ class ConfigDialog(Toplevel): 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 @@ class ConfigDialog(Toplevel): 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 @@ class ConfigDialog(Toplevel): 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 @@ class ConfigDialog(Toplevel): 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 @@ class ConfigDialog(Toplevel): 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 @@ class ConfigDialog(Toplevel): 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 @@ machine. Some do not take affect until IDLE is restarted. [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 index 0000000..bb7732c --- /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) |