[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" version = "0.0.0"
dependencies = [ dependencies = [
"bitflags 2.9.2", "bitflags 2.9.2",
"get-size2",
"insta", "insta",
"itertools 0.14.0", "itertools 0.14.0",
"rayon", "rayon",
"regex", "regex",
"ruff_db", "ruff_db",
"ruff_index",
"ruff_memory_usage", "ruff_memory_usage",
"ruff_python_ast", "ruff_python_ast",
"ruff_python_parser", "ruff_python_parser",

View file

@ -13,6 +13,7 @@ license = { workspace = true }
[dependencies] [dependencies]
bitflags = { workspace = true } bitflags = { workspace = true }
ruff_db = { workspace = true } ruff_db = { workspace = true }
ruff_index = { workspace = true }
ruff_memory_usage = { workspace = true } ruff_memory_usage = { workspace = true }
ruff_python_ast = { workspace = true } ruff_python_ast = { workspace = true }
ruff_python_parser = { workspace = true } ruff_python_parser = { workspace = true }
@ -22,6 +23,7 @@ ruff_text_size = { workspace = true }
ty_python_semantic = { workspace = true } ty_python_semantic = { workspace = true }
ty_project = { workspace = true, features = ["testing"] } ty_project = { workspace = true, features = ["testing"] }
get-size2 = { workspace = true }
itertools = { workspace = true } itertools = { workspace = true }
rayon = { workspace = true } rayon = { workspace = true }
regex = { 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 ruff_db::files::File;
use ty_project::Db; use ty_project::Db;
/// Get all document symbols for a file with the given options. /// Get all document symbols for a file with the given options.
pub fn document_symbols_with_options( pub fn document_symbols(db: &dyn Db, file: File) -> &FlatSymbols {
db: &dyn Db, symbols_for_file(db, file)
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)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::symbols::{HierarchicalSymbols, SymbolId, SymbolInfo};
use crate::tests::{CursorTest, IntoDiagnostic, cursor_test}; use crate::tests::{CursorTest, IntoDiagnostic, cursor_test};
use insta::assert_snapshot; use insta::assert_snapshot;
use ruff_db::diagnostic::{ use ruff_db::diagnostic::{
@ -324,42 +311,45 @@ class OuterClass:
impl CursorTest { impl CursorTest {
fn document_symbols(&self) -> String { 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() { if symbols.is_empty() {
return "No symbols found".to_string(); return "No symbols found".to_string();
} }
self.render_diagnostics( self.render_diagnostics(symbols.iter().flat_map(|(id, symbol)| {
symbols symbol_to_diagnostics(&symbols, id, symbol, self.cursor.file)
.into_iter() }))
.flat_map(|symbol| symbol_to_diagnostics(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 // 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 { for (child_id, child) in symbols.children(id) {
diagnostics.extend(symbol_to_diagnostics(child, file)); diagnostics.extend(symbol_to_diagnostics(symbols, child_id, child, file));
} }
diagnostics diagnostics
} }
struct DocumentSymbolDiagnostic { struct DocumentSymbolDiagnostic<'db> {
symbol: SymbolInfo, symbol: SymbolInfo<'db>,
file: File, file: File,
} }
impl DocumentSymbolDiagnostic { impl<'db> DocumentSymbolDiagnostic<'db> {
fn new(symbol: SymbolInfo, file: File) -> Self { fn new(symbol: SymbolInfo<'db>, file: File) -> Self {
Self { symbol, file } Self { symbol, file }
} }
} }
impl IntoDiagnostic for DocumentSymbolDiagnostic { impl IntoDiagnostic for DocumentSymbolDiagnostic<'_> {
fn into_diagnostic(self) -> Diagnostic { fn into_diagnostic(self) -> Diagnostic {
let symbol_kind_str = self.symbol.kind.to_string(); let symbol_kind_str = self.symbol.kind.to_string();

View file

@ -26,7 +26,7 @@ mod workspace_symbols;
pub use completion::completion; pub use completion::completion;
pub use doc_highlights::document_highlights; 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::{goto_declaration, goto_definition, goto_type_definition};
pub use goto_references::goto_references; pub use goto_references::goto_references;
pub use hover::hover; pub use hover::hover;
@ -39,7 +39,7 @@ pub use semantic_tokens::{
SemanticToken, SemanticTokenModifier, SemanticTokenType, SemanticTokens, semantic_tokens, SemanticToken, SemanticTokenModifier, SemanticTokenType, SemanticTokens, semantic_tokens,
}; };
pub use signature_help::{ParameterDetails, SignatureDetails, SignatureHelpInfo, signature_help}; 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}; pub use workspace_symbols::{WorkspaceSymbolInfo, workspace_symbols};
use ruff_db::files::{File, FileRange}; use ruff_db::files::{File, FileRange};

View file

@ -1,26 +1,22 @@
//! Implements logic used by the document symbol provider, workspace symbol //! Implements logic used by the document symbol provider, workspace symbol
//! provider, and auto-import feature of the completion provider. //! provider, and auto-import feature of the completion provider.
use std::borrow::Cow;
use std::ops::Range;
use regex::Regex; use regex::Regex;
use ruff_db::files::File; use ruff_db::files::File;
use ruff_db::parsed::parsed_module; 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::visitor::source_order::{self, SourceOrderVisitor};
use ruff_python_ast::{Expr, Stmt}; use ruff_python_ast::{Expr, Stmt};
use ruff_text_size::{Ranged, TextRange}; use ruff_text_size::{Ranged, TextRange};
use ty_project::Db; use ty_project::Db;
/// Options that control which symbols are returned /// A compiled query pattern used for searching symbols.
#[derive(Debug, Clone, PartialEq, Eq)] ///
pub struct SymbolsOptions { /// This can be used with the `FlatSymbols::search` API.
/// 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>,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct QueryPattern { pub struct QueryPattern {
re: Option<Regex>, re: Option<Regex>,
@ -28,6 +24,7 @@ pub struct QueryPattern {
} }
impl QueryPattern { impl QueryPattern {
/// Create a new query pattern from a literal search string given.
pub fn new(literal_query_string: &str) -> QueryPattern { pub fn new(literal_query_string: &str) -> QueryPattern {
let mut pattern = "(?i)".to_string(); let mut pattern = "(?i)".to_string();
for ch in literal_query_string.chars() { for ch in literal_query_string.chars() {
@ -36,14 +33,16 @@ impl QueryPattern {
} }
// In theory regex compilation could fail if the pattern string // In theory regex compilation could fail if the pattern string
// was long enough to exceed the default regex compilation size // 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 { QueryPattern {
re: Regex::new(&pattern).ok(), re: Regex::new(&pattern).ok(),
original: literal_query_string.to_string(), 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) 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. /// Symbol information for IDE features like document outline.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct SymbolInfo { pub struct SymbolInfo<'a> {
/// The name of the symbol /// The name of the symbol
pub name: String, pub name: Cow<'a, str>,
/// The kind of symbol (function, class, variable, etc.) /// The kind of symbol (function, class, variable, etc.)
pub kind: SymbolKind, pub kind: SymbolKind,
/// The range of the symbol name /// The range of the symbol name
pub name_range: TextRange, pub name_range: TextRange,
/// The full range of the symbol (including body) /// The full range of the symbol (including body)
pub full_range: TextRange, 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 /// The kind of symbol
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq, get_size2::GetSize)]
pub enum SymbolKind { pub enum SymbolKind {
Module, Module,
Class, Class,
@ -125,61 +284,68 @@ impl SymbolKind {
} }
} }
pub(crate) fn symbols_for_file<'db>( /// Returns a flat list of symbols in the file given.
db: &'db dyn Db, ///
file: File, /// The flattened list includes parent/child information and can be
options: &SymbolsOptions, /// converted into a hierarchical collection of symbols.
) -> impl Iterator<Item = &'db SymbolInfo> { #[salsa::tracked(returns(ref), heap_size=ruff_memory_usage::heap_size)]
assert!( pub(crate) fn symbols_for_file(db: &dyn Db, file: File) -> FlatSymbols {
!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> {
let parsed = parsed_module(db, file); let parsed = parsed_module(db, file);
let module = parsed.load(db); let module = parsed.load(db);
let mut visitor = SymbolVisitor { let mut visitor = SymbolVisitor {
symbols: vec![], symbols: IndexVec::new(),
symbol_stack: vec![], symbol_stack: vec![],
in_function: false, in_function: false,
options, global_only: false,
}; };
visitor.visit_body(&module.syntax().body); 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 { struct SymbolVisitor {
symbols: Vec<SymbolInfo>, symbols: IndexVec<SymbolId, SymbolTree>,
symbol_stack: Vec<SymbolInfo>, symbol_stack: Vec<SymbolId>,
/// Track if we're currently inside a function (to exclude local variables) /// Track if we're currently inside a function (to exclude local variables)
in_function: bool, in_function: bool,
options: SymbolsOptionsWithoutQuery, global_only: bool,
} }
impl SymbolVisitor { impl SymbolVisitor {
@ -189,32 +355,33 @@ impl SymbolVisitor {
} }
} }
fn add_symbol(&mut self, symbol: SymbolInfo) { fn add_symbol(&mut self, mut symbol: SymbolTree) -> SymbolId {
if self.options.hierarchical { if let Some(&parent_id) = self.symbol_stack.last() {
if let Some(parent) = self.symbol_stack.last_mut() { symbol.parent = Some(parent_id);
parent.children.push(symbol);
} else {
self.symbols.push(symbol);
}
} else {
self.symbols.push(symbol);
} }
// 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) { fn push_symbol(&mut self, symbol: SymbolTree) {
if self.options.hierarchical { let symbol_id = self.add_symbol(symbol);
self.symbol_stack.push(symbol); self.symbol_stack.push(symbol_id);
} else {
self.add_symbol(symbol);
}
} }
fn pop_symbol(&mut self) { fn pop_symbol(&mut self) {
if self.options.hierarchical { self.symbol_stack.pop().unwrap();
if let Some(symbol) = self.symbol_stack.pop() { }
self.add_symbol(symbol);
} 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 { fn is_constant_name(name: &str) -> bool {
@ -227,8 +394,7 @@ impl SourceOrderVisitor<'_> for SymbolVisitor {
match stmt { match stmt {
Stmt::FunctionDef(func_def) => { Stmt::FunctionDef(func_def) => {
let kind = if self let kind = if self
.symbol_stack .iter_symbol_stack()
.iter()
.any(|s| s.kind == SymbolKind::Class) .any(|s| s.kind == SymbolKind::Class)
{ {
if func_def.name.as_str() == "__init__" { if func_def.name.as_str() == "__init__" {
@ -240,15 +406,15 @@ impl SourceOrderVisitor<'_> for SymbolVisitor {
SymbolKind::Function SymbolKind::Function
}; };
let symbol = SymbolInfo { let symbol = SymbolTree {
parent: None,
name: func_def.name.to_string(), name: func_def.name.to_string(),
kind, kind,
name_range: func_def.name.range(), name_range: func_def.name.range(),
full_range: stmt.range(), full_range: stmt.range(),
children: Vec::new(),
}; };
if self.options.global_only { if self.global_only {
self.add_symbol(symbol); self.add_symbol(symbol);
// If global_only, don't walk function bodies // If global_only, don't walk function bodies
return; return;
@ -269,15 +435,15 @@ impl SourceOrderVisitor<'_> for SymbolVisitor {
} }
Stmt::ClassDef(class_def) => { Stmt::ClassDef(class_def) => {
let symbol = SymbolInfo { let symbol = SymbolTree {
parent: None,
name: class_def.name.to_string(), name: class_def.name.to_string(),
kind: SymbolKind::Class, kind: SymbolKind::Class,
name_range: class_def.name.range(), name_range: class_def.name.range(),
full_range: stmt.range(), full_range: stmt.range(),
children: Vec::new(),
}; };
if self.options.global_only { if self.global_only {
self.add_symbol(symbol); self.add_symbol(symbol);
// If global_only, don't walk class bodies // If global_only, don't walk class bodies
return; return;
@ -296,8 +462,7 @@ impl SourceOrderVisitor<'_> for SymbolVisitor {
let kind = if Self::is_constant_name(name.id.as_str()) { let kind = if Self::is_constant_name(name.id.as_str()) {
SymbolKind::Constant SymbolKind::Constant
} else if self } else if self
.symbol_stack .iter_symbol_stack()
.iter()
.any(|s| s.kind == SymbolKind::Class) .any(|s| s.kind == SymbolKind::Class)
{ {
SymbolKind::Field SymbolKind::Field
@ -305,12 +470,12 @@ impl SourceOrderVisitor<'_> for SymbolVisitor {
SymbolKind::Variable SymbolKind::Variable
}; };
let symbol = SymbolInfo { let symbol = SymbolTree {
parent: None,
name: name.id.to_string(), name: name.id.to_string(),
kind, kind,
name_range: name.range(), name_range: name.range(),
full_range: stmt.range(), full_range: stmt.range(),
children: Vec::new(),
}; };
self.add_symbol(symbol); self.add_symbol(symbol);
@ -326,8 +491,7 @@ impl SourceOrderVisitor<'_> for SymbolVisitor {
let kind = if Self::is_constant_name(name.id.as_str()) { let kind = if Self::is_constant_name(name.id.as_str()) {
SymbolKind::Constant SymbolKind::Constant
} else if self } else if self
.symbol_stack .iter_symbol_stack()
.iter()
.any(|s| s.kind == SymbolKind::Class) .any(|s| s.kind == SymbolKind::Class)
{ {
SymbolKind::Field SymbolKind::Field
@ -335,12 +499,12 @@ impl SourceOrderVisitor<'_> for SymbolVisitor {
SymbolKind::Variable SymbolKind::Variable
}; };
let symbol = SymbolInfo { let symbol = SymbolTree {
parent: None,
name: name.id.to_string(), name: name.id.to_string(),
kind, kind,
name_range: name.range(), name_range: name.range(),
full_range: stmt.range(), full_range: stmt.range(),
children: Vec::new(),
}; };
self.add_symbol(symbol); 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 ruff_db::files::File;
use ty_project::Db; use ty_project::Db;
@ -12,31 +12,26 @@ pub fn workspace_symbols(db: &dyn Db, query: &str) -> Vec<WorkspaceSymbolInfo> {
let project = db.project(); let project = db.project();
let options = SymbolsOptions { let query = QueryPattern::new(query);
hierarchical: false, // Workspace symbols are always flat
global_only: false,
query_string: Some(query.into()),
};
let files = project.files(db); let files = project.files(db);
let results = std::sync::Mutex::new(Vec::new()); let results = std::sync::Mutex::new(Vec::new());
{ {
let db = db.dyn_clone(); let db = db.dyn_clone();
let files = &files; let files = &files;
let options = &options;
let results = &results; let results = &results;
let query = &query;
rayon::scope(move |s| { rayon::scope(move |s| {
// For each file, extract symbols and add them to results // For each file, extract symbols and add them to results
for file in files.iter() { for file in files.iter() {
let db = db.dyn_clone(); let db = db.dyn_clone();
s.spawn(move |_| { 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 // It seems like we could do better here than
// locking `results` for every single symbol, // locking `results` for every single symbol,
// but this works pretty well as it is. // but this works pretty well as it is.
results.lock().unwrap().push(WorkspaceSymbolInfo { results.lock().unwrap().push(WorkspaceSymbolInfo {
symbol: symbol.clone(), symbol: symbol.to_owned(),
file: *file, file: *file,
}); });
} }
@ -52,7 +47,7 @@ pub fn workspace_symbols(db: &dyn Db, query: &str) -> Vec<WorkspaceSymbolInfo> {
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkspaceSymbolInfo { pub struct WorkspaceSymbolInfo {
/// The symbol information /// The symbol information
pub symbol: SymbolInfo, pub symbol: SymbolInfo<'static>,
/// The file containing the symbol /// The file containing the symbol
pub file: File, pub file: File,
} }

View file

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