diff --git a/crates/nu-cli/src/completions/cell_path_completions.rs b/crates/nu-cli/src/completions/cell_path_completions.rs index 665558190f..3a439bb790 100644 --- a/crates/nu-cli/src/completions/cell_path_completions.rs +++ b/crates/nu-cli/src/completions/cell_path_completions.rs @@ -17,14 +17,14 @@ 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); - (prefix_str, Span::new(start, pos + 1)) + 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); + (prefix_str.to_string(), Span::new(start, pos + 1)) } impl Completer for CellPathCompletion<'_> { @@ -108,14 +108,26 @@ 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 + let value = if s.is_empty() + || s.chars() + .any(|c: char| !(c.is_ascii_alphabetic() || ['_', '-'].contains(&c))) + { + 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 8aba4337a8..c8a02790dd 100644 --- a/crates/nu-cli/tests/completions/mod.rs +++ b/crates/nu-cli/tests/completions/mod.rs @@ -2005,6 +2005,35 @@ 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 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\\\"\"", + "\"|\"", + ]; + 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(); diff --git a/crates/nu-lsp/src/completion.rs b/crates/nu-lsp/src/completion.rs index 2bb2c1492e..bc28ed5058 100644 --- a/crates/nu-lsp/src/completion.rs +++ b/crates/nu-lsp/src/completion.rs @@ -481,10 +481,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