aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/display
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/display')
-rw-r--r--crates/ra_ide/src/display/function_signature.rs215
-rw-r--r--crates/ra_ide/src/display/navigation_target.rs411
-rw-r--r--crates/ra_ide/src/display/short_label.rs97
-rw-r--r--crates/ra_ide/src/display/structure.rs401
4 files changed, 1124 insertions, 0 deletions
diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs
new file mode 100644
index 000000000..d96de4e4c
--- /dev/null
+++ b/crates/ra_ide/src/display/function_signature.rs
@@ -0,0 +1,215 @@
1//! FIXME: write short doc here
2
3use std::fmt::{self, Display};
4
5use hir::{Docs, Documentation, HasSource, HirDisplay};
6use join_to_string::join;
7use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
8use std::convert::From;
9
10use crate::{
11 db,
12 display::{generic_parameters, where_predicates},
13};
14
15#[derive(Debug)]
16pub enum CallableKind {
17 Function,
18 StructConstructor,
19 VariantConstructor,
20 Macro,
21}
22
23/// Contains information about a function signature
24#[derive(Debug)]
25pub struct FunctionSignature {
26 pub kind: CallableKind,
27 /// Optional visibility
28 pub visibility: Option<String>,
29 /// Name of the function
30 pub name: Option<String>,
31 /// Documentation for the function
32 pub doc: Option<Documentation>,
33 /// Generic parameters
34 pub generic_parameters: Vec<String>,
35 /// Parameters of the function
36 pub parameters: Vec<String>,
37 /// Optional return type
38 pub ret_type: Option<String>,
39 /// Where predicates
40 pub where_predicates: Vec<String>,
41}
42
43impl FunctionSignature {
44 pub(crate) fn with_doc_opt(mut self, doc: Option<Documentation>) -> Self {
45 self.doc = doc;
46 self
47 }
48
49 pub(crate) fn from_hir(db: &db::RootDatabase, function: hir::Function) -> Self {
50 let doc = function.docs(db);
51 let ast_node = function.source(db).value;
52 FunctionSignature::from(&ast_node).with_doc_opt(doc)
53 }
54
55 pub(crate) fn from_struct(db: &db::RootDatabase, st: hir::Struct) -> Option<Self> {
56 let node: ast::StructDef = st.source(db).value;
57 match node.kind() {
58 ast::StructKind::Record(_) => return None,
59 _ => (),
60 };
61
62 let params = st
63 .fields(db)
64 .into_iter()
65 .map(|field: hir::StructField| {
66 let ty = field.ty(db);
67 format!("{}", ty.display(db))
68 })
69 .collect();
70
71 Some(
72 FunctionSignature {
73 kind: CallableKind::StructConstructor,
74 visibility: node.visibility().map(|n| n.syntax().text().to_string()),
75 name: node.name().map(|n| n.text().to_string()),
76 ret_type: node.name().map(|n| n.text().to_string()),
77 parameters: params,
78 generic_parameters: generic_parameters(&node),
79 where_predicates: where_predicates(&node),
80 doc: None,
81 }
82 .with_doc_opt(st.docs(db)),
83 )
84 }
85
86 pub(crate) fn from_enum_variant(
87 db: &db::RootDatabase,
88 variant: hir::EnumVariant,
89 ) -> Option<Self> {
90 let node: ast::EnumVariant = variant.source(db).value;
91 match node.kind() {
92 ast::StructKind::Record(_) | ast::StructKind::Unit => return None,
93 _ => (),
94 };
95
96 let parent_name = match variant.parent_enum(db).name(db) {
97 Some(name) => name.to_string(),
98 None => "missing".into(),
99 };
100
101 let name = format!("{}::{}", parent_name, variant.name(db).unwrap());
102
103 let params = variant
104 .fields(db)
105 .into_iter()
106 .map(|field: hir::StructField| {
107 let name = field.name(db);
108 let ty = field.ty(db);
109 format!("{}: {}", name, ty.display(db))
110 })
111 .collect();
112
113 Some(
114 FunctionSignature {
115 kind: CallableKind::VariantConstructor,
116 visibility: None,
117 name: Some(name),
118 ret_type: None,
119 parameters: params,
120 generic_parameters: vec![],
121 where_predicates: vec![],
122 doc: None,
123 }
124 .with_doc_opt(variant.docs(db)),
125 )
126 }
127
128 pub(crate) fn from_macro(db: &db::RootDatabase, macro_def: hir::MacroDef) -> Option<Self> {
129 let node: ast::MacroCall = macro_def.source(db).value;
130
131 let params = vec![];
132
133 Some(
134 FunctionSignature {
135 kind: CallableKind::Macro,
136 visibility: None,
137 name: node.name().map(|n| n.text().to_string()),
138 ret_type: None,
139 parameters: params,
140 generic_parameters: vec![],
141 where_predicates: vec![],
142 doc: None,
143 }
144 .with_doc_opt(macro_def.docs(db)),
145 )
146 }
147}
148
149impl From<&'_ ast::FnDef> for FunctionSignature {
150 fn from(node: &ast::FnDef) -> FunctionSignature {
151 fn param_list(node: &ast::FnDef) -> Vec<String> {
152 let mut res = vec![];
153 if let Some(param_list) = node.param_list() {
154 if let Some(self_param) = param_list.self_param() {
155 res.push(self_param.syntax().text().to_string())
156 }
157
158 res.extend(param_list.params().map(|param| param.syntax().text().to_string()));
159 }
160 res
161 }
162
163 FunctionSignature {
164 kind: CallableKind::Function,
165 visibility: node.visibility().map(|n| n.syntax().text().to_string()),
166 name: node.name().map(|n| n.text().to_string()),
167 ret_type: node
168 .ret_type()
169 .and_then(|r| r.type_ref())
170 .map(|n| n.syntax().text().to_string()),
171 parameters: param_list(node),
172 generic_parameters: generic_parameters(node),
173 where_predicates: where_predicates(node),
174 // docs are processed separately
175 doc: None,
176 }
177 }
178}
179
180impl Display for FunctionSignature {
181 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
182 if let Some(t) = &self.visibility {
183 write!(f, "{} ", t)?;
184 }
185
186 if let Some(name) = &self.name {
187 match self.kind {
188 CallableKind::Function => write!(f, "fn {}", name)?,
189 CallableKind::StructConstructor => write!(f, "struct {}", name)?,
190 CallableKind::VariantConstructor => write!(f, "{}", name)?,
191 CallableKind::Macro => write!(f, "{}!", name)?,
192 }
193 }
194
195 if !self.generic_parameters.is_empty() {
196 join(self.generic_parameters.iter())
197 .separator(", ")
198 .surround_with("<", ">")
199 .to_fmt(f)?;
200 }
201
202 join(self.parameters.iter()).separator(", ").surround_with("(", ")").to_fmt(f)?;
203
204 if let Some(t) = &self.ret_type {
205 write!(f, " -> {}", t)?;
206 }
207
208 if !self.where_predicates.is_empty() {
209 write!(f, "\nwhere ")?;
210 join(self.where_predicates.iter()).separator(",\n ").to_fmt(f)?;
211 }
212
213 Ok(())
214 }
215}
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs
new file mode 100644
index 000000000..6ac60722b
--- /dev/null
+++ b/crates/ra_ide/src/display/navigation_target.rs
@@ -0,0 +1,411 @@
1//! FIXME: write short doc here
2
3use hir::{AssocItem, Either, FieldSource, HasSource, ModuleSource, Source};
4use ra_db::{FileId, SourceDatabase};
5use ra_syntax::{
6 ast::{self, DocCommentsOwner, NameOwner},
7 match_ast, AstNode, SmolStr,
8 SyntaxKind::{self, BIND_PAT},
9 TextRange,
10};
11
12use crate::{db::RootDatabase, expand::original_range, FileSymbol};
13
14use super::short_label::ShortLabel;
15
16/// `NavigationTarget` represents and element in the editor's UI which you can
17/// click on to navigate to a particular piece of code.
18///
19/// Typically, a `NavigationTarget` corresponds to some element in the source
20/// code, like a function or a struct, but this is not strictly required.
21#[derive(Debug, Clone)]
22pub struct NavigationTarget {
23 file_id: FileId,
24 name: SmolStr,
25 kind: SyntaxKind,
26 full_range: TextRange,
27 focus_range: Option<TextRange>,
28 container_name: Option<SmolStr>,
29 description: Option<String>,
30 docs: Option<String>,
31}
32
33pub(crate) trait ToNav {
34 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget;
35}
36
37impl NavigationTarget {
38 /// When `focus_range` is specified, returns it. otherwise
39 /// returns `full_range`
40 pub fn range(&self) -> TextRange {
41 self.focus_range.unwrap_or(self.full_range)
42 }
43
44 pub fn name(&self) -> &SmolStr {
45 &self.name
46 }
47
48 pub fn container_name(&self) -> Option<&SmolStr> {
49 self.container_name.as_ref()
50 }
51
52 pub fn kind(&self) -> SyntaxKind {
53 self.kind
54 }
55
56 pub fn file_id(&self) -> FileId {
57 self.file_id
58 }
59
60 pub fn full_range(&self) -> TextRange {
61 self.full_range
62 }
63
64 pub fn docs(&self) -> Option<&str> {
65 self.docs.as_ref().map(String::as_str)
66 }
67
68 pub fn description(&self) -> Option<&str> {
69 self.description.as_ref().map(String::as_str)
70 }
71
72 /// A "most interesting" range withing the `full_range`.
73 ///
74 /// Typically, `full_range` is the whole syntax node,
75 /// including doc comments, and `focus_range` is the range of the identifier.
76 pub fn focus_range(&self) -> Option<TextRange> {
77 self.focus_range
78 }
79
80 pub(crate) fn from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget {
81 let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default();
82 if let Some(src) = module.declaration_source(db) {
83 let frange = original_range(db, src.as_ref().map(|it| it.syntax()));
84 return NavigationTarget::from_syntax(
85 frange.file_id,
86 name,
87 None,
88 frange.range,
89 src.value.syntax().kind(),
90 src.value.doc_comment_text(),
91 src.value.short_label(),
92 );
93 }
94 module.to_nav(db)
95 }
96
97 pub(crate) fn from_def(
98 db: &RootDatabase,
99 module_def: hir::ModuleDef,
100 ) -> Option<NavigationTarget> {
101 let nav = match module_def {
102 hir::ModuleDef::Module(module) => module.to_nav(db),
103 hir::ModuleDef::Function(it) => it.to_nav(db),
104 hir::ModuleDef::Adt(it) => it.to_nav(db),
105 hir::ModuleDef::Const(it) => it.to_nav(db),
106 hir::ModuleDef::Static(it) => it.to_nav(db),
107 hir::ModuleDef::EnumVariant(it) => it.to_nav(db),
108 hir::ModuleDef::Trait(it) => it.to_nav(db),
109 hir::ModuleDef::TypeAlias(it) => it.to_nav(db),
110 hir::ModuleDef::BuiltinType(..) => {
111 return None;
112 }
113 };
114 Some(nav)
115 }
116
117 #[cfg(test)]
118 pub(crate) fn assert_match(&self, expected: &str) {
119 let actual = self.debug_render();
120 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
121 }
122
123 #[cfg(test)]
124 pub(crate) fn debug_render(&self) -> String {
125 let mut buf = format!(
126 "{} {:?} {:?} {:?}",
127 self.name(),
128 self.kind(),
129 self.file_id(),
130 self.full_range()
131 );
132 if let Some(focus_range) = self.focus_range() {
133 buf.push_str(&format!(" {:?}", focus_range))
134 }
135 if let Some(container_name) = self.container_name() {
136 buf.push_str(&format!(" {}", container_name))
137 }
138 buf
139 }
140
141 /// Allows `NavigationTarget` to be created from a `NameOwner`
142 pub(crate) fn from_named(
143 db: &RootDatabase,
144 node: Source<&dyn ast::NameOwner>,
145 docs: Option<String>,
146 description: Option<String>,
147 ) -> NavigationTarget {
148 //FIXME: use `_` instead of empty string
149 let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default();
150 let focus_range =
151 node.value.name().map(|it| original_range(db, node.with_value(it.syntax())).range);
152 let frange = original_range(db, node.map(|it| it.syntax()));
153
154 NavigationTarget::from_syntax(
155 frange.file_id,
156 name,
157 focus_range,
158 frange.range,
159 node.value.syntax().kind(),
160 docs,
161 description,
162 )
163 }
164
165 fn from_syntax(
166 file_id: FileId,
167 name: SmolStr,
168 focus_range: Option<TextRange>,
169 full_range: TextRange,
170 kind: SyntaxKind,
171 docs: Option<String>,
172 description: Option<String>,
173 ) -> NavigationTarget {
174 NavigationTarget {
175 file_id,
176 name,
177 kind,
178 full_range,
179 focus_range,
180 container_name: None,
181 description,
182 docs,
183 }
184 }
185}
186
187impl ToNav for FileSymbol {
188 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
189 NavigationTarget {
190 file_id: self.file_id,
191 name: self.name.clone(),
192 kind: self.ptr.kind(),
193 full_range: self.ptr.range(),
194 focus_range: self.name_range,
195 container_name: self.container_name.clone(),
196 description: description_from_symbol(db, self),
197 docs: docs_from_symbol(db, self),
198 }
199 }
200}
201
202pub(crate) trait ToNavFromAst {}
203impl ToNavFromAst for hir::Function {}
204impl ToNavFromAst for hir::Const {}
205impl ToNavFromAst for hir::Static {}
206impl ToNavFromAst for hir::Struct {}
207impl ToNavFromAst for hir::Enum {}
208impl ToNavFromAst for hir::EnumVariant {}
209impl ToNavFromAst for hir::Union {}
210impl ToNavFromAst for hir::TypeAlias {}
211impl ToNavFromAst for hir::Trait {}
212
213impl<D> ToNav for D
214where
215 D: HasSource + ToNavFromAst + Copy,
216 D::Ast: ast::DocCommentsOwner + ast::NameOwner + ShortLabel,
217{
218 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
219 let src = self.source(db);
220 NavigationTarget::from_named(
221 db,
222 src.as_ref().map(|it| it as &dyn ast::NameOwner),
223 src.value.doc_comment_text(),
224 src.value.short_label(),
225 )
226 }
227}
228
229impl ToNav for hir::Module {
230 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
231 let src = self.definition_source(db);
232 let name = self.name(db).map(|it| it.to_string().into()).unwrap_or_default();
233 match &src.value {
234 ModuleSource::SourceFile(node) => {
235 let frange = original_range(db, src.with_value(node.syntax()));
236
237 NavigationTarget::from_syntax(
238 frange.file_id,
239 name,
240 None,
241 frange.range,
242 node.syntax().kind(),
243 None,
244 None,
245 )
246 }
247 ModuleSource::Module(node) => {
248 let frange = original_range(db, src.with_value(node.syntax()));
249
250 NavigationTarget::from_syntax(
251 frange.file_id,
252 name,
253 None,
254 frange.range,
255 node.syntax().kind(),
256 node.doc_comment_text(),
257 node.short_label(),
258 )
259 }
260 }
261 }
262}
263
264impl ToNav for hir::ImplBlock {
265 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
266 let src = self.source(db);
267 let frange = original_range(db, src.as_ref().map(|it| it.syntax()));
268
269 NavigationTarget::from_syntax(
270 frange.file_id,
271 "impl".into(),
272 None,
273 frange.range,
274 src.value.syntax().kind(),
275 None,
276 None,
277 )
278 }
279}
280
281impl ToNav for hir::StructField {
282 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
283 let src = self.source(db);
284
285 match &src.value {
286 FieldSource::Named(it) => NavigationTarget::from_named(
287 db,
288 src.with_value(it),
289 it.doc_comment_text(),
290 it.short_label(),
291 ),
292 FieldSource::Pos(it) => {
293 let frange = original_range(db, src.with_value(it.syntax()));
294 NavigationTarget::from_syntax(
295 frange.file_id,
296 "".into(),
297 None,
298 frange.range,
299 it.syntax().kind(),
300 None,
301 None,
302 )
303 }
304 }
305 }
306}
307
308impl ToNav for hir::MacroDef {
309 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
310 let src = self.source(db);
311 log::debug!("nav target {:#?}", src.value.syntax());
312 NavigationTarget::from_named(
313 db,
314 src.as_ref().map(|it| it as &dyn ast::NameOwner),
315 src.value.doc_comment_text(),
316 None,
317 )
318 }
319}
320
321impl ToNav for hir::Adt {
322 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
323 match self {
324 hir::Adt::Struct(it) => it.to_nav(db),
325 hir::Adt::Union(it) => it.to_nav(db),
326 hir::Adt::Enum(it) => it.to_nav(db),
327 }
328 }
329}
330
331impl ToNav for hir::AssocItem {
332 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
333 match self {
334 AssocItem::Function(it) => it.to_nav(db),
335 AssocItem::Const(it) => it.to_nav(db),
336 AssocItem::TypeAlias(it) => it.to_nav(db),
337 }
338 }
339}
340
341impl ToNav for hir::Local {
342 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
343 let src = self.source(db);
344 let (full_range, focus_range) = match src.value {
345 Either::A(it) => {
346 (it.syntax().text_range(), it.name().map(|it| it.syntax().text_range()))
347 }
348 Either::B(it) => (it.syntax().text_range(), Some(it.self_kw_token().text_range())),
349 };
350 let name = match self.name(db) {
351 Some(it) => it.to_string().into(),
352 None => "".into(),
353 };
354 NavigationTarget {
355 file_id: src.file_id.original_file(db),
356 name,
357 kind: BIND_PAT,
358 full_range,
359 focus_range,
360 container_name: None,
361 description: None,
362 docs: None,
363 }
364 }
365}
366
367pub(crate) fn docs_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option<String> {
368 let parse = db.parse(symbol.file_id);
369 let node = symbol.ptr.to_node(parse.tree().syntax());
370
371 match_ast! {
372 match node {
373 ast::FnDef(it) => { it.doc_comment_text() },
374 ast::StructDef(it) => { it.doc_comment_text() },
375 ast::EnumDef(it) => { it.doc_comment_text() },
376 ast::TraitDef(it) => { it.doc_comment_text() },
377 ast::Module(it) => { it.doc_comment_text() },
378 ast::TypeAliasDef(it) => { it.doc_comment_text() },
379 ast::ConstDef(it) => { it.doc_comment_text() },
380 ast::StaticDef(it) => { it.doc_comment_text() },
381 ast::RecordFieldDef(it) => { it.doc_comment_text() },
382 ast::EnumVariant(it) => { it.doc_comment_text() },
383 ast::MacroCall(it) => { it.doc_comment_text() },
384 _ => None,
385 }
386 }
387}
388
389/// Get a description of a symbol.
390///
391/// e.g. `struct Name`, `enum Name`, `fn Name`
392pub(crate) fn description_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option<String> {
393 let parse = db.parse(symbol.file_id);
394 let node = symbol.ptr.to_node(parse.tree().syntax());
395
396 match_ast! {
397 match node {
398 ast::FnDef(it) => { it.short_label() },
399 ast::StructDef(it) => { it.short_label() },
400 ast::EnumDef(it) => { it.short_label() },
401 ast::TraitDef(it) => { it.short_label() },
402 ast::Module(it) => { it.short_label() },
403 ast::TypeAliasDef(it) => { it.short_label() },
404 ast::ConstDef(it) => { it.short_label() },
405 ast::StaticDef(it) => { it.short_label() },
406 ast::RecordFieldDef(it) => { it.short_label() },
407 ast::EnumVariant(it) => { it.short_label() },
408 _ => None,
409 }
410 }
411}
diff --git a/crates/ra_ide/src/display/short_label.rs b/crates/ra_ide/src/display/short_label.rs
new file mode 100644
index 000000000..9ffc9b980
--- /dev/null
+++ b/crates/ra_ide/src/display/short_label.rs
@@ -0,0 +1,97 @@
1//! FIXME: write short doc here
2
3use format_buf::format;
4use ra_syntax::ast::{self, AstNode, NameOwner, TypeAscriptionOwner, VisibilityOwner};
5
6pub(crate) trait ShortLabel {
7 fn short_label(&self) -> Option<String>;
8}
9
10impl ShortLabel for ast::FnDef {
11 fn short_label(&self) -> Option<String> {
12 Some(crate::display::function_label(self))
13 }
14}
15
16impl ShortLabel for ast::StructDef {
17 fn short_label(&self) -> Option<String> {
18 short_label_from_node(self, "struct ")
19 }
20}
21
22impl ShortLabel for ast::UnionDef {
23 fn short_label(&self) -> Option<String> {
24 short_label_from_node(self, "union ")
25 }
26}
27
28impl ShortLabel for ast::EnumDef {
29 fn short_label(&self) -> Option<String> {
30 short_label_from_node(self, "enum ")
31 }
32}
33
34impl ShortLabel for ast::TraitDef {
35 fn short_label(&self) -> Option<String> {
36 short_label_from_node(self, "trait ")
37 }
38}
39
40impl ShortLabel for ast::Module {
41 fn short_label(&self) -> Option<String> {
42 short_label_from_node(self, "mod ")
43 }
44}
45
46impl ShortLabel for ast::TypeAliasDef {
47 fn short_label(&self) -> Option<String> {
48 short_label_from_node(self, "type ")
49 }
50}
51
52impl ShortLabel for ast::ConstDef {
53 fn short_label(&self) -> Option<String> {
54 short_label_from_ascribed_node(self, "const ")
55 }
56}
57
58impl ShortLabel for ast::StaticDef {
59 fn short_label(&self) -> Option<String> {
60 short_label_from_ascribed_node(self, "static ")
61 }
62}
63
64impl ShortLabel for ast::RecordFieldDef {
65 fn short_label(&self) -> Option<String> {
66 short_label_from_ascribed_node(self, "")
67 }
68}
69
70impl ShortLabel for ast::EnumVariant {
71 fn short_label(&self) -> Option<String> {
72 Some(self.name()?.text().to_string())
73 }
74}
75
76fn short_label_from_ascribed_node<T>(node: &T, prefix: &str) -> Option<String>
77where
78 T: NameOwner + VisibilityOwner + TypeAscriptionOwner,
79{
80 let mut buf = short_label_from_node(node, prefix)?;
81
82 if let Some(type_ref) = node.ascribed_type() {
83 format!(buf, ": {}", type_ref.syntax());
84 }
85
86 Some(buf)
87}
88
89fn short_label_from_node<T>(node: &T, label: &str) -> Option<String>
90where
91 T: NameOwner + VisibilityOwner,
92{
93 let mut buf = node.visibility().map(|v| format!("{} ", v.syntax())).unwrap_or_default();
94 buf.push_str(label);
95 buf.push_str(node.name()?.text().as_str());
96 Some(buf)
97}
diff --git a/crates/ra_ide/src/display/structure.rs b/crates/ra_ide/src/display/structure.rs
new file mode 100644
index 000000000..a80d65ac7
--- /dev/null
+++ b/crates/ra_ide/src/display/structure.rs
@@ -0,0 +1,401 @@
1//! FIXME: write short doc here
2
3use crate::TextRange;
4
5use ra_syntax::{
6 ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner},
7 match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, WalkEvent,
8};
9
10#[derive(Debug, Clone)]
11pub struct StructureNode {
12 pub parent: Option<usize>,
13 pub label: String,
14 pub navigation_range: TextRange,
15 pub node_range: TextRange,
16 pub kind: SyntaxKind,
17 pub detail: Option<String>,
18 pub deprecated: bool,
19}
20
21pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> {
22 let mut res = Vec::new();
23 let mut stack = Vec::new();
24
25 for event in file.syntax().preorder() {
26 match event {
27 WalkEvent::Enter(node) => {
28 if let Some(mut symbol) = structure_node(&node) {
29 symbol.parent = stack.last().copied();
30 stack.push(res.len());
31 res.push(symbol);
32 }
33 }
34 WalkEvent::Leave(node) => {
35 if structure_node(&node).is_some() {
36 stack.pop().unwrap();
37 }
38 }
39 }
40 }
41 res
42}
43
44fn structure_node(node: &SyntaxNode) -> Option<StructureNode> {
45 fn decl<N: NameOwner + AttrsOwner>(node: N) -> Option<StructureNode> {
46 decl_with_detail(node, None)
47 }
48
49 fn decl_with_ascription<N: NameOwner + AttrsOwner + TypeAscriptionOwner>(
50 node: N,
51 ) -> Option<StructureNode> {
52 let ty = node.ascribed_type();
53 decl_with_type_ref(node, ty)
54 }
55
56 fn decl_with_type_ref<N: NameOwner + AttrsOwner>(
57 node: N,
58 type_ref: Option<ast::TypeRef>,
59 ) -> Option<StructureNode> {
60 let detail = type_ref.map(|type_ref| {
61 let mut detail = String::new();
62 collapse_ws(type_ref.syntax(), &mut detail);
63 detail
64 });
65 decl_with_detail(node, detail)
66 }
67
68 fn decl_with_detail<N: NameOwner + AttrsOwner>(
69 node: N,
70 detail: Option<String>,
71 ) -> Option<StructureNode> {
72 let name = node.name()?;
73
74 Some(StructureNode {
75 parent: None,
76 label: name.text().to_string(),
77 navigation_range: name.syntax().text_range(),
78 node_range: node.syntax().text_range(),
79 kind: node.syntax().kind(),
80 detail,
81 deprecated: node.attrs().filter_map(|x| x.simple_name()).any(|x| x == "deprecated"),
82 })
83 }
84
85 fn collapse_ws(node: &SyntaxNode, output: &mut String) {
86 let mut can_insert_ws = false;
87 node.text().for_each_chunk(|chunk| {
88 for line in chunk.lines() {
89 let line = line.trim();
90 if line.is_empty() {
91 if can_insert_ws {
92 output.push(' ');
93 can_insert_ws = false;
94 }
95 } else {
96 output.push_str(line);
97 can_insert_ws = true;
98 }
99 }
100 })
101 }
102
103 match_ast! {
104 match node {
105 ast::FnDef(it) => {
106 let mut detail = String::from("fn");
107 if let Some(type_param_list) = it.type_param_list() {
108 collapse_ws(type_param_list.syntax(), &mut detail);
109 }
110 if let Some(param_list) = it.param_list() {
111 collapse_ws(param_list.syntax(), &mut detail);
112 }
113 if let Some(ret_type) = it.ret_type() {
114 detail.push_str(" ");
115 collapse_ws(ret_type.syntax(), &mut detail);
116 }
117
118 decl_with_detail(it, Some(detail))
119 },
120 ast::StructDef(it) => { decl(it) },
121 ast::EnumDef(it) => { decl(it) },
122 ast::EnumVariant(it) => { decl(it) },
123 ast::TraitDef(it) => { decl(it) },
124 ast::Module(it) => { decl(it) },
125 ast::TypeAliasDef(it) => {
126 let ty = it.type_ref();
127 decl_with_type_ref(it, ty)
128 },
129 ast::RecordFieldDef(it) => { decl_with_ascription(it) },
130 ast::ConstDef(it) => { decl_with_ascription(it) },
131 ast::StaticDef(it) => { decl_with_ascription(it) },
132 ast::ImplBlock(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 let first_token = it.syntax().first_token().unwrap();
155 if first_token.text().as_str() != "macro_rules" {
156 return None;
157 }
158 decl(it)
159 },
160 _ => None,
161 }
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use insta::assert_debug_snapshot;
169
170 #[test]
171 fn test_file_structure() {
172 let file = SourceFile::parse(
173 r#"
174struct Foo {
175 x: i32
176}
177
178mod m {
179 fn bar1() {}
180 fn bar2<T>(t: T) -> T {}
181 fn bar3<A,
182 B>(a: A,
183 b: B) -> Vec<
184 u32
185 > {}
186}
187
188enum E { X, Y(i32) }
189type T = ();
190static S: i32 = 92;
191const C: i32 = 92;
192
193impl E {}
194
195impl fmt::Debug for E {}
196
197macro_rules! mc {
198 () => {}
199}
200
201#[deprecated]
202fn obsolete() {}
203
204#[deprecated(note = "for awhile")]
205fn very_obsolete() {}
206"#,
207 )
208 .ok()
209 .unwrap();
210 let structure = file_structure(&file);
211 assert_debug_snapshot!(structure,
212 @r###"
213 [
214 StructureNode {
215 parent: None,
216 label: "Foo",
217 navigation_range: [8; 11),
218 node_range: [1; 26),
219 kind: STRUCT_DEF,
220 detail: None,
221 deprecated: false,
222 },
223 StructureNode {
224 parent: Some(
225 0,
226 ),
227 label: "x",
228 navigation_range: [18; 19),
229 node_range: [18; 24),
230 kind: RECORD_FIELD_DEF,
231 detail: Some(
232 "i32",
233 ),
234 deprecated: false,
235 },
236 StructureNode {
237 parent: None,
238 label: "m",
239 navigation_range: [32; 33),
240 node_range: [28; 158),
241 kind: MODULE,
242 detail: None,
243 deprecated: false,
244 },
245 StructureNode {
246 parent: Some(
247 2,
248 ),
249 label: "bar1",
250 navigation_range: [43; 47),
251 node_range: [40; 52),
252 kind: FN_DEF,
253 detail: Some(
254 "fn()",
255 ),
256 deprecated: false,
257 },
258 StructureNode {
259 parent: Some(
260 2,
261 ),
262 label: "bar2",
263 navigation_range: [60; 64),
264 node_range: [57; 81),
265 kind: FN_DEF,
266 detail: Some(
267 "fn<T>(t: T) -> T",
268 ),
269 deprecated: false,
270 },
271 StructureNode {
272 parent: Some(
273 2,
274 ),
275 label: "bar3",
276 navigation_range: [89; 93),
277 node_range: [86; 156),
278 kind: FN_DEF,
279 detail: Some(
280 "fn<A, B>(a: A, b: B) -> Vec< u32 >",
281 ),
282 deprecated: false,
283 },
284 StructureNode {
285 parent: None,
286 label: "E",
287 navigation_range: [165; 166),
288 node_range: [160; 180),
289 kind: ENUM_DEF,
290 detail: None,
291 deprecated: false,
292 },
293 StructureNode {
294 parent: Some(
295 6,
296 ),
297 label: "X",
298 navigation_range: [169; 170),
299 node_range: [169; 170),
300 kind: ENUM_VARIANT,
301 detail: None,
302 deprecated: false,
303 },
304 StructureNode {
305 parent: Some(
306 6,
307 ),
308 label: "Y",
309 navigation_range: [172; 173),
310 node_range: [172; 178),
311 kind: ENUM_VARIANT,
312 detail: None,
313 deprecated: false,
314 },
315 StructureNode {
316 parent: None,
317 label: "T",
318 navigation_range: [186; 187),
319 node_range: [181; 193),
320 kind: TYPE_ALIAS_DEF,
321 detail: Some(
322 "()",
323 ),
324 deprecated: false,
325 },
326 StructureNode {
327 parent: None,
328 label: "S",
329 navigation_range: [201; 202),
330 node_range: [194; 213),
331 kind: STATIC_DEF,
332 detail: Some(
333 "i32",
334 ),
335 deprecated: false,
336 },
337 StructureNode {
338 parent: None,
339 label: "C",
340 navigation_range: [220; 221),
341 node_range: [214; 232),
342 kind: CONST_DEF,
343 detail: Some(
344 "i32",
345 ),
346 deprecated: false,
347 },
348 StructureNode {
349 parent: None,
350 label: "impl E",
351 navigation_range: [239; 240),
352 node_range: [234; 243),
353 kind: IMPL_BLOCK,
354 detail: None,
355 deprecated: false,
356 },
357 StructureNode {
358 parent: None,
359 label: "impl fmt::Debug for E",
360 navigation_range: [265; 266),
361 node_range: [245; 269),
362 kind: IMPL_BLOCK,
363 detail: None,
364 deprecated: false,
365 },
366 StructureNode {
367 parent: None,
368 label: "mc",
369 navigation_range: [284; 286),
370 node_range: [271; 303),
371 kind: MACRO_CALL,
372 detail: None,
373 deprecated: false,
374 },
375 StructureNode {
376 parent: None,
377 label: "obsolete",
378 navigation_range: [322; 330),
379 node_range: [305; 335),
380 kind: FN_DEF,
381 detail: Some(
382 "fn()",
383 ),
384 deprecated: true,
385 },
386 StructureNode {
387 parent: None,
388 label: "very_obsolete",
389 navigation_range: [375; 388),
390 node_range: [337; 393),
391 kind: FN_DEF,
392 detail: Some(
393 "fn()",
394 ),
395 deprecated: true,
396 },
397 ]
398 "###
399 );
400 }
401}