mirror of
https://github.com/tweag/nickel.git
synced 2024-10-06 08:07:37 +03:00
Convert symbols request away from the linearizer (#1623)
This commit is contained in:
parent
17adb43f87
commit
bcb0f71caa
@ -4,8 +4,8 @@ mod output;
|
||||
pub use jsonrpc::Server;
|
||||
use log::error;
|
||||
use lsp_types::{
|
||||
CompletionParams, DocumentFormattingParams, GotoDefinitionParams, HoverParams, ReferenceParams,
|
||||
Url,
|
||||
CompletionParams, DocumentFormattingParams, DocumentSymbolParams, GotoDefinitionParams,
|
||||
HoverParams, ReferenceParams, Url,
|
||||
};
|
||||
pub use output::LspDebug;
|
||||
use serde::Deserialize;
|
||||
@ -35,6 +35,7 @@ pub enum Request {
|
||||
Completion(CompletionParams),
|
||||
Formatting(DocumentFormattingParams),
|
||||
Hover(HoverParams),
|
||||
Symbols(DocumentSymbolParams),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
use lsp_types::GotoDefinitionResponse;
|
||||
use lsp_types::{DocumentSymbolResponse, GotoDefinitionResponse};
|
||||
|
||||
pub trait LspDebug {
|
||||
fn debug(&self, w: impl Write) -> std::io::Result<()>;
|
||||
@ -157,3 +157,34 @@ impl LspDebug for lsp_types::MarkupContent {
|
||||
write!(w, "{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl LspDebug for lsp_types::SymbolInformation {
|
||||
fn debug(&self, mut w: impl Write) -> std::io::Result<()> {
|
||||
let name = &self.name;
|
||||
let kind = self.kind;
|
||||
write!(w, "{name} ({kind:?})@{}", self.location.debug_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl LspDebug for lsp_types::DocumentSymbol {
|
||||
fn debug(&self, mut w: impl Write) -> std::io::Result<()> {
|
||||
let name = &self.name;
|
||||
let kind = self.kind;
|
||||
let detail = self.detail.clone().unwrap_or_default();
|
||||
write!(
|
||||
w,
|
||||
"{name} ({kind:?} / {detail:?})@{} in {}",
|
||||
self.selection_range.debug_str(),
|
||||
self.range.debug_str()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl LspDebug for DocumentSymbolResponse {
|
||||
fn debug(&self, w: impl Write) -> std::io::Result<()> {
|
||||
match self {
|
||||
DocumentSymbolResponse::Flat(xs) => xs.debug(w),
|
||||
DocumentSymbolResponse::Nested(xs) => xs.debug(w),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ use nickel_lang_core::{
|
||||
};
|
||||
|
||||
use crate::linearization::{building::Building, AnalysisHost, Environment, LinRegistry};
|
||||
use crate::linearization::{CombinedLinearizer, TypeCollector};
|
||||
use crate::linearization::{CollectedTypes, CombinedLinearizer, TypeCollector};
|
||||
|
||||
pub trait CacheExt {
|
||||
fn typecheck_with_analysis(
|
||||
@ -78,7 +78,7 @@ impl CacheExt for Cache {
|
||||
initial_ctxt.clone(),
|
||||
self,
|
||||
lin,
|
||||
(building, HashMap::new()),
|
||||
(building, CollectedTypes::default()),
|
||||
)
|
||||
.map_err(|err| vec![Error::TypecheckError(err)])?;
|
||||
|
||||
|
@ -101,7 +101,7 @@ pub struct LinRegistry {
|
||||
pub position_lookups: HashMap<FileId, PositionLookup>,
|
||||
pub usage_lookups: HashMap<FileId, UsageLookup>,
|
||||
pub parent_lookups: HashMap<FileId, ParentLookup>,
|
||||
pub type_lookups: HashMap<FileId, HashMap<RichTermPtr, Type>>,
|
||||
pub type_lookups: HashMap<FileId, CollectedTypes<Type>>,
|
||||
}
|
||||
|
||||
impl LinRegistry {
|
||||
@ -113,7 +113,7 @@ impl LinRegistry {
|
||||
&mut self,
|
||||
file_id: FileId,
|
||||
linearization: Completed,
|
||||
type_lookups: HashMap<RichTermPtr, Type>,
|
||||
type_lookups: CollectedTypes<Type>,
|
||||
term: &RichTerm,
|
||||
) {
|
||||
self.map.insert(file_id, linearization);
|
||||
@ -154,7 +154,10 @@ impl LinRegistry {
|
||||
|
||||
pub fn get_type(&self, rt: &RichTerm) -> Option<&Type> {
|
||||
let file = rt.pos.as_opt_ref()?.src_id;
|
||||
self.type_lookups.get(&file)?.get(&RichTermPtr(rt.clone()))
|
||||
self.type_lookups
|
||||
.get(&file)?
|
||||
.terms
|
||||
.get(&RichTermPtr(rt.clone()))
|
||||
}
|
||||
|
||||
pub fn get_parent_chain<'a>(&'a self, rt: &'a RichTerm) -> Option<ParentChainIter<'a>> {
|
||||
@ -788,9 +791,24 @@ pub struct TypeCollector {
|
||||
term_ids: Vec<RichTermPtr>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CollectedTypes<Ty> {
|
||||
pub terms: HashMap<RichTermPtr, Ty>,
|
||||
pub idents: HashMap<LocIdent, Ty>,
|
||||
}
|
||||
|
||||
impl<Ty> Default for CollectedTypes<Ty> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
terms: HashMap::new(),
|
||||
idents: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Linearizer for TypeCollector {
|
||||
type Building = HashMap<RichTermPtr, UnifType>;
|
||||
type Completed = HashMap<RichTermPtr, Type>;
|
||||
type Building = CollectedTypes<UnifType>;
|
||||
type Completed = CollectedTypes<Type>;
|
||||
type CompletionExtra = Extra;
|
||||
type ItemId = usize;
|
||||
|
||||
@ -804,7 +822,7 @@ impl Linearizer for TypeCollector {
|
||||
|
||||
fn add_term(&mut self, lin: &mut Self::Building, rt: &RichTerm, ty: UnifType) -> Option<usize> {
|
||||
self.term_ids.push(RichTermPtr(rt.clone()));
|
||||
lin.insert(RichTermPtr(rt.clone()), ty);
|
||||
lin.terms.insert(RichTermPtr(rt.clone()), ty);
|
||||
Some(self.term_ids.len() - 1)
|
||||
}
|
||||
|
||||
@ -827,16 +845,33 @@ impl Linearizer for TypeCollector {
|
||||
}
|
||||
};
|
||||
|
||||
lin.into_iter()
|
||||
let terms = lin
|
||||
.terms
|
||||
.into_iter()
|
||||
.map(|(rt, uty)| (rt, transform_type(uty)))
|
||||
.collect()
|
||||
.collect();
|
||||
let idents = lin
|
||||
.idents
|
||||
.into_iter()
|
||||
.map(|(id, uty)| (id, transform_type(uty)))
|
||||
.collect();
|
||||
CollectedTypes { terms, idents }
|
||||
}
|
||||
|
||||
fn retype(&mut self, lin: &mut Self::Building, item_id: Option<usize>, new_type: UnifType) {
|
||||
if let Some(id) = item_id {
|
||||
lin.insert(self.term_ids[id].clone(), new_type);
|
||||
lin.terms.insert(self.term_ids[id].clone(), new_type);
|
||||
}
|
||||
}
|
||||
|
||||
fn retype_ident(
|
||||
&mut self,
|
||||
lin: &mut Self::Building,
|
||||
ident: &nickel_lang_core::identifier::LocIdent,
|
||||
new_type: UnifType,
|
||||
) {
|
||||
lin.idents.insert((*ident).into(), new_type);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CombinedLinearizer<T, U>(pub T, pub U);
|
||||
|
@ -1,15 +1,10 @@
|
||||
use crate::{
|
||||
files::uri_to_path,
|
||||
linearization::interface::TermKind,
|
||||
term::RawSpanExt,
|
||||
trace::{Enrich, Trace},
|
||||
};
|
||||
use lsp_server::{RequestId, Response, ResponseError};
|
||||
use lsp_types::{DocumentSymbol, DocumentSymbolParams, SymbolKind};
|
||||
use nickel_lang_core::cache::SourcePath;
|
||||
use serde_json::Value;
|
||||
use nickel_lang_core::typ::Type;
|
||||
|
||||
use crate::server::Server;
|
||||
use crate::{files::uri_to_path, term::RawSpanExt};
|
||||
|
||||
pub fn handle_document_symbols(
|
||||
params: DocumentSymbolParams,
|
||||
@ -17,47 +12,44 @@ pub fn handle_document_symbols(
|
||||
server: &mut Server,
|
||||
) -> Result<(), ResponseError> {
|
||||
let path = uri_to_path(¶ms.text_document.uri)?;
|
||||
let file_id = server.cache.id_of(&SourcePath::Path(path)).unwrap();
|
||||
let file_id = server
|
||||
.cache
|
||||
.id_of(&SourcePath::Path(path))
|
||||
.ok_or_else(|| crate::error::Error::FileNotFound(params.text_document.uri.clone()))?;
|
||||
|
||||
if let Some(completed) = server.lin_registry.map.get(&file_id) {
|
||||
Trace::enrich(&id, completed);
|
||||
let symbols = completed
|
||||
.linearization
|
||||
.iter()
|
||||
.filter_map(|item| match &item.kind {
|
||||
TermKind::Declaration { id: name, .. } => {
|
||||
// Although the position maybe shouldn't be `None`, opening the std library
|
||||
// source inside VSCode made the LSP panic on the previous `item.pos.unwrap()`.
|
||||
// Before investigating further, let's not make the VSCode extension panic in
|
||||
// the meantime.
|
||||
let (file_id, span) = item.pos.into_opt()?.to_range();
|
||||
let usage_lookups = server.lin_registry.usage_lookups.get(&file_id).unwrap();
|
||||
|
||||
let range =
|
||||
codespan_lsp::byte_span_to_range(server.cache.files(), file_id, span)
|
||||
.unwrap();
|
||||
let type_lookups = server
|
||||
.lin_registry
|
||||
.type_lookups
|
||||
.get(&file_id)
|
||||
.ok_or_else(|| crate::error::Error::FileNotFound(params.text_document.uri.clone()))?;
|
||||
|
||||
// `deprecated` is a required field but causes a warning although we are not
|
||||
// using it
|
||||
#[allow(deprecated)]
|
||||
Some(DocumentSymbol {
|
||||
name: name.to_string(),
|
||||
detail: Some(format!("{}", item.ty)),
|
||||
kind: SymbolKind::Variable,
|
||||
tags: None,
|
||||
range,
|
||||
selection_range: range,
|
||||
children: None,
|
||||
deprecated: None,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
let mut symbols = usage_lookups
|
||||
.symbols()
|
||||
.filter_map(|ident| {
|
||||
let (file_id, span) = ident.pos.into_opt()?.to_range();
|
||||
let range =
|
||||
codespan_lsp::byte_span_to_range(server.cache.files(), file_id, span).ok()?;
|
||||
let ty = type_lookups.idents.get(&ident);
|
||||
|
||||
#[allow(deprecated)] // because the `deprecated` field is... wait for it... deprecated.
|
||||
Some(DocumentSymbol {
|
||||
name: ident.ident.to_string(),
|
||||
detail: ty.map(Type::to_string),
|
||||
kind: SymbolKind::Variable,
|
||||
tags: None,
|
||||
range,
|
||||
selection_range: range,
|
||||
children: None,
|
||||
deprecated: None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
server.reply(Response::new_ok(id, symbols));
|
||||
} else {
|
||||
server.reply(Response::new_ok(id, Value::Null));
|
||||
}
|
||||
symbols.sort_by_key(|s| s.range.start);
|
||||
|
||||
server.reply(Response::new_ok(id, symbols));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use nickel_lang_core::{
|
||||
environment::Environment as GenericEnvironment,
|
||||
@ -87,6 +87,10 @@ pub struct UsageLookup {
|
||||
// environments) but its enough for us now.
|
||||
def_table: HashMap<RawSpan, Environment>,
|
||||
usage_table: HashMap<LocIdent, Vec<LocIdent>>,
|
||||
// The list of all the symbols (and their locations) in the document.
|
||||
//
|
||||
// Currently, variables bound in `let` bindings and record fields count as symbols.
|
||||
syms: HashSet<LocIdent>,
|
||||
}
|
||||
|
||||
impl UsageLookup {
|
||||
@ -125,6 +129,15 @@ impl UsageLookup {
|
||||
.and_then(|span| self.def_table.get(span))
|
||||
}
|
||||
|
||||
/// Return the list of symbols in the document.
|
||||
pub fn symbols(&self) -> impl Iterator<Item = LocIdent> + '_ {
|
||||
self.syms.iter().cloned()
|
||||
}
|
||||
|
||||
fn add_sym(&mut self, id: impl Into<LocIdent>) {
|
||||
self.syms.insert(id.into());
|
||||
}
|
||||
|
||||
fn fill(&mut self, rt: &RichTerm, env: &Environment) {
|
||||
rt.traverse_ref(
|
||||
&mut |term: &RichTerm, env: &Environment| {
|
||||
@ -154,6 +167,7 @@ impl UsageLookup {
|
||||
Term::Let(id, val, body, attrs) => {
|
||||
let mut new_env = env.clone();
|
||||
new_env.def(*id, Some(val.clone()), None);
|
||||
self.add_sym(*id);
|
||||
|
||||
self.fill(val, if attrs.rec { &new_env } else { env });
|
||||
self.fill(body, &new_env);
|
||||
@ -164,6 +178,7 @@ impl UsageLookup {
|
||||
let mut new_env = env.clone();
|
||||
if let Some(id) = maybe_id {
|
||||
new_env.def(*id, Some(val.clone()), None);
|
||||
self.add_sym(*id);
|
||||
}
|
||||
|
||||
for m in &pat.matches {
|
||||
@ -173,7 +188,8 @@ impl UsageLookup {
|
||||
term: val.clone(),
|
||||
path,
|
||||
};
|
||||
new_env.def(id, Some(term), Some(field.metadata));
|
||||
new_env.def(id, Some(term.clone()), Some(field.metadata));
|
||||
self.add_sym(id);
|
||||
}
|
||||
}
|
||||
TraverseControl::ContinueWithScope(new_env)
|
||||
@ -185,6 +201,7 @@ impl UsageLookup {
|
||||
// all the fields in the environment and then recurse into their values.
|
||||
for (id, field) in &data.fields {
|
||||
new_env.def(*id, field.value.clone(), Some(field.metadata.clone()));
|
||||
self.add_sym(*id);
|
||||
}
|
||||
|
||||
TraverseControl::ContinueWithScope(new_env)
|
||||
|
2
lsp/nls/test2.ncl
Normal file
2
lsp/nls/test2.ncl
Normal file
@ -0,0 +1,2 @@
|
||||
let x : { _ : { foo : Number | default = 1 } } = {} in
|
||||
x.PATH.foo
|
15
lsp/nls/tests/inputs/symbols-basic.ncl
Normal file
15
lsp/nls/tests/inputs/symbols-basic.ncl
Normal file
@ -0,0 +1,15 @@
|
||||
### /syms.ncl
|
||||
let foo = 3 in
|
||||
let func = fun x => 1 in
|
||||
{
|
||||
name = "value",
|
||||
other_name = {
|
||||
inner_name = let inner_binding = 3 in "value",
|
||||
},
|
||||
type_checked_block = {
|
||||
inner_name = { name = "value" },
|
||||
} : _,
|
||||
}
|
||||
### [[request]]
|
||||
### type = "Symbols"
|
||||
### textDocument.uri = "file:///syms.ncl"
|
@ -2,7 +2,8 @@ use std::collections::{hash_map::Entry, HashMap};
|
||||
|
||||
use assert_cmd::cargo::CommandCargoExt;
|
||||
use lsp_types::request::{
|
||||
Completion, Formatting, GotoDefinition, HoverRequest, References, Request as LspRequest,
|
||||
Completion, DocumentSymbolRequest, Formatting, GotoDefinition, HoverRequest, References,
|
||||
Request as LspRequest,
|
||||
};
|
||||
use nickel_lang_utils::project_root::project_root;
|
||||
use test_generator::test_resources;
|
||||
@ -40,6 +41,7 @@ impl TestHarness {
|
||||
Request::Formatting(f) => self.request::<Formatting>(f),
|
||||
Request::Hover(h) => self.request::<HoverRequest>(h),
|
||||
Request::References(r) => self.request::<References>(r),
|
||||
Request::Symbols(s) => self.request::<DocumentSymbolRequest>(s),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
source: lsp/nls/tests/main.rs
|
||||
expression: output
|
||||
---
|
||||
[foo (Variable / "Number")@0:4-0:7 in 0:4-0:7, func (Variable / "Dyn")@1:4-1:8 in 1:4-1:8, name (Variable / "String")@3:2-3:6 in 3:2-3:6, other_name (Variable / "Dyn")@4:2-4:12 in 4:2-4:12, inner_name (Variable / "Dyn")@5:4-5:14 in 5:4-5:14, inner_binding (Variable / "Number")@5:21-5:34 in 5:21-5:34, type_checked_block (Variable / "Dyn")@7:2-7:20 in 7:2-7:20, inner_name (Variable / "{ name : String }")@8:4-8:14 in 8:4-8:14, name (Variable / "String")@8:19-8:23 in 8:19-8:23]
|
||||
|
Loading…
Reference in New Issue
Block a user