Start on text highlight support

Nathan Sobo created

Change summary

crates/editor/src/display_map.rs           | 31 +++++++++-
crates/editor/src/display_map/block_map.rs |  3 
crates/editor/src/display_map/fold_map.rs  |  3 
crates/editor/src/editor.rs                | 68 +++++++++++++++--------
crates/editor/src/element.rs               | 43 ++++++++-------
crates/go_to_line/src/go_to_line.rs        |  4 
crates/gpui/src/color.rs                   | 25 ++++++++
crates/gpui/src/elements/text.rs           | 29 +++++----
crates/gpui/src/fonts.rs                   | 33 ++++++++++
crates/language/src/buffer.rs              | 10 ++-
crates/language/src/highlight_map.rs       |  8 +-
crates/language/src/language.rs            |  2 
crates/outline/src/outline.rs              |  4 
crates/search/src/buffer_search.rs         | 22 ++++--
crates/search/src/project_search.rs        |  4 
15 files changed, 198 insertions(+), 91 deletions(-)

Detailed changes

crates/editor/src/display_map.rs 🔗

@@ -7,10 +7,13 @@ use crate::{Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
 use block_map::{BlockMap, BlockPoint};
 use collections::{HashMap, HashSet};
 use fold_map::{FoldMap, ToFoldPoint as _};
-use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle};
+use gpui::{
+    fonts::{FontId, HighlightStyle},
+    Entity, ModelContext, ModelHandle,
+};
 use language::{Point, Subscription as BufferSubscription};
-use std::ops::Range;
-use sum_tree::Bias;
+use std::{any::TypeId, ops::Range, sync::Arc};
+use sum_tree::{Bias, TreeMap};
 use tab_map::TabMap;
 use wrap_map::WrapMap;
 
@@ -23,6 +26,8 @@ pub trait ToDisplayPoint {
     fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
 }
 
+type TextHighlights = TreeMap<Option<TypeId>, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
+
 pub struct DisplayMap {
     buffer: ModelHandle<MultiBuffer>,
     buffer_subscription: BufferSubscription,
@@ -30,6 +35,7 @@ pub struct DisplayMap {
     tab_map: TabMap,
     wrap_map: ModelHandle<WrapMap>,
     block_map: BlockMap,
+    text_highlights: TextHighlights,
 }
 
 impl Entity for DisplayMap {
@@ -60,6 +66,7 @@ impl DisplayMap {
             tab_map,
             wrap_map,
             block_map,
+            text_highlights: Default::default(),
         }
     }
 
@@ -79,6 +86,7 @@ impl DisplayMap {
             tabs_snapshot,
             wraps_snapshot,
             blocks_snapshot,
+            text_highlights: self.text_highlights.clone(),
         }
     }
 
@@ -156,6 +164,20 @@ impl DisplayMap {
         block_map.remove(ids);
     }
 
