use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
#[derive(Default, Clone)]
pub struct FilterInput {
text: String,
cursor: usize, anchor: Option<usize>,
}
impl std::ops::Deref for FilterInput {
type Target = str;
fn deref(&self) -> &str {
&self.text
}
}
impl From<&str> for FilterInput {
fn from(s: &str) -> Self {
let mut f = Self::default();
f.set(s);
f
}
}
impl FilterInput {
pub fn clear(&mut self) {
self.text.clear();
self.cursor = 0;
self.anchor = None;
}
pub fn set(&mut self, s: &str) {
self.text = s.to_string();
self.cursor = self.text.chars().count();
self.anchor = None;
}
fn len(&self) -> usize {
self.text.chars().count()
}
fn selection_range(&self) -> Option<(usize, usize)> {
self.anchor
.map(|a| (a.min(self.cursor), a.max(self.cursor)))
.filter(|(s, e)| s != e)
}
fn delete_selection(&mut self) -> bool {
let Some((s, e)) = self.selection_range() else {
return false;
};
let chars: Vec<char> = self.text.chars().collect();
self.text = chars[..s].iter().chain(chars[e..].iter()).collect();
self.cursor = s;
self.anchor = None;
true
}
pub fn insert_char(&mut self, c: char) {
self.delete_selection();
let mut chars: Vec<char> = self.text.chars().collect();
chars.insert(self.cursor.min(chars.len()), c);
self.text = chars.into_iter().collect();
self.cursor += 1;
}
pub fn backspace(&mut self) {
if self.delete_selection() {
return;
}
if self.cursor == 0 {
return;
}
let mut chars: Vec<char> = self.text.chars().collect();
chars.remove(self.cursor - 1);
self.text = chars.into_iter().collect();
self.cursor -= 1;
}
fn delete_forward(&mut self) {
if self.delete_selection() {
return;
}
if self.cursor >= self.len() {
return;
}
let mut chars: Vec<char> = self.text.chars().collect();
chars.remove(self.cursor);
self.text = chars.into_iter().collect();
}
fn move_to(&mut self, pos: usize, extend: bool) {
if extend {
if self.anchor.is_none() {
self.anchor = Some(self.cursor);
}
} else {
self.anchor = None;
}
self.cursor = pos.min(self.len());
}
fn paste(&mut self, s: &str) {
self.delete_selection();
let clean: String = s.chars().filter(|c| !c.is_control()).collect();
let n = clean.chars().count();
let mut chars: Vec<char> = self.text.chars().collect();
for (i, c) in clean.chars().enumerate() {
chars.insert(self.cursor + i, c);
}
self.text = chars.into_iter().collect();
self.cursor += n;
}
fn selected_text(&self) -> Option<String> {
self.selection_range()
.map(|(s, e)| self.text.chars().skip(s).take(e - s).collect())
}
pub fn key(&mut self, key: KeyEvent, clipboard: &mut Option<arboard::Clipboard>) -> bool {
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
let shift = key.modifiers.contains(KeyModifiers::SHIFT);
match key.code {
KeyCode::Left => {
let to = self.cursor.saturating_sub(1);
self.move_to(to, shift);
true
}
KeyCode::Right => {
let to = self.cursor + 1;
self.move_to(to, shift);
true
}
KeyCode::Home => {
self.move_to(0, shift);
true
}
KeyCode::End => {
let to = self.len();
self.move_to(to, shift);
true
}
KeyCode::Delete => {
self.delete_forward();
true
}
KeyCode::Char('a') if ctrl => {
self.anchor = Some(0);
self.cursor = self.len();
true
}
KeyCode::Char('c') if ctrl => {
if let Some(sel) = self.selected_text() {
let _ = clipboard.as_mut().map(|cb| cb.set_text(sel));
}
true
}
KeyCode::Char('x') if ctrl => {
if let Some(sel) = self.selected_text() {
let _ = clipboard.as_mut().map(|cb| cb.set_text(sel));
self.delete_selection();
}
true
}
KeyCode::Char('v') if ctrl => {
if let Some(text) = clipboard.as_mut().and_then(|cb| cb.get_text().ok()) {
self.paste(&text);
}
true
}
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cursor_moves_and_edits_mid_string() {
let mut f = FilterInput::from("helo");
f.move_to(2, false); f.insert_char('l'); assert_eq!(&*f, "hello");
assert_eq!(f.cursor, 3);
}
#[test]
fn select_all_then_copy_yields_full_text() {
let mut f = FilterInput::from("abc");
f.anchor = Some(0);
f.cursor = f.len();
assert_eq!(f.selected_text().as_deref(), Some("abc"));
}
#[test]
fn backspace_deletes_selection_not_last_char() {
let mut f = FilterInput::from("abcdef");
f.anchor = Some(1);
f.cursor = 4; f.backspace();
assert_eq!(&*f, "aef");
}
}