diff options
author | Ellie Huxtable <ellie@elliehuxtable.com> | 2024-02-15 19:07:08 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-15 19:07:08 +0000 |
commit | 20f329646894e11e47e7e516b076fa23976c0d5a (patch) | |
tree | fc0add6e23c4a4a8fd4ae85520aafc5576848590 | |
parent | f8d01eef998c86c52514896539c13dbfe1837e55 (diff) |
feat: support syncing aliases (#1721)
* feat: support syncing aliases
This is definitely not yet finished, but works for zsh right now.
TODO:
1. Support other shells
2. Cache the alias generation, so we don't have to do a bunch of work at
shell init time
* correct imports
* fix clippy errors
* fix tests
* add the other shells
* support xonsh
* add delete
* update rust, then make clippy happy once more
* omfg fmt too
-rw-r--r-- | Cargo.lock | 14 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | atuin-config/Cargo.toml | 23 | ||||
-rw-r--r-- | atuin-config/src/lib.rs | 2 | ||||
-rw-r--r-- | atuin-config/src/shell.rs | 10 | ||||
-rw-r--r-- | atuin-config/src/shell/bash.rs | 12 | ||||
-rw-r--r-- | atuin-config/src/shell/fish.rs | 12 | ||||
-rw-r--r-- | atuin-config/src/shell/xonsh.rs | 12 | ||||
-rw-r--r-- | atuin-config/src/shell/zsh.rs | 12 | ||||
-rw-r--r-- | atuin-config/src/store.rs | 310 | ||||
-rw-r--r-- | atuin/Cargo.toml | 1 | ||||
-rw-r--r-- | atuin/src/command/client.rs | 14 | ||||
-rw-r--r-- | atuin/src/command/client/config.rs | 22 | ||||
-rw-r--r-- | atuin/src/command/client/config/alias.rs | 42 | ||||
-rw-r--r-- | atuin/src/command/client/default_config.rs | 5 | ||||
-rw-r--r-- | atuin/src/command/client/init.rs | 112 | ||||
-rw-r--r-- | atuin/src/command/client/init/bash.rs | 23 | ||||
-rw-r--r-- | atuin/src/command/client/init/fish.rs | 42 | ||||
-rw-r--r-- | atuin/src/command/client/init/xonsh.rs | 28 | ||||
-rw-r--r-- | atuin/src/command/client/init/zsh.rs | 36 | ||||
-rw-r--r-- | atuin/src/command/init.rs | 172 | ||||
-rw-r--r-- | atuin/src/command/mod.rs | 9 |
22 files changed, 730 insertions, 186 deletions
@@ -185,6 +185,7 @@ dependencies = [ "async-trait", "atuin-client", "atuin-common", + "atuin-config", "atuin-server", "atuin-server-postgres", "base64 0.21.7", @@ -287,6 +288,19 @@ dependencies = [ ] [[package]] +name = "atuin-config" +version = "0.1.0" +dependencies = [ + "atuin-client", + "atuin-common", + "crypto_secretbox", + "eyre", + "rand", + "rmp", + "tokio", +] + +[[package]] name = "atuin-server" version = "18.0.1" dependencies = [ @@ -5,7 +5,8 @@ members = [ "atuin-server", "atuin-server-postgres", "atuin-server-database", - "atuin-common", + "atuin-common", + "atuin-config", ] resolver = "2" diff --git a/atuin-config/Cargo.toml b/atuin-config/Cargo.toml new file mode 100644 index 00000000..b6fffbf4 --- /dev/null +++ b/atuin-config/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "atuin-config" +edition = "2021" +version = "0.1.0" # intentionally not the same as the rest + +authors.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +readme.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +atuin-common = { path = "../atuin-common", version = "18.0.1" } +atuin-client = { path = "../atuin-client", version = "18.0.1" } + +eyre = { workspace = true } +tokio = { workspace = true } +rmp = { version = "0.8.11" } +rand = { workspace = true } +crypto_secretbox = "0.1.1" diff --git a/atuin-config/src/lib.rs b/atuin-config/src/lib.rs new file mode 100644 index 00000000..74daf8ef --- /dev/null +++ b/atuin-config/src/lib.rs @@ -0,0 +1,2 @@ +pub mod shell; +pub mod store; diff --git a/atuin-config/src/shell.rs b/atuin-config/src/shell.rs new file mode 100644 index 00000000..a69a2d6b --- /dev/null +++ b/atuin-config/src/shell.rs @@ -0,0 +1,10 @@ +pub mod bash; +pub mod fish; +pub mod xonsh; +pub mod zsh; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Alias { + pub name: String, + pub value: String, +} diff --git a/atuin-config/src/shell/bash.rs b/atuin-config/src/shell/bash.rs new file mode 100644 index 00000000..c5bd87b2 --- /dev/null +++ b/atuin-config/src/shell/bash.rs @@ -0,0 +1,12 @@ +use super::Alias; + +// Configuration for bash +pub fn build(aliases: &[Alias]) -> String { + let mut config = String::new(); + + for alias in aliases { + config.push_str(&format!("alias {}='{}'\n", alias.name, alias.value)); + } + + config +} diff --git a/atuin-config/src/shell/fish.rs b/atuin-config/src/shell/fish.rs new file mode 100644 index 00000000..c6277f34 --- /dev/null +++ b/atuin-config/src/shell/fish.rs @@ -0,0 +1,12 @@ +use super::Alias; + +// Configuration for fish +pub fn build(aliases: &[Alias]) -> String { + let mut config = String::new(); + + for alias in aliases { + config.push_str(&format!("alias {}='{}'\n", alias.name, alias.value)); + } + + config +} diff --git a/atuin-config/src/shell/xonsh.rs b/atuin-config/src/shell/xonsh.rs new file mode 100644 index 00000000..8b61ff4c --- /dev/null +++ b/atuin-config/src/shell/xonsh.rs @@ -0,0 +1,12 @@ +use super::Alias; + +// Configuration for xonsh +pub fn build(aliases: &[Alias]) -> String { + let mut config = String::new(); + + for alias in aliases { + config.push_str(&format!("aliases['{}'] ='{}'\n", alias.name, alias.value)); + } + + config +} diff --git a/atuin-config/src/shell/zsh.rs b/atuin-config/src/shell/zsh.rs new file mode 100644 index 00000000..6f81ed55 --- /dev/null +++ b/atuin-config/src/shell/zsh.rs @@ -0,0 +1,12 @@ +use super::Alias; + +// Configuration for zsh +pub fn build(aliases: &[Alias]) -> String { + let mut config = String::new(); + + for alias in aliases { + config.push_str(&format!("alias {}='{}'\n", alias.name, alias.value)); + } + + config +} diff --git a/atuin-config/src/store.rs b/atuin-config/src/store.rs new file mode 100644 index 00000000..96e0fb32 --- /dev/null +++ b/atuin-config/src/store.rs @@ -0,0 +1,310 @@ +use std::collections::BTreeMap; + +use atuin_client::record::sqlite_store::SqliteStore; +// Sync aliases +// This will be noticeable similar to the kv store, though I expect the two shall diverge +// While we will support a range of shell config, I'd rather have a larger number of small records +// + stores, rather than one mega config store. +use atuin_common::record::{DecryptedData, Host, HostId}; +use eyre::{bail, ensure, eyre, Result}; + +use atuin_client::record::encryption::PASETO_V4; +use atuin_client::record::store::Store; + +use crate::shell::Alias; + +const CONFIG_SHELL_ALIAS_VERSION: &str = "v0"; +const CONFIG_SHELL_ALIAS_TAG: &str = "config-shell-alias"; +const CONFIG_SHELL_ALIAS_FIELD_MAX_LEN: usize = 20000; // 20kb max total len, way more than should be needed. + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum AliasRecord { + Create(Alias), // create a full record + Delete(String), // delete by name +} + +impl AliasRecord { + pub fn serialize(&self) -> Result<DecryptedData> { + use rmp::encode; + + let mut output = vec![]; + + match self { + AliasRecord::Create(alias) => { + encode::write_u8(&mut output, 0)?; // create + encode::write_array_len(&mut output, 2)?; // 2 fields + + encode::write_str(&mut output, alias.name.as_str())?; + encode::write_str(&mut output, alias.value.as_str())?; + } + AliasRecord::Delete(name) => { + encode::write_u8(&mut output, 1)?; // delete + encode::write_array_len(&mut output, 1)?; // 1 field + + encode::write_str(&mut output, name.as_str())?; + } + } + + Ok(DecryptedData(output)) + } + + pub fn deserialize(data: &DecryptedData, version: &str) -> Result<Self> { + use rmp::decode; + + fn error_report<E: std::fmt::Debug>(err: E) -> eyre::Report { + eyre!("{err:?}") + } + + match version { + CONFIG_SHELL_ALIAS_VERSION => { + let mut bytes = decode::Bytes::new(&data.0); + + let record_type = decode::read_u8(&mut bytes).map_err(error_report)?; + + match record_type { + // create + 0 => { + let nfields = decode::read_array_len(&mut bytes).map_err(error_report)?; + ensure!( + nfields == 2, + "too many entries in v0 shell alias create record" + ); + + let bytes = bytes.remaining_slice(); + + let (key, bytes) = + decode::read_str_from_slice(bytes).map_err(error_report)?; + let (value, bytes) = + decode::read_str_from_slice(bytes).map_err(error_report)?; + + if !bytes.is_empty() { + bail!("trailing bytes in encoded shell alias record. malformed") + } + + Ok(AliasRecord::Create(Alias { + name: key.to_owned(), + value: value.to_owned(), + })) + } + + // delete + 1 => { + let nfields = decode::read_array_len(&mut bytes).map_err(error_report)?; + ensure!( + nfields == 1, + "too many entries in v0 shell alias delete record" + ); + + let bytes = bytes.remaining_slice(); + + let (key, bytes) = + decode::read_str_from_slice(bytes).map_err(error_report)?; + + if !bytes.is_empty() { + bail!("trailing bytes in encoded shell alias record. malformed") + } + + Ok(AliasRecord::Delete(key.to_owned())) + } + + n => { + bail!("unknown AliasRecord type {n}") + } + } + } + _ => { + bail!("unknown version {version:?}") + } + } + } +} + +#[derive(Debug, Clone)] +pub struct AliasStore { + pub store: SqliteStore, + pub host_id: HostId, + pub encryption_key: [u8; 32], +} + +impl AliasStore { + // will want to init the actual kv store when that is done + pub fn new(store: SqliteStore, host_id: HostId, encryption_key: [u8; 32]) -> AliasStore { + AliasStore { + store, + host_id, + encryption_key, + } + } + + pub async fn set(&self, name: &str, value: &str) -> Result<()> { + if name.len() + value.len() > CONFIG_SHELL_ALIAS_FIELD_MAX_LEN { + return Err(eyre!( + "alias record too large: max len {} bytes", + CONFIG_SHELL_ALIAS_FIELD_MAX_LEN + )); + } + + let record = AliasRecord::Create(Alias { + name: name.to_string(), + value: value.to_string(), + }); + + let bytes = record.serialize()?; + + let idx = self + .store + .last(self.host_id, CONFIG_SHELL_ALIAS_TAG) + .await? + .map_or(0, |entry| entry.idx + 1); + + let record = atuin_common::record::Record::builder() + .host(Host::new(self.host_id)) + .version(CONFIG_SHELL_ALIAS_VERSION.to_string()) + .tag(CONFIG_SHELL_ALIAS_TAG.to_string()) + .idx(idx) + .data(bytes) + .build(); + + self.store + .push(&record.encrypt::<PASETO_V4>(&self.encryption_key)) + .await?; + + Ok(()) + } + + pub async fn delete(&self, name: &str) -> Result<()> { + if name.len() > CONFIG_SHELL_ALIAS_FIELD_MAX_LEN { + return Err(eyre!( + "alias record too large: max len {} bytes", + CONFIG_SHELL_ALIAS_FIELD_MAX_LEN + )); + } + + let record = AliasRecord::Delete(name.to_string()); + + let bytes = record.serialize()?; + + let idx = self + .store + .last(self.host_id, CONFIG_SHELL_ALIAS_TAG) + .await? + .map_or(0, |entry| entry.idx + 1); + + let record = atuin_common::record::Record::builder() + .host(Host::new(self.host_id)) + .version(CONFIG_SHELL_ALIAS_VERSION.to_string()) + .tag(CONFIG_SHELL_ALIAS_TAG.to_string()) + .idx(idx) + .data(bytes) + .build(); + + self.store + .push(&record.encrypt::<PASETO_V4>(&self.encryption_key)) + .await?; + + Ok(()) + } + + pub async fn aliases(&self) -> Result<Vec<Alias>> { + let mut build = BTreeMap::new(); + + // this is sorted, oldest to newest + let tagged = self.store.all_tagged(CONFIG_SHELL_ALIAS_TAG).await?; + + for record in tagged { + let version = record.version.clone(); + + let decrypted = match version.as_str() { + CONFIG_SHELL_ALIAS_VERSION => record.decrypt::<PASETO_V4>(&self.encryption_key)?, + version => bail!("unknown version {version:?}"), + }; + + let ar = AliasRecord::deserialize(&decrypted.data, version.as_str())?; + + match ar { + AliasRecord::Create(a) => { + build.insert(a.name.clone(), a); + } + AliasRecord::Delete(d) => { + build.remove(&d); + } + } + } + + Ok(build.into_values().collect()) + } +} + +#[cfg(test)] +pub(crate) fn test_sqlite_store_timeout() -> f64 { + std::env::var("ATUIN_TEST_SQLITE_STORE_TIMEOUT") + .ok() + .and_then(|x| x.parse().ok()) + .unwrap_or(0.1) +} + +#[cfg(test)] +mod tests { + use rand::rngs::OsRng; + + use atuin_client::record::sqlite_store::SqliteStore; + + use crate::shell::Alias; + + use super::{test_sqlite_store_timeout, AliasRecord, AliasStore, CONFIG_SHELL_ALIAS_VERSION}; + use crypto_secretbox::{KeyInit, XSalsa20Poly1305}; + + #[test] + fn encode_decode() { + let record = Alias { + name: "k".to_owned(), + value: "kubectl".to_owned(), + }; + let record = AliasRecord::Create(record); + + let snapshot = [204, 0, 146, 161, 107, 167, 107, 117, 98, 101, 99, 116, 108]; + + let encoded = record.serialize().unwrap(); + let decoded = AliasRecord::deserialize(&encoded, CONFIG_SHELL_ALIAS_VERSION).unwrap(); + + assert_eq!(encoded.0, &snapshot); + assert_eq!(decoded, record); + } + + #[tokio::test] + async fn build_aliases() { + let store = SqliteStore::new(":memory:", test_sqlite_store_timeout()) + .await + .unwrap(); + let key: [u8; 32] = XSalsa20Poly1305::generate_key(&mut OsRng).into(); + let host_id = atuin_common::record::HostId(atuin_common::utils::uuid_v7()); + + let alias = AliasStore::new(store, host_id, key); + + alias.set("k", "kubectl").await.unwrap(); + + alias.set("gp", "git push").await.unwrap(); + + let mut aliases = alias.aliases().await.unwrap(); + + aliases.sort_by_key(|a| a.name.clone()); + + assert_eq!(aliases.len(), 2); + + assert_eq!( + aliases[0], + Alias { + name: String::from("gp"), + value: String::from("git push") + } + ); + + assert_eq!( + aliases[1], + Alias { + name: String::from("k"), + value: String::from("kubectl") + } + ); + } +} diff --git a/atuin/Cargo.toml b/atuin/Cargo.toml index 4941026e..694daa80 100644 --- a/atuin/Cargo.toml +++ b/atuin/Cargo.toml @@ -45,6 +45,7 @@ atuin-server-postgres = { path = "../atuin-server-postgres", version = "18.0.1", atuin-server = { path = "../atuin-server", version = "18.0.1", optional = true } atuin-client = { path = "../atuin-client", version = "18.0.1", optional = true, default-features = false } atuin-common = { path = "../atuin-common", version = "18.0.1" } +atuin-config = { path = "../atuin-config", version = "0.1.0" } log = { workspace = true } env_logger = "0.10.0" diff --git a/atuin/src/command/client.rs b/atuin/src/command/client.rs index 5b87d3ba..b74c9bc7 100644 --- a/atuin/src/command/client.rs +++ b/atuin/src/command/client.rs @@ -13,8 +13,10 @@ mod sync; mod account; mod config; +mod default_config; mod history; mod import; +mod init; mod kv; mod search; mod stats; @@ -50,6 +52,12 @@ pub enum Cmd { #[command(subcommand)] Store(store::Cmd), + #[command(subcommand)] + Config(config::Cmd), + + #[command()] + Init(init::Cmd), + /// Print example configuration #[command()] DefaultConfig, @@ -101,8 +109,12 @@ impl Cmd { Self::Store(store) => store.run(&settings, &db, sqlite_store).await, + Self::Config(config) => config.run(&settings, sqlite_store).await, + + Self::Init(init) => init.run(&settings).await, + Self::DefaultConfig => { - config::run(); + default_config::run(); Ok(()) } } diff --git a/atuin/src/command/client/config.rs b/atuin/src/command/client/config.rs index f51e45c2..a3967719 100644 --- a/atuin/src/command/client/config.rs +++ b/atuin/src/command/client/config.rs @@ -1,5 +1,21 @@ -use atuin_client::settings::Settings; +use clap::Subcommand; +use eyre::Result; -pub fn run() { - println!("{}", Settings::example_config()); +use atuin_client::{record::sqlite_store::SqliteStore, settings::Settings}; + +mod alias; + +#[derive(Subcommand, Debug)] +#[command(infer_subcommands = true)] +pub enum Cmd { + #[command(subcommand)] + Alias(alias::Cmd), +} + +impl Cmd { + pub async fn run(self, settings: &Settings, store: SqliteStore) -> Result<()> { + match self { + Self::Alias(cmd) => cmd.run(settings, store).await, + } + } } diff --git a/atuin/src/command/client/config/alias.rs b/atuin/src/command/client/config/alias.rs new file mode 100644 index 00000000..9d34d84a --- /dev/null +++ b/atuin/src/command/client/config/alias.rs @@ -0,0 +1,42 @@ +use clap::Subcommand; +use eyre::{Context, Result}; + +use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings}; + +use atuin_config::store::AliasStore; + +#[derive(Subcommand, Debug)] +#[command(infer_subcommands = true)] +pub enum Cmd { + Set { name: String, value: String }, + Delete { name: String }, +} + +impl Cmd { + async fn set(&self, store: AliasStore, name: String, value: String) -> Result<()> { + store.set(&name, &value).await?; + + Ok(()) + } + + async fn delete(&self, store: AliasStore, name: String) -> Result<()> { + store.delete(&name).await?; + + Ok(()) + } + + pub async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> { + let encryption_key: [u8; 32] = encryption::load_key(settings) + .context("could not load encryption key")? + .into(); + let host_id = Settings::host_id().expect("failed to get host_id"); + + let alias_store = AliasStore::new(store, host_id, encryption_key); + + match self { + Self::Set { name, value } => self.set(alias_store, name.clone(), value.clone()).await, + + Self::Delete { name } => self.delete(alias_store, name.clone()).await, + } + } +} diff --git a/atuin/src/command/client/default_config.rs b/atuin/src/command/client/default_config.rs new file mode 100644 index 00000000..f51e45c2 --- /dev/null +++ b/atuin/src/command/client/default_config.rs @@ -0,0 +1,5 @@ +use atuin_client::settings::Settings; + +pub fn run() { + println!("{}", Settings::example_config()); +} diff --git a/atuin/src/command/client/init.rs b/atuin/src/command/client/init.rs new file mode 100644 index 00000000..9bff3fcc --- /dev/null +++ b/atuin/src/command/client/init.rs @@ -0,0 +1,112 @@ +use std::path::PathBuf; + +use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings}; +use atuin_config::store::AliasStore; +use clap::{Parser, ValueEnum}; +use eyre::{Result, WrapErr}; + +mod bash; +mod fish; +mod xonsh; +mod zsh; + +#[derive(Parser, Debug)] +pub struct Cmd { + shell: Shell, + + /// Disable the binding of CTRL-R to atuin + #[clap(long)] + disable_ctrl_r: bool, + + /// Disable the binding of the Up Arrow key to atuin + #[clap(long)] + disable_up_arrow: bool, +} + +#[derive(Clone, Copy, ValueEnum, Debug)] +pub enum Shell { + /// Zsh setup + Zsh, + /// Bash setup + Bash, + /// Fish setup + Fish, + /// Nu setup + Nu, + /// Xonsh setup + Xonsh, +} + +impl Cmd { + fn init_nu(&self) { + let full = include_str!("../../shell/atuin.nu"); + println!("{full}"); + + if std::env::var("ATUIN_NOBIND").is_err() { + const BIND_CTRL_R: &str = r"$env.config = ( + $env.config | upsert keybindings ( + $env.config.keybindings + | append { + name: atuin + modifier: control + keycode: char_r + mode: [emacs, vi_normal, vi_insert] + event: { send: executehostcommand cmd: (_atuin_search_cmd) } + } + ) +)"; + const BIND_UP_ARROW: &str = r" +# The up arrow keybinding has surprising behavior in Nu, and is disabled by default. +# See https://github.com/atuinsh/atuin/issues/1025 for details +# $env.config = ( +# $env.config | upsert keybindings ( +# $env.config.keybindings +# | append { +# name: atuin +# modifier: none +# keycode: up +# mode: [emacs, vi_normal, vi_insert] +# event: { send: executehostcommand cmd: (_atuin_search_cmd '--shell-up-key-binding') } +# } +# ) +# ) +"; + if !self.disable_ctrl_r { + println!("{BIND_CTRL_R}"); + } + if !self.disable_up_arrow { + println!("{BIND_UP_ARROW}"); + } + } + } + + pub async fn run(self, settings: &Settings) -> Result<()> { + let record_store_path = PathBuf::from(settings.record_store_path.as_str()); + let sqlite_store = SqliteStore::new(record_store_path, settings.local_timeout).await?; + + let encryption_key: [u8; 32] = encryption::load_key(settings) + .context("could not load encryption key")? + .into(); + let host_id = Settings::host_id().expect("failed to get host_id"); + + let alias_store = AliasStore::new(sqlite_store, host_id, encryption_key); + + match self.shell { + Shell::Zsh => { + zsh::init(alias_store, self.disable_up_arrow, self.disable_ctrl_r).await?; + } + Shell::Bash => { + bash::init(alias_store, self.disable_up_arrow, self.disable_ctrl_r).await?; + } + Shell::Fish => { + fish::init(alias_store, self.disable_up_arrow, self.disable_ctrl_r).await?; + } + Shell::Nu => self.init_nu(), + Shell::Xonsh => { + xonsh::init(alias_store, self.disable_up_arrow, self.disable_ctrl_r).await?; + } + } + + Ok(()) + } +} diff --git a/atuin/src/command/client/init/bash.rs b/atuin/src/command/client/init/bash.rs new file mode 100644 index 00000000..ac2bfb00 --- /dev/null +++ b/atuin/src/command/client/init/bash.rs @@ -0,0 +1,23 @@ +use atuin_config::store::AliasStore; +use eyre::Result; + +pub async fn init(store: AliasStore, disable_up_arrow: bool, disable_ctrl_r: bool) -> Result<()> { + let base = include_str!("../../../shell/atuin.bash"); + + let aliases = store.aliases().await?; + + let aliases = atuin_config::shell::bash::build(&aliases[..]); + + let (bind_ctrl_r, bind_up_arrow) = if std::env::var("ATUIN_NOBIND").is_ok() { + (false, false) + } else { + (!disable_ctrl_r, !disable_up_arrow) + }; + + println!("__atuin_bind_ctrl_r={bind_ctrl_r}"); + println!("__atuin_bind_up_arrow={bind_up_arrow}"); + println!("{base}"); + println!("{aliases}"); + + Ok(()) +} diff --git a/atuin/src/command/client/init/fish.rs b/atuin/src/command/client/init/fish.rs new file mode 100644 index 00000000..00913a90 --- /dev/null +++ b/atuin/src/command/client/init/fish.rs @@ -0,0 +1,42 @@ +use atuin_config::store::AliasStore; +use eyre::Result; + +pub async fn init(store: AliasStore, disable_up_arrow: bool, disable_ctrl_r: bool) -> Result<()> { + let base = include_str!("../../../shell/atuin.zsh"); + + println!("{base}"); + + if std::env::var("ATUIN_NOBIND").is_err() { + const BIND_CTRL_R: &str = r"bind \cr _atuin_search"; + const BIND_UP_ARROW: &str = r"bind -k up _atuin_bind_up +bind \eOA _atuin_bind_up +bind \e\[A _atuin_bind_up"; + const BIND_CTRL_R_INS: &str = r"bind -M insert \cr _atuin_search"; + const BIND_UP_ARROW_INS: &str = r"bind -M insert -k up _atuin_bind_up +bind -M insert \eOA _atuin_bind_up +bind -M insert \e\[A _atuin_bind_up"; + + if !disable_ctrl_r { + println!("{BIND_CTRL_R}"); + } + if !disable_up_arrow { + println!("{BIND_UP_ARROW}"); + } + + println!("if bind -M insert > /dev/null 2>&1"); + if !disable_ctrl_r { + println!("{BIND_CTRL_R_INS}"); + } + if !disable_up_arrow { + println!("{BIND_UP_ARROW_INS}"); + } + println!("end"); + } + + let aliases = store.aliases().await?; + let aliases = atuin_config::shell::fish::build(&aliases[..]); + + println!("{aliases}"); + + Ok(()) +} diff --git a/atuin/src/command/client/init/xonsh.rs b/atuin/src/command/client/init/xonsh.rs new file mode 100644 index 00000000..4e5929ed --- /dev/null +++ b/atuin/src/command/client/init/xonsh.rs @@ -0,0 +1,28 @@ +use atuin_config::store::AliasStore; +use eyre::Result; + +pub async fn init(store: AliasStore, disable_up_arrow: bool, disable_ctrl_r: bool) -> Result<()> { + let base = include_str!("../../../shell/atuin.xsh"); + + let (bind_ctrl_r, bind_up_arrow) = if std::env::var("ATUIN_NOBIND").is_ok() { + (false, false) + } else { + (!disable_ctrl_r, !disable_up_arrow) + }; + println!( + "_ATUIN_BIND_CTRL_R={}", + if bind_ctrl_r { "True" } else { "False" } + ); + println!( + "_ATUIN_BIND_UP_ARROW={}", + if bind_up_arrow { "True" } else { "False" } + ); + println!("{base}"); + + let aliases = store.aliases().await?; + let aliases = atuin_config::shell::xonsh::build(&aliases[..]); + + println!("{aliases}"); + + Ok(()) +} diff --git a/atuin/src/command/client/init/zsh.r |