diff options
-rw-r--r-- | crates/ra_analysis/src/descriptors/function/mod.rs | 60 | ||||
-rw-r--r-- | crates/ra_analysis/tests/tests.rs | 147 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 13 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/generated.rs | 1 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/mod.rs | 18 | ||||
-rw-r--r-- | crates/ra_syntax/src/grammar.ron | 1 |
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 @@ | |||
1 | pub(super) mod imp; | 1 | pub(super) mod imp; |
2 | mod scope; | 2 | mod scope; |
3 | 3 | ||
4 | use std::cmp::{min, max}; | ||
5 | |||
4 | use ra_syntax::{ | 6 | use ra_syntax::{ |
5 | ast::{self, AstNode, NameOwner} | 7 | ast::{self, AstNode, DocCommentsOwner, NameOwner}, |
8 | TextRange, TextUnit | ||
6 | }; | 9 | }; |
7 | 10 | ||
8 | use crate::{ | 11 | use 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 | ||
35 | impl FnDescriptor { | 39 | impl 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] | ||
199 | fn test_fn_signature_with_docs_simple() { | ||
200 | let (desc, param) = get_signature( | ||
201 | r#" | ||
202 | // test | ||
203 | fn foo(j: u32) -> u32 { | ||
204 | j | ||
205 | } | ||
206 | |||
207 | fn 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] | ||
222 | fn 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 | /// ``` | ||
234 | pub fn add_one(x: i32) -> i32 { | ||
235 | x + 1 | ||
236 | } | ||
237 | |||
238 | pub 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( | ||
249 | r#"Adds one to the number given. | ||
250 | |||
251 | # Examples | ||
252 | |||
253 | ```rust | ||
254 | let five = 5; | ||
255 | |||
256 | assert_eq!(6, my_crate::add_one(5)); | ||
257 | ```"#.into())); | ||
258 | } | ||
259 | |||
260 | #[test] | ||
261 | fn test_fn_signature_with_docs_impl() { | ||
262 | let (desc, param) = get_signature( | ||
263 | r#" | ||
264 | struct addr; | ||
265 | impl 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 | |||
280 | pub 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( | ||
291 | r#"Adds one to the number given. | ||
292 | |||
293 | # Examples | ||
294 | |||
295 | ```rust | ||
296 | let five = 5; | ||
297 | |||
298 | assert_eq!(6, my_crate::add_one(5)); | ||
299 | ```"#.into())); | ||
300 | } | ||
301 | |||
302 | #[test] | ||
303 | fn test_fn_signature_with_docs_from_actix() { | ||
304 | let (desc, param) = get_signature( | ||
305 | r#" | ||
306 | pub trait WriteHandler<E> | ||
307 | where | ||
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 | |||
327 | pub 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( | ||
339 | r#"Method is called when writer finishes. | ||
340 | |||
341 | By default this method stops actor's `Context`."#.into())); | ||
342 | } | ||
343 | |||
344 | |||
198 | fn get_all_refs(text: &str) -> Vec<(FileId, TextRange)> { | 345 | fn 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 | }; |
10 | use gen_lsp_server::ErrorCode; | 10 | use gen_lsp_server::ErrorCode; |
11 | use ra_analysis::{FileId, FoldKind, Query, RunnableKind}; | 11 | use 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> { | |||
864 | impl<'a> ast::NameOwner<'a> for FnDef<'a> {} | 864 | impl<'a> ast::NameOwner<'a> for FnDef<'a> {} |
865 | impl<'a> ast::TypeParamsOwner<'a> for FnDef<'a> {} | 865 | impl<'a> ast::TypeParamsOwner<'a> for FnDef<'a> {} |
866 | impl<'a> ast::AttrsOwner<'a> for FnDef<'a> {} | 866 | impl<'a> ast::AttrsOwner<'a> for FnDef<'a> {} |
867 | impl<'a> ast::DocCommentsOwner<'a> for FnDef<'a> {} | ||
867 | impl<'a> FnDef<'a> { | 868 | impl<'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 | ||
68 | pub 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 | |||
68 | impl<'a> FnDef<'a> { | 86 | impl<'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 | ), |