use std::collections::VecDeque;
const INPUT_HISTORY_CAP: usize = 100;
pub struct InputBuffer {
pub content: String,
pub cursor_position: usize,
pub history: VecDeque<String>,
pub history_index: Option<usize>,
pub history_buffer: String,
}
impl InputBuffer {
pub fn new() -> Self {
Self {
content: String::new(),
cursor_position: 0,
history: VecDeque::new(),
history_index: None,
history_buffer: String::new(),
}
}
pub fn load_history(&mut self, history: VecDeque<String>) {
self.history = history;
}
pub fn add_to_history(&mut self, input: String) {
if input.trim().is_empty() {
return;
}
if let Some(last) = self.history.back()
&& last == &input
{
return;
}
if self.history.len() >= INPUT_HISTORY_CAP {
self.history.pop_front();
}
self.history.push_back(input);
}
pub fn clear(&mut self) {
self.content.clear();
self.cursor_position = 0;
}
pub fn is_empty(&self) -> bool {
self.content.is_empty()
}
pub fn get(&self) -> &str {
&self.content
}
pub fn set(&mut self, content: impl Into<String>) {
self.content = content.into();
self.cursor_position = self.content.len();
}
pub fn insert(&mut self, c: char) {
self.content.insert(self.cursor_position, c);
self.cursor_position += c.len_utf8();
}
pub fn insert_str(&mut self, s: &str) {
self.content.insert_str(self.cursor_position, s);
self.cursor_position += s.len();
}
pub fn backspace(&mut self) -> bool {
if self.cursor_position > 0 {
let prev_boundary = self.content[..self.cursor_position]
.char_indices()
.next_back()
.map(|(idx, _)| idx)
.unwrap_or(0);
self.content.remove(prev_boundary);
self.cursor_position = prev_boundary;
true
} else {
false
}
}
pub fn delete(&mut self) -> bool {
if self.cursor_position < self.content.len() {
self.content.remove(self.cursor_position);
true
} else {
false
}
}
pub fn move_left(&mut self) {
if self.cursor_position > 0 {
self.cursor_position = self.content[..self.cursor_position]
.char_indices()
.next_back()
.map(|(idx, _)| idx)
.unwrap_or(0);
}
}
pub fn move_right(&mut self) {
if self.cursor_position < self.content.len() {
self.cursor_position = self.content[self.cursor_position..]
.char_indices()
.nth(1)
.map(|(idx, _)| self.cursor_position + idx)
.unwrap_or(self.content.len());
}
}
pub fn move_home(&mut self) {
self.cursor_position = 0;
}
pub fn move_end(&mut self) {
self.cursor_position = self.content.len();
}
}
impl Default for InputBuffer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_to_history_skips_empty_and_whitespace() {
let mut buf = InputBuffer::new();
buf.add_to_history(String::new());
buf.add_to_history(" ".into());
buf.add_to_history("\t\n".into());
assert!(buf.history.is_empty());
}
#[test]
fn add_to_history_dedups_consecutive() {
let mut buf = InputBuffer::new();
buf.add_to_history("hello".into());
buf.add_to_history("hello".into());
buf.add_to_history("world".into());
buf.add_to_history("hello".into()); assert_eq!(
buf.history.iter().cloned().collect::<Vec<_>>(),
vec!["hello", "world", "hello"]
);
}
#[test]
fn add_to_history_caps_at_limit() {
let mut buf = InputBuffer::new();
for i in 0..(INPUT_HISTORY_CAP + 25) {
buf.add_to_history(format!("msg{}", i));
}
assert_eq!(buf.history.len(), INPUT_HISTORY_CAP);
assert_eq!(buf.history.front().unwrap(), "msg25");
assert_eq!(
buf.history.back().unwrap(),
&format!("msg{}", INPUT_HISTORY_CAP + 24)
);
}
}