Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
32 changes: 31 additions & 1 deletion Lib/idlelib/idle_test/test_multicall.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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('<<test-good>>', '<Control-Key-Up>')
mctext.bind('<<test-good>>', lambda e: None)
self.assertEqual(stderr.getvalue(), '')

def test_invalid_triplet_binding(self):
# gh-55646: '<Control-Key-up>' 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('<<test-bad>>', '<Control-Key-up>') # Must not raise.
mctext.bind('<<test-bad>>', 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: '<Foo-Key-Up>' 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('<<test-bad>>', '<Foo-Key-Up>') # Must not raise.
mctext.bind('<<test-bad>>', 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.
Expand Down
35 changes: 32 additions & 3 deletions Lib/idlelib/multicall.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix IDLE crash at startup when the user configuration contains an invalid key
binding, such as ``<Alt-Key-up>`` instead of ``<Alt-Key-Up>``. The invalid
binding is now ignored with a warning.
Loading