Add config option to exclude locals from doc search

This commit is contained in:
Ifeanyi Orizu 2025-08-10 10:02:03 -05:00
parent caef0f46fd
commit dc6e6d2b86
7 changed files with 350 additions and 28 deletions

View file

@ -23,6 +23,11 @@ pub enum StructureNodeKind {
Region,
}
#[derive(Debug, Clone)]
pub struct FileStructureConfig {
pub exclude_locals: bool,
}
// Feature: File Structure
//
// Provides a tree of the symbols defined in the file. Can be used to
@ -36,21 +41,24 @@ pub enum StructureNodeKind {
// | VS Code | <kbd>Ctrl+Shift+O</kbd> |
//
// ![File Structure](https://user-images.githubusercontent.com/48062697/113020654-b42fc800-917a-11eb-8388-e7dc4d92b02e.gif)
pub(crate) fn file_structure(file: &SourceFile) -> Vec<StructureNode> {
pub(crate) fn file_structure(
file: &SourceFile,
config: &FileStructureConfig,
) -> Vec<StructureNode> {
let mut res = Vec::new();
let mut stack = Vec::new();
for event in file.syntax().preorder_with_tokens() {
match event {
WalkEvent::Enter(NodeOrToken::Node(node)) => {
if let Some(mut symbol) = structure_node(&node) {
if let Some(mut symbol) = structure_node(&node, config) {
symbol.parent = stack.last().copied();
stack.push(res.len());
res.push(symbol);
}
}
WalkEvent::Leave(NodeOrToken::Node(node)) => {
if structure_node(&node).is_some() {
if structure_node(&node, config).is_some() {
stack.pop().unwrap();
}
}
@ -71,7 +79,7 @@ pub(crate) fn file_structure(file: &SourceFile) -> Vec<StructureNode> {
res
}
fn structure_node(node: &SyntaxNode) -> Option<StructureNode> {
fn structure_node(node: &SyntaxNode, config: &FileStructureConfig) -> Option<StructureNode> {
fn decl<N: HasName + HasAttrs>(node: N, kind: StructureNodeKind) -> Option<StructureNode> {
decl_with_detail(&node, None, kind)
}
@ -187,6 +195,10 @@ fn structure_node(node: &SyntaxNode) -> Option<StructureNode> {
Some(node)
},
ast::LetStmt(it) => {
if config.exclude_locals {
return None;
}
let pat = it.pat()?;
let mut label = String::new();
@ -254,9 +266,19 @@ mod tests {
use super::*;
const DEFAULT_CONFIG: FileStructureConfig = FileStructureConfig { exclude_locals: true };
fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
check_with_config(ra_fixture, &DEFAULT_CONFIG, expect);
}
fn check_with_config(
#[rust_analyzer::rust_fixture] ra_fixture: &str,
config: &FileStructureConfig,
expect: Expect,
) {
let file = SourceFile::parse(ra_fixture, span::Edition::CURRENT).ok().unwrap();
let structure = file_structure(&file);
let structure = file_structure(&file, config);
expect.assert_debug_eq(&structure)
}
@ -701,13 +723,264 @@ fn let_statements() {
),
deprecated: false,
},
]
"#]],
);
}
#[test]
fn test_file_structure_include_locals() {
check_with_config(
r#"
struct Foo {
x: i32
}
mod m {
fn bar1() {}
fn bar2<T>(t: T) -> T {}
fn bar3<A,
B>(a: A,
b: B) -> Vec<
u32
> {}
}
enum E { X, Y(i32) }
type T = ();
static S: i32 = 42;
const C: i32 = 42;
trait Tr {}
trait Alias = Tr;
macro_rules! mc {
() => {}
}
fn let_statements() {
let x = 42;
let mut y = x;
let Foo {
..
} = Foo { x };
_ = ();
let _ = g();
}
"#,
&FileStructureConfig { exclude_locals: false },
expect![[r#"
[
StructureNode {
parent: None,
label: "Foo",
navigation_range: 8..11,
node_range: 1..26,
kind: SymbolKind(
Struct,
),
detail: None,
deprecated: false,
},
StructureNode {
parent: Some(
27,
0,
),
label: "x",
navigation_range: 684..685,
node_range: 680..691,
navigation_range: 18..19,
node_range: 18..24,
kind: SymbolKind(
Field,
),
detail: Some(
"i32",
),
deprecated: false,
},
StructureNode {
parent: None,
label: "m",
navigation_range: 32..33,
node_range: 28..158,
kind: SymbolKind(
Module,
),
detail: None,
deprecated: false,
},
StructureNode {
parent: Some(
2,
),
label: "bar1",
navigation_range: 43..47,
node_range: 40..52,
kind: SymbolKind(
Function,
),
detail: Some(
"fn()",
),
deprecated: false,
},
StructureNode {
parent: Some(
2,
),
label: "bar2",
navigation_range: 60..64,
node_range: 57..81,
kind: SymbolKind(
Function,
),
detail: Some(
"fn<T>(t: T) -> T",
),
deprecated: false,
},
StructureNode {
parent: Some(
2,
),
label: "bar3",
navigation_range: 89..93,
node_range: 86..156,
kind: SymbolKind(
Function,
),
detail: Some(
"fn<A, B>(a: A, b: B) -> Vec< u32 >",
),
deprecated: false,
},
StructureNode {
parent: None,
label: "E",
navigation_range: 165..166,
node_range: 160..180,
kind: SymbolKind(
Enum,
),
detail: None,
deprecated: false,
},
StructureNode {
parent: Some(
6,
),
label: "X",
navigation_range: 169..170,
node_range: 169..170,
kind: SymbolKind(
Variant,
),
detail: None,
deprecated: false,
},
StructureNode {
parent: Some(
6,
),
label: "Y",
navigation_range: 172..173,
node_range: 172..178,
kind: SymbolKind(
Variant,
),
detail: None,
deprecated: false,
},
StructureNode {
parent: None,
label: "T",
navigation_range: 186..187,
node_range: 181..193,
kind: SymbolKind(
TypeAlias,
),
detail: Some(
"()",
),
deprecated: false,
},
StructureNode {
parent: None,
label: "S",
navigation_range: 201..202,
node_range: 194..213,
kind: SymbolKind(
Static,
),
detail: Some(
"i32",
),
deprecated: false,
},
StructureNode {
parent: None,
label: "C",
navigation_range: 220..221,
node_range: 214..232,
kind: SymbolKind(
Const,
),
detail: Some(
"i32",
),
deprecated: false,
},
StructureNode {
parent: None,
label: "Tr",
navigation_range: 239..241,
node_range: 233..244,
kind: SymbolKind(
Trait,
),
detail: None,
deprecated: false,
},
StructureNode {
parent: None,
label: "Alias",
navigation_range: 251..256,
node_range: 245..262,
kind: SymbolKind(
TraitAlias,
),
detail: None,
deprecated: false,
},
StructureNode {
parent: None,
label: "mc",
navigation_range: 277..279,
node_range: 264..296,
kind: SymbolKind(
Macro,
),
detail: None,
deprecated: false,
},
StructureNode {
parent: None,
label: "let_statements",
navigation_range: 301..315,
node_range: 298..429,
kind: SymbolKind(
Function,
),
detail: Some(
"fn()",
),
deprecated: false,
},
StructureNode {
parent: Some(
15,
),
label: "x",
navigation_range: 328..329,
node_range: 324..335,
kind: SymbolKind(
Local,
),
@ -716,11 +989,11 @@ fn let_statements() {
},
StructureNode {
parent: Some(
27,
15,
),
label: "mut y",
navigation_range: 700..705,
node_range: 696..710,
navigation_range: 344..349,
node_range: 340..354,
kind: SymbolKind(
Local,
),
@ -729,11 +1002,11 @@ fn let_statements() {
},
StructureNode {
parent: Some(
27,
15,
),
label: "Foo { .. }",
navigation_range: 719..741,
node_range: 715..754,
navigation_range: 363..385,
node_range: 359..398,
kind: SymbolKind(
Local,
),
@ -742,11 +1015,11 @@ fn let_statements() {
},
StructureNode {
parent: Some(
27,
15,
),
label: "_",
navigation_range: 804..805,
node_range: 800..812,
navigation_range: 419..420,
node_range: 415..427,
kind: SymbolKind(
Local,
),

View file

@ -81,7 +81,7 @@ pub use crate::{
annotations::{Annotation, AnnotationConfig, AnnotationKind, AnnotationLocation},
call_hierarchy::{CallHierarchyConfig, CallItem},
expand_macro::ExpandedMacro,
file_structure::{StructureNode, StructureNodeKind},
file_structure::{FileStructureConfig, StructureNode, StructureNodeKind},
folding_ranges::{Fold, FoldKind},
highlight_related::{HighlightRelatedConfig, HighlightedRange},
hover::{
@ -430,12 +430,16 @@ impl Analysis {
/// Returns a tree representation of symbols in the file. Useful to draw a
/// file outline.
pub fn file_structure(&self, file_id: FileId) -> Cancellable<Vec<StructureNode>> {
pub fn file_structure(
&self,
config: &FileStructureConfig,
file_id: FileId,
) -> Cancellable<Vec<StructureNode>> {
// FIXME: Edition
self.with_db(|db| {
let editioned_file_id_wrapper = EditionedFileId::current_edition(&self.db, file_id);
file_structure::file_structure(&db.parse(editioned_file_id_wrapper).tree())
let source_file = db.parse(editioned_file_id_wrapper).tree();
file_structure::file_structure(&source_file, config)
})
}

View file

@ -1,5 +1,5 @@
//! Read Rust code on stdin, print syntax tree on stdout.
use ide::Analysis;
use ide::{Analysis, FileStructureConfig};
use crate::cli::{flags, read_stdin};
@ -7,7 +7,12 @@ impl flags::Symbols {
pub fn run(self) -> anyhow::Result<()> {
let text = read_stdin()?;
let (analysis, file_id) = Analysis::from_single_file(text);
let structure = analysis.file_structure(file_id).unwrap();
let structure = analysis
// The default setting in config.rs (document_symbol_search_excludeLocals) is to exclude
// locals because it is unlikely that users want document search to return the names of
// local variables, but here we include them deliberately.
.file_structure(&FileStructureConfig { exclude_locals: false }, file_id)
.unwrap();
for s in structure {
println!("{s:?}");
}

View file

@ -858,6 +858,9 @@ config_data! {
/// check will be performed.
check_workspace: bool = true,
/// Exclude all locals from document symbol search.
document_symbol_search_excludeLocals: bool = true,
/// These proc-macros will be ignored when trying to expand them.
///
/// This config takes a map of crate names with the exported proc-macro names to ignore as values.
@ -1481,6 +1484,13 @@ pub enum FilesWatcher {
Server,
}
/// Configuration for document symbol search requests.
#[derive(Debug, Clone)]
pub struct DocumentSymbolConfig {
/// Should locals be excluded.
pub search_exclude_locals: bool,
}
#[derive(Debug, Clone)]
pub struct NotificationsConfig {
pub cargo_toml_not_found: bool,
@ -2438,6 +2448,12 @@ impl Config {
}
}
pub fn document_symbol(&self, source_root: Option<SourceRootId>) -> DocumentSymbolConfig {
DocumentSymbolConfig {
search_exclude_locals: *self.document_symbol_search_excludeLocals(source_root),
}
}
pub fn workspace_symbol(&self, source_root: Option<SourceRootId>) -> WorkspaceSymbolConfig {
WorkspaceSymbolConfig {
search_exclude_imports: *self.workspace_symbol_search_excludeImports(source_root),

View file

@ -8,8 +8,9 @@ use anyhow::Context;
use base64::{Engine, prelude::BASE64_STANDARD};
use ide::{
AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, CompletionFieldsToResolve,
FilePosition, FileRange, HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query,
RangeInfo, ReferenceCategory, Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit,
FilePosition, FileRange, FileStructureConfig, HoverAction, HoverGotoTypeData,
InlayFieldsToResolve, Query, RangeInfo, ReferenceCategory, Runnable, RunnableKind,
SingleResolve, SourceChange, TextEdit,
};
use ide_db::{FxHashMap, SymbolKind};
use itertools::Itertools;
@ -568,7 +569,14 @@ pub(crate) fn handle_document_symbol(
let mut parents: Vec<(lsp_types::DocumentSymbol, Option<usize>)> = Vec::new();
for symbol in snap.analysis.file_structure(file_id)? {
let config = snap.config.document_symbol(None);
let structure_nodes = snap.analysis.file_structure(
&FileStructureConfig { exclude_locals: config.search_exclude_locals },
file_id,
)?;
for symbol in structure_nodes {
let mut tags = Vec::new();
if symbol.deprecated {
tags.push(SymbolTag::DEPRECATED)
@ -588,8 +596,7 @@ pub(crate) fn handle_document_symbol(
parents.push((doc_symbol, symbol.parent));
}
// Builds hierarchy from a flat list, in reverse order (so that indices
// makes sense)
// Builds hierarchy from a flat list, in reverse order (so that indices make sense)
let document_symbols = {
let mut acc = Vec::new();
while let Some((mut node, parent_idx)) = parents.pop() {

View file

@ -610,6 +610,13 @@ The warnings will be indicated by a blue squiggly underline in code and a blue i
the `Problems Panel`.
## rust-analyzer.document.symbol.search.excludeLocals {#document.symbol.search.excludeLocals}
Default: `true`
Exclude all locals from document symbol search.
## rust-analyzer.files.exclude {#files.exclude}
Default: `[]`

View file

@ -1585,6 +1585,16 @@
}
}
},
{
"title": "Document",
"properties": {
"rust-analyzer.document.symbol.search.excludeLocals": {
"markdownDescription": "Exclude all locals from document symbol search.",
"default": true,
"type": "boolean"
}
}
},
{
"title": "Files",
"properties": {