Unnamed repository; edit this file 'description' to name the repository.
Fix markdown removal in hover handling whitespace weirdly
Lukas Wirth 2023-01-20
parent 7385467 · commit c5b1e3f
-rw-r--r--crates/ide/src/hover.rs25
-rw-r--r--crates/ide/src/hover/render.rs57
-rw-r--r--crates/ide/src/hover/tests.rs23
-rw-r--r--crates/ide/src/markdown_remove.rs147
-rw-r--r--crates/ide/src/static_index.rs6
-rw-r--r--crates/rust-analyzer/src/config.rs5
-rw-r--r--crates/rust-analyzer/src/handlers.rs3
7 files changed, 202 insertions, 64 deletions
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index f0c6505ee6..c46c1c1cd1 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -19,6 +19,7 @@ use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, T}
use crate::{
doc_links::token_as_doc_comment,
+ markdown_remove::remove_markdown,
markup::Markup,
runnables::{runnable_fn, runnable_mod},
FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, TryToNav,
@@ -26,14 +27,9 @@ use crate::{
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HoverConfig {
pub links_in_hover: bool,
- pub documentation: Option<HoverDocFormat>,
+ pub documentation: bool,
pub keywords: bool,
-}
-
-impl HoverConfig {
- fn markdown(&self) -> bool {
- matches!(self.documentation, Some(HoverDocFormat::Markdown))
- }
+ pub format: HoverDocFormat,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -90,12 +86,23 @@ pub struct HoverResult {
// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[]
pub(crate) fn hover(
db: &RootDatabase,
- FileRange { file_id, range }: FileRange,
+ file_range: FileRange,
config: &HoverConfig,
) -> Option<RangeInfo<HoverResult>> {
let sema = &hir::Semantics::new(db);
- let file = sema.parse(file_id).syntax().clone();
+ let mut res = hover_impl(sema, file_range, config)?;
+ if let HoverDocFormat::PlainText = config.format {
+ res.info.markup = remove_markdown(res.info.markup.as_str()).into();
+ }
+ Some(res)
+}
+fn hover_impl(
+ sema: &Semantics<'_, RootDatabase>,
+ FileRange { file_id, range }: FileRange,
+ config: &HoverConfig,
+) -> Option<RangeInfo<HoverResult>> {
+ let file = sema.parse(file_id).syntax().clone();
if !range.is_empty() {
return hover_ranged(&file, range, sema, config);
}
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index cb537d7ef7..d7b6264957 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -26,13 +26,12 @@ use syntax::{
use crate::{
doc_links::{remove_links, rewrite_links},
hover::walk_and_push_ty,
- markdown_remove::remove_markdown,
HoverAction, HoverConfig, HoverResult, Markup,
};
pub(super) fn type_info(
sema: &Semantics<'_, RootDatabase>,
- config: &HoverConfig,
+ _config: &HoverConfig,
expr_or_pat: &Either<ast::Expr, ast::Pat>,
) -> Option<HoverResult> {
let TypeInfo { original, adjusted } = match expr_or_pat {
@@ -55,19 +54,15 @@ pub(super) fn type_info(
let adjusted = adjusted_ty.display(sema.db).to_string();
let static_text_diff_len = "Coerced to: ".len() - "Type: ".len();
format!(
- "{bt_start}Type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}",
+ "```text\nType: {:>apad$}\nCoerced to: {:>opad$}\n```\n",
original,
adjusted,
apad = static_text_diff_len + adjusted.len().max(original.len()),
opad = original.len(),
- bt_start = if config.markdown() { "```text\n" } else { "" },
- bt_end = if config.markdown() { "```\n" } else { "" }
)
.into()
- } else if config.markdown() {
- Markup::fenced_block(&original.display(sema.db))
} else {
- original.display(sema.db).to_string().into()
+ Markup::fenced_block(&original.display(sema.db))
};
res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
Some(res)
@@ -75,7 +70,7 @@ pub(super) fn type_info(
pub(super) fn try_expr(
sema: &Semantics<'_, RootDatabase>,
- config: &HoverConfig,
+ _config: &HoverConfig,
try_expr: &ast::TryExpr,
) -> Option<HoverResult> {
let inner_ty = sema.type_of_expr(&try_expr.expr()?)?.original;
@@ -151,14 +146,12 @@ pub(super) fn try_expr(
let ppad = static_text_len_diff.min(0).abs() as usize;
res.markup = format!(
- "{bt_start}{} Type: {:>pad0$}\nPropagated as: {:>pad1$}\n{bt_end}",
+ "```text\n{} Type: {:>pad0$}\nPropagated as: {:>pad1$}\n```\n",
s,
inner_ty,
body_ty,
pad0 = ty_len_max + tpad,
pad1 = ty_len_max + ppad,
- bt_start = if config.markdown() { "```text\n" } else { "" },
- bt_end = if config.markdown() { "```\n" } else { "" }
)
.into();
Some(res)
@@ -166,7 +159,7 @@ pub(super) fn try_expr(
pub(super) fn deref_expr(
sema: &Semantics<'_, RootDatabase>,
- config: &HoverConfig,
+ _config: &HoverConfig,
deref_expr: &ast::PrefixExpr,
) -> Option<HoverResult> {
let inner_ty = sema.type_of_expr(&deref_expr.expr()?)?.original;
@@ -195,15 +188,13 @@ pub(super) fn deref_expr(
.max(adjusted.len() + coerced_len)
.max(inner.len() + deref_len);
format!(
- "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}",
+ "```text\nDereferenced from: {:>ipad$}\nTo type: {:>apad$}\nCoerced to: {:>opad$}\n```\n",
inner,
original,
adjusted,
ipad = max_len - deref_len,
apad = max_len - type_len,
opad = max_len - coerced_len,
- bt_start = if config.markdown() { "```text\n" } else { "" },
- bt_end = if config.markdown() { "```\n" } else { "" }
)
.into()
} else {
@@ -213,13 +204,11 @@ pub(super) fn deref_expr(
let deref_len = "Dereferenced from: ".len();
let max_len = (original.len() + type_len).max(inner.len() + deref_len);
format!(
- "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\n{bt_end}",
+ "```text\nDereferenced from: {:>ipad$}\nTo type: {:>apad$}\n```\n",
inner,
original,
ipad = max_len - deref_len,
apad = max_len - type_len,
- bt_start = if config.markdown() { "```text\n" } else { "" },
- bt_end = if config.markdown() { "```\n" } else { "" }
)
.into()
};
@@ -233,7 +222,7 @@ pub(super) fn keyword(
config: &HoverConfig,
token: &SyntaxToken,
) -> Option<HoverResult> {
- if !token.kind().is_keyword() || !config.documentation.is_some() || !config.keywords {
+ if !token.kind().is_keyword() || !config.documentation || !config.keywords {
return None;
}
let parent = token.parent()?;
@@ -257,7 +246,7 @@ pub(super) fn keyword(
/// i.e. `let S {a, ..} = S {a: 1, b: 2}`
pub(super) fn struct_rest_pat(
sema: &Semantics<'_, RootDatabase>,
- config: &HoverConfig,
+ _config: &HoverConfig,
pattern: &RecordPat,
) -> HoverResult {
let missing_fields = sema.record_pattern_missing_fields(pattern);
@@ -286,11 +275,7 @@ pub(super) fn struct_rest_pat(
// get rid of trailing comma
s.truncate(s.len() - 2);
- if config.markdown() {
- Markup::fenced_block(&s)
- } else {
- s.into()
- }
+ Markup::fenced_block(&s)
};
res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
res
@@ -344,13 +329,8 @@ pub(super) fn process_markup(
config: &HoverConfig,
) -> Markup {
let markup = markup.as_str();
- let markup = if !config.markdown() {
- remove_markdown(markup)
- } else if config.links_in_hover {
- rewrite_links(db, markup, def)
- } else {
- remove_links(markup)
- };
+ let markup =
+ if config.links_in_hover { rewrite_links(db, markup, def) } else { remove_links(markup) };
Markup::from(markup)
}
@@ -463,8 +443,9 @@ pub(super) fn definition(
Definition::DeriveHelper(it) => (format!("derive_helper {}", it.name(db)), None),
};
- let docs = match config.documentation {
- Some(_) => docs.or_else(|| {
+ let docs = docs
+ .filter(|_| config.documentation)
+ .or_else(|| {
// docs are missing, for assoc items of trait impls try to fall back to the docs of the
// original item of the trait
let assoc = def.as_assoc_item(db)?;
@@ -472,10 +453,8 @@ pub(super) fn definition(
let name = Some(assoc.name(db)?);
let item = trait_.items(db).into_iter().find(|it| it.name(db) == name)?;
item.docs(db)
- }),
- None => None,
- };
- let docs = docs.filter(|_| config.documentation.is_some()).map(Into::into);
+ })
+ .map(Into::into);
markup(docs, label, mod_path)
}
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index f90ca86f9b..db2aaddc0b 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -2,7 +2,7 @@ use expect_test::{expect, Expect};
use ide_db::base_db::{FileLoader, FileRange};
use syntax::TextRange;
-use crate::{fixture, hover::HoverDocFormat, HoverConfig};
+use crate::{fixture, HoverConfig, HoverDocFormat};
fn check_hover_no_result(ra_fixture: &str) {
let (analysis, position) = fixture::position(ra_fixture);
@@ -10,8 +10,9 @@ fn check_hover_no_result(ra_fixture: &str) {
.hover(
&HoverConfig {
links_in_hover: true,
- documentation: Some(HoverDocFormat::Markdown),
+ documentation: true,
keywords: true,
+ format: HoverDocFormat::Markdown,
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
@@ -26,8 +27,9 @@ fn check(ra_fixture: &str, expect: Expect) {
.hover(
&HoverConfig {
links_in_hover: true,
- documentation: Some(HoverDocFormat::Markdown),
+ documentation: true,
keywords: true,
+ format: HoverDocFormat::Markdown,
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
@@ -47,8 +49,9 @@ fn check_hover_no_links(ra_fixture: &str, expect: Expect) {
.hover(
&HoverConfig {
links_in_hover: false,
- documentation: Some(HoverDocFormat::Markdown),
+ documentation: true,
keywords: true,
+ format: HoverDocFormat::Markdown,
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
@@ -68,8 +71,9 @@ fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) {
.hover(
&HoverConfig {
links_in_hover: true,
- documentation: Some(HoverDocFormat::PlainText),
+ documentation: true,
keywords: true,
+ format: HoverDocFormat::PlainText,
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
@@ -89,8 +93,9 @@ fn check_actions(ra_fixture: &str, expect: Expect) {
.hover(
&HoverConfig {
links_in_hover: true,
- documentation: Some(HoverDocFormat::Markdown),
+ documentation: true,
keywords: true,
+ format: HoverDocFormat::Markdown,
},
FileRange { file_id, range: position.range_or_empty() },
)
@@ -105,8 +110,9 @@ fn check_hover_range(ra_fixture: &str, expect: Expect) {
.hover(
&HoverConfig {
links_in_hover: false,
- documentation: Some(HoverDocFormat::Markdown),
+ documentation: true,
keywords: true,
+ format: HoverDocFormat::Markdown,
},
range,
)
@@ -121,8 +127,9 @@ fn check_hover_range_no_results(ra_fixture: &str) {
.hover(
&HoverConfig {
links_in_hover: false,
- documentation: Some(HoverDocFormat::Markdown),
+ documentation: true,
keywords: true,
+ format: HoverDocFormat::Markdown,
},
range,
)
diff --git a/crates/ide/src/markdown_remove.rs b/crates/ide/src/markdown_remove.rs
index 3ec5c629e4..07a3fe3f02 100644
--- a/crates/ide/src/markdown_remove.rs
+++ b/crates/ide/src/markdown_remove.rs
@@ -14,9 +14,154 @@ pub(crate) fn remove_markdown(markdown: &str) -> String {
Event::SoftBreak | Event::HardBreak | Event::Rule | Event::End(Tag::CodeBlock(_)) => {
out.push('\n')
}
- _ => {}
+ Event::End(Tag::Paragraph) => {
+ out.push('\n');
+ out.push('\n');
+ }
+ Event::Start(_)
+ | Event::End(_)
+ | Event::Html(_)
+ | Event::FootnoteReference(_)
+ | Event::TaskListMarker(_) => (),
}
}
+ if let Some(p) = out.rfind(|c| c != '\n') {
+ out.drain(p + 1..);
+ }
+
out
}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::expect;
+
+ use super::*;
+
+ #[test]
+ fn smoke_test() {
+ let res = remove_markdown(
+ r##"
+A function or function pointer.
+
+Functions are the primary way code is executed within Rust. Function blocks, usually just
+called functions, can be defined in a variety of different places and be assigned many
+different attributes and modifiers.
+
+Standalone functions that just sit within a module not attached to anything else are common,
+but most functions will end up being inside [`impl`] blocks, either on another type itself, or
+as a trait impl for that type.
+
+```rust
+fn standalone_function() {
+ // code
+}
+
+pub fn public_thing(argument: bool) -> String {
+ // code
+ # "".to_string()
+}
+
+struct Thing {
+ foo: i32,
+}
+
+impl Thing {
+ pub fn new() -> Self {
+ Self {
+ foo: 42,
+ }
+ }
+}
+```
+
+In addition to presenting fixed types in the form of `fn name(arg: type, ..) -> return_type`,
+functions can also declare a list of type parameters along with trait bounds that they fall
+into.
+
+```rust
+fn generic_function<T: Clone>(x: T) -> (T, T, T) {
+ (x.clone(), x.clone(), x.clone())
+}
+
+fn generic_where<T>(x: T) -> T
+ where T: std::ops::Add<Output = T> + Copy
+{
+ x + x + x
+}
+```
+
+Declaring trait bounds in the angle brackets is functionally identical to using a `where`
+clause. It's up to the programmer to decide which works better in each situation, but `where`
+tends to be better when things get longer than one line.
+
+Along with being made public via `pub`, `fn` can also have an [`extern`] added for use in
+FFI.
+
+For more information on the various types of functions and how they're used, consult the [Rust
+book] or the [Reference].
+
+[`impl`]: keyword.impl.html
+[`extern`]: keyword.extern.html
+[Rust book]: ../book/ch03-03-how-functions-work.html
+[Reference]: ../reference/items/functions.html
+"##,
+ );
+ expect![[r#"
+ A function or function pointer.
+
+ Functions are the primary way code is executed within Rust. Function blocks, usually just
+ called functions, can be defined in a variety of different places and be assigned many
+ different attributes and modifiers.
+
+ Standalone functions that just sit within a module not attached to anything else are common,
+ but most functions will end up being inside impl blocks, either on another type itself, or
+ as a trait impl for that type.
+
+ fn standalone_function() {
+ // code
+ }
+
+ pub fn public_thing(argument: bool) -> String {
+ // code
+ # "".to_string()
+ }
+
+ struct Thing {
+ foo: i32,
+ }
+
+ impl Thing {
+ pub fn new() -> Self {
+ Self {
+ foo: 42,
+ }
+ }
+ }
+
+ In addition to presenting fixed types in the form of fn name(arg: type, ..) -> return_type,
+ functions can also declare a list of type parameters along with trait bounds that they fall
+ into.
+
+ fn generic_function<T: Clone>(x: T) -> (T, T, T) {
+ (x.clone(), x.clone(), x.clone())
+ }
+
+ fn generic_where<T>(x: T) -> T
+ where T: std::ops::Add<Output = T> + Copy
+ {
+ x + x + x
+ }
+
+ Declaring trait bounds in the angle brackets is functionally identical to using a where
+ clause. It's up to the programmer to decide which works better in each situation, but where
+ tends to be better when things get longer than one line.
+
+ Along with being made public via pub, fn can also have an extern added for use in
+ FFI.
+
+ For more information on the various types of functions and how they're used, consult the Rust
+ book or the Reference."#]].assert_eq(&res);
+ }
+}
diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs
index 7ada4f1be0..3f7f6885f6 100644
--- a/crates/ide/src/static_index.rs
+++ b/crates/ide/src/static_index.rs
@@ -16,8 +16,7 @@ use crate::{
inlay_hints::AdjustmentHintsMode,
moniker::{def_to_moniker, MonikerResult},
parent_module::crates_for,
- Analysis, Fold, HoverConfig, HoverDocFormat, HoverResult, InlayHint, InlayHintsConfig,
- TryToNav,
+ Analysis, Fold, HoverConfig, HoverResult, InlayHint, InlayHintsConfig, TryToNav,
};
/// A static representation of fully analyzed source code.
@@ -137,8 +136,9 @@ impl StaticIndex<'_> {
});
let hover_config = HoverConfig {
links_in_hover: true,
- documentation: Some(HoverDocFormat::Markdown),
+ documentation: true,
keywords: true,
+ format: crate::HoverDocFormat::Markdown,
};
let tokens = tokens.filter(|token| {
matches!(
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index fd2f934f9f..c4d9ad7dff 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -1393,7 +1393,8 @@ impl Config {
pub fn hover(&self) -> HoverConfig {
HoverConfig {
links_in_hover: self.data.hover_links_enable,
- documentation: self.data.hover_documentation_enable.then(|| {
+ documentation: self.data.hover_documentation_enable,
+ format: {
let is_markdown = try_or_def!(self
.caps
.text_document
@@ -1409,7 +1410,7 @@ impl Config {
} else {
HoverDocFormat::PlainText
}
- }),
+ },
keywords: self.data.hover_documentation_keywords_enable,
}
}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 33ca781066..4e08bd0a72 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -936,8 +936,7 @@ pub(crate) fn handle_hover(
let line_index = snap.file_line_index(file_range.file_id)?;
let range = to_proto::range(&line_index, info.range);
- let markup_kind =
- snap.config.hover().documentation.map_or(ide::HoverDocFormat::Markdown, |kind| kind);
+ let markup_kind = snap.config.hover().format;
let hover = lsp_ext::Hover {
hover: lsp_types::Hover {
contents: HoverContents::Markup(to_proto::markup_content(