diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2018-10-31 19:41:24 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2018-10-31 19:41:24 +0000 |
commit | 55ebe6380aef233fff86b7e6cead361787bf1f65 (patch) | |
tree | 9ed060e4738a0504ddfd146649b9cf8a2f2fac40 /crates | |
parent | dfba29e4fb66457d101db295e3c356a932ac005e (diff) | |
parent | 74320945b664916005d263552194201cbe9a52bc (diff) |
Merge #167
167: Attempt to extract useful comments from function signatures r=matklad a=kjeremy
I'm trying to extract useful function comments for signature info. This will also be useful for hover. This is a WIP (and actually works pretty well!) but I don't think it's the right approach long term so some guidance would be appreciated so that we could also get comments for say types and variable instances etc.
Currently `test_fn_signature_with_simple_doc` fails due to a bug in `extend` but we probably shouldn't use this approach anyway. Maybe comments should be attached to nodes somehow? I'm also thinking that maybe the markdown bits should live in the language server.
Thoughts?
Co-authored-by: Jeremy A. Kolb <[email protected]>
Diffstat (limited to 'crates')
-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 | ), |