use ide_db::SymbolKind; use syntax::{ AstNode, AstToken, NodeOrToken, SourceFile, SyntaxNode, SyntaxToken, TextRange, WalkEvent, ast::{self, HasAttrs, HasGenericParams, HasName, HasVisibility}, match_ast, }; #[derive(Debug, Clone)] pub struct StructureNode { pub parent: Option, pub label: String, pub navigation_range: TextRange, pub node_range: TextRange, pub sticky_range: Option, pub kind: StructureNodeKind, pub detail: Option, pub deprecated: bool, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum StructureNodeKind { SymbolKind(SymbolKind), ExternBlock, 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 // // * fuzzy search symbol in a file (super useful) // * draw breadcrumbs to describe the context around the cursor // * draw outline of the file // // | Editor | Shortcut | // |---------|----------| // | VS Code | Ctrl+Shift+O | // // ![File Structure](https://user-images.githubusercontent.com/48062697/113020654-b42fc800-917a-11eb-8388-e7dc4d92b02e.gif) pub(crate) fn file_structure( file: &SourceFile, config: &FileStructureConfig, ) -> Vec { 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, config) { symbol.parent = stack.last().copied(); stack.push(res.len()); res.push(symbol); } } WalkEvent::Leave(NodeOrToken::Node(node)) => { if structure_node(&node, config).is_some() { stack.pop().unwrap(); } } WalkEvent::Enter(NodeOrToken::Token(token)) => { if let Some(mut symbol) = structure_token(token) { symbol.parent = stack.last().copied(); stack.push(res.len()); res.push(symbol); } } WalkEvent::Leave(NodeOrToken::Token(token)) => { if structure_token(token).is_some() { stack.pop().unwrap(); } } } } res } fn structure_node(node: &SyntaxNode, config: &FileStructureConfig) -> Option { fn decl(node: N, kind: StructureNodeKind) -> Option { decl_with_detail(&node, None, kind) } fn decl_with_type_ref( node: &N, type_ref: Option, kind: StructureNodeKind, ) -> Option { let detail = type_ref.map(|type_ref| { let mut detail = String::new(); collapse_ws(type_ref.syntax(), &mut detail); detail }); decl_with_detail(node, detail, kind) } fn decl_with_detail( node: &N, detail: Option, kind: StructureNodeKind, ) -> Option { let name = node.name()?; Some(StructureNode { parent: None, label: name.text().to_string(), navigation_range: name.syntax().text_range(), node_range: node.syntax().text_range(), kind, detail, deprecated: node.attrs().filter_map(|x| x.simple_name()).any(|x| x == "deprecated"), sticky_range: None, }) } fn collapse_ws(node: &SyntaxNode, output: &mut String) { let mut can_insert_ws = false; node.text().for_each_chunk(|chunk| { for line in chunk.lines() { let line = line.trim(); if line.is_empty() { if can_insert_ws { output.push(' '); can_insert_ws = false; } } else { output.push_str(line); can_insert_ws = true; } } }) } match_ast! { match node { ast::Fn(it) => { let mut detail = String::from("fn"); if let Some(type_param_list) = it.generic_param_list() { collapse_ws(type_param_list.syntax(), &mut detail); } let has_self_param = if let Some(param_list) = it.param_list() { collapse_ws(param_list.syntax(), &mut detail); param_list.self_param().is_some() } else { false }; if let Some(ret_type) = it.ret_type() { detail.push(' '); collapse_ws(ret_type.syntax(), &mut detail); } decl_with_detail(&it, Some(detail), StructureNodeKind::SymbolKind(if has_self_param { SymbolKind::Method } else { SymbolKind::Function })).map(|x| { let sticky_range = it.name().zip(it.visibility()).map(|(name, vis)| { let end = it.ret_type().map(|x| x.syntax().text_range().end()).or_else(|| { it.param_list().map(|x| x.syntax().text_range().end()) }).unwrap_or(name.syntax().text_range().end()); TextRange::new(vis.syntax().text_range().start(), end) }); StructureNode { sticky_range, ..x } }) }, ast::Struct(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Struct)), ast::Union(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Union)), ast::Enum(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Enum)), ast::Variant(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Variant)), ast::Trait(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Trait)), ast::Module(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Module)), ast::Macro(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Macro)), ast::TypeAlias(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::TypeAlias)), ast::RecordField(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::Field)), ast::Const(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::Const)), ast::Static(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::Static)), ast::Impl(it) => { let target_type = it.self_ty()?; let target_trait = it.trait_(); let label = match target_trait { None => format!("impl {}", target_type.syntax().text()), Some(t) => { format!("impl {}{} for {}", it.excl_token().map(|x| x.to_string()).unwrap_or_default(), t.syntax().text(), target_type.syntax().text(), ) } }; let node = StructureNode { parent: None, label, navigation_range: target_type.syntax().text_range(), node_range: it.syntax().text_range(), kind: StructureNodeKind::SymbolKind(SymbolKind::Impl), detail: None, deprecated: false, sticky_range: None, }; Some(node) }, ast::LetStmt(it) => { if config.exclude_locals { return None; } let pat = it.pat()?; let mut label = String::new(); collapse_ws(pat.syntax(), &mut label); let node = StructureNode { parent: None, label, navigation_range: pat.syntax().text_range(), node_range: it.syntax().text_range(), kind: StructureNodeKind::SymbolKind(SymbolKind::Local), detail: it.ty().map(|ty| ty.to_string()), deprecated: false, sticky_range: None, }; Some(node) }, ast::ExternBlock(it) => { let mut label = "extern".to_owned(); let abi = it.abi()?; if let Some(abi) = abi.string_token() { label.push(' '); label.push_str(abi.text()); } Some(StructureNode { parent: None, label, navigation_range: abi.syntax().text_range(), node_range: it.syntax().text_range(), kind: StructureNodeKind::ExternBlock, detail: None, deprecated: false, sticky_range: None, }) }, _ => None, } } } fn structure_token(token: SyntaxToken) -> Option { if let Some(comment) = ast::Comment::cast(token) { let text = comment.text().trim(); if let Some(region_name) = text.strip_prefix("// region:").map(str::trim).filter(|it| !it.is_empty()) { return Some(StructureNode { parent: None, label: region_name.to_owned(), navigation_range: comment.syntax().text_range(), node_range: comment.syntax().text_range(), kind: StructureNodeKind::Region, detail: None, deprecated: false, sticky_range: None, }); } } None } #[cfg(test)] mod tests { use expect_test::{Expect, expect}; 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, config); expect.assert_debug_eq(&structure) } #[test] fn test_negative_trait_bound() { let txt = r#"impl !Unpin for Test {}"#; check( txt, expect![[r#" [ StructureNode { parent: None, label: "impl !Unpin for Test", navigation_range: 16..20, node_range: 0..23, kind: SymbolKind( Impl, ), detail: None, deprecated: false, }, ] "#]], ); } #[test] fn test_file_structure() { check( r#" struct Foo { x: i32 } mod m { fn bar1() {} fn bar2(t: T) -> T {} fn bar3(a: A, b: B) -> Vec< u32 > {} } enum E { X, Y(i32) } type T = (); static S: i32 = 92; const C: i32 = 92; trait Tr {} trait Alias = Tr; impl E {} impl fmt::Debug for E {} macro_rules! mc { () => {} } #[macro_export] macro_rules! mcexp { () => {} } /// Doc comment macro_rules! mcexp { () => {} } #[deprecated] fn obsolete() {} #[deprecated(note = "for a while")] fn very_obsolete() {} // region: Some region name // endregion // region: dontpanic mod m { fn f() {} // endregion fn g() {} } extern "C" {} fn let_statements() { let x = 42; let mut y = x; let Foo { .. } = Foo { x }; if let None = Some(x) {} _ = (); let _ = g(); } "#, 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( 0, ), label: "x", 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", ), deprecated: false, }, StructureNode { parent: Some( 2, ), label: "bar3", navigation_range: 89..93, node_range: 86..156, kind: SymbolKind( Function, ), detail: Some( "fn(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( Trait, ), detail: None, deprecated: false, }, StructureNode { parent: None, label: "impl E", navigation_range: 269..270, node_range: 264..273, kind: SymbolKind( Impl, ), detail: None, deprecated: false, }, StructureNode { parent: None, label: "impl fmt::Debug for E", navigation_range: 295..296, node_range: 275..299, kind: SymbolKind( Impl, ), detail: None, deprecated: false, }, StructureNode { parent: None, label: "mc", navigation_range: 314..316, node_range: 301..333, kind: SymbolKind( Macro, ), detail: None, deprecated: false, }, StructureNode { parent: None, label: "mcexp", navigation_range: 364..369, node_range: 335..386, kind: SymbolKind( Macro, ), detail: None, deprecated: false, }, StructureNode { parent: None, label: "mcexp", navigation_range: 417..422, node_range: 388..439, kind: SymbolKind( Macro, ), detail: None, deprecated: false, }, StructureNode { parent: None, label: "obsolete", navigation_range: 458..466, node_range: 441..471, kind: SymbolKind( Function, ), detail: Some( "fn()", ), deprecated: true, }, StructureNode { parent: None, label: "very_obsolete", navigation_range: 512..525, node_range: 473..530, kind: SymbolKind( Function, ), detail: Some( "fn()", ), deprecated: true, }, StructureNode { parent: None, label: "Some region name", navigation_range: 532..559, node_range: 532..559, kind: Region, detail: None, deprecated: false, }, StructureNode { parent: None, label: "m", navigation_range: 599..600, node_range: 574..637, kind: SymbolKind( Module, ), detail: None, deprecated: false, }, StructureNode { parent: Some( 22, ), label: "dontpanic", navigation_range: 574..594, node_range: 574..594, kind: Region, detail: None, deprecated: false, }, StructureNode { parent: Some( 22, ), label: "f", navigation_range: 606..607, node_range: 603..612, kind: SymbolKind( Function, ), detail: Some( "fn()", ), deprecated: false, }, StructureNode { parent: Some( 22, ), label: "g", navigation_range: 629..630, node_range: 613..635, kind: SymbolKind( Function, ), detail: Some( "fn()", ), deprecated: false, }, StructureNode { parent: None, label: "extern \"C\"", navigation_range: 639..649, node_range: 639..652, kind: ExternBlock, detail: None, deprecated: false, }, StructureNode { parent: None, label: "let_statements", navigation_range: 657..671, node_range: 654..814, kind: SymbolKind( Function, ), detail: Some( "fn()", ), 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 {} fn bar3(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( 0, ), label: "x", 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", ), deprecated: false, }, StructureNode { parent: Some( 2, ), label: "bar3", navigation_range: 89..93, node_range: 86..156, kind: SymbolKind( Function, ), detail: Some( "fn(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( Trait, ), 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, ), detail: None, deprecated: false, }, StructureNode { parent: Some( 15, ), label: "mut y", navigation_range: 344..349, node_range: 340..354, kind: SymbolKind( Local, ), detail: None, deprecated: false, }, StructureNode { parent: Some( 15, ), label: "Foo { .. }", navigation_range: 363..385, node_range: 359..398, kind: SymbolKind( Local, ), detail: None, deprecated: false, }, StructureNode { parent: Some( 15, ), label: "_", navigation_range: 419..420, node_range: 415..427, kind: SymbolKind( Local, ), detail: None, deprecated: false, }, ] "#]], ); } }