Skip to content

gh-89520: IDLE - Make extentions use user's keys, not all defaults #28713

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 41 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
354ad86
Make extentions use user's keys, not all defaults
CoolCat467 Oct 3, 2021
b6d2782
📜🤖 Added by blurb_it.
blurb-it[bot] Oct 3, 2021
48e18d6
Fix config and dialog to use user config extentions
CoolCat467 Oct 4, 2021
d8a267c
Merge branch 'python:main' into patch-1
CoolCat467 Oct 4, 2021
e1e8cd9
Remove debug print I forgot
CoolCat467 Oct 6, 2021
e2a2528
Remove debug comment out lines
CoolCat467 Oct 6, 2021
8fe6ecd
Merge branch 'python:main' into patch-1
CoolCat467 Oct 7, 2021
539d63b
Merge branch 'python:main' into patch-1
CoolCat467 Oct 16, 2021
1dfacb9
Merge branch 'python:main' into patch-1
CoolCat467 Nov 11, 2021
4e2161b
Fix spelling
CoolCat467 Feb 1, 2022
b425b72
Revert previous change
CoolCat467 Feb 1, 2022
88d40e0
Merge branch 'python:main' into patch-1
CoolCat467 May 24, 2022
c835095
Merge branch 'main' into patch-1
CoolCat467 Aug 27, 2023
f0e856a
Add trailing newline
CoolCat467 Aug 27, 2023
54b6c54
Merge branch 'main' into patch-1
CoolCat467 Aug 27, 2023
73c984e
Merge branch 'python:main' into patch-1
CoolCat467 Nov 11, 2023
40d864b
Simplify and update from `idleuserextend`'s testing
CoolCat467 Nov 11, 2023
fc190e3
Revert always save changes
CoolCat467 Nov 11, 2023
a2eb4b6
Merge branch 'python:main' into patch-1
CoolCat467 Nov 14, 2023
dc46fb7
Revert unnecessary formatting changes
CoolCat467 Nov 17, 2023
59b6a25
Merge branch 'main' into patch-1
CoolCat467 Nov 17, 2023
016ce89
`user_list` -> `user`, as is no longer a list but a set.
CoolCat467 Nov 17, 2023
ba7218f
Merge branch 'main' into patch-1
CoolCat467 Jan 26, 2024
954d0b8
Merge branch 'main' into patch-1
CoolCat467 Mar 11, 2024
568cd80
Revert noted strictly format change
CoolCat467 Mar 12, 2024
0240959
Remove suppression for errors that will never happen
CoolCat467 Mar 12, 2024
c032cf8
Merge branch 'main' into patch-1
CoolCat467 Mar 12, 2024
2f47ecb
Add test for ZzDummy being loaded in extensions list
CoolCat467 Mar 12, 2024
3a9d1dd
Don't require enabled
CoolCat467 Mar 12, 2024
29961f3
Add tests for reading extension keys
CoolCat467 Mar 12, 2024
8336d96
Merge branch 'main' into patch-1
CoolCat467 Mar 29, 2024
350253f
Merge branch 'main' into patch-1
CoolCat467 Apr 6, 2024
d6c8947
Merge branch 'main' into patch-1
CoolCat467 May 7, 2024
e521eef
Merge branch 'main' into patch-1
CoolCat467 May 12, 2024
9e04329
Merge branch 'main' into patch-1
CoolCat467 Aug 16, 2024
c0aaf33
Merge branch 'main' into patch-1
CoolCat467 Oct 8, 2024
e142132
Merge branch 'main' into patch-1
CoolCat467 Nov 6, 2024
455e8df
Merge branch 'main' into patch-1
CoolCat467 Dec 23, 2024
344d555
Merge branch 'main' into patch-1
CoolCat467 Feb 24, 2025
d814f03
Merge branch 'main' into patch-1
CoolCat467 Apr 18, 2025
7d0f5ac
Merge branch 'main' into patch-1
CoolCat467 Aug 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 74 additions & 38 deletions Lib/idlelib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from configparser import ConfigParser
import os
import sys
from collections import ChainMap

from tkinter.font import Font
import idlelib
Expand Down Expand Up @@ -469,61 +470,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 = {}
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
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 = {}
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
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
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

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.
Expand Down Expand Up @@ -758,7 +793,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()
Comment on lines +796 to +797
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change looks wrong, as it would load any garbage that users add to the user file. Since it affects all confit files, not obvious related to this PR.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change allows keys that are not in the default configuration to be loaded from user configuration. This means sections like extensions that might not be in the default config can be loaded.


def SaveUserCfgFiles(self):
"Write all loaded user configuration files to disk."
Expand Down
31 changes: 22 additions & 9 deletions Lib/idlelib/configdialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -1960,12 +1960,15 @@ 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 = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
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
Expand All @@ -1975,8 +1978,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:
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'
Expand All @@ -1988,9 +1995,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:
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)
Expand Down Expand Up @@ -2054,10 +2066,11 @@ 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):

# 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)

Expand Down
5 changes: 2 additions & 3 deletions Lib/idlelib/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -907,9 +907,8 @@ def RemoveKeybindings(self):
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():
self.text.event_delete(event, *keylist)
for event, keylist in xkeydefs.items():
self.text.event_delete(event, *keylist)

def ApplyKeybindings(self):
"""Apply the virtual, configurable keybindings.
Expand Down
21 changes: 21 additions & 0 deletions Lib/idlelib/idle_test/test_zzdummy.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,27 @@ def checklines(self, text, value):
actual.append(txt.startswith(value))
return actual

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"), {'<<z-out>>': ['<Control-Shift-KeyRelease-Delete>']})

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"), {'<<z-in>>': ['<Control-Shift-KeyRelease-Insert>']})
self.assertEqual(zzdummy.idleConf.GetExtensionBindings("ZzDummy"), {'<<z-in>>': ['<Control-Shift-KeyRelease-Insert>'], '<<z-out>>': ['<Control-Shift-KeyRelease-Delete>']})
# Restore
zzdummy.idleConf.userCfg["extensions"].read_dict({
"ZzDummy": {'enable': 'False'}
})

def test_init(self):
zz = self.zz
self.assertEqual(zz.editwin, self.editor)
Expand Down
Loading
Loading
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