33use crate :: interpreter:: engine:: Interpreter ;
44use crate :: interpreter:: errors:: RuntimeError ;
55use crate :: parser:: ast:: SourceLocation ;
6+ use crate :: snapshot:: { TerminalLine , TerminalLineKind } ;
67use crossterm:: event:: { self , Event , KeyCode , KeyEvent , KeyEventKind } ;
78use ratatui:: {
89 backend:: Backend ,
@@ -19,25 +20,42 @@ pub enum FocusedPane {
1920 Stack ,
2021 Heap ,
2122 Terminal ,
23+ Input ,
2224}
2325
2426impl FocusedPane {
25- /// Move focus to the next pane (clockwise: source -> terminal -> stack -> heap)
26- pub fn next ( self ) -> Self {
27+ /// Move focus to the next pane (clockwise: source -> terminal -> input -> stack -> heap)
28+ /// When `has_input` is false the Input pane is skipped.
29+ pub fn next ( self , has_input : bool ) -> Self {
2730 match self {
2831 FocusedPane :: Source => FocusedPane :: Terminal ,
29- FocusedPane :: Terminal => FocusedPane :: Stack ,
32+ FocusedPane :: Terminal => {
33+ if has_input {
34+ FocusedPane :: Input
35+ } else {
36+ FocusedPane :: Stack
37+ }
38+ }
39+ FocusedPane :: Input => FocusedPane :: Stack ,
3040 FocusedPane :: Stack => FocusedPane :: Heap ,
3141 FocusedPane :: Heap => FocusedPane :: Source ,
3242 }
3343 }
3444
3545 /// Move focus to the previous pane (counter-clockwise)
36- pub fn prev ( self ) -> Self {
46+ /// When `has_input` is false the Input pane is skipped.
47+ pub fn prev ( self , has_input : bool ) -> Self {
3748 match self {
3849 FocusedPane :: Source => FocusedPane :: Heap ,
3950 FocusedPane :: Terminal => FocusedPane :: Source ,
40- FocusedPane :: Stack => FocusedPane :: Terminal ,
51+ FocusedPane :: Input => FocusedPane :: Terminal ,
52+ FocusedPane :: Stack => {
53+ if has_input {
54+ FocusedPane :: Input
55+ } else {
56+ FocusedPane :: Terminal
57+ }
58+ }
4159 FocusedPane :: Heap => FocusedPane :: Stack ,
4260 }
4361 }
@@ -119,6 +137,8 @@ pub struct App {
119137 pub heap_scroll : super :: panes:: HeapScrollState ,
120138 /// Terminal scroll offset
121139 pub terminal_scroll : super :: panes:: TerminalScrollState ,
140+ /// Input pane scroll state
141+ pub input_scroll : super :: panes:: InputScrollState ,
122142
123143 /// Whether the app should quit
124144 pub should_quit : bool ,
@@ -140,6 +160,12 @@ pub struct App {
140160
141161 /// Typed text buffered while in scanf input mode
142162 pub scanf_input_buffer : String ,
163+
164+ /// The full original stdin input (all lines typed by user)
165+ pub original_stdin_input : String ,
166+
167+ /// All input terminal lines, preserved across snapshot navigation
168+ pub all_input_lines : Vec < TerminalLine > ,
143169}
144170
145171impl App {
@@ -162,6 +188,7 @@ impl App {
162188 prev_item_count : 0 ,
163189 } ,
164190 terminal_scroll : super :: panes:: TerminalScrollState { offset : 0 } ,
191+ input_scroll : super :: panes:: InputScrollState { offset : 0 } ,
165192 should_quit : false ,
166193 status_message : String :: from ( "Ready!" ) ,
167194 error_state : None ,
@@ -171,6 +198,8 @@ impl App {
171198 . checked_sub ( Duration :: from_secs ( 1 ) )
172199 . unwrap_or ( Instant :: now ( ) ) ,
173200 scanf_input_buffer : String :: new ( ) ,
201+ original_stdin_input : String :: new ( ) ,
202+ all_input_lines : Vec :: new ( ) ,
174203 }
175204 }
176205
@@ -206,6 +235,25 @@ impl App {
206235 }
207236 }
208237
238+ /// Sync all input lines from the interpreter's current terminal into
239+ /// the persistent `all_input_lines` list so they survive stepping back.
240+ fn sync_input_lines ( & mut self ) {
241+ self . all_input_lines = self
242+ . interpreter
243+ . terminal ( )
244+ . lines
245+ . iter ( )
246+ . filter ( |l| l. kind == TerminalLineKind :: Input )
247+ . cloned ( )
248+ . collect ( ) ;
249+ }
250+
251+ /// Whether the program uses scanf (has ever received input or is waiting for it).
252+ fn has_scanf_input ( & self ) -> bool {
253+ !self . all_input_lines . is_empty ( )
254+ || self . interpreter . is_paused_at_scanf ( )
255+ }
256+
209257 /// The total number of snapshots, or `None` when execution is paused at scanf.
210258 fn total_steps_display ( & self ) -> Option < usize > {
211259 if self . interpreter . is_paused_at_scanf ( ) {
@@ -314,16 +362,25 @@ impl App {
314362 ] )
315363 . split ( columns[ 0 ] ) ;
316364
317- let right_rows = Layout :: default ( )
318- . direction ( Direction :: Vertical )
319- . constraints ( [
320- Constraint :: Percentage ( 50 ) ,
321- Constraint :: Percentage ( 50 ) ,
322- ] )
323- . split ( columns[ 1 ] ) ;
365+ let show_input_pane = self . has_scanf_input ( ) ;
324366
325367 let scanf_mode = self . is_in_scanf_input_mode ( ) ;
326368
369+ // If the input pane is visible, split the lower-left area; otherwise
370+ // the terminal gets the full width.
371+ let ( terminal_area, input_area) = if show_input_pane {
372+ let lower_left_chunks = Layout :: default ( )
373+ . direction ( Direction :: Horizontal )
374+ . constraints ( [
375+ Constraint :: Percentage ( 60 ) ,
376+ Constraint :: Percentage ( 40 ) ,
377+ ] )
378+ . split ( left_rows[ 1 ] ) ;
379+ ( lower_left_chunks[ 0 ] , Some ( lower_left_chunks[ 1 ] ) )
380+ } else {
381+ ( left_rows[ 1 ] , None )
382+ } ;
383+
327384 super :: panes:: render_source_pane (
328385 frame,
329386 left_rows[ 0 ] ,
@@ -337,9 +394,10 @@ impl App {
337394 } ,
338395 ) ;
339396
397+ // Terminal pane
340398 super :: panes:: render_terminal_pane (
341399 frame,
342- left_rows [ 1 ] ,
400+ terminal_area ,
343401 super :: panes:: TerminalRenderData {
344402 terminal : self . interpreter . terminal ( ) ,
345403 is_focused : self . focused_pane == FocusedPane :: Terminal ,
@@ -348,6 +406,36 @@ impl App {
348406 input_buffer : & self . scanf_input_buffer ,
349407 } ,
350408 ) ;
409+ // Input pane (only for programs that use scanf)
410+ if let Some ( input_area) = input_area {
411+ let active_input_count = self
412+ . interpreter
413+ . terminal ( )
414+ . lines
415+ . iter ( )
416+ . filter ( |l| l. kind == TerminalLineKind :: Input )
417+ . count ( ) ;
418+ super :: panes:: render_input_pane (
419+ frame,
420+ input_area,
421+ super :: panes:: InputRenderData {
422+ all_input_lines : & self . all_input_lines ,
423+ active_count : active_input_count,
424+ is_focused : self . focused_pane == FocusedPane :: Input ,
425+ source_code : & self . source_code ,
426+ scroll_state : & mut self . input_scroll ,
427+ } ,
428+ ) ;
429+ }
430+
431+ // Right column: split vertically for stack (top) and heap (bottom)
432+ let right_rows = Layout :: default ( )
433+ . direction ( Direction :: Vertical )
434+ . constraints ( [
435+ Constraint :: Percentage ( 50 ) ,
436+ Constraint :: Percentage ( 50 ) ,
437+ ] )
438+ . split ( columns[ 1 ] ) ;
351439
352440 super :: panes:: render_stack_pane (
353441 frame,
@@ -404,8 +492,16 @@ impl App {
404492 match key. code {
405493 KeyCode :: Enter => {
406494 let input = std:: mem:: take ( & mut self . scanf_input_buffer ) ;
495+ if !input. is_empty ( ) {
496+ if !self . original_stdin_input . is_empty ( ) {
497+ self . original_stdin_input . push ( '\n' ) ;
498+ }
499+ self . original_stdin_input . push_str ( & input) ;
500+ }
407501 match self . interpreter . provide_scanf_input ( input) {
408502 Ok ( ( ) ) => {
503+ // Capture any new input lines from the terminal
504+ self . sync_input_lines ( ) ;
409505 self . terminal_scroll . offset = usize:: MAX ;
410506 // If another scanf is immediately reached, stay in input mode
411507 self . check_and_activate_scanf_mode ( ) ;
@@ -478,10 +574,12 @@ impl App {
478574 self . step_back_over ( ) ;
479575 }
480576 KeyCode :: Tab => {
481- self . focused_pane = self . focused_pane . next ( ) ;
577+ let has_input = self . has_scanf_input ( ) ;
578+ self . focused_pane = self . focused_pane . next ( has_input) ;
482579 }
483580 KeyCode :: BackTab => {
484- self . focused_pane = self . focused_pane . prev ( ) ;
581+ let has_input = self . has_scanf_input ( ) ;
582+ self . focused_pane = self . focused_pane . prev ( has_input) ;
485583 }
486584 KeyCode :: Left => {
487585 self . is_playing = false ;
@@ -516,6 +614,12 @@ impl App {
516614 self . terminal_scroll . offset . saturating_sub ( 1 ) ;
517615 }
518616 }
617+ FocusedPane :: Input => {
618+ if self . input_scroll . offset > 0 {
619+ self . input_scroll . offset =
620+ self . input_scroll . offset . saturating_sub ( 1 ) ;
621+ }
622+ }
519623 } ,
520624 KeyCode :: Down => match self . focused_pane {
521625 FocusedPane :: Source => {
@@ -536,6 +640,10 @@ impl App {
536640 self . terminal_scroll . offset =
537641 self . terminal_scroll . offset . saturating_add ( 1 ) ;
538642 }
643+ FocusedPane :: Input => {
644+ self . input_scroll . offset =
645+ self . input_scroll . offset . saturating_add ( 1 ) ;
646+ }
539647 } ,
540648 KeyCode :: Char ( ' ' ) => {
541649 if self . last_space_press . elapsed ( ) >= Duration :: from_millis ( 200 )
0 commit comments