use crate::input::history::HistoryProvider;
use std::borrow::Cow;
#[derive(Debug)]
pub(super) struct UnlockedStdinInputState {
autocompleted_buffer: Option<String>,
buffer: String,
cursor_position: usize,
history_search_buffer: Option<String>,
in_history_search: bool,
last_completion_index: Option<usize>,
next_history_index: usize,
original_uncompleted_history: Option<String>,
saved_cursor_position: Option<usize>,
started: bool,
uwk_buffer: Option<String>,
}
impl UnlockedStdinInputState {
#[must_use]
pub fn new() -> Self {
Self {
autocompleted_buffer: None,
buffer: String::with_capacity(0),
cursor_position: 0,
history_search_buffer: None,
in_history_search: false,
last_completion_index: None,
original_uncompleted_history: None,
next_history_index: 1,
saved_cursor_position: None,
started: false,
uwk_buffer: None,
}
}
#[must_use]
pub fn autocompleted_buffer(&self) -> Option<&str> {
self.autocompleted_buffer.as_deref()
}
#[must_use]
pub fn buffer(&self) -> Cow<'_, str> {
if let Some(ac) = self.autocompleted_buffer.as_deref() {
let mut new = self.buffer.clone();
new.push_str(ac);
Cow::Owned(new)
} else if let Some(ac) = self.history_search_buffer.as_deref() {
let mut new = self.buffer.clone();
new.push_str(ac);
Cow::Owned(new)
} else {
Cow::Borrowed(&self.buffer)
}
}
#[must_use]
pub fn history_search_buffer(&self) -> Option<&str> {
self.history_search_buffer.as_deref()
}
#[must_use]
pub const fn history_search_buffer_mut(&mut self) -> &mut Option<String> {
&mut self.history_search_buffer
}
#[must_use]
pub const fn uncompleted_input(&self) -> &str {
self.buffer.as_str()
}
#[must_use]
pub const fn cursor_position(&self) -> usize {
self.cursor_position
}
pub const fn set_cursor_position(&mut self, new_position: usize) {
self.cursor_position = new_position;
}
#[must_use]
pub const fn move_cursor_left(&mut self, amount: usize) -> bool {
if self.cursor_position > 0 {
if self.cursor_position < amount {
self.cursor_position = 0;
} else {
self.cursor_position -= amount;
}
true
} else {
false
}
}
#[must_use]
pub fn move_cursor_right(&mut self, amount: usize) -> bool {
let max_space = self
.internal_buffer_with_autocomplete_only()
.as_ref()
.chars()
.count();
if self.cursor_position < max_space {
if max_space < self.cursor_position + amount {
self.cursor_position = max_space;
} else {
self.cursor_position += amount;
}
true
} else {
false
}
}
#[must_use]
pub const fn started(&self) -> bool {
self.started
}
pub const fn mark_started(&mut self) {
self.started = true;
}
#[must_use]
pub const fn in_history_search(&self) -> bool {
self.in_history_search
}
#[must_use]
pub fn get_previous_completion_index(&mut self) -> Option<usize> {
if let Some(size) = self.last_completion_index {
if size < 2 {
self.last_completion_index = None;
} else {
self.last_completion_index = Some(size - 2);
}
self.last_completion_index
} else {
None
}
}
#[must_use]
pub const fn get_last_completion_index(&mut self) -> Option<usize> {
if let Some(size) = self.last_completion_index {
self.last_completion_index = Some(size + 1);
Some(size)
} else {
self.last_completion_index = Some(0);
None
}
}
#[must_use]
pub const fn get_next_history_index(&mut self) -> usize {
let to_return = self.next_history_index;
self.next_history_index += 1;
to_return
}
#[must_use]
pub const fn get_previous_history_index(&mut self) -> Option<usize> {
if self.next_history_index <= 1 {
None
} else {
self.next_history_index -= 2;
if self.next_history_index < 1 {
self.next_history_index = 1;
None
} else {
Some(self.next_history_index)
}
}
}
#[must_use]
pub fn original_uncompleted_history(&self) -> Option<&str> {
self.original_uncompleted_history.as_deref()
}
#[must_use]
pub fn take_original_uncompleted_history(&mut self) -> Option<String> {
self.original_uncompleted_history.take()
}
pub fn set_uncompleted_history(&mut self, new: String) {
_ = self.original_uncompleted_history.insert(new);
}
pub fn handle_new_autocomplete_suggestion(&mut self, new_data: &str) {
self.internal_reset_history_state();
_ = self.autocompleted_buffer.insert(new_data.to_owned());
_ = self.move_cursor_right(new_data.len());
}
pub fn handle_new_buff_from_history(&mut self, new_value: String) {
self.autocompleted_buffer = None;
self.buffer = new_value;
self.last_completion_index = None;
self.started = true;
if self.cursor_position > self.buffer.chars().count() {
self.cursor_position = self.buffer.chars().count();
}
}
pub fn start_history_search_suggestions(&mut self, history: Option<&dyn HistoryProvider>) {
self.promote_autocomplete_buffer();
self.history_search_buffer =
history.and_then(|provider| provider.complete_command(&self.buffer));
self.in_history_search = true;
self.started = true;
}
pub fn promote_autocomplete_buffer(&mut self) {
if let Some(extra_buff) = self.autocompleted_buffer.take() {
if !extra_buff.is_empty() {
self.buffer += &extra_buff;
}
self.started = true;
}
self.last_completion_index = None;
}
pub fn check_promote_history_buffer(&mut self) -> bool {
if self.in_history_search {
let last_character = self
.buffer
.chars()
.nth(std::cmp::max(self.cursor_position, 1) - 1);
if last_character == Some('\u{1b}') {
self.pop_n(1);
self.next_history_index = 1;
self.buffer += self.history_search_buffer.as_deref().unwrap_or_default();
self.history_search_buffer = None;
self.in_history_search = false;
self.cursor_position = 0;
return true;
}
}
false
}
#[must_use]
pub fn backspace(&mut self) -> (bool, bool) {
self.promote_autocomplete_buffer();
self.internal_reset_history_state();
if self.buffer.is_empty() {
let copied = self.started;
self.started = false;
(copied, false)
} else {
self.cursor_position -= 1;
let buffer_char_iter = self.buffer.chars();
let mut removed_buffer = String::with_capacity(self.buffer.len() - 1);
removed_buffer.extend(buffer_char_iter.take(self.cursor_position));
let buffer_char_iter = self.buffer.chars().skip(self.cursor_position + 1);
removed_buffer.extend(buffer_char_iter);
self.buffer = removed_buffer;
(self.started, true)
}
}
#[must_use]
pub fn cancel(&mut self) -> bool {
let had_started = self.started;
self.autocompleted_buffer = None;
self.last_completion_index = None;
self.buffer = String::with_capacity(0);
self.started = false;
self.next_history_index = 1;
self.history_search_buffer = None;
self.in_history_search = false;
self.original_uncompleted_history = None;
self.cursor_position = 0;
had_started
}
#[must_use]
pub fn complete(&mut self) -> String {
self.promote_autocomplete_buffer();
if let Some(history_buffer) = self.history_search_buffer.take() {
self.buffer += &history_buffer;
}
let mut completed = String::with_capacity(0);
std::mem::swap(&mut completed, &mut self.buffer);
self.autocompleted_buffer = None;
self.last_completion_index = None;
self.started = false;
self.next_history_index = 1;
self.history_search_buffer = None;
self.in_history_search = false;
self.original_uncompleted_history = None;
self.cursor_position = 0;
completed
}
#[must_use]
pub fn process(
&mut self,
character: char,
is_part_of_cursor_input: bool,
history_impl: Option<&dyn HistoryProvider>,
) -> (bool, bool) {
let had_started = self.started;
self.started = true;
let mut reset_history_search = false;
if !is_part_of_cursor_input {
self.promote_autocomplete_buffer();
self.next_history_index = 1;
if !self.in_history_search && self.history_search_buffer.is_some() {
reset_history_search = true;
} else if let Some(mutable) = self.history_search_buffer.as_mut() {
if mutable.starts_with(character) && mutable.len() > 1 {
*mutable = mutable.chars().skip(1).collect::<String>();
} else {
let mut new_buffer = self
.buffer
.chars()
.take(self.cursor_position)
.collect::<String>();
new_buffer.push(character);
new_buffer.extend(self.buffer.chars().skip(self.cursor_position));
self.history_search_buffer =
history_impl.and_then(|provider| provider.complete_command(&new_buffer));
}
} else if self.in_history_search {
let mut new_buffer = self
.buffer
.chars()
.take(self.cursor_position)
.collect::<String>();
new_buffer.push(character);
new_buffer.extend(self.buffer.chars().skip(self.cursor_position));
self.history_search_buffer =
history_impl.and_then(|provider| provider.complete_command(&new_buffer));
}
if reset_history_search {
self.history_search_buffer = None;
}
}
let mut u1b_change = false;
let last_character = self
.buffer
.chars()
.nth(std::cmp::max(self.cursor_position, 1) - 1);
if last_character == Some('\u{1b}') && character == 't' {
self.internal_do_word_swap();
return (!had_started, true);
} else if (character == '\u{18}' || character == 'x') && last_character == Some('\u{18}') {
self.pop_n(1);
if self.cursor_position == 0 {
if let Some(cursor_position) = self.saved_cursor_position {
self.cursor_position = cursor_position;
} else {
return (!had_started, false);
}
} else {
self.saved_cursor_position = Some(self.cursor_position);
self.cursor_position = 0;
}
return (!had_started, true);
}
if last_character == Some('\u{1b}') && character != '[' {
self.pop_n(1);
u1b_change = true;
if self.in_history_search {
self.next_history_index = 1;
self.buffer += self.history_search_buffer.as_deref().unwrap_or_default();
self.history_search_buffer = None;
self.in_history_search = false;
self.cursor_position = 0;
return (had_started, true);
}
}
let did_change = if self.cursor_position >= self.buffer.chars().count() {
self.buffer.push(character);
false
} else {
let mut new_string = String::with_capacity(self.buffer.len() + character.len_utf8());
new_string.extend(self.buffer.chars().take(self.cursor_position));
new_string.push(character);
new_string.extend(self.buffer.chars().skip(self.cursor_position));
self.buffer = new_string;
true
};
self.cursor_position += 1;
(
!had_started,
did_change || u1b_change || reset_history_search,
)
}
pub fn process_all(
&mut self,
data: &str,
history_impl: Option<&dyn HistoryProvider>,
) -> (bool, bool) {
self.promote_autocomplete_buffer();
self.next_history_index = 1;
let mut reset_history_search = false;
if !self.in_history_search && self.history_search_buffer.is_some() {
reset_history_search = true;
} else if let Some(mutable) = self.history_search_buffer.as_mut() {
if mutable.starts_with(data) && mutable.len() > data.len() {
*mutable = mutable
.chars()
.skip(data.chars().count())
.collect::<String>();
} else {
let mut new_buff = self.buffer.clone();
new_buff.push_str(data);
self.history_search_buffer =
history_impl.and_then(|provider| provider.complete_command(&new_buff));
}
}
if reset_history_search {
self.history_search_buffer = None;
}
let had_started = self.started;
let did_change =
if self.buffer.is_empty() || self.cursor_position > self.buffer.chars().count() {
self.buffer.push_str(&data.replace('\u{1b}', ""));
false
} else {
let mut new_string = String::with_capacity(self.buffer.len() + data.len());
new_string.extend(self.buffer.chars().take(self.cursor_position));
new_string.push_str(&data.replace('\u{1b}', ""));
new_string.extend(self.buffer.chars().skip(self.cursor_position));
self.buffer = new_string;
true
};
self.started = true;
self.last_completion_index = None;
self.cursor_position += data.chars().count();
(!had_started, did_change || reset_history_search)
}
pub fn uwk_cursor_to_begin(&mut self) -> bool {
if self.cursor_position == 0 {
return false;
}
self.promote_autocomplete_buffer();
self.internal_reset_history_state();
self.last_completion_index = None;
let all_before_cursor = self
.buffer
.chars()
.take(self.cursor_position)
.collect::<String>();
if let Some((index, _newline)) = all_before_cursor
.chars()
.enumerate()
.collect::<Vec<_>>()
.into_iter()
.rfind(|(_idx, character)| *character == '\n')
{
let cut_position = index + 1;
let diff = self.cursor_position - cut_position;
let mut new_uwk = String::new();
new_uwk.extend(self.buffer.chars().skip(cut_position).take(diff));
if new_uwk.is_empty() {
return false;
}
let mut new_buffer = String::new();
new_buffer.extend(self.buffer.chars().take(cut_position));
new_buffer.extend(self.buffer.chars().skip(self.cursor_position));
self.cursor_position -= diff;
self.buffer = new_buffer;
self.uwk_buffer = Some(new_uwk);
} else {
self.buffer = self.buffer.chars().skip(self.cursor_position).collect();
self.uwk_buffer = Some(all_before_cursor);
self.cursor_position = 0;
}
true
}
pub fn uwk_cursor_to_end(&mut self) -> bool {
if self.cursor_position
> self
.internal_buffer_with_autocomplete_only()
.chars()
.count()
{
return false;
}
self.promote_autocomplete_buffer();
self.internal_reset_history_state();
self.last_completion_index = None;
let all_after_cursor = self
.buffer
.chars()
.skip(self.cursor_position)
.collect::<String>();
if let Some((index, _newline)) = all_after_cursor
.chars()
.enumerate()
.find(|(_idx, character)| *character == '\n')
{
let uwk_buff = self
.buffer
.chars()
.skip(self.cursor_position)
.take(index)
.collect::<String>();
if uwk_buff.is_empty() {
return false;
}
let mut new_buff = self
.buffer
.chars()
.take(self.cursor_position)
.collect::<String>();
new_buff.extend(self.buffer.chars().skip(self.cursor_position + index));
self.buffer = new_buff;
self.uwk_buffer = Some(uwk_buff);
} else {
self.buffer = self.buffer.chars().take(self.cursor_position).collect();
self.uwk_buffer = Some(all_after_cursor);
}
true
}
pub fn uwk_cursor_word(&mut self) -> bool {
self.promote_autocomplete_buffer();
self.internal_reset_history_state();
let Some((word_start, word_end)) = self.get_word_bounds_at_cursor() else {
return false;
};
let removal_count = word_end - word_start;
let cursor_moves = self.cursor_position - word_start;
let mut new_buffer = self.buffer.chars().take(word_start).collect::<String>();
let uwk_buffer = self
.buffer
.chars()
.skip(word_start)
.take(removal_count)
.collect::<String>();
self.uwk_buffer = Some(uwk_buffer);
new_buffer.extend(self.buffer.chars().skip(word_end));
self.buffer = new_buffer;
self.cursor_position -= cursor_moves;
true
}
pub fn swap_last_two_characters(&mut self) -> bool {
self.promote_autocomplete_buffer();
self.internal_reset_history_state();
if self.buffer.is_empty() || self.cursor_position < 2 {
return false;
}
let mut new_buff = self
.buffer
.chars()
.take(self.cursor_position - 2)
.collect::<String>();
let mut iterator = self.buffer.chars().skip(self.cursor_position - 2);
let first = iterator.next().unwrap_or_else(|| unreachable!());
let second = iterator.next().unwrap_or_else(|| unreachable!());
new_buff.push(second);
new_buff.push(first);
new_buff.extend(iterator);
self.buffer = new_buff;
true
}
pub fn paste_uwk(&mut self) -> bool {
self.promote_autocomplete_buffer();
self.internal_reset_history_state();
let Some(uwk_data) = self.uwk_buffer.as_deref() else {
return false;
};
if self.buffer.is_empty() || self.cursor_position > self.buffer.chars().count() {
self.buffer += uwk_data;
} else {
let mut new_string = String::with_capacity(self.buffer.len() + uwk_data.len());
new_string.extend(
self.internal_buffer_with_autocomplete_only()
.chars()
.take(self.cursor_position),
);
new_string.push_str(uwk_data);
new_string.extend(
self.internal_buffer_with_autocomplete_only()
.chars()
.skip(self.cursor_position),
);
self.buffer = new_string;
}
self.started = true;
self.last_completion_index = None;
self.cursor_position += uwk_data.chars().count();
true
}
pub fn pop_n(&mut self, n: usize) {
if self.autocompleted_buffer().is_some() {
let real_max = self.buffer.chars().count();
if self.cursor_position > real_max {
self.cursor_position = real_max;
}
self.autocompleted_buffer = None;
}
self.cursor_position -= std::cmp::min(self.cursor_position, n);
let buffer_char_iter = self.buffer.chars();
let mut removed_buffer = String::with_capacity(std::cmp::max(self.buffer.len(), n) - n);
removed_buffer.extend(buffer_char_iter.take(self.cursor_position));
let skipped_iter = self.buffer.chars();
removed_buffer.extend(skipped_iter.skip(self.cursor_position + n));
self.buffer = removed_buffer;
}
pub fn pop_n_promoting_autocomplete_buffer(&mut self, n: usize) {
let prev_buffer = self.autocompleted_buffer().map(ToString::to_string);
self.pop_n(n);
if let Some(data) = prev_buffer {
self.handle_new_autocomplete_suggestion(&data);
self.promote_autocomplete_buffer();
}
}
pub fn get_word_bounds_at_cursor(&self) -> Option<(usize, usize)> {
let end_pos = if self.cursor_position > self.buffer.chars().count() {
self.buffer.chars().count()
} else {
self.buffer
.chars()
.enumerate()
.skip(self.cursor_position)
.find(|(_idx, character)| character.is_whitespace())
.map_or_else(|| self.buffer.chars().count(), |(idx, _char)| idx)
};
let start_pos_search = self
.buffer
.chars()
.enumerate()
.take(end_pos)
.collect::<Vec<_>>()
.into_iter()
.rev()
.find(|(_idx, character)| !character.is_whitespace())
.map(|(idx, _character)| idx)
.unwrap_or_default();
let start_pos = if start_pos_search == 0 {
0_usize
} else {
self.buffer
.chars()
.enumerate()
.take(start_pos_search)
.collect::<Vec<_>>()
.into_iter()
.rev()
.find(|(_idx, character)| character.is_whitespace())
.map(|(idx, _character)| idx)
.unwrap_or_default()
};
if start_pos == end_pos {
None
} else {
Some((start_pos, end_pos))
}
}
fn internal_reset_history_state(&mut self) {
self.next_history_index = 1;
self.history_search_buffer = None;
self.original_uncompleted_history = None;
}
#[must_use]
fn internal_buffer_with_autocomplete_only(&self) -> Cow<'_, str> {
if let Some(ac) = self.autocompleted_buffer.as_deref() {
let mut new = self.buffer.clone();
new.push_str(ac);
Cow::Owned(new)
} else {
Cow::Borrowed(&self.buffer)
}
}
fn internal_do_word_swap(&mut self) {
self.pop_n(1);
if self.cursor_position == 0 {
return;
}
let Some((current_word_start, current_word_end)) = self.get_word_bounds_at_cursor() else {
return;
};
if current_word_start == 0 {
return;
}
let store_cursor = self.cursor_position;
self.cursor_position = std::cmp::max(current_word_start, 1) - 1;
let Some((prev_word_start, _prev_word_end)) = self.get_word_bounds_at_cursor() else {
return;
};
self.cursor_position = store_cursor;
if current_word_start == current_word_end || current_word_start == prev_word_start {
return;
}
let mut new_buffer = self
.buffer
.chars()
.take(prev_word_start)
.collect::<String>();
let previous_word = self
.buffer
.chars()
.skip(prev_word_start)
.take(current_word_start - prev_word_start)
.collect::<String>();
let current_word = self
.buffer
.chars()
.skip(current_word_start)
.take(current_word_end - current_word_start)
.collect::<String>();
if !previous_word.starts_with(' ') && current_word.starts_with(' ') {
new_buffer.extend(current_word.chars().skip(1));
new_buffer.push(' ');
new_buffer.push_str(&previous_word);
} else {
new_buffer.push_str(¤t_word);
new_buffer.push_str(&previous_word);
}
new_buffer.extend(self.buffer.chars().skip(current_word_end));
self.buffer = new_buffer;
}
}
#[cfg(test)]
mod unit_tests {
use super::*;
use crate::input::history::SimpleHistoryProvider;
#[test]
pub fn input_starting() {
{
let mut state = UnlockedStdinInputState::new();
assert!(
!state.started(),
"UnlockedStdinInputState::new() returned a started state?",
);
_ = state.process('A', false, None);
_ = state.backspace();
assert!(
state.started(),
"State wasn't started after typing some characters?",
);
}
{
let mut state = UnlockedStdinInputState::new();
assert!(
!state.started(),
"UnlockedStdinInputState::new() returned a started state?",
);
_ = state.process_all("abcdefg", None);
assert!(
state.started(),
"UnlockedStdinInputState::process_all did not start state!",
);
}
{
let mut state = UnlockedStdinInputState::new();
assert!(
!state.started(),
"UnlockedStdinInputState::new() returned a started state?",
);
_ = state.move_cursor_left(10);
_ = state.move_cursor_right(10);
assert!(
!state.started(),
"UnlockedStdinInputState.(move_cursor_left|move_cursor_right) started cursor?",
);
}
{
let mut state = UnlockedStdinInputState::new();
assert!(
!state.started(),
"UnlockedStdinInputState::new() returned a started state?",
);
state.handle_new_buff_from_history("command from history".to_owned());
assert!(
state.started(),
"new_buff_from_history should start a brand new buffer!",
);
}
{
let mut state = UnlockedStdinInputState::new();
assert!(
!state.started(),
"UnlockedStdinInputState::new() returned a started state?",
);
state.handle_new_autocomplete_suggestion("new suggestion");
assert!(
!state.started(),
"handle_new_autocomplete_suggestion should not start input until promotion!",
);
state.promote_autocomplete_buffer();
assert!(
state.started(),
"promote_autocomplete_buffer() should start input!",
);
}
{
let mut state = UnlockedStdinInputState::new();
assert!(
!state.started(),
"UnlockedStdinInputState::new() returned a started state?",
);
state.promote_autocomplete_buffer();
assert!(
!state.started(),
"promote_autocomplete_buffer() on an empty autocomplete buffer should not start input!",
);
}
{
let mut state = UnlockedStdinInputState::new();
assert!(
!state.started(),
"UnlockedStdinInputState::new() returned a started state?",
);
_ = state.backspace();
assert!(
!state.started(),
"Hitting bbackspace should not start input state!",
);
}
{
let mut state = UnlockedStdinInputState::new();
assert!(
!state.started(),
"UnlockedStdinInputState::new() returned a started state?",
);
_ = state.uwk_cursor_to_begin();
_ = state.uwk_cursor_to_end();
_ = state.uwk_cursor_word();
_ = state.pop_n(2);
assert!(
!state.started(),
"uwk_buffer_removal / pop_n without input should not start input?",
);
}
{
let mut state = UnlockedStdinInputState::new();
assert!(
!state.started(),
"UnlockedStdinInputState::new() returned a started state?",
);
state.uwk_buffer = Some("previously cut value".to_owned());
state.paste_uwk();
assert!(state.started(), "UWK buffer pasting does start input!");
}
{
let mut state = UnlockedStdinInputState::new();
assert!(
!state.started(),
"UnlockedStdinInputState::new() returned a started state?",
);
_ = state.process('a', false, None);
assert!(state.started(), "process did not start input!");
_ = state.cancel();
assert!(!state.started(), "cancel started input?");
_ = state.process('a', false, None);
_ = state.complete();
assert!(!state.started(), "complete started input?");
}
}
#[test]
pub fn cursor_control() {
let mut state = UnlockedStdinInputState::new();
assert_eq!(state.cursor_position(), 0);
_ = state.process('a', false, None);
assert_eq!(state.cursor_position(), 1);
_ = state.process_all("bcd𓁥efg", None);
assert_eq!(state.cursor_position(), 8);
assert!(!state.move_cursor_right(1));
assert!(state.move_cursor_left(8));
assert!(state.move_cursor_right(10));
assert_eq!(state.cursor_position(), 8);
assert!(state.move_cursor_left(4));
assert_eq!(state.cursor_position(), 4);
_ = state.process('z', false, None);
assert_eq!(state.cursor_position(), 5);
_ = state.process_all("yxwvu", None);
assert_eq!(state.cursor_position(), 10);
assert!(state.move_cursor_left(100));
assert_eq!(state.cursor_position(), 0);
_ = state.handle_new_buff_from_history("new".to_owned());
assert_eq!(state.cursor_position(), 0);
assert!(state.move_cursor_right(10));
assert_eq!(state.cursor_position(), 3);
_ = state.handle_new_buff_from_history("a".to_owned());
assert_eq!(state.cursor_position(), 1);
_ = state.cancel();
assert_eq!(state.cursor_position(), 0);
_ = state.process('f', false, None);
let history_provider = SimpleHistoryProvider::new_in_memory(1);
history_provider.insert_command("from history");
state.start_history_search_suggestions(Some(&history_provider));
_ = state.process('r', false, Some(&history_provider));
assert_eq!(state.cursor_position(), 2);
_ = state.process('\u{1b}', false, Some(&history_provider));
_ = state.check_promote_history_buffer();
assert_eq!(state.cursor_position(), 0);
assert!(state.move_cursor_right(1));
_ = state.complete();
assert_eq!(state.cursor_position(), 0);
_ = state.process('a', false, None);
_ = state.handle_new_autocomplete_suggestion("n autocomplete suggestion");
assert_eq!(state.cursor_position(), 26);
_ = state.cancel();
_ = state.process_all("abcdefghijklmnopqrstuvwxyz", None);
assert!(state.move_cursor_left(13));
assert_eq!(state.cursor_position(), 13);
_ = state.uwk_cursor_to_begin();
assert_eq!(state.cursor_position(), 0);
assert!(state.move_cursor_right(13));
assert!(state.move_cursor_left(1));
_ = state.uwk_cursor_to_end();
assert_eq!(state.cursor_position(), 12);
_ = state.uwk_cursor_word();
assert_eq!(state.cursor_position(), 0);
_ = state.paste_uwk();
assert_eq!(state.cursor_position(), 12);
_ = state.process('\u{18}', false, None);
_ = state.process('\u{18}', false, None);
assert_eq!(state.cursor_position(), 0);
_ = state.process('\u{18}', false, None);
_ = state.process('x', false, None);
assert_eq!(state.cursor_position(), 12);
_ = state.backspace();
assert_eq!(state.cursor_position(), 11);
}
#[test]
pub fn do_word_swap() {
let mut state = UnlockedStdinInputState::new();
_ = state.process_all("this is a series of a word to swap", None);
assert_eq!(state.buffer(), "this is a series of a word to swap");
_ = state.process('\u{1b}', false, None);
_ = state.process('t', false, None);
assert_eq!(state.buffer(), "this is a series of a word swap to");
_ = state.cancel();
_ = state.process_all("this", None);
assert_eq!(state.buffer(), "this");
_ = state.process('\u{1b}', false, None);
_ = state.process('t', false, None);
assert_eq!(state.buffer(), "this");
_ = state.cancel();
_ = state.process_all("🢙🢙🢛🢛🢘🢚🢘🢚BA KonamiCode:", None);
_ = state.process('\u{1b}', false, None);
_ = state.process('t', false, None);
assert_eq!(state.buffer(), "KonamiCode: 🢙🢙🢛🢛🢘🢚🢘🢚BA");
assert_eq!(state.cursor_position(), 22);
_ = state.cancel();
_ = state.process_all("this data goes uh br?", None);
assert!(state.move_cursor_left(4));
_ = state.process('\u{1b}', false, None);
_ = state.process('t', false, None);
assert_eq!(state.buffer(), "this data uh goes br?");
_ = state.cancel();
_ = state.process_all("i have a big very smile ", None);
assert!(state.move_cursor_left(7));
_ = state.process('\u{1b}', false, None);
_ = state.process('t', false, None);
assert_eq!(state.buffer(), "i have a very big smile ");
}
#[test]
pub fn history_searching() {
let history_provider = SimpleHistoryProvider::new_in_memory(10);
history_provider.insert_command("test small");
history_provider.insert_command("test large");
history_provider.insert_command("previous");
history_provider.insert_command("prev");
history_provider.insert_command("longer command from history");
let mut state = UnlockedStdinInputState::new();
state.history_search_buffer = Some("test large".to_owned());
_ = state.process('t', false, Some(&history_provider));
assert!(state.history_search_buffer().is_none());
_ = state.start_history_search_suggestions(Some(&history_provider));
assert_eq!(state.history_search_buffer(), Some("est large"));
state.process_all("est ", Some(&history_provider));
assert_eq!(state.history_search_buffer(), Some("large"));
state.process_all("s", Some(&history_provider));
assert_eq!(state.history_search_buffer(), Some("mall"));
state.process_all("mall", Some(&history_provider));
assert!(state.history_search_buffer().is_none());
_ = state.cancel();
_ = state.process('p', false, Some(&history_provider));
_ = state.start_history_search_suggestions(Some(&history_provider));
assert_eq!(state.history_search_buffer(), Some("rev"));
_ = state.process('r', false, Some(&history_provider));
assert_eq!(state.history_search_buffer(), Some("ev"));
_ = state.process('e', false, Some(&history_provider));
_ = state.process('v', false, Some(&history_provider));
_ = state.process('i', false, Some(&history_provider));
assert_eq!(state.history_search_buffer(), Some("ous"));
_ = state.process('u', false, Some(&history_provider));
assert!(state.history_search_buffer().is_none());
_ = state.cancel();
_ = state.process('l', false, Some(&history_provider));
_ = state.start_history_search_suggestions(Some(&history_provider));
_ = state.process('\u{1b}', true, Some(&history_provider));
_ = state.check_promote_history_buffer();
assert!(state.history_search_buffer().is_none());
assert_eq!(state.buffer, "longer command from history");
}
#[test]
pub fn actions_that_promote_autocomplete_buffer() {
{
let mut state = UnlockedStdinInputState::new();
_ = state.process_all("long", None);
_ = state.handle_new_autocomplete_suggestion("er value");
assert_eq!(state.autocompleted_buffer(), Some("er value"));
assert_eq!(state.cursor_position(), 12);
assert_eq!(state.buffer, "long");
_ = state.promote_autocomplete_buffer();
assert!(state.autocompleted_buffer().is_none());
assert_eq!(state.cursor_position(), 12);
assert_eq!(state.buffer, "longer value");
}
{
let mut state = UnlockedStdinInputState::new();
_ = state.process_all("long", None);
_ = state.handle_new_autocomplete_suggestion("er value");
assert_eq!(state.autocompleted_buffer(), Some("er value"));
assert_eq!(state.cursor_position(), 12);
assert_eq!(state.buffer, "long");
_ = state.process('!', false, None);
assert!(state.autocompleted_buffer().is_none());
assert_eq!(state.cursor_position(), 13);
assert_eq!(state.buffer, "longer value!");
}
{
let mut state = UnlockedStdinInputState::new();
_ = state.process_all("long", None);
_ = state.handle_new_autocomplete_suggestion("er value");
assert_eq!(state.autocompleted_buffer(), Some("er value"));
assert_eq!(state.cursor_position(), 12);
assert_eq!(state.buffer, "long");
_ = state.process_all("!", None);
assert!(state.autocompleted_buffer().is_none());
assert_eq!(state.cursor_position(), 13);
assert_eq!(state.buffer, "longer value!");
}
{
let mut state = UnlockedStdinInputState::new();
_ = state.process_all("long", None);
_ = state.handle_new_autocomplete_suggestion("er value");
assert_eq!(state.autocompleted_buffer(), Some("er value"));
assert_eq!(state.cursor_position(), 12);
assert_eq!(state.buffer, "long");
_ = state.backspace();
assert!(state.autocompleted_buffer().is_none());
assert_eq!(state.cursor_position(), 11);
assert_eq!(state.buffer, "longer valu");
}
{
let mut state = UnlockedStdinInputState::new();
_ = state.process_all("long", None);
_ = state.handle_new_autocomplete_suggestion("er value");
assert_eq!(state.autocompleted_buffer(), Some("er value"));
assert_eq!(state.cursor_position(), 12);
assert_eq!(state.buffer, "long");
_ = state.cancel();
assert!(state.autocompleted_buffer().is_none());
}
{
let mut state = UnlockedStdinInputState::new();
_ = state.process_all("long", None);
_ = state.handle_new_autocomplete_suggestion("er value");
assert_eq!(state.autocompleted_buffer(), Some("er value"));
assert_eq!(state.cursor_position(), 12);
assert_eq!(state.buffer, "long");
_ = state.complete();
assert!(state.autocompleted_buffer().is_none());
}
{
let history_search = SimpleHistoryProvider::new_in_memory(1);
history_search.insert_command("longer value that comes from history");
let mut state = UnlockedStdinInputState::new();
_ = state.process_all("long", Some(&history_search));
_ = state.handle_new_autocomplete_suggestion("er value");
assert_eq!(state.autocompleted_buffer(), Some("er value"));
assert_eq!(state.cursor_position(), 12);
assert_eq!(state.buffer, "long");
state.start_history_search_suggestions(Some(&history_search));
assert!(state.autocompleted_buffer().is_none());
}
{
let history_search = SimpleHistoryProvider::new_in_memory(1);
history_search.insert_command("longer value that comes from history");
let mut state = UnlockedStdinInputState::new();
_ = state.process_all("long", Some(&history_search));
_ = state.handle_new_autocomplete_suggestion("er value");
assert_eq!(state.autocompleted_buffer(), Some("er value"));
assert_eq!(state.cursor_position(), 12);
assert_eq!(state.buffer, "long");
state.handle_new_buff_from_history("history haha".to_owned());
assert!(state.autocompleted_buffer().is_none());
}
{
let mut state = UnlockedStdinInputState::new();
_ = state.process_all("long", None);
_ = state.handle_new_autocomplete_suggestion("er value");
assert_eq!(state.autocompleted_buffer(), Some("er value"));
assert_eq!(state.cursor_position(), 12);
assert_eq!(state.buffer, "long");
assert!(state.move_cursor_left(10));
assert_eq!(state.cursor_position(), 2);
assert_eq!(state.autocompleted_buffer(), Some("er value"));
assert!(state.move_cursor_right(10));
assert_eq!(state.cursor_position(), 12);
assert_eq!(state.autocompleted_buffer(), Some("er value"));
}
{
let mut state = UnlockedStdinInputState::new();
_ = state.process_all("long", None);
_ = state.handle_new_autocomplete_suggestion("er value");
assert_eq!(state.autocompleted_buffer(), Some("er value"));
assert_eq!(state.cursor_position(), 12);
assert_eq!(state.buffer, "long");
_ = state.uwk_cursor_to_begin();
assert!(state.autocompleted_buffer().is_none());
assert_eq!(state.buffer, "");
}
{
let mut state = UnlockedStdinInputState::new();
_ = state.process_all("long", None);
_ = state.handle_new_autocomplete_suggestion("er value");
assert_eq!(state.autocompleted_buffer(), Some("er value"));
assert_eq!(state.cursor_position(), 12);
assert_eq!(state.buffer, "long");
assert!(state.move_cursor_left(1));
_ = state.uwk_cursor_to_end();
assert!(state.autocompleted_buffer().is_none());
}
{
let mut state = UnlockedStdinInputState::new();
_ = state.process_all("long", None);
_ = state.handle_new_autocomplete_suggestion("er value");
assert_eq!(state.autocompleted_buffer(), Some("er value"));
assert_eq!(state.cursor_position(), 12);
assert_eq!(state.buffer, "long");
_ = state.uwk_cursor_word();
assert!(state.autocompleted_buffer().is_none());
assert_eq!(state.buffer, "longer");
}
{
let mut state = UnlockedStdinInputState::new();
_ = state.process_all("long", None);
_ = state.handle_new_autocomplete_suggestion("er value");
assert_eq!(state.autocompleted_buffer(), Some("er value"));
assert_eq!(state.cursor_position(), 12);
assert_eq!(state.buffer, "long");
_ = state.swap_last_two_characters();
assert!(state.autocompleted_buffer().is_none());
assert_eq!(state.buffer, "longer valeu");
}
{
let mut state = UnlockedStdinInputState::new();
_ = state.process_all("long", None);
_ = state.handle_new_autocomplete_suggestion("er value");
assert_eq!(state.autocompleted_buffer(), Some("er value"));
assert_eq!(state.cursor_position(), 12);
assert_eq!(state.buffer, "long");
_ = state.pop_n(2);
assert_eq!(state.autocompleted_buffer(), None);
assert_eq!(state.cursor_position(), 2);
}
}
}