aboutsummaryrefslogtreecommitdiffstats
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/atuin-client/src/settings.rs14
-rw-r--r--crates/atuin-daemon/proto/search.proto1
-rw-r--r--crates/atuin-daemon/src/client.rs2
-rw-r--r--crates/atuin-daemon/src/components/search.rs9
-rw-r--r--crates/atuin-daemon/src/search/index.rs292
-rw-r--r--crates/atuin/src/command/client/search.rs29
-rw-r--r--crates/atuin/src/command/client/search/engines.rs50
-rw-r--r--crates/atuin/src/command/client/search/engines/daemon.rs34
-rw-r--r--crates/atuin/src/command/client/search/engines/db.rs80
-rw-r--r--crates/atuin/src/command/client/search/engines/skim.rs83
-rw-r--r--crates/atuin/src/command/client/search/interactive.rs11
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),