diff --git a/CHANGELOG.md b/CHANGELOG.md index 45693ab275c..5f39485f9e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#2971](https://github.com/martinvonz/jj/issues/2971)). This may become the default depending on feedback. +* Added completions for [Nushell](https://nushell.sh) to `jj util completion` + ### Fixed bugs * On Windows, symlinks in the repo are now materialized as regular files in the diff --git a/Cargo.lock b/Cargo.lock index 1d2c509271e..b16f0b90ba0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -398,6 +398,16 @@ dependencies = [ "clap", ] +[[package]] +name = "clap_complete_nushell" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92cd8ac7c321605310e4b3bdc047daf27c6ca1b50f6ae59b22aa0661ab45e295" +dependencies = [ + "clap", + "clap_complete", +] + [[package]] name = "clap_derive" version = "4.5.0" @@ -1603,6 +1613,7 @@ dependencies = [ "clap", "clap-markdown", "clap_complete", + "clap_complete_nushell", "clap_mangen", "config", "criterion", diff --git a/Cargo.toml b/Cargo.toml index a16579674cf..9e2589f7188 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ clap = { version = "4.5.1", features = [ "string", ] } clap_complete = "4.5.0" +clap_complete_nushell = "4.5.0" clap-markdown = "0.1.3" clap_mangen = "0.2.10" chrono = { version = "0.4.34", default-features = false, features = [ diff --git a/cli/Cargo.toml b/cli/Cargo.toml index a9c04420346..cb0e83e455d 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -38,6 +38,7 @@ chrono = { workspace = true } clap = { workspace = true } clap-markdown = { workspace = true } clap_complete = { workspace = true } +clap_complete_nushell = { workspace = true } clap_mangen = { workspace = true } config = { workspace = true } criterion = { workspace = true, optional = true } diff --git a/cli/src/commands/util.rs b/cli/src/commands/util.rs index c4d6e3426cd..d986f52559d 100644 --- a/cli/src/commands/util.rs +++ b/cli/src/commands/util.rs @@ -16,8 +16,7 @@ use std::io::Write; use std::slice; use std::time::{Duration, SystemTime}; -use clap::Subcommand; -use clap_complete::Shell; +use clap::{Command, Subcommand}; use jj_lib::repo::Repo; use tracing::instrument; @@ -40,19 +39,24 @@ pub(crate) enum UtilCommand { Apply it by running one of these: -- **bash**: `source <(jj util completion)` -- **fish**: `jj util completion --fish | source` +- **bash**: `source <(jj util completion bash)` +- **fish**: `jj util completion fish | source` +- **nushell**: + ```nu + jj util completion nushell | save "completions-jj.nu" + use "completions-jj.nu" * # Or `source "completions-jj.nu"` + ``` - **zsh**: ```shell autoload -U compinit compinit - source <(jj util completion --zsh) + source <(jj util completion zsh) ``` "#] #[derive(clap::Args, Clone, Debug)] #[command(verbatim_doc_comment)] pub(crate) struct UtilCompletionArgs { - shell: Option, + shell: Option, /// Deprecated. Use the SHELL positional argument instead. #[arg(long, hide = true)] bash: bool, @@ -91,6 +95,17 @@ pub(crate) struct UtilMarkdownHelp {} #[derive(clap::Args, Clone, Debug)] pub(crate) struct UtilConfigSchemaArgs {} +/// Available shell completions +#[derive(clap::ValueEnum, Clone, Copy, Debug, Eq, Hash, PartialEq)] +enum ShellCompletion { + Bash, + Elvish, + Fish, + Nushell, + PowerShell, + Zsh, +} + #[instrument(skip_all)] pub(crate) fn cmd_util( ui: &mut Ui, @@ -120,22 +135,21 @@ fn cmd_util_completion( )?; writeln!(ui.hint(), "Hint: Use `jj util completion {shell}` instead") }; - let mut buf = vec![]; let shell = match (args.shell, args.fish, args.zsh, args.bash) { (Some(s), false, false, false) => s, // allow `--fish` and `--zsh` for back-compat, but don't allow them to be combined (None, true, false, false) => { warn("fish")?; - Shell::Fish + ShellCompletion::Fish } (None, false, true, false) => { warn("zsh")?; - Shell::Zsh + ShellCompletion::Zsh } // default to bash for back-compat. TODO: consider making `shell` a required argument (None, false, false, _) => { warn("bash")?; - Shell::Bash + ShellCompletion::Bash } _ => { return Err(user_error( @@ -143,7 +157,8 @@ fn cmd_util_completion( )) } }; - clap_complete::generate(shell, &mut app, "jj", &mut buf); + + let buf = shell.generate(&mut app); ui.stdout_formatter().write_all(&buf)?; Ok(()) } @@ -206,3 +221,25 @@ fn cmd_util_config_schema( ui.stdout_formatter().write_all(buf)?; Ok(()) } + +impl ShellCompletion { + fn generate(&self, cmd: &mut Command) -> Vec { + use clap_complete::{generate, Shell}; + use clap_complete_nushell::Nushell; + + let mut buf = Vec::new(); + + let bin_name = "jj"; + + match self { + Self::Bash => generate(Shell::Bash, cmd, bin_name, &mut buf), + Self::Elvish => generate(Shell::Elvish, cmd, bin_name, &mut buf), + Self::Fish => generate(Shell::Fish, cmd, bin_name, &mut buf), + Self::Nushell => generate(Nushell, cmd, bin_name, &mut buf), + Self::PowerShell => generate(Shell::PowerShell, cmd, bin_name, &mut buf), + Self::Zsh => generate(Shell::Zsh, cmd, bin_name, &mut buf), + } + + buf + } +} diff --git a/cli/tests/test_util_command.rs b/cli/tests/test_util_command.rs index 251f216f17a..87f8e9a8bab 100644 --- a/cli/tests/test_util_command.rs +++ b/cli/tests/test_util_command.rs @@ -92,3 +92,21 @@ fn test_gc_operation_log() { Error: No operation ID matching "f9400b5274c6f1cfa23afbc1956349897a6975116135a59ab19d941119cc9fad93d9668b8c7d913fbd68c543dcba40a8d44135a53996a9e8ea92d4b78ef52cb6" "###); } + +#[test] +fn test_shell_completions() { + #[track_caller] + fn test(shell: &str) { + let test_env = TestEnvironment::default(); + // Use the local backend because GitBackend::gc() depends on the git CLI. + let (out, err) = test_env.jj_cmd_ok(test_env.env_root(), &["util", "completion", shell]); + // Ensures only stdout contains text + assert!(!out.is_empty()); + assert!(err.is_empty()); + } + + test("bash"); + test("fish"); + test("nushell"); + test("zsh"); +}