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.rs334
-rw-r--r--crates/ra_ide/src/display/navigation_target.rs207
-rw-r--r--crates/ra_ide/src/display/short_label.rs36
-rw-r--r--crates/ra_ide/src/display/structure.rs438
4 files changed, 143 insertions, 872 deletions
diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs
deleted file mode 100644
index a98264fb3..000000000
--- a/crates/ra_ide/src/display/function_signature.rs
+++ /dev/null
@@ -1,334 +0,0 @@
1//! FIXME: write short doc here
2
3// FIXME: this modules relies on strings and AST way too much, and it should be
4// rewritten (matklad 2020-05-07)
5use std::{
6 convert::From,
7 fmt::{self, Display},
8};
9
10use hir::{Docs, Documentation, HasSource, HirDisplay};
11use ra_ide_db::RootDatabase;
12use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
13use stdx::{split_delim, SepBy};
14
15use crate::display::{generic_parameters, where_predicates};
16
17#[derive(Debug)]
18pub enum CallableKind {
19 Function,
20 StructConstructor,
21 VariantConstructor,
22 Macro,
23}
24
25/// Contains information about a function signature
26#[derive(Debug)]
27pub struct FunctionSignature {
28 pub kind: CallableKind,
29 /// Optional visibility
30 pub visibility: Option<String>,
31 /// Qualifiers like `async`, `unsafe`, ...
32 pub qualifier: FunctionQualifier,
33 /// Name of the function
34 pub name: Option<String>,
35 /// Documentation for the function
36 pub doc: Option<Documentation>,
37 /// Generic parameters
38 pub generic_parameters: Vec<String>,
39 /// Parameters of the function
40 pub parameters: Vec<String>,
41 /// Parameter names of the function
42 pub parameter_names: Vec<String>,
43 /// Parameter types of the function
44 pub parameter_types: Vec<String>,
45 /// Optional return type
46 pub ret_type: Option<String>,
47 /// Where predicates
48 pub where_predicates: Vec<String>,
49 /// Self param presence
50 pub has_self_param: bool,
51}
52
53#[derive(Debug, Default)]
54pub struct FunctionQualifier {
55 // `async` and `const` are mutually exclusive. Do we need to enforcing it here?
56 pub is_async: bool,
57 pub is_const: bool,
58 pub is_unsafe: bool,
59 /// The string `extern ".."`
60 pub extern_abi: Option<String>,
61}
62
63impl FunctionSignature {
64 pub(crate) fn with_doc_opt(mut self, doc: Option<Documentation>) -> Self {
65 self.doc = doc;
66 self
67 }
68
69 pub(crate) fn from_hir(db: &RootDatabase, function: hir::Function) -> Self {
70 let doc = function.docs(db);
71 let ast_node = function.source(db).value;
72 FunctionSignature::from(&ast_node).with_doc_opt(doc)
73 }
74
75 pub(crate) fn from_struct(db: &RootDatabase, st: hir::Struct) -> Option<Self> {
76 let node: ast::StructDef = st.source(db).value;
77 if let ast::StructKind::Record(_) = node.kind() {
78 return None;
79 };
80
81 let mut params = vec![];
82 let mut parameter_types = vec![];
83 for field in st.fields(db).into_iter() {
84 let ty = field.signature_ty(db);
85 let raw_param = format!("{}", ty.display(db));
86
87 if let Some(param_type) = raw_param.split(':').nth(1).and_then(|it| it.get(1..)) {
88 parameter_types.push(param_type.to_string());
89 } else {
90 // useful when you have tuple struct
91 parameter_types.push(raw_param.clone());
92 }
93 params.push(raw_param);
94 }
95
96 Some(
97 FunctionSignature {
98 kind: CallableKind::StructConstructor,
99 visibility: node.visibility().map(|n| n.syntax().text().to_string()),
100 // Do we need `const`?
101 qualifier: Default::default(),
102 name: node.name().map(|n| n.text().to_string()),
103 ret_type: node.name().map(|n| n.text().to_string()),
104 parameters: params,
105 parameter_names: vec![],
106 parameter_types,
107 generic_parameters: generic_parameters(&node),
108 where_predicates: where_predicates(&node),
109 doc: None,
110 has_self_param: false,
111 }
112 .with_doc_opt(st.docs(db)),
113 )
114 }
115
116 pub(crate) fn from_enum_variant(db: &RootDatabase, variant: hir::EnumVariant) -> Option<Self> {
117 let node: ast::EnumVariant = variant.source(db).value;
118 match node.kind() {
119 ast::StructKind::Record(_) | ast::StructKind::Unit => return None,
120 _ => (),
121 };
122
123 let parent_name = variant.parent_enum(db).name(db).to_string();
124
125 let name = format!("{}::{}", parent_name, variant.name(db));
126
127 let mut params = vec![];
128 let mut parameter_types = vec![];
129 for field in variant.fields(db).into_iter() {
130 let ty = field.signature_ty(db);
131 let raw_param = format!("{}", ty.display(db));
132 if let Some(param_type) = raw_param.split(':').nth(1).and_then(|it| it.get(1..)) {
133 parameter_types.push(param_type.to_string());
134 } else {
135 // The unwrap_or_else is useful when you have tuple
136 parameter_types.push(raw_param);
137 }
138 let name = field.name(db);
139
140 params.push(format!("{}: {}", name, ty.display(db)));
141 }
142
143 Some(
144 FunctionSignature {
145 kind: CallableKind::VariantConstructor,
146 visibility: None,
147 // Do we need `const`?
148 qualifier: Default::default(),
149 name: Some(name),
150 ret_type: None,
151 parameters: params,
152 parameter_names: vec![],
153 parameter_types,
154 generic_parameters: vec![],
155 where_predicates: vec![],
156 doc: None,
157 has_self_param: false,
158 }
159 .with_doc_opt(variant.docs(db)),
160 )
161 }
162
163 pub(crate) fn from_macro(db: &RootDatabase, macro_def: hir::MacroDef) -> Option<Self> {
164 let node: ast::MacroCall = macro_def.source(db).value;
165
166 let params = vec![];
167
168 Some(
169 FunctionSignature {
170 kind: CallableKind::Macro,
171 visibility: None,
172 qualifier: Default::default(),
173 name: node.name().map(|n| n.text().to_string()),
174 ret_type: None,
175 parameters: params,
176 parameter_names: vec![],
177 parameter_types: vec![],
178 generic_parameters: vec![],
179 where_predicates: vec![],
180 doc: None,
181 has_self_param: false,
182 }
183 .with_doc_opt(macro_def.docs(db)),
184 )
185 }
186}
187
188impl From<&'_ ast::FnDef> for FunctionSignature {
189 fn from(node: &ast::FnDef) -> FunctionSignature {
190 fn param_list(node: &ast::FnDef) -> (bool, Vec<String>, Vec<String>) {
191 let mut res = vec![];
192 let mut res_types = vec![];
193 let mut has_self_param = false;
194 if let Some(param_list) = node.param_list() {
195 if let Some(self_param) = param_list.self_param() {
196 has_self_param = true;
197 let raw_param = self_param.syntax().text().to_string();
198
199 res_types.push(
200 raw_param
201 .split(':')
202 .nth(1)
203 .and_then(|it| it.get(1..))
204 .unwrap_or_else(|| "Self")
205 .to_string(),
206 );
207 res.push(raw_param);
208 }
209
210 // macro-generated functions are missing whitespace
211 fn fmt_param(param: ast::Param) -> String {
212 let text = param.syntax().text().to_string();
213 match split_delim(&text, ':') {
214 Some((left, right)) => format!("{}: {}", left.trim(), right.trim()),
215 _ => text,
216 }
217 }
218
219 res.extend(param_list.params().map(fmt_param));
220 res_types.extend(param_list.params().map(|param| {
221 let param_text = param.syntax().text().to_string();
222 match param_text.split(':').nth(1).and_then(|it| it.get(1..)) {
223 Some(it) => it.to_string(),
224 None => param_text,
225 }
226 }));
227 }
228 (has_self_param, res, res_types)
229 }
230
231 fn param_name_list(node: &ast::FnDef) -> Vec<String> {
232 let mut res = vec![];
233 if let Some(param_list) = node.param_list() {
234 if let Some(self_param) = param_list.self_param() {
235 res.push(self_param.syntax().text().to_string())
236 }
237
238 res.extend(
239 param_list
240 .params()
241 .map(|param| {
242 Some(
243 param
244 .pat()?
245 .syntax()
246 .descendants()
247 .find_map(ast::Name::cast)?
248 .text()
249 .to_string(),
250 )
251 })
252 .map(|param| param.unwrap_or_default()),
253 );
254 }
255 res
256 }
257
258 let (has_self_param, parameters, parameter_types) = param_list(node);
259
260 FunctionSignature {
261 kind: CallableKind::Function,
262 visibility: node.visibility().map(|n| n.syntax().text().to_string()),
263 qualifier: FunctionQualifier {
264 is_async: node.async_token().is_some(),
265 is_const: node.const_token().is_some(),
266 is_unsafe: node.unsafe_token().is_some(),
267 extern_abi: node.abi().map(|n| n.to_string()),
268 },
269 name: node.name().map(|n| n.text().to_string()),
270 ret_type: node
271 .ret_type()
272 .and_then(|r| r.type_ref())
273 .map(|n| n.syntax().text().to_string()),
274 parameters,
275 parameter_names: param_name_list(node),
276 parameter_types,
277 generic_parameters: generic_parameters(node),
278 where_predicates: where_predicates(node),
279 // docs are processed separately
280 doc: None,
281 has_self_param,
282 }
283 }
284}
285
286impl Display for FunctionSignature {
287 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
288 if let Some(t) = &self.visibility {
289 write!(f, "{} ", t)?;
290 }
291
292 if self.qualifier.is_async {
293 write!(f, "async ")?;
294 }
295
296 if self.qualifier.is_const {
297 write!(f, "const ")?;
298 }
299
300 if self.qualifier.is_unsafe {
301 write!(f, "unsafe ")?;
302 }
303
304 if let Some(extern_abi) = &self.qualifier.extern_abi {
305 // Keyword `extern` is included in the string.
306 write!(f, "{} ", extern_abi)?;
307 }
308
309 if let Some(name) = &self.name {
310 match self.kind {
311 CallableKind::Function => write!(f, "fn {}", name)?,
312 CallableKind::StructConstructor => write!(f, "struct {}", name)?,
313 CallableKind::VariantConstructor => write!(f, "{}", name)?,
314 CallableKind::Macro => write!(f, "{}!", name)?,
315 }
316 }
317
318 if !self.generic_parameters.is_empty() {
319 write!(f, "{}", self.generic_parameters.iter().sep_by(", ").surround_with("<", ">"))?;
320 }
321
322 write!(f, "{}", self.parameters.iter().sep_by(", ").surround_with("(", ")"))?;
323
324 if let Some(t) = &self.ret_type {
325 write!(f, " -> {}", t)?;
326 }
327
328 if !self.where_predicates.is_empty() {
329 write!(f, "\nwhere {}", self.where_predicates.iter().sep_by(",\n "))?;
330 }
331
332 Ok(())
333 }
334}
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs
index 0b52b01ab..45fbc86ef 100644
--- a/crates/ra_ide/src/display/navigation_target.rs
+++ b/crates/ra_ide/src/display/navigation_target.rs
@@ -11,7 +11,7 @@ use ra_syntax::{
11 TextRange, 11 TextRange,
12}; 12};
13 13
14use crate::{FileRange, FileSymbol}; 14use crate::FileSymbol;
15 15
16use super::short_label::ShortLabel; 16use super::short_label::ShortLabel;
17 17
@@ -22,15 +22,28 @@ use super::short_label::ShortLabel;
22/// code, like a function or a struct, but this is not strictly required. 22/// code, like a function or a struct, but this is not strictly required.
23#[derive(Debug, Clone, PartialEq, Eq, Hash)] 23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
24pub struct NavigationTarget { 24pub struct NavigationTarget {
25 // FIXME: use FileRange? 25 pub file_id: FileId,
26 file_id: FileId, 26 /// Range which encompasses the whole element.
27 full_range: TextRange, 27 ///
28 name: SmolStr, 28 /// Should include body, doc comments, attributes, etc.
29 kind: SyntaxKind, 29 ///
30 focus_range: Option<TextRange>, 30 /// Clients should use this range to answer "is the cursor inside the
31 container_name: Option<SmolStr>, 31 /// element?" question.
32 description: Option<String>, 32 pub full_range: TextRange,
33 docs: Option<String>, 33 /// A "most interesting" range withing the `full_range`.
34 ///
35 /// Typically, `full_range` is the whole syntax node, including doc
36 /// comments, and `focus_range` is the range of the identifier. "Most
37 /// interesting" range within the full range, typically the range of
38 /// identifier.
39 ///
40 /// Clients should place the cursor on this range when navigating to this target.
41 pub focus_range: Option<TextRange>,
42 pub name: SmolStr,
43 pub kind: SyntaxKind,
44 pub container_name: Option<SmolStr>,
45 pub description: Option<String>,
46 pub docs: Option<String>,
34} 47}
35 48
36pub(crate) trait ToNav { 49pub(crate) trait ToNav {
@@ -42,52 +55,10 @@ pub(crate) trait TryToNav {
42} 55}
43 56
44impl NavigationTarget { 57impl NavigationTarget {
45 /// When `focus_range` is specified, returns it. otherwise 58 pub fn focus_or_full_range(&self) -> TextRange {
46 /// returns `full_range`
47 pub fn range(&self) -> TextRange {
48 self.focus_range.unwrap_or(self.full_range) 59 self.focus_range.unwrap_or(self.full_range)
49 } 60 }
50 61
51 pub fn name(&self) -> &SmolStr {
52 &self.name
53 }
54
55 pub fn container_name(&self) -> Option<&SmolStr> {
56 self.container_name.as_ref()
57 }
58
59 pub fn kind(&self) -> SyntaxKind {
60 self.kind
61 }
62
63 pub fn file_id(&self) -> FileId {
64 self.file_id
65 }
66
67 pub fn file_range(&self) -> FileRange {
68 FileRange { file_id: self.file_id, range: self.full_range }
69 }
70
71 pub fn full_range(&self) -> TextRange {
72 self.full_range
73 }
74
75 pub fn docs(&self) -> Option<&str> {
76 self.docs.as_deref()
77 }
78
79 pub fn description(&self) -> Option<&str> {
80 self.description.as_deref()
81 }
82
83 /// A "most interesting" range withing the `full_range`.
84 ///
85 /// Typically, `full_range` is the whole syntax node,
86 /// including doc comments, and `focus_range` is the range of the identifier.
87 pub fn focus_range(&self) -> Option<TextRange> {
88 self.focus_range
89 }
90
91 pub(crate) fn from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget { 62 pub(crate) fn from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget {
92 let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); 63 let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default();
93 if let Some(src) = module.declaration_source(db) { 64 if let Some(src) = module.declaration_source(db) {
@@ -114,17 +85,12 @@ impl NavigationTarget {
114 85
115 #[cfg(test)] 86 #[cfg(test)]
116 pub(crate) fn debug_render(&self) -> String { 87 pub(crate) fn debug_render(&self) -> String {
117 let mut buf = format!( 88 let mut buf =
118 "{} {:?} {:?} {:?}", 89 format!("{} {:?} {:?} {:?}", self.name, self.kind, self.file_id, self.full_range);
119 self.name(), 90 if let Some(focus_range) = self.focus_range {
120 self.kind(),
121 self.file_id(),
122 self.full_range()
123 );
124 if let Some(focus_range) = self.focus_range() {
125 buf.push_str(&format!(" {:?}", focus_range)) 91 buf.push_str(&format!(" {:?}", focus_range))
126 } 92 }
127 if let Some(container_name) = self.container_name() { 93 if let Some(container_name) = &self.container_name {
128 buf.push_str(&format!(" {}", container_name)) 94 buf.push_str(&format!(" {}", container_name))
129 } 95 }
130 buf 96 buf
@@ -278,16 +244,22 @@ impl ToNav for hir::Module {
278impl ToNav for hir::ImplDef { 244impl ToNav for hir::ImplDef {
279 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { 245 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
280 let src = self.source(db); 246 let src = self.source(db);
281 let frange = if let Some(item) = self.is_builtin_derive(db) { 247 let derive_attr = self.is_builtin_derive(db);
248 let frange = if let Some(item) = &derive_attr {
282 original_range(db, item.syntax()) 249 original_range(db, item.syntax())
283 } else { 250 } else {
284 original_range(db, src.as_ref().map(|it| it.syntax())) 251 original_range(db, src.as_ref().map(|it| it.syntax()))
285 }; 252 };
253 let focus_range = if derive_attr.is_some() {
254 None
255 } else {
256 src.value.target_type().map(|ty| original_range(db, src.with_value(ty.syntax())).range)
257 };
286 258
287 NavigationTarget::from_syntax( 259 NavigationTarget::from_syntax(
288 frange.file_id, 260 frange.file_id,
289 "impl".into(), 261 "impl".into(),
290 None, 262 focus_range,
291 frange.range, 263 frange.range,
292 src.value.syntax().kind(), 264 src.value.syntax().kind(),
293 ) 265 )
@@ -407,16 +379,16 @@ pub(crate) fn docs_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option
407 379
408 match_ast! { 380 match_ast! {
409 match node { 381 match node {
410 ast::FnDef(it) => it.doc_comment_text(), 382 ast::Fn(it) => it.doc_comment_text(),
411 ast::StructDef(it) => it.doc_comment_text(), 383 ast::Struct(it) => it.doc_comment_text(),
412 ast::EnumDef(it) => it.doc_comment_text(), 384 ast::Enum(it) => it.doc_comment_text(),
413 ast::TraitDef(it) => it.doc_comment_text(), 385 ast::Trait(it) => it.doc_comment_text(),
414 ast::Module(it) => it.doc_comment_text(), 386 ast::Module(it) => it.doc_comment_text(),
415 ast::TypeAliasDef(it) => it.doc_comment_text(), 387 ast::TypeAlias(it) => it.doc_comment_text(),
416 ast::ConstDef(it) => it.doc_comment_text(), 388 ast::Const(it) => it.doc_comment_text(),
417 ast::StaticDef(it) => it.doc_comment_text(), 389 ast::Static(it) => it.doc_comment_text(),
418 ast::RecordFieldDef(it) => it.doc_comment_text(), 390 ast::RecordField(it) => it.doc_comment_text(),
419 ast::EnumVariant(it) => it.doc_comment_text(), 391 ast::Variant(it) => it.doc_comment_text(),
420 ast::MacroCall(it) => it.doc_comment_text(), 392 ast::MacroCall(it) => it.doc_comment_text(),
421 _ => None, 393 _ => None,
422 } 394 }
@@ -432,17 +404,88 @@ pub(crate) fn description_from_symbol(db: &RootDatabase, symbol: &FileSymbol) ->
432 404
433 match_ast! { 405 match_ast! {
434 match node { 406 match node {
435 ast::FnDef(it) => it.short_label(), 407 ast::Fn(it) => it.short_label(),
436 ast::StructDef(it) => it.short_label(), 408 ast::Struct(it) => it.short_label(),
437 ast::EnumDef(it) => it.short_label(), 409 ast::Enum(it) => it.short_label(),
438 ast::TraitDef(it) => it.short_label(), 410 ast::Trait(it) => it.short_label(),
439 ast::Module(it) => it.short_label(), 411 ast::Module(it) => it.short_label(),
440 ast::TypeAliasDef(it) => it.short_label(), 412 ast::TypeAlias(it) => it.short_label(),
441 ast::ConstDef(it) => it.short_label(), 413 ast::Const(it) => it.short_label(),
442 ast::StaticDef(it) => it.short_label(), 414 ast::Static(it) => it.short_label(),
443 ast::RecordFieldDef(it) => it.short_label(), 415 ast::RecordField(it) => it.short_label(),
444 ast::EnumVariant(it) => it.short_label(), 416 ast::Variant(it) => it.short_label(),
445 _ => None, 417 _ => None,
446 } 418 }
447 } 419 }
448} 420}
421
422#[cfg(test)]
423mod tests {
424 use expect::expect;
425
426 use crate::{mock_analysis::single_file, Query};
427
428 #[test]
429 fn test_nav_for_symbol() {
430 let (analysis, _) = single_file(
431 r#"
432enum FooInner { }
433fn foo() { enum FooInner { } }
434"#,
435 );
436
437 let navs = analysis.symbol_search(Query::new("FooInner".to_string())).unwrap();
438 expect![[r#"
439 [
440 NavigationTarget {
441 file_id: FileId(
442 1,
443 ),
444 full_range: 0..17,
445 focus_range: Some(
446 5..13,
447 ),
448 name: "FooInner",
449 kind: ENUM,
450 container_name: None,
451 description: Some(
452 "enum FooInner",
453 ),
454 docs: None,
455 },
456 NavigationTarget {
457 file_id: FileId(
458 1,
459 ),
460 full_range: 29..46,
461 focus_range: Some(
462 34..42,
463 ),
464 name: "FooInner",
465 kind: ENUM,
466 container_name: Some(
467 "foo",
468 ),
469 description: Some(
470 "enum FooInner",
471 ),
472 docs: None,
473 },
474 ]
475 "#]]
476 .assert_debug_eq(&navs);
477 }
478
479 #[test]
480 fn test_world_symbols_are_case_sensitive() {
481 let (analysis, _) = single_file(
482 r#"
483fn foo() {}
484struct Foo;
485"#,
486 );
487
488 let navs = analysis.symbol_search(Query::new("foo".to_string())).unwrap();
489 assert_eq!(navs.len(), 2)
490 }
491}
diff --git a/crates/ra_ide/src/display/short_label.rs b/crates/ra_ide/src/display/short_label.rs
index d37260e96..bddf1bd47 100644
--- a/crates/ra_ide/src/display/short_label.rs
+++ b/crates/ra_ide/src/display/short_label.rs
@@ -1,37 +1,37 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use ra_syntax::ast::{self, AstNode, NameOwner, TypeAscriptionOwner, VisibilityOwner}; 3use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
4use stdx::format_to; 4use stdx::format_to;
5 5
6pub(crate) trait ShortLabel { 6pub(crate) trait ShortLabel {
7 fn short_label(&self) -> Option<String>; 7 fn short_label(&self) -> Option<String>;
8} 8}
9 9
10impl ShortLabel for ast::FnDef { 10impl ShortLabel for ast::Fn {
11 fn short_label(&self) -> Option<String> { 11 fn short_label(&self) -> Option<String> {
12 Some(crate::display::function_label(self)) 12 Some(crate::display::function_declaration(self))
13 } 13 }
14} 14}
15 15
16impl ShortLabel for ast::StructDef { 16impl ShortLabel for ast::Struct {
17 fn short_label(&self) -> Option<String> { 17 fn short_label(&self) -> Option<String> {
18 short_label_from_node(self, "struct ") 18 short_label_from_node(self, "struct ")
19 } 19 }
20} 20}
21 21
22impl ShortLabel for ast::UnionDef { 22impl ShortLabel for ast::Union {
23 fn short_label(&self) -> Option<String> { 23 fn short_label(&self) -> Option<String> {
24 short_label_from_node(self, "union ") 24 short_label_from_node(self, "union ")
25 } 25 }
26} 26}
27 27
28impl ShortLabel for ast::EnumDef { 28impl ShortLabel for ast::Enum {
29 fn short_label(&self) -> Option<String> { 29 fn short_label(&self) -> Option<String> {
30 short_label_from_node(self, "enum ") 30 short_label_from_node(self, "enum ")
31 } 31 }
32} 32}
33 33
34impl ShortLabel for ast::TraitDef { 34impl ShortLabel for ast::Trait {
35 fn short_label(&self) -> Option<String> { 35 fn short_label(&self) -> Option<String> {
36 if self.unsafe_token().is_some() { 36 if self.unsafe_token().is_some() {
37 short_label_from_node(self, "unsafe trait ") 37 short_label_from_node(self, "unsafe trait ")
@@ -47,43 +47,43 @@ impl ShortLabel for ast::Module {
47 } 47 }
48} 48}
49 49
50impl ShortLabel for ast::TypeAliasDef { 50impl ShortLabel for ast::TypeAlias {
51 fn short_label(&self) -> Option<String> { 51 fn short_label(&self) -> Option<String> {
52 short_label_from_node(self, "type ") 52 short_label_from_node(self, "type ")
53 } 53 }
54} 54}
55 55
56impl ShortLabel for ast::ConstDef { 56impl ShortLabel for ast::Const {
57 fn short_label(&self) -> Option<String> { 57 fn short_label(&self) -> Option<String> {
58 short_label_from_ascribed_node(self, "const ") 58 short_label_from_ty(self, self.ty(), "const ")
59 } 59 }
60} 60}
61 61
62impl ShortLabel for ast::StaticDef { 62impl ShortLabel for ast::Static {
63 fn short_label(&self) -> Option<String> { 63 fn short_label(&self) -> Option<String> {
64 short_label_from_ascribed_node(self, "static ") 64 short_label_from_ty(self, self.ty(), "static ")
65 } 65 }
66} 66}
67 67
68impl ShortLabel for ast::RecordFieldDef { 68impl ShortLabel for ast::RecordField {
69 fn short_label(&self) -> Option<String> { 69 fn short_label(&self) -> Option<String> {
70 short_label_from_ascribed_node(self, "") 70 short_label_from_ty(self, self.ty(), "")
71 } 71 }
72} 72}
73 73
74impl ShortLabel for ast::EnumVariant { 74impl ShortLabel for ast::Variant {
75 fn short_label(&self) -> Option<String> { 75 fn short_label(&self) -> Option<String> {
76 Some(self.name()?.text().to_string()) 76 Some(self.name()?.text().to_string())
77 } 77 }
78} 78}
79 79
80fn short_label_from_ascribed_node<T>(node: &T, prefix: &str) -> Option<String> 80fn short_label_from_ty<T>(node: &T, ty: Option<ast::TypeRef>, prefix: &str) -> Option<String>
81where 81where
82 T: NameOwner + VisibilityOwner + TypeAscriptionOwner, 82 T: NameOwner + VisibilityOwner,
83{ 83{
84 let mut buf = short_label_from_node(node, prefix)?; 84 let mut buf = short_label_from_node(node, prefix)?;
85 85
86 if let Some(type_ref) = node.ascribed_type() { 86 if let Some(type_ref) = ty {
87 format_to!(buf, ": {}", type_ref.syntax()); 87 format_to!(buf, ": {}", type_ref.syntax());
88 } 88 }
89 89
diff --git a/crates/ra_ide/src/display/structure.rs b/crates/ra_ide/src/display/structure.rs
deleted file mode 100644
index aad5a8e4d..000000000
--- a/crates/ra_ide/src/display/structure.rs
+++ /dev/null
@@ -1,438 +0,0 @@
1use ra_syntax::{
2 ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner},
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_ascription<N: NameOwner + AttrsOwner + TypeAscriptionOwner>(
59 node: N,
60 ) -> Option<StructureNode> {
61 let ty = node.ascribed_type();
62 decl_with_type_ref(node, ty)
63 }
64
65 fn decl_with_type_ref<N: NameOwner + AttrsOwner>(
66 node: N,
67 type_ref: Option<ast::TypeRef>,
68 ) -> Option<StructureNode> {
69 let detail = type_ref.map(|type_ref| {
70 let mut detail = String::new();
71 collapse_ws(type_ref.syntax(), &mut detail);
72 detail
73 });
74 decl_with_detail(node, detail)
75 }
76
77 fn decl_with_detail<N: NameOwner + AttrsOwner>(
78 node: N,
79 detail: Option<String>,
80 ) -> Option<StructureNode> {
81 let name = node.name()?;
82
83 Some(StructureNode {
84 parent: None,
85 label: name.text().to_string(),
86 navigation_range: name.syntax().text_range(),
87 node_range: node.syntax().text_range(),
88 kind: node.syntax().kind(),
89 detail,
90 deprecated: node.attrs().filter_map(|x| x.simple_name()).any(|x| x == "deprecated"),
91 })
92 }
93
94 fn collapse_ws(node: &SyntaxNode, output: &mut String) {
95 let mut can_insert_ws = false;
96 node.text().for_each_chunk(|chunk| {
97 for line in chunk.lines() {
98 let line = line.trim();
99 if line.is_empty() {
100 if can_insert_ws {
101 output.push(' ');
102 can_insert_ws = false;
103 }
104 } else {
105 output.push_str(line);
106 can_insert_ws = true;
107 }
108 }
109 })
110 }
111
112 match_ast! {
113 match node {
114 ast::FnDef(it) => {
115 let mut detail = String::from("fn");
116 if let Some(type_param_list) = it.type_param_list() {
117 collapse_ws(type_param_list.syntax(), &mut detail);
118 }
119 if let Some(param_list) = it.param_list() {
120 collapse_ws(param_list.syntax(), &mut detail);
121 }
122 if let Some(ret_type) = it.ret_type() {
123 detail.push_str(" ");
124 collapse_ws(ret_type.syntax(), &mut detail);
125 }
126
127 decl_with_detail(it, Some(detail))
128 },
129 ast::StructDef(it) => decl(it),
130 ast::EnumDef(it) => decl(it),
131 ast::EnumVariant(it) => decl(it),
132 ast::TraitDef(it) => decl(it),
133 ast::Module(it) => decl(it),
134 ast::TypeAliasDef(it) => {
135 let ty = it.type_ref();
136 decl_with_type_ref(it, ty)
137 },
138 ast::RecordFieldDef(it) => decl_with_ascription(it),
139 ast::ConstDef(it) => decl_with_ascription(it),
140 ast::StaticDef(it) => decl_with_ascription(it),
141 ast::ImplDef(it) => {
142 let target_type = it.target_type()?;
143 let target_trait = it.target_trait();
144 let label = match target_trait {
145 None => format!("impl {}", target_type.syntax().text()),
146 Some(t) => {
147 format!("impl {} for {}", t.syntax().text(), target_type.syntax().text(),)
148 }
149 };
150
151 let node = StructureNode {
152 parent: None,
153 label,
154 navigation_range: target_type.syntax().text_range(),
155 node_range: it.syntax().text_range(),
156 kind: it.syntax().kind(),
157 detail: None,
158 deprecated: false,
159 };
160 Some(node)
161 },
162 ast::MacroCall(it) => {
163 match it.path().and_then(|it| it.segment()).and_then(|it| it.name_ref()) {
164 Some(path_segment) if path_segment.text() == "macro_rules"
165 => decl(it),
166 _ => None,
167 }
168 },
169 _ => None,
170 }
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use insta::assert_debug_snapshot;
178
179 #[test]
180 fn test_file_structure() {
181 let file = SourceFile::parse(
182 r#"
183struct Foo {
184 x: i32
185}
186
187mod m {
188 fn bar1() {}
189 fn bar2<T>(t: T) -> T {}
190 fn bar3<A,
191 B>(a: A,
192 b: B) -> Vec<
193 u32
194 > {}
195}
196
197enum E { X, Y(i32) }
198type T = ();
199static S: i32 = 92;
200const C: i32 = 92;
201
202impl E {}
203
204impl fmt::Debug for E {}
205
206macro_rules! mc {
207 () => {}
208}
209
210#[macro_export]
211macro_rules! mcexp {
212 () => {}
213}
214
215/// Doc comment
216macro_rules! mcexp {
217 () => {}
218}
219
220#[deprecated]
221fn obsolete() {}
222
223#[deprecated(note = "for awhile")]
224fn very_obsolete() {}
225"#,
226 )
227 .ok()
228 .unwrap();
229 let structure = file_structure(&file);
230 assert_debug_snapshot!(structure,
231 @r###"
232 [
233 StructureNode {
234 parent: None,
235 label: "Foo",
236 navigation_range: 8..11,
237 node_range: 1..26,
238 kind: STRUCT_DEF,
239 detail: None,
240 deprecated: false,
241 },
242 StructureNode {
243 parent: Some(
244 0,
245 ),
246 label: "x",
247 navigation_range: 18..19,
248 node_range: 18..24,
249 kind: RECORD_FIELD_DEF,
250 detail: Some(
251 "i32",
252 ),
253 deprecated: false,
254 },
255 StructureNode {
256 parent: None,
257 label: "m",
258 navigation_range: 32..33,
259 node_range: 28..158,
260 kind: MODULE,
261 detail: None,
262 deprecated: false,
263 },
264 StructureNode {
265 parent: Some(
266 2,
267 ),
268 label: "bar1",
269 navigation_range: 43..47,
270 node_range: 40..52,
271 kind: FN_DEF,
272 detail: Some(
273 "fn()",
274 ),
275 deprecated: false,
276 },
277 StructureNode {
278 parent: Some(
279 2,
280 ),
281 label: "bar2",
282 navigation_range: 60..64,
283 node_range: 57..81,
284 kind: FN_DEF,
285 detail: Some(
286 "fn<T>(t: T) -> T",
287 ),
288 deprecated: false,
289 },
290 StructureNode {
291 parent: Some(
292 2,
293 ),
294 label: "bar3",
295 navigation_range: 89..93,
296 node_range: 86..156,
297 kind: FN_DEF,
298 detail: Some(
299 "fn<A, B>(a: A, b: B) -> Vec< u32 >",
300 ),
301 deprecated: false,
302 },
303 StructureNode {
304 parent: None,
305 label: "E",
306 navigation_range: 165..166,
307 node_range: 160..180,
308 kind: ENUM_DEF,
309 detail: None,
310 deprecated: false,
311 },
312 StructureNode {
313 parent: Some(
314 6,
315 ),
316 label: "X",
317 navigation_range: 169..170,
318 node_range: 169..170,
319 kind: ENUM_VARIANT,
320 detail: None,
321 deprecated: false,
322 },
323 StructureNode {
324 parent: Some(
325 6,
326 ),
327 label: "Y",
328 navigation_range: 172..173,
329 node_range: 172..178,
330 kind: ENUM_VARIANT,
331 detail: None,
332 deprecated: false,
333 },
334 StructureNode {
335 parent: None,
336 label: "T",
337 navigation_range: 186..187,
338 node_range: 181..193,
339 kind: TYPE_ALIAS_DEF,
340 detail: Some(
341 "()",
342 ),
343 deprecated: false,
344 },
345 StructureNode {
346 parent: None,
347 label: "S",
348 navigation_range: 201..202,
349 node_range: 194..213,
350 kind: STATIC_DEF,
351 detail: Some(
352 "i32",
353 ),
354 deprecated: false,
355 },
356 StructureNode {
357 parent: None,
358 label: "C",
359 navigation_range: 220..221,
360 node_range: 214..232,
361 kind: CONST_DEF,
362 detail: Some(
363 "i32",
364 ),
365 deprecated: false,
366 },
367 StructureNode {
368 parent: None,
369 label: "impl E",
370 navigation_range: 239..240,
371 node_range: 234..243,
372 kind: IMPL_DEF,
373 detail: None,
374 deprecated: false,
375 },
376 StructureNode {
377 parent: None,
378 label: "impl fmt::Debug for E",
379 navigation_range: 265..266,
380 node_range: 245..269,
381 kind: IMPL_DEF,
382 detail: None,
383 deprecated: false,
384 },
385 StructureNode {
386 parent: None,
387 label: "mc",
388 navigation_range: 284..286,
389 node_range: 271..303,
390 kind: MACRO_CALL,
391 detail: None,
392 deprecated: false,
393 },
394 StructureNode {
395 parent: None,
396 label: "mcexp",
397 navigation_range: 334..339,
398 node_range: 305..356,
399 kind: MACRO_CALL,
400 detail: None,
401 deprecated: false,
402 },
403 StructureNode {
404 parent: None,
405 label: "mcexp",
406 navigation_range: 387..392,
407 node_range: 358..409,
408 kind: MACRO_CALL,
409 detail: None,
410 deprecated: false,
411 },
412 StructureNode {
413 parent: None,
414 label: "obsolete",
415 navigation_range: 428..436,
416 node_range: 411..441,
417 kind: FN_DEF,
418 detail: Some(
419 "fn()",
420 ),
421 deprecated: true,
422 },
423 StructureNode {
424 parent: None,
425 label: "very_obsolete",
426 navigation_range: 481..494,
427 node_range: 443..499,
428 kind: FN_DEF,
429 detail: Some(
430 "fn()",
431 ),
432 deprecated: true,
433 },
434 ]
435 "###
436 );
437 }
438}