Syntax highlighting working. Getting started on markdown support

Keith Simmons created

Change summary

Cargo.lock                        | 12 +++++
crates/editor/src/editor.rs       | 67 +++++++++++++++-----------------
crates/language/src/language.rs   |  2 
crates/lsp/src/lsp.rs             |  4 +
crates/project/Cargo.toml         |  1 
crates/project/src/lsp_command.rs | 48 +++++++++++++++++++++--
crates/project/src/project.rs     | 14 ++++-
7 files changed, 103 insertions(+), 45 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3419,6 +3419,7 @@ dependencies = [
  "lsp",
  "parking_lot",
  "postage",
+ "pulldown-cmark",
  "rand 0.8.3",
  "regex",
  "rocksdb",
@@ -3555,6 +3556,17 @@ dependencies = [
  "prost 0.9.0",
 ]
 
+[[package]]
+name = "pulldown-cmark"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34f197a544b0c9ab3ae46c359a7ec9cbbb5c7bf97054266fecb7ead794a181d6"
+dependencies = [
+ "bitflags",
+ "memchr",
+ "unicase",
+]
+
 [[package]]
 name = "quote"
 version = "1.0.9"

crates/editor/src/editor.rs 🔗

@@ -39,7 +39,7 @@ pub use multi_buffer::{
     Anchor, AnchorRangeExt, ExcerptId, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,
 };
 use ordered_float::OrderedFloat;
-use project::{Project, ProjectTransaction};
+use project::{HoverContents, Project, ProjectTransaction};
 use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
 use serde::{Deserialize, Serialize};
 use settings::Settings;
@@ -891,20 +891,28 @@ impl CodeActionsMenu {
     }
 }
 
