Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(complete): Add dynamic completion for nushell #5841

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions clap_complete_nushell/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ clap = { path = "../", version = "4.0.0", default-features = false, features = [
clap_complete = { path = "../clap_complete", version = "4.0.0" }
completest = { version = "0.4.0", optional = true }
completest-nu = { version = "0.4.0", optional = true }
write-json = { version = "0.1.4", optional = true }

[dev-dependencies]
snapbox = { version = "0.6.0", features = ["diff", "examples", "dir"] }
clap = { path = "../", version = "4.0.0", default-features = false, features = ["std", "help"] }

[features]
default = []
unstable-dynamic = ["clap_complete/unstable-dynamic", "dep:write-json"]
unstable-shell-tests = ["dep:completest", "dep:completest-nu"]

[lints]
Expand Down
127 changes: 127 additions & 0 deletions clap_complete_nushell/src/dynamic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use clap::Command;
use clap_complete::env::EnvCompleter;
use std::ffi::OsString;
use std::io::{Error, Write};
use std::path::Path;

impl EnvCompleter for super::Nushell {
fn name(&self) -> &'static str {
"nushell"
}

fn is(&self, name: &str) -> bool {
name.eq_ignore_ascii_case("nushell") || name.eq_ignore_ascii_case("nu")
}

fn write_registration(
&self,
var: &str,
name: &str,
bin: &str,
completer: &str,
buf: &mut dyn Write,
) -> Result<(), Error> {
writeln!(
buf,
r#"
# External completer for {name}
#
# This module can either be `source`d for a simplified installation or loaded as a module (`use`)
# to be integrated into your existing external completer setup.
#
# Example 1 (simplified installation):
# ```
# use {name}-completer.nu # (replace with path to this file)
# {name}-completer install
# ```
#
# Example 2 (integrate with custom external completer):
# ```
# use {name}-completer.nu # (replace with path to this file)
# $env.config.completions.external.enable = true
# $env.config.completions.external.completer = {{ |spans|
# if ({name}-completer handles $spans) {{
# {name}-completer complete $spans
# }} else {{
# # any other external completers
# }}
# }}
# ```

# Workaround for https://github.com/nushell/nushell/issues/8483
def expand-alias []: list -> list {{
let spans = $in
if ($spans | length) == 0 {{
return $spans
}}

let expanded_alias = (scope aliases | where name == $spans.0 | get --ignore-errors 0.expansion)
if $expanded_alias != null {{
# put the first word of the expanded alias first in the span
$spans | skip 1 | prepend ($expanded_alias | split row " " | take 1)
}} else {{
$spans
}}
}}

# Determines whether the completer for {name} is supposed to handle the command line
export def handles [
spans: list # The spans that were passed to the external completer closure
]: nothing -> bool {{
($spans | expand-alias | get --ignore-errors 0) == r#'{bin}'#
}}

# Performs the completion for {name}
export def complete [
spans: list # The spans that were passed to the external completer closure
]: nothing -> list {{
{var}=nushell ^r#'{completer}'# -- ...$spans | from json
}}

# Installs this module as an external completer for {name} globally.
#
# For commands other {name}, it will fall back to whatever external completer
# was defined previously (if any).
export def --env install []: nothing -> nothing {{
$env.config = $env.config
| upsert completions.external.enable true
| upsert completions.external.completer {{ |original_config|
let previous_completer = $original_config
| get --ignore-errors completions.external.completer
| default {{ |spans| null }}
{{ |spans|
if (handles $spans) {{
complete $spans
}} else {{
do $previous_completer $spans
}}
}}
}}
}}
"#
)
}

fn write_complete(
&self,
cmd: &mut Command,
args: Vec<OsString>,
current_dir: Option<&Path>,
buf: &mut dyn Write,
) -> Result<(), Error> {
let idx = (args.len() - 1).max(0);
let candidates = clap_complete::engine::complete(cmd, args, idx, current_dir)?;
let mut strbuf = String::new();
{
let mut records = write_json::array(&mut strbuf);
for candidate in candidates {
let mut record = records.object();
record.string("value", candidate.get_value().to_string_lossy().as_ref());
if let Some(help) = candidate.get_help() {
record.string("description", &help.to_string()[..]);
}
}
}
write!(buf, "{strbuf}")
}
}
4 changes: 4 additions & 0 deletions clap_complete_nushell/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ use clap::{builder::PossibleValue, Arg, ArgAction, Command};
use clap_complete::Generator;

/// Generate Nushell complete file
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Nushell;

#[cfg(feature = "unstable-dynamic")]
mod dynamic;

impl Generator for Nushell {
fn file_name(&self, name: &str) -> String {
format!("{name}.nu")
Expand Down
Loading