mirror of
https://github.com/nushell/nushell.git
synced 2025-06-03 00:26:26 +02:00
fix(lsp): several edge cases of inaccurate references
This commit is contained in:
parent
15146e68ad
commit
dc8b50a21e
@ -7,15 +7,20 @@ use nu_protocol::{
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Adjust span if quoted
|
/// Adjust span if quoted
|
||||||
fn strip_quotes(span: Span, working_set: &StateWorkingSet) -> Span {
|
fn strip_quotes(span: Span, working_set: &StateWorkingSet) -> (Vec<u8>, Span) {
|
||||||
let text = String::from_utf8_lossy(working_set.get_span_contents(span));
|
let text = working_set.get_span_contents(span);
|
||||||
if text.len() > 1
|
if text.len() > 1
|
||||||
&& ((text.starts_with('"') && text.ends_with('"'))
|
&& ((text.starts_with(b"\"") && text.ends_with(b"\""))
|
||||||
|| (text.starts_with('\'') && text.ends_with('\'')))
|
|| (text.starts_with(b"'") && text.ends_with(b"'")))
|
||||||
{
|
{
|
||||||
Span::new(span.start.saturating_add(1), span.end.saturating_sub(1))
|
(
|
||||||
|
text.get(1..text.len() - 1)
|
||||||
|
.expect("Invalid quoted span!")
|
||||||
|
.to_vec(),
|
||||||
|
Span::new(span.start.saturating_add(1), span.end.saturating_sub(1)),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
span
|
(text.to_vec(), span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,9 +75,8 @@ fn try_find_id_in_def(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let span = strip_quotes(span?, working_set);
|
let (name, span) = strip_quotes(span?, working_set);
|
||||||
let name = working_set.get_span_contents(span);
|
let decl_id = Id::Declaration(working_set.find_decl(&name).or_else(|| {
|
||||||
let decl_id = Id::Declaration(working_set.find_decl(name).or_else(|| {
|
|
||||||
// for defs inside def
|
// for defs inside def
|
||||||
// TODO: get scope by position
|
// TODO: get scope by position
|
||||||
// https://github.com/nushell/nushell/issues/15291
|
// https://github.com/nushell/nushell/issues/15291
|
||||||
@ -114,8 +118,8 @@ fn try_find_id_in_mod(
|
|||||||
Argument::Positional(expr) => {
|
Argument::Positional(expr) => {
|
||||||
let name = expr.as_string()?;
|
let name = expr.as_string()?;
|
||||||
let module_id = working_set.find_module(name.as_bytes())?;
|
let module_id = working_set.find_module(name.as_bytes())?;
|
||||||
let found_id = Id::Module(module_id);
|
let found_id = Id::Module(module_id, name.as_bytes().to_vec());
|
||||||
let found_span = strip_quotes(arg.span(), working_set);
|
let found_span = strip_quotes(arg.span(), working_set).1;
|
||||||
id_ref
|
id_ref
|
||||||
.is_none_or(|id_r| found_id == *id_r)
|
.is_none_or(|id_r| found_id == *id_r)
|
||||||
.then_some((found_id, found_span))
|
.then_some((found_id, found_span))
|
||||||
@ -160,14 +164,16 @@ fn try_find_id_in_use(
|
|||||||
(*decl_id == *decl_id_ref).then_some(Id::Declaration(*decl_id))
|
(*decl_id == *decl_id_ref).then_some(Id::Declaration(*decl_id))
|
||||||
}),
|
}),
|
||||||
// this is only for argument `members`
|
// this is only for argument `members`
|
||||||
Some(Id::Module(module_id_ref)) => module.submodules.get(name).and_then(|module_id| {
|
Some(Id::Module(module_id_ref, name_ref)) => {
|
||||||
(*module_id == *module_id_ref).then_some(Id::Module(*module_id))
|
module.submodules.get(name).and_then(|module_id| {
|
||||||
}),
|
(*module_id == *module_id_ref && name_ref == name)
|
||||||
|
.then_some(Id::Module(*module_id, name.to_vec()))
|
||||||
|
})
|
||||||
|
}
|
||||||
None => module
|
None => module
|
||||||
.submodules
|
.submodules
|
||||||
.get(name)
|
.get(name)
|
||||||
.cloned()
|
.map(|id| Id::Module(*id, name.to_vec()))
|
||||||
.map(Id::Module)
|
|
||||||
.or(module.decls.get(name).cloned().map(Id::Declaration))
|
.or(module.decls.get(name).cloned().map(Id::Declaration))
|
||||||
.or(module.constants.get(name).cloned().map(Id::Variable)),
|
.or(module.constants.get(name).cloned().map(Id::Variable)),
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -178,16 +184,17 @@ fn try_find_id_in_use(
|
|||||||
// Get module id if required
|
// Get module id if required
|
||||||
let module_name = call.arguments.first()?;
|
let module_name = call.arguments.first()?;
|
||||||
let span = module_name.span();
|
let span = module_name.span();
|
||||||
if let Some(Id::Module(id_ref)) = id {
|
let (span_content, clean_span) = strip_quotes(span, working_set);
|
||||||
|
if let Some(Id::Module(id_ref, name_ref)) = id {
|
||||||
// still need to check the rest, if id not matched
|
// still need to check the rest, if id not matched
|
||||||
if module_id == *id_ref {
|
if module_id == *id_ref && *name_ref == span_content {
|
||||||
return Some((Id::Module(module_id), strip_quotes(span, working_set)));
|
return Some((Id::Module(module_id, span_content.to_vec()), clean_span));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(pos) = location {
|
if let Some(pos) = location {
|
||||||
// first argument of `use`/`hide` should always be module name
|
// first argument of `use`/`hide` should always be module name
|
||||||
if span.contains(*pos) {
|
if span.contains(*pos) {
|
||||||
return Some((Id::Module(module_id), strip_quotes(span, working_set)));
|
return Some((Id::Module(module_id, span_content.to_vec()), clean_span));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,14 +207,13 @@ fn try_find_id_in_use(
|
|||||||
let name = e.as_string()?;
|
let name = e.as_string()?;
|
||||||
Some((
|
Some((
|
||||||
find_by_name(name.as_bytes())?,
|
find_by_name(name.as_bytes())?,
|
||||||
strip_quotes(item_expr.span, working_set),
|
strip_quotes(item_expr.span, working_set).1,
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
let arguments = call.arguments.get(1..)?;
|
for arg in call.arguments.get(1..)?.iter().rev() {
|
||||||
for arg in arguments {
|
|
||||||
let Argument::Positional(expr) = arg else {
|
let Argument::Positional(expr) = arg else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@ -216,7 +222,7 @@ fn try_find_id_in_use(
|
|||||||
}
|
}
|
||||||
let matched = match &expr.expr {
|
let matched = match &expr.expr {
|
||||||
Expr::String(name) => {
|
Expr::String(name) => {
|
||||||
find_by_name(name.as_bytes()).map(|id| (id, strip_quotes(expr.span, working_set)))
|
find_by_name(name.as_bytes()).map(|id| (id, strip_quotes(expr.span, working_set).1))
|
||||||
}
|
}
|
||||||
Expr::List(items) => search_in_list_items(items),
|
Expr::List(items) => search_in_list_items(items),
|
||||||
Expr::FullCellPath(fcp) => {
|
Expr::FullCellPath(fcp) => {
|
||||||
@ -248,7 +254,7 @@ fn try_find_id_in_overlay(
|
|||||||
id: Option<&Id>,
|
id: Option<&Id>,
|
||||||
) -> Option<(Id, Span)> {
|
) -> Option<(Id, Span)> {
|
||||||
let check_location = |span: &Span| location.is_none_or(|pos| span.contains(*pos));
|
let check_location = |span: &Span| location.is_none_or(|pos| span.contains(*pos));
|
||||||
let module_from_parser_info = |span: Span| {
|
let module_from_parser_info = |span: Span, name: &str| {
|
||||||
let Expression {
|
let Expression {
|
||||||
expr: Expr::Overlay(Some(module_id)),
|
expr: Expr::Overlay(Some(module_id)),
|
||||||
..
|
..
|
||||||
@ -256,18 +262,22 @@ fn try_find_id_in_overlay(
|
|||||||
else {
|
else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let found_id = Id::Module(*module_id);
|
let found_id = Id::Module(*module_id, name.as_bytes().to_vec());
|
||||||
id.is_none_or(|id_r| found_id == *id_r)
|
id.is_none_or(|id_r| found_id == *id_r)
|
||||||
.then_some((found_id, strip_quotes(span, working_set)))
|
.then_some((found_id, strip_quotes(span, working_set).1))
|
||||||
};
|
};
|
||||||
// NOTE: `overlay_expr` doesn't work for `overlay hide`
|
// NOTE: `overlay_expr` doesn't exist for `overlay hide`
|
||||||
let module_from_overlay_name = |name: &str, span: Span| {
|
let module_from_overlay_name = |name: &str, span: Span| {
|
||||||
let found_id = Id::Module(working_set.find_overlay(name.as_bytes())?.origin);
|
let found_id = Id::Module(
|
||||||
|
working_set.find_overlay(name.as_bytes())?.origin,
|
||||||
|
name.as_bytes().to_vec(),
|
||||||
|
);
|
||||||
id.is_none_or(|id_r| found_id == *id_r)
|
id.is_none_or(|id_r| found_id == *id_r)
|
||||||
.then_some((found_id, strip_quotes(span, working_set)))
|
.then_some((found_id, strip_quotes(span, working_set).1))
|
||||||
};
|
};
|
||||||
|
|
||||||
for arg in call.arguments.iter() {
|
// check `as alias` first
|
||||||
|
for arg in call.arguments.iter().rev() {
|
||||||
let Argument::Positional(expr) = arg else {
|
let Argument::Positional(expr) = arg else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@ -275,11 +285,11 @@ fn try_find_id_in_overlay(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let matched = match &expr.expr {
|
let matched = match &expr.expr {
|
||||||
Expr::String(name) => module_from_parser_info(expr.span)
|
Expr::String(name) => module_from_parser_info(expr.span, name)
|
||||||
.or_else(|| module_from_overlay_name(name, expr.span)),
|
.or_else(|| module_from_overlay_name(name, expr.span)),
|
||||||
// keyword 'as'
|
// keyword 'as'
|
||||||
Expr::Keyword(kwd) => match &kwd.expr.expr {
|
Expr::Keyword(kwd) => match &kwd.expr.expr {
|
||||||
Expr::String(name) => module_from_parser_info(kwd.expr.span)
|
Expr::String(name) => module_from_parser_info(kwd.expr.span, name)
|
||||||
.or_else(|| module_from_overlay_name(name, kwd.expr.span)),
|
.or_else(|| module_from_overlay_name(name, kwd.expr.span)),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
@ -349,7 +359,9 @@ fn find_id_in_expr(
|
|||||||
FindMapResult::Found((Id::CellPath(var_id, tail), span))
|
FindMapResult::Found((Id::CellPath(var_id, tail), span))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Overlay(Some(module_id)) => FindMapResult::Found((Id::Module(*module_id), span)),
|
Expr::Overlay(Some(module_id)) => {
|
||||||
|
FindMapResult::Found((Id::Module(*module_id, vec![]), span))
|
||||||
|
}
|
||||||
// terminal value expressions
|
// terminal value expressions
|
||||||
Expr::Bool(_)
|
Expr::Bool(_)
|
||||||
| Expr::Binary(_)
|
| Expr::Binary(_)
|
||||||
|
@ -55,7 +55,7 @@ impl LanguageServer {
|
|||||||
let var = working_set.get_variable(*var_id);
|
let var = working_set.get_variable(*var_id);
|
||||||
Some(var.declaration_span)
|
Some(var.declaration_span)
|
||||||
}
|
}
|
||||||
Id::Module(module_id) => {
|
Id::Module(module_id, _) => {
|
||||||
let module = working_set.get_module(*module_id);
|
let module = working_set.get_module(*module_id);
|
||||||
module.span
|
module.span
|
||||||
}
|
}
|
||||||
|
@ -178,7 +178,7 @@ impl LanguageServer {
|
|||||||
working_set.get_decl(decl_id),
|
working_set.get_decl(decl_id),
|
||||||
false,
|
false,
|
||||||
)),
|
)),
|
||||||
Id::Module(module_id) => {
|
Id::Module(module_id, _) => {
|
||||||
let description = working_set
|
let description = working_set
|
||||||
.get_module_comments(module_id)?
|
.get_module_comments(module_id)?
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -43,7 +43,7 @@ pub(crate) enum Id {
|
|||||||
Variable(VarId),
|
Variable(VarId),
|
||||||
Declaration(DeclId),
|
Declaration(DeclId),
|
||||||
Value(Type),
|
Value(Type),
|
||||||
Module(ModuleId),
|
Module(ModuleId, Vec<u8>),
|
||||||
CellPath(VarId, Vec<PathMember>),
|
CellPath(VarId, Vec<PathMember>),
|
||||||
External(String),
|
External(String),
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ impl SymbolCache {
|
|||||||
range,
|
range,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Id::Module(module_id) => {
|
Id::Module(module_id, _) => {
|
||||||
let module = working_set.get_module(module_id);
|
let module = working_set.get_module(module_id);
|
||||||
let span = module.span?;
|
let span = module.span?;
|
||||||
if !doc_span.contains(span.start) {
|
if !doc_span.contains(span.start) {
|
||||||
@ -165,7 +165,7 @@ impl SymbolCache {
|
|||||||
.chain((0..working_set.num_modules()).filter_map(|id| {
|
.chain((0..working_set.num_modules()).filter_map(|id| {
|
||||||
Self::get_symbol_by_id(
|
Self::get_symbol_by_id(
|
||||||
working_set,
|
working_set,
|
||||||
Id::Module(ModuleId::new(id)),
|
Id::Module(ModuleId::new(id), vec![]),
|
||||||
doc,
|
doc,
|
||||||
&cached_file.covered_span,
|
&cached_file.covered_span,
|
||||||
)
|
)
|
||||||
|
@ -101,9 +101,14 @@ impl LanguageServer {
|
|||||||
let (id, cursor_span) = find_id(&block, &working_set, &location)?;
|
let (id, cursor_span) = find_id(&block, &working_set, &location)?;
|
||||||
let mut refs = find_reference_by_id(&block, &working_set, &id);
|
let mut refs = find_reference_by_id(&block, &working_set, &id);
|
||||||
let definition_span = Self::find_definition_span_by_id(&working_set, &id);
|
let definition_span = Self::find_definition_span_by_id(&working_set, &id);
|
||||||
if let Some(extra_span) =
|
if let Some(extra_span) = Self::reference_not_in_ast(
|
||||||
Self::reference_not_in_ast(&id, &working_set, definition_span, file_span, cursor_span)
|
&id,
|
||||||
{
|
&String::from_utf8_lossy(working_set.get_span_contents(cursor_span)),
|
||||||
|
&working_set,
|
||||||
|
definition_span,
|
||||||
|
file_span,
|
||||||
|
cursor_span,
|
||||||
|
) {
|
||||||
if !refs.contains(&extra_span) {
|
if !refs.contains(&extra_span) {
|
||||||
refs.push(extra_span);
|
refs.push(extra_span);
|
||||||
}
|
}
|
||||||
@ -333,6 +338,7 @@ impl LanguageServer {
|
|||||||
/// which is not covered in the AST
|
/// which is not covered in the AST
|
||||||
fn reference_not_in_ast(
|
fn reference_not_in_ast(
|
||||||
id: &Id,
|
id: &Id,
|
||||||
|
name_ref: &str,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
definition_span: Option<Span>,
|
definition_span: Option<Span>,
|
||||||
file_span: Span,
|
file_span: Span,
|
||||||
@ -340,14 +346,17 @@ impl LanguageServer {
|
|||||||
) -> Option<Span> {
|
) -> Option<Span> {
|
||||||
if let (Id::Variable(_), Some(decl_span)) = (&id, definition_span) {
|
if let (Id::Variable(_), Some(decl_span)) = (&id, definition_span) {
|
||||||
if file_span.contains_span(decl_span) && decl_span.end > decl_span.start {
|
if file_span.contains_span(decl_span) && decl_span.end > decl_span.start {
|
||||||
let leading_dashes = working_set
|
let content = working_set.get_span_contents(decl_span);
|
||||||
.get_span_contents(decl_span)
|
let leading_dashes = content
|
||||||
.iter()
|
.iter()
|
||||||
// remove leading dashes for flags
|
// remove leading dashes for flags
|
||||||
.take_while(|c| *c == &b'-')
|
.take_while(|c| *c == &b'-')
|
||||||
.count();
|
.count();
|
||||||
let start = decl_span.start + leading_dashes;
|
let start = decl_span.start + leading_dashes;
|
||||||
return Some(Span {
|
return content
|
||||||
|
.get(leading_dashes..)
|
||||||
|
.is_some_and(|name| name.starts_with(name_ref.as_bytes()))
|
||||||
|
.then_some(Span {
|
||||||
start,
|
start,
|
||||||
end: start + sample_span.end - sample_span.start,
|
end: start + sample_span.end - sample_span.start,
|
||||||
});
|
});
|
||||||
@ -436,6 +445,7 @@ impl LanguageServer {
|
|||||||
.unwrap_or(Span::unknown());
|
.unwrap_or(Span::unknown());
|
||||||
if let Some(extra_span) = Self::reference_not_in_ast(
|
if let Some(extra_span) = Self::reference_not_in_ast(
|
||||||
&id_tracker.id,
|
&id_tracker.id,
|
||||||
|
&id_tracker.name,
|
||||||
&working_set,
|
&working_set,
|
||||||
definition_span,
|
definition_span,
|
||||||
file_span,
|
file_span,
|
||||||
@ -635,7 +645,7 @@ mod tests {
|
|||||||
script.push("foo.nu");
|
script.push("foo.nu");
|
||||||
let script = path_to_uri(&script);
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -659,14 +669,16 @@ mod tests {
|
|||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
let message_num = 5;
|
let message_num = 6;
|
||||||
let messages =
|
let messages =
|
||||||
send_reference_request(&client_connection, script.clone(), 0, 12, message_num);
|
send_reference_request(&client_connection, script.clone(), 0, 12, message_num);
|
||||||
assert_eq!(messages.len(), message_num);
|
assert_eq!(messages.len(), message_num);
|
||||||
|
let mut has_response = false;
|
||||||
for message in messages {
|
for message in messages {
|
||||||
match message {
|
match message {
|
||||||
Message::Notification(n) => assert_eq!(n.method, "$/progress"),
|
Message::Notification(n) => assert_eq!(n.method, "$/progress"),
|
||||||
Message::Response(r) => {
|
Message::Response(r) => {
|
||||||
|
has_response = true;
|
||||||
let result = r.result.unwrap();
|
let result = r.result.unwrap();
|
||||||
let array = result.as_array().unwrap();
|
let array = result.as_array().unwrap();
|
||||||
assert!(array.contains(&serde_json::json!(
|
assert!(array.contains(&serde_json::json!(
|
||||||
@ -687,6 +699,7 @@ mod tests {
|
|||||||
_ => panic!("unexpected message type"),
|
_ => panic!("unexpected message type"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
assert!(has_response);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -717,14 +730,16 @@ mod tests {
|
|||||||
// thus changing the cached `StateDelta`
|
// thus changing the cached `StateDelta`
|
||||||
open_unchecked(&client_connection, script_foo);
|
open_unchecked(&client_connection, script_foo);
|
||||||
|
|
||||||
let message_num = 5;
|
let message_num = 6;
|
||||||
let messages =
|
let messages =
|
||||||
send_reference_request(&client_connection, script.clone(), 0, 23, message_num);
|
send_reference_request(&client_connection, script.clone(), 0, 23, message_num);
|
||||||
assert_eq!(messages.len(), message_num);
|
assert_eq!(messages.len(), message_num);
|
||||||
|
let mut has_response = false;
|
||||||
for message in messages {
|
for message in messages {
|
||||||
match message {
|
match message {
|
||||||
Message::Notification(n) => assert_eq!(n.method, "$/progress"),
|
Message::Notification(n) => assert_eq!(n.method, "$/progress"),
|
||||||
Message::Response(r) => {
|
Message::Response(r) => {
|
||||||
|
has_response = true;
|
||||||
let result = r.result.unwrap();
|
let result = r.result.unwrap();
|
||||||
let array = result.as_array().unwrap();
|
let array = result.as_array().unwrap();
|
||||||
assert!(array.contains(&serde_json::json!(
|
assert!(array.contains(&serde_json::json!(
|
||||||
@ -745,6 +760,61 @@ mod tests {
|
|||||||
_ => panic!("unexpected message type"),
|
_ => panic!("unexpected message type"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
assert!(has_response);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_path_reference_in_workspace() {
|
||||||
|
let mut script = fixtures();
|
||||||
|
script.push("lsp");
|
||||||
|
script.push("workspace");
|
||||||
|
let (client_connection, _recv) = initialize_language_server(
|
||||||
|
None,
|
||||||
|
serde_json::to_value(InitializeParams {
|
||||||
|
workspace_folders: Some(vec![WorkspaceFolder {
|
||||||
|
uri: path_to_uri(&script),
|
||||||
|
name: "random name".to_string(),
|
||||||
|
}]),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.ok(),
|
||||||
|
);
|
||||||
|
script.push("baz.nu");
|
||||||
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
|
let message_num = 6;
|
||||||
|
let messages =
|
||||||
|
send_reference_request(&client_connection, script.clone(), 0, 12, message_num);
|
||||||
|
assert_eq!(messages.len(), message_num);
|
||||||
|
let mut has_response = false;
|
||||||
|
for message in messages {
|
||||||
|
match message {
|
||||||
|
Message::Notification(n) => assert_eq!(n.method, "$/progress"),
|
||||||
|
Message::Response(r) => {
|
||||||
|
has_response = true;
|
||||||
|
let result = r.result.unwrap();
|
||||||
|
let array = result.as_array().unwrap();
|
||||||
|
assert!(array.contains(&serde_json::json!(
|
||||||
|
{
|
||||||
|
"uri": script.to_string().replace("baz", "bar"),
|
||||||
|
"range": { "start": { "line": 0, "character": 4 }, "end": { "line": 0, "character": 12 } }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
));
|
||||||
|
assert!(array.contains(&serde_json::json!(
|
||||||
|
{
|
||||||
|
"uri": script.to_string(),
|
||||||
|
"range": { "start": { "line": 6, "character": 4 }, "end": { "line": 6, "character": 12 } }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => panic!("unexpected message type"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(has_response);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -768,7 +838,7 @@ mod tests {
|
|||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
let message_num = 5;
|
let message_num = 6;
|
||||||
let messages = send_rename_prepare_request(
|
let messages = send_rename_prepare_request(
|
||||||
&client_connection,
|
&client_connection,
|
||||||
script.clone(),
|
script.clone(),
|
||||||
@ -778,19 +848,24 @@ mod tests {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
assert_eq!(messages.len(), message_num);
|
assert_eq!(messages.len(), message_num);
|
||||||
|
let mut has_response = false;
|
||||||
for message in messages {
|
for message in messages {
|
||||||
match message {
|
match message {
|
||||||
Message::Notification(n) => assert_eq!(n.method, "$/progress"),
|
Message::Notification(n) => assert_eq!(n.method, "$/progress"),
|
||||||
Message::Response(r) => assert_json_eq!(
|
Message::Response(r) => {
|
||||||
|
has_response = true;
|
||||||
|
assert_json_eq!(
|
||||||
r.result,
|
r.result,
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"start": { "line": 6, "character": 13 },
|
"start": { "line": 6, "character": 13 },
|
||||||
"end": { "line": 6, "character": 20 }
|
"end": { "line": 6, "character": 20 }
|
||||||
}),
|
}),
|
||||||
),
|
)
|
||||||
|
}
|
||||||
_ => panic!("unexpected message type"),
|
_ => panic!("unexpected message type"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
assert!(has_response);
|
||||||
|
|
||||||
if let Message::Response(r) = send_rename_request(&client_connection, script.clone(), 6, 11)
|
if let Message::Response(r) = send_rename_request(&client_connection, script.clone(), 6, 11)
|
||||||
{
|
{
|
||||||
@ -817,7 +892,7 @@ mod tests {
|
|||||||
assert!(
|
assert!(
|
||||||
changs_bar.contains(
|
changs_bar.contains(
|
||||||
&serde_json::json!({
|
&serde_json::json!({
|
||||||
"range": { "start": { "line": 0, "character": 20 }, "end": { "line": 0, "character": 27 } },
|
"range": { "start": { "line": 0, "character": 22 }, "end": { "line": 0, "character": 29 } },
|
||||||
"newText": "new"
|
"newText": "new"
|
||||||
})
|
})
|
||||||
));
|
));
|
||||||
@ -847,7 +922,7 @@ mod tests {
|
|||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
let message_num = 5;
|
let message_num = 6;
|
||||||
let messages = send_rename_prepare_request(
|
let messages = send_rename_prepare_request(
|
||||||
&client_connection,
|
&client_connection,
|
||||||
script.clone(),
|
script.clone(),
|
||||||
@ -857,19 +932,24 @@ mod tests {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
assert_eq!(messages.len(), message_num);
|
assert_eq!(messages.len(), message_num);
|
||||||
|
let mut has_response = false;
|
||||||
for message in messages {
|
for message in messages {
|
||||||
match message {
|
match message {
|
||||||
Message::Notification(n) => assert_eq!(n.method, "$/progress"),
|
Message::Notification(n) => assert_eq!(n.method, "$/progress"),
|
||||||
Message::Response(r) => assert_json_eq!(
|
Message::Response(r) => {
|
||||||
|
has_response = true;
|
||||||
|
assert_json_eq!(
|
||||||
r.result,
|
r.result,
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"start": { "line": 3, "character": 3 },
|
"start": { "line": 3, "character": 3 },
|
||||||
"end": { "line": 3, "character": 8 }
|
"end": { "line": 3, "character": 8 }
|
||||||
}),
|
}),
|
||||||
),
|
)
|
||||||
|
}
|
||||||
_ => panic!("unexpected message type"),
|
_ => panic!("unexpected message type"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
assert!(has_response);
|
||||||
|
|
||||||
if let Message::Response(r) = send_rename_request(&client_connection, script.clone(), 3, 5)
|
if let Message::Response(r) = send_rename_request(&client_connection, script.clone(), 3, 5)
|
||||||
{
|
{
|
||||||
@ -934,11 +1014,14 @@ mod tests {
|
|||||||
} else {
|
} else {
|
||||||
panic!("Progress not cancelled");
|
panic!("Progress not cancelled");
|
||||||
};
|
};
|
||||||
|
let mut has_response = false;
|
||||||
for message in messages {
|
for message in messages {
|
||||||
match message {
|
match message {
|
||||||
Message::Notification(n) => assert_eq!(n.method, "$/progress"),
|
Message::Notification(n) => assert_eq!(n.method, "$/progress"),
|
||||||
// the response of the preempting hover request
|
// the response of the preempting hover request
|
||||||
Message::Response(r) => assert_json_eq!(
|
Message::Response(r) => {
|
||||||
|
has_response = true;
|
||||||
|
assert_json_eq!(
|
||||||
r.result,
|
r.result,
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"contents": {
|
"contents": {
|
||||||
@ -946,13 +1029,14 @@ mod tests {
|
|||||||
"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"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
),
|
)
|
||||||
|
}
|
||||||
_ => panic!("unexpected message type"),
|
_ => panic!("unexpected message type"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
assert!(has_response);
|
||||||
|
|
||||||
if let Message::Response(r) = send_rename_request(&client_connection, script.clone(), 6, 11)
|
if let Message::Response(r) = send_rename_request(&client_connection, script, 6, 11) {
|
||||||
{
|
|
||||||
// should not return any changes
|
// should not return any changes
|
||||||
assert_json_eq!(r.result.unwrap()["changes"], serde_json::json!({}));
|
assert_json_eq!(r.result.unwrap()["changes"], serde_json::json!({}));
|
||||||
} else {
|
} else {
|
||||||
@ -998,7 +1082,7 @@ mod tests {
|
|||||||
let (client_connection, _recv) = initialize_language_server(None, None);
|
let (client_connection, _recv) = initialize_language_server(None, None);
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
let message = send_document_highlight_request(&client_connection, script.clone(), 3, 5);
|
let message = send_document_highlight_request(&client_connection, script, 3, 5);
|
||||||
let Message::Response(r) = message else {
|
let Message::Response(r) = message else {
|
||||||
panic!("unexpected message type");
|
panic!("unexpected message type");
|
||||||
};
|
};
|
||||||
@ -1010,4 +1094,52 @@ mod tests {
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn document_highlight_module_alias() {
|
||||||
|
let mut script = fixtures();
|
||||||
|
script.push("lsp");
|
||||||
|
script.push("workspace");
|
||||||
|
script.push("baz.nu");
|
||||||
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
|
let (client_connection, _recv) = initialize_language_server(None, None);
|
||||||
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
|
let message = send_document_highlight_request(&client_connection, script, 4, 17);
|
||||||
|
let Message::Response(r) = message else {
|
||||||
|
panic!("unexpected message type");
|
||||||
|
};
|
||||||
|
assert_json_eq!(
|
||||||
|
r.result,
|
||||||
|
serde_json::json!([
|
||||||
|
{ "range": { "start": { "line": 0, "character": 24 }, "end": { "line": 0, "character": 30 } }, "kind": 1 },
|
||||||
|
{ "range": { "start": { "line": 4, "character": 13 }, "end": { "line": 4, "character": 19 } }, "kind": 1 }
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TODO: associate the module record with the submodule name in `use`?
|
||||||
|
#[test]
|
||||||
|
fn document_highlight_module_record() {
|
||||||
|
let mut script = fixtures();
|
||||||
|
script.push("lsp");
|
||||||
|
script.push("workspace");
|
||||||
|
script.push("baz.nu");
|
||||||
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
|
let (client_connection, _recv) = initialize_language_server(None, None);
|
||||||
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
|
let message = send_document_highlight_request(&client_connection, script, 8, 0);
|
||||||
|
let Message::Response(r) = message else {
|
||||||
|
panic!("unexpected message type");
|
||||||
|
};
|
||||||
|
assert_json_eq!(
|
||||||
|
r.result,
|
||||||
|
serde_json::json!([
|
||||||
|
{ "range": { "start": { "line": 8, "character": 1 }, "end": { "line": 8, "character": 8 } }, "kind": 1 },
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
2
tests/fixtures/lsp/workspace/bar.nu
vendored
2
tests/fixtures/lsp/workspace/bar.nu
vendored
@ -1,4 +1,4 @@
|
|||||||
use foo.nu [ foooo "foo str" ]
|
use ./foo.nu [ foooo "foo str" ]
|
||||||
|
|
||||||
export def "bar str" [
|
export def "bar str" [
|
||||||
] {
|
] {
|
||||||
|
10
tests/fixtures/lsp/workspace/baz.nu
vendored
Normal file
10
tests/fixtures/lsp/workspace/baz.nu
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
overlay use ./foo.nu as prefix --prefix
|
||||||
|
alias aname = prefix mod name sub module cmd name
|
||||||
|
aname
|
||||||
|
prefix foo str
|
||||||
|
overlay hide prefix
|
||||||
|
|
||||||
|
use ./foo.nu [ "mod name" cst_mod ]
|
||||||
|
|
||||||
|
$cst_mod."sub module".var_name
|
||||||
|
mod name sub module cmd name
|
12
tests/fixtures/lsp/workspace/foo.nu
vendored
12
tests/fixtures/lsp/workspace/foo.nu
vendored
@ -5,3 +5,15 @@ export def foooo [
|
|||||||
}
|
}
|
||||||
|
|
||||||
export def "foo str" [] { "foo" }
|
export def "foo str" [] { "foo" }
|
||||||
|
|
||||||
|
export module "mod name" {
|
||||||
|
export module "sub module" {
|
||||||
|
export def "cmd name" [] { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export module cst_mod {
|
||||||
|
export module "sub module" {
|
||||||
|
export const var_name = "const value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user