+    pub fn highlight_text(
+        &mut self,
+        type_id: TypeId,
+        ranges: Vec<Range<Anchor>>,
+        style: HighlightStyle,
+    ) {
+        self.text_highlights
+            .insert(Some(type_id), Arc::new((style, ranges)));
+    }
+
+    pub fn clear_text_highlights(&mut self, type_id: TypeId) {
+        self.text_highlights.remove(&Some(type_id));
+    }
+
     pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext<Self>) {
         self.wrap_map
             .update(cx, |map, cx| map.set_font(font_id, font_size, cx));
@@ -178,6 +200,7 @@ pub struct DisplaySnapshot {
     tabs_snapshot: tab_map::TabSnapshot,
     wraps_snapshot: wrap_map::WrapSnapshot,
     blocks_snapshot: block_map::BlockSnapshot,
+    text_highlights: TextHighlights,
 }
 
 impl DisplaySnapshot {
@@ -1146,7 +1169,7 @@ mod tests {
         let mut chunks: Vec<(String, Option<Color>)> = Vec::new();
         for chunk in snapshot.chunks(rows, true) {
             let color = chunk
-                .highlight_id
+                .syntax_highlight_id
                 .and_then(|id| id.style(theme).map(|s| s.color));
             if let Some((last_chunk, last_color)) = chunks.last_mut() {
                 if color == *last_color {

crates/editor/src/display_map/block_map.rs 🔗

@@ -807,7 +807,8 @@ impl<'a> Iterator for BlockChunks<'a> {
 
             return Some(Chunk {
                 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) },
-                highlight_id: None,
+                syntax_highlight_id: None,
+                highlight_style: None,
                 diagnostic: None,
             });
         }

crates/editor/src/display_map/fold_map.rs 🔗

@@ -984,7 +984,8 @@ impl<'a> Iterator for FoldChunks<'a> {
             self.output_offset += output_text.len();
             return Some(Chunk {
                 text: output_text,
-                highlight_id: None,
+                syntax_highlight_id: None,
+                highlight_style: None,
                 diagnostic: None,
             });
         }

crates/editor/src/editor.rs 🔗

@@ -440,7 +440,7 @@ pub struct Editor {
     vertical_scroll_margin: f32,
     placeholder_text: Option<Arc<str>>,
     highlighted_rows: Option<Range<u32>>,
-    highlighted_ranges: BTreeMap<TypeId, (Color, Vec<Range<Anchor>>)>,
+    background_highlights: BTreeMap<TypeId, (Color, Vec<Range<Anchor>>)>,
     nav_history: Option<ItemNavHistory>,
     context_menu: Option<ContextMenu>,
     completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
@@ -920,7 +920,7 @@ impl Editor {
             vertical_scroll_margin: 3.0,
             placeholder_text: None,
             highlighted_rows: None,
-            highlighted_ranges: Default::default(),
+            background_highlights: Default::default(),
             nav_history: None,
             context_menu: None,
             completion_tasks: Default::default(),
@@ -2350,7 +2350,7 @@ impl Editor {
             if let Some(editor) = editor.act_as::<Self>(cx) {
                 editor.update(cx, |editor, cx| {
                     let color = editor.style(cx).highlighted_line_background;
-                    editor.highlight_ranges::<Self>(ranges_to_highlight, color, cx);
+                    editor.highlight_background::<Self>(ranges_to_highlight, color, cx);
                 });
             }
         });
@@ -2444,12 +2444,12 @@ impl Editor {
                         }
                     }
 
-                    this.highlight_ranges::<DocumentHighlightRead>(
+                    this.highlight_background::<DocumentHighlightRead>(
                         read_ranges,
                         read_background,
                         cx,
                     );
-                    this.highlight_ranges::<DocumentHighlightWrite>(
+                    this.highlight_background::<DocumentHighlightWrite>(
                         write_ranges,
                         write_background,
                         cx,
@@ -4333,7 +4333,7 @@ impl Editor {
                 if let Some(editor) = editor.act_as::<Self>(cx) {
                     editor.update(cx, |editor, cx| {
                         let color = editor.style(cx).highlighted_line_background;
-                        editor.highlight_ranges::<Self>(ranges_to_highlight, color, cx);
+                        editor.highlight_background::<Self>(ranges_to_highlight, color, cx);
                     });
                 }
             });
@@ -4398,14 +4398,14 @@ impl Editor {
                             None,
                             cx,
                         );
-                        editor.highlight_ranges::<Rename>(
+                        editor.highlight_background::<Rename>(
                             vec![Anchor::min()..Anchor::max()],
                             style.diff_background_inserted,
                             cx,
                         );
                         editor
                     });
-                    this.highlight_ranges::<Rename>(
+                    this.highlight_background::<Rename>(
                         vec![range.clone()],
                         style.diff_background_deleted,
                         cx,
@@ -4500,7 +4500,7 @@ impl Editor {
     fn take_rename(&mut self, cx: &mut ViewContext<Self>) -> Option<RenameState> {
         let rename = self.pending_rename.take()?;
         self.remove_blocks([rename.block_id].into_iter().collect(), cx);
-        self.clear_highlighted_ranges::<Rename>(cx);
+        self.clear_background_highlights::<Rename>(cx);
 
         let editor = rename.editor.read(cx);
         let snapshot = self.buffer.read(cx).snapshot(cx);
@@ -4545,7 +4545,7 @@ impl Editor {
             }
             let rename = self.pending_rename.take().unwrap();
             self.remove_blocks([rename.block_id].into_iter().collect(), cx);
-            self.clear_highlighted_ranges::<Rename>(cx);
+            self.clear_background_highlights::<Rename>(cx);
         }
     }
 
@@ -5265,7 +5265,7 @@ impl Editor {
             .update(cx, |map, cx| map.set_wrap_width(width, cx))
     }
 
-    pub fn set_highlighted_rows(&mut self, rows: Option<Range<u32>>) {
+    pub fn highlight_rows(&mut self, rows: Option<Range<u32>>) {
         self.highlighted_rows = rows;
     }
 
@@ -5273,27 +5273,27 @@ impl Editor {
         self.highlighted_rows.clone()
     }
 
-    pub fn highlight_ranges<T: 'static>(
+    pub fn highlight_background<T: 'static>(
         &mut self,
         ranges: Vec<Range<Anchor>>,
         color: Color,
         cx: &mut ViewContext<Self>,
     ) {
-        self.highlighted_ranges
+        self.background_highlights
             .insert(TypeId::of::<T>(), (color, ranges));
         cx.notify();
     }
 
-    pub fn clear_highlighted_ranges<T: 'static>(
+    pub fn clear_background_highlights<T: 'static>(
         &mut self,
         cx: &mut ViewContext<Self>,
     ) -> Option<(Color, Vec<Range<Anchor>>)> {
         cx.notify();
-        self.highlighted_ranges.remove(&TypeId::of::<T>())
+        self.background_highlights.remove(&TypeId::of::<T>())
     }
 
     #[cfg(feature = "test-support")]
-    pub fn all_highlighted_ranges(
+    pub fn all_background_highlights(
         &mut self,
         cx: &mut ViewContext<Self>,
     ) -> Vec<(Range<DisplayPoint>, Color)> {
@@ -5301,23 +5301,23 @@ impl Editor {
         let buffer = &snapshot.buffer_snapshot;
         let start = buffer.anchor_before(0);
         let end = buffer.anchor_after(buffer.len());
-        self.highlighted_ranges_in_range(start..end, &snapshot)
+        self.background_highlights_in_range(start..end, &snapshot)
     }
 
-    pub fn highlighted_ranges_for_type<T: 'static>(&self) -> Option<(Color, &[Range<Anchor>])> {
-        self.highlighted_ranges
+    pub fn background_highlights_for_type<T: 'static>(&self) -> Option<(Color, &[Range<Anchor>])> {
+        self.background_highlights
             .get(&TypeId::of::<T>())
             .map(|(color, ranges)| (*color, ranges.as_slice()))
     }
 
-    pub fn highlighted_ranges_in_range(
+    pub fn background_highlights_in_range(
         &self,
         search_range: Range<Anchor>,
         display_snapshot: &DisplaySnapshot,
     ) -> Vec<(Range<DisplayPoint>, Color)> {
         let mut results = Vec::new();
         let buffer = &display_snapshot.buffer_snapshot;
-        for (color, ranges) in self.highlighted_ranges.values() {
+        for (color, ranges) in self.background_highlights.values() {
             let start_ix = match ranges.binary_search_by(|probe| {
                 let cmp = probe.end.cmp(&search_range.start, &buffer).unwrap();
                 if cmp.is_gt() {
@@ -5346,6 +5346,24 @@ impl Editor {
         results
     }
 
+    pub fn highlight_text<T: 'static>(
+        &mut self,
+        ranges: Vec<Range<Anchor>>,
+        style: HighlightStyle,
+        cx: &mut ViewContext<Self>,
+    ) {
+        self.display_map.update(cx, |map, _| {
+            map.highlight_text(TypeId::of::<T>(), ranges, style)
+        });
+        cx.notify();
+    }
+
+    pub fn clear_text_highlights<T: 'static>(&mut self, cx: &mut ViewContext<Self>) {
+        self.display_map
+            .update(cx, |map, _| map.clear_text_highlights(TypeId::of::<T>()));
+        cx.notify();
+    }
+
     fn next_blink_epoch(&mut self) -> usize {
         self.blink_epoch += 1;
         self.blink_epoch
@@ -8868,7 +8886,7 @@ mod tests {
                 buffer.anchor_after(range.start)..buffer.anchor_after(range.end)
             };
 
-            editor.highlight_ranges::<Type1>(
+            editor.highlight_background::<Type1>(
                 vec![
                     anchor_range(Point::new(2, 1)..Point::new(2, 3)),
                     anchor_range(Point::new(4, 2)..Point::new(4, 4)),
@@ -8878,7 +8896,7 @@ mod tests {
                 Color::red(),
                 cx,
             );
-            editor.highlight_ranges::<Type2>(
+            editor.highlight_background::<Type2>(
                 vec![
                     anchor_range(Point::new(3, 2)..Point::new(3, 5)),
                     anchor_range(Point::new(5, 3)..Point::new(5, 6)),
@@ -8890,7 +8908,7 @@ mod tests {
             );
 
             let snapshot = editor.snapshot(cx);
-            let mut highlighted_ranges = editor.highlighted_ranges_in_range(
+            let mut highlighted_ranges = editor.background_highlights_in_range(
                 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
                 &snapshot,
             );
@@ -8919,7 +8937,7 @@ mod tests {
                 ]
             );
             assert_eq!(
-                editor.highlighted_ranges_in_range(
+                editor.background_highlights_in_range(
                     anchor_range(Point::new(5, 6)..Point::new(6, 4)),
                     &snapshot,
                 ),

crates/editor/src/element.rs 🔗

@@ -606,30 +606,33 @@ impl EditorElement {
         } else {
             let style = &self.style;
             let chunks = snapshot.chunks(rows.clone(), true).map(|chunk| {
-                let highlight_style = chunk
-                    .highlight_id
-                    .and_then(|highlight_id| highlight_id.style(&style.syntax));
-                let highlight = if let Some(severity) = chunk.diagnostic {
+                let mut highlight_style = HighlightStyle {
+                    color: style.text.color,
+                    font_properties: style.text.font_properties,
+                    ..Default::default()
+                };
+
+                if let Some(syntax_highlight_style) = chunk
+                    .syntax_highlight_id
+                    .and_then(|id| id.style(&style.syntax))
+                {
+                    highlight_style.highlight(syntax_highlight_style);
+                }
+
+                if let Some(style) = chunk.highlight_style {
+                    highlight_style.highlight(style);
+                }
+
+                if let Some(severity) = chunk.diagnostic {
                     let diagnostic_style = super::diagnostic_style(severity, true, style);
-                    let underline = Some(Underline {
+                    highlight_style.underline = Some(Underline {
                         color: diagnostic_style.message.text.color,
                         thickness: 1.0.into(),
                         squiggly: true,
                     });
-                    if let Some(mut highlight) = highlight_style {
-                        highlight.underline = underline;
-                        Some(highlight)
-                    } else {
-                        Some(HighlightStyle {
-                            underline,
-                            color: style.text.color,
-                            font_properties: style.text.font_properties,
-                        })
-                    }
-                } else {
-                    highlight_style
-                };
-                (chunk.text, highlight)
+                }
+
+                (chunk.text, highlight_style)
             });
             layout_highlighted_chunks(
                 chunks,
@@ -852,7 +855,7 @@ impl Element for EditorElement {
             let display_map = view.display_map.update(cx, |map, cx| map.snapshot(cx));
 
             highlighted_rows = view.highlighted_rows();
-            highlighted_ranges = view.highlighted_ranges_in_range(
+            highlighted_ranges = view.background_highlights_in_range(
                 start_anchor.clone()..end_anchor.clone(),
                 &display_map,
             );

crates/go_to_line/src/go_to_line.rs 🔗

@@ -126,7 +126,7 @@ impl GoToLine {
                         let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
                         let display_point = point.to_display_point(&snapshot);
                         let row = display_point.row();
-                        active_editor.set_highlighted_rows(Some(row..row + 1));
+                        active_editor.highlight_rows(Some(row..row + 1));
                         active_editor.request_autoscroll(Autoscroll::Center, cx);
                     });
                     cx.notify();
@@ -143,7 +143,7 @@ impl Entity for GoToLine {
     fn release(&mut self, cx: &mut MutableAppContext) {
         let scroll_position = self.prev_scroll_position.take();
         self.active_editor.update(cx, |editor, cx| {
-            editor.set_highlighted_rows(None);
+            editor.highlight_rows(None);
             if let Some(scroll_position) = scroll_position {
                 editor.set_scroll_position(scroll_position, cx);
             }

crates/gpui/src/color.rs 🔗

@@ -5,7 +5,7 @@ use std::{
 };
 
 use crate::json::ToJson;
-use pathfinder_color::ColorU;
+use pathfinder_color::{ColorF, ColorU};
 use serde::{
     de::{self, Unexpected},
     Deserialize, Deserializer,
@@ -48,6 +48,29 @@ impl Color {
     pub fn from_u32(rgba: u32) -> Self {
         Self(ColorU::from_u32(rgba))
     }
+
+    pub fn blend(source: Color, dest: Color) -> Color {
+        if dest.a == 255 {
+            return dest;
+        }
+
+        let source = source.0.to_f32();
+        let dest = dest.0.to_f32();
+
+        let a = source.a() + (dest.a() * (1. - source.a()));
+        let r = ((source.r() * source.a()) + (dest.r() * dest.a() * (1. - source.a()))) / a;
+        let g = ((source.g() * source.a()) + (dest.g() * dest.a() * (1. - source.a()))) / a;
+        let b = ((source.b() * source.a()) + (dest.b() * dest.a() * (1. - source.a()))) / a;
+
+        Self(ColorF::new(r, g, b, a).to_u8())
+    }
+
+    pub fn fade_out(&mut self, factor: f32) {
+        let source_alpha = 1. - factor.clamp(0., 1.);
+        let dest_alpha = self.0.a as f32 / 255.;
+        let dest_alpha = source_alpha + (dest_alpha * (1. - source_alpha));
+        self.0.a = (dest_alpha * (1. / 255.)) as u8;
+    }
 }
 
 impl<'de> Deserialize<'de> for Color {

crates/gpui/src/elements/text.rs 🔗

@@ -67,17 +67,20 @@ impl Element for Text {
         let mut highlight_ranges = self.highlights.iter().peekable();
         let chunks = std::iter::from_fn(|| {
             let result;
-            if let Some((range, highlight)) = highlight_ranges.peek() {
+            if let Some((range, highlight_style)) = highlight_ranges.peek() {
                 if offset < range.start {
-                    result = Some((&self.text[offset..range.start], None));
+                    result = Some((
+                        &self.text[offset..range.start],
+                        HighlightStyle::from(&self.style),
+                    ));
                     offset = range.start;
                 } else {
-                    result = Some((&self.text[range.clone()], Some(*highlight)));
+                    result = Some((&self.text[range.clone()], *highlight_style));
                     highlight_ranges.next();
                     offset = range.end;
                 }
             } else if offset < self.text.len() {
-                result = Some((&self.text[offset..], None));
+                result = Some((&self.text[offset..], HighlightStyle::from(&self.style)));
                 offset = self.text.len();
             } else {
                 result = None;
@@ -197,24 +200,24 @@ impl Element for Text {
 
 /// Perform text layout on a series of highlighted chunks of text.
 pub fn layout_highlighted_chunks<'a>(
-    chunks: impl Iterator<Item = (&'a str, Option<HighlightStyle>)>,
-    style: &'a TextStyle,
+    chunks: impl Iterator<Item = (&'a str, HighlightStyle)>,
+    text_style: &'a TextStyle,
     text_layout_cache: &'a TextLayoutCache,
     font_cache: &'a Arc<FontCache>,
     max_line_len: usize,
     max_line_count: usize,
 ) -> Vec<Line> {
     let mut layouts = Vec::with_capacity(max_line_count);
-    let mut prev_font_properties = style.font_properties.clone();
-    let mut prev_font_id = style.font_id;
+    let mut prev_font_properties = text_style.font_properties.clone();
+    let mut prev_font_id = text_style.font_id;
     let mut line = String::new();
     let mut styles = Vec::new();
     let mut row = 0;
     let mut line_exceeded_max_len = false;
-    for (chunk, highlight_style) in chunks.chain([("\n", None)]) {
+    for (chunk, highlight_style) in chunks.chain([("\n", Default::default())]) {
         for (ix, mut line_chunk) in chunk.split('\n').enumerate() {
             if ix > 0 {
-                layouts.push(text_layout_cache.layout_str(&line, style.font_size, &styles));
+                layouts.push(text_layout_cache.layout_str(&line, text_style.font_size, &styles));
                 line.clear();
                 styles.clear();
                 row += 1;
@@ -225,15 +228,13 @@ pub fn layout_highlighted_chunks<'a>(
             }
 
             if !line_chunk.is_empty() && !line_exceeded_max_len {
-                let highlight_style = highlight_style.unwrap_or(style.clone().into());
-
                 // Avoid a lookup if the font properties match the previous ones.
                 let font_id = if highlight_style.font_properties == prev_font_properties {
                     prev_font_id
                 } else {
                     font_cache
-                        .select_font(style.font_family_id, &highlight_style.font_properties)
-                        .unwrap_or(style.font_id)
+                        .select_font(text_style.font_family_id, &highlight_style.font_properties)
+                        .unwrap_or(text_style.font_id)
                 };
 
                 if line.len() + line_chunk.len() > max_line_len {

crates/gpui/src/fonts.rs 🔗

@@ -31,13 +31,16 @@ pub struct TextStyle {
     pub underline: Option<Underline>,
 }
 
-#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, Default, PartialEq)]
 pub struct HighlightStyle {
     pub color: Color,
     pub font_properties: Properties,
     pub underline: Option<Underline>,
+    pub fade_out: Option<f32>,
 }
 
+impl Eq for HighlightStyle {}
+
 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
 pub struct Underline {
     pub color: Color,
@@ -83,6 +86,8 @@ struct HighlightStyleJson {
     italic: bool,
     #[serde(default)]
     underline: UnderlineStyleJson,
+    #[serde(default)]
+    fade_out: Option<f32>,
 }
 
 #[derive(Deserialize)]
@@ -131,7 +136,10 @@ impl TextStyle {
         if self.font_properties != style.font_properties {
             self.font_id = font_cache.select_font(self.font_family_id, &style.font_properties)?;
         }
-        self.color = style.color;
+        self.color = Color::blend(self.color, style.color);
+        if let Some(factor) = style.fade_out {
+            self.color.fade_out(factor);
+        }
         self.underline = style.underline;
         Ok(self)
     }
@@ -199,10 +207,17 @@ impl TextStyle {
 
 impl From<TextStyle> for HighlightStyle {
     fn from(other: TextStyle) -> Self {
+        Self::from(&other)
+    }
+}
+
+impl From<&TextStyle> for HighlightStyle {
+    fn from(other: &TextStyle) -> Self {
         Self {
             color: other.color,
             font_properties: other.font_properties,
             underline: other.underline,
+            fade_out: None,
         }
     }
 }
@@ -246,6 +261,18 @@ impl HighlightStyle {
             color: json.color,
             font_properties,
             underline: underline_from_json(json.underline, json.color),
+            fade_out: json.fade_out,
+        }
+    }
+
+    pub fn highlight(&mut self, other: HighlightStyle) {
+        self.color = Color::blend(other.color, self.color);
+        if let Some(factor) = other.fade_out {
+            self.color.fade_out(factor);
+        }
+        self.font_properties = other.font_properties;
+        if other.underline.is_some() {
+            self.underline = other.underline;
         }
     }
 }
@@ -256,6 +283,7 @@ impl From<Color> for HighlightStyle {
             color,
             font_properties: Default::default(),
             underline: None,
+            fade_out: None,
         }
     }
 }
@@ -295,6 +323,7 @@ impl<'de> Deserialize<'de> for HighlightStyle {
                 color: serde_json::from_value(json).map_err(de::Error::custom)?,
                 font_properties: Properties::new(),
                 underline: None,
+                fade_out: None,
             })
         }
     }

crates/language/src/buffer.rs 🔗

@@ -12,7 +12,7 @@ use crate::{
 use anyhow::{anyhow, Result};
 use clock::ReplicaId;
 use futures::FutureExt as _;
-use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task};
+use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, MutableAppContext, Task};
 use lazy_static::lazy_static;
 use parking_lot::Mutex;
 use similar::{ChangeTag, TextDiff};
@@ -248,7 +248,8 @@ pub struct BufferChunks<'a> {
 #[derive(Clone, Copy, Debug, Default)]
 pub struct Chunk<'a> {
     pub text: &'a str,
-    pub highlight_id: Option<HighlightId>,
+    pub syntax_highlight_id: Option<HighlightId>,
+    pub highlight_style: Option<HighlightStyle>,
     pub diagnostic: Option<DiagnosticSeverity>,
 }
 
@@ -1716,7 +1717,7 @@ impl BufferSnapshot {
                             offset += chunk.text.len();
                         }
                         let style = chunk
-                            .highlight_id
+                            .syntax_highlight_id
                             .zip(theme)
                             .and_then(|(highlight, theme)| highlight.style(theme));
                         if let Some(style) = style {
@@ -2086,7 +2087,8 @@ impl<'a> Iterator for BufferChunks<'a> {
 
             Some(Chunk {
                 text: slice,
-                highlight_id,
+                syntax_highlight_id: highlight_id,
+                highlight_style: None,
                 diagnostic: self.current_diagnostic_severity(),
             })
         } else {

crates/language/src/highlight_map.rs 🔗

@@ -8,7 +8,7 @@ pub struct HighlightMap(Arc<[HighlightId]>);
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub struct HighlightId(pub u32);
 
-const DEFAULT_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX);
+const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX);
 
 impl HighlightMap {
     pub fn new(capture_names: &[String], theme: &SyntaxTheme) -> Self {
@@ -36,7 +36,7 @@ impl HighlightMap {
                             Some((i, len))
                         })
                         .max_by_key(|(_, len)| *len)
-                        .map_or(DEFAULT_HIGHLIGHT_ID, |(i, _)| HighlightId(i as u32))
+                        .map_or(DEFAULT_SYNTAX_HIGHLIGHT_ID, |(i, _)| HighlightId(i as u32))
                 })
                 .collect(),
         )
@@ -46,7 +46,7 @@ impl HighlightMap {
         self.0
             .get(capture_id as usize)
             .copied()
-            .unwrap_or(DEFAULT_HIGHLIGHT_ID)
+            .unwrap_or(DEFAULT_SYNTAX_HIGHLIGHT_ID)
     }
 }
 
@@ -72,7 +72,7 @@ impl Default for HighlightMap {
 
 impl Default for HighlightId {
     fn default() -> Self {
-        DEFAULT_HIGHLIGHT_ID
+        DEFAULT_SYNTAX_HIGHLIGHT_ID
     }
 }
 

crates/language/src/language.rs 🔗

@@ -515,7 +515,7 @@ impl Language {
             for chunk in BufferChunks::new(text, range, Some(&tree), self.grammar.as_ref(), vec![])
             {
                 let end_offset = offset + chunk.text.len();
-                if let Some(highlight_id) = chunk.highlight_id {
+                if let Some(highlight_id) = chunk.syntax_highlight_id {
                     result.push((offset..end_offset, highlight_id));
                 }
                 offset = end_offset;

crates/outline/src/outline.rs 🔗

@@ -186,7 +186,7 @@ impl OutlineView {
                 let end = outline_item.range.end.to_point(&buffer_snapshot);
                 let display_rows = start.to_display_point(&snapshot).row()
                     ..end.to_display_point(&snapshot).row() + 1;
-                active_editor.set_highlighted_rows(Some(display_rows));
+                active_editor.highlight_rows(Some(display_rows));
                 active_editor.request_autoscroll(Autoscroll::Center, cx);
             });
         }
@@ -207,7 +207,7 @@ impl OutlineView {
 
     fn restore_active_editor(&mut self, cx: &mut MutableAppContext) {
         self.active_editor.update(cx, |editor, cx| {
-            editor.set_highlighted_rows(None);
+            editor.highlight_rows(None);
             if let Some(scroll_position) = self.prev_scroll_position {
                 editor.set_scroll_position(scroll_position, cx);
             }

crates/search/src/buffer_search.rs 🔗

@@ -151,7 +151,9 @@ impl Toolbar for SearchBar {
         self.dismissed = true;
         for (editor, _) in &self.editors_with_matches {
             if let Some(editor) = editor.upgrade(cx) {
-                editor.update(cx, |editor, cx| editor.clear_highlighted_ranges::<Self>(cx));
+                editor.update(cx, |editor, cx| {
+                    editor.clear_background_highlights::<Self>(cx)
+                });
             }
         }
     }
@@ -397,7 +399,9 @@ impl SearchBar {
                 if Some(&editor) == self.active_editor.as_ref() {
                     active_editor_matches = Some((editor.downgrade(), ranges));
                 } else {
-                    editor.update(cx, |editor, cx| editor.clear_highlighted_ranges::<Self>(cx));
+                    editor.update(cx, |editor, cx| {
+                        editor.clear_background_highlights::<Self>(cx)
+                    });
                 }
             }
         }
@@ -410,7 +414,9 @@ impl SearchBar {
         if let Some(editor) = self.active_editor.as_ref() {
             if query.is_empty() {
                 self.active_match_index.take();
-                editor.update(cx, |editor, cx| editor.clear_highlighted_ranges::<Self>(cx));
+                editor.update(cx, |editor, cx| {
+                    editor.clear_background_highlights::<Self>(cx)
+                });
             } else {
                 let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
                 let query = if self.regex {
@@ -480,7 +486,7 @@ impl SearchBar {
                                         }
                                     }
 
-                                    editor.highlight_ranges::<Self>(
+                                    editor.highlight_background::<Self>(
                                         ranges,
                                         theme.match_background,
                                         cx,
@@ -557,7 +563,7 @@ mod tests {
         editor.next_notification(&cx).await;
         editor.update(cx, |editor, cx| {
             assert_eq!(
-                editor.all_highlighted_ranges(cx),
+                editor.all_background_highlights(cx),
                 &[
                     (
                         DisplayPoint::new(2, 17)..DisplayPoint::new(2, 19),
@@ -578,7 +584,7 @@ mod tests {
         editor.next_notification(&cx).await;
         editor.update(cx, |editor, cx| {
             assert_eq!(
-                editor.all_highlighted_ranges(cx),
+                editor.all_background_highlights(cx),
                 &[(
                     DisplayPoint::new(2, 43)..DisplayPoint::new(2, 45),
                     Color::red(),
@@ -594,7 +600,7 @@ mod tests {
         editor.next_notification(&cx).await;
         editor.update(cx, |editor, cx| {
             assert_eq!(
-                editor.all_highlighted_ranges(cx),
+                editor.all_background_highlights(cx),
                 &[
                     (
                         DisplayPoint::new(0, 24)..DisplayPoint::new(0, 26),
@@ -635,7 +641,7 @@ mod tests {
         editor.next_notification(&cx).await;
         editor.update(cx, |editor, cx| {
             assert_eq!(
-                editor.all_highlighted_ranges(cx),
+                editor.all_background_highlights(cx),
                 &[
                     (
                         DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43),

crates/search/src/project_search.rs 🔗

@@ -565,7 +565,7 @@ impl ProjectSearchView {
                 if reset_selections {
                     editor.select_ranges(match_ranges.first().cloned(), Some(Autoscroll::Fit), cx);
                 }
-                editor.highlight_ranges::<Self>(match_ranges, theme.match_background, cx);
+                editor.highlight_background::<Self>(match_ranges, theme.match_background, cx);
             });
             if self.query_editor.is_focused(cx) {
                 self.focus_results_editor(cx);
@@ -764,7 +764,7 @@ mod tests {
             assert_eq!(
                 search_view
                     .results_editor
-                    .update(cx, |editor, cx| editor.all_highlighted_ranges(cx)),
+                    .update(cx, |editor, cx| editor.all_background_highlights(cx)),
                 &[
                     (
                         DisplayPoint::new(2, 32)..DisplayPoint::new(2, 35),