@@ -850,6 +850,79 @@ def test_history_navigation_with_down_arrow(self):
850850 self .assertEqual (output , "1+1" )
851851 self .assert_screen_equal (reader , "1+1" , clean = True )
852852
853+ def test_history_navigation_with_ctrl_up (self ):
854+ # Submit two multiline blocks, then use ctrl+up to jump directly
855+ # to the previous history entry (unlike up which moves line-by-line)
856+ code = "def foo():\n x = 1\n \n def bar():\n y = 2\n \n "
857+ events = itertools .chain (
858+ code_to_events (code ),
859+ [
860+ # Single ctrl+up should recall the entire previous entry
861+ Event (evt = "key" , data = "ctrl up" , raw = bytearray (b"\x1b [1;5A" )),
862+ Event (evt = "key" , data = "\n " , raw = bytearray (b"\n " )),
863+ Event (evt = "key" , data = "\n " , raw = bytearray (b"\n " )),
864+ ],
865+ )
866+
867+ reader = self .prepare_reader (events )
868+
869+ output = multiline_input (reader )
870+ self .assertEqual (output , "def foo():\n x = 1\n " )
871+ output = multiline_input (reader )
872+ self .assertEqual (output , "def bar():\n y = 2\n " )
873+ # One ctrl+up jumps straight to previous history item
874+ output = multiline_input (reader )
875+ self .assertEqual (output , "def foo():\n x = 1\n " )
876+
877+ def test_history_navigation_with_ctrl_down (self ):
878+ # Submit two multiline entries, ctrl+up twice then ctrl+down once
879+ # With multiline entries, regular down would only move one line,
880+ # but ctrl+down jumps to the next history entry entirely.
881+ code = "def foo():\n x = 1\n \n def bar():\n y = 2\n \n "
882+ events = itertools .chain (
883+ code_to_events (code ),
884+ [
885+ Event (evt = "key" , data = "ctrl up" , raw = bytearray (b"\x1b [1;5A" )),
886+ Event (evt = "key" , data = "ctrl up" , raw = bytearray (b"\x1b [1;5A" )),
887+ Event (evt = "key" , data = "ctrl down" , raw = bytearray (b"\x1b [1;5B" )),
888+ Event (evt = "key" , data = "\n " , raw = bytearray (b"\n " )),
889+ Event (evt = "key" , data = "\n " , raw = bytearray (b"\n " )),
890+ ],
891+ )
892+
893+ reader = self .prepare_reader (events )
894+
895+ output = multiline_input (reader )
896+ self .assertEqual (output , "def foo():\n x = 1\n " )
897+ output = multiline_input (reader )
898+ self .assertEqual (output , "def bar():\n y = 2\n " )
899+ # ctrl+up twice (to foo), ctrl+down once (back to bar)
900+ output = multiline_input (reader )
901+ self .assertEqual (output , "def bar():\n y = 2\n " )
902+
903+ def test_ctrl_up_at_start_of_history (self ):
904+ # Submit a multiline block, then try ctrl+up twice
905+ # (second one should error since we're already at the oldest entry)
906+ code = "def foo():\n x = 1\n \n "
907+ events = itertools .chain (
908+ code_to_events (code ),
909+ [
910+ # Already at oldest item; second ctrl+up should error but not crash
911+ Event (evt = "key" , data = "ctrl up" , raw = bytearray (b"\x1b [1;5A" )),
912+ Event (evt = "key" , data = "ctrl up" , raw = bytearray (b"\x1b [1;5A" )),
913+ Event (evt = "key" , data = "\n " , raw = bytearray (b"\n " )),
914+ Event (evt = "key" , data = "\n " , raw = bytearray (b"\n " )),
915+ ],
916+ )
917+
918+ reader = self .prepare_reader (events )
919+
920+ output = multiline_input (reader )
921+ self .assertEqual (output , "def foo():\n x = 1\n " )
922+ # Second ctrl+up at start of history stays on first item
923+ output = multiline_input (reader )
924+ self .assertEqual (output , "def foo():\n x = 1\n " )
925+
853926 def test_history_search (self ):
854927 events = itertools .chain (
855928 code_to_events ("1+1\n 2+2\n 3+3\n " ),
0 commit comments