diff options
| author | Ellie Huxtable <ellie@atuin.sh> | 2026-04-13 19:53:44 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-13 19:53:44 +0100 |
| commit | b28e2739a0f1b7ef11d878163c5599c3442eacf6 (patch) | |
| tree | 04de8cb20585836ad989bd8421143963eeef3ba9 /crates | |
| parent | feat: track coding agent shell usage (#3388) (diff) | |
| download | atuin-b28e2739a0f1b7ef11d878163c5599c3442eacf6.zip | |
feat: remove agent search from tui (#3397)
This is essentially not a useful place to search agent history
Right now, using the CLI is the best way to explore this. We are looking
into building another TUI for searching records more widely and in more
detail
## Checks
- [ ] I am happy for maintainers to push small adjustments to this PR,
to speed up the review cycle
- [ ] I have checked that there are no existing pull requests for the
same thing
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), |