-#[derive(Clone)]
 struct HoverPopover {
     pub point: DisplayPoint,
-    pub text: String,
-    pub runs: Vec<(Range<usize>, HighlightStyle)>,
+    pub contents: Vec<HoverContents>,
 }
 
 impl HoverPopover {
     fn render(&self, style: EditorStyle) -> (DisplayPoint, ElementBox) {
+        let contents = self.contents.first().unwrap();
         (
             self.point,
-            Text::new(self.text.clone(), style.text.clone())
+            Text::new(contents.text.clone(), style.text.clone())
                 .with_soft_wrap(false)
-                .with_highlights(self.runs.clone())
+                .with_highlights(
+                    contents
+                        .runs
+                        .iter()
+                        .filter_map(|(range, id)| {
+                            id.style(style.theme.syntax.as_ref())
+                                .map(|style| (range.clone(), style))
+                        })
+                        .collect(),
+                )
                 .contained()
                 .with_style(style.hover_popover)
                 .boxed(),
@@ -2458,7 +2466,6 @@ impl Editor {
     }
 
     fn hover(&mut self, action: &Hover, cx: &mut ViewContext<Self>) {
-        // dbg!("hover");
         if let Some(point) = action.point {
             self.show_hover(&ShowHover(point), cx);
         } else {
@@ -2520,7 +2527,7 @@ impl Editor {
         let task = cx.spawn_weak(|this, mut cx| {
             async move {
                 // TODO: what to show while LSP is loading?
-                let mut text = None;
+                let mut contents = None;
 
                 let hover = match hover.await {
                     Ok(hover) => hover,
@@ -2528,37 +2535,27 @@ impl Editor {
                 };
 
                 if let Some(hover) = hover {
-                    text = Some(match hover.contents {
-                        lsp::HoverContents::Scalar(marked_string) => match marked_string {
-                            lsp::MarkedString::String(string) => string,
-                            lsp::MarkedString::LanguageString(string) => string.value,
-                        },
-                        lsp::HoverContents::Array(marked_strings) => {
-                            // TODO: what to do?
-                            todo!()
-                        }
-                        lsp::HoverContents::Markup(markup) => markup.value,
-                    });
-
-                    if let Some(range) = hover.range {
-                        let offset_range = range.to_offset(&buffer_snapshot);
-                        if offset_range
-                            .contains(&point.to_offset(&snapshot.display_snapshot, Bias::Left))
-                        {
-                            point = offset_range
-                                .start
-                                .to_display_point(&snapshot.display_snapshot);
-                        } else {
-                            text = None;
+                    if hover.contents.is_empty() {
+                        contents = None;
+                    } else {
+                        contents = Some(hover.contents);
+
+                        if let Some(range) = hover.range {
+                            let offset_range = range.to_offset(&buffer_snapshot);
+                            if offset_range
+                                .contains(&point.to_offset(&snapshot.display_snapshot, Bias::Left))
+                            {
+                                point = offset_range
+                                    .start
+                                    .to_display_point(&snapshot.display_snapshot);
+                            } else {
+                                contents = None;
+                            }
                         }
                     }
                 };
 
-                let hover_popover = text.map(|text| HoverPopover {
-                    point,
-                    text,
-                    runs: Vec::new(),
-                });
+                let hover_popover = contents.map(|contents| HoverPopover { point, contents });
 
                 if let Some(this) = this.upgrade(&cx) {
                     this.update(&mut cx, |this, cx| {

crates/language/src/language.rs 🔗

@@ -236,7 +236,7 @@ impl LanguageRegistry {
         self.languages
             .read()
             .iter()
-            .find(|language| language.name().as_ref() == name)
+            .find(|language| language.name().to_lowercase() == name.to_lowercase())
             .cloned()
     }
 

crates/lsp/src/lsp.rs 🔗

@@ -296,6 +296,10 @@ impl LanguageServer {
                         prepare_support: Some(true),
                         ..Default::default()
                     }),
+                    hover: Some(HoverClientCapabilities {
+                        content_format: Some(vec![MarkupKind::Markdown]),
+                        ..Default::default()
+                    }),
                     ..Default::default()
                 }),
                 experimental: Some(json!({

crates/project/Cargo.toml 🔗

@@ -38,6 +38,7 @@ libc = "0.2"
 log = { version = "0.4.16", features = ["kv_unstable_serde"] }
 parking_lot = "0.11.1"
 postage = { version = "0.4.1", features = ["futures-traits"] }
+pulldown-cmark = { version = "0.9.1", default-features = false }
 rand = "0.8.3"
 regex = "1.5"
 serde = { version = "1.0", features = ["derive", "rc"] }

crates/project/src/lsp_command.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{DocumentHighlight, Hover, Location, Project, ProjectTransaction};
+use crate::{DocumentHighlight, Hover, HoverContents, Location, Project, ProjectTransaction};
 use anyhow::{anyhow, Result};
 use async_trait::async_trait;
 use client::{proto, PeerId};
@@ -835,10 +835,48 @@ impl LspCommand for GetHover {
                 })
             });
 
-            Hover {
-                contents: hover.contents,
-                range,
+            fn highlight(lsp_marked_string: lsp::MarkedString, project: &Project) -> HoverContents {
+                match lsp_marked_string {
+                    lsp::MarkedString::LanguageString(lsp::LanguageString { language, value }) => {
+                        if let Some(language) = project.languages().get_language(&language) {
+                            let runs =
+                                language.highlight_text(&value.as_str().into(), 0..value.len());
+                            HoverContents { text: value, runs }
+                        } else {
+                            HoverContents {
+                                text: value,
+                                runs: Vec::new(),
+                            }
+                        }
+                    }
+                    lsp::MarkedString::String(text) => HoverContents {
+                        text,
+                        runs: Vec::new(),
+                    },
+                }
             }
+
+            let contents = cx.read(|cx| {
+                let project = project.read(cx);
+                match dbg!(hover.contents) {
+                    lsp::HoverContents::Scalar(marked_string) => {
+                        vec![highlight(marked_string, project)]
+                    }
+                    lsp::HoverContents::Array(marked_strings) => marked_strings
+                        .into_iter()
+                        .map(|marked_string| highlight(marked_string, project))
+                        .collect(),
+                    lsp::HoverContents::Markup(markup_content) => {
+                        // TODO: handle markdown
+                        vec![HoverContents {
+                            text: markup_content.value,
+                            runs: Vec::new(),
+                        }]
+                    }
+                }
+            });
+
+            Hover { contents, range }
         }))
     }
 
@@ -855,7 +893,7 @@ impl LspCommand for GetHover {
 
     async fn from_proto(
         message: Self::ProtoRequest,
-        project: ModelHandle<Project>,
+        _: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {

crates/project/src/project.rs 🔗

@@ -19,9 +19,9 @@ use language::{
     point_to_lsp,
     proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
     range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CodeAction, CodeLabel, Completion,
-    Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent, File as _, Language,
-    LanguageRegistry, LanguageServerName, LocalFile, LspAdapter, OffsetRangeExt, Operation, Patch,
-    PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
+    Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent, File as _, HighlightId,
+    Language, LanguageRegistry, LanguageServerName, LocalFile, LspAdapter, OffsetRangeExt,
+    Operation, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
 };
 use lsp::{DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer};
 use lsp_command::*;
@@ -216,9 +216,15 @@ pub struct Symbol {
     pub signature: [u8; 32],
 }
 
+#[derive(Debug)]
+pub struct HoverContents {
+    pub text: String,
+    pub runs: Vec<(Range<usize>, HighlightId)>,
+}
+
 #[derive(Debug)]
 pub struct Hover {
-    pub contents: lsp::HoverContents,
+    pub contents: Vec<HoverContents>,
     pub range: Option<Range<language::Anchor>>,
 }