use atuin_client::{
database::{Context, Database, DbSearchMode, OptFilters},
history::{History, HistoryId, all_user_author_filter},
settings::{FilterMode, SearchMode, Settings, Shells},
};
use enum_dispatch::enum_dispatch;
use eyre::Result;
use super::cursor::Cursor;
#[cfg(feature = "daemon")]
pub mod daemon;
pub mod db;
#[allow(unused)] pub fn engine(search_mode: SearchMode, settings: &Settings) -> AnySearchEngine {
match search_mode {
#[cfg(feature = "daemon")]
SearchMode::DaemonFuzzy => Box::new(daemon::Search::new(settings)).into(),
#[cfg(not(feature = "daemon"))]
SearchMode::DaemonFuzzy => {
db::Search(DbSearchMode::Fuzzy).into()
}
SearchMode::Prefix => db::Search(DbSearchMode::Prefix).into(),
SearchMode::FullText => db::Search(DbSearchMode::FullText).into(),
SearchMode::Fuzzy => db::Search(DbSearchMode::Fuzzy).into(),
}
}
pub struct SearchState {
pub input: Cursor,
pub filter_mode: FilterMode,
pub context: Context,
pub custom_context: Option<HistoryId>,
pub shells: Shells,
}
impl SearchState {
pub(crate) fn rotate_filter_mode(&mut self, settings: &Settings, offset: isize) {
let mut i = settings
.search
.filters
.iter()
.position(|&m| m == self.filter_mode)
.unwrap_or_default();
for _ in 0..settings.search.filters.len() {
i = (i.wrapping_add_signed(offset)) % settings.search.filters.len();
let mode = settings.search.filters[i];
if self.filter_mode_available(mode, settings) {
self.filter_mode = mode;
break;
}
}
}
fn filter_mode_available(&self, mode: FilterMode, settings: &Settings) -> bool {
match mode {
FilterMode::Global | FilterMode::SessionPreload => self.custom_context.is_none(),
FilterMode::Workspace => settings.workspaces && self.context.git_root.is_some(),
_ => true,
}
}
}
#[enum_dispatch]
pub trait SearchEngine: Send + Sync + 'static {
async fn full_query(
&mut self,
state: &SearchState,
db: &mut dyn Database,
) -> Result<Vec<History>>;
async fn query(&mut self, state: &SearchState, db: &mut dyn Database) -> Result<Vec<History>> {
if state.input.as_str().is_empty() {
let shells = state.shells.to_filter();
Ok(db
.search(
DbSearchMode::FullText,
state.filter_mode,
&state.context,
"",
OptFilters {
limit: Some(200),
authors: all_user_author_filter(),
shells: shells.as_filter(),
..Default::default()
},
)
.await?
.into_iter()
.collect::<Vec<_>>())
} else {
self.full_query(state, db).await
}
}
fn get_highlight_indices(&self, command: &str, search_input: &str) -> Vec<usize>;
}
impl<T: SearchEngine> SearchEngine for Box<T> {
async fn full_query(
&mut self,
state: &SearchState,
db: &mut dyn Database,
) -> Result<Vec<History>> {
T::full_query(self, state, db).await
}
async fn query(&mut self, state: &SearchState, db: &mut dyn Database) -> Result<Vec<History>> {
T::query(self, state, db).await
}
fn get_highlight_indices(&self, command: &str, search_input: &str) -> Vec<usize> {
T::get_highlight_indices(self, command, search_input)
}
}
#[enum_dispatch(SearchEngine)]
pub enum AnySearchEngine {
Db(db::Search),
#[cfg(feature = "daemon")]
Daemon(Box<daemon::Search>),
}