mirror of
https://github.com/nushell/nushell.git
synced 2025-05-30 22:57:07 +02:00
feat(lsp): better completion item documentation (#14905)
# Description This PR adds those markdown doc strings (previously only available via hover) to completion items: <img width="676" alt="image" src="https://github.com/user-attachments/assets/58c44d7d-4b49-4955-b3f0-fa7a727a8bc0" /> It also refactors a bit, primarily to prevent namespace pollution. # User-Facing Changes # Tests + Formatting # After Submitting
This commit is contained in:
parent
fd684a204c
commit
299453ecb7
@ -1,3 +1,4 @@
|
|||||||
|
use crate::Id;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{
|
ast::{
|
||||||
Argument, Block, Call, Expr, Expression, ExternalArgument, ListItem, MatchPattern, Pattern,
|
Argument, Block, Call, Expr, Expression, ExternalArgument, ListItem, MatchPattern, Pattern,
|
||||||
@ -6,9 +7,7 @@ use nu_protocol::{
|
|||||||
engine::StateWorkingSet,
|
engine::StateWorkingSet,
|
||||||
Span,
|
Span,
|
||||||
};
|
};
|
||||||
use std::{path::PathBuf, sync::Arc};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::Id;
|
|
||||||
|
|
||||||
/// similar to flatten_block, but allows extra map function
|
/// similar to flatten_block, but allows extra map function
|
||||||
pub fn ast_flat_map<'a, T, F>(
|
pub fn ast_flat_map<'a, T, F>(
|
||||||
@ -243,7 +242,7 @@ fn try_find_id_in_mod(
|
|||||||
id_ref: Option<&Id>,
|
id_ref: Option<&Id>,
|
||||||
) -> Option<(Id, Span)> {
|
) -> Option<(Id, Span)> {
|
||||||
let call_name = working_set.get_span_contents(call.head);
|
let call_name = working_set.get_span_contents(call.head);
|
||||||
if call_name != "module".as_bytes() && call_name != "export module".as_bytes() {
|
if call_name != b"module" && call_name != b"export module" {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let check_location = |span: &Span| location.map_or(true, |pos| span.contains(*pos));
|
let check_location = |span: &Span| location.map_or(true, |pos| span.contains(*pos));
|
||||||
@ -283,7 +282,7 @@ fn try_find_id_in_use(
|
|||||||
id: Option<&Id>,
|
id: Option<&Id>,
|
||||||
) -> Option<(Id, Span)> {
|
) -> Option<(Id, Span)> {
|
||||||
let call_name = working_set.get_span_contents(call.head);
|
let call_name = working_set.get_span_contents(call.head);
|
||||||
if call_name != "use".as_bytes() {
|
if call_name != b"use" {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let find_by_name = |name: &[u8]| match id {
|
let find_by_name = |name: &[u8]| match id {
|
||||||
@ -307,7 +306,7 @@ fn try_find_id_in_use(
|
|||||||
let get_module_id = |span: Span| {
|
let get_module_id = |span: Span| {
|
||||||
let span = strip_quotes(span, working_set);
|
let span = strip_quotes(span, working_set);
|
||||||
let name = String::from_utf8_lossy(working_set.get_span_contents(span));
|
let name = String::from_utf8_lossy(working_set.get_span_contents(span));
|
||||||
let path = PathBuf::from(name.as_ref());
|
let path = std::path::PathBuf::from(name.as_ref());
|
||||||
let stem = path.file_stem().and_then(|fs| fs.to_str()).unwrap_or(&name);
|
let stem = path.file_stem().and_then(|fs| fs.to_str()).unwrap_or(&name);
|
||||||
let found_id = Id::Module(working_set.find_module(stem.as_bytes())?);
|
let found_id = Id::Module(working_set.find_module(stem.as_bytes())?);
|
||||||
id.map_or(true, |id_r| found_id == *id_r)
|
id.map_or(true, |id_r| found_id == *id_r)
|
||||||
@ -420,7 +419,7 @@ fn find_id_in_expr(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// find the leaf node at the given location from ast
|
/// find the leaf node at the given location from ast
|
||||||
pub fn find_id(
|
pub(crate) fn find_id(
|
||||||
ast: &Arc<Block>,
|
ast: &Arc<Block>,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
location: &usize,
|
location: &usize,
|
||||||
@ -452,11 +451,9 @@ fn find_reference_by_id_in_expr(
|
|||||||
.filter_map(|arg| arg.expr())
|
.filter_map(|arg| arg.expr())
|
||||||
.flat_map(recur)
|
.flat_map(recur)
|
||||||
.collect();
|
.collect();
|
||||||
if let Id::Declaration(decl_id) = id {
|
if matches!(id, Id::Declaration(decl_id) if call.decl_id == *decl_id) {
|
||||||
if *decl_id == call.decl_id {
|
occurs.push(call.head);
|
||||||
occurs.push(call.head);
|
return Some(occurs);
|
||||||
return Some(occurs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let Some((_, span_found)) = try_find_id_in_def(call, working_set, None, Some(id))
|
if let Some((_, span_found)) = try_find_id_in_def(call, working_set, None, Some(id))
|
||||||
.or(try_find_id_in_mod(call, working_set, None, Some(id)))
|
.or(try_find_id_in_mod(call, working_set, None, Some(id)))
|
||||||
@ -470,7 +467,11 @@ fn find_reference_by_id_in_expr(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_reference_by_id(ast: &Arc<Block>, working_set: &StateWorkingSet, id: &Id) -> Vec<Span> {
|
pub(crate) fn find_reference_by_id(
|
||||||
|
ast: &Arc<Block>,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
id: &Id,
|
||||||
|
) -> Vec<Span> {
|
||||||
ast_flat_map(ast, working_set, &|e| {
|
ast_flat_map(ast, working_set, &|e| {
|
||||||
find_reference_by_id_in_expr(e, working_set, id)
|
find_reference_by_id_in_expr(e, working_set, id)
|
||||||
})
|
})
|
||||||
|
@ -49,11 +49,10 @@ impl LanguageServer {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use assert_json_diff::assert_json_eq;
|
|
||||||
use nu_test_support::fs::fixtures;
|
|
||||||
|
|
||||||
use crate::path_to_uri;
|
use crate::path_to_uri;
|
||||||
use crate::tests::{initialize_language_server, open_unchecked, update};
|
use crate::tests::{initialize_language_server, open_unchecked, update};
|
||||||
|
use assert_json_diff::assert_json_eq;
|
||||||
|
use nu_test_support::fs::fixtures;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn publish_diagnostics_variable_does_not_exists() {
|
fn publish_diagnostics_variable_does_not_exists() {
|
||||||
|
@ -4,7 +4,10 @@ use nu_protocol::engine::StateWorkingSet;
|
|||||||
use nu_protocol::Span;
|
use nu_protocol::Span;
|
||||||
|
|
||||||
impl LanguageServer {
|
impl LanguageServer {
|
||||||
pub fn find_definition_span_by_id(working_set: &StateWorkingSet, id: &Id) -> Option<Span> {
|
pub(crate) fn find_definition_span_by_id(
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
id: &Id,
|
||||||
|
) -> Option<Span> {
|
||||||
match id {
|
match id {
|
||||||
Id::Declaration(decl_id) => {
|
Id::Declaration(decl_id) => {
|
||||||
let block_id = working_set.get_decl(*decl_id).block_id()?;
|
let block_id = working_set.get_decl(*decl_id).block_id()?;
|
||||||
@ -22,7 +25,7 @@ impl LanguageServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn goto_definition(
|
pub(crate) fn goto_definition(
|
||||||
&mut self,
|
&mut self,
|
||||||
params: &GotoDefinitionParams,
|
params: &GotoDefinitionParams,
|
||||||
) -> Option<GotoDefinitionResponse> {
|
) -> Option<GotoDefinitionResponse> {
|
||||||
@ -54,8 +57,8 @@ mod tests {
|
|||||||
use crate::tests::{initialize_language_server, open_unchecked};
|
use crate::tests::{initialize_language_server, open_unchecked};
|
||||||
use assert_json_diff::assert_json_eq;
|
use assert_json_diff::assert_json_eq;
|
||||||
use lsp_server::{Connection, Message};
|
use lsp_server::{Connection, Message};
|
||||||
use lsp_types::request::{GotoDefinition, Request};
|
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
|
request::{GotoDefinition, Request},
|
||||||
GotoDefinitionParams, PartialResultParams, Position, TextDocumentIdentifier,
|
GotoDefinitionParams, PartialResultParams, Position, TextDocumentIdentifier,
|
||||||
TextDocumentPositionParams, Uri, WorkDoneProgressParams,
|
TextDocumentPositionParams, Uri, WorkDoneProgressParams,
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::ast::{ast_flat_map, expr_flat_map};
|
use crate::ast::{ast_flat_map, expr_flat_map};
|
||||||
use crate::{span_to_range, LanguageServer};
|
use crate::{span_to_range, LanguageServer};
|
||||||
use lsp_textdocument::FullTextDocument;
|
use lsp_textdocument::FullTextDocument;
|
||||||
@ -12,6 +10,7 @@ use nu_protocol::{
|
|||||||
engine::StateWorkingSet,
|
engine::StateWorkingSet,
|
||||||
Type,
|
Type,
|
||||||
};
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
fn type_short_name(t: &Type) -> String {
|
fn type_short_name(t: &Type) -> String {
|
||||||
match t {
|
match t {
|
||||||
@ -145,12 +144,11 @@ fn extract_inlay_hints_from_expression(
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageServer {
|
impl LanguageServer {
|
||||||
pub fn get_inlay_hints(&mut self, params: &InlayHintParams) -> Option<Vec<InlayHint>> {
|
pub(crate) fn get_inlay_hints(&mut self, params: &InlayHintParams) -> Option<Vec<InlayHint>> {
|
||||||
Some(self.inlay_hints.get(¶ms.text_document.uri)?.clone())
|
Some(self.inlay_hints.get(¶ms.text_document.uri)?.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_inlay_hints(
|
pub(crate) fn extract_inlay_hints(
|
||||||
&self,
|
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
block: &Arc<Block>,
|
block: &Arc<Block>,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
@ -164,17 +162,15 @@ impl LanguageServer {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use assert_json_diff::assert_json_eq;
|
|
||||||
use lsp_types::request::Request;
|
|
||||||
use nu_test_support::fs::fixtures;
|
|
||||||
|
|
||||||
use crate::path_to_uri;
|
use crate::path_to_uri;
|
||||||
use crate::tests::{initialize_language_server, open_unchecked};
|
use crate::tests::{initialize_language_server, open_unchecked};
|
||||||
|
use assert_json_diff::assert_json_eq;
|
||||||
use lsp_server::{Connection, Message};
|
use lsp_server::{Connection, Message};
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
request::InlayHintRequest, InlayHintParams, Position, Range, TextDocumentIdentifier, Uri,
|
request::{InlayHintRequest, Request},
|
||||||
WorkDoneProgressParams,
|
InlayHintParams, Position, Range, TextDocumentIdentifier, Uri, WorkDoneProgressParams,
|
||||||
};
|
};
|
||||||
|
use nu_test_support::fs::fixtures;
|
||||||
|
|
||||||
fn send_inlay_hint_request(client_connection: &Connection, uri: Uri) -> Message {
|
fn send_inlay_hint_request(client_connection: &Connection, uri: Uri) -> Message {
|
||||||
client_connection
|
client_connection
|
||||||
|
@ -1,33 +1,31 @@
|
|||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
use ast::find_id;
|
|
||||||
use crossbeam_channel::{Receiver, Sender};
|
|
||||||
use lsp_server::{Connection, IoThreads, Message, Response, ResponseError};
|
use lsp_server::{Connection, IoThreads, Message, Response, ResponseError};
|
||||||
use lsp_textdocument::{FullTextDocument, TextDocuments};
|
use lsp_textdocument::{FullTextDocument, TextDocuments};
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
request::{self, Request},
|
request::{self, Request},
|
||||||
CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse, CompletionTextEdit,
|
CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionParams,
|
||||||
Hover, HoverContents, HoverParams, InlayHint, Location, MarkupContent, MarkupKind, OneOf,
|
CompletionResponse, CompletionTextEdit, Documentation, Hover, HoverContents, HoverParams,
|
||||||
Position, Range, ReferencesOptions, RenameOptions, ServerCapabilities, TextDocumentSyncKind,
|
InlayHint, Location, MarkupContent, MarkupKind, OneOf, Position, Range, ReferencesOptions,
|
||||||
TextEdit, Uri, WorkDoneProgressOptions, WorkspaceFolder, WorkspaceFoldersServerCapabilities,
|
RenameOptions, ServerCapabilities, TextDocumentSyncKind, TextEdit, Uri,
|
||||||
|
WorkDoneProgressOptions, WorkspaceFolder, WorkspaceFoldersServerCapabilities,
|
||||||
WorkspaceServerCapabilities,
|
WorkspaceServerCapabilities,
|
||||||
};
|
};
|
||||||
use miette::{miette, IntoDiagnostic, Result};
|
use miette::{miette, IntoDiagnostic, Result};
|
||||||
use nu_cli::{NuCompleter, SuggestionKind};
|
use nu_cli::{NuCompleter, SuggestionKind};
|
||||||
use nu_parser::parse;
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Block,
|
ast::Block,
|
||||||
engine::{CachedFile, EngineState, Stack, StateDelta, StateWorkingSet},
|
engine::{CachedFile, Command, EngineState, Stack, StateDelta, StateWorkingSet},
|
||||||
DeclId, ModuleId, Span, Type, Value, VarId,
|
DeclId, ModuleId, Span, Type, VarId,
|
||||||
};
|
};
|
||||||
use std::{collections::BTreeMap, sync::Mutex};
|
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
sync::Mutex,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use symbols::SymbolCache;
|
use symbols::SymbolCache;
|
||||||
use url::Url;
|
|
||||||
use workspace::{InternalMessage, RangePerDoc};
|
use workspace::{InternalMessage, RangePerDoc};
|
||||||
|
|
||||||
mod ast;
|
mod ast;
|
||||||
@ -39,7 +37,7 @@ mod symbols;
|
|||||||
mod workspace;
|
mod workspace;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum Id {
|
pub(crate) enum Id {
|
||||||
Variable(VarId),
|
Variable(VarId),
|
||||||
Declaration(DeclId),
|
Declaration(DeclId),
|
||||||
Value(Type),
|
Value(Type),
|
||||||
@ -56,30 +54,33 @@ pub struct LanguageServer {
|
|||||||
workspace_folders: BTreeMap<String, WorkspaceFolder>,
|
workspace_folders: BTreeMap<String, WorkspaceFolder>,
|
||||||
/// for workspace wide requests
|
/// for workspace wide requests
|
||||||
occurrences: BTreeMap<Uri, Vec<Range>>,
|
occurrences: BTreeMap<Uri, Vec<Range>>,
|
||||||
channels: Option<(Sender<bool>, Arc<Receiver<InternalMessage>>)>,
|
channels: Option<(
|
||||||
|
crossbeam_channel::Sender<bool>,
|
||||||
|
Arc<crossbeam_channel::Receiver<InternalMessage>>,
|
||||||
|
)>,
|
||||||
/// set to true when text changes
|
/// set to true when text changes
|
||||||
need_parse: bool,
|
need_parse: bool,
|
||||||
/// cache `StateDelta` to avoid repeated parsing
|
/// cache `StateDelta` to avoid repeated parsing
|
||||||
cached_state_delta: Option<StateDelta>,
|
cached_state_delta: Option<StateDelta>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn path_to_uri(path: impl AsRef<Path>) -> Uri {
|
pub(crate) fn path_to_uri(path: impl AsRef<Path>) -> Uri {
|
||||||
Uri::from_str(
|
Uri::from_str(
|
||||||
Url::from_file_path(path)
|
url::Url::from_file_path(path)
|
||||||
.expect("Failed to convert path to Url")
|
.expect("Failed to convert path to Url")
|
||||||
.as_str(),
|
.as_str(),
|
||||||
)
|
)
|
||||||
.expect("Failed to convert Url to lsp_types::Uri.")
|
.expect("Failed to convert Url to lsp_types::Uri.")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uri_to_path(uri: &Uri) -> PathBuf {
|
pub(crate) fn uri_to_path(uri: &Uri) -> PathBuf {
|
||||||
Url::from_str(uri.as_str())
|
url::Url::from_str(uri.as_str())
|
||||||
.expect("Failed to convert Uri to Url")
|
.expect("Failed to convert Uri to Url")
|
||||||
.to_file_path()
|
.to_file_path()
|
||||||
.expect("Failed to convert Url to path")
|
.expect("Failed to convert Url to path")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn span_to_range(span: &Span, file: &FullTextDocument, offset: usize) -> Range {
|
pub(crate) fn span_to_range(span: &Span, file: &FullTextDocument, offset: usize) -> Range {
|
||||||
let start = file.position_at(span.start.saturating_sub(offset) as u32);
|
let start = file.position_at(span.start.saturating_sub(offset) as u32);
|
||||||
let end = file.position_at(span.end.saturating_sub(offset) as u32);
|
let end = file.position_at(span.end.saturating_sub(offset) as u32);
|
||||||
Range { start, end }
|
Range { start, end }
|
||||||
@ -251,14 +252,14 @@ impl LanguageServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Send a cancel message to a running bg thread
|
/// Send a cancel message to a running bg thread
|
||||||
pub fn cancel_background_thread(&mut self) {
|
pub(crate) fn cancel_background_thread(&mut self) {
|
||||||
if let Some((sender, _)) = &self.channels {
|
if let Some((sender, _)) = &self.channels {
|
||||||
sender.send(true).ok();
|
sender.send(true).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check results from background thread
|
/// Check results from background thread
|
||||||
pub fn handle_internal_messages(&mut self) -> Result<bool> {
|
pub(crate) fn handle_internal_messages(&mut self) -> Result<bool> {
|
||||||
let mut reset = false;
|
let mut reset = false;
|
||||||
if let Some((_, receiver)) = &self.channels {
|
if let Some((_, receiver)) = &self.channels {
|
||||||
for im in receiver.try_iter() {
|
for im in receiver.try_iter() {
|
||||||
@ -286,10 +287,13 @@ impl LanguageServer {
|
|||||||
Ok(reset)
|
Ok(reset)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_engine_state(&self) -> EngineState {
|
pub(crate) fn new_engine_state(&self) -> EngineState {
|
||||||
let mut engine_state = self.initial_engine_state.clone();
|
let mut engine_state = self.initial_engine_state.clone();
|
||||||
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||||
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
|
engine_state.add_env_var(
|
||||||
|
"PWD".into(),
|
||||||
|
nu_protocol::Value::test_string(cwd.to_string_lossy()),
|
||||||
|
);
|
||||||
// merge the cached `StateDelta` if text not changed
|
// merge the cached `StateDelta` if text not changed
|
||||||
if !self.need_parse {
|
if !self.need_parse {
|
||||||
engine_state
|
engine_state
|
||||||
@ -303,7 +307,7 @@ impl LanguageServer {
|
|||||||
engine_state
|
engine_state
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_and_find<'a>(
|
pub(crate) fn parse_and_find<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
engine_state: &'a mut EngineState,
|
engine_state: &'a mut EngineState,
|
||||||
uri: &Uri,
|
uri: &Uri,
|
||||||
@ -321,12 +325,12 @@ impl LanguageServer {
|
|||||||
.get_document(uri)
|
.get_document(uri)
|
||||||
.ok_or_else(|| miette!("\nFailed to get document"))?;
|
.ok_or_else(|| miette!("\nFailed to get document"))?;
|
||||||
let location = file.offset_at(pos) as usize + file_span.start;
|
let location = file.offset_at(pos) as usize + file_span.start;
|
||||||
let (id, span) = find_id(&block, &working_set, &location)
|
let (id, span) = ast::find_id(&block, &working_set, &location)
|
||||||
.ok_or_else(|| miette!("\nFailed to find current name"))?;
|
.ok_or_else(|| miette!("\nFailed to find current name"))?;
|
||||||
Ok((working_set, id, span, file_span.start))
|
Ok((working_set, id, span, file_span.start))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_file<'a>(
|
pub(crate) fn parse_file<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
engine_state: &'a mut EngineState,
|
engine_state: &'a mut EngineState,
|
||||||
uri: &Uri,
|
uri: &Uri,
|
||||||
@ -339,10 +343,11 @@ impl LanguageServer {
|
|||||||
let file_path_str = file_path.to_str()?;
|
let file_path_str = file_path.to_str()?;
|
||||||
let contents = file.get_content(None).as_bytes();
|
let contents = file.get_content(None).as_bytes();
|
||||||
let _ = working_set.files.push(file_path.clone(), Span::unknown());
|
let _ = working_set.files.push(file_path.clone(), Span::unknown());
|
||||||
let block = parse(&mut working_set, Some(file_path_str), contents, false);
|
let block = nu_parser::parse(&mut working_set, Some(file_path_str), contents, false);
|
||||||
let span = working_set.get_span_for_filename(file_path_str)?;
|
let span = working_set.get_span_for_filename(file_path_str)?;
|
||||||
if need_hints {
|
if need_hints {
|
||||||
let file_inlay_hints = self.extract_inlay_hints(&working_set, &block, span.start, file);
|
let file_inlay_hints =
|
||||||
|
Self::extract_inlay_hints(&working_set, &block, span.start, file);
|
||||||
self.inlay_hints.insert(uri.clone(), file_inlay_hints);
|
self.inlay_hints.insert(uri.clone(), file_inlay_hints);
|
||||||
}
|
}
|
||||||
if self.need_parse {
|
if self.need_parse {
|
||||||
@ -418,6 +423,141 @@ impl LanguageServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_decl_description(decl: &dyn Command) -> String {
|
||||||
|
let mut description = String::new();
|
||||||
|
|
||||||
|
// 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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
description.push_str("-----\n### Usage \n```nu\n");
|
||||||
|
let signature = decl.signature();
|
||||||
|
description.push_str(&format!(" {}", signature.name));
|
||||||
|
if !signature.named.is_empty() {
|
||||||
|
description.push_str(" {flags}");
|
||||||
|
}
|
||||||
|
for required_arg in &signature.required_positional {
|
||||||
|
description.push_str(&format!(" <{}>", required_arg.name));
|
||||||
|
}
|
||||||
|
for optional_arg in &signature.optional_positional {
|
||||||
|
description.push_str(&format!(" <{}?>", optional_arg.name));
|
||||||
|
}
|
||||||
|
if let Some(arg) = &signature.rest_positional {
|
||||||
|
description.push_str(&format!(" <...{}>", arg.name));
|
||||||
|
}
|
||||||
|
description.push_str("\n```\n");
|
||||||
|
|
||||||
|
// Flags
|
||||||
|
if !signature.named.is_empty() {
|
||||||
|
description.push_str("\n### Flags\n\n");
|
||||||
|
let mut first = true;
|
||||||
|
for named in &signature.named {
|
||||||
|
if first {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
description.push('\n');
|
||||||
|
}
|
||||||
|
description.push_str(" ");
|
||||||
|
if let Some(short_flag) = &named.short {
|
||||||
|
description.push_str(&format!("`-{short_flag}`"));
|
||||||
|
}
|
||||||
|
if !named.long.is_empty() {
|
||||||
|
if named.short.is_some() {
|
||||||
|
description.push_str(", ");
|
||||||
|
}
|
||||||
|
description.push_str(&format!("`--{}`", named.long));
|
||||||
|
}
|
||||||
|
if let Some(arg) = &named.arg {
|
||||||
|
description.push_str(&format!(" `<{}>`", arg.to_type()));
|
||||||
|
}
|
||||||
|
if !named.desc.is_empty() {
|
||||||
|
description.push_str(&format!(" - {}", named.desc));
|
||||||
|
}
|
||||||
|
description.push('\n');
|
||||||
|
}
|
||||||
|
description.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parameters
|
||||||
|
if !signature.required_positional.is_empty()
|
||||||
|
|| !signature.optional_positional.is_empty()
|
||||||
|
|| signature.rest_positional.is_some()
|
||||||
|
{
|
||||||
|
description.push_str("\n### Parameters\n\n");
|
||||||
|
let mut first = true;
|
||||||
|
for required_arg in &signature.required_positional {
|
||||||
|
if first {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
description.push('\n');
|
||||||
|
}
|
||||||
|
description.push_str(&format!(
|
||||||
|
" `{}: {}`",
|
||||||
|
required_arg.name,
|
||||||
|
required_arg.shape.to_type()
|
||||||
|
));
|
||||||
|
if !required_arg.desc.is_empty() {
|
||||||
|
description.push_str(&format!(" - {}", required_arg.desc));
|
||||||
|
}
|
||||||
|
description.push('\n');
|
||||||
|
}
|
||||||
|
for optional_arg in &signature.optional_positional {
|
||||||
|
if first {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
description.push('\n');
|
||||||
|
}
|
||||||
|
description.push_str(&format!(
|
||||||
|
" `{}: {}`",
|
||||||
|
optional_arg.name,
|
||||||
|
optional_arg.shape.to_type()
|
||||||
|
));
|
||||||
|
if !optional_arg.desc.is_empty() {
|
||||||
|
description.push_str(&format!(" - {}", optional_arg.desc));
|
||||||
|
}
|
||||||
|
description.push('\n');
|
||||||
|
}
|
||||||
|
if let Some(arg) = &signature.rest_positional {
|
||||||
|
if !first {
|
||||||
|
description.push('\n');
|
||||||
|
}
|
||||||
|
description.push_str(&format!(" `...{}: {}`", arg.name, arg.shape.to_type()));
|
||||||
|
if !arg.desc.is_empty() {
|
||||||
|
description.push_str(&format!(" - {}", arg.desc));
|
||||||
|
}
|
||||||
|
description.push('\n');
|
||||||
|
}
|
||||||
|
description.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input/output types
|
||||||
|
if !signature.input_output_types.is_empty() {
|
||||||
|
description.push_str("\n### Input/output types\n");
|
||||||
|
description.push_str("\n```nu\n");
|
||||||
|
for input_output in &signature.input_output_types {
|
||||||
|
description.push_str(&format!(" {} | {}\n", input_output.0, input_output.1));
|
||||||
|
}
|
||||||
|
description.push_str("\n```\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Examples
|
||||||
|
if !decl.examples().is_empty() {
|
||||||
|
description.push_str("### Example(s)\n");
|
||||||
|
for example in decl.examples() {
|
||||||
|
description.push_str(&format!(
|
||||||
|
" {}\n```nu\n {}\n```\n",
|
||||||
|
example.description, example.example
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
description
|
||||||
|
}
|
||||||
|
|
||||||
fn hover(&mut self, params: &HoverParams) -> Option<Hover> {
|
fn hover(&mut self, params: &HoverParams) -> Option<Hover> {
|
||||||
let mut engine_state = self.new_engine_state();
|
let mut engine_state = self.new_engine_state();
|
||||||
|
|
||||||
@ -448,150 +588,20 @@ impl LanguageServer {
|
|||||||
match id {
|
match id {
|
||||||
Id::Variable(var_id) => {
|
Id::Variable(var_id) => {
|
||||||
let var = working_set.get_variable(var_id);
|
let var = working_set.get_variable(var_id);
|
||||||
let contents =
|
let contents = format!(
|
||||||
format!("{} `{}`", if var.mutable { "mutable " } else { "" }, var.ty);
|
"{}{} `{}`",
|
||||||
|
if var.const_val.is_some() {
|
||||||
|
"const "
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
if var.mutable { "mutable " } else { "" },
|
||||||
|
var.ty,
|
||||||
|
);
|
||||||
markdown_hover(contents)
|
markdown_hover(contents)
|
||||||
}
|
}
|
||||||
Id::Declaration(decl_id) => {
|
Id::Declaration(decl_id) => {
|
||||||
let decl = working_set.get_decl(decl_id);
|
markdown_hover(Self::get_decl_description(working_set.get_decl(decl_id)))
|
||||||
|
|
||||||
let mut description = String::new();
|
|
||||||
|
|
||||||
// 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()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Usage
|
|
||||||
description.push_str("-----\n### Usage \n```nu\n");
|
|
||||||
let signature = decl.signature();
|
|
||||||
description.push_str(&format!(" {}", signature.name));
|
|
||||||
if !signature.named.is_empty() {
|
|
||||||
description.push_str(" {flags}");
|
|
||||||
}
|
|
||||||
for required_arg in &signature.required_positional {
|
|
||||||
description.push_str(&format!(" <{}>", required_arg.name));
|
|
||||||
}
|
|
||||||
for optional_arg in &signature.optional_positional {
|
|
||||||
description.push_str(&format!(" <{}?>", optional_arg.name));
|
|
||||||
}
|
|
||||||
if let Some(arg) = &signature.rest_positional {
|
|
||||||
description.push_str(&format!(" <...{}>", arg.name));
|
|
||||||
}
|
|
||||||
description.push_str("\n```\n");
|
|
||||||
|
|
||||||
// Flags
|
|
||||||
if !signature.named.is_empty() {
|
|
||||||
description.push_str("\n### Flags\n\n");
|
|
||||||
let mut first = true;
|
|
||||||
for named in &signature.named {
|
|
||||||
if first {
|
|
||||||
first = false;
|
|
||||||
} else {
|
|
||||||
description.push('\n');
|
|
||||||
}
|
|
||||||
description.push_str(" ");
|
|
||||||
if let Some(short_flag) = &named.short {
|
|
||||||
description.push_str(&format!("`-{short_flag}`"));
|
|
||||||
}
|
|
||||||
if !named.long.is_empty() {
|
|
||||||
if named.short.is_some() {
|
|
||||||
description.push_str(", ");
|
|
||||||
}
|
|
||||||
description.push_str(&format!("`--{}`", named.long));
|
|
||||||
}
|
|
||||||
if let Some(arg) = &named.arg {
|
|
||||||
description.push_str(&format!(" `<{}>`", arg.to_type()));
|
|
||||||
}
|
|
||||||
if !named.desc.is_empty() {
|
|
||||||
description.push_str(&format!(" - {}", named.desc));
|
|
||||||
}
|
|
||||||
description.push('\n');
|
|
||||||
}
|
|
||||||
description.push('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parameters
|
|
||||||
if !signature.required_positional.is_empty()
|
|
||||||
|| !signature.optional_positional.is_empty()
|
|
||||||
|| signature.rest_positional.is_some()
|
|
||||||
{
|
|
||||||
description.push_str("\n### Parameters\n\n");
|
|
||||||
let mut first = true;
|
|
||||||
for required_arg in &signature.required_positional {
|
|
||||||
if first {
|
|
||||||
first = false;
|
|
||||||
} else {
|
|
||||||
description.push('\n');
|
|
||||||
}
|
|
||||||
description.push_str(&format!(
|
|
||||||
" `{}: {}`",
|
|
||||||
required_arg.name,
|
|
||||||
required_arg.shape.to_type()
|
|
||||||
));
|
|
||||||
if !required_arg.desc.is_empty() {
|
|
||||||
description.push_str(&format!(" - {}", required_arg.desc));
|
|
||||||
}
|
|
||||||
description.push('\n');
|
|
||||||
}
|
|
||||||
for optional_arg in &signature.optional_positional {
|
|
||||||
if first {
|
|
||||||
first = false;
|
|
||||||
} else {
|
|
||||||
description.push('\n');
|
|
||||||
}
|
|
||||||
description.push_str(&format!(
|
|
||||||
" `{}: {}`",
|
|
||||||
optional_arg.name,
|
|
||||||
optional_arg.shape.to_type()
|
|
||||||
));
|
|
||||||
if !optional_arg.desc.is_empty() {
|
|
||||||
description.push_str(&format!(" - {}", optional_arg.desc));
|
|
||||||
}
|
|
||||||
description.push('\n');
|
|
||||||
}
|
|
||||||
if let Some(arg) = &signature.rest_positional {
|
|
||||||
if !first {
|
|
||||||
description.push('\n');
|
|
||||||
}
|
|
||||||
description.push_str(&format!(
|
|
||||||
" `...{}: {}`",
|
|
||||||
arg.name,
|
|
||||||
arg.shape.to_type()
|
|
||||||
));
|
|
||||||
if !arg.desc.is_empty() {
|
|
||||||
description.push_str(&format!(" - {}", arg.desc));
|
|
||||||
}
|
|
||||||
description.push('\n');
|
|
||||||
}
|
|
||||||
description.push('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input/output types
|
|
||||||
if !signature.input_output_types.is_empty() {
|
|
||||||
description.push_str("\n### Input/output types\n");
|
|
||||||
description.push_str("\n```nu\n");
|
|
||||||
for input_output in &signature.input_output_types {
|
|
||||||
description
|
|
||||||
.push_str(&format!(" {} | {}\n", input_output.0, input_output.1));
|
|
||||||
}
|
|
||||||
description.push_str("\n```\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Examples
|
|
||||||
if !decl.examples().is_empty() {
|
|
||||||
description.push_str("### Example(s)\n");
|
|
||||||
for example in decl.examples() {
|
|
||||||
description.push_str(&format!(
|
|
||||||
" {}\n```nu\n {}\n```\n",
|
|
||||||
example.description, example.example
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
markdown_hover(description)
|
|
||||||
}
|
}
|
||||||
Id::Module(module_id) => {
|
Id::Module(module_id) => {
|
||||||
let mut description = String::new();
|
let mut description = String::new();
|
||||||
@ -612,40 +622,62 @@ impl LanguageServer {
|
|||||||
let docs = self.docs.lock().ok()?;
|
let docs = self.docs.lock().ok()?;
|
||||||
let file = docs.get_document(&path_uri)?;
|
let file = docs.get_document(&path_uri)?;
|
||||||
|
|
||||||
let mut completer = NuCompleter::new(
|
let engine_state = Arc::new(self.initial_engine_state.clone());
|
||||||
Arc::new(self.initial_engine_state.clone()),
|
let mut completer = NuCompleter::new(engine_state.clone(), Arc::new(Stack::new()));
|
||||||
Arc::new(Stack::new()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let location = file.offset_at(params.text_document_position.position) as usize;
|
let location = file.offset_at(params.text_document_position.position) as usize;
|
||||||
let results = completer.fetch_completions_at(&file.get_content(None)[..location], location);
|
let results = completer.fetch_completions_at(&file.get_content(None)[..location], location);
|
||||||
if results.is_empty() {
|
(!results.is_empty()).then_some(CompletionResponse::Array(
|
||||||
None
|
results
|
||||||
} else {
|
.into_iter()
|
||||||
Some(CompletionResponse::Array(
|
.map(|r| {
|
||||||
results
|
let mut start = params.text_document_position.position;
|
||||||
.into_iter()
|
start.character -= (r.suggestion.span.end - r.suggestion.span.start) as u32;
|
||||||
.map(|r| {
|
let decl_id = r.kind.clone().and_then(|kind| {
|
||||||
let mut start = params.text_document_position.position;
|
matches!(kind, SuggestionKind::Command(_))
|
||||||
start.character -= (r.suggestion.span.end - r.suggestion.span.start) as u32;
|
.then_some(engine_state.find_decl(r.suggestion.value.as_bytes(), &[])?)
|
||||||
|
});
|
||||||
|
|
||||||
CompletionItem {
|
CompletionItem {
|
||||||
label: r.suggestion.value.clone(),
|
label: r.suggestion.value.clone(),
|
||||||
detail: r.suggestion.description,
|
label_details: r
|
||||||
kind: Self::lsp_completion_item_kind(r.kind),
|
.kind
|
||||||
text_edit: Some(CompletionTextEdit::Edit(TextEdit {
|
.clone()
|
||||||
range: Range {
|
.map(|kind| match kind {
|
||||||
start,
|
SuggestionKind::Type(t) => t.to_string(),
|
||||||
end: params.text_document_position.position,
|
SuggestionKind::Command(cmd) => cmd.to_string(),
|
||||||
},
|
})
|
||||||
new_text: r.suggestion.value,
|
.map(|s| CompletionItemLabelDetails {
|
||||||
})),
|
detail: None,
|
||||||
..Default::default()
|
description: Some(s),
|
||||||
}
|
}),
|
||||||
})
|
detail: r.suggestion.description,
|
||||||
.collect(),
|
documentation: r
|
||||||
))
|
.suggestion
|
||||||
}
|
.extra
|
||||||
|
.map(|ex| ex.join("\n"))
|
||||||
|
.or(decl_id.map(|decl_id| {
|
||||||
|
Self::get_decl_description(engine_state.get_decl(decl_id))
|
||||||
|
}))
|
||||||
|
.map(|value| {
|
||||||
|
Documentation::MarkupContent(MarkupContent {
|
||||||
|
kind: MarkupKind::Markdown,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
kind: Self::lsp_completion_item_kind(r.kind),
|
||||||
|
text_edit: Some(CompletionTextEdit::Edit(TextEdit {
|
||||||
|
range: Range {
|
||||||
|
start,
|
||||||
|
end: params.text_document_position.position,
|
||||||
|
},
|
||||||
|
new_text: r.suggestion.value,
|
||||||
|
})),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lsp_completion_item_kind(
|
fn lsp_completion_item_kind(
|
||||||
@ -681,8 +713,9 @@ mod tests {
|
|||||||
};
|
};
|
||||||
use nu_test_support::fs::fixtures;
|
use nu_test_support::fs::fixtures;
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
pub fn initialize_language_server(
|
pub(crate) fn initialize_language_server(
|
||||||
params: Option<InitializeParams>,
|
params: Option<InitializeParams>,
|
||||||
) -> (Connection, Receiver<Result<()>>) {
|
) -> (Connection, Receiver<Result<()>>) {
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
@ -713,7 +746,7 @@ mod tests {
|
|||||||
|
|
||||||
let _initialize_response = client_connection
|
let _initialize_response = client_connection
|
||||||
.receiver
|
.receiver
|
||||||
.recv_timeout(std::time::Duration::from_secs(2))
|
.recv_timeout(Duration::from_secs(2))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
(client_connection, recv)
|
(client_connection, recv)
|
||||||
@ -739,17 +772,17 @@ mod tests {
|
|||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(recv
|
assert!(recv.recv_timeout(Duration::from_secs(2)).unwrap().is_ok());
|
||||||
.recv_timeout(std::time::Duration::from_secs(2))
|
|
||||||
.unwrap()
|
|
||||||
.is_ok());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_unchecked(client_connection: &Connection, uri: Uri) -> lsp_server::Notification {
|
pub(crate) fn open_unchecked(
|
||||||
|
client_connection: &Connection,
|
||||||
|
uri: Uri,
|
||||||
|
) -> lsp_server::Notification {
|
||||||
open(client_connection, uri).unwrap()
|
open(client_connection, uri).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open(
|
pub(crate) fn open(
|
||||||
client_connection: &Connection,
|
client_connection: &Connection,
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
) -> Result<lsp_server::Notification, String> {
|
) -> Result<lsp_server::Notification, String> {
|
||||||
@ -783,7 +816,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(
|
pub(crate) fn update(
|
||||||
client_connection: &Connection,
|
client_connection: &Connection,
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
text: String,
|
text: String,
|
||||||
@ -822,7 +855,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_hover_request(
|
pub(crate) fn send_hover_request(
|
||||||
client_connection: &Connection,
|
client_connection: &Connection,
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
line: u32,
|
line: u32,
|
||||||
@ -846,7 +879,7 @@ mod tests {
|
|||||||
|
|
||||||
client_connection
|
client_connection
|
||||||
.receiver
|
.receiver
|
||||||
.recv_timeout(std::time::Duration::from_secs(2))
|
.recv_timeout(Duration::from_secs(2))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -991,7 +1024,7 @@ mod tests {
|
|||||||
|
|
||||||
client_connection
|
client_connection
|
||||||
.receiver
|
.receiver
|
||||||
.recv_timeout(std::time::Duration::from_secs(2))
|
.recv_timeout(Duration::from_secs(2))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1014,20 +1047,18 @@ mod tests {
|
|||||||
panic!()
|
panic!()
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_json_eq!(
|
assert_json_include!(
|
||||||
result,
|
actual: result,
|
||||||
serde_json::json!([
|
expected: serde_json::json!([
|
||||||
{
|
{
|
||||||
"label": "$greeting",
|
"label": "$greeting",
|
||||||
|
"labelDetails": { "description": "string" },
|
||||||
"textEdit": {
|
"textEdit": {
|
||||||
"newText": "$greeting",
|
"newText": "$greeting",
|
||||||
"range": {
|
"range": { "start": { "character": 5, "line": 2 }, "end": { "character": 9, "line": 2 } }
|
||||||
"start": { "character": 5, "line": 2 },
|
},
|
||||||
"end": { "character": 9, "line": 2 }
|
"kind": 6
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"kind": 6
|
|
||||||
}
|
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1051,21 +1082,17 @@ mod tests {
|
|||||||
panic!()
|
panic!()
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_json_eq!(
|
assert_json_include!(
|
||||||
result,
|
actual: result,
|
||||||
serde_json::json!([
|
expected: serde_json::json!([
|
||||||
{
|
{
|
||||||
"label": "config nu",
|
"label": "config nu",
|
||||||
"detail": "Edit nu configurations.",
|
"detail": "Edit nu configurations.",
|
||||||
"textEdit": {
|
"textEdit": { "range": { "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 8 }, },
|
||||||
"range": {
|
"newText": "config nu"
|
||||||
"start": { "line": 0, "character": 0 },
|
},
|
||||||
"end": { "line": 0, "character": 8 },
|
"kind": 3
|
||||||
},
|
}
|
||||||
"newText": "config nu"
|
|
||||||
},
|
|
||||||
"kind": 3
|
|
||||||
}
|
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1089,21 +1116,19 @@ mod tests {
|
|||||||
panic!()
|
panic!()
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_json_eq!(
|
assert_json_include!(
|
||||||
result,
|
actual: result,
|
||||||
serde_json::json!([
|
expected: serde_json::json!([
|
||||||
{
|
{
|
||||||
"label": "str trim",
|
"label": "str trim",
|
||||||
|
"labelDetails": { "description": "built-in" },
|
||||||
"detail": "Trim whitespace or specific character.",
|
"detail": "Trim whitespace or specific character.",
|
||||||
"textEdit": {
|
"textEdit": {
|
||||||
"range": {
|
"range": { "start": { "line": 0, "character": 8 }, "end": { "line": 0, "character": 13 }, },
|
||||||
"start": { "line": 0, "character": 8 },
|
"newText": "str trim"
|
||||||
"end": { "line": 0, "character": 13 },
|
},
|
||||||
},
|
"kind": 3
|
||||||
"newText": "str trim"
|
}
|
||||||
},
|
|
||||||
"kind": 3
|
|
||||||
}
|
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1132,15 +1157,13 @@ mod tests {
|
|||||||
expected: serde_json::json!([
|
expected: serde_json::json!([
|
||||||
{
|
{
|
||||||
"label": "overlay",
|
"label": "overlay",
|
||||||
|
"labelDetails": { "description": "keyword" },
|
||||||
"textEdit": {
|
"textEdit": {
|
||||||
"newText": "overlay",
|
"newText": "overlay",
|
||||||
"range": {
|
"range": { "start": { "character": 0, "line": 0 }, "end": { "character": 2, "line": 0 } }
|
||||||
"start": { "character": 0, "line": 0 },
|
},
|
||||||
"end": { "character": 2, "line": 0 }
|
"kind": 14
|
||||||
}
|
},
|
||||||
},
|
|
||||||
"kind": 14
|
|
||||||
},
|
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use lsp_server::{Message, RequestId, Response, ResponseError};
|
use crate::LanguageServer;
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
notification::{
|
notification::{
|
||||||
DidChangeTextDocument, DidChangeWorkspaceFolders, DidCloseTextDocument,
|
DidChangeTextDocument, DidChangeWorkspaceFolders, DidCloseTextDocument,
|
||||||
@ -8,8 +8,6 @@ use lsp_types::{
|
|||||||
DidOpenTextDocumentParams, ProgressParams, ProgressParamsValue, ProgressToken, Uri,
|
DidOpenTextDocumentParams, ProgressParams, ProgressParamsValue, ProgressToken, Uri,
|
||||||
WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressEnd, WorkDoneProgressReport,
|
WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressEnd, WorkDoneProgressReport,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::LanguageServer;
|
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
|
|
||||||
impl LanguageServer {
|
impl LanguageServer {
|
||||||
@ -57,7 +55,7 @@ impl LanguageServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_progress_notification(
|
pub(crate) fn send_progress_notification(
|
||||||
&self,
|
&self,
|
||||||
token: ProgressToken,
|
token: ProgressToken,
|
||||||
value: WorkDoneProgress,
|
value: WorkDoneProgress,
|
||||||
@ -74,7 +72,7 @@ impl LanguageServer {
|
|||||||
.into_diagnostic()
|
.into_diagnostic()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_progress_begin(&self, token: ProgressToken, title: String) -> Result<()> {
|
pub(crate) fn send_progress_begin(&self, token: ProgressToken, title: String) -> Result<()> {
|
||||||
self.send_progress_notification(
|
self.send_progress_notification(
|
||||||
token,
|
token,
|
||||||
WorkDoneProgress::Begin(WorkDoneProgressBegin {
|
WorkDoneProgress::Begin(WorkDoneProgressBegin {
|
||||||
@ -86,7 +84,7 @@ impl LanguageServer {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_progress_report(
|
pub(crate) fn send_progress_report(
|
||||||
&self,
|
&self,
|
||||||
token: ProgressToken,
|
token: ProgressToken,
|
||||||
percentage: u32,
|
percentage: u32,
|
||||||
@ -102,20 +100,29 @@ impl LanguageServer {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_progress_end(&self, token: ProgressToken, message: Option<String>) -> Result<()> {
|
pub(crate) fn send_progress_end(
|
||||||
|
&self,
|
||||||
|
token: ProgressToken,
|
||||||
|
message: Option<String>,
|
||||||
|
) -> Result<()> {
|
||||||
self.send_progress_notification(
|
self.send_progress_notification(
|
||||||
token,
|
token,
|
||||||
WorkDoneProgress::End(WorkDoneProgressEnd { message }),
|
WorkDoneProgress::End(WorkDoneProgressEnd { message }),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_error_message(&self, id: RequestId, code: i32, message: String) -> Result<()> {
|
pub(crate) fn send_error_message(
|
||||||
|
&self,
|
||||||
|
id: lsp_server::RequestId,
|
||||||
|
code: i32,
|
||||||
|
message: String,
|
||||||
|
) -> Result<()> {
|
||||||
self.connection
|
self.connection
|
||||||
.sender
|
.sender
|
||||||
.send(Message::Response(Response {
|
.send(lsp_server::Message::Response(lsp_server::Response {
|
||||||
id,
|
id,
|
||||||
result: None,
|
result: None,
|
||||||
error: Some(ResponseError {
|
error: Some(lsp_server::ResponseError {
|
||||||
code,
|
code,
|
||||||
message,
|
message,
|
||||||
data: None,
|
data: None,
|
||||||
@ -128,15 +135,14 @@ impl LanguageServer {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use assert_json_diff::assert_json_eq;
|
|
||||||
use lsp_server::Message;
|
|
||||||
use lsp_types::Range;
|
|
||||||
use nu_test_support::fs::fixtures;
|
|
||||||
|
|
||||||
use crate::path_to_uri;
|
use crate::path_to_uri;
|
||||||
use crate::tests::{
|
use crate::tests::{
|
||||||
initialize_language_server, open, open_unchecked, send_hover_request, update,
|
initialize_language_server, open, open_unchecked, 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;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hover_correct_documentation_on_let() {
|
fn hover_correct_documentation_on_let() {
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
use std::collections::{BTreeMap, HashSet};
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
|
|
||||||
use crate::{path_to_uri, span_to_range, uri_to_path, Id, LanguageServer};
|
use crate::{path_to_uri, span_to_range, uri_to_path, Id, LanguageServer};
|
||||||
use lsp_textdocument::{FullTextDocument, TextDocuments};
|
use lsp_textdocument::{FullTextDocument, TextDocuments};
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
DocumentSymbolParams, DocumentSymbolResponse, Location, Range, SymbolInformation, SymbolKind,
|
DocumentSymbolParams, DocumentSymbolResponse, Location, Range, SymbolInformation, SymbolKind,
|
||||||
Uri, WorkspaceSymbolParams, WorkspaceSymbolResponse,
|
Uri, WorkspaceSymbolParams, WorkspaceSymbolResponse,
|
||||||
};
|
};
|
||||||
use nu_parser::parse;
|
|
||||||
use nu_protocol::ModuleId;
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{CachedFile, EngineState, StateWorkingSet},
|
engine::{CachedFile, EngineState, StateWorkingSet},
|
||||||
DeclId, Span, VarId,
|
DeclId, ModuleId, Span, VarId,
|
||||||
};
|
};
|
||||||
use nucleo_matcher::pattern::{AtomKind, CaseMatching, Normalization, Pattern};
|
use nucleo_matcher::pattern::{AtomKind, CaseMatching, Normalization, Pattern};
|
||||||
use nucleo_matcher::{Config, Matcher, Utf32Str};
|
use nucleo_matcher::{Config, Matcher, Utf32Str};
|
||||||
use std::{cmp::Ordering, path::Path};
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
|
collections::{BTreeMap, HashSet},
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
};
|
||||||
|
|
||||||
/// Struct stored in cache, uri not included
|
/// Struct stored in cache, uri not included
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
@ -69,7 +68,7 @@ impl Symbol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Cache symbols for each opened file to avoid repeated parsing
|
/// Cache symbols for each opened file to avoid repeated parsing
|
||||||
pub struct SymbolCache {
|
pub(crate) struct SymbolCache {
|
||||||
/// Fuzzy matcher for symbol names
|
/// Fuzzy matcher for symbol names
|
||||||
matcher: Matcher,
|
matcher: Matcher,
|
||||||
/// File Uri --> Symbols
|
/// File Uri --> Symbols
|
||||||
@ -187,7 +186,7 @@ impl SymbolCache {
|
|||||||
.get_document_content(uri, None)
|
.get_document_content(uri, None)
|
||||||
.expect("Failed to get_document_content!")
|
.expect("Failed to get_document_content!")
|
||||||
.as_bytes();
|
.as_bytes();
|
||||||
parse(
|
nu_parser::parse(
|
||||||
&mut working_set,
|
&mut working_set,
|
||||||
Some(
|
Some(
|
||||||
uri_to_path(uri)
|
uri_to_path(uri)
|
||||||
@ -198,7 +197,7 @@ impl SymbolCache {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
for cached_file in working_set.files() {
|
for cached_file in working_set.files() {
|
||||||
let path = Path::new(&*cached_file.name);
|
let path = std::path::Path::new(&*cached_file.name);
|
||||||
if !path.is_file() {
|
if !path.is_file() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -267,7 +266,7 @@ impl SymbolCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageServer {
|
impl LanguageServer {
|
||||||
pub fn document_symbol(
|
pub(crate) fn document_symbol(
|
||||||
&mut self,
|
&mut self,
|
||||||
params: &DocumentSymbolParams,
|
params: &DocumentSymbolParams,
|
||||||
) -> Option<DocumentSymbolResponse> {
|
) -> Option<DocumentSymbolResponse> {
|
||||||
@ -280,7 +279,7 @@ impl LanguageServer {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn workspace_symbol(
|
pub(crate) fn workspace_symbol(
|
||||||
&mut self,
|
&mut self,
|
||||||
params: &WorkspaceSymbolParams,
|
params: &WorkspaceSymbolParams,
|
||||||
) -> Option<WorkspaceSymbolResponse> {
|
) -> Option<WorkspaceSymbolResponse> {
|
||||||
@ -298,17 +297,16 @@ impl LanguageServer {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use assert_json_diff::assert_json_eq;
|
|
||||||
use lsp_types::{PartialResultParams, TextDocumentIdentifier};
|
|
||||||
use nu_test_support::fs::fixtures;
|
|
||||||
|
|
||||||
use crate::path_to_uri;
|
use crate::path_to_uri;
|
||||||
use crate::tests::{initialize_language_server, open_unchecked, update};
|
use crate::tests::{initialize_language_server, open_unchecked, update};
|
||||||
|
use assert_json_diff::assert_json_eq;
|
||||||
use lsp_server::{Connection, Message};
|
use lsp_server::{Connection, Message};
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
request::{DocumentSymbolRequest, Request, WorkspaceSymbolRequest},
|
request::{DocumentSymbolRequest, Request, WorkspaceSymbolRequest},
|
||||||
DocumentSymbolParams, Uri, WorkDoneProgressParams, WorkspaceSymbolParams,
|
DocumentSymbolParams, PartialResultParams, TextDocumentIdentifier, Uri,
|
||||||
|
WorkDoneProgressParams, WorkspaceSymbolParams,
|
||||||
};
|
};
|
||||||
|
use nu_test_support::fs::fixtures;
|
||||||
|
|
||||||
fn send_document_symbol_request(client_connection: &Connection, uri: Uri) -> Message {
|
fn send_document_symbol_request(client_connection: &Connection, uri: Uri) -> Message {
|
||||||
client_connection
|
client_connection
|
||||||
|
@ -1,5 +1,18 @@
|
|||||||
|
use crate::{
|
||||||
|
ast::{find_id, find_reference_by_id},
|
||||||
|
path_to_uri, span_to_range, uri_to_path, Id, LanguageServer,
|
||||||
|
};
|
||||||
use lsp_textdocument::FullTextDocument;
|
use lsp_textdocument::FullTextDocument;
|
||||||
use nu_parser::parse;
|
use lsp_types::{
|
||||||
|
DocumentHighlight, DocumentHighlightKind, DocumentHighlightParams, Location,
|
||||||
|
PrepareRenameResponse, ProgressToken, Range, ReferenceParams, RenameParams,
|
||||||
|
TextDocumentPositionParams, TextEdit, Uri, WorkspaceEdit, WorkspaceFolder,
|
||||||
|
};
|
||||||
|
use miette::{miette, IntoDiagnostic, Result};
|
||||||
|
use nu_protocol::{
|
||||||
|
engine::{EngineState, StateWorkingSet},
|
||||||
|
Span,
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, HashMap},
|
collections::{BTreeMap, HashMap},
|
||||||
fs,
|
fs,
|
||||||
@ -7,72 +20,37 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
|
||||||
ast::{find_id, find_reference_by_id},
|
|
||||||
path_to_uri, span_to_range, uri_to_path, Id, LanguageServer,
|
|
||||||
};
|
|
||||||
use crossbeam_channel::{Receiver, Sender};
|
|
||||||
use lsp_server::{Message, Request, Response};
|
|
||||||
use lsp_types::{
|
|
||||||
DocumentHighlight, DocumentHighlightKind, DocumentHighlightParams, Location,
|
|
||||||
PrepareRenameResponse, ProgressToken, Range, ReferenceParams, RenameParams,
|
|
||||||
TextDocumentPositionParams, TextEdit, Uri, WorkspaceEdit, WorkspaceFolder,
|
|
||||||
};
|
|
||||||
use miette::{miette, IntoDiagnostic, Result};
|
|
||||||
use nu_glob::{glob, Paths};
|
|
||||||
use nu_protocol::{
|
|
||||||
engine::{EngineState, StateWorkingSet},
|
|
||||||
Span,
|
|
||||||
};
|
|
||||||
use serde_json::Value;
|
|
||||||
|
|
||||||
/// Message type indicating ranges of interest in each doc
|
/// Message type indicating ranges of interest in each doc
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RangePerDoc {
|
pub(crate) struct RangePerDoc {
|
||||||
pub uri: Uri,
|
pub uri: Uri,
|
||||||
pub ranges: Vec<Range>,
|
pub ranges: Vec<Range>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Message sent from background thread to main
|
/// Message sent from background thread to main
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum InternalMessage {
|
pub(crate) enum InternalMessage {
|
||||||
RangeMessage(RangePerDoc),
|
RangeMessage(RangePerDoc),
|
||||||
Cancelled(ProgressToken),
|
Cancelled(ProgressToken),
|
||||||
Finished(ProgressToken),
|
Finished(ProgressToken),
|
||||||
OnGoing(ProgressToken, u32),
|
OnGoing(ProgressToken, u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_nu_scripts_in_folder(folder_uri: &Uri) -> Result<Paths> {
|
fn find_nu_scripts_in_folder(folder_uri: &Uri) -> Result<nu_glob::Paths> {
|
||||||
let path = uri_to_path(folder_uri);
|
let path = uri_to_path(folder_uri);
|
||||||
if !path.is_dir() {
|
if !path.is_dir() {
|
||||||
return Err(miette!("\nworkspace folder does not exist."));
|
return Err(miette!("\nworkspace folder does not exist."));
|
||||||
}
|
}
|
||||||
let pattern = format!("{}/**/*.nu", path.to_string_lossy());
|
let pattern = format!("{}/**/*.nu", path.to_string_lossy());
|
||||||
glob(&pattern).into_diagnostic()
|
nu_glob::glob(&pattern).into_diagnostic()
|
||||||
}
|
|
||||||
|
|
||||||
fn find_reference_in_file(
|
|
||||||
working_set: &mut StateWorkingSet,
|
|
||||||
file: &FullTextDocument,
|
|
||||||
fp: &Path,
|
|
||||||
id: &Id,
|
|
||||||
) -> Option<Vec<Span>> {
|
|
||||||
let block = parse(
|
|
||||||
working_set,
|
|
||||||
fp.to_str(),
|
|
||||||
file.get_content(None).as_bytes(),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
let references: Vec<Span> = find_reference_by_id(&block, working_set, id);
|
|
||||||
|
|
||||||
// add_block to avoid repeated parsing
|
|
||||||
working_set.add_block(block);
|
|
||||||
(!references.is_empty()).then_some(references)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageServer {
|
impl LanguageServer {
|
||||||
/// Get initial workspace folders from initialization response
|
/// Get initial workspace folders from initialization response
|
||||||
pub fn initialize_workspace_folders(&mut self, init_params: Value) -> Result<()> {
|
pub(crate) fn initialize_workspace_folders(
|
||||||
|
&mut self,
|
||||||
|
init_params: serde_json::Value,
|
||||||
|
) -> Result<()> {
|
||||||
if let Some(array) = init_params.get("workspaceFolders") {
|
if let Some(array) = init_params.get("workspaceFolders") {
|
||||||
let folders: Vec<WorkspaceFolder> =
|
let folders: Vec<WorkspaceFolder> =
|
||||||
serde_json::from_value(array.clone()).into_diagnostic()?;
|
serde_json::from_value(array.clone()).into_diagnostic()?;
|
||||||
@ -84,7 +62,7 @@ impl LanguageServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Highlight all occurrences of the text at cursor, in current file
|
/// Highlight all occurrences of the text at cursor, in current file
|
||||||
pub fn document_highlight(
|
pub(crate) fn document_highlight(
|
||||||
&mut self,
|
&mut self,
|
||||||
params: &DocumentHighlightParams,
|
params: &DocumentHighlightParams,
|
||||||
) -> Option<Vec<DocumentHighlight>> {
|
) -> Option<Vec<DocumentHighlight>> {
|
||||||
@ -122,7 +100,7 @@ impl LanguageServer {
|
|||||||
|
|
||||||
/// The rename request only happens after the client received a `PrepareRenameResponse`,
|
/// The rename request only happens after the client received a `PrepareRenameResponse`,
|
||||||
/// and a new name typed in, could happen before ranges ready for all files in the workspace folder
|
/// and a new name typed in, could happen before ranges ready for all files in the workspace folder
|
||||||
pub fn rename(&mut self, params: &RenameParams) -> Option<WorkspaceEdit> {
|
pub(crate) fn rename(&mut self, params: &RenameParams) -> Option<WorkspaceEdit> {
|
||||||
let new_name = params.new_name.to_owned();
|
let new_name = params.new_name.to_owned();
|
||||||
// changes in WorkspaceEdit have mutable key
|
// changes in WorkspaceEdit have mutable key
|
||||||
#[allow(clippy::mutable_key_type)]
|
#[allow(clippy::mutable_key_type)]
|
||||||
@ -153,7 +131,11 @@ impl LanguageServer {
|
|||||||
/// - `timeout`: timeout in milliseconds, when timeout
|
/// - `timeout`: timeout in milliseconds, when timeout
|
||||||
/// 1. Respond with all ranges found so far
|
/// 1. Respond with all ranges found so far
|
||||||
/// 2. Cancel the background thread
|
/// 2. Cancel the background thread
|
||||||
pub fn references(&mut self, params: &ReferenceParams, timeout: u128) -> Option<Vec<Location>> {
|
pub(crate) fn references(
|
||||||
|
&mut self,
|
||||||
|
params: &ReferenceParams,
|
||||||
|
timeout: u128,
|
||||||
|
) -> Option<Vec<Location>> {
|
||||||
self.occurrences = BTreeMap::new();
|
self.occurrences = BTreeMap::new();
|
||||||
let mut engine_state = self.new_engine_state();
|
let mut engine_state = self.new_engine_state();
|
||||||
let path_uri = params.text_document_position.text_document.uri.to_owned();
|
let path_uri = params.text_document_position.text_document.uri.to_owned();
|
||||||
@ -213,7 +195,7 @@ impl LanguageServer {
|
|||||||
/// 1. Parse current file to find the content at the cursor that is suitable for a workspace wide renaming
|
/// 1. Parse current file to find the content at the cursor that is suitable for a workspace wide renaming
|
||||||
/// 2. Parse all nu scripts in the same workspace folder, with the variable/command name in it.
|
/// 2. Parse all nu scripts in the same workspace folder, with the variable/command name in it.
|
||||||
/// 3. Store the results in `self.occurrences` for later rename quest
|
/// 3. Store the results in `self.occurrences` for later rename quest
|
||||||
pub fn prepare_rename(&mut self, request: Request) -> Result<()> {
|
pub(crate) fn prepare_rename(&mut self, request: lsp_server::Request) -> Result<()> {
|
||||||
let params: TextDocumentPositionParams =
|
let params: TextDocumentPositionParams =
|
||||||
serde_json::from_value(request.params).into_diagnostic()?;
|
serde_json::from_value(request.params).into_diagnostic()?;
|
||||||
self.occurrences = BTreeMap::new();
|
self.occurrences = BTreeMap::new();
|
||||||
@ -244,7 +226,7 @@ impl LanguageServer {
|
|||||||
let response = PrepareRenameResponse::Range(range);
|
let response = PrepareRenameResponse::Range(range);
|
||||||
self.connection
|
self.connection
|
||||||
.sender
|
.sender
|
||||||
.send(Message::Response(Response {
|
.send(lsp_server::Message::Response(lsp_server::Response {
|
||||||
id: request.id,
|
id: request.id,
|
||||||
result: serde_json::to_value(response).ok(),
|
result: serde_json::to_value(response).ok(),
|
||||||
error: None,
|
error: None,
|
||||||
@ -252,10 +234,7 @@ impl LanguageServer {
|
|||||||
.into_diagnostic()?;
|
.into_diagnostic()?;
|
||||||
|
|
||||||
// have to clone it again in order to move to another thread
|
// have to clone it again in order to move to another thread
|
||||||
let mut engine_state = self.new_engine_state();
|
let engine_state = self.new_engine_state();
|
||||||
engine_state
|
|
||||||
.merge_delta(working_set.render())
|
|
||||||
.into_diagnostic()?;
|
|
||||||
let current_workspace_folder = self
|
let current_workspace_folder = self
|
||||||
.get_workspace_folder_by_uri(&path_uri)
|
.get_workspace_folder_by_uri(&path_uri)
|
||||||
.ok_or_else(|| miette!("\nCurrent file is not in any workspace"))?;
|
.ok_or_else(|| miette!("\nCurrent file is not in any workspace"))?;
|
||||||
@ -273,6 +252,25 @@ impl LanguageServer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_reference_in_file(
|
||||||
|
working_set: &mut StateWorkingSet,
|
||||||
|
file: &FullTextDocument,
|
||||||
|
fp: &Path,
|
||||||
|
id: &Id,
|
||||||
|
) -> Option<Vec<Span>> {
|
||||||
|
let block = nu_parser::parse(
|
||||||
|
working_set,
|
||||||
|
fp.to_str(),
|
||||||
|
file.get_content(None).as_bytes(),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
let references: Vec<Span> = find_reference_by_id(&block, working_set, id);
|
||||||
|
|
||||||
|
// add_block to avoid repeated parsing
|
||||||
|
working_set.add_block(block);
|
||||||
|
(!references.is_empty()).then_some(references)
|
||||||
|
}
|
||||||
|
|
||||||
/// NOTE: for arguments whose declaration is in a signature
|
/// NOTE: for arguments whose declaration is in a signature
|
||||||
/// which is not covered in the AST
|
/// which is not covered in the AST
|
||||||
fn reference_not_in_ast(
|
fn reference_not_in_ast(
|
||||||
@ -300,6 +298,8 @@ impl LanguageServer {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Time consuming task running in a background thread
|
||||||
|
/// communicating with the main thread using `InternalMessage`
|
||||||
fn find_reference_in_workspace(
|
fn find_reference_in_workspace(
|
||||||
&self,
|
&self,
|
||||||
engine_state: EngineState,
|
engine_state: EngineState,
|
||||||
@ -308,7 +308,10 @@ impl LanguageServer {
|
|||||||
span: Span,
|
span: Span,
|
||||||
token: ProgressToken,
|
token: ProgressToken,
|
||||||
message: String,
|
message: String,
|
||||||
) -> Result<(Sender<bool>, Arc<Receiver<InternalMessage>>)> {
|
) -> Result<(
|
||||||
|
crossbeam_channel::Sender<bool>,
|
||||||
|
Arc<crossbeam_channel::Receiver<InternalMessage>>,
|
||||||
|
)> {
|
||||||
let (data_sender, data_receiver) = crossbeam_channel::unbounded::<InternalMessage>();
|
let (data_sender, data_receiver) = crossbeam_channel::unbounded::<InternalMessage>();
|
||||||
let (cancel_sender, cancel_receiver) = crossbeam_channel::bounded::<bool>(1);
|
let (cancel_sender, cancel_receiver) = crossbeam_channel::bounded::<bool>(1);
|
||||||
let engine_state = Arc::new(engine_state);
|
let engine_state = Arc::new(engine_state);
|
||||||
@ -371,32 +374,34 @@ impl LanguageServer {
|
|||||||
}
|
}
|
||||||
&FullTextDocument::new("nu".to_string(), 0, content_string.into())
|
&FullTextDocument::new("nu".to_string(), 0, content_string.into())
|
||||||
};
|
};
|
||||||
let _ = find_reference_in_file(&mut working_set, file, fp, &id).map(|mut refs| {
|
let _ = Self::find_reference_in_file(&mut working_set, file, fp, &id).map(
|
||||||
let file_span = working_set
|
|mut refs| {
|
||||||
.get_span_for_filename(fp.to_string_lossy().as_ref())
|
let file_span = working_set
|
||||||
.unwrap_or(Span::unknown());
|
.get_span_for_filename(fp.to_string_lossy().as_ref())
|
||||||
if let Some(extra_span) = Self::reference_not_in_ast(
|
.unwrap_or(Span::unknown());
|
||||||
&id,
|
if let Some(extra_span) = Self::reference_not_in_ast(
|
||||||
&working_set,
|
&id,
|
||||||
definition_span,
|
&working_set,
|
||||||
file_span,
|
definition_span,
|
||||||
span,
|
file_span,
|
||||||
) {
|
span,
|
||||||
if !refs.contains(&extra_span) {
|
) {
|
||||||
refs.push(extra_span)
|
if !refs.contains(&extra_span) {
|
||||||
|
refs.push(extra_span)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
let ranges = refs
|
||||||
let ranges = refs
|
.iter()
|
||||||
.iter()
|
.map(|span| span_to_range(span, file, file_span.start))
|
||||||
.map(|span| span_to_range(span, file, file_span.start))
|
.collect();
|
||||||
.collect();
|
data_sender
|
||||||
data_sender
|
.send(InternalMessage::RangeMessage(RangePerDoc { uri, ranges }))
|
||||||
.send(InternalMessage::RangeMessage(RangePerDoc { uri, ranges }))
|
.ok();
|
||||||
.ok();
|
data_sender
|
||||||
data_sender
|
.send(InternalMessage::OnGoing(token.clone(), percentage))
|
||||||
.send(InternalMessage::OnGoing(token.clone(), percentage))
|
.ok();
|
||||||
.ok();
|
},
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
data_sender
|
data_sender
|
||||||
.send(InternalMessage::Finished(token.clone()))
|
.send(InternalMessage::Finished(token.clone()))
|
||||||
@ -421,21 +426,17 @@ impl LanguageServer {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::path_to_uri;
|
||||||
|
use crate::tests::{initialize_language_server, open_unchecked, send_hover_request};
|
||||||
use assert_json_diff::assert_json_eq;
|
use assert_json_diff::assert_json_eq;
|
||||||
use lsp_server::{Connection, Message};
|
use lsp_server::{Connection, Message};
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
request, request::Request, InitializeParams, PartialResultParams, Position,
|
request, request::Request, DocumentHighlightParams, InitializeParams, PartialResultParams,
|
||||||
ReferenceContext, ReferenceParams, TextDocumentIdentifier, TextDocumentPositionParams, Uri,
|
Position, ReferenceContext, ReferenceParams, RenameParams, TextDocumentIdentifier,
|
||||||
WorkDoneProgressParams, WorkspaceFolder,
|
TextDocumentPositionParams, Uri, WorkDoneProgressParams, WorkspaceFolder,
|
||||||
};
|
};
|
||||||
use lsp_types::{DocumentHighlightParams, RenameParams};
|
|
||||||
use nu_parser::parse;
|
|
||||||
use nu_protocol::engine::StateWorkingSet;
|
|
||||||
use nu_test_support::fs::fixtures;
|
use nu_test_support::fs::fixtures;
|
||||||
|
|
||||||
use crate::path_to_uri;
|
|
||||||
use crate::tests::{initialize_language_server, open_unchecked, send_hover_request};
|
|
||||||
|
|
||||||
fn send_reference_request(
|
fn send_reference_request(
|
||||||
client_connection: &Connection,
|
client_connection: &Connection,
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
@ -873,8 +874,8 @@ mod tests {
|
|||||||
nu_protocol::Value::test_string(script_path.to_str().unwrap()),
|
nu_protocol::Value::test_string(script_path.to_str().unwrap()),
|
||||||
);
|
);
|
||||||
script_path.push("bar.nu");
|
script_path.push("bar.nu");
|
||||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
let mut working_set = nu_protocol::engine::StateWorkingSet::new(&engine_state);
|
||||||
parse(
|
nu_parser::parse(
|
||||||
&mut working_set,
|
&mut working_set,
|
||||||
script_path.to_str(),
|
script_path.to_str(),
|
||||||
std::fs::read(script_path.clone()).unwrap().as_slice(),
|
std::fs::read(script_path.clone()).unwrap().as_slice(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user