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
This commit is contained in:
Ellie Huxtable 2023-07-14 20:58:20 +01:00 committed by GitHub
parent 5d26d3f47a
commit 465faca6d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 83 additions and 13 deletions

View file

@ -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 async_trait::async_trait;
use atuin_common::utils; use atuin_common::utils;
@ -25,6 +29,7 @@ pub struct Context {
pub cwd: String, pub cwd: String,
pub hostname: String, pub hostname: String,
pub host_id: String, pub host_id: String,
pub git_root: Option<PathBuf>,
} }
#[derive(Default, Clone)] #[derive(Default, Clone)]
@ -52,11 +57,13 @@ pub fn current_context() -> Context {
); );
let cwd = utils::get_current_dir(); let cwd = utils::get_current_dir();
let host_id = Settings::host_id().expect("failed to load host ID"); let host_id = Settings::host_id().expect("failed to load host ID");
let git_root = utils::in_git_repo(cwd.as_str());
Context { Context {
session, session,
hostname, hostname,
cwd, cwd,
git_root,
host_id: host_id.0.as_simple().to_string(), 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::Host => query.and_where_eq("hostname", quote(&context.hostname)),
FilterMode::Session => query.and_where_eq("session", quote(&context.session)), FilterMode::Session => query.and_where_eq("session", quote(&context.session)),
FilterMode::Directory => query.and_where_eq("cwd", quote(&context.cwd)), FilterMode::Directory => query.and_where_eq("cwd", quote(&context.cwd)),
FilterMode::Workspace => query.and_where_like_any("cwd", context.cwd.clone()),
}; };
if unique { if unique {
@ -380,11 +388,18 @@ impl Database for Sqlite {
sql.order_desc("timestamp"); 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 { match filter {
FilterMode::Global => &mut sql, FilterMode::Global => &mut sql,
FilterMode::Host => sql.and_where_eq("hostname", quote(&context.hostname)), FilterMode::Host => sql.and_where_eq("hostname", quote(&context.hostname)),
FilterMode::Session => sql.and_where_eq("session", quote(&context.session)), FilterMode::Session => sql.and_where_eq("session", quote(&context.session)),
FilterMode::Directory => sql.and_where_eq("cwd", quote(&context.cwd)), FilterMode::Directory => sql.and_where_eq("cwd", quote(&context.cwd)),
FilterMode::Workspace => sql.and_where_like_left("cwd", git_root),
}; };
let orig_query = query; let orig_query = query;
@ -556,6 +571,7 @@ mod test {
session: "beepboopiamasession".to_string(), session: "beepboopiamasession".to_string(),
cwd: "/home/ellie".to_string(), cwd: "/home/ellie".to_string(),
host_id: "test-host".to_string(), host_id: "test-host".to_string(),
git_root: None,
}; };
let results = db let results = db
@ -765,6 +781,7 @@ mod test {
session: "beepboopiamasession".to_string(), session: "beepboopiamasession".to_string(),
cwd: "/home/ellie".to_string(), cwd: "/home/ellie".to_string(),
host_id: "test-host".to_string(), host_id: "test-host".to_string(),
git_root: None,
}; };
let mut db = Sqlite::new("sqlite::memory:").await.unwrap(); let mut db = Sqlite::new("sqlite::memory:").await.unwrap();

View file

@ -72,6 +72,9 @@ pub enum FilterMode {
#[serde(rename = "directory")] #[serde(rename = "directory")]
Directory = 3, Directory = 3,
#[serde(rename = "workspace")]
Workspace = 4,
} }
impl FilterMode { impl FilterMode {
@ -81,6 +84,7 @@ impl FilterMode {
FilterMode::Host => "HOST", FilterMode::Host => "HOST",
FilterMode::Session => "SESSION", FilterMode::Session => "SESSION",
FilterMode::Directory => "DIRECTORY", FilterMode::Directory => "DIRECTORY",
FilterMode::Workspace => "WORKSPACE",
} }
} }
} }
@ -163,6 +167,7 @@ pub struct Settings {
pub history_filter: RegexSet, pub history_filter: RegexSet,
#[serde(with = "serde_regex", default = "RegexSet::empty")] #[serde(with = "serde_regex", default = "RegexSet::empty")]
pub cwd_filter: RegexSet, pub cwd_filter: RegexSet,
pub workspaces: bool,
// This is automatically loaded when settings is created. Do not set in // This is automatically loaded when settings is created. Do not set in
// config! Keep secrets and settings apart. // config! Keep secrets and settings apart.
@ -374,6 +379,7 @@ impl Settings {
.set_default("scroll_context_lines", 1)? .set_default("scroll_context_lines", 1)?
.set_default("shell_up_key_binding", false)? .set_default("shell_up_key_binding", false)?
.set_default("session_token", "")? .set_default("session_token", "")?
.set_default("workspaces", false)?
.add_source( .add_source(
Environment::with_prefix("atuin") Environment::with_prefix("atuin")
.prefix_separator("_") .prefix_separator("_")

View file

@ -46,6 +46,31 @@ pub fn uuid_v4() -> String {
Uuid::new_v4().as_simple().to_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<PathBuf> {
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 // TODO: more reliable, more tested
// I don't want to use ProjectDirs, it puts config in awkward places on // 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. // mac. Data too. Seems to be more intended for GUI apps.

View file

@ -194,14 +194,21 @@ async fn run_non_interactive(
let context = current_context(); let context = current_context();
let opt_filter = OptFilters { let opt_filter = OptFilters {
cwd: dir, cwd: dir.clone(),
..filter_options ..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 let results = db
.search( .search(
settings.search_mode, settings.search_mode,
settings.filter_mode, filter_mode,
&context, &context,
query.join(" ").as_str(), query.join(" ").as_str(),
opt_filter, opt_filter,

View file

@ -184,15 +184,27 @@ impl State {
} }
KeyCode::Char('u') if ctrl => self.search.input.clear(), KeyCode::Char('u') if ctrl => self.search.input.clear(),
KeyCode::Char('r') if ctrl => { KeyCode::Char('r') if ctrl => {
pub static FILTER_MODES: [FilterMode; 4] = [ let filter_modes = if settings.workspaces && self.search.context.git_root.is_some()
FilterMode::Global, {
FilterMode::Host, vec![
FilterMode::Session, FilterMode::Global,
FilterMode::Directory, 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 = self.search.filter_mode as usize;
let i = (i + 1) % FILTER_MODES.len(); let i = (i + 1) % filter_modes.len();
self.search.filter_mode = FILTER_MODES[i]; self.search.filter_mode = filter_modes[i];
} }
KeyCode::Char('s') if ctrl => { KeyCode::Char('s') if ctrl => {
self.switched_search_mode = true; self.switched_search_mode = true;
@ -586,14 +598,16 @@ pub async fn history(
search_mode: settings.search_mode, search_mode: settings.search_mode,
search: SearchState { search: SearchState {
input, input,
context, filter_mode: if settings.workspaces && context.git_root.is_some() {
filter_mode: if settings.shell_up_key_binding { FilterMode::Workspace
} else if settings.shell_up_key_binding {
settings settings
.filter_mode_shell_up_key_binding .filter_mode_shell_up_key_binding
.unwrap_or(settings.filter_mode) .unwrap_or(settings.filter_mode)
} else { } else {
settings.filter_mode settings.filter_mode
}, },
context,
}, },
engine: engines::engine(settings.search_mode), engine: engines::engine(settings.search_mode),
results_len: 0, results_len: 0,

View file

@ -80,6 +80,7 @@ impl Cmd {
} else { } else {
self.period.join(" ") self.period.join(" ")
}; };
let history = if words.as_str() == "all" { let history = if words.as_str() == "all" {
db.list(FilterMode::Global, &context, None, false).await? db.list(FilterMode::Global, &context, None, false).await?
} else if words.trim() == "today" { } else if words.trim() == "today" {