toon_format/tui/state/
repl_state.rs1use std::collections::HashMap;
4
5#[derive(Debug, Clone)]
7pub struct ReplState {
8 pub active: bool,
10 pub input: String,
12 pub output: Vec<ReplLine>,
14 pub variables: HashMap<String, String>,
16 pub history: Vec<String>,
18 pub history_index: Option<usize>,
20 pub last_result: Option<String>,
22 pub scroll_offset: usize,
24}
25
26#[derive(Debug, Clone)]
28pub struct ReplLine {
29 pub kind: ReplLineKind,
30 pub content: String,
31}
32
33#[derive(Debug, Clone, PartialEq)]
34pub enum ReplLineKind {
35 Prompt,
36 Success,
37 Error,
38 Info,
39}
40
41impl ReplState {
42 pub fn new() -> Self {
43 Self {
44 active: false,
45 input: String::new(),
46 output: vec![ReplLine {
47 kind: ReplLineKind::Info,
48 content: "TOON REPL - Type 'help' for commands, 'exit' to close".to_string(),
49 }],
50 variables: HashMap::new(),
51 history: Vec::new(),
52 history_index: None,
53 last_result: None,
54 scroll_offset: 0,
55 }
56 }
57
58 pub fn activate(&mut self) {
59 self.active = true;
60 self.input.clear();
61 self.history_index = None;
62 }
63
64 pub fn deactivate(&mut self) {
65 self.active = false;
66 self.input.clear();
67 self.history_index = None;
68 }
69
70 pub fn add_prompt(&mut self, cmd: &str) {
71 self.output.push(ReplLine {
72 kind: ReplLineKind::Prompt,
73 content: format!("> {cmd}"),
74 });
75 }
76
77 pub fn add_success(&mut self, msg: String) {
78 for line in msg.lines() {
79 self.output.push(ReplLine {
80 kind: ReplLineKind::Success,
81 content: line.to_string(),
82 });
83 }
84 }
85
86 pub fn add_error(&mut self, msg: String) {
87 self.output.push(ReplLine {
88 kind: ReplLineKind::Error,
89 content: format!("✗ {msg}"),
90 });
91 }
92
93 pub fn add_info(&mut self, msg: String) {
94 let content = if msg.is_empty() || msg.starts_with(" ") || msg.starts_with("📖") {
95 msg
96 } else {
97 format!("✓ {msg}")
98 };
99
100 self.output.push(ReplLine {
101 kind: ReplLineKind::Info,
102 content,
103 });
104 }
105
106 pub fn add_to_history(&mut self, cmd: String) {
107 if cmd.trim().is_empty() {
108 return;
109 }
110 if self.history.last() == Some(&cmd) {
111 return;
112 }
113 self.history.push(cmd);
114 if self.history.len() > 100 {
115 self.history.remove(0);
116 }
117 }
118
119 pub fn history_up(&mut self) {
120 if self.history.is_empty() {
121 return;
122 }
123 let new_index = match self.history_index {
124 None => Some(self.history.len() - 1),
125 Some(0) => Some(0),
126 Some(i) => Some(i - 1),
127 };
128 if let Some(idx) = new_index {
129 self.input = self.history[idx].clone();
130 self.history_index = new_index;
131 }
132 }
133
134 pub fn history_down(&mut self) {
135 match self.history_index {
136 None => (),
137 Some(i) if i >= self.history.len() - 1 => {
138 self.input.clear();
139 self.history_index = None;
140 }
141 Some(i) => {
142 let new_idx = i + 1;
143 self.input = self.history[new_idx].clone();
144 self.history_index = Some(new_idx);
145 }
146 }
147 }
148
149 pub fn scroll_up(&mut self) {
150 if self.scroll_offset > 0 {
151 self.scroll_offset -= 1;
152 }
153 }
154
155 pub fn scroll_down(&mut self, visible_lines: usize) {
156 let max_scroll = self.output.len().saturating_sub(visible_lines);
157 if self.scroll_offset < max_scroll {
158 self.scroll_offset += 1;
159 }
160 }
161
162 pub fn scroll_to_bottom(&mut self) {
163 if self.output.len() <= 30 {
164 self.scroll_offset = 0;
165 } else {
166 self.scroll_offset = self.output.len().saturating_sub(30);
167 }
168 }
169}
170
171impl Default for ReplState {
172 fn default() -> Self {
173 Self::new()
174 }
175}