use std::time::Instant;
use super::super::{
click_sequence::ClickSequence,
composer_buffer::{ComposerBuffer, ComposerEditKey, EditOutcome},
composer_history::{history_step, HistoryStep},
paste_burst::{collapsed_paste_for, CollapsedPaste},
HistoryDirection, PasteSegment,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum SideEditFollowUp {
None,
ScrollTranscript(HistoryDirection),
}
#[derive(Debug, Default)]
pub(super) struct SideComposer {
pub(super) buffer: ComposerBuffer,
history: Vec<String>,
history_cursor: Option<usize>,
history_draft: Option<(String, Vec<PasteSegment>)>,
clicks: ClickSequence,
}
impl SideComposer {
pub(super) fn apply_edit(&mut self, edit: ComposerEditKey) -> SideEditFollowUp {
if matches!(edit, ComposerEditKey::Home | ComposerEditKey::End) {
self.reset_history_navigation();
}
match self.buffer.apply_edit(edit) {
EditOutcome::Edited => self.reset_history_navigation(),
EditOutcome::VerticalEdge(direction) => {
if self.recall_history(direction) {
return SideEditFollowUp::None;
}
if self.buffer.is_empty() {
return SideEditFollowUp::ScrollTranscript(direction);
}
self.buffer.move_vertically(direction);
}
EditOutcome::TextUnchanged => {}
}
SideEditFollowUp::None
}
pub(super) fn insert_paste(&mut self, text: &str) -> Option<CollapsedPaste> {
self.clicks.cancel();
self.reset_history_navigation();
let Some(paste) = collapsed_paste_for(text) else {
self.buffer.insert_text(text);
return None;
};
self.buffer.insert_collapsed_paste(&paste, text);
Some(paste)
}
pub(super) fn replace_text(&mut self, text: String) {
self.reset_history_navigation();
self.buffer.replace_all(text, Vec::new());
}
pub(super) fn clear(&mut self) {
self.reset_history_navigation();
self.buffer.clear();
}
pub(super) fn take_submission(&mut self) -> String {
let text = self.buffer.take_expanded();
self.reset_history_navigation();
let prompt = text.trim();
if !prompt.is_empty() && self.history.last().map(String::as_str) != Some(prompt) {
self.history.push(prompt.to_owned());
}
text
}
pub(super) fn pointer_press(&mut self, index: usize, now: Instant, column: u16, row: u16) {
let index = self.buffer.caret_index(index);
let double_click = self.clicks.register(now, column, row, index);
self.buffer.pointer_press(index, double_click);
}
pub(super) fn cancel_click_sequence(&mut self) {
self.clicks.cancel();
}
pub(super) fn pointer_drag(&mut self, index: usize) {
self.clicks.cancel();
self.buffer.pointer_drag(index);
}
pub(super) fn pointer_release(&mut self, index: Option<usize>) {
self.buffer.pointer_release(index);
}
pub(super) fn cancel_pointer(&mut self) {
self.buffer.clear_selection();
self.clicks.cancel();
}
pub(super) fn settle_pointer(&mut self) {
self.clicks.cancel();
self.buffer.finalize_selection();
}
fn reset_history_navigation(&mut self) {
self.history_cursor = None;
self.history_draft = None;
}
fn recall_history(&mut self, direction: HistoryDirection) -> bool {
let Some(step) = history_step(direction, self.history.len(), self.history_cursor) else {
return false;
};
match step {
HistoryStep::Recall { index, save_draft } => {
if save_draft {
self.history_draft = Some((
self.buffer.text().to_owned(),
self.buffer.paste_segments().to_vec(),
));
}
self.buffer
.replace_all(self.history[index].clone(), Vec::new());
self.history_cursor = Some(index);
}
HistoryStep::RestoreDraft => {
let (text, segments) = self.history_draft.take().unwrap_or_default();
self.buffer.replace_all(text, segments);
self.history_cursor = None;
}
}
true
}
}