Pass Documentation up to LSP and add "rust" to our codeblocks there

This commit is contained in:
Jeremy Kolb 2019-01-29 21:39:09 -05:00
parent 48d2acb297
commit b88ba007cc
8 changed files with 103 additions and 89 deletions

View file

@ -3,9 +3,10 @@ use ra_db::SourceDatabase;
use ra_syntax::{ use ra_syntax::{
AstNode, SyntaxNode, TextUnit, TextRange, AstNode, SyntaxNode, TextUnit, TextRange,
SyntaxKind::FN_DEF, SyntaxKind::FN_DEF,
ast::{self, ArgListOwner, DocCommentsOwner}, ast::{self, ArgListOwner},
algo::find_node_at_offset, algo::find_node_at_offset,
}; };
use hir::Docs;
use crate::{FilePosition, CallInfo, db::RootDatabase}; use crate::{FilePosition, CallInfo, db::RootDatabase};
@ -26,7 +27,9 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal
let fn_file = db.parse(symbol.file_id); let fn_file = db.parse(symbol.file_id);
let fn_def = symbol.ptr.to_node(&fn_file); let fn_def = symbol.ptr.to_node(&fn_file);
let fn_def = ast::FnDef::cast(fn_def).unwrap(); let fn_def = ast::FnDef::cast(fn_def).unwrap();
let mut call_info = CallInfo::new(fn_def)?; let function = hir::source_binder::function_from_source(db, symbol.file_id, fn_def)?;
let mut call_info = CallInfo::new(db, function, fn_def)?;
// If we have a calling expression let's find which argument we are on // If we have a calling expression let's find which argument we are on
let num_params = call_info.parameters.len(); let num_params = call_info.parameters.len();
let has_self = fn_def.param_list().and_then(|l| l.self_param()).is_some(); let has_self = fn_def.param_list().and_then(|l| l.self_param()).is_some();
@ -110,46 +113,13 @@ impl<'a> FnCallNode<'a> {
} }
impl CallInfo { impl CallInfo {
fn new(node: &ast::FnDef) -> Option<Self> { fn new(db: &RootDatabase, function: hir::Function, node: &ast::FnDef) -> Option<Self> {
let label: String = if let Some(body) = node.body() { let label = crate::completion::function_label(node)?;
let body_range = body.syntax().range(); let doc = function.docs(db);
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"));
}
Some(CallInfo { Some(CallInfo {
parameters: param_list(node), parameters: param_list(node),
label: label.trim().to_owned(), label,
doc, doc,
active_parameter: None, active_parameter: None,
}) })
@ -284,7 +254,7 @@ fn bar() {
assert_eq!(info.parameters, vec!["j".to_string()]); assert_eq!(info.parameters, vec!["j".to_string()]);
assert_eq!(info.active_parameter, Some(0)); assert_eq!(info.active_parameter, Some(0));
assert_eq!(info.label, "fn foo(j: u32) -> u32".to_string()); 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] #[test]
@ -313,18 +283,18 @@ pub fn do() {
assert_eq!(info.active_parameter, Some(0)); assert_eq!(info.active_parameter, Some(0));
assert_eq!(info.label, "pub fn add_one(x: i32) -> i32".to_string()); assert_eq!(info.label, "pub fn add_one(x: i32) -> i32".to_string());
assert_eq!( assert_eq!(
info.doc, info.doc.map(|it| it.into()),
Some( Some(
r#"Adds one to the number given. r#"Adds one to the number given.
# Examples # Examples
```rust ```
let five = 5; let five = 5;
assert_eq!(6, my_crate::add_one(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.active_parameter, Some(0));
assert_eq!(info.label, "pub fn add_one(x: i32) -> i32".to_string()); assert_eq!(info.label, "pub fn add_one(x: i32) -> i32".to_string());
assert_eq!( assert_eq!(
info.doc, info.doc.map(|it| it.into()),
Some( Some(
r#"Adds one to the number given. r#"Adds one to the number given.
# Examples # Examples
```rust ```
let five = 5; let five = 5;
assert_eq!(6, my_crate::add_one(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.active_parameter, Some(1));
assert_eq!( assert_eq!(
info.doc, info.doc.map(|it| it.into()),
Some( Some(
r#"Method is called when writer finishes. r#"Method is called when writer finishes.
By default this method stops actor's `Context`."# By default this method stops actor's `Context`."#
.into() .to_string()
) )
); );
} }

View file

@ -10,6 +10,7 @@ mod complete_scope;
mod complete_postfix; mod complete_postfix;
use ra_db::SourceDatabase; use ra_db::SourceDatabase;
use ra_syntax::ast::{self, AstNode};
use crate::{ use crate::{
db, db,
@ -61,3 +62,21 @@ pub(crate) fn completions(db: &db::RootDatabase, position: FilePosition) -> Opti
complete_postfix::complete_postfix(&mut acc, &ctx); complete_postfix::complete_postfix(&mut acc, &ctx);
Some(acc) Some(acc)
} }
pub fn function_label(node: &ast::FnDef) -> Option<String> {
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())
}

View file

@ -1,12 +1,12 @@
use hir::{Docs, Documentation}; use hir::{Docs, Documentation};
use ra_syntax::{ use ra_syntax::TextRange;
ast::{self, AstNode},
TextRange,
};
use ra_text_edit::TextEdit; use ra_text_edit::TextEdit;
use test_utils::tested_by; 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. /// `CompletionItem` describes a single completion variant in the editor pop-up.
/// It is basically a POD with various properties. To construct a /// 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()) self.detail.as_ref().map(|it| it.as_str())
} }
/// A doc-comment /// A doc-comment
pub fn documentation(&self) -> Option<&str> { pub fn documentation(&self) -> Option<Documentation> {
self.documentation.as_ref().map(|it| it.contents()) self.documentation.clone()
} }
/// What string is used for filtering. /// What string is used for filtering.
pub fn lookup(&self) -> &str { pub fn lookup(&self) -> &str {
@ -252,7 +252,7 @@ impl Builder {
self.documentation = Some(docs); 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); self.detail = Some(label);
} }
@ -292,24 +292,9 @@ impl Into<Vec<CompletionItem>> for Completions {
} }
} }
fn function_label(ctx: &CompletionContext, function: hir::Function) -> Option<String> { fn function_item_label(ctx: &CompletionContext, function: hir::Function) -> Option<String> {
let node = function.source(ctx.db).1; let node = function.source(ctx.db).1;
function_label(&node)
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())
} }
#[cfg(test)] #[cfg(test)]

View file

@ -58,6 +58,7 @@ pub use ra_ide_api_light::{
pub use ra_db::{ pub use ra_db::{
Canceled, CrateGraph, CrateId, FileId, FilePosition, FileRange, SourceRootId Canceled, CrateGraph, CrateId, FileId, FilePosition, FileRange, SourceRootId
}; };
pub use hir::Documentation;
// We use jemalloc mainly to get heap usage statistics, actual performance // We use jemalloc mainly to get heap usage statistics, actual performance
// differnece is not measures. // differnece is not measures.
@ -266,7 +267,7 @@ impl<T> RangeInfo<T> {
#[derive(Debug)] #[derive(Debug)]
pub struct CallInfo { pub struct CallInfo {
pub label: String, pub label: String,
pub doc: Option<String>, pub doc: Option<Documentation>,
pub parameters: Vec<String>, pub parameters: Vec<String>,
pub active_parameter: Option<usize>, pub active_parameter: Option<usize>,
} }

View file

@ -87,13 +87,6 @@ impl ConvWith for CompletionItem {
None None
}; };
let documentation = self.documentation().map(|value| {
Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
value: value.to_string(),
})
});
let mut res = lsp_types::CompletionItem { let mut res = lsp_types::CompletionItem {
label: self.label().to_string(), label: self.label().to_string(),
detail: self.detail().map(|it| it.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()), kind: self.kind().map(|it| it.conv()),
text_edit: Some(text_edit), text_edit: Some(text_edit),
additional_text_edits, additional_text_edits,
documentation: documentation, documentation: self.documentation().map(|it| it.conv()),
..Default::default() ..Default::default()
}; };
res.insert_text_format = Some(match self.insert_text_format() { 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 { impl ConvWith for TextEdit {
type Ctx = LineIndex; type Ctx = LineIndex;
type Output = Vec<lsp_types::TextEdit>; type Output = Vec<lsp_types::TextEdit>;

View file

@ -2,6 +2,7 @@ mod caps;
mod cargo_target_spec; mod cargo_target_spec;
mod conv; mod conv;
mod main_loop; mod main_loop;
mod markdown;
mod project_model; mod project_model;
pub mod req; pub mod req;
mod server_world; mod server_world;

View file

@ -1,7 +1,7 @@
use gen_lsp_server::ErrorCode; use gen_lsp_server::ErrorCode;
use lsp_types::{ use lsp_types::{
CodeActionResponse, CodeLens, Command, Diagnostic, DiagnosticSeverity, CodeActionResponse, CodeLens, Command, Diagnostic, DiagnosticSeverity,
DocumentFormattingParams, DocumentHighlight, DocumentSymbol, Documentation, FoldingRange, DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange,
FoldingRangeKind, FoldingRangeParams, Hover, HoverContents, Location, MarkupContent, FoldingRangeKind, FoldingRangeParams, Hover, HoverContents, Location, MarkupContent,
MarkupKind, ParameterInformation, ParameterLabel, Position, PrepareRenameResponse, Range, MarkupKind, ParameterInformation, ParameterLabel, Position, PrepareRenameResponse, Range,
RenameParams, SignatureInformation, SymbolInformation, TextDocumentIdentifier, TextEdit, RenameParams, SignatureInformation, SymbolInformation, TextDocumentIdentifier, TextEdit,
@ -401,12 +401,9 @@ pub fn handle_signature_help(
documentation: None, documentation: None,
}) })
.collect(); .collect();
let documentation = call_info.doc.map(|value| {
Documentation::MarkupContent(MarkupContent { let documentation = call_info.doc.map(|it| it.conv());
kind: MarkupKind::Markdown,
value,
})
});
let sig_info = SignatureInformation { let sig_info = SignatureInformation {
label: call_info.label, label: call_info.label,
documentation, documentation,

View file

@ -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```"
);
}
}