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};
let fn_file = db.parse(symbol.file_id);
let fn_def = symbol.ptr.to_node(&fn_file);
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
let num_params = call_info.parameters.len();
let has_self = fn_def.param_list().and_then(|l| l.self_param()).is_some();
}
impl CallInfo {
- fn new(node: &ast::FnDef) -> Option<Self> {
- 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<Self> {
+ 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,
})
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]
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()
)
);
}
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()
)
);
}
);
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()
)
);
}
mod complete_postfix;
use ra_db::SourceDatabase;
+use ra_syntax::ast::{self, AstNode};
use crate::{
db,
complete_postfix::complete_postfix(&mut acc, &ctx);
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())
+}
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
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<Documentation> {
+ self.documentation.clone()
}
/// What string is used for filtering.
pub fn lookup(&self) -> &str {
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);
}
}
}
-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 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)]
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.
#[derive(Debug)]
pub struct CallInfo {
pub label: String,
- pub doc: Option<String>,
+ pub doc: Option<Documentation>,
pub parameters: Vec<String>,
pub active_parameter: Option<usize>,
}
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()),
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() {
}
}
+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<lsp_types::TextEdit>;
mod cargo_target_spec;
mod conv;
mod main_loop;
+mod markdown;
mod project_model;
pub mod req;
mod server_world;
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,
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,
--- /dev/null
+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```"
+ );
+ }
+}