From 354ad86e2c9d8a2a6ec452d36737c80fd0092a2e Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Sun, 3 Oct 2021 16:30:01 -0500 Subject: [PATCH 01/17] Make extentions use user's keys, not all defaults I was trying to write an extension for Idle, and was noticing extension keys defined in ~/.idlerc weren't being used, so I looked into it and found `GetExtensionKeys`, `__GetRawExtensionKeys`, and `GetKeyBinding` would never check for user keys, always assuming default keys have everything. This is not always true. In a system with multiple users, you would not want to modify the default config file for a custom extension, as that is used for everyone. This pull request is changing aforementioned functions to also check user config files, and overwrite values acquired from the default config file if the user config redefined one. --- Lib/idlelib/config.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Lib/idlelib/config.py b/Lib/idlelib/config.py index 5ce5f4a4f7bd0d..cdea21cd945654 100644 --- a/Lib/idlelib/config.py +++ b/Lib/idlelib/config.py @@ -484,6 +484,12 @@ def GetExtensionKeys(self, extensionName): event = '<<' + eventName + '>>' binding = activeKeys[event] extKeys[event] = binding + if self.userCfg['extensions'].has_section(keysName): + eventNames = self.userCfg['extensions'].GetOptionList(keysName) + for eventName in eventNames: + event = '<<' + eventName + '>>' + binding = activeKeys[event] + extKeys[event] = binding return extKeys def __GetRawExtensionKeys(self,extensionName): @@ -502,6 +508,13 @@ def __GetRawExtensionKeys(self,extensionName): 'extensions', keysName, eventName, default='').split() event = '<<' + eventName + '>>' extKeys[event] = binding + if self.userCfg['extensions'].has_section(keysName): + eventNames = self.userCfg['extensions'].GetOptionList(keysName) + for eventName in eventNames: + binding = self.GetOption( + 'extensions', keysName, eventName, default='').split() + event = '<<' + eventName + '>>' + extKeys[event] = binding return extKeys def GetExtensionBindings(self, extensionName): @@ -521,7 +534,13 @@ def GetExtensionBindings(self, extensionName): 'extensions', bindsName, eventName, default='').split() event = '<<' + eventName + '>>' extBinds[event] = binding - + if self.userCfg['extensions'].has_section(bindsName): + eventNames = self.userCfg['extensions'].GetOptionList(bindsName) + for eventName in eventNames: + binding = self.GetOption( + 'extensions', bindsName, eventName, default='').split() + event = '<<' + eventName + '>>' + extBinds[event] = binding return extBinds def GetKeyBinding(self, keySetName, eventStr): @@ -755,7 +774,8 @@ def LoadCfgFiles(self): "Load all configuration files." for key in self.defaultCfg: self.defaultCfg[key].Load() - self.userCfg[key].Load() #same keys + for key in self.userCfg: + self.userCfg[key].Load() #not nessicerally same keys, could be more def SaveUserCfgFiles(self): "Write all loaded user configuration files to disk." From b6d278208eec5ab82682e73c2e0e804cf2da3995 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 3 Oct 2021 21:55:34 +0000 Subject: [PATCH 02/17] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Misc/NEWS.d/next/IDLE/2021-10-03-21-55-34.bpo-45357.etEExa.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/IDLE/2021-10-03-21-55-34.bpo-45357.etEExa.rst diff --git a/Misc/NEWS.d/next/IDLE/2021-10-03-21-55-34.bpo-45357.etEExa.rst b/Misc/NEWS.d/next/IDLE/2021-10-03-21-55-34.bpo-45357.etEExa.rst new file mode 100644 index 00000000000000..eeed4b262012e1 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2021-10-03-21-55-34.bpo-45357.etEExa.rst @@ -0,0 +1 @@ +Make idlelib.config.idleConf's functions GetExtensionKeys, __GetRawExtensionKeys, and GetKeyBinding look at user config files, allowing custom extensions to have key-binds defined in ~/.idlerc instead of in the default key-binds file. \ No newline at end of file From 48e18d6529c4ef0ae6c436ec122031ef8b130a71 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Sun, 3 Oct 2021 21:50:28 -0500 Subject: [PATCH 03/17] Fix config and dialog to use user config extentions Config saving and loading also weren't working quite right for extensions. --- Lib/idlelib/config.py | 60 ++++++++++++++++++++----------------- Lib/idlelib/configdialog.py | 50 ++++++++++++++++++++----------- 2 files changed, 64 insertions(+), 46 deletions(-) diff --git a/Lib/idlelib/config.py b/Lib/idlelib/config.py index cdea21cd945654..bf9b740290dbce 100644 --- a/Lib/idlelib/config.py +++ b/Lib/idlelib/config.py @@ -28,6 +28,7 @@ from configparser import ConfigParser import os import sys +from collections import ChainMap from tkinter.font import Font import idlelib @@ -192,7 +193,7 @@ def GetUserCfgDir(self): except OSError: pass userDir = '~' - if userDir == "~": # still no path to home! + else: # still no path to home! # traditionally IDLE has defaulted to os.getcwd(), is this adequate? userDir = os.getcwd() userDir = os.path.join(userDir, cfgDir) @@ -208,7 +209,7 @@ def GetUserCfgDir(self): except OSError: pass raise SystemExit - # TODO continue without userDIr instead of exit + # TODO continue without userDir instead of exit return userDir def GetOption(self, configType, section, option, default=None, type=None, @@ -266,7 +267,7 @@ def GetSectionList(self, configSet, configType): if configSet == 'user': cfgParser = self.userCfg[configType] elif configSet == 'default': - cfgParser=self.defaultCfg[configType] + cfgParser = self.defaultCfg[configType] else: raise InvalidConfigSet('Invalid configSet specified') return cfgParser.sections() @@ -402,7 +403,7 @@ def current_colors_and_keys(self, section): @staticmethod def default_keys(): - if sys.platform[:3] == 'win': + if sys.platform.startswith('win'): return 'IDLE Classic Windows' elif sys.platform == 'darwin': return 'IDLE Classic OSX' @@ -440,7 +441,7 @@ def GetExtensions(self, active_only=True, option = "enable_editor" else: option = "enable_shell" - if self.GetOption('extensions', extn,option, + if self.GetOption('extensions', extn, option, default=True, type='bool', warn_on_default=False): activeExtns.append(extn) @@ -478,14 +479,16 @@ def GetExtensionKeys(self, extensionName): keysName = extensionName + '_cfgBindings' activeKeys = self.GetCurrentKeySet() extKeys = {} + + values = [[]] if self.defaultCfg['extensions'].has_section(keysName): - eventNames = self.defaultCfg['extensions'].GetOptionList(keysName) - for eventName in eventNames: - event = '<<' + eventName + '>>' - binding = activeKeys[event] - extKeys[event] = binding + values.append(self.defaultCfg['extensions'].GetOptionList(keysName)) if self.userCfg['extensions'].has_section(keysName): - eventNames = self.userCfg['extensions'].GetOptionList(keysName) + values.append(self.userCfg['extensions'].GetOptionList(keysName)) + values.reverse() + eventNames = ChainMap(*values) + + if eventNames: for eventName in eventNames: event = '<<' + eventName + '>>' binding = activeKeys[event] @@ -501,15 +504,16 @@ def __GetRawExtensionKeys(self,extensionName): """ keysName = extensionName+'_cfgBindings' extKeys = {} + + values = [[]] if self.defaultCfg['extensions'].has_section(keysName): - eventNames = self.defaultCfg['extensions'].GetOptionList(keysName) - for eventName in eventNames: - binding = self.GetOption( - 'extensions', keysName, eventName, default='').split() - event = '<<' + eventName + '>>' - extKeys[event] = binding + values.append(self.defaultCfg['extensions'].GetOptionList(keysName)) if self.userCfg['extensions'].has_section(keysName): - eventNames = self.userCfg['extensions'].GetOptionList(keysName) + values.append(self.userCfg['extensions'].GetOptionList(keysName)) + values.reverse() + eventNames = ChainMap(*values) + + if eventNames: for eventName in eventNames: binding = self.GetOption( 'extensions', keysName, eventName, default='').split() @@ -527,15 +531,14 @@ def GetExtensionBindings(self, extensionName): bindsName = extensionName + '_bindings' extBinds = self.GetExtensionKeys(extensionName) #add the non-configurable bindings + values = [[]] if self.defaultCfg['extensions'].has_section(bindsName): - eventNames = self.defaultCfg['extensions'].GetOptionList(bindsName) - for eventName in eventNames: - binding = self.GetOption( - 'extensions', bindsName, eventName, default='').split() - event = '<<' + eventName + '>>' - extBinds[event] = binding + values.append(self.defaultCfg['extensions'].GetOptionList(bindsName)) if self.userCfg['extensions'].has_section(bindsName): - eventNames = self.userCfg['extensions'].GetOptionList(bindsName) + values.append(self.userCfg['extensions'].GetOptionList(bindsName)) + values.reverse() + eventNames = ChainMap(*values) + if eventNames: for eventName in eventNames: binding = self.GetOption( 'extensions', bindsName, eventName, default='').split() @@ -597,7 +600,7 @@ def IsCoreBinding(self, virtualEvent): """ return ('<<'+virtualEvent+'>>') in self.GetCoreKeys() -# TODO make keyBindins a file or class attribute used for test above +# TODO make keyBindings a file or class attribute used for test above # and copied in function below. former_extension_events = { # Those with user-configurable keys. @@ -720,7 +723,7 @@ def GetExtraHelpSourceList(self, configSet): cfgParser = self.defaultCfg['main'] else: raise InvalidConfigSet('Invalid configSet specified') - options=cfgParser.GetOptionList('HelpFiles') + options = cfgParser.GetOptionList('HelpFiles') for option in options: value=cfgParser.Get('HelpFiles', option, default=';') if value.find(';') == -1: #malformed config entry with no ';' @@ -731,7 +734,7 @@ def GetExtraHelpSourceList(self, configSet): menuItem=value[0].strip() helpPath=value[1].strip() if menuItem and helpPath: #neither are empty strings - helpSources.append( (menuItem,helpPath,option) ) + helpSources.append( (menuItem, helpPath, option) ) helpSources.sort(key=lambda x: x[2]) return helpSources @@ -837,6 +840,7 @@ def save_option(config_type, section, item, value): Helper for save_all. """ if idleConf.defaultCfg[config_type].has_option(section, item): + print((idleConf.defaultCfg[config_type].Get(section, item), value)) if idleConf.defaultCfg[config_type].Get(section, item) == value: # The setting equals a default setting, remove it from user cfg. return idleConf.userCfg[config_type].RemoveOption(section, item) diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py index 6d0893680274b3..811d66eabd5f25 100644 --- a/Lib/idlelib/configdialog.py +++ b/Lib/idlelib/configdialog.py @@ -1971,7 +1971,11 @@ def load_extensions(self): self.extensions[ext_name] = [] for ext_name in self.extensions: - opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name)) + opt_list = self.ext_defaultCfg.GetOptionList(ext_name) + user_list = self.ext_userCfg.GetOptionList(ext_name) + opt_list.extend(user_list) + user_list = set(user_list) + opt_list = list(set(sorted(opt_list))) # Bring 'enable' options to the beginning of the list. enables = [opt_name for opt_name in opt_list @@ -1981,8 +1985,12 @@ def load_extensions(self): opt_list = enables + opt_list for opt_name in opt_list: - def_str = self.ext_defaultCfg.Get( - ext_name, opt_name, raw=True) + if opt_name in user_list: + def_str = self.ext_userCfg.Get( + ext_name, opt_name, raw=True) + else: + def_str = self.ext_defaultCfg.Get( + ext_name, opt_name, raw=True) try: def_obj = {'True':True, 'False':False}[def_str] opt_type = 'bool' @@ -1994,9 +2002,14 @@ def load_extensions(self): def_obj = def_str opt_type = None try: - value = self.ext_userCfg.Get( - ext_name, opt_name, type=opt_type, raw=True, - default=def_obj) + if opt_name in user_list: + value = self.ext_userCfg.Get( + ext_name, opt_name, type=opt_type, raw=True, + default=def_obj) + else: + value = self.ext_defaultCfg.Get( + ext_name, opt_name, type=opt_type, raw=True, + default=def_obj) except ValueError: # Need this until .Get fixed. value = def_obj # Bad values overwritten by entry. var = StringVar(self) @@ -2044,7 +2057,7 @@ def create_extension_frame(self, ext_name): validatecommand=(self.is_int, '%P'), width=10 ).grid(row=row, column=1, sticky=NSEW, padx=7) - else: # type == 'str' + else: # opt['type'] == 'str' # Limit size to fit non-expanding space with larger font. Entry(entry_area, textvariable=var, width=15 ).grid(row=row, column=1, sticky=NSEW, padx=7) @@ -2060,10 +2073,10 @@ def set_extension_value(self, section, opt): default = opt['default'] value = opt['var'].get().strip() or default opt['var'].set(value) - # if self.defaultCfg.has_section(section): - # Currently, always true; if not, indent to return. - if (value == default): - return self.ext_userCfg.RemoveOption(section, name) + # Default config does not necessarily have section. + if self.ext_defaultCfg.has_section(section): + if (value == default): + return self.ext_userCfg.RemoveOption(section, name) # Set the option. return self.ext_userCfg.SetOption(section, name, value) @@ -2076,14 +2089,15 @@ def save_all_changed_extensions(self): Methods: set_extension_value """ - has_changes = False +## has_changes = False for ext_name in self.extensions: - options = self.extensions[ext_name] - for opt in options: - if self.set_extension_value(ext_name, opt): - has_changes = True - if has_changes: - self.ext_userCfg.Save() +## options = self.extensions[ext_name] + for opt in self.extensions[ext_name]: + self.set_extension_value(ext_name, opt) +## if self.set_extension_value(ext_name, opt): +## has_changes = True +## if has_changes: + self.ext_userCfg.Save() class HelpFrame(LabelFrame): From e1e8cd99890abe6652880ac1a513ee762dfeedb9 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Tue, 5 Oct 2021 22:44:54 -0500 Subject: [PATCH 04/17] Remove debug print I forgot --- Lib/idlelib/config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/idlelib/config.py b/Lib/idlelib/config.py index bf9b740290dbce..7e3d4504caf840 100644 --- a/Lib/idlelib/config.py +++ b/Lib/idlelib/config.py @@ -840,7 +840,6 @@ def save_option(config_type, section, item, value): Helper for save_all. """ if idleConf.defaultCfg[config_type].has_option(section, item): - print((idleConf.defaultCfg[config_type].Get(section, item), value)) if idleConf.defaultCfg[config_type].Get(section, item) == value: # The setting equals a default setting, remove it from user cfg. return idleConf.userCfg[config_type].RemoveOption(section, item) From e2a25289a29202665dc7e2dbaafd458b978f1d49 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Tue, 5 Oct 2021 22:47:53 -0500 Subject: [PATCH 05/17] Remove debug comment out lines --- Lib/idlelib/configdialog.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py index 811d66eabd5f25..bf6ccdd62d189a 100644 --- a/Lib/idlelib/configdialog.py +++ b/Lib/idlelib/configdialog.py @@ -2089,14 +2089,9 @@ def save_all_changed_extensions(self): Methods: set_extension_value """ -## has_changes = False for ext_name in self.extensions: -## options = self.extensions[ext_name] for opt in self.extensions[ext_name]: self.set_extension_value(ext_name, opt) -## if self.set_extension_value(ext_name, opt): -## has_changes = True -## if has_changes: self.ext_userCfg.Save() From 4e2161b90b7f5a673b10b277561d0285b60859dc Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Tue, 1 Feb 2022 14:57:47 -0600 Subject: [PATCH 06/17] Fix spelling Co-authored-by: Jelle Zijlstra --- Lib/idlelib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/config.py b/Lib/idlelib/config.py index 7e3d4504caf840..44b235d32d9736 100644 --- a/Lib/idlelib/config.py +++ b/Lib/idlelib/config.py @@ -778,7 +778,7 @@ def LoadCfgFiles(self): for key in self.defaultCfg: self.defaultCfg[key].Load() for key in self.userCfg: - self.userCfg[key].Load() #not nessicerally same keys, could be more + self.userCfg[key].Load() #not necessarily same keys, could be more def SaveUserCfgFiles(self): "Write all loaded user configuration files to disk." From b425b72e9b2506d485bd1ea44d2d0c205d297f1f Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Tue, 1 Feb 2022 15:00:59 -0600 Subject: [PATCH 07/17] Revert previous change --- Lib/idlelib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/config.py b/Lib/idlelib/config.py index 44b235d32d9736..8185db6e603a7b 100644 --- a/Lib/idlelib/config.py +++ b/Lib/idlelib/config.py @@ -193,7 +193,7 @@ def GetUserCfgDir(self): except OSError: pass userDir = '~' - else: # still no path to home! + if userDir == '~': # still no path to home! # traditionally IDLE has defaulted to os.getcwd(), is this adequate? userDir = os.getcwd() userDir = os.path.join(userDir, cfgDir) From f0e856abb4c5c614ec7ed70d164d71881f3d0d76 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Sun, 27 Aug 2023 00:26:56 -0500 Subject: [PATCH 08/17] Add trailing newline --- Misc/NEWS.d/next/IDLE/2021-10-03-21-55-34.bpo-45357.etEExa.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/IDLE/2021-10-03-21-55-34.bpo-45357.etEExa.rst b/Misc/NEWS.d/next/IDLE/2021-10-03-21-55-34.bpo-45357.etEExa.rst index eeed4b262012e1..5f52ab0526014c 100644 --- a/Misc/NEWS.d/next/IDLE/2021-10-03-21-55-34.bpo-45357.etEExa.rst +++ b/Misc/NEWS.d/next/IDLE/2021-10-03-21-55-34.bpo-45357.etEExa.rst @@ -1 +1 @@ -Make idlelib.config.idleConf's functions GetExtensionKeys, __GetRawExtensionKeys, and GetKeyBinding look at user config files, allowing custom extensions to have key-binds defined in ~/.idlerc instead of in the default key-binds file. \ No newline at end of file +Make idlelib.config.idleConf's functions GetExtensionKeys, __GetRawExtensionKeys, and GetKeyBinding look at user config files, allowing custom extensions to have key-binds defined in ~/.idlerc instead of in the default key-binds file. From 40d864b6908000a8f3f4bd61514ba58db738588d Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Sat, 11 Nov 2023 15:52:59 -0600 Subject: [PATCH 09/17] Simplify and update from `idleuserextend`'s testing --- Lib/idlelib/config.py | 131 ++++++++++++++++++++---------------- Lib/idlelib/configdialog.py | 25 ++++--- Lib/idlelib/editor.py | 8 ++- 3 files changed, 91 insertions(+), 73 deletions(-) diff --git a/Lib/idlelib/config.py b/Lib/idlelib/config.py index c67d3a063d8ec5..0298577f446aa2 100644 --- a/Lib/idlelib/config.py +++ b/Lib/idlelib/config.py @@ -469,82 +469,95 @@ def GetExtnNameForEvent(self, virtualEvent): extName = extn # TODO return here? return extName - def GetExtensionKeys(self, extensionName): + def GetExtensionKeys(self, extension_name): """Return dict: {configurable extensionName event : active keybinding}. Events come from default config extension_cfgBindings section. Keybindings come from GetCurrentKeySet() active key dict, where previously used bindings are disabled. """ - keysName = extensionName + '_cfgBindings' - activeKeys = self.GetCurrentKeySet() - extKeys = {} - - values = [[]] - if self.defaultCfg['extensions'].has_section(keysName): - values.append(self.defaultCfg['extensions'].GetOptionList(keysName)) - if self.userCfg['extensions'].has_section(keysName): - values.append(self.userCfg['extensions'].GetOptionList(keysName)) - values.reverse() - eventNames = ChainMap(*values) - - if eventNames: - for eventName in eventNames: - event = '<<' + eventName + '>>' - binding = activeKeys[event] - extKeys[event] = binding - return extKeys - - def __GetRawExtensionKeys(self,extensionName): + bindings_section = f'{extension_name}_cfgBindings' + current_keyset = idleConf.GetCurrentKeySet() + extension_keys = {} + + event_names = set() + if self.userCfg['extensions'].has_section(bindings_section): + event_names |= set( + self.userCfg['extensions'].GetOptionList(bindings_section) + ) + if self.defaultCfg['extensions'].has_section(bindings_section): + event_names |= set( + self.defaultCfg['extensions'].GetOptionList(bindings_section) + ) + + for event_name in event_names: + event = f'<<{event_name}>>' + binding = current_keyset.get(event, None) + if binding is None: + continue + extension_keys[event] = binding + return extension_keys + + def __GetRawExtensionKeys(self, extension_name): """Return dict {configurable extensionName event : keybinding list}. Events come from default config extension_cfgBindings section. Keybindings list come from the splitting of GetOption, which tries user config before default config. """ - keysName = extensionName+'_cfgBindings' - extKeys = {} - - values = [[]] - if self.defaultCfg['extensions'].has_section(keysName): - values.append(self.defaultCfg['extensions'].GetOptionList(keysName)) - if self.userCfg['extensions'].has_section(keysName): - values.append(self.userCfg['extensions'].GetOptionList(keysName)) - values.reverse() - eventNames = ChainMap(*values) - - if eventNames: - for eventName in eventNames: - binding = self.GetOption( - 'extensions', keysName, eventName, default='').split() - event = '<<' + eventName + '>>' - extKeys[event] = binding - return extKeys - - def GetExtensionBindings(self, extensionName): + bindings_section = f'{extension_name}_cfgBindings' + extension_keys = {} + + event_names = [] + if self.userCfg['extensions'].has_section(bindings_section): + event_names.append(self.userCfg['extensions'].GetOptionList(bindings_section)) + if self.defaultCfg['extensions'].has_section(bindings_section): + event_names.append(self.defaultCfg['extensions'].GetOptionList(bindings_section)) + + # Because chain map, favors user bindings over default bindings + for event_name in ChainMap(*event_names): + binding = self.GetOption( + 'extensions', + bindings_section, + event_name, + default='', + ).split() + event = f'<<{event_name}>>' + extension_keys[event] = binding + return extension_keys + + def GetExtensionBindings(self, extension_name): """Return dict {extensionName event : active or defined keybinding}. Augment self.GetExtensionKeys(extensionName) with mapping of non- configurable events (from default config) to GetOption splits, as in self.__GetRawExtensionKeys. """ - bindsName = extensionName + '_bindings' - extBinds = self.GetExtensionKeys(extensionName) - #add the non-configurable bindings - values = [[]] - if self.defaultCfg['extensions'].has_section(bindsName): - values.append(self.defaultCfg['extensions'].GetOptionList(bindsName)) - if self.userCfg['extensions'].has_section(bindsName): - values.append(self.userCfg['extensions'].GetOptionList(bindsName)) - values.reverse() - eventNames = ChainMap(*values) - if eventNames: - for eventName in eventNames: - binding = self.GetOption( - 'extensions', bindsName, eventName, default='').split() - event = '<<' + eventName + '>>' - extBinds[event] = binding - return extBinds + bindings_section = f'{extension_name}_bindings' + extension_keys = self.GetExtensionKeys(extension_name) + + # add the non-configurable bindings + values = [] + if self.userCfg['extensions'].has_section(bindings_section): + values.append( + self.userCfg['extensions'].GetOptionList(bindings_section) + ) + if self.defaultCfg['extensions'].has_section(bindings_section): + values.append( + self.defaultCfg['extensions'].GetOptionList(bindings_section) + ) + + # Because chain map, favors user bindings over default bindings + for event_name in ChainMap(*values): + binding = self.GetOption( + 'extensions', + bindings_section, + event_name, + default='' + ).split() + event = f'<<{event_name}>>' + extension_keys[event] = binding + return extension_keys def GetKeyBinding(self, keySetName, eventStr): """Return the keybinding list for keySetName eventStr. @@ -780,7 +793,7 @@ def LoadCfgFiles(self): for key in self.defaultCfg: self.defaultCfg[key].Load() for key in self.userCfg: - self.userCfg[key].Load() #not necessarily same keys, could be more + self.userCfg[key].Load() def SaveUserCfgFiles(self): "Write all loaded user configuration files to disk." diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py index c2fc4365d66797..995c8fb16cc9da 100644 --- a/Lib/idlelib/configdialog.py +++ b/Lib/idlelib/configdialog.py @@ -1960,20 +1960,22 @@ def create_page_extensions(self): 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: - opt_list = self.ext_defaultCfg.GetOptionList(ext_name) - user_list = self.ext_userCfg.GetOptionList(ext_name) - opt_list.extend(user_list) - user_list = set(user_list) - opt_list = list(set(sorted(opt_list))) + default = set(self.ext_defaultCfg.GetOptionList(ext_name)) + user = set(self.ext_userCfg.GetOptionList(ext_name)) + opt_list = sorted(default | user) # Bring 'enable' options to the beginning of the list. - enables = [opt_name for opt_name in opt_list - if opt_name.startswith('enable')] + enables = [ + opt_name + for opt_name in opt_list + if opt_name.startswith('enable') + ] for opt_name in enables: opt_list.remove(opt_name) opt_list = enables + opt_list @@ -2067,10 +2069,11 @@ def set_extension_value(self, section, opt): default = opt['default'] value = opt['var'].get().strip() or default opt['var'].set(value) - # Default config does not necessarily have section. - if self.ext_defaultCfg.has_section(section): - if (value == default): - return self.ext_userCfg.RemoveOption(section, name) + + # Only save option in user config if it differs from the default + if self.ext_defaultCfg.has_section(section) and value == default: + return self.ext_userCfg.RemoveOption(section, name) + # Set the option. return self.ext_userCfg.SetOption(section, name, value) diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 69b27d0683a104..80bd38f19d3d79 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -1,3 +1,4 @@ +import contextlib import importlib.abc import importlib.util import os @@ -903,11 +904,12 @@ def RemoveKeybindings(self): # Called from configdialog.deactivate_current_config. self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet() for event, keylist in keydefs.items(): - self.text.event_delete(event, *keylist) + with contextlib.suppress(ValueError): + self.text.event_delete(event, *keylist) for extensionName in self.get_standard_extension_names(): xkeydefs = idleConf.GetExtensionBindings(extensionName) - if xkeydefs: - for event, keylist in xkeydefs.items(): + for event, keylist in xkeydefs.items(): + with contextlib.suppress(ValueError): self.text.event_delete(event, *keylist) def ApplyKeybindings(self): From fc190e36e62a9651f9b86450ae47d217b5d19f60 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Sat, 11 Nov 2023 16:01:36 -0600 Subject: [PATCH 10/17] Revert always save changes --- Lib/idlelib/configdialog.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py index 995c8fb16cc9da..df6bca24d42fd1 100644 --- a/Lib/idlelib/configdialog.py +++ b/Lib/idlelib/configdialog.py @@ -2086,10 +2086,13 @@ def save_all_changed_extensions(self): Methods: set_extension_value """ + has_changes = False for ext_name in self.extensions: for opt in self.extensions[ext_name]: - self.set_extension_value(ext_name, opt) - self.ext_userCfg.Save() + if self.set_extension_value(ext_name, opt): + has_changes = True + if has_changes: + self.ext_userCfg.Save() class HelpFrame(LabelFrame): From dc46fb7dbffeb01f8978b92d2f131239ac67be7a Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Fri, 17 Nov 2023 16:07:26 -0600 Subject: [PATCH 11/17] Revert unnecessary formatting changes --- Lib/idlelib/config.py | 14 +++++++------- Lib/idlelib/configdialog.py | 5 +++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Lib/idlelib/config.py b/Lib/idlelib/config.py index 0298577f446aa2..fd137e265120b7 100644 --- a/Lib/idlelib/config.py +++ b/Lib/idlelib/config.py @@ -193,7 +193,7 @@ def GetUserCfgDir(self): except OSError: pass userDir = '~' - if userDir == '~': # still no path to home! + if userDir == "~": # still no path to home! # traditionally IDLE has defaulted to os.getcwd(), is this adequate? userDir = os.getcwd() userDir = os.path.join(userDir, cfgDir) @@ -209,7 +209,7 @@ def GetUserCfgDir(self): except OSError: pass raise SystemExit - # TODO continue without userDir instead of exit + # TODO continue without userDIr instead of exit return userDir def GetOption(self, configType, section, option, default=None, type=None, @@ -267,7 +267,7 @@ def GetSectionList(self, configSet, configType): if configSet == 'user': cfgParser = self.userCfg[configType] elif configSet == 'default': - cfgParser = self.defaultCfg[configType] + cfgParser=self.defaultCfg[configType] else: raise InvalidConfigSet('Invalid configSet specified') return cfgParser.sections() @@ -403,7 +403,7 @@ def current_colors_and_keys(self, section): @staticmethod def default_keys(): - if sys.platform.startswith('win'): + if sys.platform[:3] == 'win': return 'IDLE Classic Windows' elif sys.platform == 'darwin': return 'IDLE Classic OSX' @@ -441,7 +441,7 @@ def GetExtensions(self, active_only=True, option = "enable_editor" else: option = "enable_shell" - if self.GetOption('extensions', extn, option, + if self.GetOption('extensions', extn,option, default=True, type='bool', warn_on_default=False): activeExtns.append(extn) @@ -738,7 +738,7 @@ def GetExtraHelpSourceList(self, configSet): cfgParser = self.defaultCfg['main'] else: raise InvalidConfigSet('Invalid configSet specified') - options = cfgParser.GetOptionList('HelpFiles') + options=cfgParser.GetOptionList('HelpFiles') for option in options: value=cfgParser.Get('HelpFiles', option, default=';') if value.find(';') == -1: #malformed config entry with no ';' @@ -749,7 +749,7 @@ def GetExtraHelpSourceList(self, configSet): menuItem=value[0].strip() helpPath=value[1].strip() if menuItem and helpPath: #neither are empty strings - helpSources.append( (menuItem, helpPath, option) ) + helpSources.append( (menuItem,helpPath,option) ) helpSources.sort(key=lambda x: x[2]) return helpSources diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py index df6bca24d42fd1..1d71e25575d173 100644 --- a/Lib/idlelib/configdialog.py +++ b/Lib/idlelib/configdialog.py @@ -2053,7 +2053,7 @@ def create_extension_frame(self, ext_name): validatecommand=(self.is_int, '%P'), width=10 ).grid(row=row, column=1, sticky=NSEW, padx=7) - else: # opt['type'] == 'str' + else: # type == 'str' # Limit size to fit non-expanding space with larger font. Entry(entry_area, textvariable=var, width=15 ).grid(row=row, column=1, sticky=NSEW, padx=7) @@ -2088,7 +2088,8 @@ def save_all_changed_extensions(self): """ has_changes = False for ext_name in self.extensions: - for opt in self.extensions[ext_name]: + options = self.extensions[ext_name] + for opt in options: if self.set_extension_value(ext_name, opt): has_changes = True if has_changes: From 016ce892e819b29b39b7a88ced74263b0c00087c Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Fri, 17 Nov 2023 16:12:36 -0600 Subject: [PATCH 12/17] `user_list` -> `user`, as is no longer a list but a set. --- Lib/idlelib/configdialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py index 1d71e25575d173..f7b2c1e816f86f 100644 --- a/Lib/idlelib/configdialog.py +++ b/Lib/idlelib/configdialog.py @@ -1981,7 +1981,7 @@ def load_extensions(self): opt_list = enables + opt_list for opt_name in opt_list: - if opt_name in user_list: + if opt_name in user: def_str = self.ext_userCfg.Get( ext_name, opt_name, raw=True) else: @@ -1998,7 +1998,7 @@ def load_extensions(self): def_obj = def_str opt_type = None try: - if opt_name in user_list: + if opt_name in user: value = self.ext_userCfg.Get( ext_name, opt_name, type=opt_type, raw=True, default=def_obj) From 568cd80d14b8235da0257ec9736e78376b876222 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Mon, 11 Mar 2024 19:26:25 -0500 Subject: [PATCH 13/17] Revert noted strictly format change --- Lib/idlelib/configdialog.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py index f7b2c1e816f86f..9645db3cf0ae64 100644 --- a/Lib/idlelib/configdialog.py +++ b/Lib/idlelib/configdialog.py @@ -1971,11 +1971,8 @@ def load_extensions(self): opt_list = sorted(default | user) # Bring 'enable' options to the beginning of the list. - enables = [ - opt_name - for opt_name in opt_list - if opt_name.startswith('enable') - ] + enables = [opt_name for opt_name in opt_list + if opt_name.startswith('enable')] for opt_name in enables: opt_list.remove(opt_name) opt_list = enables + opt_list From 024095903e48a29dfcaa7fd220a8cfaa262c9dcc Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Mon, 11 Mar 2024 20:53:44 -0500 Subject: [PATCH 14/17] Remove suppression for errors that will never happen --- Lib/idlelib/editor.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 61225397328c5b..a96050515f9827 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -1,4 +1,3 @@ -import contextlib import importlib.abc import importlib.util import os @@ -905,13 +904,11 @@ def RemoveKeybindings(self): # Called from configdialog.deactivate_current_config. self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet() for event, keylist in keydefs.items(): - with contextlib.suppress(ValueError): - self.text.event_delete(event, *keylist) + self.text.event_delete(event, *keylist) for extensionName in self.get_standard_extension_names(): xkeydefs = idleConf.GetExtensionBindings(extensionName) for event, keylist in xkeydefs.items(): - with contextlib.suppress(ValueError): - self.text.event_delete(event, *keylist) + self.text.event_delete(event, *keylist) def ApplyKeybindings(self): """Apply the virtual, configurable keybindings. From 2f47ecbf6b6e10623c4a05e3d9eee6a903ff8d9d Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Mon, 11 Mar 2024 21:30:26 -0500 Subject: [PATCH 15/17] Add test for ZzDummy being loaded in extensions list --- Lib/idlelib/idle_test/test_zzdummy.py | 3 + Lib/idlelib/idle_test/test_zzdummy_user.py | 175 +++++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 Lib/idlelib/idle_test/test_zzdummy_user.py diff --git a/Lib/idlelib/idle_test/test_zzdummy.py b/Lib/idlelib/idle_test/test_zzdummy.py index 209d8564da0664..ec07e188d026d2 100644 --- a/Lib/idlelib/idle_test/test_zzdummy.py +++ b/Lib/idlelib/idle_test/test_zzdummy.py @@ -82,6 +82,9 @@ def checklines(self, text, value): actual.append(txt.startswith(value)) return actual + def test_exists(self): + self.assertIn("ZzDummy", zzdummy.idleConf.GetExtensions()) + def test_init(self): zz = self.zz self.assertEqual(zz.editwin, self.editor) diff --git a/Lib/idlelib/idle_test/test_zzdummy_user.py b/Lib/idlelib/idle_test/test_zzdummy_user.py new file mode 100644 index 00000000000000..47c56ce4ab0a72 --- /dev/null +++ b/Lib/idlelib/idle_test/test_zzdummy_user.py @@ -0,0 +1,175 @@ +"Test zzdummy, coverage 100%." + +from idlelib import zzdummy +import unittest +from test.support import requires +from tkinter import Tk, Text +from unittest import mock +from idlelib import config +from idlelib import editor +from idlelib import format + + +real_usercfg = zzdummy.idleConf.userCfg +test_usercfg = { + 'main': config.IdleUserConfParser(''), + 'highlight': config.IdleUserConfParser(''), + 'keys': config.IdleUserConfParser(''), + 'extensions': config.IdleUserConfParser(''), +} +test_usercfg["extensions"].read_dict( + {"ZzDummy": {'enable': 'False', 'enable_shell': 'False', 'enable_editor': 'True', 'z-text': 'Z'}} +) +real_defaultcfg = zzdummy.idleConf.defaultCfg +test_defaultcfg = { + 'main': config.IdleUserConfParser(''), + 'highlight': config.IdleUserConfParser(''), + 'keys': config.IdleUserConfParser(''), + 'extensions': config.IdleUserConfParser(''), +} +test_defaultcfg["extensions"].read_dict( + { + "AutoComplete": {'popupwait': '2000'}, + "CodeContext": {'maxlines': '15'}, + "FormatParagraph": {'max-width': '72'}, + "ParenMatch": {'style': 'expression', 'flash-delay': '500', 'bell': 'True'}, + } +) +code_sample = """\ + +class C1: + # Class comment. + def __init__(self, a, b): + self.a = a + self.b = b +""" + + +class DummyEditwin: + get_selection_indices = editor.EditorWindow.get_selection_indices + def __init__(self, root, text): + self.root = root + self.top = root + self.text = text + self.fregion = format.FormatRegion(self) + self.text.undo_block_start = mock.Mock() + self.text.undo_block_stop = mock.Mock() + + +class ZZDummyTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + root = cls.root = Tk() + root.withdraw() + text = cls.text = Text(cls.root) + cls.editor = DummyEditwin(root, text) + zzdummy.idleConf.userCfg = test_usercfg + zzdummy.idleConf.defaultCfg = test_defaultcfg + + @classmethod + def tearDownClass(cls): + zzdummy.idleConf.defaultCfg = real_defaultcfg + zzdummy.idleConf.userCfg = real_usercfg + del cls.editor, cls.text + cls.root.update_idletasks() + for id in cls.root.tk.call('after', 'info'): + cls.root.after_cancel(id) # Need for EditorWindow. + cls.root.destroy() + del cls.root + + def setUp(self): + text = self.text + text.insert('1.0', code_sample) + text.undo_block_start.reset_mock() + text.undo_block_stop.reset_mock() + zz = self.zz = zzdummy.ZzDummy(self.editor) + zzdummy.ZzDummy.ztext = '# ignore #' + + def tearDown(self): + self.text.delete('1.0', 'end') + del self.zz + + def checklines(self, text, value): + # Verify that there are lines being checked. + end_line = int(float(text.index('end'))) + + # Check each line for the starting text. + actual = [] + for line in range(1, end_line): + txt = text.get(f'{line}.0', f'{line}.end') + actual.append(txt.startswith(value)) + return actual + + def test_exists(self): + self.assertIn("ZzDummy", zzdummy.idleConf.GetExtensions()) + + def test_init(self): + zz = self.zz + self.assertEqual(zz.editwin, self.editor) + self.assertEqual(zz.text, self.editor.text) + + def test_reload(self): + self.assertEqual(self.zz.ztext, '# ignore #') + test_usercfg['extensions'].SetOption('ZzDummy', 'z-text', 'spam') + zzdummy.ZzDummy.reload() + self.assertEqual(self.zz.ztext, 'spam') + + def test_z_in_event(self): + eq = self.assertEqual + zz = self.zz + text = zz.text + eq(self.zz.ztext, '# ignore #') + + # No lines have the leading text. + expected = [False, False, False, False, False, False, False] + actual = self.checklines(text, zz.ztext) + eq(expected, actual) + + text.tag_add('sel', '2.0', '4.end') + eq(zz.z_in_event(), 'break') + expected = [False, True, True, True, False, False, False] + actual = self.checklines(text, zz.ztext) + eq(expected, actual) + + text.undo_block_start.assert_called_once() + text.undo_block_stop.assert_called_once() + + def test_z_out_event(self): + eq = self.assertEqual + zz = self.zz + text = zz.text + eq(self.zz.ztext, '# ignore #') + + # Prepend text. + text.tag_add('sel', '2.0', '5.end') + zz.z_in_event() + text.undo_block_start.reset_mock() + text.undo_block_stop.reset_mock() + + # Select a few lines to remove text. + text.tag_remove('sel', '1.0', 'end') + text.tag_add('sel', '3.0', '4.end') + eq(zz.z_out_event(), 'break') + expected = [False, True, False, False, True, False, False] + actual = self.checklines(text, zz.ztext) + eq(expected, actual) + + text.undo_block_start.assert_called_once() + text.undo_block_stop.assert_called_once() + + def test_roundtrip(self): + # Insert and remove to all code should give back original text. + zz = self.zz + text = zz.text + + text.tag_add('sel', '1.0', 'end-1c') + zz.z_in_event() + zz.z_out_event() + + self.assertEqual(text.get('1.0', 'end-1c'), code_sample) + + +if __name__ == '__main__': + unittest.main(verbosity=2) From 3a9d1ddd55a1a3755f812b081bc75070a51f8c80 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Mon, 11 Mar 2024 22:00:10 -0500 Subject: [PATCH 16/17] Don't require enabled --- Lib/idlelib/idle_test/test_zzdummy.py | 4 +++- Lib/idlelib/idle_test/test_zzdummy_user.py | 13 ++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Lib/idlelib/idle_test/test_zzdummy.py b/Lib/idlelib/idle_test/test_zzdummy.py index ec07e188d026d2..dca90e585cb3ad 100644 --- a/Lib/idlelib/idle_test/test_zzdummy.py +++ b/Lib/idlelib/idle_test/test_zzdummy.py @@ -83,7 +83,9 @@ def checklines(self, text, value): return actual def test_exists(self): - self.assertIn("ZzDummy", zzdummy.idleConf.GetExtensions()) + self.assertEqual(zzdummy.idleConf.GetSectionList('user', 'extensions'), []) + self.assertEqual(zzdummy.idleConf.GetSectionList('default', 'extensions'), ['AutoComplete', 'CodeContext', 'FormatParagraph', 'ParenMatch', 'ZzDummy', 'ZzDummy_cfgBindings', 'ZzDummy_bindings']) + self.assertIn("ZzDummy", zzdummy.idleConf.GetExtensions(False)) def test_init(self): zz = self.zz diff --git a/Lib/idlelib/idle_test/test_zzdummy_user.py b/Lib/idlelib/idle_test/test_zzdummy_user.py index 47c56ce4ab0a72..a76673d6ac1813 100644 --- a/Lib/idlelib/idle_test/test_zzdummy_user.py +++ b/Lib/idlelib/idle_test/test_zzdummy_user.py @@ -17,9 +17,11 @@ 'keys': config.IdleUserConfParser(''), 'extensions': config.IdleUserConfParser(''), } -test_usercfg["extensions"].read_dict( - {"ZzDummy": {'enable': 'False', 'enable_shell': 'False', 'enable_editor': 'True', 'z-text': 'Z'}} -) +test_usercfg["extensions"].read_dict({ + "ZzDummy": {'enable': 'True', 'enable_shell': 'False', 'enable_editor': 'True', 'z-text': 'Z'}, + "ZzDummy_cfgBindings": {'z-in': ''}, + "ZzDummy_bindings": {'z-out': ''}, +}) real_defaultcfg = zzdummy.idleConf.defaultCfg test_defaultcfg = { 'main': config.IdleUserConfParser(''), @@ -35,6 +37,9 @@ "ParenMatch": {'style': 'expression', 'flash-delay': '500', 'bell': 'True'}, } ) +test_defaultcfg["keys"].read_dict( + {name: dict(real_defaultcfg["keys"][name]) for name in real_defaultcfg["keys"]} +) code_sample = """\ class C1: @@ -103,6 +108,8 @@ def checklines(self, text, value): return actual def test_exists(self): + self.assertEqual(zzdummy.idleConf.GetSectionList('user', 'extensions'), ['ZzDummy', 'ZzDummy_cfgBindings', 'ZzDummy_bindings']) + self.assertEqual(zzdummy.idleConf.GetSectionList('default', 'extensions'), ['AutoComplete', 'CodeContext', 'FormatParagraph', 'ParenMatch']) self.assertIn("ZzDummy", zzdummy.idleConf.GetExtensions()) def test_init(self): From 29961f3103f59c253c0c9ebbb5b7a50ca35e55b4 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Mon, 11 Mar 2024 22:52:47 -0500 Subject: [PATCH 17/17] Add tests for reading extension keys --- Lib/idlelib/idle_test/test_zzdummy.py | 16 +++++++++++++ Lib/idlelib/idle_test/test_zzdummy_user.py | 27 +++++++++++++--------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/Lib/idlelib/idle_test/test_zzdummy.py b/Lib/idlelib/idle_test/test_zzdummy.py index dca90e585cb3ad..3ca3ed53d00f5c 100644 --- a/Lib/idlelib/idle_test/test_zzdummy.py +++ b/Lib/idlelib/idle_test/test_zzdummy.py @@ -86,6 +86,22 @@ def test_exists(self): self.assertEqual(zzdummy.idleConf.GetSectionList('user', 'extensions'), []) self.assertEqual(zzdummy.idleConf.GetSectionList('default', 'extensions'), ['AutoComplete', 'CodeContext', 'FormatParagraph', 'ParenMatch', 'ZzDummy', 'ZzDummy_cfgBindings', 'ZzDummy_bindings']) self.assertIn("ZzDummy", zzdummy.idleConf.GetExtensions(False)) + self.assertNotIn("ZzDummy", zzdummy.idleConf.GetExtensions()) + self.assertEqual(zzdummy.idleConf.GetExtensionKeys("ZzDummy"), {}) + self.assertEqual(zzdummy.idleConf.GetExtensionBindings("ZzDummy"), {'<>': ['']}) + + def test_exists_user(self): + zzdummy.idleConf.userCfg["extensions"].read_dict({ + "ZzDummy": {'enable': 'True'} + }) + self.assertEqual(zzdummy.idleConf.GetSectionList('user', 'extensions'), ["ZzDummy"]) + self.assertIn("ZzDummy", zzdummy.idleConf.GetExtensions()) + self.assertEqual(zzdummy.idleConf.GetExtensionKeys("ZzDummy"), {'<>': ['']}) + self.assertEqual(zzdummy.idleConf.GetExtensionBindings("ZzDummy"), {'<>': [''], '<>': ['']}) + # Restore + zzdummy.idleConf.userCfg["extensions"].read_dict({ + "ZzDummy": {'enable': 'False'} + }) def test_init(self): zz = self.zz diff --git a/Lib/idlelib/idle_test/test_zzdummy_user.py b/Lib/idlelib/idle_test/test_zzdummy_user.py index a76673d6ac1813..ebe401bc847a24 100644 --- a/Lib/idlelib/idle_test/test_zzdummy_user.py +++ b/Lib/idlelib/idle_test/test_zzdummy_user.py @@ -29,17 +29,20 @@ 'keys': config.IdleUserConfParser(''), 'extensions': config.IdleUserConfParser(''), } -test_defaultcfg["extensions"].read_dict( - { - "AutoComplete": {'popupwait': '2000'}, - "CodeContext": {'maxlines': '15'}, - "FormatParagraph": {'max-width': '72'}, - "ParenMatch": {'style': 'expression', 'flash-delay': '500', 'bell': 'True'}, - } -) -test_defaultcfg["keys"].read_dict( - {name: dict(real_defaultcfg["keys"][name]) for name in real_defaultcfg["keys"]} -) +test_defaultcfg["extensions"].read_dict({ + "AutoComplete": {'popupwait': '2000'}, + "CodeContext": {'maxlines': '15'}, + "FormatParagraph": {'max-width': '72'}, + "ParenMatch": {'style': 'expression', 'flash-delay': '500', 'bell': 'True'}, +}) +test_defaultcfg["main"].read_dict({ + "Theme": {"default": 1, "name": "IDLE Classic", "name2": ""}, + "Keys": {"default": 1, "name": "IDLE Classic", "name2": ""}, +}) +for key in ("keys",): + real_default = real_defaultcfg[key] + value = {name: dict(real_default[name]) for name in real_default} + test_defaultcfg[key].read_dict(value) code_sample = """\ class C1: @@ -111,6 +114,8 @@ def test_exists(self): self.assertEqual(zzdummy.idleConf.GetSectionList('user', 'extensions'), ['ZzDummy', 'ZzDummy_cfgBindings', 'ZzDummy_bindings']) self.assertEqual(zzdummy.idleConf.GetSectionList('default', 'extensions'), ['AutoComplete', 'CodeContext', 'FormatParagraph', 'ParenMatch']) self.assertIn("ZzDummy", zzdummy.idleConf.GetExtensions()) + self.assertEqual(zzdummy.idleConf.GetExtensionKeys("ZzDummy"), {'<>': ['']}) + self.assertEqual(zzdummy.idleConf.GetExtensionBindings("ZzDummy"), {'<>': [''], '<>': ['']}) def test_init(self): zz = self.zz pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy