mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
Pass Documentation up to LSP and add "rust" to our codeblocks there
This commit is contained in:
parent
48d2acb297
commit
b88ba007cc
8 changed files with 103 additions and 89 deletions
|
@ -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()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
38
crates/ra_lsp_server/src/markdown.rs
Normal file
38
crates/ra_lsp_server/src/markdown.rs
Normal 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```"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue