Skip to content

Commit 2866d37

Browse files
committed
feat: update version to 0.6.0 and add input pane functionality
1 parent 08a7eec commit 2866d37

File tree

6 files changed

+311
-18
lines changed

6 files changed

+311
-18
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "crustty"
3-
version = "0.5.1"
3+
version = "0.6.0"
44
edition = "2021"
55
authors = ["Sean Yang <sean@seanyang.me>"]
66
description = "A time-travel C interpreter with memory visualization"

src/ui/app.rs

Lines changed: 123 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use crate::interpreter::engine::Interpreter;
44
use crate::interpreter::errors::RuntimeError;
55
use crate::parser::ast::SourceLocation;
6+
use crate::snapshot::{TerminalLine, TerminalLineKind};
67
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
78
use ratatui::{
89
backend::Backend,
@@ -19,25 +20,42 @@ pub enum FocusedPane {
1920
Stack,
2021
Heap,
2122
Terminal,
23+
Input,
2224
}
2325

2426
impl 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

145171
impl 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

Comments
 (0)