use super::filter::HistoryFilter;
use super::render::visible_result_rows;
use super::{HistoryDbMode, load_history};
use crate::history::HistoryStore;
use crate::pager::TextScrollState;
use reedline::{HistoryItem, HistoryItemId};
use std::io;
pub(super) struct BrowsableHistoryItem {
pub(super) item: HistoryItem,
pub(super) selected: bool,
}
pub(super) struct HistoryBrowser {
pub(super) entries: Vec<BrowsableHistoryItem>,
pub(super) filtered: Vec<(usize, u32)>,
pub(super) filter: HistoryFilter,
pub(super) cursor: usize,
pub(super) scroll_offset: usize,
pub(super) feedback_message: Option<String>,
pub(super) db_mode: HistoryDbMode,
pub(super) store: HistoryStore,
pub(super) text_scroll: TextScrollState,
pub(super) show_delete_dialog: bool,
pub(super) filter_active: bool,
pub(super) cached_selected_count: usize,
}
impl HistoryBrowser {
pub(super) fn new(
entries: Vec<HistoryItem>,
db_mode: HistoryDbMode,
store: HistoryStore,
) -> Self {
let browsable: Vec<BrowsableHistoryItem> = entries
.into_iter()
.map(|item| BrowsableHistoryItem {
item,
selected: false,
})
.collect();
let filtered: Vec<(usize, u32)> =
browsable.iter().enumerate().map(|(i, _)| (i, 0)).collect();
HistoryBrowser {
entries: browsable,
filtered,
filter: HistoryFilter::default(),
cursor: 0,
scroll_offset: 0,
feedback_message: None,
db_mode,
store,
text_scroll: TextScrollState::new(),
show_delete_dialog: false,
filter_active: false,
cached_selected_count: 0,
}
}
pub(super) fn selected_count(&self) -> usize {
self.cached_selected_count
}
pub(super) fn toggle_selection(&mut self) {
if let Some(&(idx, _)) = self.filtered.get(self.cursor) {
let entry = &mut self.entries[idx];
entry.selected = !entry.selected;
if entry.selected {
self.cached_selected_count += 1;
} else {
self.cached_selected_count = self.cached_selected_count.saturating_sub(1);
}
}
}
pub(super) fn select_all_visible(&mut self) {
for &(idx, _) in &self.filtered {
if !self.entries[idx].selected {
self.entries[idx].selected = true;
self.cached_selected_count += 1;
}
}
}
pub(super) fn unselect_all(&mut self) {
for entry in &mut self.entries {
entry.selected = false;
}
self.cached_selected_count = 0;
}
pub(super) fn delete_selected(&mut self) -> io::Result<()> {
let ids_to_delete: Vec<i64> = self
.entries
.iter()
.filter(|e| e.selected)
.filter_map(|e| e.item.id)
.map(|id| id.0)
.collect();
if ids_to_delete.is_empty() {
return Ok(());
}
let ids: Vec<HistoryItemId> = ids_to_delete
.iter()
.copied()
.map(HistoryItemId::new)
.collect();
if let Err(error) = self.store.delete_many(&ids) {
self.entries = load_history(&self.store)?
.into_iter()
.map(|item| BrowsableHistoryItem {
item,
selected: false,
})
.collect();
self.cached_selected_count = 0;
self.update_filter();
return Err(io::Error::other(error));
}
let feedback = format!("Deleted {} entries", ids_to_delete.len());
self.entries.retain(|e| !e.selected);
self.cached_selected_count = 0;
self.update_filter();
if self.cursor >= self.filtered.len() && !self.filtered.is_empty() {
self.cursor = self.filtered.len() - 1;
}
self.feedback_message = Some(feedback);
Ok(())
}
pub(super) fn move_cursor_up(&mut self) {
if self.cursor > 0 {
self.cursor -= 1;
if self.cursor < self.scroll_offset {
self.scroll_offset = self.cursor;
}
}
}
pub(super) fn move_cursor_down(&mut self) {
let visible_rows = visible_result_rows();
if self.cursor + 1 < self.filtered.len() {
self.cursor += 1;
if self.cursor >= self.scroll_offset + visible_rows {
self.scroll_offset = self.cursor - visible_rows + 1;
}
}
}
pub(super) fn move_page_up(&mut self) {
if self.filtered.is_empty() {
return;
}
let page_size = visible_result_rows();
self.cursor = self.cursor.saturating_sub(page_size);
self.scroll_offset = self.scroll_offset.saturating_sub(page_size);
}
pub(super) fn move_page_down(&mut self) {
if self.filtered.is_empty() {
return;
}
let page_size = visible_result_rows();
let max_cursor = self.filtered.len() - 1;
self.cursor = (self.cursor + page_size).min(max_cursor);
let max_scroll = self.filtered.len().saturating_sub(page_size);
self.scroll_offset = (self.scroll_offset + page_size).min(max_scroll);
}
pub(super) fn move_to_top(&mut self) {
self.cursor = 0;
self.scroll_offset = 0;
}
pub(super) fn move_to_bottom(&mut self) {
if !self.filtered.is_empty() {
self.cursor = self.filtered.len() - 1;
let visible_rows = visible_result_rows();
if self.cursor >= visible_rows {
self.scroll_offset = self.cursor - visible_rows + 1;
}
}
}
pub(super) fn current_command(&self) -> Option<&str> {
self.filtered
.get(self.cursor)
.map(|&(idx, _)| self.entries[idx].item.command_line.as_str())
}
pub(super) fn update_text_scroll(&mut self) -> bool {
self.text_scroll.update(self.cursor)
}
}