From e8bb153b19ffa0ad815eb8934d40cd89ce550b99 Mon Sep 17 00:00:00 2001
From: Aleksey Kladov <aleksey.kladov@gmail.com>
Date: Wed, 8 Jul 2020 22:37:35 +0200
Subject: [PATCH] Add Markup type

---
 crates/ra_ide/src/hover.rs           | 75 ++++++++++------------------
 crates/ra_ide/src/lib.rs             |  2 +
 crates/ra_ide/src/markup.rs          | 38 ++++++++++++++
 crates/rust-analyzer/src/handlers.rs | 15 +++---
 crates/rust-analyzer/src/to_proto.rs |  8 ++-
 5 files changed, 79 insertions(+), 59 deletions(-)
 create mode 100644 crates/ra_ide/src/markup.rs

diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index 61359c77021..e469f41662c 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -16,6 +16,7 @@ use crate::{
     display::{
         macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav, TryToNav,
     },
+    markup::Markup,
     runnables::runnable,
     FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
 };
@@ -68,8 +69,8 @@ pub struct HoverGotoTypeData {
 /// Contains the results when hovering over an item
 #[derive(Debug, Default)]
 pub struct HoverResult {
-    results: Vec<String>,
-    actions: Vec<HoverAction>,
+    pub markup: Markup,
+    pub actions: Vec<HoverAction>,
 }
 
 impl HoverResult {
@@ -78,22 +79,7 @@ impl HoverResult {
     }
 
     pub fn is_empty(&self) -> bool {
-        self.results.is_empty()
-    }
-
-    pub fn len(&self) -> usize {
-        self.results.len()
-    }
-
-    pub fn actions(&self) -> &[HoverAction] {
-        &self.actions
-    }
-    /// Returns the results converted into markup
-    /// for displaying in a UI
-    ///
-    /// Does not process actions!
-    pub fn to_markup(&self) -> String {
-        self.results.join("\n\n___\n")
+        self.markup.is_empty()
     }
 
     fn push_action(&mut self, action: HoverAction) {
@@ -128,7 +114,7 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
     if let Some(definition) = definition {
         let range = sema.original_range(&node).range;
         if let Some(text) = hover_text_from_name_kind(db, definition) {
-            res.results.push(text);
+            res.markup.push_section(&text);
         }
         if !res.is_empty() {
             if let Some(action) = show_implementations_action(db, definition) {
@@ -168,7 +154,7 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
         }
     }?;
 
-    res.results.push(rust_code_markup(&ty.display(db)));
+    res.markup.push_section(&rust_code_markup(&ty.display(db)));
     let range = sema.original_range(&node).range;
     Some(RangeInfo::new(range, res))
 }
@@ -406,19 +392,12 @@ mod tests {
     fn check_hover_result(ra_fixture: &str, expected: &[&str]) -> (String, Vec<HoverAction>) {
         let (analysis, position) = analysis_and_position(ra_fixture);
         let hover = analysis.hover(position).unwrap().unwrap();
-        let mut results = hover.info.results.clone();
-        results.sort();
-
-        for (markup, expected) in
-            results.iter().zip(expected.iter().chain(std::iter::repeat(&"<missing>")))
-        {
-            assert_eq!(trim_markup(&markup), *expected);
-        }
-
-        assert_eq!(hover.info.len(), expected.len());
+        let expected = expected.join("\n\n___\n");
+        let actual = trim_markup(hover.info.markup.as_str());
+        assert_eq!(expected, actual);
 
         let content = analysis.db.file_text(position.file_id);
-        (content[hover.range].to_string(), hover.info.actions().to_vec())
+        (content[hover.range].to_string(), hover.info.actions.clone())
     }
 
     fn check_hover_no_result(ra_fixture: &str) {
@@ -439,7 +418,7 @@ fn main() {
         );
         let hover = analysis.hover(position).unwrap().unwrap();
         assert_eq!(hover.range, TextRange::new(58.into(), 63.into()));
-        assert_eq!(trim_markup(&hover.info.results[0]), ("u32"));
+        assert_eq!(trim_markup(&hover.info.markup.as_str()), ("u32"));
     }
 
     #[test]
@@ -638,7 +617,7 @@ fn main() {
             ",
         );
         let hover = analysis.hover(position).unwrap().unwrap();
-        assert_eq!(trim_markup(&hover.info.results[0]), ("Option\n```\n\n```rust\nSome"));
+        assert_eq!(trim_markup(&hover.info.markup.as_str()), ("Option\n```\n\n```rust\nSome"));
 
         let (analysis, position) = analysis_and_position(
             "
@@ -651,7 +630,7 @@ fn main() {
             ",
         );
         let hover = analysis.hover(position).unwrap().unwrap();
-        assert_eq!(trim_markup(&hover.info.results[0]), ("Option<i32>"));
+        assert_eq!(trim_markup(&hover.info.markup.as_str()), ("Option<i32>"));
     }
 
     #[test]
@@ -708,14 +687,14 @@ The Some variant
     fn hover_for_local_variable() {
         let (analysis, position) = analysis_and_position("fn func(foo: i32) { fo<|>o; }");
         let hover = analysis.hover(position).unwrap().unwrap();
-        assert_eq!(trim_markup(&hover.info.results[0]), "i32");
+        assert_eq!(trim_markup(&hover.info.markup.as_str()), "i32");
     }
 
     #[test]
     fn hover_for_local_variable_pat() {
         let (analysis, position) = analysis_and_position("fn func(fo<|>o: i32) {}");
         let hover = analysis.hover(position).unwrap().unwrap();
-        assert_eq!(trim_markup(&hover.info.results[0]), "i32");
+        assert_eq!(trim_markup(&hover.info.markup.as_str()), "i32");
     }
 
     #[test]
@@ -726,14 +705,14 @@ fn func(foo: i32) { if true { <|>foo; }; }
 ",
         );
         let hover = analysis.hover(position).unwrap().unwrap();
-        assert_eq!(trim_markup(&hover.info.results[0]), "i32");
+        assert_eq!(trim_markup(&hover.info.markup.as_str()), "i32");
     }
 
     #[test]
     fn hover_for_param_edge() {
         let (analysis, position) = analysis_and_position("fn func(<|>foo: i32) {}");
         let hover = analysis.hover(position).unwrap().unwrap();
-        assert_eq!(trim_markup(&hover.info.results[0]), "i32");
+        assert_eq!(trim_markup(&hover.info.markup.as_str()), "i32");
     }
 
     #[test]
@@ -754,7 +733,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
             ",
         );
         let hover = analysis.hover(position).unwrap().unwrap();
-        assert_eq!(trim_markup(&hover.info.results[0]), ("Thing"));
+        assert_eq!(trim_markup(&hover.info.markup.as_str()), ("Thing"));
     }
 
     #[test]
