diff -r 46567fda0b29 Lib/idlelib/config-keys.def --- a/Lib/idlelib/config-keys.def Sat Jul 09 11:05:42 2016 +0200 +++ b/Lib/idlelib/config-keys.def Sun Jul 10 01:40:05 2016 -0400 @@ -109,6 +109,57 @@ del-word-left= del-word-right= +[IDLE Modern Unix] +copy = +cut = +paste = +beginning-of-line = +center-insert = +close-all-windows = +close-window = +do-nothing = +end-of-file = +history-next = +history-previous = +interrupt-execution = +view-restart = +restart-shell = +open-class-browser = +open-module = +open-new-window = +open-window-from-file = +plain-newline-and-indent = +print-window = +python-context-help = +python-docs = +redo = +remove-selection = +save-copy-of-window-as-file = +save-window-as-file = +save-window = +select-all = +toggle-auto-coloring = +undo = +find = +find-again = +find-in-files = +find-selection = +replace = +goto-line = +smart-backspace = +newline-and-indent = +smart-indent = +indent-region = +dedent-region = +comment-region = +uncomment-region = +tabify-region = +untabify-region = +toggle-tabs = +change-indentwidth = +del-word-left = +del-word-right = + [IDLE Classic Mac] copy= cut= diff -r 46567fda0b29 Lib/idlelib/config-main.def --- a/Lib/idlelib/config-main.def Sat Jul 09 11:05:42 2016 +0200 +++ b/Lib/idlelib/config-main.def Sun Jul 10 01:40:05 2016 -0400 @@ -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 -r 46567fda0b29 Lib/idlelib/config.py --- a/Lib/idlelib/config.py Sat Jul 09 11:05:42 2016 +0200 +++ b/Lib/idlelib/config.py Sun Jul 10 01:40:05 2016 -0400 @@ -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 '<>'. """ 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 @@ '<>': [''] } 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__': @@ -765,3 +801,4 @@ dumpCfg(idleConf.defaultCfg) dumpCfg(idleConf.userCfg) print('\nlines = ', line, ', crc = ', crc, sep='') + print(idleConf.CurrentTheme()) diff -r 46567fda0b29 Lib/idlelib/configdialog.py --- a/Lib/idlelib/configdialog.py Sat Jul 09 11:05:42 2016 +0200 +++ b/Lib/idlelib/configdialog.py Sun Jul 10 01:40:05 2016 -0400 @@ -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 -r 46567fda0b29 Lib/idlelib/idle_test/test_config.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/idlelib/idle_test/test_config.py Sun Jul 10 01:40:05 2016 -0400 @@ -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)