feat(lsp): show value on hover for const variables and CellPaths (#14940)

# Description

e.g.
<img width="299" alt="image"
src="https://github.com/user-attachments/assets/3c16835a-6d4d-48ec-b7d6-68d5bdb88ea2"
/>

and `goto def` on cell paths now finds the specific cell (only for
const) instead of doing nothing.

# User-Facing Changes
# Tests + Formatting
# After Submitting
This commit is contained in:
zc he 2025-01-28 20:17:07 +08:00 committed by GitHub
parent a2705f9eb5
commit 4bc28f1752
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 222 additions and 336 deletions

View File

@ -1,8 +1,8 @@
use crate::Id;
use nu_protocol::{
ast::{
Argument, Block, Call, Expr, Expression, ExternalArgument, ListItem, MatchPattern, Pattern,
PipelineRedirection, RecordItem,
Argument, Block, Call, Expr, Expression, ExternalArgument, ListItem, MatchPattern,
PathMember, Pattern, PipelineRedirection, RecordItem,
},
engine::StateWorkingSet,
Span,
@ -389,6 +389,11 @@ fn try_find_id_in_overlay(
return None;
}
let check_location = |span: &Span| location.map_or(true, |pos| span.contains(*pos));
let module_from_overlay_name = |name: &str, span: Span| {
let found_id = Id::Module(working_set.find_overlay(name.as_bytes())?.origin);
id.map_or(true, |id_r| found_id == *id_r)
.then_some((found_id, strip_quotes(span, working_set)))
};
for arg in call.arguments.iter() {
let Argument::Positional(expr) = arg else {
continue;
@ -397,21 +402,11 @@ fn try_find_id_in_overlay(
continue;
};
let matched = match &expr.expr {
Expr::String(name) => {
let name = name.as_bytes();
get_matched_module_id(working_set, expr.span, id).or_else(|| {
let found_id = Id::Module(working_set.find_overlay(name)?.origin);
id.map_or(true, |id_r| found_id == *id_r)
.then_some((found_id, strip_quotes(expr.span, working_set)))
})
}
Expr::String(name) => get_matched_module_id(working_set, expr.span, id)
.or(module_from_overlay_name(name, expr.span)),
// keyword 'as'
Expr::Keyword(kwd) => match &kwd.expr.expr {
Expr::String(name) => {
let found_id = Id::Module(working_set.find_overlay(name.as_bytes())?.origin);
id.map_or(true, |id_r| found_id == *id_r)
.then_some((found_id, strip_quotes(kwd.expr.span, working_set)))
}
Expr::String(name) => module_from_overlay_name(name, kwd.expr.span),
_ => None,
},
_ => None,
@ -470,6 +465,27 @@ fn find_id_in_expr(
.map(|p| vec![p])
}
}
Expr::FullCellPath(fcp) => {
if fcp.head.span.contains(*location) {
None
} else {
let Expression {
expr: Expr::Var(var_id),
..
} = fcp.head
else {
return None;
};
let tail: Vec<PathMember> = fcp
.tail
.clone()
.into_iter()
.take_while(|pm| pm.span().start <= *location)
.collect();
let span = tail.last()?.span();
Some(vec![(Id::CellPath(var_id, tail), span)])
}
}
Expr::Overlay(Some(module_id)) => Some(vec![(Id::Module(*module_id), span)]),
// terminal value expressions
Expr::Bool(_)

View File

@ -21,6 +21,16 @@ impl LanguageServer {
let module = working_set.get_module(*module_id);
module.span
}
Id::CellPath(var_id, cell_path) => {
let var = working_set.get_variable(*var_id);
Some(
var.const_val
.clone()
.and_then(|val| val.follow_cell_path(cell_path, false).ok())
.map(|val| val.span())
.unwrap_or(var.declaration_span),
)
}
_ => None,
}
}
@ -54,8 +64,8 @@ impl LanguageServer {
#[cfg(test)]
mod tests {
use crate::path_to_uri;
use crate::tests::{initialize_language_server, open_unchecked};
use assert_json_diff::{assert_json_eq, assert_json_include};
use crate::tests::{initialize_language_server, open_unchecked, result_from_message};
use assert_json_diff::assert_json_eq;
use lsp_server::{Connection, Message};
use lsp_types::{
request::{GotoDefinition, Request},
@ -99,40 +109,10 @@ mod tests {
let mut none_existent_path = root();
none_existent_path.push("none-existent.nu");
let script = path_to_uri(&none_existent_path);
let resp = send_goto_definition_request(&client_connection, script.clone(), 0, 0);
client_connection
.sender
.send(Message::Request(lsp_server::Request {
id: 2.into(),
method: GotoDefinition::METHOD.to_string(),
params: serde_json::to_value(GotoDefinitionParams {
text_document_position_params: TextDocumentPositionParams {
text_document: TextDocumentIdentifier {
uri: path_to_uri(&none_existent_path),
},
position: Position {
line: 0,
character: 0,
},
},
work_done_progress_params: WorkDoneProgressParams::default(),
partial_result_params: PartialResultParams::default(),
})
.unwrap(),
}))
.unwrap();
let resp = client_connection
.receiver
.recv_timeout(std::time::Duration::from_secs(2))
.unwrap();
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(result, serde_json::json!(null));
assert_json_eq!(result_from_message(resp), serde_json::json!(null));
}
#[test]
@ -146,16 +126,10 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_goto_definition_request(&client_connection, script.clone(), 2, 12);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!({
"uri": script,
"range": {
@ -166,6 +140,31 @@ mod tests {
);
}
#[test]
fn goto_definition_of_cell_path() {
let (client_connection, _recv) = initialize_language_server(None);
let mut script = fixtures();
script.push("lsp");
script.push("hover");
script.push("cell_path.nu");
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_goto_definition_request(&client_connection, script.clone(), 4, 7);
assert_json_eq!(
result_from_message(resp).pointer("/range/start").unwrap(),
serde_json::json!({ "line": 1, "character": 10 })
);
let resp = send_goto_definition_request(&client_connection, script.clone(), 4, 9);
assert_json_eq!(
result_from_message(resp).pointer("/range/start").unwrap(),
serde_json::json!({ "line": 1, "character": 17 })
);
}
#[test]
fn goto_definition_of_command() {
let (client_connection, _recv) = initialize_language_server(None);
@ -177,16 +176,10 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_goto_definition_request(&client_connection, script.clone(), 4, 1);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!({
"uri": script,
"range": {
@ -208,16 +201,10 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_goto_definition_request(&client_connection, script.clone(), 4, 2);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!({
"uri": script,
"range": {
@ -239,16 +226,10 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_goto_definition_request(&client_connection, script.clone(), 1, 14);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!({
"uri": script,
"range": {
@ -270,16 +251,10 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_goto_definition_request(&client_connection, script.clone(), 1, 21);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!({
"uri": script,
"range": {
@ -301,16 +276,10 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_goto_definition_request(&client_connection, script.clone(), 2, 9);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!({
"uri": script,
"range": {
@ -332,16 +301,10 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_goto_definition_request(&client_connection, script.clone(), 1, 16);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!({
"uri": script,
"range": {
@ -363,16 +326,10 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_goto_definition_request(&client_connection, script.clone(), 3, 15);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!({
"uri": script,
"range": {
@ -394,16 +351,10 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_goto_definition_request(&client_connection, script.clone(), 0, 23);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!({
"uri": script.to_string().replace("use_module", "module"),
"range": {
@ -425,16 +376,10 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_goto_definition_request(&client_connection, script.clone(), 3, 6);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!({
"uri": script.to_string().replace("use_module", "module"),
"range": {
@ -458,57 +403,21 @@ mod tests {
open_unchecked(&client_connection, script.clone());
let resp = send_goto_definition_request(&client_connection, script.clone(), 1, 20);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_include!(
actual: result,
expected: serde_json::json!({
"uri": script.to_string().replace("use_module", "module"),
"range": {
"start": { "line": 0, "character": 0 },
"end": { "line": 3 }
}
})
assert_json_eq!(
result_from_message(resp).pointer("/range/start").unwrap(),
serde_json::json!({ "line": 0, "character": 0 })
);
let resp = send_goto_definition_request(&client_connection, script.clone(), 1, 25);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_include!(
actual: result,
expected: serde_json::json!({
"uri": script.to_string().replace("use_module", "module"),
"range": {
"start": { "line": 0, "character": 0 },
"end": { "line": 3 }
}
})
assert_json_eq!(
result_from_message(resp).pointer("/range/start").unwrap(),
serde_json::json!({ "line": 0, "character": 0 })
);
let resp = send_goto_definition_request(&client_connection, script.clone(), 2, 30);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_include!(
actual: result,
expected: serde_json::json!({
"uri": script.to_string().replace("use_module", "module"),
"range": {
"start": { "line": 0, "character": 0 },
"end": { "line": 3 }
}
})
assert_json_eq!(
result_from_message(resp).pointer("/range/start").unwrap(),
serde_json::json!({ "line": 0, "character": 0 })
);
}
}

View File

@ -163,7 +163,7 @@ impl LanguageServer {
#[cfg(test)]
mod tests {
use crate::path_to_uri;
use crate::tests::{initialize_language_server, open_unchecked};
use crate::tests::{initialize_language_server, open_unchecked, result_from_message};
use assert_json_diff::assert_json_eq;
use lsp_server::{Connection, Message};
use lsp_types::{
@ -213,16 +213,10 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_inlay_hint_request(&client_connection, script.clone());
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!([
{ "position": { "line": 0, "character": 9 }, "label": ": int", "kind": 1 },
{ "position": { "line": 1, "character": 7 }, "label": ": string", "kind": 1 },
@ -246,16 +240,10 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_inlay_hint_request(&client_connection, script.clone());
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!([
{ "position": { "line": 0, "character": 8 }, "label": ": int", "kind": 1 },
{ "position": { "line": 1, "character": 10 }, "label": ": float", "kind": 1 },
@ -280,16 +268,10 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_inlay_hint_request(&client_connection, script.clone());
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!([
{
"position": { "line": 9, "character": 9 },

View File

@ -13,7 +13,7 @@ use lsp_types::{
use miette::{miette, IntoDiagnostic, Result};
use nu_cli::{NuCompleter, SuggestionKind};
use nu_protocol::{
ast::Block,
ast::{Block, PathMember},
engine::{CachedFile, Command, EngineState, Stack, StateDelta, StateWorkingSet},
DeclId, ModuleId, Span, Type, VarId,
};
@ -36,12 +36,13 @@ mod notification;
mod symbols;
mod workspace;
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Id {
Variable(VarId),
Declaration(DeclId),
Value(Type),
Module(ModuleId),
CellPath(VarId, Vec<PathMember>),
}
pub struct LanguageServer {
@ -423,19 +424,20 @@ impl LanguageServer {
}
}
fn get_decl_description(decl: &dyn Command) -> String {
fn get_decl_description(decl: &dyn Command, skip_description: bool) -> String {
let mut description = String::new();
// First description
description.push_str(&format!("{}\n", decl.description().replace('\r', "")));
if !skip_description {
// First description
description.push_str(&format!("{}\n", decl.description().replace('\r', "")));
// Additional description
if !decl.extra_description().is_empty() {
description.push_str(&format!("\n{}\n", decl.extra_description()));
// Additional description
if !decl.extra_description().is_empty() {
description.push_str(&format!("\n{}\n", decl.extra_description()));
}
}
// Usage
description.push_str("-----\n### Usage \n```nu\n");
description.push_str("---\n### Usage \n```nu\n");
let signature = decl.signature();
description.push_str(&format!(" {}", signature.name));
if !signature.named.is_empty() {
@ -588,29 +590,49 @@ impl LanguageServer {
match id {
Id::Variable(var_id) => {
let var = working_set.get_variable(var_id);
let value = var
.const_val
.clone()
.and_then(|v| v.coerce_into_string().ok())
.map(|s| format!("\n---\n{}", s))
.unwrap_or_default();
let contents = format!(
"{}{} `{}`",
if var.const_val.is_some() {
"const "
} else {
""
},
"{} ```\n{}\n``` {}",
if var.mutable { "mutable " } else { "" },
var.ty,
value
);
markdown_hover(contents)
}
Id::Declaration(decl_id) => {
markdown_hover(Self::get_decl_description(working_set.get_decl(decl_id)))
Id::CellPath(var_id, cell_path) => {
let var = working_set.get_variable(var_id);
markdown_hover(
var.const_val
.clone()
.and_then(|val| val.follow_cell_path(&cell_path, false).ok())
.map(|val| {
let ty = val.get_type().clone();
let value_string = val
.coerce_into_string()
.ok()
.map(|s| format!("\n---\n{}", s))
.unwrap_or_default();
format!("```\n{}\n```{}", ty, value_string)
})
.unwrap_or("`unknown`".into()),
)
}
Id::Declaration(decl_id) => markdown_hover(Self::get_decl_description(
working_set.get_decl(decl_id),
false,
)),
Id::Module(module_id) => {
let mut description = String::new();
for cmt_span in working_set.get_module_comments(module_id)? {
description.push_str(
String::from_utf8_lossy(working_set.get_span_contents(*cmt_span)).as_ref(),
);
description.push('\n');
}
let description = working_set
.get_module_comments(module_id)?
.iter()
.map(|sp| String::from_utf8_lossy(working_set.get_span_contents(*sp)).into())
.collect::<Vec<String>>()
.join("\n");
markdown_hover(description)
}
Id::Value(t) => markdown_hover(format!("`{}`", t)),
@ -657,7 +679,7 @@ impl LanguageServer {
.extra
.map(|ex| ex.join("\n"))
.or(decl_id.map(|decl_id| {
Self::get_decl_description(engine_state.get_decl(decl_id))
Self::get_decl_description(engine_state.get_decl(decl_id), true)
}))
.map(|value| {
Documentation::MarkupContent(MarkupContent {
@ -855,6 +877,13 @@ mod tests {
}
}
pub(crate) fn result_from_message(message: lsp_server::Message) -> serde_json::Value {
match message {
Message::Response(Response { result, .. }) => result.expect("Empty result!"),
_ => panic!("Unexpected message type!"),
}
}
pub(crate) fn send_hover_request(
client_connection: &Connection,
uri: Uri,
@ -894,17 +923,45 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_hover_request(&client_connection, script.clone(), 2, 0);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
serde_json::json!({ "contents": { "kind": "markdown", "value": " `table`" } })
result_from_message(resp),
serde_json::json!({ "contents": { "kind": "markdown", "value": " ```\ntable\n``` " } })
);
}
#[test]
fn hover_on_cell_path() {
let (client_connection, _recv) = initialize_language_server(None);
let mut script = fixtures();
script.push("lsp");
script.push("hover");
script.push("cell_path.nu");
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_hover_request(&client_connection, script.clone(), 4, 3);
let result = result_from_message(resp);
assert_json_eq!(
result.pointer("/contents/value").unwrap(),
serde_json::json!("```\nlist<any>\n```")
);
let resp = send_hover_request(&client_connection, script.clone(), 4, 7);
let result = result_from_message(resp);
assert_json_eq!(
result.pointer("/contents/value").unwrap(),
serde_json::json!("```\nrecord<bar: int>\n```")
);
let resp = send_hover_request(&client_connection, script.clone(), 4, 11);
let result = result_from_message(resp);
assert_json_eq!(
result.pointer("/contents/value").unwrap(),
serde_json::json!("```\nint\n```\n---\n2")
);
}
@ -919,20 +976,14 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_hover_request(&client_connection, script.clone(), 3, 0);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!({
"contents": {
"kind": "markdown",
"value": "Renders some greeting message\n-----\n### Usage \n```nu\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n"
"value": "Renders some greeting message\n---\n### Usage \n```nu\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n"
}
})
);
@ -949,20 +1000,14 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_hover_request(&client_connection, script.clone(), 5, 8);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!({
"contents": {
"kind": "markdown",
"value": "Concatenate multiple strings into a single string, with an optional separator between each.\n-----\n### Usage \n```nu\n str join {flags} <separator?>\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n\n### Parameters\n\n `separator: string` - Optional separator to use when creating string.\n\n\n### Input/output types\n\n```nu\n list<any> | string\n string | string\n\n```\n### Example(s)\n Create a string from input\n```nu\n ['nu', 'shell'] | str join\n```\n Create a string from input with a separator\n```nu\n ['nu', 'shell'] | str join '-'\n```\n"
"value": "Concatenate multiple strings into a single string, with an optional separator between each.\n---\n### Usage \n```nu\n str join {flags} <separator?>\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n\n### Parameters\n\n `separator: string` - Optional separator to use when creating string.\n\n\n### Input/output types\n\n```nu\n list<any> | string\n string | string\n\n```\n### Example(s)\n Create a string from input\n```nu\n ['nu', 'shell'] | str join\n```\n Create a string from input with a separator\n```nu\n ['nu', 'shell'] | str join '-'\n```\n"
}
})
);
@ -979,22 +1024,16 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_hover_request(&client_connection, script.clone(), 3, 12);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
let result = result_from_message(resp);
assert_eq!(
result
.unwrap()
.pointer("/contents/value")
.unwrap()
.to_string()
.replace("\\r", ""),
"\"# module doc\\n\""
"\"# module doc\""
);
}
@ -1039,16 +1078,10 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_complete_request(&client_connection, script, 2, 9);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_include!(
actual: result,
actual: result_from_message(resp),
expected: serde_json::json!([
{
"label": "$greeting",
@ -1074,16 +1107,10 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_complete_request(&client_connection, script, 0, 8);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_include!(
actual: result,
actual: result_from_message(resp),
expected: serde_json::json!([
{
"label": "config nu",
@ -1108,16 +1135,10 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_complete_request(&client_connection, script, 0, 13);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_include!(
actual: result,
actual: result_from_message(resp),
expected: serde_json::json!([
{
"label": "str trim",
@ -1144,16 +1165,10 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_complete_request(&client_connection, script, 0, 2);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_include!(
actual: result,
actual: result_from_message(resp),
expected: serde_json::json!([
{
"label": "overlay",

View File

@ -137,10 +137,10 @@ impl LanguageServer {
mod tests {
use crate::path_to_uri;
use crate::tests::{
initialize_language_server, open, open_unchecked, send_hover_request, update,
initialize_language_server, open, open_unchecked, result_from_message, send_hover_request,
update,
};
use assert_json_diff::assert_json_eq;
use lsp_server::Message;
use lsp_types::Range;
use nu_test_support::fs::fixtures;
@ -155,20 +155,14 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_hover_request(&client_connection, script.clone(), 0, 0);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!({
"contents": {
"kind": "markdown",
"value": "Create a variable and give it a value.\n\nThis command is a parser keyword. For details, check:\n https://www.nushell.sh/book/thinking_in_nu.html\n-----\n### Usage \n```nu\n let {flags} <var_name> <initial_value>\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n\n### Parameters\n\n `var_name: any` - Variable name.\n\n `initial_value: any` - Equals sign followed by value.\n\n\n### Input/output types\n\n```nu\n any | nothing\n\n```\n### Example(s)\n Set a variable to a value\n```nu\n let x = 10\n```\n Set a variable to the result of an expression\n```nu\n let x = 10 + 100\n```\n Set a variable based on the condition\n```nu\n let x = if false { -1 } else { 1 }\n```\n"
"value": "Create a variable and give it a value.\n\nThis command is a parser keyword. For details, check:\n https://www.nushell.sh/book/thinking_in_nu.html\n---\n### Usage \n```nu\n let {flags} <var_name> <initial_value>\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n\n### Parameters\n\n `var_name: any` - Variable name.\n\n `initial_value: any` - Equals sign followed by value.\n\n\n### Input/output types\n\n```nu\n any | nothing\n\n```\n### Example(s)\n Set a variable to a value\n```nu\n let x = 10\n```\n Set a variable to the result of an expression\n```nu\n let x = 10 + 100\n```\n Set a variable based on the condition\n```nu\n let x = if false { -1 } else { 1 }\n```\n"
}
})
);
@ -196,20 +190,14 @@ hello"#,
),
None,
);
let resp = send_hover_request(&client_connection, script.clone(), 3, 0);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!({
"contents": {
"kind": "markdown",
"value": "Renders some updated greeting message\n-----\n### Usage \n```nu\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n"
"value": "Renders some updated greeting message\n---\n### Usage \n```nu\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n"
}
})
);
@ -241,20 +229,14 @@ hello"#,
},
}),
);
let resp = send_hover_request(&client_connection, script.clone(), 3, 0);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!({
"contents": {
"kind": "markdown",
"value": "Renders some updated greeting message\n-----\n### Usage \n```nu\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n"
"value": "Renders some updated greeting message\n---\n### Usage \n```nu\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n"
}
})
);

View File

@ -298,7 +298,7 @@ impl LanguageServer {
#[cfg(test)]
mod tests {
use crate::path_to_uri;
use crate::tests::{initialize_language_server, open_unchecked, update};
use crate::tests::{initialize_language_server, open_unchecked, result_from_message, update};
use assert_json_diff::assert_json_eq;
use lsp_server::{Connection, Message};
use lsp_types::{
@ -361,15 +361,9 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_document_symbol_request(&client_connection, script.clone());
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(result, serde_json::json!([]));
assert_json_eq!(result_from_message(resp), serde_json::json!([]));
}
#[test]
@ -383,16 +377,10 @@ mod tests {
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_document_symbol_request(&client_connection, script.clone());
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!([
{
"name": "def_foo",
@ -446,16 +434,10 @@ mod tests {
},
}),
);
let resp = send_document_symbol_request(&client_connection, script.clone());
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!([
{
"name": "var_bar",
@ -490,16 +472,10 @@ mod tests {
open_unchecked(&client_connection, script_foo.clone());
open_unchecked(&client_connection, script_bar.clone());
let resp = send_workspace_symbol_request(&client_connection, "br".to_string());
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!([
{
"name": "def_bar",
@ -556,16 +532,10 @@ mod tests {
open_unchecked(&client_connection, script_foo.clone());
open_unchecked(&client_connection, script_bar.clone());
let resp = send_workspace_symbol_request(&client_connection, "foo".to_string());
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_eq!(
result,
result_from_message(resp),
serde_json::json!([
{
"name": "def_foo",

View File

@ -846,7 +846,7 @@ mod tests {
serde_json::json!({
"contents": {
"kind": "markdown",
"value": "\n-----\n### Usage \n```nu\n foo str {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n"
"value": "\n---\n### Usage \n```nu\n foo str {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n"
}
}),
),

View File

@ -68,6 +68,13 @@ impl PathMember {
} => *optional = true,
}
}
pub fn span(&self) -> Span {
match self {
PathMember::String { span, .. } => *span,
PathMember::Int { span, .. } => *span,
}
}
}
impl PartialEq for PathMember {

5
tests/fixtures/lsp/hover/cell_path.nu vendored Normal file
View File

@ -0,0 +1,5 @@
const r = {
foo: [1 {bar : 2}]
}
$r.foo.1.bar