diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-04-09 18:55:44 +0100 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-04-09 18:55:44 +0100 |
commit | 2fc2d4373b2c4e96bebf320a84270eee3afe34aa (patch) | |
tree | 5bdb33ae377b4004f0b28ec5e2edff71b41e8c4e /crates/ra_ide_api/src/display | |
parent | 5f700179fc7ed16d2848a6dbc7cf23da3b8df6c7 (diff) | |
parent | 45a2b9252401cc580dfa2e0e761313cc8334d47c (diff) |
Merge #1110
1110: Introduce display module and implement new FunctionSignature for CallInfo's r=matklad a=vipentti
This introduces a new module `display` in `ra_ide_api` that contains UI-related things, in addition this refactors CallInfo's function signatures into a new `FunctionSignature` type, which implements `Display` and can be converted into `lsp_types::SignatureInformation` in the `conv` layer.
Currently only `CallInfo` uses the `FunctionSignature` directly, but `function_label` now uses the same signature and returns it as a string, using the `Display` implementation.
This also fixes #960
I think this similar structure could be applied to other UI-displayable items, so instead of the `ra_ide_api` returning `Strings` we could return some intermediate structures that can be converted into a UI-displayable `String` easily, but that could also provide some additional information.
Co-authored-by: Ville Penttinen <[email protected]>
Diffstat (limited to 'crates/ra_ide_api/src/display')
-rw-r--r-- | crates/ra_ide_api/src/display/function_signature.rs | 101 | ||||
-rw-r--r-- | crates/ra_ide_api/src/display/navigation_target.rs | 329 | ||||
-rw-r--r-- | crates/ra_ide_api/src/display/snapshots/tests__file_structure.snap | 182 | ||||
-rw-r--r-- | crates/ra_ide_api/src/display/structure.rs | 190 |
4 files changed, 802 insertions, 0 deletions
diff --git a/crates/ra_ide_api/src/display/function_signature.rs b/crates/ra_ide_api/src/display/function_signature.rs new file mode 100644 index 000000000..d09950bce --- /dev/null +++ b/crates/ra_ide_api/src/display/function_signature.rs | |||
@@ -0,0 +1,101 @@ | |||
1 | use super::{where_predicates, generic_parameters}; | ||
2 | use crate::db; | ||
3 | use std::fmt::{self, Display}; | ||
4 | use join_to_string::join; | ||
5 | use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; | ||
6 | use std::convert::From; | ||
7 | use hir::{Docs, Documentation}; | ||
8 | |||
9 | /// Contains information about a function signature | ||
10 | #[derive(Debug)] | ||
11 | pub struct FunctionSignature { | ||
12 | /// Optional visibility | ||
13 | pub visibility: Option<String>, | ||
14 | /// Name of the function | ||
15 | pub name: Option<String>, | ||
16 | /// Documentation for the function | ||
17 | pub doc: Option<Documentation>, | ||
18 | /// Generic parameters | ||
19 | pub generic_parameters: Vec<String>, | ||
20 | /// Parameters of the function | ||
21 | pub parameters: Vec<String>, | ||
22 | /// Optional return type | ||
23 | pub ret_type: Option<String>, | ||
24 | /// Where predicates | ||
25 | pub where_predicates: Vec<String>, | ||
26 | } | ||
27 | |||
28 | impl FunctionSignature { | ||
29 | pub(crate) fn with_doc_opt(mut self, doc: Option<Documentation>) -> Self { | ||
30 | self.doc = doc; | ||
31 | self | ||
32 | } | ||
33 | |||
34 | pub(crate) fn from_hir(db: &db::RootDatabase, function: hir::Function) -> Self { | ||
35 | let doc = function.docs(db); | ||
36 | let (_, ast_node) = function.source(db); | ||
37 | FunctionSignature::from(&*ast_node).with_doc_opt(doc) | ||
38 | } | ||
39 | } | ||
40 | |||
41 | impl From<&'_ ast::FnDef> for FunctionSignature { | ||
42 | fn from(node: &ast::FnDef) -> FunctionSignature { | ||
43 | fn param_list(node: &ast::FnDef) -> Vec<String> { | ||
44 | let mut res = vec![]; | ||
45 | if let Some(param_list) = node.param_list() { | ||
46 | if let Some(self_param) = param_list.self_param() { | ||
47 | res.push(self_param.syntax().text().to_string()) | ||
48 | } | ||
49 | |||
50 | res.extend(param_list.params().map(|param| param.syntax().text().to_string())); | ||
51 | } | ||
52 | res | ||
53 | } | ||
54 | |||
55 | FunctionSignature { | ||
56 | visibility: node.visibility().map(|n| n.syntax().text().to_string()), | ||
57 | name: node.name().map(|n| n.text().to_string()), | ||
58 | ret_type: node | ||
59 | .ret_type() | ||
60 | .and_then(|r| r.type_ref()) | ||
61 | .map(|n| n.syntax().text().to_string()), | ||
62 | parameters: param_list(node), | ||
63 | generic_parameters: generic_parameters(node), | ||
64 | where_predicates: where_predicates(node), | ||
65 | // docs are processed separately | ||
66 | doc: None, | ||
67 | } | ||
68 | } | ||
69 | } | ||
70 | |||
71 | impl Display for FunctionSignature { | ||
72 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
73 | if let Some(t) = &self.visibility { | ||
74 | write!(f, "{} ", t)?; | ||
75 | } | ||
76 | |||
77 | if let Some(name) = &self.name { | ||
78 | write!(f, "fn {}", name)?; | ||
79 | } | ||
80 | |||
81 | if !self.generic_parameters.is_empty() { | ||
82 | join(self.generic_parameters.iter()) | ||
83 | .separator(", ") | ||
84 | .surround_with("<", ">") | ||
85 | .to_fmt(f)?; | ||
86 | } | ||
87 | |||
88 | join(self.parameters.iter()).separator(", ").surround_with("(", ")").to_fmt(f)?; | ||
89 | |||
90 | if let Some(t) = &self.ret_type { | ||
91 | write!(f, " -> {}", t)?; | ||
92 | } | ||
93 | |||
94 | if !self.where_predicates.is_empty() { | ||
95 | write!(f, "\nwhere ")?; | ||
96 | join(self.where_predicates.iter()).separator(",\n ").to_fmt(f)?; | ||
97 | } | ||
98 | |||
99 | Ok(()) | ||
100 | } | ||
101 | } | ||
diff --git a/crates/ra_ide_api/src/display/navigation_target.rs b/crates/ra_ide_api/src/display/navigation_target.rs new file mode 100644 index 000000000..3c518faf5 --- /dev/null +++ b/crates/ra_ide_api/src/display/navigation_target.rs | |||
@@ -0,0 +1,329 @@ | |||
1 | use ra_db::{FileId, SourceDatabase}; | ||
2 | use ra_syntax::{ | ||
3 | SyntaxNode, SyntaxNodePtr, AstNode, SmolStr, TextRange, TreeArc, | ||
4 | SyntaxKind::{self, NAME}, | ||
5 | ast::{self, NameOwner, VisibilityOwner, TypeAscriptionOwner}, | ||
6 | algo::visit::{visitor, Visitor}, | ||
7 | }; | ||
8 | use hir::{ModuleSource, FieldSource, Name, ImplItem}; | ||
9 | |||
10 | use crate::{FileSymbol, db::RootDatabase}; | ||
11 | |||
12 | /// `NavigationTarget` represents and element in the editor's UI which you can | ||
13 | /// click on to navigate to a particular piece of code. | ||
14 | /// | ||
15 | /// Typically, a `NavigationTarget` corresponds to some element in the source | ||
16 | /// code, like a function or a struct, but this is not strictly required. | ||
17 | #[derive(Debug, Clone)] | ||
18 | pub struct NavigationTarget { | ||
19 | file_id: FileId, | ||
20 | name: SmolStr, | ||
21 | kind: SyntaxKind, | ||
22 | full_range: TextRange, | ||
23 | focus_range: Option<TextRange>, | ||
24 | container_name: Option<SmolStr>, | ||
25 | } | ||
26 | |||
27 | impl NavigationTarget { | ||
28 | /// When `focus_range` is specified, returns it. otherwise | ||
29 | /// returns `full_range` | ||
30 | pub fn range(&self) -> TextRange { | ||
31 | self.focus_range.unwrap_or(self.full_range) | ||
32 | } | ||
33 | |||
34 | pub fn name(&self) -> &SmolStr { | ||
35 | &self.name | ||
36 | } | ||
37 | |||
38 | pub fn container_name(&self) -> Option<&SmolStr> { | ||
39 | self.container_name.as_ref() | ||
40 | } | ||
41 | |||
42 | pub fn kind(&self) -> SyntaxKind { | ||
43 | self.kind | ||
44 | } | ||
45 | |||
46 | pub fn file_id(&self) -> FileId { | ||
47 | self.file_id | ||
48 | } | ||
49 | |||
50 | pub fn full_range(&self) -> TextRange { | ||
51 | self.full_range | ||
52 | } | ||
53 | |||
54 | /// A "most interesting" range withing the `full_range`. | ||
55 | /// | ||
56 | /// Typically, `full_range` is the whole syntax node, | ||
57 | /// including doc comments, and `focus_range` is the range of the identifier. | ||
58 | pub fn focus_range(&self) -> Option<TextRange> { | ||
59 | self.focus_range | ||
60 | } | ||
61 | |||
62 | pub(crate) fn from_bind_pat(file_id: FileId, pat: &ast::BindPat) -> NavigationTarget { | ||
63 | NavigationTarget::from_named(file_id, pat) | ||
64 | } | ||
65 | |||
66 | pub(crate) fn from_symbol(symbol: FileSymbol) -> NavigationTarget { | ||
67 | NavigationTarget { | ||
68 | file_id: symbol.file_id, | ||
69 | name: symbol.name.clone(), | ||
70 | kind: symbol.ptr.kind(), | ||
71 | full_range: symbol.ptr.range(), | ||
72 | focus_range: symbol.name_range, | ||
73 | container_name: symbol.container_name.clone(), | ||
74 | } | ||
75 | } | ||
76 | |||
77 | pub(crate) fn from_scope_entry( | ||
78 | file_id: FileId, | ||
79 | name: Name, | ||
80 | ptr: SyntaxNodePtr, | ||
81 | ) -> NavigationTarget { | ||
82 | NavigationTarget { | ||
83 | file_id, | ||
84 | name: name.to_string().into(), | ||
85 | full_range: ptr.range(), | ||
86 | focus_range: None, | ||
87 | kind: NAME, | ||
88 | container_name: None, | ||
89 | } | ||
90 | } | ||
91 | |||
92 | pub(crate) fn from_module(db: &RootDatabase, module: hir::Module) -> NavigationTarget { | ||
93 | let (file_id, source) = module.definition_source(db); | ||
94 | let file_id = file_id.as_original_file(); | ||
95 | let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); | ||
96 | match source { | ||
97 | ModuleSource::SourceFile(node) => { | ||
98 | NavigationTarget::from_syntax(file_id, name, None, node.syntax()) | ||
99 | } | ||
100 | ModuleSource::Module(node) => { | ||
101 | NavigationTarget::from_syntax(file_id, name, None, node.syntax()) | ||
102 | } | ||
103 | } | ||
104 | } | ||
105 | |||
106 | pub(crate) fn from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget { | ||
107 | let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); | ||
108 | if let Some((file_id, source)) = module.declaration_source(db) { | ||
109 | let file_id = file_id.as_original_file(); | ||
110 | return NavigationTarget::from_syntax(file_id, name, None, source.syntax()); | ||
111 | } | ||
112 | NavigationTarget::from_module(db, module) | ||
113 | } | ||
114 | |||
115 | pub(crate) fn from_function(db: &RootDatabase, func: hir::Function) -> NavigationTarget { | ||
116 | let (file_id, fn_def) = func.source(db); | ||
117 | NavigationTarget::from_named(file_id.original_file(db), &*fn_def) | ||
118 | } | ||
119 | |||
120 | pub(crate) fn from_field(db: &RootDatabase, field: hir::StructField) -> NavigationTarget { | ||
121 | let (file_id, field) = field.source(db); | ||
122 | let file_id = file_id.original_file(db); | ||
123 | match field { | ||
124 | FieldSource::Named(it) => NavigationTarget::from_named(file_id, &*it), | ||
125 | FieldSource::Pos(it) => { | ||
126 | NavigationTarget::from_syntax(file_id, "".into(), None, it.syntax()) | ||
127 | } | ||
128 | } | ||
129 | } | ||
130 | |||
131 | pub(crate) fn from_adt_def(db: &RootDatabase, adt_def: hir::AdtDef) -> NavigationTarget { | ||
132 | match adt_def { | ||
133 | hir::AdtDef::Struct(s) => { | ||
134 | let (file_id, node) = s.source(db); | ||
135 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
136 | } | ||
137 | hir::AdtDef::Enum(s) => { | ||
138 | let (file_id, node) = s.source(db); | ||
139 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
140 | } | ||
141 | } | ||
142 | } | ||
143 | |||
144 | pub(crate) fn from_def(db: &RootDatabase, module_def: hir::ModuleDef) -> NavigationTarget { | ||
145 | match module_def { | ||
146 | hir::ModuleDef::Module(module) => NavigationTarget::from_module(db, module), | ||
147 | hir::ModuleDef::Function(func) => NavigationTarget::from_function(db, func), | ||
148 | hir::ModuleDef::Struct(s) => { | ||
149 | let (file_id, node) = s.source(db); | ||
150 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
151 | } | ||
152 | hir::ModuleDef::Const(s) => { | ||
153 | let (file_id, node) = s.source(db); | ||
154 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
155 | } | ||
156 | hir::ModuleDef::Static(s) => { | ||
157 | let (file_id, node) = s.source(db); | ||
158 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
159 | } | ||
160 | hir::ModuleDef::Enum(e) => { | ||
161 | let (file_id, node) = e.source(db); | ||
162 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
163 | } | ||
164 | hir::ModuleDef::EnumVariant(var) => { | ||
165 | let (file_id, node) = var.source(db); | ||
166 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
167 | } | ||
168 | hir::ModuleDef::Trait(e) => { | ||
169 | let (file_id, node) = e.source(db); | ||
170 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
171 | } | ||
172 | hir::ModuleDef::TypeAlias(e) => { | ||
173 | let (file_id, node) = e.source(db); | ||
174 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
175 | } | ||
176 | } | ||
177 | } | ||
178 | |||
179 | pub(crate) fn from_impl_block( | ||
180 | db: &RootDatabase, | ||
181 | impl_block: hir::ImplBlock, | ||
182 | ) -> NavigationTarget { | ||
183 | let (file_id, node) = impl_block.source(db); | ||
184 | NavigationTarget::from_syntax( | ||
185 | file_id.as_original_file(), | ||
186 | "impl".into(), | ||
187 | None, | ||
188 | node.syntax(), | ||
189 | ) | ||
190 | } | ||
191 | |||
192 | pub(crate) fn from_impl_item(db: &RootDatabase, impl_item: hir::ImplItem) -> NavigationTarget { | ||
193 | match impl_item { | ||
194 | ImplItem::Method(f) => NavigationTarget::from_function(db, f), | ||
195 | ImplItem::Const(c) => { | ||
196 | let (file_id, node) = c.source(db); | ||
197 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
198 | } | ||
199 | ImplItem::TypeAlias(a) => { | ||
200 | let (file_id, node) = a.source(db); | ||
201 | NavigationTarget::from_named(file_id.original_file(db), &*node) | ||
202 | } | ||
203 | } | ||
204 | } | ||
205 | |||
206 | #[cfg(test)] | ||
207 | pub(crate) fn assert_match(&self, expected: &str) { | ||
208 | let actual = self.debug_render(); | ||
209 | test_utils::assert_eq_text!(expected.trim(), actual.trim(),); | ||
210 | } | ||
211 | |||
212 | #[cfg(test)] | ||
213 | pub(crate) fn debug_render(&self) -> String { | ||
214 | let mut buf = format!( | ||
215 | "{} {:?} {:?} {:?}", | ||
216 | self.name(), | ||
217 | self.kind(), | ||
218 | self.file_id(), | ||
219 | self.full_range() | ||
220 | ); | ||
221 | if let Some(focus_range) = self.focus_range() { | ||
222 | buf.push_str(&format!(" {:?}", focus_range)) | ||
223 | } | ||
224 | if let Some(container_name) = self.container_name() { | ||
225 | buf.push_str(&format!(" {}", container_name)) | ||
226 | } | ||
227 | buf | ||
228 | } | ||
229 | |||
230 | /// Allows `NavigationTarget` to be created from a `NameOwner` | ||
231 | pub(crate) fn from_named(file_id: FileId, node: &impl ast::NameOwner) -> NavigationTarget { | ||
232 | let name = node.name().map(|it| it.text().clone()).unwrap_or_default(); | ||
233 | let focus_range = node.name().map(|it| it.syntax().range()); | ||
234 | NavigationTarget::from_syntax(file_id, name, focus_range, node.syntax()) | ||
235 | } | ||
236 | |||
237 | fn from_syntax( | ||
238 | file_id: FileId, | ||
239 | name: SmolStr, | ||
240 | focus_range: Option<TextRange>, | ||
241 | node: &SyntaxNode, | ||
242 | ) -> NavigationTarget { | ||
243 | NavigationTarget { | ||
244 | file_id, | ||
245 | name, | ||
246 | kind: node.kind(), | ||
247 | full_range: node.range(), | ||
248 | focus_range, | ||
249 | // ptr: Some(LocalSyntaxPtr::new(node)), | ||
250 | container_name: None, | ||
251 | } | ||
252 | } | ||
253 | |||
254 | pub(crate) fn node(&self, db: &RootDatabase) -> Option<TreeArc<SyntaxNode>> { | ||
255 | let source_file = db.parse(self.file_id()); | ||
256 | let source_file = source_file.syntax(); | ||
257 | let node = source_file | ||
258 | .descendants() | ||
259 | .find(|node| node.kind() == self.kind() && node.range() == self.full_range())? | ||
260 | .to_owned(); | ||
261 | Some(node) | ||
262 | } | ||
263 | |||
264 | pub(crate) fn docs(&self, db: &RootDatabase) -> Option<String> { | ||
265 | let node = self.node(db)?; | ||
266 | fn doc_comments<N: ast::DocCommentsOwner>(node: &N) -> Option<String> { | ||
267 | node.doc_comment_text() | ||
268 | } | ||
269 | |||
270 | visitor() | ||
271 | .visit(doc_comments::<ast::FnDef>) | ||
272 | .visit(doc_comments::<ast::StructDef>) | ||
273 | .visit(doc_comments::<ast::EnumDef>) | ||
274 | .visit(doc_comments::<ast::TraitDef>) | ||
275 | .visit(doc_comments::<ast::Module>) | ||
276 | .visit(doc_comments::<ast::TypeAliasDef>) | ||
277 | .visit(doc_comments::<ast::ConstDef>) | ||
278 | .visit(doc_comments::<ast::StaticDef>) | ||
279 | .visit(doc_comments::<ast::NamedFieldDef>) | ||
280 | .visit(doc_comments::<ast::EnumVariant>) | ||
281 | .accept(&node)? | ||
282 | } | ||
283 | |||
284 | /// Get a description of this node. | ||
285 | /// | ||
286 | /// e.g. `struct Name`, `enum Name`, `fn Name` | ||
287 | pub(crate) fn description(&self, db: &RootDatabase) -> Option<String> { | ||
288 | // FIXME: After type inference is done, add type information to improve the output | ||
289 | let node = self.node(db)?; | ||
290 | |||
291 | fn visit_ascribed_node<T>(node: &T, prefix: &str) -> Option<String> | ||
292 | where | ||
293 | T: NameOwner + VisibilityOwner + TypeAscriptionOwner, | ||
294 | { | ||
295 | let mut string = visit_node(node, prefix)?; | ||
296 | |||
297 | if let Some(type_ref) = node.ascribed_type() { | ||
298 | string.push_str(": "); | ||
299 | type_ref.syntax().text().push_to(&mut string); | ||
300 | } | ||
301 | |||
302 | Some(string) | ||
303 | } | ||
304 | |||
305 | fn visit_node<T>(node: &T, label: &str) -> Option<String> | ||
306 | where | ||
307 | T: NameOwner + VisibilityOwner, | ||
308 | { | ||
309 | let mut string = | ||
310 | node.visibility().map(|v| format!("{} ", v.syntax().text())).unwrap_or_default(); | ||
311 | string.push_str(label); | ||
312 | string.push_str(node.name()?.text().as_str()); | ||
313 | Some(string) | ||
314 | } | ||
315 | |||
316 | visitor() | ||
317 | .visit(|node: &ast::FnDef| Some(crate::display::function_label(node))) | ||
318 | .visit(|node: &ast::StructDef| visit_node(node, "struct ")) | ||
319 | .visit(|node: &ast::EnumDef| visit_node(node, "enum ")) | ||
320 | .visit(|node: &ast::TraitDef| visit_node(node, "trait ")) | ||
321 | .visit(|node: &ast::Module| visit_node(node, "mod ")) | ||
322 | .visit(|node: &ast::TypeAliasDef| visit_node(node, "type ")) | ||
323 | .visit(|node: &ast::ConstDef| visit_ascribed_node(node, "const ")) | ||
324 | .visit(|node: &ast::StaticDef| visit_ascribed_node(node, "static ")) | ||
325 | .visit(|node: &ast::NamedFieldDef| visit_ascribed_node(node, "")) | ||
326 | .visit(|node: &ast::EnumVariant| Some(node.name()?.text().to_string())) | ||
327 | .accept(&node)? | ||
328 | } | ||
329 | } | ||
diff --git a/crates/ra_ide_api/src/display/snapshots/tests__file_structure.snap b/crates/ra_ide_api/src/display/snapshots/tests__file_structure.snap new file mode 100644 index 000000000..32dd99484 --- /dev/null +++ b/crates/ra_ide_api/src/display/snapshots/tests__file_structure.snap | |||
@@ -0,0 +1,182 @@ | |||
1 | --- | ||
2 | created: "2019-04-08T09:44:50.196004400Z" | ||
3 | creator: [email protected] | ||
4 | source: crates/ra_ide_api/src/display/structure.rs | ||
5 | expression: structure | ||
6 | --- | ||
7 | [ | ||
8 | StructureNode { | ||
9 | parent: None, | ||
10 | label: "Foo", | ||
11 | navigation_range: [8; 11), | ||
12 | node_range: [1; 26), | ||
13 | kind: STRUCT_DEF, | ||
14 | detail: None, | ||
15 | deprecated: false | ||
16 | }, | ||
17 | StructureNode { | ||
18 | parent: Some( | ||
19 | 0 | ||
20 | ), | ||
21 | label: "x", | ||
22 | navigation_range: [18; 19), | ||
23 | node_range: [18; 24), | ||
24 | kind: NAMED_FIELD_DEF, | ||
25 | detail: Some( | ||
26 | "i32" | ||
27 | ), | ||
28 | deprecated: false | ||
29 | }, | ||
30 | StructureNode { | ||
31 | parent: None, | ||
32 | label: "m", | ||
33 | navigation_range: [32; 33), | ||
34 | node_range: [28; 158), | ||
35 | kind: MODULE, | ||
36 | detail: None, | ||
37 | deprecated: false | ||
38 | }, | ||
39 | StructureNode { | ||
40 | parent: Some( | ||
41 | 2 | ||
42 | ), | ||
43 | label: "bar1", | ||
44 | navigation_range: [43; 47), | ||
45 | node_range: [40; 52), | ||
46 | kind: FN_DEF, | ||
47 | detail: Some( | ||
48 | "fn()" | ||
49 | ), | ||
50 | deprecated: false | ||
51 | }, | ||
52 | StructureNode { | ||
53 | parent: Some( | ||
54 | 2 | ||
55 | ), | ||
56 | label: "bar2", | ||
57 | navigation_range: [60; 64), | ||
58 | node_range: [57; 81), | ||
59 | kind: FN_DEF, | ||
60 | detail: Some( | ||
61 | "fn<T>(t: T) -> T" | ||
62 | ), | ||
63 | deprecated: false | ||
64 | }, | ||
65 | StructureNode { | ||
66 | parent: Some( | ||
67 | 2 | ||
68 | ), | ||
69 | label: "bar3", | ||
70 | navigation_range: [89; 93), | ||
71 | node_range: [86; 156), | ||
72 | kind: FN_DEF, | ||
73 | detail: Some( | ||
74 | "fn<A, B>(a: A, b: B) -> Vec< u32 >" | ||
75 | ), | ||
76 | deprecated: false | ||
77 | }, | ||
78 | StructureNode { | ||
79 | parent: None, | ||
80 | label: "E", | ||
81 | navigation_range: [165; 166), | ||
82 | node_range: [160; 180), | ||
83 | kind: ENUM_DEF, | ||
84 | detail: None, | ||
85 | deprecated: false | ||
86 | }, | ||
87 | StructureNode { | ||
88 | parent: Some( | ||
89 | 6 | ||
90 | ), | ||
91 | label: "X", | ||
92 | navigation_range: [169; 170), | ||
93 | node_range: [169; 170), | ||
94 | kind: ENUM_VARIANT, | ||
95 | detail: None, | ||
96 | deprecated: false | ||
97 | }, | ||
98 | StructureNode { | ||
99 | parent: Some( | ||
100 | 6 | ||
101 | ), | ||
102 | label: "Y", | ||
103 | navigation_range: [172; 173), | ||
104 | node_range: [172; 178), | ||
105 | kind: ENUM_VARIANT, | ||
106 | detail: None, | ||
107 | deprecated: false | ||
108 | }, | ||
109 | StructureNode { | ||
110 | parent: None, | ||
111 | label: "T", | ||
112 | navigation_range: [186; 187), | ||
113 | node_range: [181; 193), | ||
114 | kind: TYPE_ALIAS_DEF, | ||
115 | detail: Some( | ||
116 | "()" | ||
117 | ), | ||
118 | deprecated: false | ||
119 | }, | ||
120 | StructureNode { | ||
121 | parent: None, | ||
122 | label: "S", | ||
123 | navigation_range: [201; 202), | ||
124 | node_range: [194; 213), | ||
125 | kind: STATIC_DEF, | ||
126 | detail: Some( | ||
127 | "i32" | ||
128 | ), | ||
129 | deprecated: false | ||
130 | }, | ||
131 | StructureNode { | ||
132 | parent: None, | ||
133 | label: "C", | ||
134 | navigation_range: [220; 221), | ||
135 | node_range: [214; 232), | ||
136 | kind: CONST_DEF, | ||
137 | detail: Some( | ||
138 | "i32" | ||
139 | ), | ||
140 | deprecated: false | ||
141 | }, | ||
142 | StructureNode { | ||
143 | parent: None, | ||
144 | label: "impl E", | ||
145 | navigation_range: [239; 240), | ||
146 | node_range: [234; 243), | ||
147 | kind: IMPL_BLOCK, | ||
148 | detail: None, | ||
149 | deprecated: false | ||
150 | }, | ||
151 | StructureNode { | ||
152 | parent: None, | ||
153 | label: "impl fmt::Debug for E", | ||
154 | navigation_range: [265; 266), | ||
155 | node_range: [245; 269), | ||
156 | kind: IMPL_BLOCK, | ||
157 | detail: None, | ||
158 | deprecated: false | ||
159 | }, | ||
160 | StructureNode { | ||
161 | parent: None, | ||
162 | label: "obsolete", | ||
163 | navigation_range: [288; 296), | ||
164 | node_range: [271; 301), | ||
165 | kind: FN_DEF, | ||
166 | detail: Some( | ||
167 | "fn()" | ||
168 | ), | ||
169 | deprecated: true | ||
170 | }, | ||
171 | StructureNode { | ||
172 | parent: None, | ||
173 | label: "very_obsolete", | ||
174 | navigation_range: [341; 354), | ||
175 | node_range: [303; 359), | ||
176 | kind: FN_DEF, | ||
177 | detail: Some( | ||
178 | "fn()" | ||
179 | ), | ||
180 | deprecated: true | ||
181 | } | ||
182 | ] | ||
diff --git a/crates/ra_ide_api/src/display/structure.rs b/crates/ra_ide_api/src/display/structure.rs new file mode 100644 index 000000000..ec2c9bbc6 --- /dev/null +++ b/crates/ra_ide_api/src/display/structure.rs | |||
@@ -0,0 +1,190 @@ | |||
1 | use crate::TextRange; | ||
2 | |||
3 | use ra_syntax::{ | ||
4 | algo::visit::{visitor, Visitor}, | ||
5 | ast::{self, AttrsOwner, NameOwner, TypeParamsOwner, TypeAscriptionOwner}, | ||
6 | AstNode, SourceFile, SyntaxKind, SyntaxNode, 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 | pub detail: Option<String>, | ||
17 | pub deprecated: bool, | ||
18 | } | ||
19 | |||
20 | pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> { | ||
21 | let mut res = Vec::new(); | ||
22 | let mut stack = Vec::new(); | ||
23 | |||
24 | for event in file.syntax().preorder() { | ||
25 | match event { | ||
26 | WalkEvent::Enter(node) => { | ||
27 | if let Some(mut symbol) = structure_node(node) { | ||
28 | symbol.parent = stack.last().map(|&n| n); | ||
29 | stack.push(res.len()); | ||
30 | res.push(symbol); | ||
31 | } | ||
32 | } | ||
33 | WalkEvent::Leave(node) => { | ||
34 | if structure_node(node).is_some() { | ||
35 | stack.pop().unwrap(); | ||
36 | } | ||
37 | } | ||
38 | } | ||
39 | } | ||
40 | res | ||
41 | } | ||
42 | |||
43 | fn structure_node(node: &SyntaxNode) -> Option<StructureNode> { | ||
44 | fn decl<N: NameOwner + AttrsOwner>(node: &N) -> Option<StructureNode> { | ||
45 | decl_with_detail(node, None) | ||
46 | } | ||
47 | |||
48 | fn decl_with_ascription<N: NameOwner + AttrsOwner + TypeAscriptionOwner>( | ||
49 | node: &N, | ||
50 | ) -> Option<StructureNode> { | ||
51 | decl_with_type_ref(node, node.ascribed_type()) | ||
52 | } | ||
53 | |||
54 | fn decl_with_type_ref<N: NameOwner + AttrsOwner>( | ||
55 | node: &N, | ||
56 | type_ref: Option<&ast::TypeRef>, | ||
57 | ) -> Option<StructureNode> { | ||
58 | let detail = type_ref.map(|type_ref| { | ||
59 | let mut detail = String::new(); | ||
60 | collapse_ws(type_ref.syntax(), &mut detail); | ||
61 | detail | ||
62 | }); | ||
63 | decl_with_detail(node, detail) | ||
64 | } | ||
65 | |||
66 | fn decl_with_detail<N: NameOwner + AttrsOwner>( | ||
67 | node: &N, | ||
68 | detail: Option<String>, | ||
69 | ) -> Option<StructureNode> { | ||
70 | let name = node.name()?; | ||
71 | |||
72 | Some(StructureNode { | ||
73 | parent: None, | ||
74 | label: name.text().to_string(), | ||
75 | navigation_range: name.syntax().range(), | ||
76 | node_range: node.syntax().range(), | ||
77 | kind: node.syntax().kind(), | ||
78 | detail, | ||
79 | deprecated: node.attrs().filter_map(|x| x.as_named()).any(|x| x == "deprecated"), | ||
80 | }) | ||
81 | } | ||
82 | |||
83 | fn collapse_ws(node: &SyntaxNode, output: &mut String) { | ||
84 | let mut can_insert_ws = false; | ||
85 | for line in node.text().chunks().flat_map(|chunk| chunk.lines()) { | ||
86 | let line = line.trim(); | ||
87 | if line.is_empty() { | ||
88 | if can_insert_ws { | ||
89 | output.push_str(" "); | ||
90 | can_insert_ws = false; | ||
91 | } | ||
92 | } else { | ||
93 | output.push_str(line); | ||
94 | can_insert_ws = true; | ||
95 | } | ||
96 | } | ||
97 | } | ||
98 | |||
99 | visitor() | ||
100 | .visit(|fn_def: &ast::FnDef| { | ||
101 | let mut detail = String::from("fn"); | ||
102 | if let Some(type_param_list) = fn_def.type_param_list() { | ||
103 | collapse_ws(type_param_list.syntax(), &mut detail); | ||
104 | } | ||
105 | if let Some(param_list) = fn_def.param_list() { | ||
106 | collapse_ws(param_list.syntax(), &mut detail); | ||
107 | } | ||
108 | if let Some(ret_type) = fn_def.ret_type() { | ||
109 | detail.push_str(" "); | ||
110 | collapse_ws(ret_type.syntax(), &mut detail); | ||
111 | } | ||
112 | |||
113 | decl_with_detail(fn_def, Some(detail)) | ||
114 | }) | ||
115 | .visit(decl::<ast::StructDef>) | ||
116 | .visit(decl::<ast::EnumDef>) | ||
117 | .visit(decl::<ast::EnumVariant>) | ||
118 | .visit(decl::<ast::TraitDef>) | ||
119 | .visit(decl::<ast::Module>) | ||
120 | .visit(|td: &ast::TypeAliasDef| decl_with_type_ref(td, td.type_ref())) | ||
121 | .visit(decl_with_ascription::<ast::NamedFieldDef>) | ||
122 | .visit(decl_with_ascription::<ast::ConstDef>) | ||
123 | .visit(decl_with_ascription::<ast::StaticDef>) | ||
124 | .visit(|im: &ast::ImplBlock| { | ||
125 | let target_type = im.target_type()?; | ||
126 | let target_trait = im.target_trait(); | ||
127 | let label = match target_trait { | ||
128 | None => format!("impl {}", target_type.syntax().text()), | ||
129 | Some(t) => { | ||
130 | format!("impl {} for {}", t.syntax().text(), target_type.syntax().text(),) | ||
131 | } | ||
132 | }; | ||
133 | |||
134 | let node = StructureNode { | ||
135 | parent: None, | ||
136 | label, | ||
137 | navigation_range: target_type.syntax().range(), | ||
138 | node_range: im.syntax().range(), | ||
139 | kind: im.syntax().kind(), | ||
140 | detail: None, | ||
141 | deprecated: false, | ||
142 | }; | ||
143 | Some(node) | ||
144 | }) | ||
145 | .accept(node)? | ||
146 | } | ||
147 | |||
148 | #[cfg(test)] | ||
149 | mod tests { | ||
150 | use super::*; | ||
151 | use insta::assert_debug_snapshot_matches; | ||
152 | |||
153 | #[test] | ||
154 | fn test_file_structure() { | ||
155 | let file = SourceFile::parse( | ||
156 | r#" | ||
157 | struct Foo { | ||
158 | x: i32 | ||
159 | } | ||
160 | |||
161 | mod m { | ||
162 | fn bar1() {} | ||
163 | fn bar2<T>(t: T) -> T {} | ||
164 | fn bar3<A, | ||
165 | B>(a: A, | ||
166 | b: B) -> Vec< | ||
167 | u32 | ||
168 | > {} | ||
169 | } | ||
170 | |||
171 | enum E { X, Y(i32) } | ||
172 | type T = (); | ||
173 | static S: i32 = 92; | ||
174 | const C: i32 = 92; | ||
175 | |||
176 | impl E {} | ||
177 | |||
178 | impl fmt::Debug for E {} | ||
179 | |||
180 | #[deprecated] | ||
181 | fn obsolete() {} | ||
182 | |||
183 | #[deprecated(note = "for awhile")] | ||
184 | fn very_obsolete() {} | ||
185 | "#, | ||
186 | ); | ||
187 | let structure = file_structure(&file); | ||
188 | assert_debug_snapshot_matches!("file_structure", structure); | ||
189 | } | ||
190 | } | ||