diff --git a/crates/atuin-client/config.toml b/crates/atuin-client/config.toml index 4b2810e5..388e3f85 100644 --- a/crates/atuin-client/config.toml +++ b/crates/atuin-client/config.toml @@ -40,12 +40,17 @@ ## possible values: prefix, fulltext, fuzzy, skim # search_mode = "fuzzy" -## which filter mode to use -## possible values: global, host, session, directory +## which filter mode to use by default +## possible values: "global", "host", "session", "directory", "workspace" +## consider using search.filters to customize the enablement and order of filter modes # filter_mode = "global" ## With workspace filtering enabled, Atuin will filter for commands executed -## in any directory within a git repository tree (default: false) +## in any directory within a git repository tree (default: false). +## +## To use workspace mode by default when available, set this to true and +## set filter_mode to "workspace" or leave it unspecified and +## set search.filters to include "workspace" before other filter modes. # workspaces = false ## which filter mode to use when atuin is invoked from a shell up-key binding @@ -254,3 +259,9 @@ records = true ## Whether the theme manager should output normal or extra information to help fix themes. ## Boolean, true or false. If unset, left up to the theme manager. # debug = true + +[search] +## The list of enabled filter modes, in order of priority. +## The "workspace" mode is skipped when not in a workspace or workspaces = false. +## Default filter mode can be overridden with the filter_mode setting. +# filters = [ "global", "host", "session", "workspace", "directory" ] diff --git a/crates/atuin-client/src/settings.rs b/crates/atuin-client/src/settings.rs index efbdb33d..10c804fa 100644 --- a/crates/atuin-client/src/settings.rs +++ b/crates/atuin-client/src/settings.rs @@ -370,6 +370,12 @@ pub struct Daemon { pub tcp_port: u64, } +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Search { + /// The list of enabled filter modes, in order of priority. + pub filters: Vec, +} + impl Default for Preview { fn default() -> Self { Self { @@ -400,6 +406,20 @@ impl Default for Daemon { } } +impl Default for Search { + fn default() -> Self { + Self { + filters: vec![ + FilterMode::Global, + FilterMode::Host, + FilterMode::Session, + FilterMode::Workspace, + FilterMode::Directory, + ], + } + } +} + // The preview height strategy also takes max_preview_height into account. #[derive(Clone, Debug, Deserialize, Copy, PartialEq, Eq, ValueEnum, Serialize)] pub enum PreviewStrategy { @@ -430,7 +450,7 @@ pub struct Settings { pub key_path: String, pub session_path: String, pub search_mode: SearchMode, - pub filter_mode: FilterMode, + pub filter_mode: Option, pub filter_mode_shell_up_key_binding: Option, pub search_mode_shell_up_key_binding: Option, pub shell_up_key_binding: bool, @@ -486,6 +506,9 @@ pub struct Settings { #[serde(default)] pub daemon: Daemon, + #[serde(default)] + pub search: Search, + #[serde(default)] pub theme: Theme, } @@ -688,6 +711,13 @@ impl Settings { None } + pub fn default_filter_mode(&self) -> FilterMode { + self.filter_mode + .filter(|x| self.search.filters.contains(x)) + .or(self.search.filters.first().copied()) + .unwrap_or(FilterMode::Global) + } + #[cfg(not(feature = "check-update"))] pub async fn needs_update(&self) -> Option { None @@ -715,7 +745,7 @@ impl Settings { .set_default("sync_address", "https://api.atuin.sh")? .set_default("sync_frequency", "10m")? .set_default("search_mode", "fuzzy")? - .set_default("filter_mode", "global")? + .set_default("filter_mode", None::)? .set_default("style", "compact")? .set_default("inline_height", 40)? .set_default("show_preview", true)? @@ -758,6 +788,10 @@ impl Settings { .set_default("daemon.socket_path", socket_path.to_str())? .set_default("daemon.systemd_socket", false)? .set_default("daemon.tcp_port", 8889)? + .set_default( + "search.filters", + vec!["global", "host", "session", "workspace", "directory"], + )? .set_default("theme.name", "default")? .set_default("theme.debug", None::)? .set_default( diff --git a/crates/atuin/src/command/client/history.rs b/crates/atuin/src/command/client/history.rs index a4eea825..5769b94f 100644 --- a/crates/atuin/src/command/client/history.rs +++ b/crates/atuin/src/command/client/history.rs @@ -466,7 +466,7 @@ impl Cmd { (true, true) => [Session, Directory], (true, false) => [Session, Global], (false, true) => [Global, Directory], - (false, false) => [settings.filter_mode, Global], + (false, false) => [settings.default_filter_mode(), Global], }; let history = db diff --git a/crates/atuin/src/command/client/search.rs b/crates/atuin/src/command/client/search.rs index 12e73458..b8dfe659 100644 --- a/crates/atuin/src/command/client/search.rs +++ b/crates/atuin/src/command/client/search.rs @@ -176,7 +176,7 @@ impl Cmd { settings.search_mode = self.search_mode.unwrap(); } if self.filter_mode.is_some() { - settings.filter_mode = self.filter_mode.unwrap(); + settings.filter_mode = self.filter_mode; } if self.inline_height.is_some() { settings.inline_height = self.inline_height.unwrap(); @@ -287,12 +287,7 @@ async fn run_non_interactive( ..filter_options }; - let dir = dir.unwrap_or_else(|| "/".to_string()); - let filter_mode = if settings.workspaces && utils::has_git_dir(dir.as_str()) { - FilterMode::Workspace - } else { - settings.filter_mode - }; + let filter_mode = settings.default_filter_mode(); let results = db .search( diff --git a/crates/atuin/src/command/client/search/engines.rs b/crates/atuin/src/command/client/search/engines.rs index 105ce147..30a23cb2 100644 --- a/crates/atuin/src/command/client/search/engines.rs +++ b/crates/atuin/src/command/client/search/engines.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use atuin_client::{ database::{Context, Database}, history::History, - settings::{FilterMode, SearchMode}, + settings::{FilterMode, SearchMode, Settings}, }; use eyre::Result; @@ -24,6 +24,32 @@ pub struct SearchState { pub context: Context, } +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::Workspace => settings.workspaces && self.context.git_root.is_some(), + _ => true, + } + } +} + #[async_trait] pub trait SearchEngine: Send + Sync + 'static { async fn full_query( diff --git a/crates/atuin/src/command/client/search/interactive.rs b/crates/atuin/src/command/client/search/interactive.rs index 5549cbfd..5288a0ee 100644 --- a/crates/atuin/src/command/client/search/interactive.rs +++ b/crates/atuin/src/command/client/search/interactive.rs @@ -464,29 +464,7 @@ impl State { } } KeyCode::Char('u') if ctrl => self.search.input.clear(), - KeyCode::Char('r') if ctrl => { - let filter_modes = if settings.workspaces && self.search.context.git_root.is_some() - { - vec![ - FilterMode::Global, - FilterMode::Host, - FilterMode::Session, - FilterMode::Directory, - FilterMode::Workspace, - ] - } else { - vec![ - FilterMode::Global, - FilterMode::Host, - FilterMode::Session, - FilterMode::Directory, - ] - }; - - let i = self.search.filter_mode as usize; - let i = (i + 1) % filter_modes.len(); - self.search.filter_mode = filter_modes[i]; - } + KeyCode::Char('r') if ctrl => self.search.rotate_filter_mode(settings, 1), KeyCode::Char('s') if ctrl => { self.switched_search_mode = true; self.search_mode = self.search_mode.next(settings); @@ -1092,15 +1070,12 @@ pub async fn history( tab_index: 0, search: SearchState { input, - filter_mode: if settings.workspaces && context.git_root.is_some() { - FilterMode::Workspace - } else if settings.shell_up_key_binding { - settings - .filter_mode_shell_up_key_binding - .unwrap_or(settings.filter_mode) - } else { - settings.filter_mode - }, + filter_mode: settings + .filter_mode_shell_up_key_binding + .filter(|_| settings.shell_up_key_binding) + .or_else(|| Some(settings.default_filter_mode())) + .filter(|&x| x != FilterMode::Workspace || context.git_root.is_some()) + .unwrap_or(FilterMode::Global), context, }, engine: engines::engine(search_mode),