Repository: atuinsh/atuin (language: rust)

Issue:
Feature request: Allow changing order of filter modes
I would like to change the order in which the filter modes are used and actually be able to drop some of them (for example, I do not care about `directory` filter mode).

This is probably related to #366 - but I am not sure whether we're talking about the very same thing here.


Discussion / hints:
If you haven't yet, you should look at #1417. I suggested exactly the feature in your PR, and I believe @ellie agreed that this is probably a good idea.
Agreed, this PR you mentioned is somewhat related to this, yes. I'll leave the decision how to move forward up to the maintainers :heart: 

Fix patch:
diff --git a/crates/atuin-client/config.toml b/crates/atuin-client/config.toml
index 4b2810e5256..388e3f858cb 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 efbdb33d4cd..10c804fa5e5 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<FilterMode>,
+}
+
 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<FilterMode>,
     pub filter_mode_shell_up_key_binding: Option<FilterMode>,
     pub search_mode_shell_up_key_binding: Option<SearchMode>,
     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<Version> {
         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::<String>)?
             .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::<bool>)?
             .set_default(
diff --git a/crates/atuin/src/command/client/history.rs b/crates/atuin/src/command/client/history.rs
index a4eea8255ea..5769b94f8a6 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 12e73458ffd..b8dfe659b0f 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 105ce147e7b..30a23cb2b0e 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 c87fff1c1fb..54249f50422 100644
--- a/crates/atuin/src/command/client/search/interactive.rs
+++ b/crates/atuin/src/command/client/search/interactive.rs
@@ -459,29 +459,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);
@@ -1087,15 +1065,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),


Test patch: