diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs index ee1e137993..2eb388e0e7 100644 --- a/crates/ra_ide_api/src/call_info.rs +++ b/crates/ra_ide_api/src/call_info.rs @@ -3,9 +3,10 @@ use ra_db::SourceDatabase; use ra_syntax::{ AstNode, SyntaxNode, TextUnit, TextRange, SyntaxKind::FN_DEF, - ast::{self, ArgListOwner, DocCommentsOwner}, + ast::{self, ArgListOwner}, algo::find_node_at_offset, }; +use hir::Docs; use crate::{FilePosition, CallInfo, db::RootDatabase}; @@ -26,7 +27,9 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option FnCallNode<'a> { } impl CallInfo { - fn new(node: &ast::FnDef) -> Option { - let label: String = if let Some(body) = node.body() { - let body_range = body.syntax().range(); - let label: String = node - .syntax() - .children() - .filter(|child| !child.range().is_subrange(&body_range)) // Filter out body - .filter(|child| ast::Comment::cast(child).is_none()) // Filter out doc comments - .map(|node| node.text().to_string()) - .collect(); - label - } else { - node.syntax().text().to_string() - }; - - let mut doc = None; - if let Some(docs) = node.doc_comment_text() { - // Massage markdown - let mut processed_lines = Vec::new(); - let mut in_code_block = false; - for line in docs.lines() { - if line.starts_with("```") { - in_code_block = !in_code_block; - } - - let line = if in_code_block && line.starts_with("```") && !line.contains("rust") { - "```rust".into() - } else { - line.to_string() - }; - - processed_lines.push(line); - } - - doc = Some(processed_lines.join("\n")); - } + fn new(db: &RootDatabase, function: hir::Function, node: &ast::FnDef) -> Option { + let label = crate::completion::function_label(node)?; + let doc = function.docs(db); Some(CallInfo { parameters: param_list(node), - label: label.trim().to_owned(), + label, doc, active_parameter: None, }) @@ -284,7 +254,7 @@ fn bar() { assert_eq!(info.parameters, vec!["j".to_string()]); assert_eq!(info.active_parameter, Some(0)); assert_eq!(info.label, "fn foo(j: u32) -> u32".to_string()); - assert_eq!(info.doc, Some("test".into())); + assert_eq!(info.doc.map(|it| it.into()), Some("test".to_string())); } #[test] @@ -313,18 +283,18 @@ pub fn do() { assert_eq!(info.active_parameter, Some(0)); assert_eq!(info.label, "pub fn add_one(x: i32) -> i32".to_string()); assert_eq!( - info.doc, + info.doc.map(|it| it.into()), Some( r#"Adds one to the number given. # Examples -```rust +``` let five = 5; assert_eq!(6, my_crate::add_one(5)); ```"# - .into() + .to_string() ) ); } @@ -359,18 +329,18 @@ pub fn do_it() { assert_eq!(info.active_parameter, Some(0)); assert_eq!(info.label, "pub fn add_one(x: i32) -> i32".to_string()); assert_eq!( - info.doc, + info.doc.map(|it| it.into()), Some( r#"Adds one to the number given. # Examples -```rust +``` let five = 5; assert_eq!(6, my_crate::add_one(5)); ```"# - .into() + .to_string() ) ); } @@ -414,12 +384,12 @@ pub fn foo() { ); assert_eq!(info.active_parameter, Some(1)); assert_eq!( - info.doc, + info.doc.map(|it| it.into()), Some( r#"Method is called when writer finishes. By default this method stops actor's `Context`."# - .into() + .to_string() ) ); } diff --git a/crates/ra_ide_api/src/completion.rs b/crates/ra_ide_api/src/completion.rs index b1867de427..722d94f3a2 100644 --- a/crates/ra_ide_api/src/completion.rs +++ b/crates/ra_ide_api/src/completion.rs @@ -10,6 +10,7 @@ mod complete_scope; mod complete_postfix; use ra_db::SourceDatabase; +use ra_syntax::ast::{self, AstNode}; use crate::{ db, @@ -61,3 +62,21 @@ pub(crate) fn completions(db: &db::RootDatabase, position: FilePosition) -> Opti complete_postfix::complete_postfix(&mut acc, &ctx); Some(acc) } + +pub fn function_label(node: &ast::FnDef) -> Option { + let label: String = if let Some(body) = node.body() { + let body_range = body.syntax().range(); + let label: String = node + .syntax() + .children() + .filter(|child| !child.range().is_subrange(&body_range)) // Filter out body + .filter(|child| ast::Comment::cast(child).is_none()) // Filter out comments + .map(|node| node.text().to_string()) + .collect(); + label + } else { + node.syntax().text().to_string() + }; + + Some(label.trim().to_owned()) +} diff --git a/crates/ra_ide_api/src/completion/completion_item.rs b/crates/ra_ide_api/src/completion/completion_item.rs index 49bd636a5d..d3bc148944 100644 --- a/crates/ra_ide_api/src/completion/completion_item.rs +++ b/crates/ra_ide_api/src/completion/completion_item.rs @@ -1,12 +1,12 @@ use hir::{Docs, Documentation}; -use ra_syntax::{ - ast::{self, AstNode}, - TextRange, -}; +use ra_syntax::TextRange; use ra_text_edit::TextEdit; use test_utils::tested_by; -use crate::completion::completion_context::CompletionContext; +use crate::completion::{ + completion_context::CompletionContext, + function_label, +}; /// `CompletionItem` describes a single completion variant in the editor pop-up. /// It is basically a POD with various properties. To construct a @@ -97,8 +97,8 @@ impl CompletionItem { self.detail.as_ref().map(|it| it.as_str()) } /// A doc-comment - pub fn documentation(&self) -> Option<&str> { - self.documentation.as_ref().map(|it| it.contents()) + pub fn documentation(&self) -> Option { + self.documentation.clone() } /// What string is used for filtering. pub fn lookup(&self) -> &str { @@ -252,7 +252,7 @@ impl Builder { self.documentation = Some(docs); } - if let Some(label) = function_label(ctx, function) { + if let Some(label) = function_item_label(ctx, function) { self.detail = Some(label); } @@ -292,24 +292,9 @@ impl Into> for Completions { } } -fn function_label(ctx: &CompletionContext, function: hir::Function) -> Option { +fn function_item_label(ctx: &CompletionContext, function: hir::Function) -> Option { let node = function.source(ctx.db).1; - - let label: String = if let Some(body) = node.body() { - let body_range = body.syntax().range(); - let label: String = node - .syntax() - .children() - .filter(|child| !child.range().is_subrange(&body_range)) // Filter out body - .filter(|child| ast::Comment::cast(child).is_none()) // Filter out comments - .map(|node| node.text().to_string()) - .collect(); - label - } else { - node.syntax().text().to_string() - }; - - Some(label.trim().to_owned()) + function_label(&node) } #[cfg(test)] diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index 51947e4ccd..09cf0216d0 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs @@ -58,6 +58,7 @@ pub use ra_ide_api_light::{ pub use ra_db::{ Canceled, CrateGraph, CrateId, FileId, FilePosition, FileRange, SourceRootId }; +pub use hir::Documentation; // We use jemalloc mainly to get heap usage statistics, actual performance // differnece is not measures. @@ -266,7 +267,7 @@ impl RangeInfo { #[derive(Debug)] pub struct CallInfo { pub label: String, - pub doc: Option, + pub doc: Option, pub parameters: Vec, pub active_parameter: Option, } diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs index 8c87f51951..c033ecdeaa 100644 --- a/crates/ra_lsp_server/src/conv.rs +++ b/crates/ra_lsp_server/src/conv.rs @@ -87,13 +87,6 @@ impl ConvWith for CompletionItem { None }; - let documentation = self.documentation().map(|value| { - Documentation::MarkupContent(MarkupContent { - kind: MarkupKind::Markdown, - value: value.to_string(), - }) - }); - let mut res = lsp_types::CompletionItem { label: self.label().to_string(), detail: self.detail().map(|it| it.to_string()), @@ -101,7 +94,7 @@ impl ConvWith for CompletionItem { kind: self.kind().map(|it| it.conv()), text_edit: Some(text_edit), additional_text_edits, - documentation: documentation, + documentation: self.documentation().map(|it| it.conv()), ..Default::default() }; res.insert_text_format = Some(match self.insert_text_format() { @@ -160,6 +153,16 @@ impl ConvWith for Range { } } +impl Conv for ra_ide_api::Documentation { + type Output = lsp_types::Documentation; + fn conv(self) -> Documentation { + Documentation::MarkupContent(MarkupContent { + kind: MarkupKind::Markdown, + value: crate::markdown::sanitize_markdown(self).into(), + }) + } +} + impl ConvWith for TextEdit { type Ctx = LineIndex; type Output = Vec; diff --git a/crates/ra_lsp_server/src/lib.rs b/crates/ra_lsp_server/src/lib.rs index f93d4b37d9..5b5f3b948f 100644 --- a/crates/ra_lsp_server/src/lib.rs +++ b/crates/ra_lsp_server/src/lib.rs @@ -2,6 +2,7 @@ mod caps; mod cargo_target_spec; mod conv; mod main_loop; +mod markdown; mod project_model; pub mod req; mod server_world; diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index 9478ebfb89..4f75f9a229 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs @@ -1,7 +1,7 @@ use gen_lsp_server::ErrorCode; use lsp_types::{ CodeActionResponse, CodeLens, Command, Diagnostic, DiagnosticSeverity, - DocumentFormattingParams, DocumentHighlight, DocumentSymbol, Documentation, FoldingRange, + DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeKind, FoldingRangeParams, Hover, HoverContents, Location, MarkupContent, MarkupKind, ParameterInformation, ParameterLabel, Position, PrepareRenameResponse, Range, RenameParams, SignatureInformation, SymbolInformation, TextDocumentIdentifier, TextEdit, @@ -401,12 +401,9 @@ pub fn handle_signature_help( documentation: None, }) .collect(); - let documentation = call_info.doc.map(|value| { - Documentation::MarkupContent(MarkupContent { - kind: MarkupKind::Markdown, - value, - }) - }); + + let documentation = call_info.doc.map(|it| it.conv()); + let sig_info = SignatureInformation { label: call_info.label, documentation, diff --git a/crates/ra_lsp_server/src/markdown.rs b/crates/ra_lsp_server/src/markdown.rs new file mode 100644 index 0000000000..f505755e86 --- /dev/null +++ b/crates/ra_lsp_server/src/markdown.rs @@ -0,0 +1,38 @@ +use ra_ide_api::Documentation; + +pub(crate) fn sanitize_markdown(docs: Documentation) -> Documentation { + let docs: String = docs.into(); + + // Massage markdown + let mut processed_lines = Vec::new(); + let mut in_code_block = false; + for line in docs.lines() { + if line.starts_with("```") { + in_code_block = !in_code_block; + } + + let line = if in_code_block && line.starts_with("```") && !line.contains("rust") { + "```rust".into() + } else { + line.to_string() + }; + + processed_lines.push(line); + } + + Documentation::new(&processed_lines.join("\n")) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_codeblock_adds_rust() { + let comment = "```\nfn some_rust() {}\n```"; + assert_eq!( + sanitize_markdown(Documentation::new(comment)).contents(), + "```rust\nfn some_rust() {}\n```" + ); + } +}