From 465faca6d1255cb630de00e374a1675be25aa547 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Fri, 14 Jul 2023 20:58:20 +0100 Subject: [PATCH] Add workspace mode, enable if in git repo (#1053) * Add workspace mode, enable if in git repo * Fix tests * Should now be good * Page filter modes correctly if in workspace --- atuin-client/src/database.rs | 19 ++++++++++- atuin-client/src/settings.rs | 6 ++++ atuin-common/src/utils.rs | 25 ++++++++++++++ atuin/src/command/client/search.rs | 11 ++++-- .../src/command/client/search/interactive.rs | 34 +++++++++++++------ atuin/src/command/client/stats.rs | 1 + 6 files changed, 83 insertions(+), 13 deletions(-) diff --git a/atuin-client/src/database.rs b/atuin-client/src/database.rs index 218c1d6..e37eb2b 100644 --- a/atuin-client/src/database.rs +++ b/atuin-client/src/database.rs @@ -1,4 +1,8 @@ -use std::{env, path::Path, str::FromStr}; +use std::{ + env, + path::{Path, PathBuf}, + str::FromStr, +}; use async_trait::async_trait; use atuin_common::utils; @@ -25,6 +29,7 @@ pub struct Context { pub cwd: String, pub hostname: String, pub host_id: String, + pub git_root: Option, } #[derive(Default, Clone)] @@ -52,11 +57,13 @@ pub fn current_context() -> Context { ); let cwd = utils::get_current_dir(); let host_id = Settings::host_id().expect("failed to load host ID"); + let git_root = utils::in_git_repo(cwd.as_str()); Context { session, hostname, cwd, + git_root, host_id: host_id.0.as_simple().to_string(), } } @@ -261,6 +268,7 @@ impl Database for Sqlite { FilterMode::Host => query.and_where_eq("hostname", quote(&context.hostname)), FilterMode::Session => query.and_where_eq("session", quote(&context.session)), FilterMode::Directory => query.and_where_eq("cwd", quote(&context.cwd)), + FilterMode::Workspace => query.and_where_like_any("cwd", context.cwd.clone()), }; if unique { @@ -380,11 +388,18 @@ impl Database for Sqlite { sql.order_desc("timestamp"); } + let git_root = if let Some(git_root) = context.git_root.clone() { + git_root.to_str().unwrap_or("/").to_string() + } else { + context.cwd.clone() + }; + match filter { FilterMode::Global => &mut sql, FilterMode::Host => sql.and_where_eq("hostname", quote(&context.hostname)), FilterMode::Session => sql.and_where_eq("session", quote(&context.session)), FilterMode::Directory => sql.and_where_eq("cwd", quote(&context.cwd)), + FilterMode::Workspace => sql.and_where_like_left("cwd", git_root), }; let orig_query = query; @@ -556,6 +571,7 @@ mod test { session: "beepboopiamasession".to_string(), cwd: "/home/ellie".to_string(), host_id: "test-host".to_string(), + git_root: None, }; let results = db @@ -765,6 +781,7 @@ mod test { session: "beepboopiamasession".to_string(), cwd: "/home/ellie".to_string(), host_id: "test-host".to_string(), + git_root: None, }; let mut db = Sqlite::new("sqlite::memory:").await.unwrap(); diff --git a/atuin-client/src/settings.rs b/atuin-client/src/settings.rs index bb41a89..3bcb270 100644 --- a/atuin-client/src/settings.rs +++ b/atuin-client/src/settings.rs @@ -72,6 +72,9 @@ pub enum FilterMode { #[serde(rename = "directory")] Directory = 3, + + #[serde(rename = "workspace")] + Workspace = 4, } impl FilterMode { @@ -81,6 +84,7 @@ impl FilterMode { FilterMode::Host => "HOST", FilterMode::Session => "SESSION", FilterMode::Directory => "DIRECTORY", + FilterMode::Workspace => "WORKSPACE", } } } @@ -163,6 +167,7 @@ pub struct Settings { pub history_filter: RegexSet, #[serde(with = "serde_regex", default = "RegexSet::empty")] pub cwd_filter: RegexSet, + pub workspaces: bool, // This is automatically loaded when settings is created. Do not set in // config! Keep secrets and settings apart. @@ -374,6 +379,7 @@ impl Settings { .set_default("scroll_context_lines", 1)? .set_default("shell_up_key_binding", false)? .set_default("session_token", "")? + .set_default("workspaces", false)? .add_source( Environment::with_prefix("atuin") .prefix_separator("_") diff --git a/atuin-common/src/utils.rs b/atuin-common/src/utils.rs index 776a63d..d2db3ac 100644 --- a/atuin-common/src/utils.rs +++ b/atuin-common/src/utils.rs @@ -46,6 +46,31 @@ pub fn uuid_v4() -> String { Uuid::new_v4().as_simple().to_string() } +pub fn has_git_dir(path: &str) -> bool { + let mut gitdir = PathBuf::from(path); + gitdir.push(".git"); + + gitdir.exists() +} + +// detect if any parent dir has a git repo in it +// I really don't want to bring in libgit for something simple like this +// If we start to do anything more advanced, then perhaps +pub fn in_git_repo(path: &str) -> Option { + let mut gitdir = PathBuf::from(path); + + while gitdir.parent().is_some() && !has_git_dir(gitdir.to_str().unwrap()) { + gitdir.pop(); + } + + // No parent? then we hit root, finding no git + if gitdir.parent().is_some() { + return Some(gitdir); + } + + None +} + // TODO: more reliable, more tested // I don't want to use ProjectDirs, it puts config in awkward places on // mac. Data too. Seems to be more intended for GUI apps. diff --git a/atuin/src/command/client/search.rs b/atuin/src/command/client/search.rs index ff7511d..95e92fd 100644 --- a/atuin/src/command/client/search.rs +++ b/atuin/src/command/client/search.rs @@ -194,14 +194,21 @@ async fn run_non_interactive( let context = current_context(); let opt_filter = OptFilters { - cwd: dir, + cwd: dir.clone(), ..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 results = db .search( settings.search_mode, - settings.filter_mode, + filter_mode, &context, query.join(" ").as_str(), opt_filter, diff --git a/atuin/src/command/client/search/interactive.rs b/atuin/src/command/client/search/interactive.rs index eec7ac8..401dafe 100644 --- a/atuin/src/command/client/search/interactive.rs +++ b/atuin/src/command/client/search/interactive.rs @@ -184,15 +184,27 @@ impl State { } KeyCode::Char('u') if ctrl => self.search.input.clear(), KeyCode::Char('r') if ctrl => { - pub static FILTER_MODES: [FilterMode; 4] = [ - FilterMode::Global, - FilterMode::Host, - FilterMode::Session, - FilterMode::Directory, - ]; + 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]; + let i = (i + 1) % filter_modes.len(); + self.search.filter_mode = filter_modes[i]; } KeyCode::Char('s') if ctrl => { self.switched_search_mode = true; @@ -586,14 +598,16 @@ pub async fn history( search_mode: settings.search_mode, search: SearchState { input, - context, - filter_mode: if settings.shell_up_key_binding { + 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 }, + context, }, engine: engines::engine(settings.search_mode), results_len: 0, diff --git a/atuin/src/command/client/stats.rs b/atuin/src/command/client/stats.rs index 5134f22..cbb5682 100644 --- a/atuin/src/command/client/stats.rs +++ b/atuin/src/command/client/stats.rs @@ -80,6 +80,7 @@ impl Cmd { } else { self.period.join(" ") }; + let history = if words.as_str() == "all" { db.list(FilterMode::Global, &context, None, false).await? } else if words.trim() == "today" {