use std::collections::BTreeSet;
use bcode_session_models::SessionInputHistoryEntry;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InputHistoryOutcome {
Entry {
index: usize,
total: usize,
text: String,
},
DraftRestored(String),
Empty,
NotBrowsing,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InputHistory {
entries: Vec<SessionInputHistoryEntry>,
sequences: BTreeSet<u64>,
index: Option<usize>,
browse_len: Option<usize>,
draft: Option<String>,
}
impl InputHistory {
#[must_use]
pub fn from_entries(entries: &[SessionInputHistoryEntry]) -> Self {
let entries = entries
.iter()
.filter(|entry| !entry.text.trim().is_empty())
.cloned()
.collect::<Vec<_>>();
Self {
sequences: entries.iter().map(|entry| entry.sequence).collect(),
entries,
index: None,
browse_len: None,
draft: None,
}
}
#[must_use]
pub fn entries(&self) -> &[SessionInputHistoryEntry] {
&self.entries
}
pub fn push_committed(&mut self, sequence: u64, timestamp_ms: u64, text: &str) {
if text.trim().is_empty() || !self.sequences.insert(sequence) {
return;
}
self.entries.push(SessionInputHistoryEntry {
sequence,
timestamp_ms,
text: text.to_owned(),
});
}
pub fn prepend_committed(
&mut self,
messages: impl IntoIterator<Item = SessionInputHistoryEntry>,
) {
let messages = messages
.into_iter()
.filter(|entry| !entry.text.trim().is_empty() && self.sequences.insert(entry.sequence))
.collect::<Vec<_>>();
if messages.is_empty() {
return;
}
let added = messages.len();
self.entries.splice(0..0, messages);
if let Some(index) = &mut self.index {
*index = index.saturating_add(added);
}
if let Some(browse_len) = &mut self.browse_len {
*browse_len = browse_len.saturating_add(added);
}
}
pub fn previous(&mut self, current_draft: &str) -> InputHistoryOutcome {
if self.entries.is_empty() {
return InputHistoryOutcome::Empty;
}
let browse_len = self.browse_len.unwrap_or(self.entries.len());
let next_index = self.index.map_or_else(
|| browse_len.saturating_sub(1),
|index| index.saturating_sub(1),
);
if self.index.is_none() {
self.draft = Some(current_draft.to_owned());
self.browse_len = Some(self.entries.len());
}
self.index = Some(next_index);
self.entry_outcome(next_index)
}
pub fn next(&mut self) -> InputHistoryOutcome {
let Some(index) = self.index else {
return InputHistoryOutcome::NotBrowsing;
};
let browse_len = self.browse_len.unwrap_or(self.entries.len());
if index + 1 < browse_len {
let next_index = index + 1;
self.index = Some(next_index);
self.entry_outcome(next_index)
} else {
self.index = None;
self.browse_len = None;
InputHistoryOutcome::DraftRestored(self.draft.take().unwrap_or_default())
}
}
#[must_use]
pub const fn is_browsing(&self) -> bool {
self.index.is_some()
}
pub fn reset_navigation(&mut self) {
self.index = None;
self.browse_len = None;
self.draft = None;
}
fn entry_outcome(&self, index: usize) -> InputHistoryOutcome {
InputHistoryOutcome::Entry {
index: index.saturating_add(1),
total: self.entries.len(),
text: self.entries[index].text.clone(),
}
}
}