From 5189e9486c4d18dca894fd245bcccf3b4e6b798b Mon Sep 17 00:00:00 2001 From: blindfs Date: Fri, 11 Apr 2025 08:11:57 +0800 Subject: [PATCH 1/4] fix(completion): quoted cell path completion --- .../src/completions/cell_path_completions.rs | 30 +++++++++++++------ crates/nu-cli/tests/completions/mod.rs | 27 +++++++++++++++++ 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/crates/nu-cli/src/completions/cell_path_completions.rs b/crates/nu-cli/src/completions/cell_path_completions.rs index 665558190f..1191380eb4 100644 --- a/crates/nu-cli/src/completions/cell_path_completions.rs +++ b/crates/nu-cli/src/completions/cell_path_completions.rs @@ -24,7 +24,10 @@ fn prefix_from_path_member(member: &PathMember, pos: usize) -> (String, Span) { .get(..pos + 1 - start) .map(str::to_string) .unwrap_or(prefix_str); - (prefix_str, Span::new(start, pos + 1)) + // strip wrapping quotes + let quotations = ['"', '\'', '`']; + let prefix_str = prefix_str.strip_prefix(quotations).unwrap_or(&prefix_str); + (prefix_str.to_string(), Span::new(start, pos + 1)) } impl Completer for CellPathCompletion<'_> { @@ -108,14 +111,23 @@ fn get_suggestions_by_value( value: &Value, current_span: reedline::Span, ) -> Vec { - let to_suggestion = |s: String, v: Option<&Value>| SemanticSuggestion { - suggestion: Suggestion { - value: s, - span: current_span, - description: v.map(|v| v.get_type().to_string()), - ..Suggestion::default() - }, - kind: Some(SuggestionKind::CellPath), + let to_suggestion = |s: String, v: Option<&Value>| { + // Check if the string needs quoting (has spaces or punctuation) + let value = if s.contains(|c: char| c.is_whitespace() || c.is_ascii_punctuation()) { + format!("{:?}", s) + } else { + s + }; + + SemanticSuggestion { + suggestion: Suggestion { + value, + span: current_span, + description: v.map(|v| v.get_type().to_string()), + ..Suggestion::default() + }, + kind: Some(SuggestionKind::CellPath), + } }; match value { Value::Record { val, .. } => val diff --git a/crates/nu-cli/tests/completions/mod.rs b/crates/nu-cli/tests/completions/mod.rs index 9792f2b19b..5c6a10059a 100644 --- a/crates/nu-cli/tests/completions/mod.rs +++ b/crates/nu-cli/tests/completions/mod.rs @@ -2007,6 +2007,33 @@ fn table_cell_path_completions() { match_suggestions(&expected, &suggestions); } +#[test] +fn quoted_cell_path_completions() { + let (_, _, mut engine, mut stack) = new_engine(); + let command = r#"let foo = {'foo bar':1 'foo\\"bar"': 1 '.': 1 '|': 1}"#; + assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok()); + let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); + + let expected: Vec<_> = vec![ + "\".\"", + "\"foo bar\"", + "\"foo\\\\\\\\\\\"bar\\\"\"", + "\"|\"", + ]; + let completion_str = "$foo."; + let suggestions = completer.complete(completion_str, completion_str.len()); + match_suggestions(&expected, &suggestions); + + let expected: Vec<_> = vec!["\"foo bar\"", "\"foo\\\\\\\\\\\"bar\\\"\""]; + let completion_str = "$foo.`foo"; + let suggestions = completer.complete(completion_str, completion_str.len()); + match_suggestions(&expected, &suggestions); + + let completion_str = "$foo.foo"; + let suggestions = completer.complete(completion_str, completion_str.len()); + match_suggestions(&expected, &suggestions); +} + #[test] fn alias_of_command_and_flags() { let (_, _, mut engine, mut stack) = new_engine(); From a715d040559edec6a503119acaf2c9f43ad97e4d Mon Sep 17 00:00:00 2001 From: blindfs Date: Fri, 11 Apr 2025 09:18:11 +0800 Subject: [PATCH 2/4] fix: test --- crates/nu-cli/src/completions/cell_path_completions.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/nu-cli/src/completions/cell_path_completions.rs b/crates/nu-cli/src/completions/cell_path_completions.rs index 1191380eb4..23315b22f0 100644 --- a/crates/nu-cli/src/completions/cell_path_completions.rs +++ b/crates/nu-cli/src/completions/cell_path_completions.rs @@ -113,7 +113,9 @@ fn get_suggestions_by_value( ) -> Vec { let to_suggestion = |s: String, v: Option<&Value>| { // Check if the string needs quoting (has spaces or punctuation) - let value = if s.contains(|c: char| c.is_whitespace() || c.is_ascii_punctuation()) { + let value = if s.contains(|c: char| { + c.is_whitespace() || (c.is_ascii_punctuation() && !(['_', '-'].contains(&c))) + }) { format!("{:?}", s) } else { s From 53c94f8a06347b2d75ed0beee8c667d70b25ef92 Mon Sep 17 00:00:00 2001 From: blindfs Date: Fri, 11 Apr 2025 11:51:39 +0800 Subject: [PATCH 3/4] fix: edge cases --- .../src/completions/cell_path_completions.rs | 18 ++++++++---------- crates/nu-cli/tests/completions/mod.rs | 4 +++- crates/nu-lsp/src/completion.rs | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/nu-cli/src/completions/cell_path_completions.rs b/crates/nu-cli/src/completions/cell_path_completions.rs index 23315b22f0..6ff407e139 100644 --- a/crates/nu-cli/src/completions/cell_path_completions.rs +++ b/crates/nu-cli/src/completions/cell_path_completions.rs @@ -17,16 +17,13 @@ pub struct CellPathCompletion<'a> { fn prefix_from_path_member(member: &PathMember, pos: usize) -> (String, Span) { let (prefix_str, start) = match member { - PathMember::String { val, span, .. } => (val.clone(), span.start), - PathMember::Int { val, span, .. } => (val.to_string(), span.start), + PathMember::String { val, span, .. } => (val, span.start), + PathMember::Int { val, span, .. } => (&val.to_string(), span.start), }; - let prefix_str = prefix_str - .get(..pos + 1 - start) - .map(str::to_string) - .unwrap_or(prefix_str); + let prefix_str = prefix_str.get(..pos + 1 - start).unwrap_or(prefix_str); // strip wrapping quotes let quotations = ['"', '\'', '`']; - let prefix_str = prefix_str.strip_prefix(quotations).unwrap_or(&prefix_str); + let prefix_str = prefix_str.strip_prefix(quotations).unwrap_or(prefix_str); (prefix_str.to_string(), Span::new(start, pos + 1)) } @@ -113,9 +110,10 @@ fn get_suggestions_by_value( ) -> Vec { let to_suggestion = |s: String, v: Option<&Value>| { // Check if the string needs quoting (has spaces or punctuation) - let value = if s.contains(|c: char| { - c.is_whitespace() || (c.is_ascii_punctuation() && !(['_', '-'].contains(&c))) - }) { + let value = if s.is_empty() + || s.chars() + .any(|c: char| !(c.is_ascii_alphabetic() || ['_', '-'].contains(&c))) + { format!("{:?}", s) } else { s diff --git a/crates/nu-cli/tests/completions/mod.rs b/crates/nu-cli/tests/completions/mod.rs index 5c6a10059a..8acc6e8409 100644 --- a/crates/nu-cli/tests/completions/mod.rs +++ b/crates/nu-cli/tests/completions/mod.rs @@ -2010,12 +2010,14 @@ fn table_cell_path_completions() { #[test] fn quoted_cell_path_completions() { let (_, _, mut engine, mut stack) = new_engine(); - let command = r#"let foo = {'foo bar':1 'foo\\"bar"': 1 '.': 1 '|': 1}"#; + let command = r#"let foo = {'foo bar':1 'foo\\"bar"': 1 '.': 1 '|': 1 1: 1 "": 1}"#; assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok()); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); let expected: Vec<_> = vec![ + "\"\"", "\".\"", + "\"1\"", "\"foo bar\"", "\"foo\\\\\\\\\\\"bar\\\"\"", "\"|\"", diff --git a/crates/nu-lsp/src/completion.rs b/crates/nu-lsp/src/completion.rs index 2d03acbb27..874d138eab 100644 --- a/crates/nu-lsp/src/completion.rs +++ b/crates/nu-lsp/src/completion.rs @@ -486,10 +486,10 @@ mod tests { actual: result_from_message(resp), expected: serde_json::json!([ { - "label": "1", + "label": "\"1\"", "detail": "string", "textEdit": { - "newText": "1", + "newText": "\"1\"", "range": { "start": { "line": 1, "character": 5 }, "end": { "line": 1, "character": 5 } } }, "kind": 10 From 180cb856d544b0daa7774b882856948803a9a18f Mon Sep 17 00:00:00 2001 From: blindfs Date: Fri, 11 Apr 2025 11:53:37 +0800 Subject: [PATCH 4/4] doc: comment update --- crates/nu-cli/src/completions/cell_path_completions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-cli/src/completions/cell_path_completions.rs b/crates/nu-cli/src/completions/cell_path_completions.rs index 6ff407e139..3a439bb790 100644 --- a/crates/nu-cli/src/completions/cell_path_completions.rs +++ b/crates/nu-cli/src/completions/cell_path_completions.rs @@ -109,7 +109,7 @@ fn get_suggestions_by_value( current_span: reedline::Span, ) -> Vec { let to_suggestion = |s: String, v: Option<&Value>| { - // Check if the string needs quoting (has spaces or punctuation) + // Check if the string needs quoting let value = if s.is_empty() || s.chars() .any(|c: char| !(c.is_ascii_alphabetic() || ['_', '-'].contains(&c)))