use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct ReplState {
pub active: bool,
pub input: String,
pub output: Vec<ReplLine>,
pub variables: HashMap<String, String>,
pub history: Vec<String>,
pub history_index: Option<usize>,
pub last_result: Option<String>,
pub scroll_offset: usize,
}
#[derive(Debug, Clone)]
pub struct ReplLine {
pub kind: ReplLineKind,
pub content: String,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ReplLineKind {
Prompt,
Success,
Error,
Info,
}
impl ReplState {
pub fn new() -> Self {
Self {
active: false,
input: String::new(),
output: vec![ReplLine {
kind: ReplLineKind::Info,
content: "TOON REPL - Type 'help' for commands, 'exit' to close".to_string(),
}],
variables: HashMap::new(),
history: Vec::new(),
history_index: None,
last_result: None,
scroll_offset: 0,
}
}
pub fn activate(&mut self) {
self.active = true;
self.input.clear();
self.history_index = None;
}
pub fn deactivate(&mut self) {
self.active = false;
self.input.clear();
self.history_index = None;
}
pub fn add_prompt(&mut self, cmd: &str) {
self.output.push(ReplLine {
kind: ReplLineKind::Prompt,
content: format!("> {cmd}"),
});
}
pub fn add_success(&mut self, msg: String) {
for line in msg.lines() {
self.output.push(ReplLine {
kind: ReplLineKind::Success,
content: line.to_string(),
});
}
}
pub fn add_error(&mut self, msg: String) {
self.output.push(ReplLine {
kind: ReplLineKind::Error,
content: format!("✗ {msg}"),
});
}
pub fn add_info(&mut self, msg: String) {
let content = if msg.is_empty() || msg.starts_with(" ") || msg.starts_with("📖") {
msg
} else {
format!("✓ {msg}")
};
self.output.push(ReplLine {
kind: ReplLineKind::Info,
content,
});
}
pub fn add_to_history(&mut self, cmd: String) {
if cmd.trim().is_empty() {
return;
}
if self.history.last() == Some(&cmd) {
return;
}
self.history.push(cmd);
if self.history.len() > 100 {
self.history.remove(0);
}
}
pub fn history_up(&mut self) {
if self.history.is_empty() {
return;
}
let new_index = match self.history_index {
None => Some(self.history.len() - 1),
Some(0) => Some(0),
Some(i) => Some(i - 1),
};
if let Some(idx) = new_index {
self.input = self.history[idx].clone();
self.history_index = new_index;
}
}
pub fn history_down(&mut self) {
match self.history_index {
None => (),
Some(i) if i >= self.history.len() - 1 => {
self.input.clear();
self.history_index = None;
}
Some(i) => {
let new_idx = i + 1;
self.input = self.history[new_idx].clone();
self.history_index = Some(new_idx);
}
}
}
pub fn scroll_up(&mut self) {
if self.scroll_offset > 0 {
self.scroll_offset -= 1;
}
}
pub fn scroll_down(&mut self, visible_lines: usize) {
let max_scroll = self.output.len().saturating_sub(visible_lines);
if self.scroll_offset < max_scroll {
self.scroll_offset += 1;
}
}
pub fn scroll_to_bottom(&mut self) {
if self.output.len() <= 30 {
self.scroll_offset = 0;
} else {
self.scroll_offset = self.output.len().saturating_sub(30);
}
}
}
impl Default for ReplState {
fn default() -> Self {
Self::new()
}
}