1 use ide_db::SymbolKind;
2 use syntax::{
3 ast::{self, HasAttrs, HasGenericParams, HasName},
4 match_ast, AstNode, AstToken, NodeOrToken, SourceFile, SyntaxNode, SyntaxToken, TextRange,
5 WalkEvent,
6 };
7
8 #[derive(Debug, Clone)]
9 pub struct StructureNode {
10 pub parent: Option<usize>,
11 pub label: String,
12 pub navigation_range: TextRange,
13 pub node_range: TextRange,
14 pub kind: StructureNodeKind,
15 pub detail: Option<String>,
16 pub deprecated: bool,
17 }
18
19 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
20 pub enum StructureNodeKind {
21 SymbolKind(SymbolKind),
22 Region,
23 }
24
25 // Feature: File Structure
26 //
27 // Provides a tree of the symbols defined in the file. Can be used to
28 //
29 // * fuzzy search symbol in a file (super useful)
30 // * draw breadcrumbs to describe the context around the cursor
31 // * draw outline of the file
32 //
33 // |===
34 // | Editor | Shortcut
35 //
36 // | VS Code | kbd:[Ctrl+Shift+O]
37 // |===
38 //
39 // image::https://user-images.githubusercontent.com/48062697/113020654-b42fc800-917a-11eb-8388-e7dc4d92b02e.gif[]
40
file_structure(file: &SourceFile) -> Vec<StructureNode>41 pub(crate) fn file_structure(file: &SourceFile) -> Vec<StructureNode> {
42 let mut res = Vec::new();
43 let mut stack = Vec::new();
44
45 for event in file.syntax().preorder_with_tokens() {
46 match event {
47 WalkEvent::Enter(NodeOrToken::Node(node)) => {
48 if let Some(mut symbol) = structure_node(&node) {
49 symbol.parent = stack.last().copied();
50 stack.push(res.len());
51 res.push(symbol);
52 }
53 }
54 WalkEvent::Leave(NodeOrToken::Node(node)) => {
55 if structure_node(&node).is_some() {
56 stack.pop().unwrap();
57 }
58 }
59 WalkEvent::Enter(NodeOrToken::Token(token)) => {
60 if let Some(mut symbol) = structure_token(token) {
61 symbol.parent = stack.last().copied();
62 stack.push(res.len());
63 res.push(symbol);
64 }
65 }
66 WalkEvent::Leave(NodeOrToken::Token(token)) => {
67 if structure_token(token).is_some() {
68 stack.pop().unwrap();
69 }
70 }
71 }
72 }
73 res
74 }
75
structure_node(node: &SyntaxNode) -> Option<StructureNode>76 fn structure_node(node: &SyntaxNode) -> Option<StructureNode> {
77 fn decl<N: HasName + HasAttrs>(node: N, kind: StructureNodeKind) -> Option<StructureNode> {
78 decl_with_detail(&node, None, kind)
79 }
80
81 fn decl_with_type_ref<N: HasName + HasAttrs>(
82 node: &N,
83 type_ref: Option<ast::Type>,
84 kind: StructureNodeKind,
85 ) -> Option<StructureNode> {
86 let detail = type_ref.map(|type_ref| {
87 let mut detail = String::new();
88 collapse_ws(type_ref.syntax(), &mut detail);
89 detail
90 });
91 decl_with_detail(node, detail, kind)
92 }
93
94 fn decl_with_detail<N: HasName + HasAttrs>(
95 node: &N,
96 detail: Option<String>,
97 kind: StructureNodeKind,
98 ) -> Option<StructureNode> {
99 let name = node.name()?;
100
101 Some(StructureNode {
102 parent: None,
103 label: name.text().to_string(),
104 navigation_range: name.syntax().text_range(),
105 node_range: node.syntax().text_range(),
106 kind,
107 detail,
108 deprecated: node.attrs().filter_map(|x| x.simple_name()).any(|x| x == "deprecated"),
109 })
110 }
111
112 fn collapse_ws(node: &SyntaxNode, output: &mut String) {
113 let mut can_insert_ws = false;
114 node.text().for_each_chunk(|chunk| {
115 for line in chunk.lines() {
116 let line = line.trim();
117 if line.is_empty() {
118 if can_insert_ws {
119 output.push(' ');
120 can_insert_ws = false;
121 }
122 } else {
123 output.push_str(line);
124 can_insert_ws = true;
125 }
126 }
127 })
128 }
129
130 match_ast! {
131 match node {
132 ast::Fn(it) => {
133 let mut detail = String::from("fn");
134 if let Some(type_param_list) = it.generic_param_list() {
135 collapse_ws(type_param_list.syntax(), &mut detail);
136 }
137 if let Some(param_list) = it.param_list() {
138 collapse_ws(param_list.syntax(), &mut detail);
139 }
140 if let Some(ret_type) = it.ret_type() {
141 detail.push(' ');
142 collapse_ws(ret_type.syntax(), &mut detail);
143 }
144
145 decl_with_detail(&it, Some(detail), StructureNodeKind::SymbolKind(SymbolKind::Function))
146 },
147 ast::Struct(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Struct)),
148 ast::Union(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Union)),
149 ast::Enum(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Enum)),
150 ast::Variant(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Variant)),
151 ast::Trait(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Trait)),
152 ast::TraitAlias(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::TraitAlias)),
153 ast::Module(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Module)),
154 ast::TypeAlias(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::TypeAlias)),
155 ast::RecordField(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::Field)),
156 ast::Const(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::Const)),
157 ast::Static(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::Static)),
158 ast::Impl(it) => {
159 let target_type = it.self_ty()?;
160 let target_trait = it.trait_();
161 let label = match target_trait {
162 None => format!("impl {}", target_type.syntax().text()),
163 Some(t) => {
164 format!("impl {}{} for {}",
165 it.excl_token().map(|x| x.to_string()).unwrap_or_default(),
166 t.syntax().text(),
167 target_type.syntax().text(),
168 )
169 }
170 };
171
172 let node = StructureNode {
173 parent: None,
174 label,
175 navigation_range: target_type.syntax().text_range(),
176 node_range: it.syntax().text_range(),
177 kind: StructureNodeKind::SymbolKind(SymbolKind::Impl),
178 detail: None,
179 deprecated: false,
180 };
181 Some(node)
182 },
183 ast::Macro(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Macro)),
184 _ => None,
185 }
186 }
187 }
188
structure_token(token: SyntaxToken) -> Option<StructureNode>189 fn structure_token(token: SyntaxToken) -> Option<StructureNode> {
190 if let Some(comment) = ast::Comment::cast(token) {
191 let text = comment.text().trim();
192
193 if let Some(region_name) = text.strip_prefix("// region:").map(str::trim) {
194 return Some(StructureNode {
195 parent: None,
196 label: region_name.to_string(),
197 navigation_range: comment.syntax().text_range(),
198 node_range: comment.syntax().text_range(),
199 kind: StructureNodeKind::Region,
200 detail: None,
201 deprecated: false,
202 });
203 }
204 }
205
206 None
207 }
208
209 #[cfg(test)]
210 mod tests {
211 use expect_test::{expect, Expect};
212
213 use super::*;
214
check(ra_fixture: &str, expect: Expect)215 fn check(ra_fixture: &str, expect: Expect) {
216 let file = SourceFile::parse(ra_fixture).ok().unwrap();
217 let structure = file_structure(&file);
218 expect.assert_debug_eq(&structure)
219 }
220
221 #[test]
test_negative_trait_bound()222 fn test_negative_trait_bound() {
223 let txt = r#"impl !Unpin for Test {}"#;
224 check(
225 txt,
226 expect![[r#"
227 [
228 StructureNode {
229 parent: None,
230 label: "impl !Unpin for Test",
231 navigation_range: 16..20,
232 node_range: 0..23,
233 kind: SymbolKind(
234 Impl,
235 ),
236 detail: None,
237 deprecated: false,
238 },
239 ]
240 "#]],
241 );
242 }
243
244 #[test]
test_file_structure()245 fn test_file_structure() {
246 check(
247 r#"
248 struct Foo {
249 x: i32
250 }
251
252 mod m {
253 fn bar1() {}
254 fn bar2<T>(t: T) -> T {}
255 fn bar3<A,
256 B>(a: A,
257 b: B) -> Vec<
258 u32
259 > {}
260 }
261
262 enum E { X, Y(i32) }
263 type T = ();
264 static S: i32 = 92;
265 const C: i32 = 92;
266 trait Tr {}
267 trait Alias = Tr;
268
269 impl E {}
270
271 impl fmt::Debug for E {}
272
273 macro_rules! mc {
274 () => {}
275 }
276
277 #[macro_export]
278 macro_rules! mcexp {
279 () => {}
280 }
281
282 /// Doc comment
283 macro_rules! mcexp {
284 () => {}
285 }
286
287 #[deprecated]
288 fn obsolete() {}
289
290 #[deprecated(note = "for awhile")]
291 fn very_obsolete() {}
292
293 // region: Some region name
294 // endregion
295
296 // region: dontpanic
297 mod m {
298 fn f() {}
299 // endregion
300 fn g() {}
301 }
302 "#,
303 expect![[r#"
304 [
305 StructureNode {
306 parent: None,
307 label: "Foo",
308 navigation_range: 8..11,
309 node_range: 1..26,
310 kind: SymbolKind(
311 Struct,
312 ),
313 detail: None,
314 deprecated: false,
315 },
316 StructureNode {
317 parent: Some(
318 0,
319 ),
320 label: "x",
321 navigation_range: 18..19,
322 node_range: 18..24,
323 kind: SymbolKind(
324 Field,
325 ),
326 detail: Some(
327 "i32",
328 ),
329 deprecated: false,
330 },
331 StructureNode {
332 parent: None,
333 label: "m",
334 navigation_range: 32..33,
335 node_range: 28..158,
336 kind: SymbolKind(
337 Module,
338 ),
339 detail: None,
340 deprecated: false,
341 },
342 StructureNode {
343 parent: Some(
344 2,
345 ),
346 label: "bar1",
347 navigation_range: 43..47,
348 node_range: 40..52,
349 kind: SymbolKind(
350 Function,
351 ),
352 detail: Some(
353 "fn()",
354 ),
355 deprecated: false,
356 },
357 StructureNode {
358 parent: Some(
359 2,
360 ),
361 label: "bar2",
362 navigation_range: 60..64,
363 node_range: 57..81,
364 kind: SymbolKind(
365 Function,
366 ),
367 detail: Some(
368 "fn<T>(t: T) -> T",
369 ),
370 deprecated: false,
371 },
372 StructureNode {
373 parent: Some(
374 2,
375 ),
376 label: "bar3",
377 navigation_range: 89..93,
378 node_range: 86..156,
379 kind: SymbolKind(
380 Function,
381 ),
382 detail: Some(
383 "fn<A, B>(a: A, b: B) -> Vec< u32 >",
384 ),
385 deprecated: false,
386 },
387 StructureNode {
388 parent: None,
389 label: "E",
390 navigation_range: 165..166,
391 node_range: 160..180,
392 kind: SymbolKind(
393 Enum,
394 ),
395 detail: None,
396 deprecated: false,
397 },
398 StructureNode {
399 parent: Some(
400 6,
401 ),
402 label: "X",
403 navigation_range: 169..170,
404 node_range: 169..170,
405 kind: SymbolKind(
406 Variant,
407 ),
408 detail: None,
409 deprecated: false,
410 },
411 StructureNode {
412 parent: Some(
413 6,
414 ),
415 label: "Y",
416 navigation_range: 172..173,
417 node_range: 172..178,
418 kind: SymbolKind(
419 Variant,
420 ),
421 detail: None,
422 deprecated: false,
423 },
424 StructureNode {
425 parent: None,
426 label: "T",
427 navigation_range: 186..187,
428 node_range: 181..193,
429 kind: SymbolKind(
430 TypeAlias,
431 ),
432 detail: Some(
433 "()",
434 ),
435 deprecated: false,
436 },
437 StructureNode {
438 parent: None,
439 label: "S",
440 navigation_range: 201..202,
441 node_range: 194..213,
442 kind: SymbolKind(
443 Static,
444 ),
445 detail: Some(
446 "i32",
447 ),
448 deprecated: false,
449 },
450 StructureNode {
451 parent: None,
452 label: "C",
453 navigation_range: 220..221,
454 node_range: 214..232,
455 kind: SymbolKind(
456 Const,
457 ),
458 detail: Some(
459 "i32",
460 ),
461 deprecated: false,
462 },
463 StructureNode {
464 parent: None,
465 label: "Tr",
466 navigation_range: 239..241,
467 node_range: 233..244,
468 kind: SymbolKind(
469 Trait,
470 ),
471 detail: None,
472 deprecated: false,
473 },
474 StructureNode {
475 parent: None,
476 label: "Alias",
477 navigation_range: 251..256,
478 node_range: 245..262,
479 kind: SymbolKind(
480 TraitAlias,
481 ),
482 detail: None,
483 deprecated: false,
484 },
485 StructureNode {
486 parent: None,
487 label: "impl E",
488 navigation_range: 269..270,
489 node_range: 264..273,
490 kind: SymbolKind(
491 Impl,
492 ),
493 detail: None,
494 deprecated: false,
495 },
496 StructureNode {
497 parent: None,
498 label: "impl fmt::Debug for E",
499 navigation_range: 295..296,
500 node_range: 275..299,
501 kind: SymbolKind(
502 Impl,
503 ),
504 detail: None,
505 deprecated: false,
506 },
507 StructureNode {
508 parent: None,
509 label: "mc",
510 navigation_range: 314..316,
511 node_range: 301..333,
512 kind: SymbolKind(
513 Macro,
514 ),
515 detail: None,
516 deprecated: false,
517 },
518 StructureNode {
519 parent: None,
520 label: "mcexp",
521 navigation_range: 364..369,
522 node_range: 335..386,
523 kind: SymbolKind(
524 Macro,
525 ),
526 detail: None,
527 deprecated: false,
528 },
529 StructureNode {
530 parent: None,
531 label: "mcexp",
532 navigation_range: 417..422,
533 node_range: 388..439,
534 kind: SymbolKind(
535 Macro,
536 ),
537 detail: None,
538 deprecated: false,
539 },
540 StructureNode {
541 parent: None,
542 label: "obsolete",
543 navigation_range: 458..466,
544 node_range: 441..471,
545 kind: SymbolKind(
546 Function,
547 ),
548 detail: Some(
549 "fn()",
550 ),
551 deprecated: true,
552 },
553 StructureNode {
554 parent: None,
555 label: "very_obsolete",
556 navigation_range: 511..524,
557 node_range: 473..529,
558 kind: SymbolKind(
559 Function,
560 ),
561 detail: Some(
562 "fn()",
563 ),
564 deprecated: true,
565 },
566 StructureNode {
567 parent: None,
568 label: "Some region name",
569 navigation_range: 531..558,
570 node_range: 531..558,
571 kind: Region,
572 detail: None,
573 deprecated: false,
574 },
575 StructureNode {
576 parent: None,
577 label: "m",
578 navigation_range: 598..599,
579 node_range: 573..636,
580 kind: SymbolKind(
581 Module,
582 ),
583 detail: None,
584 deprecated: false,
585 },
586 StructureNode {
587 parent: Some(
588 22,
589 ),
590 label: "dontpanic",
591 navigation_range: 573..593,
592 node_range: 573..593,
593 kind: Region,
594 detail: None,
595 deprecated: false,
596 },
597 StructureNode {
598 parent: Some(
599 22,
600 ),
601 label: "f",
602 navigation_range: 605..606,
603 node_range: 602..611,
604 kind: SymbolKind(
605 Function,
606 ),
607 detail: Some(
608 "fn()",
609 ),
610 deprecated: false,
611 },
612 StructureNode {
613 parent: Some(
614 22,
615 ),
616 label: "g",
617 navigation_range: 628..629,
618 node_range: 612..634,
619 kind: SymbolKind(
620 Function,
621 ),
622 detail: Some(
623 "fn()",
624 ),
625 deprecated: false,
626 },
627 ]
628 "#]],
629 );
630 }
631 }
632