diff --git a/src/prompt_toolkit/input/ansi_escape_sequences.py b/src/prompt_toolkit/input/ansi_escape_sequences.py index 1fba418b7..1e81dbb9c 100644 --- a/src/prompt_toolkit/input/ansi_escape_sequences.py +++ b/src/prompt_toolkit/input/ansi_escape_sequences.py @@ -185,10 +185,10 @@ "\x1b[5;6~": Keys.ControlShiftPageUp, "\x1b[6;6~": Keys.ControlShiftPageDown, "\x1b[2;7~": (Keys.Escape, Keys.ControlInsert), - "\x1b[5;7~": (Keys.Escape, Keys.ControlPageDown), + "\x1b[5;7~": (Keys.Escape, Keys.ControlPageUp), "\x1b[6;7~": (Keys.Escape, Keys.ControlPageDown), "\x1b[2;8~": (Keys.Escape, Keys.ControlShiftInsert), - "\x1b[5;8~": (Keys.Escape, Keys.ControlShiftPageDown), + "\x1b[5;8~": (Keys.Escape, Keys.ControlShiftPageUp), "\x1b[6;8~": (Keys.Escape, Keys.ControlShiftPageDown), # -- # Arrows. @@ -232,8 +232,8 @@ "\x1b[1;3F": (Keys.Escape, Keys.End), "\x1b[1;3H": (Keys.Escape, Keys.Home), # Alt+shift+number. - "\x1b[1;4A": (Keys.Escape, Keys.ShiftDown), - "\x1b[1;4B": (Keys.Escape, Keys.ShiftUp), + "\x1b[1;4A": (Keys.Escape, Keys.ShiftUp), + "\x1b[1;4B": (Keys.Escape, Keys.ShiftDown), "\x1b[1;4C": (Keys.Escape, Keys.ShiftRight), "\x1b[1;4D": (Keys.Escape, Keys.ShiftLeft), "\x1b[1;4F": (Keys.Escape, Keys.ShiftEnd), @@ -255,22 +255,22 @@ "\x1bOc": Keys.ControlRight, # rxvt "\x1bOd": Keys.ControlLeft, # rxvt # Control + shift + arrows. - "\x1b[1;6A": Keys.ControlShiftDown, - "\x1b[1;6B": Keys.ControlShiftUp, + "\x1b[1;6A": Keys.ControlShiftUp, + "\x1b[1;6B": Keys.ControlShiftDown, "\x1b[1;6C": Keys.ControlShiftRight, "\x1b[1;6D": Keys.ControlShiftLeft, "\x1b[1;6F": Keys.ControlShiftEnd, "\x1b[1;6H": Keys.ControlShiftHome, # Control + Meta + arrows. - "\x1b[1;7A": (Keys.Escape, Keys.ControlDown), - "\x1b[1;7B": (Keys.Escape, Keys.ControlUp), + "\x1b[1;7A": (Keys.Escape, Keys.ControlUp), + "\x1b[1;7B": (Keys.Escape, Keys.ControlDown), "\x1b[1;7C": (Keys.Escape, Keys.ControlRight), "\x1b[1;7D": (Keys.Escape, Keys.ControlLeft), "\x1b[1;7F": (Keys.Escape, Keys.ControlEnd), "\x1b[1;7H": (Keys.Escape, Keys.ControlHome), # Meta + Shift + arrows. - "\x1b[1;8A": (Keys.Escape, Keys.ControlShiftDown), - "\x1b[1;8B": (Keys.Escape, Keys.ControlShiftUp), + "\x1b[1;8A": (Keys.Escape, Keys.ControlShiftUp), + "\x1b[1;8B": (Keys.Escape, Keys.ControlShiftDown), "\x1b[1;8C": (Keys.Escape, Keys.ControlShiftRight), "\x1b[1;8D": (Keys.Escape, Keys.ControlShiftLeft), "\x1b[1;8F": (Keys.Escape, Keys.ControlShiftEnd), diff --git a/tests/test_inputstream.py b/tests/test_inputstream.py index ab1b03689..62708a5ce 100644 --- a/tests/test_inputstream.py +++ b/tests/test_inputstream.py @@ -139,3 +139,48 @@ def test_cpr_response_2(processor, stream): assert len(processor.keys) == 2 assert processor.keys[0].key == Keys.CPRResponse assert processor.keys[1].key == Keys.ControlJ + + +def _key_names(value): + "Flatten an ANSI_SEQUENCES value into the key names it resolves to." + if isinstance(value, tuple): + return [k.name for k in value] + return [value.name if hasattr(value, "name") else str(value)] + + +@pytest.mark.parametrize( + "final,direction", + [("A", "Up"), ("B", "Down"), ("C", "Right"), ("D", "Left")], +) +def test_modified_cursor_keys_keep_their_direction(final, direction): + """CSI 1 ; A is cursor-up at every modifier level, and so on for B/C/D. + + Regression test for https://github.com/prompt-toolkit/python-prompt-toolkit/issues/2092 + where A and B were transposed at modifiers 4, 6, 7 and 8 but correct at 2, 3, 5 and 9, + so Ctrl+Shift+Up moved the cursor down. + """ + from prompt_toolkit.input.ansi_escape_sequences import ANSI_SEQUENCES + + for modifier in range(2, 17): + value = ANSI_SEQUENCES.get(f"\x1b[1;{modifier}{final}") + if value is None: + continue + names = _key_names(value) + assert any(direction in name for name in names), ( + f"\\x1b[1;{modifier}{final} should name {direction}, got {names}" + ) + + +@pytest.mark.parametrize("number,direction", [(5, "PageUp"), (6, "PageDown")]) +def test_modified_page_keys_keep_their_direction(number, direction): + """CSI 5 ; ~ is PageUp and CSI 6 ; ~ is PageDown, at every level.""" + from prompt_toolkit.input.ansi_escape_sequences import ANSI_SEQUENCES + + for modifier in range(2, 17): + value = ANSI_SEQUENCES.get(f"\x1b[{number};{modifier}~") + if value is None: + continue + names = _key_names(value) + assert any(direction in name for name in names), ( + f"\\x1b[{number};{modifier}~ should name {direction}, got {names}" + )