https://github.com/python/cpython/commit/0b18efdf12c56b7b1f2080aa26c419b0014bde77
commit: 0b18efdf12c56b7b1f2080aa26c419b0014bde77
branch: main
author: Serhiy Storchaka <[email protected]>
committer: terryjreedy <[email protected]>
date: 2026-09-18T04:08:44-04:00
summary:

gh-56596: Make IDLE key bindings work with Caps Lock on (#157709)

Tk does not fold the case of letter keysyms, and Caps Lock changes it,
so bind each key sequence with the other case of its letters too.
---------

Co-authored-by: Terry Jan Reedy <[email protected]>

files:
A Misc/NEWS.d/next/IDLE/2026-09-17-23-00-00.gh-issue-56596.capslock.rst
M Lib/idlelib/editor.py
M Lib/idlelib/idle_test/test_editor.py

diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py
index 8e15319b5baab20..1d5e181f42f7645 100644
--- a/Lib/idlelib/editor.py
+++ b/Lib/idlelib/editor.py
@@ -34,6 +34,10 @@
 TK_TABWIDTH_DEFAULT = 8
 darwin = sys.platform == 'darwin'
 
+# A letter keysym in a key sequence, such as "s" in "<Control-Key-s>".
+_letter_key_re = re.compile(r'(?<=-Key-)[a-zA-Z](?=>)')
+
+
 class EditorWindow:
     is_shell = False  # PyShell overrides.
     from idlelib.percolator import Percolator
@@ -1186,6 +1190,12 @@ def apply_bindings(self, keydefs=None):
         for event, keylist in keydefs.items():
             if keylist:
                 text.event_add(event, *keylist)
+                # Caps Lock changes the case of letter keysyms, so bind
+                # the sequences with the other case too (gh-56596).
+                for keys in keylist:
+                    other = _letter_key_re.sub(lambda m: m[0].swapcase(), keys)
+                    if other not in keylist:
+                        text.event_add(event, other)
 
     def fill_menus(self, menudefs=None, keydefs=None):
         """Fill in dropdown menus used by this window.
diff --git a/Lib/idlelib/idle_test/test_editor.py 
b/Lib/idlelib/idle_test/test_editor.py
index e32981091b72a6e..2aaeb5037901b8f 100644
--- a/Lib/idlelib/idle_test/test_editor.py
+++ b/Lib/idlelib/idle_test/test_editor.py
@@ -31,6 +31,26 @@ def test_init(self):
         self.assertEqual(e.root, self.root)
         e._close()
 
+    def test_apply_bindings_caps_lock(self):
+        # gh-56596: Caps Lock changes the case of letter keysyms, so the
+        # sequences are bound with both cases.
+        e = Editor(root=self.root)
+        try:
+            e.apply_bindings({'<<spam>>': ('<Control-Key-s>', '<Key-F1>'),
+                              '<<eggs>>': 
('<Control-Key-x><Alt-Shift-Key-S>',),
+                              '<<ham>>': ('<Control-Key-h>', 
'<Control-Key-H>')})
+            self.assertEqual(set(e.text.event_info('<<spam>>')),
+                             {'<Control-KeyPress-s>', '<Control-KeyPress-S>',
+                              '<KeyPress-F1>'})
+            # Existing variants are not added again.
+            self.assertEqual(e.text.event_info('<<ham>>'),
+                             ('<Control-KeyPress-h>', '<Control-KeyPress-H>'))
+            self.assertEqual(set(e.text.event_info('<<eggs>>')),
+                             {'<Control-Key-x><Shift-Alt-Key-S>',
+                              '<Control-Key-X><Shift-Alt-Key-s>'})
+        finally:
+            e._close()
+
     def test_set_width_zero_char_width(self):
         # A zero-width '0' must not raise ZeroDivisionError (gh-90304).
         e = Editor(root=self.root)
diff --git 
a/Misc/NEWS.d/next/IDLE/2026-09-17-23-00-00.gh-issue-56596.capslock.rst 
b/Misc/NEWS.d/next/IDLE/2026-09-17-23-00-00.gh-issue-56596.capslock.rst
new file mode 100644
index 000000000000000..70878e4f8012638
--- /dev/null
+++ b/Misc/NEWS.d/next/IDLE/2026-09-17-23-00-00.gh-issue-56596.capslock.rst
@@ -0,0 +1,2 @@
+Make IDLE keyboard shortcuts with ascii letters work when Caps Lock is on even 
when only one
+of the upper and lowercase versions are defined.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to