aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/file_structure.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/file_structure.rs')
-rw-r--r--crates/ra_ide/src/file_structure.rs431
1 files changed, 431 insertions, 0 deletions
diff --git a/crates/ra_ide/src/file_structure.rs b/crates/ra_ide/src/file_structure.rs
new file mode 100644
index 000000000..91765140a
--- /dev/null
+++ b/crates/ra_ide/src/file_structure.rs
@@ -0,0 +1,431 @@
1use ra_syntax::{
2 ast::{self, AttrsOwner, GenericParamsOwner, NameOwner},
3 match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, WalkEvent,
4};
5
6#[derive(Debug, Clone)]
7pub struct StructureNode {
8 pub parent: Option<usize>,
9 pub label: String,
10 pub navigation_range: TextRange,
11 pub node_range: TextRange,
12 pub kind: SyntaxKind,
13 pub detail: Option<String>,
14 pub deprecated: bool,
15}
16
17// Feature: File Structure
18//
19// Provides a tree of the symbols defined in the file. Can be used to
20//
21// * fuzzy search symbol in a file (super useful)
22// * draw breadcrumbs to describe the context around the cursor
23// * draw outline of the file
24//
25// |===
26// | Editor | Shortcut
27//
28// | VS Code | kbd:[Ctrl+Shift+O]
29// |===
30pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> {
31 let mut res = Vec::new();
32 let mut stack = Vec::new();
33
34 for event in file.syntax().preorder() {
35 match event {
36 WalkEvent::Enter(node) => {
37 if let Some(mut symbol) = structure_node(&node) {
38 symbol.parent = stack.last().copied();
39 stack.push(res.len());
40 res.push(symbol);
41 }
42 }
43 WalkEvent::Leave(node) => {
44 if structure_node(&node).is_some() {
45 stack.pop().unwrap();
46 }
47 }
48 }
49 }
50 res
51}
52
53fn structure_node(node: &SyntaxNode) -> Option<StructureNode> {
54 fn decl<N: NameOwner + AttrsOwner>(node: N) -> Option<StructureNode> {
55 decl_with_detail(&node, None)
56 }
57
58 fn decl_with_type_ref<N: NameOwner + AttrsOwner>(
59 node: &N,
60 type_ref: Option<ast::TypeRef>,
61 ) -> Option<StructureNode> {
62 let detail = type_ref.map(|type_ref| {
63 let mut detail = String::new();
64 collapse_ws(type_ref.syntax(), &mut detail);
65 detail
66 });
67 decl_with_detail(node, detail)
68 }
69
70 fn decl_with_detail<N: NameOwner + AttrsOwner>(
71 node: &N,
72 detail: Option<String>,
73 ) -> Option<StructureNode> {
74 let name = node.name()?;
75
76 Some(StructureNode {
77 parent: None,
78 label: name.text().to_string(),
79 navigation_range: name.syntax().text_range(),
80 node_range: node.syntax().text_range(),
81 kind: node.syntax().kind(),
82 detail,
83 deprecated: node.attrs().filter_map(|x| x.simple_name()).any(|x| x == "deprecated"),
84 })
85 }
86
87 fn collapse_ws(node: &SyntaxNode, output: &mut String) {
88 let mut can_insert_ws = false;
89 node.text().for_each_chunk(|chunk| {
90 for line in chunk.lines() {
91 let line = line.trim();
92 if line.is_empty() {
93 if can_insert_ws {
94 output.push(' ');
95 can_insert_ws = false;
96 }
97 } else {
98 output.push_str(line);
99 can_insert_ws = true;
100 }
101 }
102 })
103 }
104
105 match_ast! {
106 match node {
107 ast::Fn(it) => {
108 let mut detail = String::from("fn");
109 if let Some(type_param_list) = it.generic_param_list() {
110 collapse_ws(type_param_list.syntax(), &mut detail);
111 }
112 if let Some(param_list) = it.param_list() {
113 collapse_ws(param_list.syntax(), &mut detail);
114 }
115 if let Some(ret_type) = it.ret_type() {
116 detail.push_str(" ");
117 collapse_ws(ret_type.syntax(), &mut detail);
118 }
119
120 decl_with_detail(&it, Some(detail))
121 },
122 ast::Struct(it) => decl(it),
123 ast::Union(it) => decl(it),
124 ast::Enum(it) => decl(it),
125 ast::Variant(it) => decl(it),
126 ast::Trait(it) => decl(it),
127 ast::Module(it) => decl(it),
128 ast::TypeAlias(it) => decl_with_type_ref(&it, it.ty()),
129 ast::RecordField(it) => decl_with_type_ref(&it, it.ty()),
130 ast::Const(it) => decl_with_type_ref(&it, it.ty()),
131 ast::Static(it) => decl_with_type_ref(&it, it.ty()),
132 ast::Impl(it) => {
133 let target_type = it.target_type()?;
134 let target_trait = it.target_trait();
135 let label = match target_trait {
136 None => format!("impl {}", target_type.syntax().text()),
137 Some(t) => {
138 format!("impl {} for {}", t.syntax().text(), target_type.syntax().text(),)
139 }
140 };
141
142 let node = StructureNode {
143 parent: None,
144 label,
145 navigation_range: target_type.syntax().text_range(),
146 node_range: it.syntax().text_range(),
147 kind: it.syntax().kind(),
148 detail: None,
149 deprecated: false,
150 };
151 Some(node)
152 },
153 ast::MacroCall(it) => {
154 match it.path().and_then(|it| it.segment()).and_then(|it| it.name_ref()) {
155 Some(path_segment) if path_segment.text() == "macro_rules"
156 => decl(it),
157 _ => None,
158 }
159 },
160 _ => None,
161 }
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use expect::{expect, Expect};
168
169 use super::*;
170
171 fn check(ra_fixture: &str, expect: Expect) {
172 let file = SourceFile::parse(ra_fixture).ok().unwrap();
173 let structure = file_structure(&file);
174 expect.assert_debug_eq(&structure)
175 }
176
177 #[test]
178 fn test_file_structure() {
179 check(
180 r#"
181struct Foo {
182 x: i32
183}
184
185mod m {
186 fn bar1() {}
187 fn bar2<T>(t: T) -> T {}
188 fn bar3<A,
189 B>(a: A,
190 b: B) -> Vec<
191 u32
192 > {}
193}
194
195enum E { X, Y(i32) }
196type T = ();
197static S: i32 = 92;
198const C: i32 = 92;
199
200impl E {}
201
202impl fmt::Debug for E {}
203
204macro_rules! mc {
205 () => {}
206}
207
208#[macro_export]
209macro_rules! mcexp {
210 () => {}
211}
212
213/// Doc comment
214macro_rules! mcexp {
215 () => {}
216}
217
218#[deprecated]
219fn obsolete() {}
220
221#[deprecated(note = "for awhile")]
222fn very_obsolete() {}
223"#,
224 expect![[r#"
225 [
226 StructureNode {
227 parent: None,
228 label: "Foo",
229 navigation_range: 8..11,
230 node_range: 1..26,
231 kind: STRUCT,
232 detail: None,
233 deprecated: false,
234 },
235 StructureNode {
236 parent: Some(
237 0,
238 ),
239 label: "x",
240 navigation_range: 18..19,
241 node_range: 18..24,
242 kind: RECORD_FIELD,
243 detail: Some(
244 "i32",
245 ),
246 deprecated: false,
247 },
248 StructureNode {
249 parent: None,
250 label: "m",
251 navigation_range: 32..33,
252 node_range: 28..158,
253 kind: MODULE,
254 detail: None,
255 deprecated: false,
256 },
257 StructureNode {
258 parent: Some(
259 2,
260 ),
261 label: "bar1",
262 navigation_range: 43..47,
263 node_range: 40..52,
264 kind: FN,
265 detail: Some(
266 "fn()",
267 ),
268 deprecated: false,
269 },
270 StructureNode {
271 parent: Some(
272 2,
273 ),
274 label: "bar2",
275 navigation_range: 60..64,
276 node_range: 57..81,
277 kind: FN,
278 detail: Some(
279 "fn<T>(t: T) -> T",
280 ),
281 deprecated: false,
282 },
283 StructureNode {
284 parent: Some(
285 2,
286 ),
287 label: "bar3",
288 navigation_range: 89..93,
289 node_range: 86..156,
290 kind: FN,
291 detail: Some(
292 "fn<A, B>(a: A, b: B) -> Vec< u32 >",
293 ),
294 deprecated: false,
295 },
296 StructureNode {
297 parent: None,
298 label: "E",
299 navigation_range: 165..166,
300 node_range: 160..180,
301 kind: ENUM,
302 detail: None,
303 deprecated: false,
304 },
305 StructureNode {
306 parent: Some(
307 6,
308 ),
309 label: "X",
310 navigation_range: 169..170,
311 node_range: 169..170,
312 kind: VARIANT,
313 detail: None,
314 deprecated: false,
315 },
316 StructureNode {
317 parent: Some(
318 6,
319 ),
320 label: "Y",
321 navigation_range: 172..173,
322 node_range: 172..178,
323 kind: VARIANT,
324 detail: None,
325 deprecated: false,
326 },
327 StructureNode {
328 parent: None,
329 label: "T",
330 navigation_range: 186..187,
331 node_range: 181..193,
332 kind: TYPE_ALIAS,
333 detail: Some(
334 "()",
335 ),
336 deprecated: false,
337 },
338 StructureNode {
339 parent: None,
340 label: "S",
341 navigation_range: 201..202,
342 node_range: 194..213,
343 kind: STATIC,
344 detail: Some(
345 "i32",
346 ),
347 deprecated: false,
348 },
349 StructureNode {
350 parent: None,
351 label: "C",
352 navigation_range: 220..221,
353 node_range: 214..232,
354 kind: CONST,
355 detail: Some(
356 "i32",
357 ),
358 deprecated: false,
359 },
360 StructureNode {
361 parent: None,
362 label: "impl E",
363 navigation_range: 239..240,
364 node_range: 234..243,
365 kind: IMPL,
366 detail: None,
367 deprecated: false,
368 },
369 StructureNode {
370 parent: None,
371 label: "impl fmt::Debug for E",
372 navigation_range: 265..266,
373 node_range: 245..269,
374 kind: IMPL,
375 detail: None,
376 deprecated: false,
377 },
378 StructureNode {
379 parent: None,
380 label: "mc",
381 navigation_range: 284..286,
382 node_range: 271..303,
383 kind: MACRO_CALL,
384 detail: None,
385 deprecated: false,
386 },
387 StructureNode {
388 parent: None,
389 label: "mcexp",
390 navigation_range: 334..339,
391 node_range: 305..356,
392 kind: MACRO_CALL,
393 detail: None,
394 deprecated: false,
395 },
396 StructureNode {
397 parent: None,
398 label: "mcexp",
399 navigation_range: 387..392,
400 node_range: 358..409,
401 kind: MACRO_CALL,
402 detail: None,
403 deprecated: false,
404 },
405 StructureNode {
406 parent: None,
407 label: "obsolete",
408 navigation_range: 428..436,
409 node_range: 411..441,
410 kind: FN,
411 detail: Some(
412 "fn()",
413 ),
414 deprecated: true,
415 },
416 StructureNode {
417 parent: None,
418 label: "very_obsolete",
419 navigation_range: 481..494,
420 node_range: 443..499,
421 kind: FN,
422 detail: Some(
423 "fn()",
424 ),
425 deprecated: true,
426 },
427 ]
428 "#]],
429 );
430 }
431}