diff options
Diffstat (limited to 'crates/ra_editor')
-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 |
3 files changed, 131 insertions, 248 deletions
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 | } | ||