#![allow(dead_code)]
use crossterm::event::KeyEvent;
use ratatui::style::Style;
use tui_textarea::TextArea;
use crate::tui::theme::Theme;
const MAX_HISTORY: usize = 1000;
pub struct Prompt {
textarea: TextArea<'static>,
theme: Theme,
history: Vec<String>,
hist_pos: Option<usize>,
stash: String,
mask: Option<char>,
placeholder: String,
}
const DEFAULT_PLACEHOLDER: &str = "Type a command, or / for the palette…";
impl Prompt {
pub fn new(theme: &Theme) -> Self {
let mut p = Prompt {
textarea: TextArea::default(),
theme: theme.clone(),
history: Vec::new(),
hist_pos: None,
stash: String::new(),
mask: None,
placeholder: DEFAULT_PLACEHOLDER.to_string(),
};
p.fresh_textarea();
p
}
fn fresh_textarea(&mut self) {
let mut ta = TextArea::default();
Self::apply_style(&self.theme, &mut ta);
self.apply_overrides(&mut ta);
self.textarea = ta;
}
fn apply_style(theme: &Theme, textarea: &mut TextArea<'static>) {
textarea.set_cursor_line_style(Style::default());
textarea.set_style(Style::default().fg(theme.fg));
textarea.set_cursor_style(Style::default().bg(theme.accent).fg(theme.bg));
}
fn apply_overrides(&self, textarea: &mut TextArea<'static>) {
textarea.set_placeholder_text(self.placeholder.clone());
match self.mask {
Some(c) => textarea.set_mask_char(c),
None => textarea.clear_mask_char(),
}
}
pub fn set_mask(&mut self, c: char) {
self.mask = Some(c);
self.textarea.set_mask_char(c);
}
pub fn clear_mask(&mut self) {
self.mask = None;
self.textarea.clear_mask_char();
}
pub fn set_placeholder(&mut self, s: &str) {
self.placeholder = s.to_string();
self.textarea.set_placeholder_text(s.to_string());
}
pub fn retheme(&mut self, theme: &Theme) {
self.theme = theme.clone();
let t = self.theme.clone();
Self::apply_style(&t, &mut self.textarea);
let mask = self.mask;
match mask {
Some(c) => self.textarea.set_mask_char(c),
None => self.textarea.clear_mask_char(),
}
}
pub fn text(&self) -> String {
self.textarea.lines().join("\n")
}
pub fn is_empty(&self) -> bool {
self.textarea.lines().iter().all(|l| l.is_empty())
}
pub fn line_count(&self) -> usize {
self.textarea.lines().len().max(1)
}
pub fn input(&mut self, key: KeyEvent) {
self.textarea.input(key);
self.hist_pos = None;
}
pub fn newline(&mut self) {
self.textarea.insert_str("\n");
}
pub fn clear(&mut self) {
self.hist_pos = None;
self.stash.clear();
self.fresh_textarea();
}
fn set_text(&mut self, s: &str) {
let lines: Vec<String> = s.split('\n').map(|l| l.to_string()).collect();
let mut ta = TextArea::from(lines);
Self::apply_style(&self.theme, &mut ta);
self.apply_overrides(&mut ta);
self.textarea = ta;
}
fn push_history(&mut self, entry: String) {
self.history.push(entry);
if self.history.len() > MAX_HISTORY {
self.history.remove(0);
}
}
pub fn submit(&mut self) -> String {
let trimmed = self.text().trim().to_string();
if !trimmed.is_empty() && self.history.last().map(|h| h != &trimmed).unwrap_or(true) {
self.push_history(trimmed.clone());
}
self.hist_pos = None;
self.stash.clear();
self.fresh_textarea();
trimmed
}
pub fn history_prev(&mut self) {
if self.history.is_empty() {
return;
}
let next = match self.hist_pos {
None => {
self.stash = self.text();
self.history.len() - 1
}
Some(0) => 0,
Some(p) => p - 1,
};
self.hist_pos = Some(next);
let entry = self.history[next].clone();
self.set_text(&entry);
}
pub fn history_next(&mut self) {
match self.hist_pos {
None => {}
Some(p) if p + 1 < self.history.len() => {
self.hist_pos = Some(p + 1);
let entry = self.history[p + 1].clone();
self.set_text(&entry);
}
Some(_) => {
self.hist_pos = None;
let stash = self.stash.clone();
self.set_text(&stash);
}
}
}
pub fn widget(&self) -> impl ratatui::widgets::Widget + '_ {
self.textarea.widget()
}
}
#[cfg(test)]
mod history_cap_tests {
use super::*;
use crate::tui::theme::Theme;
#[test]
fn history_is_capped_fifo() {
let theme = Theme::dark();
let mut p = Prompt::new(&theme);
for i in 0..(MAX_HISTORY + 50) {
p.push_history(format!("cmd{}", i));
}
assert_eq!(
p.history.len(),
MAX_HISTORY,
"history must be capped at MAX_HISTORY"
);
assert!(!p.history.contains(&"cmd0".to_string()));
assert!(p.history.contains(&format!("cmd{}", MAX_HISTORY + 49)));
}
}