@@ -778,7 +757,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
         );
         let hover = analysis.hover(position).unwrap().unwrap();
         assert_eq!(
-            trim_markup(&hover.info.results[0]),
+            trim_markup(&hover.info.markup.as_str()),
             ("wrapper::Thing\n```\n\n```rust\nfn new() -> Thing")
         );
     }
@@ -802,7 +781,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
             ",
         );
         let hover = analysis.hover(position).unwrap().unwrap();
-        assert_eq!(trim_markup(&hover.info.results[0]), ("const C: u32"));
+        assert_eq!(trim_markup(&hover.info.markup.as_str()), ("const C: u32"));
     }
 
     #[test]
@@ -818,7 +797,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
         ",
         );
         let hover = analysis.hover(position).unwrap().unwrap();
-        assert_eq!(trim_markup(&hover.info.results[0]), ("Thing"));
+        assert_eq!(trim_markup(&hover.info.markup.as_str()), ("Thing"));
 
         /* FIXME: revive these tests
                 let (analysis, position) = analysis_and_position(
@@ -833,7 +812,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
                 );
 
                 let hover = analysis.hover(position).unwrap().unwrap();
-                assert_eq!(trim_markup(&hover.info.results[0]), ("Thing"));
+                assert_eq!(trim_markup(&hover.info.markup.as_str()), ("Thing"));
 
                 let (analysis, position) = analysis_and_position(
                     "
@@ -846,7 +825,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
                     ",
                 );
                 let hover = analysis.hover(position).unwrap().unwrap();
-                assert_eq!(trim_markup(&hover.info.results[0]), ("enum Thing"));
+                assert_eq!(trim_markup(&hover.info.markup.as_str()), ("enum Thing"));
 
                 let (analysis, position) = analysis_and_position(
                     "
@@ -858,7 +837,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
                     ",
                 );
                 let hover = analysis.hover(position).unwrap().unwrap();
-                assert_eq!(trim_markup(&hover.info.results[0]), ("enum Thing"));
+                assert_eq!(trim_markup(&hover.info.markup.as_str()), ("enum Thing"));
         */
     }
 
@@ -875,7 +854,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
             ",
         );
         let hover = analysis.hover(position).unwrap().unwrap();
-        assert_eq!(trim_markup(&hover.info.results[0]), "i32");
+        assert_eq!(trim_markup(&hover.info.markup.as_str()), "i32");
     }
 
     #[test]
@@ -892,7 +871,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
             ",
         );
         let hover = analysis.hover(position).unwrap().unwrap();
-        assert_eq!(trim_markup(&hover.info.results[0]), ("macro_rules! foo"));
+        assert_eq!(trim_markup(&hover.info.markup.as_str()), ("macro_rules! foo"));
     }
 
     #[test]
