use crate::input::{
autocomplete::AutocompleteProvider,
history::HistoryProvider,
stdin::{MultibyteCursorControl, state::raw::UnlockedStdinInputState},
};
use std::borrow::Cow;
#[allow(clippy::struct_excessive_bools)]
pub struct InputStateTransaction<'lf> {
locked: &'lf mut UnlockedStdinInputState,
append_buffer: Option<String>,
cancel_is_queued: bool,
clear_is_queued: bool,
pause_is_queued: bool,
start_is_queued: bool,
finish_queued: Option<(Option<String>, String)>,
multibyte_queued: Option<MultibyteCursorControl>,
multibyte_paste_counter: u8,
multibyte_cursor_counter: u8,
}
impl<'lf> InputStateTransaction<'lf> {
#[must_use]
pub(super) fn new(lock: &'lf mut UnlockedStdinInputState) -> Self {
Self {
locked: lock,
append_buffer: Some(String::new()),
cancel_is_queued: false,
clear_is_queued: false,
pause_is_queued: false,
start_is_queued: false,
finish_queued: None,
multibyte_cursor_counter: 0,
multibyte_paste_counter: 0,
multibyte_queued: None,
}
}
pub fn finished_processing(mut self) -> Option<String> {
if self.locked.check_promote_history_buffer() {
self.append_buffer = None;
}
self.append_buffer
}
#[must_use]
pub fn cursor_position(&self) -> usize {
self.locked.cursor_position()
}
#[must_use]
pub fn move_cursor_left(&mut self, amount: usize) -> bool {
let did_move = self.locked.move_cursor_left(amount);
if did_move {
self.append_buffer = None;
}
did_move
}
#[must_use]
pub fn move_cursor_right(&mut self, amount: usize) -> bool {
let did_move = self.locked.move_cursor_right(amount);
if did_move {
self.append_buffer = None;
}
did_move
}
#[must_use]
pub fn current_input(&self) -> Cow<'_, str> {
self.locked.buffer()
}
#[must_use]
pub fn get_last_completion_index(&mut self) -> Option<usize> {
self.locked.get_last_completion_index()
}
#[must_use]
pub fn get_previous_completion_index(&mut self) -> Option<usize> {
self.locked.get_previous_completion_index()
}
#[must_use]
pub fn uncompleted_input(&self) -> &str {
self.locked.uncompleted_input()
}
pub fn backspace(&mut self) {
let (was_previously_started, did_backspace) = self.locked.backspace();
if was_previously_started && did_backspace {
self.append_buffer = None;
}
if !was_previously_started && !did_backspace && self.locked.started() {
_ = self.cancel();
}
}
#[must_use]
pub fn cancel(&mut self) -> bool {
self.cancel_is_queued = true;
let had_started = self.locked.cancel();
if !had_started && !self.start_is_queued {
self.append_buffer = Some(String::new());
}
had_started
}
pub fn clear(&mut self) {
self.locked.promote_autocomplete_buffer();
self.append_buffer = None;
self.clear_is_queued = true;
}
pub fn complete(&mut self) {
let prev_autocomplete = self.append_buffer.take();
self.append_buffer = Some(String::new());
let completed = self.locked.complete();
if !completed.is_empty() {
_ = self.finish_queued.insert((prev_autocomplete, completed));
}
}
pub fn tab_action(
&mut self,
autocomplete_provider: Option<&dyn AutocompleteProvider>,
history_provider: Option<&dyn HistoryProvider>,
do_insert_tab: bool,
is_shift_tab: bool,
) {
if let Some(buff) = self.locked.history_search_buffer_mut().take() {
self.locked.handle_new_autocomplete_suggestion(&buff);
self.append_buffer = None;
return;
}
let Some(ap) = autocomplete_provider else {
if do_insert_tab {
return self.new_character_action(' ', history_provider);
}
return;
};
if ap.can_autocomplete() {
let completion_index = if is_shift_tab {
self.get_previous_completion_index()
} else {
self.get_last_completion_index()
};
self.append_buffer = None;
let Some(ac_value) = ap.get_tab_completion(completion_index, self.uncompleted_input())
else {
if do_insert_tab {
return self.new_character_action(' ', history_provider);
}
return;
};
if ac_value.is_empty() {
if do_insert_tab {
self.new_character_action(' ', history_provider);
}
} else {
self.locked.handle_new_autocomplete_suggestion(&ac_value);
}
} else if do_insert_tab {
self.new_character_action(' ', history_provider);
}
}
pub fn previous_history_action(&mut self, history_provider: Option<&dyn HistoryProvider>) {
let Some(hprovider) = history_provider else {
return self.cancel_if_empty();
};
let next_index = self.locked.get_next_history_index();
if let Some(history_item) = hprovider.get_previous_command(next_index) {
let prev_buff_is_empty = self.locked.uncompleted_input().is_empty();
let max_len = history_item.chars().count();
if !prev_buff_is_empty && self.locked.original_uncompleted_history().is_none() {
let to_save = self.locked.uncompleted_input().to_owned();
self.locked.set_uncompleted_history(to_save);
}
self.locked.handle_new_buff_from_history(history_item);
self.append_buffer = None;
if !self.locked.started() {
self.locked.mark_started();
self.start_is_queued = true;
}
if prev_buff_is_empty {
self.locked.set_cursor_position(max_len);
}
} else {
self.cancel_if_empty();
}
}
pub fn next_history_action(&mut self, history_provider: Option<&dyn HistoryProvider>) {
let Some(hprovider) = history_provider else {
return self.cancel_if_empty();
};
let Some(next_index) = self.locked.get_previous_history_index() else {
if let Some(value) = self.locked.take_original_uncompleted_history() {
self.locked.handle_new_buff_from_history(value);
self.append_buffer = None;
return;
}
return self.cancel_if_empty();
};
if let Some(history_item) = hprovider.get_previous_command(next_index) {
self.locked.handle_new_buff_from_history(history_item);
self.append_buffer = None;
if !self.locked.started() {
self.locked.mark_started();
self.start_is_queued = true;
}
} else if let Some(value) = self.locked.take_original_uncompleted_history() {
self.locked.handle_new_buff_from_history(value);
self.append_buffer = None;
} else {
self.cancel_if_empty();
}
}
pub fn start_history_search_action(&mut self, history: Option<&dyn HistoryProvider>) {
self.locked.start_history_search_suggestions(history);
self.append_buffer = None;
}
pub fn new_character_action(
&mut self,
new_character: char,
history: Option<&dyn HistoryProvider>,
) {
self.check_multibyte_paste(new_character);
let did_multibyte = self.check_multibyte_u1b(new_character);
let did_change;
(self.start_is_queued, did_change) =
self.locked.process(new_character, did_multibyte, history);
if did_change {
self.append_buffer = None;
} else if let Some(buf) = self.append_buffer.as_mut() {
buf.push(new_character);
}
}
pub fn new_string_action(&mut self, data: &str, history: Option<&dyn HistoryProvider>) {
let did_change;
(self.start_is_queued, did_change) = self.locked.process_all(data, history);
if did_change {
self.append_buffer = None;
} else if let Some(buf) = self.append_buffer.as_mut() {
*buf += data;
}
}
pub fn uwk_cursor_to_begin(&mut self) {
if self.locked.uwk_cursor_to_begin() {
self.append_buffer = None;
}
}
pub fn uwk_cursor_to_end(&mut self) {
if self.locked.uwk_cursor_to_end() {
self.append_buffer = None;
}
}
pub fn uwk_cursor_word(&mut self) {
if self.locked.uwk_cursor_word() {
self.append_buffer = None;
}
}
pub fn paste_uwk(&mut self) {
if self.locked.paste_uwk() {
self.append_buffer = None;
}
}
pub fn swap_last_two_characters(&mut self) {
if self.locked.swap_last_two_characters() {
self.append_buffer = None;
}
}
pub fn pause_output(&mut self) {
self.locked.promote_autocomplete_buffer();
self.pause_is_queued = true;
}
#[must_use]
pub const fn did_trigger_cancel(&mut self) -> bool {
let queued = self.cancel_is_queued;
self.cancel_is_queued = false;
queued
}
#[must_use]
pub const fn did_trigger_clear(&mut self) -> bool {
let queued = self.clear_is_queued;
self.clear_is_queued = false;
queued
}
#[must_use]
pub const fn did_trigger_pause(&mut self) -> bool {
let queued = self.pause_is_queued;
self.pause_is_queued = false;
queued
}
#[must_use]
pub fn did_trigger_multibyte_paste(&mut self) -> bool {
if self.multibyte_paste_counter == 9 {
self.multibyte_paste_counter = 0;
self.locked.pop_n(9);
if let Some(ledger) = self.append_buffer.as_mut() {
for _ in 0..9 {
_ = ledger.pop();
}
}
return true;
}
false
}
pub fn did_trigger_multibyte_cursor(
&mut self,
autocomplete_provider: Option<&dyn AutocompleteProvider>,
history_provider: Option<&dyn HistoryProvider>,
ansi_supported: bool,
) -> Option<(bool, usize, Option<String>, usize)> {
let event = self.multibyte_queued.take()?;
match event {
MultibyteCursorControl::ShiftTab => {
self.locked.pop_n(3);
if let Some(ledger) = self.append_buffer.as_mut() {
for _ in 0..3 {
_ = ledger.pop();
}
}
self.tab_action(autocomplete_provider, history_provider, false, true);
}
MultibyteCursorControl::UpArrow => {
self.locked.pop_n_promoting_autocomplete_buffer(3);
if let Some(ledger) = self.append_buffer.as_mut() {
for _ in 0..3 {
_ = ledger.pop();
}
}
self.previous_history_action(history_provider);
}
MultibyteCursorControl::DownArrow => {
self.locked.pop_n_promoting_autocomplete_buffer(3);
if let Some(ledger) = self.append_buffer.as_mut() {
for _ in 0..3 {
_ = ledger.pop();
}
}
self.next_history_action(history_provider);
}
MultibyteCursorControl::LeftArrow | MultibyteCursorControl::RightArrow => {
self.locked.pop_n_promoting_autocomplete_buffer(3);
if let Some(ledger) = self.append_buffer.as_mut() {
for _ in 0..3 {
_ = ledger.pop();
}
}
if ansi_supported {
let org_append = self.append_buffer.take();
let org_position = self.cursor_position();
let move_left = matches!(event, MultibyteCursorControl::LeftArrow);
let move_result = if move_left {
self.locked.move_cursor_left(1)
} else {
self.locked.move_cursor_right(1)
};
return if move_result {
self.append_buffer = Some(String::with_capacity(0));
Some((move_left, 1, org_append, org_position))
} else {
None
};
}
}
MultibyteCursorControl::ShiftLeftArrow | MultibyteCursorControl::ShiftRightArrow => {
self.locked.pop_n_promoting_autocomplete_buffer(6);
if let Some(ledger) = self.append_buffer.as_mut() {
for _ in 0..6 {
_ = ledger.pop();
}
}
if ansi_supported {
let move_left = matches!(event, MultibyteCursorControl::ShiftLeftArrow);
let cursor_move_amount =
if let Some((start, end)) = self.locked.get_word_bounds_at_cursor() {
if move_left {
self.locked.cursor_position() - start
} else {
end - self.locked.cursor_position()
}
} else if move_left {
self.locked.cursor_position()
} else {
self.locked.uncompleted_input().chars().count()
};
let move_result = if move_left {
self.locked.move_cursor_left(cursor_move_amount)
} else {
self.locked.move_cursor_right(cursor_move_amount)
};
return if move_result {
let org_append = self.append_buffer.take();
self.append_buffer = Some(String::with_capacity(0));
let org_position = self.cursor_position();
Some((move_left, cursor_move_amount, org_append, org_position))
} else {
None
};
}
}
}
self.multibyte_cursor_counter = 0;
None
}
#[must_use]
pub fn did_trigger_start(&mut self) -> bool {
let queued = self.start_is_queued;
self.start_is_queued = false;
queued
}
#[must_use]
pub fn did_trigger_finish(&mut self) -> Option<(Option<String>, String)> {
self.finish_queued.take()
}
fn cancel_if_empty(&mut self) {
if self.locked.buffer().is_empty() && self.locked.started() {
_ = self.cancel();
}
}
fn check_multibyte_paste(&mut self, new_character: char) {
if new_character == '\\' && self.multibyte_paste_counter == 0 {
self.multibyte_paste_counter += 1;
} else if self.multibyte_paste_counter > 0 {
let do_reset = match self.multibyte_paste_counter {
1 => new_character != '\\',
2 => new_character != 'u',
3 => new_character != '{',
4 => new_character != '1',
5 => new_character != 'b',
6 => new_character != '}',
7 => new_character != '[',
8 => new_character != 'Z',
_ => true,
};
if do_reset {
self.multibyte_paste_counter = 0;
} else {
self.multibyte_paste_counter += 1;
}
}
}
fn check_multibyte_u1b(&mut self, new_character: char) -> bool {
if new_character == '\u{1b}' && self.multibyte_cursor_counter == 0 {
self.multibyte_cursor_counter += 1;
true
} else if self.multibyte_cursor_counter > 0 {
let mut did_multibyte = true;
let do_reset = match self.multibyte_cursor_counter {
1 => {
let should_reset = new_character != '[';
if should_reset {
did_multibyte = false;
}
should_reset
}
2 => match new_character {
'A' => {
_ = self
.multibyte_queued
.insert(MultibyteCursorControl::UpArrow);
true
}
'B' => {
_ = self
.multibyte_queued
.insert(MultibyteCursorControl::DownArrow);
true
}
'C' => {
_ = self
.multibyte_queued
.insert(MultibyteCursorControl::RightArrow);
true
}
'D' => {
_ = self
.multibyte_queued
.insert(MultibyteCursorControl::LeftArrow);
true
}
'Z' => {
_ = self
.multibyte_queued
.insert(MultibyteCursorControl::ShiftTab);
true
}
'1' => false,
_ => {
did_multibyte = false;
true
}
},
3 => {
if new_character != ';' {
did_multibyte = false;
}
false
}
4 => {
if new_character != '2' {
did_multibyte = false;
}
false
}
5 => match new_character {
'C' => {
_ = self
.multibyte_queued
.insert(MultibyteCursorControl::ShiftRightArrow);
true
}
'D' => {
_ = self
.multibyte_queued
.insert(MultibyteCursorControl::ShiftLeftArrow);
true
}
_ => {
did_multibyte = false;
true
}
},
_ => {
did_multibyte = false;
true
}
};
if do_reset {
self.multibyte_cursor_counter = 0;
} else {
self.multibyte_cursor_counter += 1;
}
did_multibyte
} else {
false
}
}
}