diff --git a/lsp/lsp-harness/src/output.rs b/lsp/lsp-harness/src/output.rs index e1502d3dd4..cc4416e39e 100644 --- a/lsp/lsp-harness/src/output.rs +++ b/lsp/lsp-harness/src/output.rs @@ -183,11 +183,13 @@ impl LspDebug for lsp_types::DocumentSymbol { let name = &self.name; let kind = self.kind; let detail = self.detail.clone().unwrap_or_default(); + let children = self.children.as_deref().unwrap_or(&[]); write!( w, - "{name} ({kind:?} / {detail:?})@{} in {}", + "{name} ({kind:?} / {detail:?})@{} in {}: {}", self.selection_range.debug_str(), - self.range.debug_str() + self.range.debug_str(), + Iter(children.iter()).debug_str(), ) } } diff --git a/lsp/nls/src/requests/symbols.rs b/lsp/nls/src/requests/symbols.rs index d411e6e274..770e7f9f8d 100644 --- a/lsp/nls/src/requests/symbols.rs +++ b/lsp/nls/src/requests/symbols.rs @@ -1,10 +1,59 @@ use lsp_server::{RequestId, Response, ResponseError}; use lsp_types::{DocumentSymbol, DocumentSymbolParams, SymbolKind}; +use nickel_lang_core::term::RichTerm; use nickel_lang_core::typ::Type; +use crate::analysis::CollectedTypes; use crate::cache::CacheExt as _; +use crate::field_walker::{FieldResolver, Record}; use crate::server::Server; use crate::term::RawSpanExt; +use crate::world::World; + +// Returns a hierarchy of "publicly accessible" symbols in a term. +// +// Basically, if the term "evaluates" (in the sense of FieldResolver's heuristics) to a record, +// all fields in that record count as publicly accessible symbols. Then we recurse into +// each of those. +fn symbols( + world: &World, + type_lookups: &CollectedTypes, + rt: &RichTerm, +) -> Vec { + let resolver = FieldResolver::new(world); + let root_records = resolver.resolve_path(rt, [].into_iter()); + root_records + .into_iter() + .filter_map(|rec| match rec { + Record::RecordTerm(data) => Some(data), + Record::RecordType(_) => None, + }) + .flat_map(|rt| { + rt.fields.into_iter().filter_map(|(id, field)| { + let ty = type_lookups.idents.get(&id.into()); + let (file_id, span) = id.pos.into_opt()?.to_range(); + let range = + crate::codespan_lsp::byte_span_to_range(world.cache.files(), file_id, span) + .ok()?; + + let children = field.value.map(|v| symbols(world, type_lookups, &v)); + + #[allow(deprecated)] + // because the `deprecated` field is... wait for it... deprecated. + Some(DocumentSymbol { + name: id.ident().to_string(), + detail: ty.map(Type::to_string), + kind: SymbolKind::VARIABLE, + tags: None, + range, + selection_range: range, + children, + deprecated: None, + }) + }) + }) + .collect() +} pub fn handle_document_symbols( params: DocumentSymbolParams, @@ -17,32 +66,13 @@ pub fn handle_document_symbols( .file_id(¶ms.text_document.uri)? .ok_or_else(|| crate::error::Error::FileNotFound(params.text_document.uri.clone()))?; - let usage_lookups = &server.world.file_analysis(file_id)?.usage_lookup; let type_lookups = &server.world.file_analysis(file_id)?.type_lookup; + let term = server.world.cache.get_ref(file_id); - let mut symbols = usage_lookups - .symbols() - .filter_map(|ident| { - let (file_id, span) = ident.pos.into_opt()?.to_range(); - let range = - crate::codespan_lsp::byte_span_to_range(server.world.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::>(); - + let mut symbols = term + .map(|t| symbols(&server.world, type_lookups, t)) + .unwrap_or_default(); + // Sort so the response is deterministic. symbols.sort_by_key(|s| s.range.start); server.reply(Response::new_ok(id, symbols)); diff --git a/lsp/nls/src/usage.rs b/lsp/nls/src/usage.rs index 6aef367add..68903fd2a7 100644 --- a/lsp/nls/src/usage.rs +++ b/lsp/nls/src/usage.rs @@ -86,11 +86,6 @@ impl UsageLookup { .and_then(|span| self.def_table.get(span)) } - /// Return the list of symbols in the document. - pub fn symbols(&self) -> impl Iterator + '_ { - self.syms.keys().cloned() - } - fn add_sym(&mut self, def: Def) { self.syms.insert(def.ident(), def); } diff --git a/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__symbols-basic.ncl.snap b/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__symbols-basic.ncl.snap index 6bb0416ae6..b9d7dc8fbd 100644 --- a/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__symbols-basic.ncl.snap +++ b/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__symbols-basic.ncl.snap @@ -2,5 +2,4 @@ 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] - +[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: []], 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: []]]]