Skip to content
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 @@ -36,13 +36,15 @@ clap = { path = "../", version = "4.0.0", default-features = false, features = [
clap_complete = { path = "../clap_complete", version = "4.5.51" }
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
138 changes: 138 additions & 0 deletions clap_complete_nushell/src/dynamic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//! Implements dynamic completion for Nushell.
//!
//! There is no direct equivalent of other shells' `source $(COMPLETE=... your-clap-bin)` in nushell,
//! because code being sourced must exist at parse-time.
//!
//! One way to get close to that is to split the completion integration into two parts:
//! 1. a minimal part that goes into `env.nu`, which updates the actual completion integration
//! 2. the completion integration, which is placed into the user's autoload directory
//!
//! To install the completion integration, the user runs
//! ```nu
//! COMPLETE=nushell your-clap-bin | save --raw --force --append $nu.env-path
//! ```
// Std
use std::ffi::{OsStr, OsString};
use std::fmt::Display;
use std::io::{Error, Write};
use std::path::Path;

// External
use clap::Command;
Comment on lines +15 to +22
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't comment the groupings

use clap_complete::env::EnvCompleter;

/// Generate integration for dynamic completion in Nushell
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason you have all of these traits implemented?

pub struct Nushell;

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

fn is(&self, name: &str) -> bool {
name == "nushell"
}

fn write_registration(
&self,
var: &str,
name: &str,
bin: &str,
completer: &str,
buf: &mut dyn Write,
) -> Result<(), Error> {
let mode_var = ModeVar(var).to_string();
if std::env::var_os(&mode_var).as_ref().map(|x| x.as_os_str())
== Some(OsStr::new("integration"))
{
write_completion_script(var, name, bin, completer, buf)
} else {
write_refresh_completion_integration(var, name, completer, buf)
}
}

fn write_complete(
&self,
cmd: &mut Command,
args: Vec<OsString>,
current_dir: Option<&Path>,
buf: &mut dyn Write,
) -> Result<(), Error> {
let idx = args.len().saturating_sub(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}")
}
}

struct ModeVar<'a>(&'a str);

impl<'a> Display for ModeVar<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "_{0}__mode", self.0)
}
}

fn write_refresh_completion_integration(
var: &str,
name: &str,
completer: &str,
buf: &mut dyn Write,
) -> Result<(), Error> {
let mode = ModeVar(var);
writeln!(
buf,
r#"
# Refresh completer integration for {name} (must be in env.nu)
do {{
# Search for existing script to avoid duplicates in case autoload dirs change
let completer_script_name = '{name}-completer.nu'
let autoload_dir = $nu.user-autoload-dirs
| where {{ path join $completer_script_name | path exists }}
| get 0 --optional
| default ($nu.user-autoload-dirs | get 0 --optional)
mkdir $autoload_dir
let completer_path = ($autoload_dir | path join $completer_script_name)
{var}=nushell {mode}=integration ^r#'{completer}'# | save --raw --force $completer_path
}}
"#
)
}

fn write_completion_script(
var: &str,
name: &str,
_bin: &str,
completer: &str,
buf: &mut dyn Write,
) -> Result<(), Error> {
writeln!(
buf,
r#"
# Performs the completion for {name}
def {name}-completer [
spans: list<string> # The spans that were passed to the external completer closure
]: nothing -> list {{
{var}=nushell ^r#'{completer}'# -- ...$spans | from json
}}
@complete {name}-completer
def --wrapped {name} [...args] {{
^r#'{completer}'# ...$args
}}
"#
)
}
4 changes: 4 additions & 0 deletions clap_complete_nushell/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@ use clap::{builder::PossibleValue, Arg, ArgAction, Command};
use clap_complete::Generator;

/// Generate Nushell complete file
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still needed?

pub struct Nushell;

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

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