use ratatui::layout::Position;
#[derive(Debug, Default)]
pub struct InputState
{
pub(crate) input: String,
pub(crate) character_index: usize,
pub(crate) cursor: Position,
pub(crate) clear_on_cancel: bool,
pub(crate) clear_on_confirm: bool,
pub(crate) max_length: Option<usize>,
pub(crate) is_numeric: bool,
pub(crate) right_aligned: bool,
pub(crate) label: Option<String>,
pub(crate) pending: Option<String>,
pub(crate) placeholder: String,
pub(crate) placeholder_options: Vec<String>,
pub(crate) placeholder_options_index: usize,
}
impl InputState
{
pub fn new() -> Self
{
Self::default()
}
pub fn with_placeholder<S>(
mut self,
placeholder: S,
) -> Self
where
S: AsRef<str>,
{
self.placeholder = placeholder
.as_ref()
.to_string();
self
}
pub fn set_label<S>(
&mut self,
label: S,
) where
S: AsRef<str>,
{
self.label = Some(
label
.as_ref()
.to_string(),
)
}
pub fn with_label<S>(
mut self,
label: S,
) -> Self
where
S: AsRef<str>,
{
self.set_label(label);
self
}
pub fn has_label(&self) -> bool
{
self.label
.is_some()
}
pub fn label(&self) -> Option<&String>
{
self.label
.as_ref()
}
pub fn right_aligned(mut self) -> Self
{
self.right_aligned = true;
self
}
pub fn numeric(mut self) -> Self
{
self.is_numeric = true;
self
}
pub fn with_default<S>(
mut self,
value: S,
) -> Self
where
S: AsRef<str>,
{
self.input = value
.as_ref()
.to_string();
self
}
pub fn with_max_length(
mut self,
length: usize,
) -> Self
{
self.max_length = Some(length);
self
}
pub fn set<S>(
&mut self,
text: S,
) where
S: AsRef<str>,
{
self.clear();
self.write(text);
}
pub fn write<S>(
&mut self,
text: S,
) where
S: AsRef<str>,
{
let mut inner = move |text: &str| {
for c in text.chars()
{
self.enter_char(c);
}
};
inner(text.as_ref())
}
pub fn write_pending<S>(
&mut self,
text: S,
) where
S: AsRef<str>,
{
let mut inner = move |text: &str| {
if let Some(pending) = self
.pending
.as_ref()
{
for _ in 0..pending
.chars()
.count()
{
self.delete_char();
}
}
for c in text.chars()
{
self.enter_char(c);
}
self.pending = Some(text.to_string());
};
inner(text.as_ref())
}
pub fn input(&self) -> String
{
if let Some(pending) = self
.pending
.as_ref()
{
self.input
.strip_suffix(pending)
.map(|s| s.to_string())
.unwrap_or(
self.input
.clone(),
)
}
else
{
self.input
.clone()
}
}
pub fn set_clear_on_cancel(
&mut self,
value: bool,
)
{
self.clear_on_cancel = value;
}
pub fn with_clear_on_cancel(mut self) -> Self
{
self.set_clear_on_cancel(true);
self
}
pub fn clear_on_cancel(&self) -> bool
{
self.clear_on_cancel
}
pub fn set_clear_on_confirm(
&mut self,
value: bool,
)
{
self.clear_on_confirm = value;
}
pub fn with_clear_on_confirm(mut self) -> Self
{
self.set_clear_on_confirm(true);
self
}
pub fn clear_on_confirm(&self) -> bool
{
self.clear_on_confirm
}
pub fn cursor(&self) -> Position
{
self.cursor
}
pub fn clear(&mut self)
{
self.input
.clear();
self.reset_cursor();
}
pub(crate) fn move_cursor_left(&mut self)
{
let cursor_moved_left = self
.character_index
.saturating_sub(1);
self.character_index = self.clamp_cursor(cursor_moved_left);
}
pub(crate) fn move_cursor_start(&mut self)
{
self.character_index = self.clamp_cursor(0);
}
pub(crate) fn move_cursor_right(&mut self)
{
let cursor_moved_right = self
.character_index
.saturating_add(1);
self.character_index = self.clamp_cursor(cursor_moved_right);
}
pub(crate) fn move_cursor_end(&mut self)
{
self.character_index = self.clamp_cursor(
self.input
.chars()
.count(),
)
}
pub(crate) fn enter_char(
&mut self,
new_char: char,
)
{
if let Some(max_length) = self.max_length
{
if self
.input
.chars()
.count()
>= max_length
{
return;
}
}
if self.is_numeric && ((new_char as u8) < 48 || (new_char as u8) > 57)
{
return;
}
let index = self.byte_index();
self.input
.insert(
index, new_char,
);
self.move_cursor_right();
}
fn byte_index(&mut self) -> usize
{
self.input
.char_indices()
.map(|(i, _)| i)
.nth(self.character_index)
.unwrap_or(
self.input
.len(),
)
}
pub(crate) fn supp_char(&mut self)
{
let is_not_cursor_rightmost = self.character_index
!= self
.input
.chars()
.count();
if is_not_cursor_rightmost
{
let before = self
.input
.chars()
.take(self.character_index);
let after = self
.input
.chars()
.skip(self.character_index + 1);
self.input = before
.chain(after)
.collect();
}
}
pub(crate) fn delete_char(&mut self)
{
let is_not_cursor_leftmost = self.character_index != 0;
if is_not_cursor_leftmost
{
let current_index = self.character_index;
let from_left_to_current_index = current_index - 1;
let before_char_to_delete = self
.input
.chars()
.take(from_left_to_current_index);
let after_char_to_delete = self
.input
.chars()
.skip(current_index);
self.input = before_char_to_delete
.chain(after_char_to_delete)
.collect();
self.move_cursor_left();
}
}
fn clamp_cursor(
&self,
new_cursor_pos: usize,
) -> usize
{
new_cursor_pos.clamp(
0,
self.input
.chars()
.count(),
)
}
pub fn reset_cursor(&mut self)
{
self.character_index = 0;
}
pub fn word_under_cursor(&self) -> String
{
let mut start_index = self.character_index;
if start_index
== self
.input
.chars()
.count()
{
start_index -= 1;
}
start_index = start_index.saturating_sub(1);
while start_index > 0
&& (self
.input
.chars()
.nth(start_index)
.unwrap_or(' ')
!= ' ')
{
start_index -= 1;
}
let end_index = self.character_index;
let ending = self
.input
.chars()
.count();
let (start, _) = self
.input
.char_indices()
.nth(start_index)
.unwrap();
let part = if end_index == ending
{
&self.input[start..]
}
else
{
let (end, _) = self
.input
.char_indices()
.nth(end_index)
.unwrap();
&self.input[start..end]
};
part.trim()
.to_string()
}
pub fn has_placeholder(&self) -> bool
{
!self
.placeholder
.is_empty()
}
pub(crate) fn clear_placeholder(&mut self)
{
self.placeholder
.clear();
self.placeholder_options
.clear();
self.placeholder_options_index = 0;
}
}