mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:34:57 +00:00
[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:
parent
f407f12f4c
commit
205eae14d2
8 changed files with 317 additions and 164 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
¶ms.text_document.uri,
|
¶ms.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(),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue