diff options
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/atuin-client/src/settings.rs | 14 | ||||
| -rw-r--r-- | crates/atuin-daemon/proto/search.proto | 1 | ||||
| -rw-r--r-- | crates/atuin-daemon/src/client.rs | 2 | ||||
| -rw-r--r-- | crates/atuin-daemon/src/components/search.rs | 9 | ||||
| -rw-r--r-- | crates/atuin-daemon/src/search/index.rs | 292 | ||||
| -rw-r--r-- | crates/atuin/src/command/client/search.rs | 29 | ||||
| -rw-r--r-- | crates/atuin/src/command/client/search/engines.rs | 50 | ||||
| -rw-r--r-- | crates/atuin/src/command/client/search/engines/daemon.rs | 34 | ||||
| -rw-r--r-- | crates/atuin/src/command/client/search/engines/db.rs | 80 | ||||
| -rw-r--r-- | crates/atuin/src/command/client/search/engines/skim.rs | 83 | ||||
| -rw-r--r-- | crates/atuin/src/command/client/search/interactive.rs | 11 |
11 files changed, 74 insertions, 531 deletions
diff --git a/crates/atuin-client/src/settings.rs b/crates/atuin-client/src/settings.rs index 5944de59..25c3bd65 100644 --- a/crates/atuin-client/src/settings.rs +++ b/crates/atuin-client/src/settings.rs @@ -565,13 +565,6 @@ pub struct Search { /// The overall frecency score multiplier for the search index (default: 1.0). /// Applied after combining recency and frequency scores. pub frecency_score_multiplier: f64, - - /// Filter history by author. Special values: - /// - `$all-user`: any author that is NOT a known AI agent (default) - /// - `$all-agent`: any known AI agent author - /// - literal strings like "ellie", "claude-code" - #[serde(default = "Search::default_authors")] - pub authors: Vec<String>, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -851,17 +844,10 @@ impl Default for Search { recency_score_multiplier: 1.0, frequency_score_multiplier: 1.0, frecency_score_multiplier: 1.0, - authors: Self::default_authors(), } } } -impl Search { - fn default_authors() -> Vec<String> { - vec![crate::history::AUTHOR_FILTER_ALL_USER.to_string()] - } -} - impl Default for Tmux { fn default() -> Self { Self { diff --git a/crates/atuin-daemon/proto/search.proto b/crates/atuin-daemon/proto/search.proto index 5eea2b62..6b84acbd 100644 --- a/crates/atuin-daemon/proto/search.proto +++ b/crates/atuin-daemon/proto/search.proto @@ -23,7 +23,6 @@ message SearchRequest { uint64 query_id = 2; // Incrementing ID to match responses to queries FilterMode filter_mode = 3; SearchContext context = 4; - repeated string authors = 5; // Author filter ($all-user, $all-agent, or literals) } message SearchResponse { diff --git a/crates/atuin-daemon/src/client.rs b/crates/atuin-daemon/src/client.rs index 51334ee1..5f4ce20f 100644 --- a/crates/atuin-daemon/src/client.rs +++ b/crates/atuin-daemon/src/client.rs @@ -213,14 +213,12 @@ impl SearchClient { query_id: u64, filter_mode: FilterMode, context: Option<Context>, - authors: Vec<String>, ) -> Result<tonic::Streaming<SearchResponse>> { let request = SearchRequest { query, query_id, filter_mode: RpcFilterMode::from(filter_mode).into(), context: context.map(RpcSearchContext::from), - authors, }; let request_stream = tokio_stream::once(request); let response = span!(Level::TRACE, "daemon_client_search.request") diff --git a/crates/atuin-daemon/src/components/search.rs b/crates/atuin-daemon/src/components/search.rs index a2e74aa5..9fc87fae 100644 --- a/crates/atuin-daemon/src/components/search.rs +++ b/crates/atuin-daemon/src/components/search.rs @@ -304,7 +304,6 @@ impl SearchSvc for SearchGrpcService { .try_into() .unwrap_or(FilterMode::Global); let proto_context = search_req.context; - let authors = search_req.authors; debug!( "search request: query = {}, query_id = {}, filter_mode = {}, context = {:?}", @@ -333,13 +332,7 @@ impl SearchSvc for SearchGrpcService { .in_scope(|| async { let index = index.read().await; index - .search( - &query, - index_filter, - &query_context, - &authors, - RESULTS_LIMIT, - ) + .search(&query, index_filter, &query_context, RESULTS_LIMIT) .await }) .await; diff --git a/crates/atuin-daemon/src/search/index.rs b/crates/atuin-daemon/src/search/index.rs index 90751155..bb155979 100644 --- a/crates/atuin-daemon/src/search/index.rs +++ b/crates/atuin-daemon/src/search/index.rs @@ -12,9 +12,7 @@ use std::{ sync::Arc, }; -use atuin_client::history::{ - AUTHOR_FILTER_ALL_AGENT, AUTHOR_FILTER_ALL_USER, History, KNOWN_AGENTS, -}; +use atuin_client::history::{History, is_known_agent}; use atuin_client::settings::Search; use atuin_nucleo::{Injector, Nucleo, pattern}; use dashmap::DashMap; @@ -116,100 +114,6 @@ pub struct CommandData { hosts: HashSet<Spur>, /// All sessions where this command has been run (as 16-byte UUIDs). sessions: HashSet<[u8; 16]>, - /// All authors who have run this command (interned keys). - authors: HashSet<Spur>, - /// Per-invocation metadata for returning the newest row that matches the active filters. - invocations: Vec<InvocationData>, -} - -struct InvocationData { - id: [u8; 16], - timestamp: i64, - directory: Spur, - host: Spur, - session: [u8; 16], - author: Spur, -} - -#[derive(Default)] -struct CompiledAuthorFilter { - include_all_users: bool, - include_all_agents: bool, - literal_authors: HashSet<Spur>, -} - -impl CompiledAuthorFilter { - fn new(filters: &[String], interner: &ThreadedRodeo) -> Self { - let mut compiled = Self::default(); - - for filter in filters { - match filter.as_str() { - AUTHOR_FILTER_ALL_USER => compiled.include_all_users = true, - AUTHOR_FILTER_ALL_AGENT => compiled.include_all_agents = true, - literal => { - if let Some(author) = interner.get(literal) { - compiled.literal_authors.insert(author); - } - } - } - } - - compiled - } - - fn is_empty(&self) -> bool { - !self.include_all_users && !self.include_all_agents && self.literal_authors.is_empty() - } - - fn matches_author(&self, author: Spur, agent_authors: &HashSet<Spur>) -> bool { - self.is_empty() - || self.literal_authors.contains(&author) - || (self.include_all_users && !agent_authors.contains(&author)) - || (self.include_all_agents && agent_authors.contains(&author)) - } -} - -impl InvocationData { - fn new(history: &History, interner: &ThreadedRodeo) -> Option<Self> { - Some(Self { - id: parse_uuid_bytes(&history.id.0)?, - timestamp: history.timestamp.unix_timestamp(), - directory: interner.get_or_intern(with_trailing_slash(&history.cwd)), - host: interner.get_or_intern(&history.hostname), - session: parse_uuid_bytes(&history.session)?, - author: interner.get_or_intern(&history.author), - }) - } - - fn matches_mode(&self, mode: &IndexFilterMode, interner: &ThreadedRodeo) -> bool { - match mode { - IndexFilterMode::Global => true, - IndexFilterMode::Directory(dir) => { - interner.get(dir).is_some_and(|spur| self.directory == spur) - } - IndexFilterMode::Workspace(prefix) => { - interner.resolve(&self.directory).starts_with(prefix) - } - IndexFilterMode::Host(hostname) => { - interner.get(hostname).is_some_and(|spur| self.host == spur) - } - IndexFilterMode::Session(session) => { - parse_uuid_bytes(session).is_some_and(|bytes| self.session == bytes) - } - } - } - - fn matches_authors( - &self, - filter: &CompiledAuthorFilter, - agent_authors: &HashSet<Spur>, - ) -> bool { - filter.matches_author(self.author, agent_authors) - } - - fn id_string(&self) -> String { - format_uuid_bytes(&self.id) - } } impl CommandData { @@ -222,7 +126,6 @@ impl CommandData { let dir_key = interner.get_or_intern(with_trailing_slash(&history.cwd)); let host_key = interner.get_or_intern(&history.hostname); - let invocation = InvocationData::new(history, interner)?; let mut directories = HashSet::new(); directories.insert(dir_key); @@ -233,9 +136,6 @@ impl CommandData { let mut sessions = HashSet::new(); sessions.insert(session); - let mut authors = HashSet::new(); - authors.insert(interner.get_or_intern(&history.author)); - let mut global_frecency = FrecencyData::default(); global_frecency.record_use(timestamp); @@ -246,8 +146,6 @@ impl CommandData { directories, hosts, sessions, - authors, - invocations: vec![invocation], }) } @@ -262,9 +160,6 @@ impl CommandData { }; let timestamp = history.timestamp.unix_timestamp(); - let Some(invocation) = InvocationData::new(history, interner) else { - return false; - }; // Update global frecency self.global_frecency.record_use(timestamp); @@ -274,8 +169,6 @@ impl CommandData { self.directories.insert(dir_key); self.hosts.insert(interner.get_or_intern(&history.hostname)); self.sessions.insert(session); - self.authors.insert(interner.get_or_intern(&history.author)); - self.invocations.push(invocation); // Update most recent if this invocation is newer if timestamp > self.most_recent_timestamp { @@ -291,27 +184,6 @@ impl CommandData { format_uuid_bytes(&self.most_recent_id) } - fn most_recent_matching_id( - &self, - mode: &IndexFilterMode, - authors: &CompiledAuthorFilter, - interner: &ThreadedRodeo, - agent_authors: &HashSet<Spur>, - ) -> Option<String> { - if matches!(mode, IndexFilterMode::Global) && authors.is_empty() { - return Some(self.most_recent_id()); - } - - self.invocations - .iter() - .filter(|invocation| { - invocation.matches_mode(mode, interner) - && invocation.matches_authors(authors, agent_authors) - }) - .max_by_key(|invocation| invocation.timestamp) - .map(InvocationData::id_string) - } - /// Check if any invocation matches a directory filter (exact match). /// O(1) lookup using pre-computed index. pub fn has_invocation_in_dir(&self, dir: &str, interner: &ThreadedRodeo) -> bool { @@ -341,16 +213,6 @@ impl CommandData { pub fn has_invocation_in_session(&self, session: &str) -> bool { parse_uuid_bytes(session).is_some_and(|bytes| self.sessions.contains(&bytes)) } - - fn matches_authors( - &self, - filter: &CompiledAuthorFilter, - agent_authors: &HashSet<Spur>, - ) -> bool { - self.authors - .iter() - .any(|author| filter.matches_author(*author, agent_authors)) - } } /// Filter mode for search queries. @@ -426,6 +288,10 @@ impl SearchIndex { /// If the command already exists, updates its invocation data. /// If it's a new command, adds it to both the map and Nucleo. pub fn add_history(&self, history: &History) { + if is_known_agent(&history.author) { + return; + } + let command = history.command.as_str(); // DashMap with Arc<str> keys can be looked up with &str via Borrow trait @@ -474,7 +340,6 @@ impl SearchIndex { query: &str, filter_mode: IndexFilterMode, _context: &QueryContext, - authors: &[String], limit: u32, ) -> Vec<String> { let mut nucleo = self.nucleo.write().await; @@ -482,21 +347,8 @@ impl SearchIndex { // Get precomputed frecency map (may be None if not yet computed) let frecency_map = self.frecency_map.read().await.clone(); - // Build filter based on mode + authors - let mode_filter = self.build_filter(&filter_mode); - let compiled_authors = CompiledAuthorFilter::new(authors, &self.interner); - let author_filter = self.build_author_filter(&compiled_authors); - let agent_authors: HashSet<Spur> = KNOWN_AGENTS - .iter() - .filter_map(|author| self.interner.get(author)) - .collect(); - let filter = - match (mode_filter, author_filter) { - (Some(mf), Some(af)) => Some(Arc::new(move |cmd: &String| mf(cmd) && af(cmd)) - as atuin_nucleo::Filter<String>), - (Some(f), None) | (None, Some(f)) => Some(f), - (None, None) => None, - }; + // Build filter based on mode + let filter = self.build_filter(&filter_mode); nucleo.set_filter(filter); // Build scorer from precomputed frecency (or None if not available) @@ -527,14 +379,9 @@ impl SearchIndex { .filter_map(|item| { let cmd = item.data; // DashMap<Arc<str>, _>::get accepts &str via Borrow trait - self.commands.get(cmd.as_str()).and_then(|data| { - data.most_recent_matching_id( - &filter_mode, - &compiled_authors, - &self.interner, - &agent_authors, - ) - }) + self.commands + .get(cmd.as_str()) + .map(|data| data.most_recent_id()) }) .collect() }) @@ -609,36 +456,6 @@ impl SearchIndex { Some(Arc::new(move |cmd: &String| passing_commands.contains(cmd))) } - /// Build author filter predicate. - /// Same pre-computation approach as `build_filter`: find all commands with - /// a matching author, collect into a HashSet, wrap as a Nucleo Filter closure. - fn build_author_filter( - &self, - authors: &CompiledAuthorFilter, - ) -> Option<atuin_nucleo::Filter<String>> { - if authors.is_empty() { - return None; - } - - let agent_authors: HashSet<Spur> = KNOWN_AGENTS - .iter() - .filter_map(|a| self.interner.get(a)) - .collect(); - - let passing_commands: Arc<HashSet<String>> = { - let mut set = HashSet::new(); - for entry in self.commands.iter() { - let passes = entry.matches_authors(authors, &agent_authors); - if passes { - set.insert(entry.key().to_string()); - } - } - Arc::new(set) - }; - - Some(Arc::new(move |cmd: &String| passing_commands.contains(cmd))) - } - /// Build scorer from precomputed frecency map. /// /// Returns None if frecency map is not available (search still works, just without frecency ranking). @@ -821,86 +638,6 @@ mod tests { } #[tokio::test] - async fn search_index_returns_latest_matching_author_invocation() { - let index = SearchIndex::new(); - - let user_history: History = History::import() - .timestamp(datetime!(2024-01-01 10:00 UTC)) - .command("git status") - .cwd("/home/user/project") - .author("ellie") - .build() - .into(); - let agent_history: History = History::import() - .timestamp(datetime!(2024-01-01 11:00 UTC)) - .command("git status") - .cwd("/home/user/project") - .author("codex") - .build() - .into(); - - index.add_history(&user_history); - index.add_history(&agent_history); - - let user_results = index - .search( - "git", - IndexFilterMode::Global, - &QueryContext::default(), - &[AUTHOR_FILTER_ALL_USER.to_string()], - 10, - ) - .await; - assert_eq!( - user_results, - vec![Uuid::parse_str(&user_history.id.0).unwrap().to_string()] - ); - - let agent_results = index - .search( - "git", - IndexFilterMode::Global, - &QueryContext::default(), - &[AUTHOR_FILTER_ALL_AGENT.to_string()], - 10, - ) - .await; - assert_eq!( - agent_results, - vec![Uuid::parse_str(&agent_history.id.0).unwrap().to_string()] - ); - } - - #[tokio::test] - async fn search_index_returns_latest_matching_directory_invocation() { - let index = SearchIndex::new(); - - let dir1 = "/home/user/project"; - let dir2 = "/home/user/other"; - - let project_history = make_history("git status", dir1, datetime!(2024-01-01 10:00 UTC)); - let other_history = make_history("git status", dir2, datetime!(2024-01-01 11:00 UTC)); - - index.add_history(&project_history); - index.add_history(&other_history); - - let results = index - .search( - "git", - IndexFilterMode::Directory(with_trailing_slash(dir1)), - &QueryContext::default(), - &[], - 10, - ) - .await; - - assert_eq!( - results, - vec![Uuid::parse_str(&project_history.id.0).unwrap().to_string()] - ); - } - - #[tokio::test] async fn search_index_add_and_search() { let index = SearchIndex::new(); @@ -928,13 +665,7 @@ mod tests { // Search for "git" - should match 2 commands let results = index - .search( - "git", - IndexFilterMode::Global, - &QueryContext::default(), - &[], - 10, - ) + .search("git", IndexFilterMode::Global, &QueryContext::default(), 10) .await; assert_eq!(results.len(), 2); @@ -944,7 +675,6 @@ mod tests { "", IndexFilterMode::Directory(with_trailing_slash("/home/user/project")), &QueryContext::default(), - &[], 10, ) .await; diff --git a/crates/atuin/src/command/client/search.rs b/crates/atuin/src/command/client/search.rs index 19045867..8eaf1b0c 100644 --- a/crates/atuin/src/command/client/search.rs +++ b/crates/atuin/src/command/client/search.rs @@ -146,12 +146,6 @@ pub struct Cmd { } impl Cmd { - fn resolved_authors(&self, settings: &Settings) -> Vec<String> { - self.author - .clone() - .unwrap_or_else(|| settings.search.authors.clone()) - } - /// Returns true if this search command will run in interactive (TUI) mode pub fn is_interactive(&self) -> bool { self.interactive @@ -168,7 +162,6 @@ impl Cmd { store: SqliteStore, theme: &Theme, ) -> Result<()> { - let authors = self.resolved_authors(settings); let query = self.query.unwrap_or_else(|| { std::env::var("ATUIN_QUERY").map_or_else( |_| vec![], @@ -233,8 +226,7 @@ impl Cmd { let history_store = HistoryStore::new(store.clone(), host_id, encryption_key); if self.interactive { - let item = - interactive::history(&query, authors, settings, db, &history_store, theme).await?; + let item = interactive::history(&query, settings, db, &history_store, theme).await?; if let Some(result_file) = self.result_file { let mut file = File::create(result_file)?; @@ -261,7 +253,7 @@ impl Cmd { offset: self.offset, reverse: self.reverse, include_duplicates: self.include_duplicates, - authors, + authors: self.author.clone().unwrap_or_default(), }; let mut entries = @@ -351,7 +343,6 @@ async fn run_non_interactive( #[cfg(test)] mod tests { use super::Cmd; - use atuin_client::settings::Settings; use clap::Parser; #[test] @@ -373,22 +364,12 @@ mod tests { } #[test] - fn search_authors_default_to_settings() { - let cmd = Cmd::try_parse_from(["search"]).unwrap(); - let settings = Settings::default(); - - assert_eq!(cmd.resolved_authors(&settings), settings.search.authors); - } - - #[test] - fn search_authors_cli_override_config() { + fn search_author_cli_flag() { let cmd = Cmd::try_parse_from(["search", "--author", "codex", "--author", "ellie"]).unwrap(); - let settings = Settings::default(); - assert_eq!( - cmd.resolved_authors(&settings), - vec!["codex".to_string(), "ellie".to_string()] + cmd.author, + Some(vec!["codex".to_string(), "ellie".to_string()]) ); } } diff --git a/crates/atuin/src/command/client/search/engines.rs b/crates/atuin/src/command/client/search/engines.rs index 98692828..27c1c1b8 100644 --- a/crates/atuin/src/command/client/search/engines.rs +++ b/crates/atuin/src/command/client/search/engines.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use atuin_client::{ database::{Context, Database, OptFilters}, - history::{History, HistoryId}, + history::{AUTHOR_FILTER_ALL_USER, History, HistoryId}, settings::{FilterMode, SearchMode, Settings}, }; use eyre::Result; @@ -33,30 +33,6 @@ pub struct SearchState { pub filter_mode: FilterMode, pub context: Context, pub custom_context: Option<HistoryId>, - pub authors: Vec<String>, -} - -async fn search_db( - state: &SearchState, - db: &dyn Database, - mode: SearchMode, - query: &str, -) -> Result<Vec<History>> { - Ok(db - .search( - mode, - state.filter_mode, - &state.context, - query, - OptFilters { - limit: Some(200), - authors: state.authors.clone(), - ..Default::default() - }, - ) - .await? - .into_iter() - .collect()) } impl SearchState { @@ -94,17 +70,23 @@ pub trait SearchEngine: Send + Sync + 'static { db: &mut dyn Database, ) -> Result<Vec<History>>; - async fn empty_query( - &mut self, - state: &SearchState, - db: &mut dyn Database, - ) -> Result<Vec<History>> { - search_db(state, db, SearchMode::FullText, "").await - } - async fn query(&mut self, state: &SearchState, db: &mut dyn Database) -> Result<Vec<History>> { if state.input.as_str().is_empty() { - self.empty_query(state, db).await + Ok(db + .search( + SearchMode::FullText, + state.filter_mode, + &state.context, + "", + OptFilters { + limit: Some(200), + authors: vec![AUTHOR_FILTER_ALL_USER.to_string()], + ..Default::default() + }, + ) + .await? + .into_iter() + .collect::<Vec<_>>()) } else { self.full_query(state, db).await } diff --git a/crates/atuin/src/command/client/search/engines/daemon.rs b/crates/atuin/src/command/client/search/engines/daemon.rs index 4fb3e2ea..8b15c180 100644 --- a/crates/atuin/src/command/client/search/engines/daemon.rs +++ b/crates/atuin/src/command/client/search/engines/daemon.rs @@ -1,9 +1,7 @@ -use std::collections::HashMap; - use async_trait::async_trait; use atuin_client::{ - database::Database, - history::History, + database::{Database, OptFilters}, + history::{AUTHOR_FILTER_ALL_USER, History}, settings::{SearchMode, Settings}, }; use atuin_daemon::client::{DaemonClientErrorKind, SearchClient, classify_error}; @@ -15,7 +13,7 @@ use eyre::Result; use tracing::{Level, debug, instrument, span}; use uuid::Uuid; -use super::{SearchEngine, SearchState, search_db}; +use super::{SearchEngine, SearchState}; use crate::command::client::daemon; pub struct Search { @@ -86,9 +84,21 @@ impl Search { state: &SearchState, db: &dyn Database, ) -> Result<Vec<History>> { - search_db(state, db, SearchMode::FullText, state.input.as_str()) + let results = db + .search( + SearchMode::FullText, + state.filter_mode, + &state.context, + state.input.as_str(), + OptFilters { + limit: Some(200), + authors: vec![AUTHOR_FILTER_ALL_USER.to_string()], + ..Default::default() + }, + ) .await - .map_or_else(|_| Ok(Vec::new()), Ok) + .map_or(Vec::new(), |r| r.into_iter().collect()); + Ok(results) } #[instrument(skip_all, level = Level::TRACE, name = "hydrate_from_db", fields(count = ids.len()))] @@ -133,7 +143,6 @@ impl SearchEngine for Search { query_id, state.filter_mode, Some(state.context.clone()), - state.authors.clone(), ) .await } @@ -154,7 +163,6 @@ impl SearchEngine for Search { query_id, state.filter_mode, Some(state.context.clone()), - state.authors.clone(), ) .await? } @@ -199,14 +207,12 @@ impl SearchEngine for Search { // // Hydrate from local database let results = self.hydrate_from_db(db, &ids).await?; - // Reorder results to match the order from the daemon (which is ranked by relevance) + // // Reorder results to match the order from the daemon (which is ranked by relevance) let ordered_results = span!(Level::TRACE, "reorder_results").in_scope(|| { - let results_by_id: HashMap<&str, &History> = - results.iter().map(|h| (h.id.0.as_str(), h)).collect(); let mut ordered_results = Vec::with_capacity(results.len()); for id in &ids { - if let Some(history) = results_by_id.get(id.as_str()) { - ordered_results.push((*history).clone()); + if let Some(history) = results.iter().find(|h| h.id.0 == *id) { + ordered_results.push(history.clone()); } } ordered_results diff --git a/crates/atuin/src/command/client/search/engines/db.rs b/crates/atuin/src/command/client/search/engines/db.rs index 29daaaa1..b15aabd8 100644 --- a/crates/atuin/src/command/client/search/engines/db.rs +++ b/crates/atuin/src/command/client/search/engines/db.rs @@ -1,9 +1,10 @@ -use super::{SearchEngine, SearchState, search_db}; +use super::{SearchEngine, SearchState}; use async_trait::async_trait; use atuin_client::{ database::Database, + database::OptFilters, database::{QueryToken, QueryTokenizer}, - history::History, + history::{AUTHOR_FILTER_ALL_USER, History}, settings::SearchMode, }; use eyre::Result; @@ -22,10 +23,22 @@ impl SearchEngine for Search { state: &SearchState, db: &mut dyn Database, ) -> Result<Vec<History>> { - search_db(state, db, self.0, state.input.as_str()) + let results = db + .search( + self.0, + state.filter_mode, + &state.context, + state.input.as_str(), + OptFilters { + limit: Some(200), + authors: vec![AUTHOR_FILTER_ALL_USER.to_string()], + ..Default::default() + }, + ) .await // ignore errors as it may be caused by incomplete regex - .map_or_else(|_| Ok(Vec::new()), Ok) + .map_or(Vec::new(), |r| r.into_iter().collect()); + Ok(results) } #[instrument(skip_all, level = Level::TRACE, name = "db_highlight")] @@ -95,62 +108,3 @@ pub fn get_highlight_indices_fulltext(command: &str, search_input: &str) -> Vec< ret.dedup(); ret } - -#[cfg(test)] -mod tests { - use super::*; - use crate::command::client::search::cursor::Cursor; - use atuin_client::{ - database::{Context, Database, Sqlite}, - history::{AUTHOR_FILTER_ALL_USER, History}, - settings::FilterMode, - }; - use time::macros::datetime; - - fn context() -> Context { - Context { - session: uuid::Uuid::now_v7().as_simple().to_string(), - cwd: "/tmp".to_string(), - hostname: "host:user".to_string(), - host_id: String::new(), - git_root: None, - } - } - - #[tokio::test] - async fn empty_query_uses_author_filters() { - let mut db = Sqlite::new(":memory:", 0.1).await.unwrap(); - - let user_history: History = History::import() - .timestamp(datetime!(2024-01-01 10:00 UTC)) - .command("git status") - .cwd("/tmp") - .author("ellie") - .build() - .into(); - let agent_history: History = History::import() - .timestamp(datetime!(2024-01-01 11:00 UTC)) - .command("git diff") - .cwd("/tmp") - .author("codex") - .build() - .into(); - - db.save_bulk(&[user_history.clone(), agent_history]) - .await - .unwrap(); - - let mut engine = Search(SearchMode::Fuzzy); - let state = SearchState { - input: Cursor::from(String::new()), - filter_mode: FilterMode::Global, - context: context(), - custom_context: None, - authors: vec![AUTHOR_FILTER_ALL_USER.to_string()], - }; - - let results = engine.query(&state, &mut db).await.unwrap(); - - assert_eq!(results, vec![user_history]); - } -} diff --git a/crates/atuin/src/command/client/search/engines/skim.rs b/crates/atuin/src/command/client/search/engines/skim.rs index 4075f148..1005b667 100644 --- a/crates/atuin/src/command/client/search/engines/skim.rs +++ b/crates/atuin/src/command/client/search/engines/skim.rs @@ -1,9 +1,9 @@ -use std::{collections::HashMap, path::Path}; +use std::path::Path; use async_trait::async_trait; use atuin_client::{ database::Database, - history::{History, author_matches_filters}, + history::{History, is_known_agent}, settings::FilterMode, }; use eyre::Result; @@ -57,23 +57,7 @@ impl SearchEngine for Search { #[instrument(skip_all, level = Level::TRACE, name = "load_all_history")] async fn load_all_history(db: &dyn Database) -> Vec<(History, i32)> { - let histories = db - .query_history("SELECT * FROM history WHERE deleted_at IS NULL ORDER BY timestamp DESC") - .await - .unwrap_or_default(); - - let mut counts = HashMap::new(); - for history in &histories { - *counts.entry(history.command.clone()).or_insert(0) += 1; - } - - histories - .into_iter() - .map(|history| { - let count = counts.get(&history.command).copied().unwrap_or(1); - (history, count) - }) - .collect() + db.all_with_count().await.unwrap() } #[allow(clippy::too_many_lines)] @@ -92,7 +76,7 @@ async fn fuzzy_search( if i % 256 == 0 { yield_now().await; } - if !author_matches_filters(&history.author, &state.authors) { + if is_known_agent(&history.author) { continue; } let context = &state.context; @@ -243,62 +227,3 @@ fn path_dist(a: &Path, b: &Path) -> usize { b.len() - a.len() + dist } - -#[cfg(test)] -mod tests { - use super::*; - use crate::command::client::search::cursor::Cursor; - use atuin_client::{ - database::{Context, Database, Sqlite}, - history::{AUTHOR_FILTER_ALL_USER, History}, - settings::FilterMode, - }; - use time::macros::datetime; - - fn context() -> Context { - Context { - session: uuid::Uuid::now_v7().as_simple().to_string(), - cwd: "/tmp".to_string(), - hostname: "host:user".to_string(), - host_id: String::new(), - git_root: None, - } - } - - #[tokio::test] - async fn skim_search_uses_author_filters() { - let mut db = Sqlite::new(":memory:", 0.1).await.unwrap(); - - let user_history: History = History::import() - .timestamp(datetime!(2024-01-01 10:00 UTC)) - .command("git status") - .cwd("/tmp") - .author("ellie") - .build() - .into(); - let agent_history: History = History::import() - .timestamp(datetime!(2024-01-01 11:00 UTC)) - .command("git stash") - .cwd("/tmp") - .author("codex") - .build() - .into(); - - db.save_bulk(&[user_history.clone(), agent_history]) - .await - .unwrap(); - - let mut engine = Search::new(); - let state = SearchState { - input: Cursor::from("git st".to_owned()), - filter_mode: FilterMode::Global, - context: context(), - custom_context: None, - authors: vec![AUTHOR_FILTER_ALL_USER.to_string()], - }; - - let results = engine.query(&state, &mut db).await.unwrap(); - - assert_eq!(results, vec![user_history]); - } -} diff --git a/crates/atuin/src/command/client/search/interactive.rs b/crates/atuin/src/command/client/search/interactive.rs index f572ed7d..ee38ddaa 100644 --- a/crates/atuin/src/command/client/search/interactive.rs +++ b/crates/atuin/src/command/client/search/interactive.rs @@ -1600,7 +1600,6 @@ fn compute_popup_placement( )] pub async fn history( query: &[String], - authors: Vec<String>, settings: &Settings, mut db: impl Database, history_store: &HistoryStore, @@ -1769,7 +1768,6 @@ pub async fn history( filter_mode: default_filter_mode, context: initial_context.clone(), custom_context: None, - authors, }, engine: engines::engine(search_mode, settings), results_len: 0, @@ -2261,7 +2259,6 @@ mod tests { git_root: None, }, custom_context: None, - authors: vec![], }, engine: engines::engine(SearchMode::Fuzzy, &settings), now: Box::new(OffsetDateTime::now_utc), @@ -2317,7 +2314,6 @@ mod tests { git_root: None, }, custom_context: None, - authors: vec![], }, engine: engines::engine(SearchMode::Fuzzy, &settings), now: Box::new(OffsetDateTime::now_utc), @@ -2437,7 +2433,6 @@ mod tests { git_root: None, }, custom_context: None, - authors: vec![], }, engine: engines::engine(SearchMode::Fuzzy, &settings), now: Box::new(OffsetDateTime::now_utc), @@ -2497,7 +2492,6 @@ mod tests { git_root: None, }, custom_context: None, - authors: vec![], }, engine: engines::engine(SearchMode::Fuzzy, &settings), now: Box::new(OffsetDateTime::now_utc), @@ -2553,7 +2547,6 @@ mod tests { git_root: None, }, custom_context: None, - authors: vec![], }, engine: engines::engine(SearchMode::Fuzzy, &settings), now: Box::new(OffsetDateTime::now_utc), @@ -2605,7 +2598,6 @@ mod tests { git_root: None, }, custom_context: None, - authors: vec![], }, engine: engines::engine(SearchMode::Fuzzy, &settings), now: Box::new(OffsetDateTime::now_utc), @@ -2666,7 +2658,6 @@ mod tests { git_root: None, }, custom_context: None, - authors: vec![], }, engine: engines::engine(SearchMode::Fuzzy, &settings), now: Box::new(OffsetDateTime::now_utc), @@ -2728,7 +2719,6 @@ mod tests { git_root: None, }, custom_context: None, - authors: vec![], }, engine: engines::engine(SearchMode::Fuzzy, &settings), now: Box::new(OffsetDateTime::now_utc), @@ -3108,7 +3098,6 @@ mod tests { git_root: None, }, custom_context: None, - authors: vec![], }, engine: engines::engine(SearchMode::Fuzzy, &settings), now: Box::new(OffsetDateTime::now_utc), |
