From c75230747af0f3182b013155913b9809701e078a Mon Sep 17 00:00:00 2001 From: "Alexis (Poliorcetics) Bourget" Date: Fri, 16 Feb 2024 21:23:41 +0100 Subject: [PATCH] completion: Add support for Nushell completions --- CHANGELOG.md | 2 ++ Cargo.lock | 15 +++++++-- Cargo.toml | 3 +- cli/Cargo.toml | 1 + cli/src/commands/util.rs | 53 +++++++++++++++++++++++++++----- cli/tests/cli-reference@.md.snap | 7 ++++- cli/tests/test_util_command.rs | 18 +++++++++++ docs/install-and-setup.md | 7 +++++ 8 files changed, 94 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca1bfa654..458a5b38a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `jj move --from/--to` can now be abbreviated to `jj move -f/-t` +* 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 1d2c50927..d171b01fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -391,13 +391,23 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.0" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299353be8209bd133b049bf1c63582d184a8b39fd9c04f15fe65f50f88bdfe6c" +checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" dependencies = [ "clap", ] +[[package]] +name = "clap_complete_nushell" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0e48e026ce7df2040239117d25e4e79714907420c70294a5ce4b6bbe6a7b6" +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 a16579674..a799dc17b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,8 @@ clap = { version = "4.5.1", features = [ "wrap_help", "string", ] } -clap_complete = "4.5.0" +clap_complete = "4.5.1" +clap_complete_nushell = "4.5.1" 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 a9c044203..cb0e83e45 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 02c134bf8..d986f5255 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; @@ -42,6 +41,11 @@ Apply it by running one of these: - **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 @@ -52,7 +56,7 @@ Apply it by running one of these: #[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/cli-reference@.md.snap b/cli/tests/cli-reference@.md.snap index e40ebb4cb..3201a151d 100644 --- a/cli/tests/cli-reference@.md.snap +++ b/cli/tests/cli-reference@.md.snap @@ -1742,6 +1742,11 @@ Apply it by running one of these: - **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 @@ -1755,7 +1760,7 @@ Apply it by running one of these: * `` - Possible values: `bash`, `elvish`, `fish`, `powershell`, `zsh` + Possible values: `bash`, `elvish`, `fish`, `nushell`, `power-shell`, `zsh` ###### **Options:** diff --git a/cli/tests/test_util_command.rs b/cli/tests/test_util_command.rs index 251f216f1..87f8e9a8b 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"); +} diff --git a/docs/install-and-setup.md b/docs/install-and-setup.md index 057ae5966..185c9580f 100644 --- a/docs/install-and-setup.md +++ b/docs/install-and-setup.md @@ -179,6 +179,13 @@ source <(jj util completion zsh) jj util completion fish | source ``` +### Nushell + +```nu +jj util completion nushell | save completions-jj.nu +use completions-jj.nu * # Or `source completions-jj.nu` +``` + ### Xonsh ```shell