diff --git a/Lib/idlelib/idle_test/test_multicall.py b/Lib/idlelib/idle_test/test_multicall.py index fcce2030579f4b..db66fdf14ba209 100644 --- a/Lib/idlelib/idle_test/test_multicall.py +++ b/Lib/idlelib/idle_test/test_multicall.py @@ -2,7 +2,7 @@ from idlelib import multicall import unittest -from test.support import requires +from test.support import requires, captured_stderr from tkinter import Tk, Text @@ -43,6 +43,36 @@ def test_yview(self): mctext = self.mc(self.root) self.assertIs(mctext.yview.__func__, Text.yview) + def test_valid_binding(self): + # A valid key binding must bind without a warning (cf. gh-55646). + mctext = self.mc(self.root) + with captured_stderr() as stderr: + mctext.event_add('<>', '') + mctext.bind('<>', lambda e: None) + self.assertEqual(stderr.getvalue(), '') + + def test_invalid_triplet_binding(self): + # gh-55646: '' parses into a triplet on every platform, + # so Tk rejects it in bind(). + mctext = self.mc(self.root) + with captured_stderr() as stderr: + mctext.event_add('<>', '') # Must not raise. + mctext.bind('<>', lambda e: None) # Must not raise. + warning = stderr.getvalue() + self.assertIn('invalid key binding', warning) + self.assertIn('test-bad', warning) # The offending action is named. + + def test_invalid_nontriplet_binding(self): + # gh-55646: '' has no valid modifier, so MultiCall does not + # parse it and falls back to Tk's event_add, which rejects it. + mctext = self.mc(self.root) + with captured_stderr() as stderr: + mctext.event_add('<>', '') # Must not raise. + mctext.bind('<>', lambda e: None) # Must not raise. + warning = stderr.getvalue() + self.assertIn('invalid key binding', warning) + self.assertIn('test-bad', warning) # The offending action is named. + def test_event_delete_unbound_sequence(self): # gh-89360: deleting a sequence that was not added to a virtual # event is ignored instead of raising ValueError. diff --git a/Lib/idlelib/multicall.py b/Lib/idlelib/multicall.py index 95f4fee7fbe740..5589884e2af78f 100644 --- a/Lib/idlelib/multicall.py +++ b/Lib/idlelib/multicall.py @@ -310,6 +310,17 @@ def _triplet_to_sequence(triplet): else: return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'>' + +def warn_bad_binding(virtual, sequence, err): + # gh-55646: warn instead of crashing on an invalid key binding. + action = virtual[2:-2] if virtual[:2] == '<<' and virtual[-2:] == '>>' \ + else virtual + print(f'Warning: ignoring invalid key binding {sequence!r} ' + f'for {action!r}: {err}. ' + f'Please reconfigure it in the IDLE Settings dialog.', + file=sys.stderr) + + _multicall_dict = {} def MultiCallCreator(widget): """Return a MultiCall class which inherits its methods from the @@ -343,8 +354,17 @@ def bind(self, sequence=None, func=None, add=None): self.__binders[triplet[1]].unbind(triplet, ei[0]) ei[0] = func if ei[0] is not None: + bad = [] for triplet in ei[1]: - self.__binders[triplet[1]].bind(triplet, func) + try: + self.__binders[triplet[1]].bind(triplet, func) + except tkinter.TclError as err: + warn_bad_binding(sequence, + _triplet_to_sequence(triplet), + err) + bad.append(triplet) + for triplet in bad: # Drop the invalid sequences. + ei[1].remove(triplet) else: self.__eventinfo[sequence] = [func, []] return widget.bind(self, sequence, func, add) @@ -371,10 +391,19 @@ def event_add(self, virtual, *sequences): triplet = _parse_sequence(seq) if triplet is None: #print("Tkinter event_add(%s)" % seq, file=sys.__stderr__) - widget.event_add(self, virtual, seq) + try: + widget.event_add(self, virtual, seq) + except tkinter.TclError as err: + warn_bad_binding(virtual, seq, err) + continue # Drop the invalid sequence. else: if func is not None: - self.__binders[triplet[1]].bind(triplet, func) + try: + self.__binders[triplet[1]].bind(triplet, func) + except tkinter.TclError as err: + warn_bad_binding(virtual, + _triplet_to_sequence(triplet), err) + continue # Drop the invalid sequence. triplets.append(triplet) def event_delete(self, virtual, *sequences): diff --git a/Misc/NEWS.d/next/IDLE/2026-07-01-21-00-00.gh-issue-55646.kBind9.rst b/Misc/NEWS.d/next/IDLE/2026-07-01-21-00-00.gh-issue-55646.kBind9.rst new file mode 100644 index 00000000000000..31e97b3c64bcfe --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2026-07-01-21-00-00.gh-issue-55646.kBind9.rst @@ -0,0 +1,3 @@ +Fix IDLE crash at startup when the user configuration contains an invalid key +binding, such as ```` instead of ````. The invalid +binding is now ignored with a warning.