diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_analysis/src/imp.rs | 4 | ||||
-rw-r--r-- | crates/ra_analysis/src/lib.rs | 5 | ||||
-rw-r--r-- | crates/ra_analysis/src/symbol_index.rs | 123 | ||||
-rw-r--r-- | crates/ra_editor/src/lib.rs | 4 | ||||
-rw-r--r-- | crates/ra_editor/src/structure.rs | 129 | ||||
-rw-r--r-- | crates/ra_editor/src/symbols.rs | 246 |
6 files changed, 255 insertions, 256 deletions
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs index 5669aa94d..f3b513de1 100644 --- a/crates/ra_analysis/src/imp.rs +++ b/crates/ra_analysis/src/imp.rs | |||
@@ -10,7 +10,7 @@ use hir::{ | |||
10 | self, FnSignatureInfo, Problem, source_binder, | 10 | self, FnSignatureInfo, Problem, source_binder, |
11 | }; | 11 | }; |
12 | use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase}; | 12 | use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase}; |
13 | use ra_editor::{self, FileSymbol, find_node_at_offset, LineIndex, LocalEdit, Severity}; | 13 | use ra_editor::{self, find_node_at_offset, LineIndex, LocalEdit, Severity}; |
14 | use ra_syntax::{ | 14 | use ra_syntax::{ |
15 | algo::find_covering_node, | 15 | algo::find_covering_node, |
16 | ast::{self, ArgListOwner, Expr, FnDef, NameOwner}, | 16 | ast::{self, ArgListOwner, Expr, FnDef, NameOwner}, |
@@ -25,7 +25,7 @@ use crate::{ | |||
25 | completion::{CompletionItem, completions}, | 25 | completion::{CompletionItem, completions}, |
26 | CrateId, db, Diagnostic, FileId, FilePosition, FileRange, FileSystemEdit, | 26 | CrateId, db, Diagnostic, FileId, FilePosition, FileRange, FileSystemEdit, |
27 | Query, ReferenceResolution, RootChange, SourceChange, SourceFileEdit, | 27 | Query, ReferenceResolution, RootChange, SourceChange, SourceFileEdit, |
28 | symbol_index::{LibrarySymbolsQuery, SymbolIndex, SymbolsDatabase}, | 28 | symbol_index::{LibrarySymbolsQuery, SymbolIndex, SymbolsDatabase, FileSymbol}, |
29 | }; | 29 | }; |
30 | 30 | ||
31 | #[derive(Debug, Default)] | 31 | #[derive(Debug, Default)] |
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs index e6cfaecc3..ff28271ab 100644 --- a/crates/ra_analysis/src/lib.rs +++ b/crates/ra_analysis/src/lib.rs | |||
@@ -36,10 +36,11 @@ use crate::{ | |||
36 | 36 | ||
37 | pub use crate::{ | 37 | pub use crate::{ |
38 | completion::{CompletionItem, CompletionItemKind, InsertText}, | 38 | completion::{CompletionItem, CompletionItemKind, InsertText}, |
39 | runnables::{Runnable, RunnableKind} | 39 | runnables::{Runnable, RunnableKind}, |
40 | symbol_index::FileSymbol, | ||
40 | }; | 41 | }; |
41 | pub use ra_editor::{ | 42 | pub use ra_editor::{ |
42 | FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, StructureNode, Severity | 43 | Fold, FoldKind, HighlightedRange, LineIndex, StructureNode, Severity |
43 | }; | 44 | }; |
44 | pub use hir::FnSignatureInfo; | 45 | pub use hir::FnSignatureInfo; |
45 | 46 | ||
diff --git a/crates/ra_analysis/src/symbol_index.rs b/crates/ra_analysis/src/symbol_index.rs index e5bdf0aa1..edb2268fb 100644 --- a/crates/ra_analysis/src/symbol_index.rs +++ b/crates/ra_analysis/src/symbol_index.rs | |||
@@ -4,10 +4,11 @@ use std::{ | |||
4 | }; | 4 | }; |
5 | 5 | ||
6 | use fst::{self, Streamer}; | 6 | use fst::{self, Streamer}; |
7 | use ra_editor::{self, FileSymbol}; | ||
8 | use ra_syntax::{ | 7 | use ra_syntax::{ |
9 | SourceFileNode, | 8 | AstNode, SyntaxNodeRef, SourceFileNode, SmolStr, TextRange, |
9 | algo::visit::{visitor, Visitor}, | ||
10 | SyntaxKind::{self, *}, | 10 | SyntaxKind::{self, *}, |
11 | ast::{self, NameOwner, DocCommentsOwner}, | ||
11 | }; | 12 | }; |
12 | use ra_db::{SyntaxDatabase, SourceRootId}; | 13 | use ra_db::{SyntaxDatabase, SourceRootId}; |
13 | use rayon::prelude::*; | 14 | use rayon::prelude::*; |
@@ -65,8 +66,9 @@ impl SymbolIndex { | |||
65 | ) -> SymbolIndex { | 66 | ) -> SymbolIndex { |
66 | let mut symbols = files | 67 | let mut symbols = files |
67 | .flat_map(|(file_id, file)| { | 68 | .flat_map(|(file_id, file)| { |
68 | ra_editor::file_symbols(&file) | 69 | file.syntax() |
69 | .into_iter() | 70 | .descendants() |
71 | .filter_map(to_symbol) | ||
70 | .map(move |symbol| (symbol.name.as_str().to_lowercase(), (file_id, symbol))) | 72 | .map(move |symbol| (symbol.name.as_str().to_lowercase(), (file_id, symbol))) |
71 | .collect::<Vec<_>>() | 73 | .collect::<Vec<_>>() |
72 | }) | 74 | }) |
@@ -121,3 +123,116 @@ fn is_type(kind: SyntaxKind) -> bool { | |||
121 | _ => false, | 123 | _ => false, |
122 | } | 124 | } |
123 | } | 125 | } |
126 | |||
127 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
128 | pub struct FileSymbol { | ||
129 | pub name: SmolStr, | ||
130 | pub node_range: TextRange, | ||
131 | pub kind: SyntaxKind, | ||
132 | } | ||
133 | |||
134 | impl FileSymbol { | ||
135 | pub fn docs(&self, file: &SourceFileNode) -> Option<String> { | ||
136 | file.syntax() | ||
137 | .descendants() | ||
138 | .filter(|node| node.kind() == self.kind && node.range() == self.node_range) | ||
139 | .filter_map(|node: SyntaxNodeRef| { | ||
140 | fn doc_comments<'a, N: DocCommentsOwner<'a>>(node: N) -> Option<String> { | ||
141 | let comments = node.doc_comment_text(); | ||
142 | if comments.is_empty() { | ||
143 | None | ||
144 | } else { | ||
145 | Some(comments) | ||
146 | } | ||
147 | } | ||
148 | |||
149 | visitor() | ||
150 | .visit(doc_comments::<ast::FnDef>) | ||
151 | .visit(doc_comments::<ast::StructDef>) | ||
152 | .visit(doc_comments::<ast::EnumDef>) | ||
153 | .visit(doc_comments::<ast::TraitDef>) | ||
154 | .visit(doc_comments::<ast::Module>) | ||
155 | .visit(doc_comments::<ast::TypeDef>) | ||
156 | .visit(doc_comments::<ast::ConstDef>) | ||
157 | .visit(doc_comments::<ast::StaticDef>) | ||
158 | .accept(node)? | ||
159 | }) | ||
160 | .nth(0) | ||
161 | } | ||
162 | /// Get a description of this node. | ||
163 | /// | ||
164 | /// e.g. `struct Name`, `enum Name`, `fn Name` | ||
165 | pub fn description(&self, file: &SourceFileNode) -> Option<String> { | ||
166 | // TODO: After type inference is done, add type information to improve the output | ||
167 | file.syntax() | ||
168 | .descendants() | ||
169 | .filter(|node| node.kind() == self.kind && node.range() == self.node_range) | ||
170 | .filter_map(|node: SyntaxNodeRef| { | ||
171 | // TODO: Refactor to be have less repetition | ||
172 | visitor() | ||
173 | .visit(|node: ast::FnDef| { | ||
174 | let mut string = "fn ".to_string(); | ||
175 | node.name()?.syntax().text().push_to(&mut string); | ||
176 | Some(string) | ||
177 | }) | ||
178 | .visit(|node: ast::StructDef| { | ||
179 | let mut string = "struct ".to_string(); | ||
180 | node.name()?.syntax().text().push_to(&mut string); | ||
181 | Some(string) | ||
182 | }) | ||
183 | .visit(|node: ast::EnumDef| { | ||
184 | let mut string = "enum ".to_string(); | ||
185 | node.name()?.syntax().text().push_to(&mut string); | ||
186 | Some(string) | ||
187 | }) | ||
188 | .visit(|node: ast::TraitDef| { | ||
189 | let mut string = "trait ".to_string(); | ||
190 | node.name()?.syntax().text().push_to(&mut string); | ||
191 | Some(string) | ||
192 | }) | ||
193 | .visit(|node: ast::Module| { | ||
194 | let mut string = "mod ".to_string(); | ||
195 | node.name()?.syntax().text().push_to(&mut string); | ||
196 | Some(string) | ||
197 | }) | ||
198 | .visit(|node: ast::TypeDef| { | ||
199 | let mut string = "type ".to_string(); | ||
200 | node.name()?.syntax().text().push_to(&mut string); | ||
201 | Some(string) | ||
202 | }) | ||
203 | .visit(|node: ast::ConstDef| { | ||
204 | let mut string = "const ".to_string(); | ||
205 | node.name()?.syntax().text().push_to(&mut string); | ||
206 | Some(string) | ||
207 | }) | ||
208 | .visit(|node: ast::StaticDef| { | ||
209 | let mut string = "static ".to_string(); | ||
210 | node.name()?.syntax().text().push_to(&mut string); | ||
211 | Some(string) | ||
212 | }) | ||
213 | .accept(node)? | ||
214 | }) | ||
215 | .nth(0) | ||
216 | } | ||
217 | } | ||
218 | |||
219 | fn to_symbol(node: SyntaxNodeRef) -> Option<FileSymbol> { | ||
220 | fn decl<'a, N: NameOwner<'a>>(node: N) -> Option<FileSymbol> { | ||
221 | let name = node.name()?; | ||
222 | Some(FileSymbol { | ||
223 | name: name.text(), | ||
224 | node_range: node.syntax().range(), | ||
225 | kind: node.syntax().kind(), | ||
226 | }) | ||
227 | } | ||
228 | visitor() | ||
229 | .visit(decl::<ast::FnDef>) | ||
230 | .visit(decl::<ast::StructDef>) | ||
231 | .visit(decl::<ast::EnumDef>) | ||
232 | .visit(decl::<ast::TraitDef>) | ||
233 | .visit(decl::<ast::Module>) | ||
234 | .visit(decl::<ast::TypeDef>) | ||
235 | .visit(decl::<ast::ConstDef>) | ||
236 | .visit(decl::<ast::StaticDef>) | ||
237 | .accept(node)? | ||
238 | } | ||
diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs index b03f9ea54..bfc745e58 100644 --- a/crates/ra_editor/src/lib.rs +++ b/crates/ra_editor/src/lib.rs | |||
@@ -3,7 +3,7 @@ mod extend_selection; | |||
3 | mod folding_ranges; | 3 | mod folding_ranges; |
4 | mod line_index; | 4 | mod line_index; |
5 | mod line_index_utils; | 5 | mod line_index_utils; |
6 | mod symbols; | 6 | mod structure; |
7 | #[cfg(test)] | 7 | #[cfg(test)] |
8 | mod test_utils; | 8 | mod test_utils; |
9 | mod typing; | 9 | mod typing; |
@@ -15,7 +15,7 @@ pub use self::{ | |||
15 | folding_ranges::{folding_ranges, Fold, FoldKind}, | 15 | folding_ranges::{folding_ranges, Fold, FoldKind}, |
16 | line_index::{LineCol, LineIndex}, | 16 | line_index::{LineCol, LineIndex}, |
17 | line_index_utils::translate_offset_with_edit, | 17 | line_index_utils::translate_offset_with_edit, |
18 | symbols::{file_structure, file_symbols, FileSymbol, StructureNode}, | 18 | structure::{file_structure, StructureNode}, |
19 | typing::{join_lines, on_enter, on_eq_typed}, | 19 | typing::{join_lines, on_enter, on_eq_typed}, |
20 | diagnostics::diagnostics | 20 | diagnostics::diagnostics |
21 | }; | 21 | }; |
diff --git a/crates/ra_editor/src/structure.rs b/crates/ra_editor/src/structure.rs new file mode 100644 index 000000000..2292b1ddf --- /dev/null +++ b/crates/ra_editor/src/structure.rs | |||
@@ -0,0 +1,129 @@ | |||
1 | use crate::TextRange; | ||
2 | |||
3 | use ra_syntax::{ | ||
4 | algo::visit::{visitor, Visitor}, | ||
5 | ast::{self, NameOwner}, | ||
6 | AstNode, SourceFileNode, SyntaxKind, SyntaxNodeRef, WalkEvent, | ||
7 | }; | ||
8 | |||
9 | #[derive(Debug, Clone)] | ||
10 | pub struct StructureNode { | ||
11 | pub parent: Option<usize>, | ||
12 | pub label: String, | ||
13 | pub navigation_range: TextRange, | ||
14 | pub node_range: TextRange, | ||
15 | pub kind: SyntaxKind, | ||
16 | } | ||
17 | |||
18 | pub fn file_structure(file: &SourceFileNode) -> Vec<StructureNode> { | ||
19 | let mut res = Vec::new(); | ||
20 | let mut stack = Vec::new(); | ||
21 | |||
22 | for event in file.syntax().preorder() { | ||
23 | match event { | ||
24 | WalkEvent::Enter(node) => { | ||
25 | if let Some(mut symbol) = structure_node(node) { | ||
26 | symbol.parent = stack.last().map(|&n| n); | ||
27 | stack.push(res.len()); | ||
28 | res.push(symbol); | ||
29 | } | ||
30 | } | ||
31 | WalkEvent::Leave(node) => { | ||
32 | if structure_node(node).is_some() { | ||
33 | stack.pop().unwrap(); | ||
34 | } | ||
35 | } | ||
36 | } | ||
37 | } | ||
38 | res | ||
39 | } | ||
40 | |||
41 | fn structure_node(node: SyntaxNodeRef) -> Option<StructureNode> { | ||
42 | fn decl<'a, N: NameOwner<'a>>(node: N) -> Option<StructureNode> { | ||
43 | let name = node.name()?; | ||
44 | Some(StructureNode { | ||
45 | parent: None, | ||
46 | label: name.text().to_string(), | ||
47 | navigation_range: name.syntax().range(), | ||
48 | node_range: node.syntax().range(), | ||
49 | kind: node.syntax().kind(), | ||
50 | }) | ||
51 | } | ||
52 | |||
53 | visitor() | ||
54 | .visit(decl::<ast::FnDef>) | ||
55 | .visit(decl::<ast::StructDef>) | ||
56 | .visit(decl::<ast::NamedFieldDef>) | ||
57 | .visit(decl::<ast::EnumDef>) | ||
58 | .visit(decl::<ast::TraitDef>) | ||
59 | .visit(decl::<ast::Module>) | ||
60 | .visit(decl::<ast::TypeDef>) | ||
61 | .visit(decl::<ast::ConstDef>) | ||
62 | .visit(decl::<ast::StaticDef>) | ||
63 | .visit(|im: ast::ImplItem| { | ||
64 | let target_type = im.target_type()?; | ||
65 | let target_trait = im.target_trait(); | ||
66 | let label = match target_trait { | ||
67 | None => format!("impl {}", target_type.syntax().text()), | ||
68 | Some(t) => format!( | ||
69 | "impl {} for {}", | ||
70 | t.syntax().text(), | ||
71 | target_type.syntax().text(), | ||
72 | ), | ||
73 | }; | ||
74 | |||
75 | let node = StructureNode { | ||
76 | parent: None, | ||
77 | label, | ||
78 | navigation_range: target_type.syntax().range(), | ||
79 | node_range: im.syntax().range(), | ||
80 | kind: im.syntax().kind(), | ||
81 | }; | ||
82 | Some(node) | ||
83 | }) | ||
84 | .accept(node)? | ||
85 | } | ||
86 | |||
87 | #[cfg(test)] | ||
88 | mod tests { | ||
89 | use super::*; | ||
90 | use test_utils::assert_eq_dbg; | ||
91 | |||
92 | #[test] | ||
93 | fn test_file_structure() { | ||
94 | let file = SourceFileNode::parse( | ||
95 | r#" | ||
96 | struct Foo { | ||
97 | x: i32 | ||
98 | } | ||
99 | |||
100 | mod m { | ||
101 | fn bar() {} | ||
102 | } | ||
103 | |||
104 | enum E { X, Y(i32) } | ||
105 | type T = (); | ||
106 | static S: i32 = 92; | ||
107 | const C: i32 = 92; | ||
108 | |||
109 | impl E {} | ||
110 | |||
111 | impl fmt::Debug for E {} | ||
112 | "#, | ||
113 | ); | ||
114 | let structure = file_structure(&file); | ||
115 | assert_eq_dbg( | ||
116 | r#"[StructureNode { parent: None, label: "Foo", navigation_range: [8; 11), node_range: [1; 26), kind: STRUCT_DEF }, | ||
117 | StructureNode { parent: Some(0), label: "x", navigation_range: [18; 19), node_range: [18; 24), kind: NAMED_FIELD_DEF }, | ||
118 | StructureNode { parent: None, label: "m", navigation_range: [32; 33), node_range: [28; 53), kind: MODULE }, | ||
119 | StructureNode { parent: Some(2), label: "bar", navigation_range: [43; 46), node_range: [40; 51), kind: FN_DEF }, | ||
120 | StructureNode { parent: None, label: "E", navigation_range: [60; 61), node_range: [55; 75), kind: ENUM_DEF }, | ||
121 | StructureNode { parent: None, label: "T", navigation_range: [81; 82), node_range: [76; 88), kind: TYPE_DEF }, | ||
122 | StructureNode { parent: None, label: "S", navigation_range: [96; 97), node_range: [89; 108), kind: STATIC_DEF }, | ||
123 | StructureNode { parent: None, label: "C", navigation_range: [115; 116), node_range: [109; 127), kind: CONST_DEF }, | ||
124 | StructureNode { parent: None, label: "impl E", navigation_range: [134; 135), node_range: [129; 138), kind: IMPL_ITEM }, | ||
125 | StructureNode { parent: None, label: "impl fmt::Debug for E", navigation_range: [160; 161), node_range: [140; 164), kind: IMPL_ITEM }]"#, | ||
126 | &structure, | ||
127 | ) | ||
128 | } | ||
129 | } | ||
diff --git a/crates/ra_editor/src/symbols.rs b/crates/ra_editor/src/symbols.rs deleted file mode 100644 index 9e25decfb..000000000 --- a/crates/ra_editor/src/symbols.rs +++ /dev/null | |||
@@ -1,246 +0,0 @@ | |||
1 | use crate::TextRange; | ||
2 | |||
3 | use ra_syntax::{ | ||
4 | algo::visit::{visitor, Visitor}, | ||
5 | ast::{self, DocCommentsOwner, NameOwner}, | ||
6 | AstNode, SourceFileNode, SmolStr, SyntaxKind, SyntaxNodeRef, WalkEvent, | ||
7 | }; | ||
8 | |||
9 | #[derive(Debug, Clone)] | ||
10 | pub struct StructureNode { | ||
11 | pub parent: Option<usize>, | ||
12 | pub label: String, | ||
13 | pub navigation_range: TextRange, | ||
14 | pub node_range: TextRange, | ||
15 | pub kind: SyntaxKind, | ||
16 | } | ||
17 | |||
18 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
19 | pub struct FileSymbol { | ||
20 | pub name: SmolStr, | ||
21 | pub node_range: TextRange, | ||
22 | pub kind: SyntaxKind, | ||
23 | } | ||
24 | |||
25 | impl FileSymbol { | ||
26 | pub fn docs(&self, file: &SourceFileNode) -> Option<String> { | ||
27 | file.syntax() | ||
28 | .descendants() | ||
29 | .filter(|node| node.kind() == self.kind && node.range() == self.node_range) | ||
30 | .filter_map(|node: SyntaxNodeRef| { | ||
31 | fn doc_comments<'a, N: DocCommentsOwner<'a>>(node: N) -> Option<String> { | ||
32 | let comments = node.doc_comment_text(); | ||
33 | if comments.is_empty() { | ||
34 | None | ||
35 | } else { | ||
36 | Some(comments) | ||
37 | } | ||
38 | } | ||
39 | |||
40 | visitor() | ||
41 | .visit(doc_comments::<ast::FnDef>) | ||
42 | .visit(doc_comments::<ast::StructDef>) | ||
43 | .visit(doc_comments::<ast::EnumDef>) | ||
44 | .visit(doc_comments::<ast::TraitDef>) | ||
45 | .visit(doc_comments::<ast::Module>) | ||
46 | .visit(doc_comments::<ast::TypeDef>) | ||
47 | .visit(doc_comments::<ast::ConstDef>) | ||
48 | .visit(doc_comments::<ast::StaticDef>) | ||
49 | .accept(node)? | ||
50 | }) | ||
51 | .nth(0) | ||
52 | } | ||
53 | /// Get a description of this node. | ||
54 | /// | ||
55 | /// e.g. `struct Name`, `enum Name`, `fn Name` | ||
56 | pub fn description(&self, file: &SourceFileNode) -> Option<String> { | ||
57 | // TODO: After type inference is done, add type information to improve the output | ||
58 | file.syntax() | ||
59 | .descendants() | ||
60 | .filter(|node| node.kind() == self.kind && node.range() == self.node_range) | ||
61 | .filter_map(|node: SyntaxNodeRef| { | ||
62 | // TODO: Refactor to be have less repetition | ||
63 | visitor() | ||
64 | .visit(|node: ast::FnDef| { | ||
65 | let mut string = "fn ".to_string(); | ||
66 | node.name()?.syntax().text().push_to(&mut string); | ||
67 | Some(string) | ||
68 | }) | ||
69 | .visit(|node: ast::StructDef| { | ||
70 | let mut string = "struct ".to_string(); | ||
71 | node.name()?.syntax().text().push_to(&mut string); | ||
72 | Some(string) | ||
73 | }) | ||
74 | .visit(|node: ast::EnumDef| { | ||
75 | let mut string = "enum ".to_string(); | ||
76 | node.name()?.syntax().text().push_to(&mut string); | ||
77 | Some(string) | ||
78 | }) | ||
79 | .visit(|node: ast::TraitDef| { | ||
80 | let mut string = "trait ".to_string(); | ||
81 | node.name()?.syntax().text().push_to(&mut string); | ||
82 | Some(string) | ||
83 | }) | ||
84 | .visit(|node: ast::Module| { | ||
85 | let mut string = "mod ".to_string(); | ||
86 | node.name()?.syntax().text().push_to(&mut string); | ||
87 | Some(string) | ||
88 | }) | ||
89 | .visit(|node: ast::TypeDef| { | ||
90 | let mut string = "type ".to_string(); | ||
91 | node.name()?.syntax().text().push_to(&mut string); | ||
92 | Some(string) | ||
93 | }) | ||
94 | .visit(|node: ast::ConstDef| { | ||
95 | let mut string = "const ".to_string(); | ||
96 | node.name()?.syntax().text().push_to(&mut string); | ||
97 | Some(string) | ||
98 | }) | ||
99 | .visit(|node: ast::StaticDef| { | ||
100 | let mut string = "static ".to_string(); | ||
101 | node.name()?.syntax().text().push_to(&mut string); | ||
102 | Some(string) | ||
103 | }) | ||
104 | .accept(node)? | ||
105 | }) | ||
106 | .nth(0) | ||
107 | } | ||
108 | } | ||
109 | |||
110 | pub fn file_symbols(file: &SourceFileNode) -> Vec<FileSymbol> { | ||
111 | file.syntax().descendants().filter_map(to_symbol).collect() | ||
112 | } | ||
113 | |||
114 | fn to_symbol(node: SyntaxNodeRef) -> Option<FileSymbol> { | ||
115 | fn decl<'a, N: NameOwner<'a>>(node: N) -> Option<FileSymbol> { | ||
116 | let name = node.name()?; | ||
117 | Some(FileSymbol { | ||
118 | name: name.text(), | ||
119 | node_range: node.syntax().range(), | ||
120 | kind: node.syntax().kind(), | ||
121 | }) | ||
122 | } | ||
123 | visitor() | ||
124 | .visit(decl::<ast::FnDef>) | ||
125 | .visit(decl::<ast::StructDef>) | ||
126 | .visit(decl::<ast::EnumDef>) | ||
127 | .visit(decl::<ast::TraitDef>) | ||
128 | .visit(decl::<ast::Module>) | ||
129 | .visit(decl::<ast::TypeDef>) | ||
130 | .visit(decl::<ast::ConstDef>) | ||
131 | .visit(decl::<ast::StaticDef>) | ||
132 | .accept(node)? | ||
133 | } | ||
134 | |||
135 | pub fn file_structure(file: &SourceFileNode) -> Vec<StructureNode> { | ||
136 | let mut res = Vec::new(); | ||
137 | let mut stack = Vec::new(); | ||
138 | |||
139 | for event in file.syntax().preorder() { | ||
140 | match event { | ||
141 | WalkEvent::Enter(node) => { | ||
142 | if let Some(mut symbol) = structure_node(node) { | ||
143 | symbol.parent = stack.last().map(|&n| n); | ||
144 | stack.push(res.len()); | ||
145 | res.push(symbol); | ||
146 | } | ||
147 | } | ||
148 | WalkEvent::Leave(node) => { | ||
149 | if structure_node(node).is_some() { | ||
150 | stack.pop().unwrap(); | ||
151 | } | ||
152 | } | ||
153 | } | ||
154 | } | ||
155 | res | ||
156 | } | ||
157 | |||
158 | fn structure_node(node: SyntaxNodeRef) -> Option<StructureNode> { | ||
159 | fn decl<'a, N: NameOwner<'a>>(node: N) -> Option<StructureNode> { | ||
160 | let name = node.name()?; | ||
161 | Some(StructureNode { | ||
162 | parent: None, | ||
163 | label: name.text().to_string(), | ||
164 | navigation_range: name.syntax().range(), | ||
165 | node_range: node.syntax().range(), | ||
166 | kind: node.syntax().kind(), | ||
167 | }) | ||
168 | } | ||
169 | |||
170 | visitor() | ||
171 | .visit(decl::<ast::FnDef>) | ||
172 | .visit(decl::<ast::StructDef>) | ||
173 | .visit(decl::<ast::NamedFieldDef>) | ||
174 | .visit(decl::<ast::EnumDef>) | ||
175 | .visit(decl::<ast::TraitDef>) | ||
176 | .visit(decl::<ast::Module>) | ||
177 | .visit(decl::<ast::TypeDef>) | ||
178 | .visit(decl::<ast::ConstDef>) | ||
179 | .visit(decl::<ast::StaticDef>) | ||
180 | .visit(|im: ast::ImplItem| { | ||
181 | let target_type = im.target_type()?; | ||
182 | let target_trait = im.target_trait(); | ||
183 | let label = match target_trait { | ||
184 | None => format!("impl {}", target_type.syntax().text()), | ||
185 | Some(t) => format!( | ||
186 | "impl {} for {}", | ||
187 | t.syntax().text(), | ||
188 | target_type.syntax().text(), | ||
189 | ), | ||
190 | }; | ||
191 | |||
192 | let node = StructureNode { | ||
193 | parent: None, | ||
194 | label, | ||
195 | navigation_range: target_type.syntax().range(), | ||
196 | node_range: im.syntax().range(), | ||
197 | kind: im.syntax().kind(), | ||
198 | }; | ||
199 | Some(node) | ||
200 | }) | ||
201 | .accept(node)? | ||
202 | } | ||
203 | |||
204 | #[cfg(test)] | ||
205 | mod tests { | ||
206 | use super::*; | ||
207 | use test_utils::assert_eq_dbg; | ||
208 | |||
209 | #[test] | ||
210 | fn test_file_structure() { | ||
211 | let file = SourceFileNode::parse( | ||
212 | r#" | ||
213 | struct Foo { | ||
214 | x: i32 | ||
215 | } | ||
216 | |||
217 | mod m { | ||
218 | fn bar() {} | ||
219 | } | ||
220 | |||
221 | enum E { X, Y(i32) } | ||
222 | type T = (); | ||
223 | static S: i32 = 92; | ||
224 | const C: i32 = 92; | ||
225 | |||
226 | impl E {} | ||
227 | |||
228 | impl fmt::Debug for E {} | ||
229 | "#, | ||
230 | ); | ||
231 | let symbols = file_structure(&file); | ||
232 | assert_eq_dbg( | ||
233 | r#"[StructureNode { parent: None, label: "Foo", navigation_range: [8; 11), node_range: [1; 26), kind: STRUCT_DEF }, | ||
234 | StructureNode { parent: Some(0), label: "x", navigation_range: [18; 19), node_range: [18; 24), kind: NAMED_FIELD_DEF }, | ||
235 | StructureNode { parent: None, label: "m", navigation_range: [32; 33), node_range: [28; 53), kind: MODULE }, | ||
236 | StructureNode { parent: Some(2), label: "bar", navigation_range: [43; 46), node_range: [40; 51), kind: FN_DEF }, | ||
237 | StructureNode { parent: None, label: "E", navigation_range: [60; 61), node_range: [55; 75), kind: ENUM_DEF }, | ||
238 | StructureNode { parent: None, label: "T", navigation_range: [81; 82), node_range: [76; 88), kind: TYPE_DEF }, | ||
239 | StructureNode { parent: None, label: "S", navigation_range: [96; 97), node_range: [89; 108), kind: STATIC_DEF }, | ||
240 | StructureNode { parent: None, label: "C", navigation_range: [115; 116), node_range: [109; 127), kind: CONST_DEF }, | ||
241 | StructureNode { parent: None, label: "impl E", navigation_range: [134; 135), node_range: [129; 138), kind: IMPL_ITEM }, | ||
242 | StructureNode { parent: None, label: "impl fmt::Debug for E", navigation_range: [160; 161), node_range: [140; 164), kind: IMPL_ITEM }]"#, | ||
243 | &symbols, | ||
244 | ) | ||
245 | } | ||
246 | } | ||