@@ -903,7 +882,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
             ",
         );
         let hover = analysis.hover(position).unwrap().unwrap();
-        assert_eq!(trim_markup(&hover.info.results[0]), "i32");
+        assert_eq!(trim_markup(&hover.info.markup.as_str()), "i32");
     }
 
     #[test]
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index dcfa186dc2d..6a4f5cb3db1 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -17,6 +17,7 @@ macro_rules! eprintln {
 
 pub mod mock_analysis;
 
+mod markup;
 mod prime_caches;
 mod status;
 mod completion;
@@ -68,6 +69,7 @@ pub use crate::{
     folding_ranges::{Fold, FoldKind},
     hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult},
     inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
+    markup::Markup,
     references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult},
     runnables::{Runnable, RunnableKind, TestId},
     syntax_highlighting::{
diff --git a/crates/ra_ide/src/markup.rs b/crates/ra_ide/src/markup.rs
new file mode 100644
index 00000000000..2f2b3cc25a3
--- /dev/null
+++ b/crates/ra_ide/src/markup.rs
@@ -0,0 +1,38 @@
+//! Markdown formatting.
+//!
+//! Sometimes, we want to display a "rich text" in the UI. At the moment, we use
+//! markdown for this purpose. It doesn't feel like a right option, but that's
+//! what is used by LSP, so let's keep it simple.
+use std::fmt;
+
+#[derive(Default, Debug)]
+pub struct Markup {
+    text: String,
+}
+
+impl From<Markup> for String {
+    fn from(markup: Markup) -> Self {
+        markup.text
+    }
+}
+
+impl fmt::Display for Markup {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::Display::fmt(&self.text, f)
+    }
+}
+
+impl Markup {
+    pub fn as_str(&self) -> &str {
+        self.text.as_str()
+    }
+    pub fn is_empty(&self) -> bool {
+        self.text.is_empty()
+    }
+    pub fn push_section(&mut self, section: &str) {
+        if !self.text.is_empty() {
+            self.text.push_str("\n\n___\n");
+        }
+        self.text.push_str(section);
+    }
+}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index f374334fe37..8ce6e1c711a 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -12,10 +12,10 @@ use lsp_types::{
     CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem,
     CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
     CodeLens, Command, CompletionItem, Diagnostic, DocumentFormattingParams, DocumentHighlight,
-    DocumentSymbol, FoldingRange, FoldingRangeParams, HoverContents, Location, MarkupContent,
-    MarkupKind, Position, PrepareRenameResponse, Range, RenameParams, SemanticTokensParams,
-    SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation,
-    TextDocumentIdentifier, Url, WorkspaceEdit,
+    DocumentSymbol, FoldingRange, FoldingRangeParams, HoverContents, Location, Position,
+    PrepareRenameResponse, Range, RenameParams, SemanticTokensParams, SemanticTokensRangeParams,
+    SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, TextDocumentIdentifier,
+    Url, WorkspaceEdit,
 };
 use ra_ide::{
     FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, NavigationTarget, Query,
@@ -584,13 +584,10 @@ pub(crate) fn handle_hover(
     let range = to_proto::range(&line_index, info.range);
     let hover = lsp_ext::Hover {
         hover: lsp_types::Hover {
-            contents: HoverContents::Markup(MarkupContent {
-                kind: MarkupKind::Markdown,
-                value: crate::markdown::format_docs(&info.info.to_markup()),
-            }),
+            contents: HoverContents::Markup(to_proto::markup_content(info.info.markup)),
             range: Some(range),
         },
-        actions: prepare_hover_actions(&snap, position.file_id, info.info.actions()),
+        actions: prepare_hover_actions(&snap, position.file_id, &info.info.actions),
     };
 
     Ok(Some(hover))
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 7fa4ffbc642..263f58a00e5 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -6,8 +6,8 @@ use ra_db::{FileId, FileRange};
 use ra_ide::{
     Assist, AssistKind, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold,
     FoldKind, FunctionSignature, Highlight, HighlightModifier, HighlightTag, HighlightedRange,
-    Indel, InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, ReferenceAccess,
-    ResolvedAssist, Runnable, Severity, SourceChange, SourceFileEdit, TextEdit,
+    Indel, InlayHint, InlayKind, InsertTextFormat, LineIndex, Markup, NavigationTarget,
+    ReferenceAccess, ResolvedAssist, Runnable, Severity, SourceChange, SourceFileEdit, TextEdit,
 };
 use ra_syntax::{SyntaxKind, TextRange, TextSize};
 
@@ -696,6 +696,10 @@ pub(crate) fn runnable(
     })
 }
 
+pub(crate) fn markup_content(markup: Markup) -> lsp_types::MarkupContent {
+    lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value: markup.into() }
+}
+
 #[cfg(test)]
 mod tests {
     use ra_ide::Analysis;