diff --git a/Cargo.lock b/Cargo.lock index 720ab6f..bd0b243 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -435,6 +435,15 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_complete" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c9f1dde76b736e3681f28cec9d5a61299cbaae0fce80a68e43724ad56031eb" +dependencies = [ + "clap", +] + [[package]] name = "clap_derive" version = "4.6.0" @@ -1649,6 +1658,7 @@ dependencies = [ "base64", "chrono", "clap", + "clap_complete", "comfy-table", "config", "ctor", diff --git a/Cargo.toml b/Cargo.toml index df07211..4147de6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ thiserror = "2.0" base64 = "0.22" chrono = { version = "0.4", features = ["serde"] } clap = { version = "4.6", features = ["derive", "env"] } +clap_complete = "4" config = "0.15" ctor = "0.2" directories = "6.0" diff --git a/README.md b/README.md index 26ee293..857891b 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,24 @@ source $KEEP_BASH_PROFILE The modulefile prepends `keep/bin` to `PATH` and sets `KEEP_BASH_PROFILE` pointing to `profile.bash`. +### Shell Completion + +Tab completion is available for `bash`, `zsh`, `fish`, `elvish`, and `powershell`. + +**Bash** — add to `~/.bashrc`: + +```sh +. <(keep --generate-completion bash) +``` + +**Zsh** — add to `~/.zshrc`: + +```sh +. <(keep --generate-completion zsh) +``` + +**With `profile.bash`**: Completions for `keep`, `@` (save), and `@@` (get) are loaded automatically when sourcing `profile.bash`. + ### Build with Server/Client Features ```sh diff --git a/profile.bash b/profile.bash index 44c9b1a..bffe126 100755 --- a/profile.bash +++ b/profile.bash @@ -40,4 +40,22 @@ function @@ { keep --get "$@" } +# Shell completions +. <(command keep --generate-completion bash) + +___keep_save_completion() { + COMP_WORDS=(keep --save "${COMP_WORDS[@]:1}") + COMP_CWORD=$((COMP_CWORD + 1)) + _keep +} + +___keep_get_completion() { + COMP_WORDS=(keep --get "${COMP_WORDS[@]:1}") + COMP_CWORD=$((COMP_CWORD + 1)) + _keep +} + +complete -F ___keep_save_completion @ +complete -F ___keep_get_completion @@ + __keep_preexec_init diff --git a/src/args.rs b/src/args.rs index 08a9e53..7f9f58c 100644 --- a/src/args.rs +++ b/src/args.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use std::str::FromStr; use clap::*; +use clap_complete::Shell; /// Main struct for command-line arguments, parsed via Clap. #[derive(Parser, Debug, Clone)] @@ -68,6 +69,10 @@ pub struct ModeArgs { #[arg(help("Generate default configuration and output to stdout"))] pub generate_config: bool, + #[arg(help_heading("Mode Options"), long, conflicts_with_all(["save", "get", "diff", "list", "delete", "info", "status", "server", "generate_config"]))] + #[arg(help("Generate shell completion script (bash, zsh, fish, elvish, powershell)"))] + pub generate_completion: Option, + #[arg(help_heading("Server Options"), long, env("KEEP_SERVER_ADDRESS"))] #[arg(help("Server address to bind to"))] pub server_address: Option, diff --git a/src/main.rs b/src/main.rs index e326cb5..a17467f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,6 +28,12 @@ fn main() -> Result<(), Error> { cmd.error(ErrorKind::ValueValidation, e).exit(); } + // Handle --generate-completion early (prints to stdout and exits) + if let Some(shell) = args.mode.generate_completion { + clap_complete::generate(shell, &mut Args::command(), "keep", &mut std::io::stdout()); + std::process::exit(0); + } + let start = Instant::now(); let mut builder = env_logger::Builder::new(); let show_module = args.options.verbose >= 2;