aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_analysis/src/descriptors/function/mod.rs60
-rw-r--r--crates/ra_analysis/tests/tests.rs147
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs13
-rw-r--r--crates/ra_syntax/src/ast/generated.rs1
-rw-r--r--crates/ra_syntax/src/ast/mod.rs18
-rw-r--r--crates/ra_syntax/src/grammar.ron1
6 files changed, 235 insertions, 5 deletions
diff --git a/crates/ra_analysis/src/descriptors/function/mod.rs b/crates/ra_analysis/src/descriptors/function/mod.rs
index bb68b0ce7..ae40f3e8f 100644
--- a/crates/ra_analysis/src/descriptors/function/mod.rs
+++ b/crates/ra_analysis/src/descriptors/function/mod.rs
@@ -1,8 +1,11 @@
1pub(super) mod imp; 1pub(super) mod imp;
2mod scope; 2mod scope;
3 3
4use std::cmp::{min, max};
5
4use ra_syntax::{ 6use ra_syntax::{
5 ast::{self, AstNode, NameOwner} 7 ast::{self, AstNode, DocCommentsOwner, NameOwner},
8 TextRange, TextUnit
6}; 9};
7 10
8use crate::{ 11use crate::{
@@ -30,14 +33,17 @@ pub struct FnDescriptor {
30 pub label: String, 33 pub label: String,
31 pub ret_type: Option<String>, 34 pub ret_type: Option<String>,
32 pub params: Vec<String>, 35 pub params: Vec<String>,
36 pub doc: Option<String>
33} 37}
34 38
35impl FnDescriptor { 39impl FnDescriptor {
36 pub fn new(node: ast::FnDef) -> Option<Self> { 40 pub fn new(node: ast::FnDef) -> Option<Self> {
37 let name = node.name()?.text().to_string(); 41 let name = node.name()?.text().to_string();
38 42
43 let mut doc = None;
44
39 // Strip the body out for the label. 45 // Strip the body out for the label.
40 let label: String = if let Some(body) = node.body() { 46 let mut label: String = if let Some(body) = node.body() {
41 let body_range = body.syntax().range(); 47 let body_range = body.syntax().range();
42 let label: String = node 48 let label: String = node
43 .syntax() 49 .syntax()
@@ -50,6 +56,36 @@ impl FnDescriptor {
50 node.syntax().text().to_string() 56 node.syntax().text().to_string()
51 }; 57 };
52 58
59 if let Some((comment_range, docs)) = FnDescriptor::extract_doc_comments(node) {
60 let comment_range = comment_range.checked_sub(node.syntax().range().start()).unwrap();
61 let start = comment_range.start().to_usize();
62 let end = comment_range.end().to_usize();
63
64 // Remove the comment from the label
65 label.replace_range(start..end, "");
66
67 // Massage markdown
68 let mut processed_lines = Vec::new();
69 let mut in_code_block = false;
70 for line in docs.lines() {
71 if line.starts_with("```") {
72 in_code_block = !in_code_block;
73 }
74
75 let line = if in_code_block && line.starts_with("```") && !line.contains("rust") {
76 "```rust".into()
77 } else {
78 line.to_string()
79 };
80
81 processed_lines.push(line);
82 }
83
84 if !processed_lines.is_empty() {
85 doc = Some(processed_lines.join("\n"));
86 }
87 }
88
53 let params = FnDescriptor::param_list(node); 89 let params = FnDescriptor::param_list(node);
54 let ret_type = node.ret_type().map(|r| r.syntax().text().to_string()); 90 let ret_type = node.ret_type().map(|r| r.syntax().text().to_string());
55 91
@@ -57,10 +93,28 @@ impl FnDescriptor {
57 name, 93 name,
58 ret_type, 94 ret_type,
59 params, 95 params,
60 label, 96 label: label.trim().to_owned(),
97 doc
61 }) 98 })
62 } 99 }
63 100
101 fn extract_doc_comments(node: ast::FnDef) -> Option<(TextRange, String)> {
102 if node.doc_comments().count() == 0 {
103 return None;
104 }
105
106 let comment_text = node.doc_comment_text();
107
108 let (begin, end) = node.doc_comments()
109 .map(|comment| comment.syntax().range())
110 .map(|range| (range.start().to_usize(), range.end().to_usize()))
111 .fold((std::usize::MAX, std::usize::MIN), |acc, range| (min(acc.0, range.0), max(acc.1, range.1)));
112
113 let range = TextRange::from_to(TextUnit::from_usize(begin), TextUnit::from_usize(end));
114
115 Some((range, comment_text))
116 }
117
64 fn param_list(node: ast::FnDef) -> Vec<String> { 118 fn param_list(node: ast::FnDef) -> Vec<String> {
65 let mut res = vec![]; 119 let mut res = vec![];
66 if let Some(param_list) = node.param_list() { 120 if let Some(param_list) = node.param_list() {
diff --git a/crates/ra_analysis/tests/tests.rs b/crates/ra_analysis/tests/tests.rs
index 94e025677..9e2478d9e 100644
--- a/crates/ra_analysis/tests/tests.rs
+++ b/crates/ra_analysis/tests/tests.rs
@@ -195,6 +195,153 @@ fn bar() {
195 assert_eq!(param, Some(1)); 195 assert_eq!(param, Some(1));
196} 196}
197 197
198#[test]
199fn test_fn_signature_with_docs_simple() {
200 let (desc, param) = get_signature(
201 r#"
202// test
203fn foo(j: u32) -> u32 {
204 j
205}
206
207fn bar() {
208 let _ = foo(<|>);
209}
210"#,
211 );
212
213 assert_eq!(desc.name, "foo".to_string());
214 assert_eq!(desc.params, vec!["j".to_string()]);
215 assert_eq!(desc.ret_type, Some("-> u32".to_string()));
216 assert_eq!(param, Some(0));
217 assert_eq!(desc.label, "fn foo(j: u32) -> u32".to_string());
218 assert_eq!(desc.doc, Some("test".into()));
219}
220
221#[test]
222fn test_fn_signature_with_docs() {
223 let (desc, param) = get_signature(
224 r#"
225/// Adds one to the number given.
226///
227/// # Examples
228///
229/// ```
230/// let five = 5;
231///
232/// assert_eq!(6, my_crate::add_one(5));
233/// ```
234pub fn add_one(x: i32) -> i32 {
235 x + 1
236}
237
238pub fn do() {
239 add_one(<|>
240}"#,
241 );
242
243 assert_eq!(desc.name, "add_one".to_string());
244 assert_eq!(desc.params, vec!["x".to_string()]);
245 assert_eq!(desc.ret_type, Some("-> i32".to_string()));
246 assert_eq!(param, Some(0));
247 assert_eq!(desc.label, "pub fn add_one(x: i32) -> i32".to_string());
248 assert_eq!(desc.doc, Some(
249r#"Adds one to the number given.
250
251# Examples
252
253```rust
254let five = 5;
255
256assert_eq!(6, my_crate::add_one(5));
257```"#.into()));
258}
259
260#[test]
261fn test_fn_signature_with_docs_impl() {
262 let (desc, param) = get_signature(
263 r#"
264struct addr;
265impl addr {
266 /// Adds one to the number given.
267 ///
268 /// # Examples
269 ///
270 /// ```
271 /// let five = 5;
272 ///
273 /// assert_eq!(6, my_crate::add_one(5));
274 /// ```
275 pub fn add_one(x: i32) -> i32 {
276 x + 1
277 }
278}
279
280pub fn do_it() {
281 addr {};
282 addr::add_one(<|>);
283}"#);
284
285 assert_eq!(desc.name, "add_one".to_string());
286 assert_eq!(desc.params, vec!["x".to_string()]);
287 assert_eq!(desc.ret_type, Some("-> i32".to_string()));
288 assert_eq!(param, Some(0));
289 assert_eq!(desc.label, "pub fn add_one(x: i32) -> i32".to_string());
290 assert_eq!(desc.doc, Some(
291r#"Adds one to the number given.
292
293# Examples
294
295```rust
296let five = 5;
297
298assert_eq!(6, my_crate::add_one(5));
299```"#.into()));
300}
301
302#[test]
303fn test_fn_signature_with_docs_from_actix() {
304 let (desc, param) = get_signature(
305 r#"
306pub trait WriteHandler<E>
307where
308 Self: Actor,
309 Self::Context: ActorContext,
310{
311 /// Method is called when writer emits error.
312 ///
313 /// If this method returns `ErrorAction::Continue` writer processing
314 /// continues otherwise stream processing stops.
315 fn error(&mut self, err: E, ctx: &mut Self::Context) -> Running {
316 Running::Stop
317 }
318
319 /// Method is called when writer finishes.
320 ///
321 /// By default this method stops actor's `Context`.
322 fn finished(&mut self, ctx: &mut Self::Context) {
323 ctx.stop()
324 }
325}
326
327pub fn foo() {
328 WriteHandler r;
329 r.finished(<|>);
330}
331
332"#);
333
334 assert_eq!(desc.name, "finished".to_string());
335 assert_eq!(desc.params, vec!["&mut self".to_string(), "ctx".to_string()]);
336 assert_eq!(desc.ret_type, None);
337 assert_eq!(param, Some(1));
338 assert_eq!(desc.doc, Some(
339r#"Method is called when writer finishes.
340
341By default this method stops actor's `Context`."#.into()));
342}
343
344
198fn get_all_refs(text: &str) -> Vec<(FileId, TextRange)> { 345fn get_all_refs(text: &str) -> Vec<(FileId, TextRange)> {
199 let (analysis, position) = single_file_with_position(text); 346 let (analysis, position) = single_file_with_position(text);
200 analysis.find_all_refs(position.file_id, position.offset).unwrap() 347 analysis.find_all_refs(position.file_id, position.offset).unwrap()
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 4ac08e527..20cb5f772 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -5,7 +5,7 @@ use languageserver_types::{
5 CodeActionResponse, Command, CompletionItem, CompletionItemKind, Diagnostic, 5 CodeActionResponse, Command, CompletionItem, CompletionItemKind, Diagnostic,
6 DiagnosticSeverity, DocumentSymbol, FoldingRange, FoldingRangeKind, FoldingRangeParams, 6 DiagnosticSeverity, DocumentSymbol, FoldingRange, FoldingRangeKind, FoldingRangeParams,
7 InsertTextFormat, Location, Position, SymbolInformation, TextDocumentIdentifier, TextEdit, 7 InsertTextFormat, Location, Position, SymbolInformation, TextDocumentIdentifier, TextEdit,
8 RenameParams, WorkspaceEdit, PrepareRenameResponse 8 RenameParams, WorkspaceEdit, PrepareRenameResponse, Documentation, MarkupContent, MarkupKind
9}; 9};
10use gen_lsp_server::ErrorCode; 10use gen_lsp_server::ErrorCode;
11use ra_analysis::{FileId, FoldKind, Query, RunnableKind}; 11use ra_analysis::{FileId, FoldKind, Query, RunnableKind};
@@ -465,9 +465,18 @@ pub fn handle_signature_help(
465 }) 465 })
466 .collect(); 466 .collect();
467 467
468 let documentation = if let Some(doc) = descriptor.doc {
469 Some(Documentation::MarkupContent(MarkupContent {
470 kind: MarkupKind::Markdown,
471 value: doc
472 }))
473 } else {
474 None
475 };
476
468 let sig_info = SignatureInformation { 477 let sig_info = SignatureInformation {
469 label: descriptor.label, 478 label: descriptor.label,
470 documentation: None, 479 documentation,
471 parameters: Some(parameters), 480 parameters: Some(parameters),
472 }; 481 };
473 482
diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs
index d0cd060d3..b6a15dbdc 100644
--- a/crates/ra_syntax/src/ast/generated.rs
+++ b/crates/ra_syntax/src/ast/generated.rs
@@ -864,6 +864,7 @@ impl<'a> AstNode<'a> for FnDef<'a> {
864impl<'a> ast::NameOwner<'a> for FnDef<'a> {} 864impl<'a> ast::NameOwner<'a> for FnDef<'a> {}
865impl<'a> ast::TypeParamsOwner<'a> for FnDef<'a> {} 865impl<'a> ast::TypeParamsOwner<'a> for FnDef<'a> {}
866impl<'a> ast::AttrsOwner<'a> for FnDef<'a> {} 866impl<'a> ast::AttrsOwner<'a> for FnDef<'a> {}
867impl<'a> ast::DocCommentsOwner<'a> for FnDef<'a> {}
867impl<'a> FnDef<'a> { 868impl<'a> FnDef<'a> {
868 pub fn param_list(self) -> Option<ParamList<'a>> { 869 pub fn param_list(self) -> Option<ParamList<'a>> {
869 super::child_opt(self) 870 super::child_opt(self)
diff --git a/crates/ra_syntax/src/ast/mod.rs b/crates/ra_syntax/src/ast/mod.rs
index c033263a1..3aa11b9dd 100644
--- a/crates/ra_syntax/src/ast/mod.rs
+++ b/crates/ra_syntax/src/ast/mod.rs
@@ -65,6 +65,24 @@ pub trait AttrsOwner<'a>: AstNode<'a> {
65 } 65 }
66} 66}
67 67
68pub trait DocCommentsOwner<'a>: AstNode<'a> {
69 fn doc_comments(self) -> AstChildren<'a, Comment<'a>> { children(self) }
70
71 /// Returns the textual content of a doc comment block as a single string.
72 /// That is, strips leading `///` and joins lines
73 fn doc_comment_text(self) -> String {
74 self.doc_comments()
75 .map(|comment| {
76 let prefix = comment.prefix();
77 let trimmed = comment.text().as_str()
78 .trim()
79 .trim_start_matches(prefix)
80 .trim_start();
81 trimmed.to_owned()
82 }).join("\n")
83 }
84}
85
68impl<'a> FnDef<'a> { 86impl<'a> FnDef<'a> {
69 pub fn has_atom_attr(&self, atom: &str) -> bool { 87 pub fn has_atom_attr(&self, atom: &str) -> bool {
70 self.attrs().filter_map(|x| x.as_atom()).any(|x| x == atom) 88 self.attrs().filter_map(|x| x.as_atom()).any(|x| x == atom)
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron
index c1c215e0d..6951db010 100644
--- a/crates/ra_syntax/src/grammar.ron
+++ b/crates/ra_syntax/src/grammar.ron
@@ -251,6 +251,7 @@ Grammar(
251 "NameOwner", 251 "NameOwner",
252 "TypeParamsOwner", 252 "TypeParamsOwner",
253 "AttrsOwner", 253 "AttrsOwner",
254 "DocCommentsOwner"
254 ], 255 ],
255 options: [ "ParamList", ["body", "Block"], "RetType" ], 256 options: [ "ParamList", ["body", "Block"], "RetType" ],
256 ), 257 ),