Include highlighting runs in Outline

Max Brunsfeld created

Change summary

crates/editor/src/multi_buffer.rs |  5 +++--
crates/language/src/buffer.rs     | 23 +++++++++++++++++++++--
crates/language/src/outline.rs    |  3 ++-
crates/language/src/tests.rs      |  2 +-
crates/outline/src/outline.rs     | 13 +++++++++----
5 files changed, 36 insertions(+), 10 deletions(-)

Detailed changes

crates/editor/src/multi_buffer.rs 🔗

@@ -1698,9 +1698,9 @@ impl MultiBufferSnapshot {
             })
     }
 
-    pub fn outline(&self) -> Option<Outline<Anchor>> {
+    pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option<Outline<Anchor>> {
         let buffer = self.as_singleton()?;
-        let outline = buffer.outline()?;
+        let outline = buffer.outline(theme)?;
         let excerpt_id = &self.excerpts.iter().next().unwrap().id;
         Some(Outline::new(
             outline
@@ -1711,6 +1711,7 @@ impl MultiBufferSnapshot {
                     range: self.anchor_in_excerpt(excerpt_id.clone(), item.range.start)
                         ..self.anchor_in_excerpt(excerpt_id.clone(), item.range.end),
                     text: item.text,
+                    text_runs: item.text_runs,
                     name_ranges: item.name_ranges,
                 })
                 .collect(),

crates/language/src/buffer.rs 🔗

@@ -1835,7 +1835,7 @@ impl BufferSnapshot {
         }
     }
 
-    pub fn outline(&self) -> Option<Outline<Anchor>> {
+    pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option<Outline<Anchor>> {
         let tree = self.tree.as_ref()?;
         let grammar = self
             .language
@@ -1849,6 +1849,8 @@ impl BufferSnapshot {
             TextProvider(self.as_rope()),
         );
 
+        let mut chunks = self.chunks(0..self.len(), theme);
+
         let item_capture_ix = grammar.outline_query.capture_index_for_name("item")?;
         let name_capture_ix = grammar.outline_query.capture_index_for_name("name")?;
         let context_capture_ix = grammar
@@ -1863,6 +1865,7 @@ impl BufferSnapshot {
                 let range = item_node.start_byte()..item_node.end_byte();
                 let mut text = String::new();
                 let mut name_ranges = Vec::new();
+                let mut text_runs = Vec::new();
 
                 for capture in mat.captures {
                     let node_is_name;
@@ -1890,7 +1893,22 @@ impl BufferSnapshot {
 
                         name_ranges.push(start..end);
                     }
-                    text.extend(self.text_for_range(range));
+
+                    let mut offset = range.start;
+                    chunks.seek(offset);
+                    while let Some(mut chunk) = chunks.next() {
+                        if chunk.text.len() > range.end - offset {
+                            chunk.text = &chunk.text[0..(range.end - offset)];
+                            offset = range.end;
+                        } else {
+                            offset += chunk.text.len();
+                        }
+                        text_runs.push((chunk.text.len(), chunk.highlight_style));
+                        text.push_str(chunk.text);
+                        if offset >= range.end {
+                            break;
+                        }
+                    }
                 }
 
                 while stack.last().map_or(false, |prev_range| {
@@ -1905,6 +1923,7 @@ impl BufferSnapshot {
                     range: self.anchor_after(range.start)..self.anchor_before(range.end),
                     text,
                     name_ranges: name_ranges.into_boxed_slice(),
+                    text_runs,
                 })
             })
             .collect::<Vec<_>>();

crates/language/src/outline.rs 🔗

@@ -1,5 +1,5 @@
 use fuzzy::{StringMatch, StringMatchCandidate};
-use gpui::executor::Background;
+use gpui::{executor::Background, fonts::HighlightStyle};
 use std::{ops::Range, sync::Arc};
 
 #[derive(Debug)]
@@ -14,6 +14,7 @@ pub struct OutlineItem<T> {
     pub range: Range<T>,
     pub text: String,
     pub name_ranges: Box<[Range<u32>]>,
+    pub text_runs: Vec<(usize, Option<HighlightStyle>)>,
 }
 
 impl<T> Outline<T> {

crates/language/src/tests.rs 🔗

@@ -332,7 +332,7 @@ async fn test_outline(mut cx: gpui::TestAppContext) {
 
     let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, None, cx));
     let outline = buffer
-        .read_with(&cx, |buffer, _| buffer.snapshot().outline())
+        .read_with(&cx, |buffer, _| buffer.snapshot().outline(None))
         .unwrap();
 
     assert_eq!(

crates/outline/src/outline.rs 🔗

@@ -167,11 +167,16 @@ impl OutlineView {
             .to_any()
             .downcast::<Editor>()
             .unwrap();
-        let buffer = editor.read(cx).buffer().read(cx).read(cx).outline();
+        let settings = workspace.settings();
+        let buffer = editor
+            .read(cx)
+            .buffer()
+            .read(cx)
+            .read(cx)
+            .outline(Some(settings.borrow().theme.editor.syntax.as_ref()));
         if let Some(outline) = buffer {
-            workspace.toggle_modal(cx, |cx, workspace| {
-                let view =
-                    cx.add_view(|cx| OutlineView::new(outline, editor, workspace.settings(), cx));
+            workspace.toggle_modal(cx, |cx, _| {
+                let view = cx.add_view(|cx| OutlineView::new(outline, editor, settings, cx));
                 cx.subscribe(&view, Self::on_event).detach();
                 view
             })