diff --git a/Cargo.lock b/Cargo.lock index 0faa4ab..8654149 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1301,6 +1301,7 @@ dependencies = [ "nu-cmd-lang", "nu-parser", "nu-protocol", + "nu-utils", "nuon", "rayon", "rstest", diff --git a/Cargo.toml b/Cargo.toml index 0f8fb66..88bfe7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ nu-ansi-term = "0.50.3" nu-cmd-lang = "0.110.0" nu-parser = "0.110.0" nu-protocol = "0.110.0" +nu-utils = "0.110.0" nuon = "0.110.0" rayon = "1.11.0" thiserror = "2.0.18" diff --git a/src/formatting.rs b/src/formatting.rs index 3644a71..9a9f07e 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -13,8 +13,9 @@ use nu_protocol::{ RecordItem, RedirectionTarget, }, engine::{EngineState, StateWorkingSet}, - Signature, Span, SyntaxShape, + Completion, Signature, Span, SyntaxShape, }; +use nu_utils::NuCow; /// Commands that format their block arguments in a special way const BLOCK_COMMANDS: &[&str] = &["for", "while", "loop", "module"]; @@ -576,6 +577,60 @@ impl<'a> Formatter<'a> { } } + /// Write custom completion if present (e.g., @`completion_name` or @[list items]) + fn write_custom_completion(&mut self, completion: &Option) { + match completion { + Some(Completion::Command(decl_id)) => { + let decl = self.working_set.get_decl(*decl_id); + let name = decl.name(); + self.write("@"); + // Quote command names that contain special characters (like spaces) + if name.contains(' ') + || name.contains('-') + || name.contains('[') + || name.contains(']') + { + self.write("\""); + self.write(name); + self.write("\""); + } else { + self.write(name); + } + } + Some(Completion::List(list)) => { + self.write("@["); + match list { + NuCow::Borrowed(items) => { + for (i, item) in items.iter().enumerate() { + if i > 0 { + self.write(" "); + } + self.write(item); + } + } + NuCow::Owned(items) => { + for (i, item) in items.iter().enumerate() { + if i > 0 { + self.write(" "); + } + self.write(item); + } + } + } + self.write("]"); + } + None => {} + } + } + + /// Write a `SyntaxShape` with any necessary prettifying + fn write_shape(&mut self, shape: &SyntaxShape) { + match shape { + SyntaxShape::Closure(Option::None) => self.write("closure"), + _ => self.write(&format!("{}", shape)), + } + } + /// Format a signature (for def commands) fn format_signature(&mut self, sig: &Signature) { self.write("["); @@ -612,12 +667,8 @@ impl<'a> Formatter<'a> { self.write(¶m.name); if param.shape != SyntaxShape::Any { self.write(": "); - match ¶m.shape { - // Fixes an issue in which closure type hints were formatted as `closure()`. - // TODO: This feels hacky. Should this be addressed in the `nu-protocol` crate instead? - SyntaxShape::Closure(Option::None) => self.write("closure"), - _ => self.write(&format!("{}", param.shape)), - } + self.write_shape(¶m.shape); + self.write_custom_completion(¶m.completion); } } @@ -631,7 +682,8 @@ impl<'a> Formatter<'a> { } if param.shape != SyntaxShape::Any { self.write(": "); - self.write(&format!("{}", param.shape)); + self.write_shape(¶m.shape); + self.write_custom_completion(¶m.completion); } if let Some(default) = ¶m.default_value { self.write(" = "); @@ -664,7 +716,8 @@ impl<'a> Formatter<'a> { } if let Some(shape) = &flag.arg { self.write(": "); - self.write(&format!("{}", shape)); + self.write_shape(shape); + self.write_custom_completion(&flag.completion); } if let Some(default) = &flag.default_value { self.write(" = "); @@ -679,7 +732,8 @@ impl<'a> Formatter<'a> { self.write(&rest.name); if rest.shape != SyntaxShape::Any { self.write(": "); - self.write(&format!("{}", rest.shape)); + self.write_shape(&rest.shape); + self.write_custom_completion(&rest.completion); } } diff --git a/tests/fixtures/expected/issue81.nu b/tests/fixtures/expected/issue81.nu new file mode 100644 index 0000000..6738617 --- /dev/null +++ b/tests/fixtures/expected/issue81.nu @@ -0,0 +1,5 @@ +def f1 [] { } +def f2 [foo: int@f1 = 1] { } +def f3 [x: string@[a b c]] { } +def "complete foo" [] { } +def test [x: int@"complete foo"] { } diff --git a/tests/fixtures/input/issue81.nu b/tests/fixtures/input/issue81.nu new file mode 100644 index 0000000..6738617 --- /dev/null +++ b/tests/fixtures/input/issue81.nu @@ -0,0 +1,5 @@ +def f1 [] { } +def f2 [foo: int@f1 = 1] { } +def f3 [x: string@[a b c]] { } +def "complete foo" [] { } +def test [x: int@"complete foo"] { } diff --git a/tests/ground_truth.rs b/tests/ground_truth.rs index 39d59b0..5e6054f 100644 --- a/tests/ground_truth.rs +++ b/tests/ground_truth.rs @@ -755,6 +755,18 @@ fn issue76_test() { run_ground_truth_test(&test_binary, "issue76"); } +#[test] +fn issue81_test() { + let test_binary = get_test_binary(); + run_ground_truth_test(&test_binary, "issue81"); +} + +#[test] +fn idempotency_issue81_test() { + let test_binary = get_test_binary(); + run_idempotency_test(&test_binary, "issue81"); +} + #[test] fn ground_truth_inline_param_comment_issue77() { let test_binary = get_test_binary();