[ty] Rejigger workspace symbols for more efficient caching

In effect, we make the Salsa query aspect keyed only on whether we want
global symbols. We move everything else (hierarchical and querying) to
an aggregate step *after* the query.

This was a somewhat involved change since we want to return a flattened
list from visiting the source while also preserving enough information
to reform the symbols into a hierarchical structure that the LSP
expects. But I think overall the API has gotten simpler and we encode
more invariants into the type system. (For example, previously you got a
runtime assertion if you tried to provide a query string while enabling
hierarchical mode. But now that's prevented by construction.)
This commit is contained in:
Andrew Gallant 2025-08-23 08:11:39 -04:00 committed by Andrew Gallant
parent f407f12f4c
commit 205eae14d2
8 changed files with 317 additions and 164 deletions

2
Cargo.lock generated
View file

@ -4241,11 +4241,13 @@ name = "ty_ide"
version = "0.0.0"
dependencies = [
"bitflags 2.9.2",
"get-size2",
"insta",
"itertools 0.14.0",
"rayon",
"regex",
"ruff_db",
"ruff_index",
"ruff_memory_usage",
"ruff_python_ast",
"ruff_python_parser",

View file

@ -13,6 +13,7 @@ license = { workspace = true }
[dependencies]
bitflags = { workspace = true }
ruff_db = { workspace = true }
ruff_index = { workspace = true }
ruff_memory_usage = { workspace = true }
ruff_python_ast = { workspace = true }
ruff_python_parser = { workspace = true }
@ -22,6 +23,7 @@ ruff_text_size = { workspace = true }
ty_python_semantic = { workspace = true }
ty_project = { workspace = true, features = ["testing"] }
get-size2 = { workspace = true }
itertools = { workspace = true }
rayon = { workspace = true }
regex = { workspace = true }

View file

@ -1,29 +1,16 @@
use crate::symbols::{SymbolInfo, SymbolsOptions, symbols_for_file};
use crate::symbols::{FlatSymbols, symbols_for_file};
use ruff_db::files::File;
use ty_project::Db;
/// Get all document symbols for a file with the given options.
pub fn document_symbols_with_options(
db: &dyn Db,
file: File,
options: &SymbolsOptions,
) -> Vec<SymbolInfo> {
symbols_for_file(db, file, options).cloned().collect()
}
/// Get all document symbols for a file (hierarchical by default).
pub fn document_symbols(db: &dyn Db, file: File) -> Vec<SymbolInfo> {
let options = SymbolsOptions {
hierarchical: true,
global_only: false,
query_string: None,
};
document_symbols_with_options(db, file, &options)
pub fn document_symbols(db: &dyn Db, file: File) -> &FlatSymbols {
symbols_for_file(db, file)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::symbols::{HierarchicalSymbols, SymbolId, SymbolInfo};
use crate::tests::{CursorTest, IntoDiagnostic, cursor_test};
use insta::assert_snapshot;
use ruff_db::diagnostic::{
@ -324,42 +311,45 @@ class OuterClass:
impl CursorTest {
fn document_symbols(&self) -> String {
let symbols = document_symbols(&self.db, self.cursor.file);
let symbols = document_symbols(&self.db, self.cursor.file).to_hierarchical();
if symbols.is_empty() {
return "No symbols found".to_string();
}
self.render_diagnostics(
symbols
.into_iter()
.flat_map(|symbol| symbol_to_diagnostics(symbol, self.cursor.file)),
)
self.render_diagnostics(symbols.iter().flat_map(|(id, symbol)| {
symbol_to_diagnostics(&symbols, id, symbol, self.cursor.file)
}))
}
}
fn symbol_to_diagnostics(symbol: SymbolInfo, file: File) -> Vec<DocumentSymbolDiagnostic> {
fn symbol_to_diagnostics<'db>(
symbols: &'db HierarchicalSymbols,
id: SymbolId,
symbol: SymbolInfo<'db>,
file: File,
) -> Vec<DocumentSymbolDiagnostic<'db>> {
// Output the symbol and recursively output all child symbols
let mut diagnostics = vec![DocumentSymbolDiagnostic::new(symbol.clone(), file)];
let mut diagnostics = vec![DocumentSymbolDiagnostic::new(symbol, file)];
for child in symbol.children {
diagnostics.extend(symbol_to_diagnostics(child, file));
for (child_id, child) in symbols.children(id) {
diagnostics.extend(symbol_to_diagnostics(symbols, child_id, child, file));
}
diagnostics
}
struct DocumentSymbolDiagnostic {
symbol: SymbolInfo,
struct DocumentSymbolDiagnostic<'db> {
symbol: SymbolInfo<'db>,
file: File,
}
impl DocumentSymbolDiagnostic {
fn new(symbol: SymbolInfo, file: File) -> Self {
impl<'db> DocumentSymbolDiagnostic<'db> {
fn new(symbol: SymbolInfo<'db>, file: File) -> Self {
Self { symbol, file }
}
}
impl IntoDiagnostic for DocumentSymbolDiagnostic {
impl IntoDiagnostic for DocumentSymbolDiagnostic<'_> {
fn into_diagnostic(self) -> Diagnostic {
let symbol_kind_str = self.symbol.kind.to_string();

View file

@ -26,7 +26,7 @@ mod workspace_symbols;
pub use completion::completion;
pub use doc_highlights::document_highlights;
pub use document_symbols::{document_symbols, document_symbols_with_options};
pub use document_symbols::document_symbols;
pub use goto::{goto_declaration, goto_definition, goto_type_definition};
pub use goto_references::goto_references;
pub use hover::hover;
@ -39,7 +39,7 @@ pub use semantic_tokens::{
SemanticToken, SemanticTokenModifier, SemanticTokenType, SemanticTokens, semantic_tokens,
};
pub use signature_help::{ParameterDetails, SignatureDetails, SignatureHelpInfo, signature_help};
pub use symbols::{SymbolInfo, SymbolKind, SymbolsOptions};
pub use symbols::{FlatSymbols, HierarchicalSymbols, SymbolId, SymbolInfo, SymbolKind};
pub use workspace_symbols::{WorkspaceSymbolInfo, workspace_symbols};
use ruff_db::files::{File, FileRange};

View file

@ -1,26 +1,22 @@
//! Implements logic used by the document symbol provider, workspace symbol
//! provider, and auto-import feature of the completion provider.
use std::borrow::Cow;
use std::ops::Range;
use regex::Regex;
use ruff_db::files::File;
use ruff_db::parsed::parsed_module;
use ruff_index::{IndexVec, newtype_index};
use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor};
use ruff_python_ast::{Expr, Stmt};
use ruff_text_size::{Ranged, TextRange};
use ty_project::Db;
/// Options that control which symbols are returned
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SymbolsOptions {
/// Return a hierarchy of symbols or a flattened list?
pub hierarchical: bool,
/// Include only symbols in the global scope
pub global_only: bool,
/// Query string for filtering symbol names
pub query_string: Option<QueryPattern>,
}
/// A compiled query pattern used for searching symbols.
///
/// This can be used with the `FlatSymbols::search` API.
#[derive(Clone, Debug)]
pub struct QueryPattern {
re: Option<Regex>,
@ -28,6 +24,7 @@ pub struct QueryPattern {
}
impl QueryPattern {
/// Create a new query pattern from a literal search string given.
pub fn new(literal_query_string: &str) -> QueryPattern {
let mut pattern = "(?i)".to_string();
for ch in literal_query_string.chars() {
@ -36,14 +33,16 @@ impl QueryPattern {
}
// In theory regex compilation could fail if the pattern string
// was long enough to exceed the default regex compilation size
// limit. But this length would be approaching ~10MB or so.
// limit. But this length would be approaching ~10MB or so. If
// is does somehow fail, we'll just fall back to simple substring
// search using `original`.
QueryPattern {
re: Regex::new(&pattern).ok(),
original: literal_query_string.to_string(),
}
}
fn is_match(&self, symbol: &SymbolInfo) -> bool {
fn is_match(&self, symbol: &SymbolInfo<'_>) -> bool {
self.is_match_symbol_name(&symbol.name)
}
@ -73,23 +72,183 @@ impl PartialEq for QueryPattern {
}
}
/// A flat list of indexed symbols for a single file.
#[derive(Clone, Debug, Default, PartialEq, Eq, get_size2::GetSize)]
pub struct FlatSymbols {
symbols: IndexVec<SymbolId, SymbolTree>,
}
impl FlatSymbols {
/// Get the symbol info for the symbol identified by the given ID.
///
/// Returns `None` when the given ID does not reference a symbol in this
/// collection.
pub fn get(&self, id: SymbolId) -> Option<SymbolInfo<'_>> {
self.symbols.get(id).map(Into::into)
}
/// Returns true if and only if this collection is empty.
pub fn is_empty(&self) -> bool {
self.symbols.is_empty()
}
/// Returns the total number of symbols in this collection.
pub fn len(&self) -> usize {
self.symbols.len()
}
/// Returns an iterator over every symbol along with its ID.
pub fn iter(&self) -> impl Iterator<Item = (SymbolId, SymbolInfo<'_>)> {
self.symbols
.iter_enumerated()
.map(|(id, symbol)| (id, symbol.into()))
}
/// Returns a sequence of symbols that matches the given query.
pub fn search(&self, query: &QueryPattern) -> impl Iterator<Item = (SymbolId, SymbolInfo<'_>)> {
self.iter().filter(|(_, symbol)| query.is_match(symbol))
}
/// Turns this flat sequence of symbols into a hierarchy of symbols.
pub fn to_hierarchical(&self) -> HierarchicalSymbols {
let mut children_ids: IndexVec<SymbolId, Vec<SymbolId>> = IndexVec::new();
for (id, symbol) in self.symbols.iter_enumerated() {
children_ids.push(vec![]);
let Some(parent_id) = symbol.parent else {
continue;
};
// OK because the symbol visitor guarantees that
// all parents are ordered before their children.
assert!(parent_id.index() < id.index());
children_ids[parent_id].push(id);
}
// Now flatten our map of symbol ID to its children
// IDs into a single vec that doesn't nest allocations.
let mut symbols = IndexVec::new();
let mut children: Vec<SymbolId> = vec![];
let mut last_end: usize = 0;
for (tree, child_symbol_ids) in self.symbols.iter().zip(children_ids) {
let start = last_end;
let end = start + child_symbol_ids.len();
symbols.push(SymbolTreeWithChildren {
tree: tree.clone(),
children: start..end,
});
children.extend_from_slice(&child_symbol_ids);
last_end = end;
}
HierarchicalSymbols { symbols, children }
}
}
/// A collection of hierarchical indexed symbols for a single file.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct HierarchicalSymbols {
symbols: IndexVec<SymbolId, SymbolTreeWithChildren>,
children: Vec<SymbolId>,
}
impl HierarchicalSymbols {
/// Get the symbol info for the symbol identified by the given ID.
///
/// Returns `None` when the given ID does not reference a symbol in this
/// collection.
pub fn get(&self, id: SymbolId) -> Option<SymbolInfo<'_>> {
self.symbols.get(id).map(Into::into)
}
/// Returns true if and only if this collection is empty.
pub fn is_empty(&self) -> bool {
self.symbols.is_empty()
}
/// Returns the total number of symbols in this collection.
pub fn len(&self) -> usize {
self.symbols.len()
}
/// Returns an iterator over every top-level symbol along with its ID.
pub fn iter(&self) -> impl Iterator<Item = (SymbolId, SymbolInfo<'_>)> {
self.symbols
.iter_enumerated()
.filter(|(_, symbol)| symbol.tree.parent.is_none())
.map(|(id, symbol)| (id, symbol.into()))
}
/// Returns an iterator over the child symbols for the symbol
/// identified by the given ID.
///
/// Returns `None` when there aren't any children or when the given
/// ID does not reference a symbol in this collection.
pub fn children(&self, id: SymbolId) -> impl Iterator<Item = (SymbolId, SymbolInfo<'_>)> {
self.symbols
.get(id)
.into_iter()
.flat_map(|symbol| self.children[symbol.children.clone()].iter())
.copied()
.map(|id| (id, SymbolInfo::from(&self.symbols[id])))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct SymbolTreeWithChildren {
tree: SymbolTree,
/// The index range into `HierarchicalSymbols::children`
/// corresponding to the children symbol IDs for this
/// symbol.
children: Range<usize>,
}
/// Uniquely identifies a symbol.
#[newtype_index]
#[derive(get_size2::GetSize)]
pub struct SymbolId;
/// Symbol information for IDE features like document outline.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SymbolInfo {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SymbolInfo<'a> {
/// The name of the symbol
pub name: String,
pub name: Cow<'a, str>,
/// The kind of symbol (function, class, variable, etc.)
pub kind: SymbolKind,
/// The range of the symbol name
pub name_range: TextRange,
/// The full range of the symbol (including body)
pub full_range: TextRange,
/// Child symbols (e.g., methods in a class)
pub children: Vec<SymbolInfo>,
}
impl SymbolInfo<'_> {
pub fn to_owned(&self) -> SymbolInfo<'static> {
SymbolInfo {
name: Cow::Owned(self.name.to_string()),
kind: self.kind,
name_range: self.name_range,
full_range: self.full_range,
}
}
}
impl<'a> From<&'a SymbolTree> for SymbolInfo<'a> {
fn from(symbol: &'a SymbolTree) -> SymbolInfo<'a> {
SymbolInfo {
name: Cow::Borrowed(&symbol.name),
kind: symbol.kind,
name_range: symbol.name_range,
full_range: symbol.full_range,
}
}
}
impl<'a> From<&'a SymbolTreeWithChildren> for SymbolInfo<'a> {
fn from(symbol: &'a SymbolTreeWithChildren) -> SymbolInfo<'a> {
SymbolInfo::from(&symbol.tree)
}
}
/// The kind of symbol
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, get_size2::GetSize)]
pub enum SymbolKind {
Module,
Class,
@ -125,61 +284,68 @@ impl SymbolKind {
}
}
pub(crate) fn symbols_for_file<'db>(
db: &'db dyn Db,
file: File,
options: &SymbolsOptions,
) -> impl Iterator<Item = &'db SymbolInfo> {
assert!(
!options.hierarchical || options.query_string.is_none(),
"Cannot use hierarchical mode with a query string"
);
let ingredient = SymbolsOptionsWithoutQuery {
hierarchical: options.hierarchical,
global_only: options.global_only,
};
symbols_for_file_inner(db, file, ingredient)
.iter()
.filter(|symbol| {
let Some(ref query) = options.query_string else {
return true;
};
query.is_match(symbol)
})
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct SymbolsOptionsWithoutQuery {
hierarchical: bool,
global_only: bool,
}
#[salsa::tracked(returns(deref))]
fn symbols_for_file_inner<'db>(
db: &'db dyn Db,
file: File,
options: SymbolsOptionsWithoutQuery,
) -> Vec<SymbolInfo> {
/// Returns a flat list of symbols in the file given.
///
/// The flattened list includes parent/child information and can be
/// converted into a hierarchical collection of symbols.
#[salsa::tracked(returns(ref), heap_size=ruff_memory_usage::heap_size)]
pub(crate) fn symbols_for_file(db: &dyn Db, file: File) -> FlatSymbols {
let parsed = parsed_module(db, file);
let module = parsed.load(db);
let mut visitor = SymbolVisitor {
symbols: vec![],
symbols: IndexVec::new(),
symbol_stack: vec![],
in_function: false,
options,
global_only: false,
};
visitor.visit_body(&module.syntax().body);
visitor.symbols
FlatSymbols {
symbols: visitor.symbols,
}
}
/// Returns a flat list of *only global* symbols in the file given.
///
/// While callers can convert this into a hierarchical collection of
/// symbols, it won't result in anything meaningful since the flat list
/// returned doesn't include children.
#[salsa::tracked(returns(ref), heap_size=ruff_memory_usage::heap_size)]
pub(crate) fn symbols_for_file_global_only(db: &dyn Db, file: File) -> FlatSymbols {
let parsed = parsed_module(db, file);
let module = parsed.load(db);
let mut visitor = SymbolVisitor {
symbols: IndexVec::new(),
symbol_stack: vec![],
in_function: false,
global_only: true,
};
visitor.visit_body(&module.syntax().body);
FlatSymbols {
symbols: visitor.symbols,
}
}
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
struct SymbolTree {
parent: Option<SymbolId>,
name: String,
kind: SymbolKind,
name_range: TextRange,
full_range: TextRange,
}
/// A visitor over all symbols in a single file.
///
/// This guarantees that child symbols have a symbol ID greater
/// than all of its parents.
struct SymbolVisitor {
symbols: Vec<SymbolInfo>,
symbol_stack: Vec<SymbolInfo>,
symbols: IndexVec<SymbolId, SymbolTree>,
symbol_stack: Vec<SymbolId>,
/// Track if we're currently inside a function (to exclude local variables)
in_function: bool,
options: SymbolsOptionsWithoutQuery,
global_only: bool,
}
impl SymbolVisitor {
@ -189,32 +355,33 @@ impl SymbolVisitor {
}
}
fn add_symbol(&mut self, symbol: SymbolInfo) {
if self.options.hierarchical {
if let Some(parent) = self.symbol_stack.last_mut() {
parent.children.push(symbol);
} else {
self.symbols.push(symbol);
fn add_symbol(&mut self, mut symbol: SymbolTree) -> SymbolId {
if let Some(&parent_id) = self.symbol_stack.last() {
symbol.parent = Some(parent_id);
}
} else {
// It's important that we push the symbol and allocate
// an ID before visiting its child. This preserves the
// guarantee that parent IDs are always less than their
// children IDs.
let symbol_id = self.symbols.next_index();
self.symbols.push(symbol);
}
symbol_id
}
fn push_symbol(&mut self, symbol: SymbolInfo) {
if self.options.hierarchical {
self.symbol_stack.push(symbol);
} else {
self.add_symbol(symbol);
}
fn push_symbol(&mut self, symbol: SymbolTree) {
let symbol_id = self.add_symbol(symbol);
self.symbol_stack.push(symbol_id);
}
fn pop_symbol(&mut self) {
if self.options.hierarchical {
if let Some(symbol) = self.symbol_stack.pop() {
self.add_symbol(symbol);
}
self.symbol_stack.pop().unwrap();
}
fn iter_symbol_stack(&self) -> impl Iterator<Item = &SymbolTree> {
self.symbol_stack
.iter()
.copied()
.map(|id| &self.symbols[id])
}
fn is_constant_name(name: &str) -> bool {
@ -227,8 +394,7 @@ impl SourceOrderVisitor<'_> for SymbolVisitor {
match stmt {
Stmt::FunctionDef(func_def) => {
let kind = if self
.symbol_stack
.iter()
.iter_symbol_stack()
.any(|s| s.kind == SymbolKind::Class)
{
if func_def.name.as_str() == "__init__" {
@ -240,15 +406,15 @@ impl SourceOrderVisitor<'_> for SymbolVisitor {
SymbolKind::Function
};
let symbol = SymbolInfo {
let symbol = SymbolTree {
parent: None,
name: func_def.name.to_string(),
kind,
name_range: func_def.name.range(),
full_range: stmt.range(),
children: Vec::new(),
};
if self.options.global_only {
if self.global_only {
self.add_symbol(symbol);
// If global_only, don't walk function bodies
return;
@ -269,15 +435,15 @@ impl SourceOrderVisitor<'_> for SymbolVisitor {
}
Stmt::ClassDef(class_def) => {
let symbol = SymbolInfo {
let symbol = SymbolTree {
parent: None,
name: class_def.name.to_string(),
kind: SymbolKind::Class,
name_range: class_def.name.range(),
full_range: stmt.range(),
children: Vec::new(),
};
if self.options.global_only {
if self.global_only {
self.add_symbol(symbol);
// If global_only, don't walk class bodies
return;
@ -296,8 +462,7 @@ impl SourceOrderVisitor<'_> for SymbolVisitor {
let kind = if Self::is_constant_name(name.id.as_str()) {
SymbolKind::Constant
} else if self
.symbol_stack
.iter()
.iter_symbol_stack()
.any(|s| s.kind == SymbolKind::Class)
{
SymbolKind::Field
@ -305,12 +470,12 @@ impl SourceOrderVisitor<'_> for SymbolVisitor {
SymbolKind::Variable
};
let symbol = SymbolInfo {
let symbol = SymbolTree {
parent: None,
name: name.id.to_string(),
kind,
name_range: name.range(),
full_range: stmt.range(),
children: Vec::new(),
};
self.add_symbol(symbol);
@ -326,8 +491,7 @@ impl SourceOrderVisitor<'_> for SymbolVisitor {
let kind = if Self::is_constant_name(name.id.as_str()) {
SymbolKind::Constant
} else if self
.symbol_stack
.iter()
.iter_symbol_stack()
.any(|s| s.kind == SymbolKind::Class)
{
SymbolKind::Field
@ -335,12 +499,12 @@ impl SourceOrderVisitor<'_> for SymbolVisitor {
SymbolKind::Variable
};
let symbol = SymbolInfo {
let symbol = SymbolTree {
parent: None,
name: name.id.to_string(),
kind,
name_range: name.range(),
full_range: stmt.range(),
children: Vec::new(),
};
self.add_symbol(symbol);

View file

@ -1,4 +1,4 @@
use crate::symbols::{SymbolInfo, SymbolsOptions, symbols_for_file};
use crate::symbols::{QueryPattern, SymbolInfo, symbols_for_file_global_only};
use ruff_db::files::File;
use ty_project::Db;
@ -12,31 +12,26 @@ pub fn workspace_symbols(db: &dyn Db, query: &str) -> Vec<WorkspaceSymbolInfo> {
let project = db.project();
let options = SymbolsOptions {
hierarchical: false, // Workspace symbols are always flat
global_only: false,
query_string: Some(query.into()),
};
let query = QueryPattern::new(query);
let files = project.files(db);
let results = std::sync::Mutex::new(Vec::new());
{
let db = db.dyn_clone();
let files = &files;
let options = &options;
let results = &results;
let query = &query;
rayon::scope(move |s| {
// For each file, extract symbols and add them to results
for file in files.iter() {
let db = db.dyn_clone();
s.spawn(move |_| {
for symbol in symbols_for_file(&*db, *file, options) {
for (_, symbol) in symbols_for_file_global_only(&*db, *file).search(query) {
// It seems like we could do better here than
// locking `results` for every single symbol,
// but this works pretty well as it is.
results.lock().unwrap().push(WorkspaceSymbolInfo {
symbol: symbol.clone(),
symbol: symbol.to_owned(),
file: *file,
});
}
@ -52,7 +47,7 @@ pub fn workspace_symbols(db: &dyn Db, query: &str) -> Vec<WorkspaceSymbolInfo> {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkspaceSymbolInfo {
/// The symbol information
pub symbol: SymbolInfo,
pub symbol: SymbolInfo<'static>,
/// The file containing the symbol
pub file: File,
}

View file

@ -4,7 +4,7 @@ use lsp_types::request::DocumentSymbolRequest;
use lsp_types::{DocumentSymbol, DocumentSymbolParams, SymbolInformation, Url};
use ruff_db::source::{line_index, source_text};
use ruff_source_file::LineIndex;
use ty_ide::{SymbolInfo, SymbolsOptions, document_symbols_with_options};
use ty_ide::{HierarchicalSymbols, SymbolId, SymbolInfo, document_symbols};
use ty_project::ProjectDatabase;
use crate::document::{PositionEncoding, ToRangeExt};
@ -51,24 +51,19 @@ impl BackgroundDocumentRequestHandler for DocumentSymbolRequestHandler {
.resolved_client_capabilities()
.supports_hierarchical_document_symbols();
let options = SymbolsOptions {
hierarchical: supports_hierarchical,
global_only: false,
query_string: None,
};
let symbols = document_symbols_with_options(db, file, &options);
let symbols = document_symbols(db, file);
if symbols.is_empty() {
return Ok(None);
}
if supports_hierarchical {
// Return hierarchical symbols
let symbols = symbols.to_hierarchical();
let lsp_symbols: Vec<DocumentSymbol> = symbols
.into_iter()
.map(|symbol| {
.iter()
.map(|(id, symbol)| {
convert_to_lsp_document_symbol(
&symbols,
id,
symbol,
&source,
&line_index,
@ -81,8 +76,8 @@ impl BackgroundDocumentRequestHandler for DocumentSymbolRequestHandler {
} else {
// Return flattened symbols as SymbolInformation
let lsp_symbols: Vec<SymbolInformation> = symbols
.into_iter()
.map(|symbol| {
.iter()
.map(|(_, symbol)| {
convert_to_lsp_symbol_information(
symbol,
&params.text_document.uri,
@ -101,7 +96,9 @@ impl BackgroundDocumentRequestHandler for DocumentSymbolRequestHandler {
impl RetriableRequestHandler for DocumentSymbolRequestHandler {}
fn convert_to_lsp_document_symbol(
symbol: SymbolInfo,
symbols: &HierarchicalSymbols,
id: SymbolId,
symbol: SymbolInfo<'_>,
source: &str,
line_index: &LineIndex,
encoding: PositionEncoding,
@ -109,7 +106,7 @@ fn convert_to_lsp_document_symbol(
let symbol_kind = convert_symbol_kind(symbol.kind);
DocumentSymbol {
name: symbol.name,
name: symbol.name.into_owned(),
detail: None,
kind: symbol_kind,
tags: None,
@ -118,10 +115,13 @@ fn convert_to_lsp_document_symbol(
range: symbol.full_range.to_lsp_range(source, line_index, encoding),
selection_range: symbol.name_range.to_lsp_range(source, line_index, encoding),
children: Some(
symbol
.children
.into_iter()
.map(|child| convert_to_lsp_document_symbol(child, source, line_index, encoding))
symbols
.children(id)
.map(|(child_id, child)| {
convert_to_lsp_document_symbol(
symbols, child_id, child, source, line_index, encoding,
)
})
.collect(),
),
}

View file

@ -27,7 +27,7 @@ pub(crate) fn convert_symbol_kind(kind: ty_ide::SymbolKind) -> SymbolKind {
/// Convert a `ty_ide` `SymbolInfo` to LSP `SymbolInformation`
pub(crate) fn convert_to_lsp_symbol_information(
symbol: SymbolInfo,
symbol: SymbolInfo<'_>,
uri: &Url,
source: &str,
line_index: &LineIndex,
@ -36,7 +36,7 @@ pub(crate) fn convert_to_lsp_symbol_information(
let symbol_kind = convert_symbol_kind(symbol.kind);
SymbolInformation {
name: symbol.name,
name: symbol.name.into_owned(),
kind: symbol_kind,
tags: None,
#[allow(deprecated)]