debugger: Parse and highlight text with ANSI escape sequences (#32915)

Cole Miller created

Relanding #32817 with an improved approach, bugs fixed, and a test.

Release Notes:

- N/A

Change summary

Cargo.lock                                         |   1 
crates/debugger_ui/Cargo.toml                      |   1 
crates/debugger_ui/src/session/running/console.rs  | 263 +++++++++++++++
crates/debugger_ui/src/tests/console.rs            | 203 ++++++++++++
crates/editor/src/display_map.rs                   |  36 +
crates/editor/src/display_map/custom_highlights.rs |  17 
crates/editor/src/display_map/inlay_map.rs         |   4 
crates/editor/src/editor.rs                        | 108 +++++-
crates/editor/src/editor_tests.rs                  |   8 
crates/editor/src/element.rs                       |  16 
crates/editor/src/highlight_matching_bracket.rs    |   2 
crates/editor/src/hover_popover.rs                 |   2 
crates/editor/src/items.rs                         |   9 
crates/editor/src/test/editor_test_context.rs      |   4 
crates/language_tools/src/syntax_tree_view.rs      |   2 
crates/project/src/debugger/dap_command.rs         |   2 
crates/search/src/project_search.rs                |   2 
crates/vim/src/normal/yank.rs                      |   2 
crates/vim/src/replace.rs                          |   2 
19 files changed, 605 insertions(+), 79 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -4268,6 +4268,7 @@ dependencies = [
 name = "debugger_ui"
 version = "0.1.0"
 dependencies = [
+ "alacritty_terminal",
  "anyhow",
  "client",
  "collections",

crates/debugger_ui/Cargo.toml 🔗

@@ -26,6 +26,7 @@ test-support = [
 ]
 
 [dependencies]
+alacritty_terminal.workspace = true
 anyhow.workspace = true
 client.workspace = true
 collections.workspace = true

crates/debugger_ui/src/session/running/console.rs 🔗

@@ -2,13 +2,15 @@ use super::{
     stack_frame_list::{StackFrameList, StackFrameListEvent},
     variable_list::VariableList,
 };
+use alacritty_terminal::vte::ansi;
 use anyhow::Result;
 use collections::HashMap;
 use dap::OutputEvent;
 use editor::{Bias, CompletionProvider, Editor, EditorElement, EditorStyle, ExcerptId};
 use fuzzy::StringMatchCandidate;
 use gpui::{
-    Context, Entity, FocusHandle, Focusable, Render, Subscription, Task, TextStyle, WeakEntity,
+    Context, Entity, FocusHandle, Focusable, HighlightStyle, Hsla, Render, Subscription, Task,
+    TextStyle, WeakEntity,
 };
 use language::{Buffer, CodeLabel, ToOffset};
 use menu::Confirm;
@@ -17,8 +19,8 @@ use project::{
     debugger::session::{CompletionsQuery, OutputToken, Session, SessionEvent},
 };
 use settings::Settings;
-use std::{cell::RefCell, rc::Rc, usize};
-use theme::ThemeSettings;
+use std::{cell::RefCell, ops::Range, rc::Rc, usize};
+use theme::{Theme, ThemeSettings};
 use ui::{Divider, prelude::*};
 
 pub struct Console {
@@ -136,18 +138,193 @@ impl Console {
         cx: &mut App,
     ) {
         self.console.update(cx, |console, cx| {
-            let mut to_insert = String::default();
+            console.set_read_only(false);
+
             for event in events {
-                use std::fmt::Write;
+                let to_insert = format!("{}\n", event.output.trim_end());
+
+                let mut ansi_handler = ConsoleHandler::default();
+                let mut ansi_processor = ansi::Processor::<ansi::StdSyncHandler>::default();
+
+                let len = console.buffer().read(cx).len(cx);
+                ansi_processor.advance(&mut ansi_handler, to_insert.as_bytes());
+                let output = std::mem::take(&mut ansi_handler.output);
+                let mut spans = std::mem::take(&mut ansi_handler.spans);
+                let mut background_spans = std::mem::take(&mut ansi_handler.background_spans);
+                if ansi_handler.current_range_start < output.len() {
+                    spans.push((
+                        ansi_handler.current_range_start..output.len(),
+                        ansi_handler.current_color,
+                    ));
+                }
+                if ansi_handler.current_background_range_start < output.len() {
+                    background_spans.push((
+                        ansi_handler.current_background_range_start..output.len(),
+                        ansi_handler.current_background_color,
+                    ));
+                }
+                console.move_to_end(&editor::actions::MoveToEnd, window, cx);
+                console.insert(&output, window, cx);
+                let buffer = console.buffer().read(cx).snapshot(cx);
+
+                struct ConsoleAnsiHighlight;
+
+                for (range, color) in spans {
+                    let Some(color) = color else { continue };
+                    let start_offset = len + range.start;
+                    let range = start_offset..len + range.end;
+                    let range = buffer.anchor_after(range.start)..buffer.anchor_before(range.end);
+                    let style = HighlightStyle {
+                        color: Some(terminal_view::terminal_element::convert_color(
+                            &color,
+                            cx.theme(),
+                        )),
+                        ..Default::default()
+                    };
+                    console.highlight_text_key::<ConsoleAnsiHighlight>(
+                        start_offset,
+                        vec![range],
+                        style,
+                        cx,
+                    );
+                }
 
-                _ = write!(to_insert, "{}\n", event.output.trim_end());
+                for (range, color) in background_spans {
+                    let Some(color) = color else { continue };
+                    let start_offset = len + range.start;
+                    let range = start_offset..len + range.end;
+                    let range = buffer.anchor_after(range.start)..buffer.anchor_before(range.end);
+
+                    let color_fetcher: fn(&Theme) -> Hsla = match color {
+                        // Named and theme defined colors
+                        ansi::Color::Named(n) => match n {
+                            ansi::NamedColor::Black => |theme| theme.colors().terminal_ansi_black,
+                            ansi::NamedColor::Red => |theme| theme.colors().terminal_ansi_red,
+                            ansi::NamedColor::Green => |theme| theme.colors().terminal_ansi_green,
+                            ansi::NamedColor::Yellow => |theme| theme.colors().terminal_ansi_yellow,
+                            ansi::NamedColor::Blue => |theme| theme.colors().terminal_ansi_blue,
+                            ansi::NamedColor::Magenta => {
+                                |theme| theme.colors().terminal_ansi_magenta
+                            }
+                            ansi::NamedColor::Cyan => |theme| theme.colors().terminal_ansi_cyan,
+                            ansi::NamedColor::White => |theme| theme.colors().terminal_ansi_white,
+                            ansi::NamedColor::BrightBlack => {
+                                |theme| theme.colors().terminal_ansi_bright_black
+                            }
+                            ansi::NamedColor::BrightRed => {
+                                |theme| theme.colors().terminal_ansi_bright_red
+                            }
+                            ansi::NamedColor::BrightGreen => {
+                                |theme| theme.colors().terminal_ansi_bright_green
+                            }
+                            ansi::NamedColor::BrightYellow => {
+                                |theme| theme.colors().terminal_ansi_bright_yellow
+                            }
+                            ansi::NamedColor::BrightBlue => {
+                                |theme| theme.colors().terminal_ansi_bright_blue
+                            }
+                            ansi::NamedColor::BrightMagenta => {
+                                |theme| theme.colors().terminal_ansi_bright_magenta
+                            }
+                            ansi::NamedColor::BrightCyan => {
+                                |theme| theme.colors().terminal_ansi_bright_cyan
+                            }
+                            ansi::NamedColor::BrightWhite => {
+                                |theme| theme.colors().terminal_ansi_bright_white
+                            }
+                            ansi::NamedColor::Foreground => {
+                                |theme| theme.colors().terminal_foreground
+                            }
+                            ansi::NamedColor::Background => {
+                                |theme| theme.colors().terminal_background
+                            }
+                            ansi::NamedColor::Cursor => |theme| theme.players().local().cursor,
+                            ansi::NamedColor::DimBlack => {
+                                |theme| theme.colors().terminal_ansi_dim_black
+                            }
+                            ansi::NamedColor::DimRed => {
+                                |theme| theme.colors().terminal_ansi_dim_red
+                            }
+                            ansi::NamedColor::DimGreen => {
+                                |theme| theme.colors().terminal_ansi_dim_green
+                            }
+                            ansi::NamedColor::DimYellow => {
+                                |theme| theme.colors().terminal_ansi_dim_yellow
+                            }
+                            ansi::NamedColor::DimBlue => {
+                                |theme| theme.colors().terminal_ansi_dim_blue
+                            }
+                            ansi::NamedColor::DimMagenta => {
+                                |theme| theme.colors().terminal_ansi_dim_magenta
+                            }
+                            ansi::NamedColor::DimCyan => {
+                                |theme| theme.colors().terminal_ansi_dim_cyan
+                            }
+                            ansi::NamedColor::DimWhite => {
+                                |theme| theme.colors().terminal_ansi_dim_white
+                            }
+                            ansi::NamedColor::BrightForeground => {
+                                |theme| theme.colors().terminal_bright_foreground
+                            }
+                            ansi::NamedColor::DimForeground => {
+                                |theme| theme.colors().terminal_dim_foreground
+                            }
+                        },
+                        // 'True' colors
+                        ansi::Color::Spec(_) => |theme| theme.colors().editor_background,
+                        // 8 bit, indexed colors
+                        ansi::Color::Indexed(i) => {
+                            match i {
+                                // 0-15 are the same as the named colors above
+                                0 => |theme| theme.colors().terminal_ansi_black,
+                                1 => |theme| theme.colors().terminal_ansi_red,
+                                2 => |theme| theme.colors().terminal_ansi_green,
+                                3 => |theme| theme.colors().terminal_ansi_yellow,
+                                4 => |theme| theme.colors().terminal_ansi_blue,
+                                5 => |theme| theme.colors().terminal_ansi_magenta,
+                                6 => |theme| theme.colors().terminal_ansi_cyan,
+                                7 => |theme| theme.colors().terminal_ansi_white,
+                                8 => |theme| theme.colors().terminal_ansi_bright_black,
+                                9 => |theme| theme.colors().terminal_ansi_bright_red,
+                                10 => |theme| theme.colors().terminal_ansi_bright_green,
+                                11 => |theme| theme.colors().terminal_ansi_bright_yellow,
+                                12 => |theme| theme.colors().terminal_ansi_bright_blue,
+                                13 => |theme| theme.colors().terminal_ansi_bright_magenta,
+                                14 => |theme| theme.colors().terminal_ansi_bright_cyan,
+                                15 => |theme| theme.colors().terminal_ansi_bright_white,
+                                // 16-231 are a 6x6x6 RGB color cube, mapped to 0-255 using steps defined by XTerm.
+                                // See: https://github.com/xterm-x11/xterm-snapshots/blob/master/256colres.pl
+                                // 16..=231 => {
+                                //     let (r, g, b) = rgb_for_index(index as u8);
+                                //     rgba_color(
+                                //         if r == 0 { 0 } else { r * 40 + 55 },
+                                //         if g == 0 { 0 } else { g * 40 + 55 },
+                                //         if b == 0 { 0 } else { b * 40 + 55 },
+                                //     )
+                                // }
+                                // 232-255 are a 24-step grayscale ramp from (8, 8, 8) to (238, 238, 238).
+                                // 232..=255 => {
+                                //     let i = index as u8 - 232; // Align index to 0..24
+                                //     let value = i * 10 + 8;
+                                //     rgba_color(value, value, value)
+                                // }
+                                // For compatibility with the alacritty::Colors interface
+                                // See: https://github.com/alacritty/alacritty/blob/master/alacritty_terminal/src/term/color.rs
+                                _ => |_| gpui::black(),
+                            }
+                        }
+                    };
+
+                    console.highlight_background_key::<ConsoleAnsiHighlight>(
+                        start_offset,
+                        &[range],
+                        color_fetcher,
+                        cx,
+                    );
+                }
             }
 
-            console.set_read_only(false);
-            console.move_to_end(&editor::actions::MoveToEnd, window, cx);
-            console.insert(&to_insert, window, cx);
             console.set_read_only(true);
-
             cx.notify();
         });
     }
@@ -459,3 +636,69 @@ impl ConsoleQueryBarCompletionProvider {
         })
     }
 }
+
+#[derive(Default)]
+struct ConsoleHandler {
+    output: String,
+    spans: Vec<(Range<usize>, Option<ansi::Color>)>,
+    background_spans: Vec<(Range<usize>, Option<ansi::Color>)>,
+    current_range_start: usize,
+    current_background_range_start: usize,
+    current_color: Option<ansi::Color>,
+    current_background_color: Option<ansi::Color>,
+    pos: usize,
+}
+
+impl ConsoleHandler {
+    fn break_span(&mut self, color: Option<ansi::Color>) {
+        self.spans.push((
+            self.current_range_start..self.output.len(),
+            self.current_color,
+        ));
+        self.current_color = color;
+        self.current_range_start = self.pos;
+    }
+
+    fn break_background_span(&mut self, color: Option<ansi::Color>) {
+        self.background_spans.push((
+            self.current_background_range_start..self.output.len(),
+            self.current_background_color,
+        ));
+        self.current_background_color = color;
+        self.current_background_range_start = self.pos;
+    }
+}
+
+impl ansi::Handler for ConsoleHandler {
+    fn input(&mut self, c: char) {
+        self.output.push(c);
+        self.pos += c.len_utf8();
+    }
+
+    fn linefeed(&mut self) {
+        self.output.push('\n');
+        self.pos += 1;
+    }
+
+    fn put_tab(&mut self, count: u16) {
+        self.output
+            .extend(std::iter::repeat('\t').take(count as usize));
+        self.pos += count as usize;
+    }
+
+    fn terminal_attribute(&mut self, attr: ansi::Attr) {
+        match attr {
+            ansi::Attr::Foreground(color) => {
+                self.break_span(Some(color));
+            }
+            ansi::Attr::Background(color) => {
+                self.break_background_span(Some(color));
+            }
+            ansi::Attr::Reset => {
+                self.break_span(None);
+                self.break_background_span(None);
+            }
+            _ => {}
+        }
+    }
+}

crates/debugger_ui/src/tests/console.rs 🔗

@@ -3,6 +3,7 @@ use crate::{
     *,
 };
 use dap::requests::StackTrace;
+use editor::{DisplayPoint, display_map::DisplayRow};
 use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
 use project::{FakeFs, Project};
 use serde_json::json;
@@ -110,7 +111,7 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
     client
         .fake_event(dap::messages::Events::Output(dap::OutputEvent {
             category: Some(dap::OutputEventCategory::Stdout),
-            output: "Second output line after thread stopped!".to_string(),
+            output: "\tSecond output line after thread stopped!".to_string(),
             data: None,
             variables_reference: None,
             source: None,
@@ -124,7 +125,7 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
     client
         .fake_event(dap::messages::Events::Output(dap::OutputEvent {
             category: Some(dap::OutputEventCategory::Console),
-            output: "Second console output line after thread stopped!".to_string(),
+            output: "\tSecond console output line after thread stopped!".to_string(),
             data: None,
             variables_reference: None,
             source: None,
@@ -150,13 +151,209 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
                 .unwrap();
 
             assert_eq!(
-                "First console output line before thread stopped!\nFirst output line before thread stopped!\nSecond output line after thread stopped!\nSecond console output line after thread stopped!\n",
+                "First console output line before thread stopped!\nFirst output line before thread stopped!\n\tSecond output line after thread stopped!\n\tSecond console output line after thread stopped!\n",
                 active_session_panel.read(cx).running_state().read(cx).console().read(cx).editor().read(cx).text(cx).as_str()
             );
         })
         .unwrap();
 }
 
+#[gpui::test]
+async fn test_escape_code_processing(executor: BackgroundExecutor, cx: &mut TestAppContext) {
+    init_test(cx);
+
+    let fs = FakeFs::new(executor.clone());
+
+    fs.insert_tree(
+        path!("/project"),
+        json!({
+            "main.rs": "First line\nSecond line\nThird line\nFourth line",
+        }),
+    )
+    .await;
+
+    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
+    let workspace = init_test_workspace(&project, cx).await;
+    let cx = &mut VisualTestContext::from_window(*workspace, cx);
+    workspace
+        .update(cx, |workspace, window, cx| {
+            workspace.focus_panel::<DebugPanel>(window, cx);
+        })
+        .unwrap();
+
+    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
+    let client = session.read_with(cx, |session, _| session.adapter_client().unwrap());
+
+    client.on_request::<StackTrace, _>(move |_, _| {
+        Ok(dap::StackTraceResponse {
+            stack_frames: Vec::default(),
+            total_frames: None,
+        })
+    });
+
+    client
+        .fake_event(dap::messages::Events::Output(dap::OutputEvent {
+            category: None,
+            output: "Checking latest version of JavaScript...".to_string(),
+            data: None,
+            variables_reference: None,
+            source: None,
+            line: None,
+            column: None,
+            group: None,
+            location_reference: None,
+        }))
+        .await;
+    client
+        .fake_event(dap::messages::Events::Output(dap::OutputEvent {
+            category: None,
+            output: "   \u{1b}[1m\u{1b}[38;2;173;127;168m▲ Next.js 15.1.5\u{1b}[39m\u{1b}[22m"
+                .to_string(),
+            data: None,
+            variables_reference: None,
+            source: None,
+            line: None,
+            column: None,
+            group: None,
+            location_reference: None,
+        }))
+        .await;
+    client
+        .fake_event(dap::messages::Events::Output(dap::OutputEvent {
+            category: None,
+            output: "   - Local:        http://localhost:3000\n   - Network:      http://192.168.1.144:3000\n\n \u{1b}[32m\u{1b}[1m✓\u{1b}[22m\u{1b}[39m Starting..."
+                .to_string(),
+            data: None,
+            variables_reference: None,
+            source: None,
+            line: None,
+            column: None,
+            group: None,
+            location_reference: None,
+        }))
+        .await;
+    // [crates/debugger_ui/src/session/running/console.rs:147:9] &to_insert = "Could not read source map for file:///Users/cole/roles-at/node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/typescript.js: ENOENT: no such file or directory, open '/Users/cole/roles-at/node_modules/.pnpm/typescript@5.7.3/node_modules/typescript/lib/typescript.js.map'\n"
+    client
+        .fake_event(dap::messages::Events::Output(dap::OutputEvent {
+            category: None,
+            output: "Something else...".to_string(),
+            data: None,
+            variables_reference: None,
+            source: None,
+            line: None,
+            column: None,
+            group: None,
+            location_reference: None,
+        }))
+        .await;
+    client
+        .fake_event(dap::messages::Events::Output(dap::OutputEvent {
+            category: None,
+            output: " \u{1b}[32m\u{1b}[1m✓\u{1b}[22m\u{1b}[39m Ready in 1009ms\n".to_string(),
+            data: None,
+            variables_reference: None,
+            source: None,
+            line: None,
+            column: None,
+            group: None,
+            location_reference: None,
+        }))
+        .await;
+
+    // introduce some background highlight
+    client
+        .fake_event(dap::messages::Events::Output(dap::OutputEvent {
+            category: None,
+            output: "\u{1b}[41m\u{1b}[37mBoth background and foreground!".to_string(),
+            data: None,
+            variables_reference: None,
+            source: None,
+            line: None,
+            column: None,
+            group: None,
+            location_reference: None,
+        }))
+        .await;
+    // another random line
+    client
+        .fake_event(dap::messages::Events::Output(dap::OutputEvent {
+            category: None,
+            output: "Even more...".to_string(),
+            data: None,
+            variables_reference: None,
+            source: None,
+            line: None,
+            column: None,
+            group: None,
+            location_reference: None,
+        }))
+        .await;
+
+    cx.run_until_parked();
+
+    let _running_state =
+        active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
+            cx.focus_self(window);
+            item.running_state().clone()
+        });
+
+    cx.run_until_parked();
+
+    workspace
+        .update(cx, |workspace, window, cx| {
+            let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
+            let active_debug_session_panel = debug_panel
+                .update(cx, |this, _| this.active_session())
+                .unwrap();
+
+            let editor =
+                active_debug_session_panel
+                    .read(cx)
+                    .running_state()
+                    .read(cx)
+                    .console()
+                    .read(cx)
+                    .editor().clone();
+
+            assert_eq!(
+                "Checking latest version of JavaScript...\n   ▲ Next.js 15.1.5\n   - Local:        http://localhost:3000\n   - Network:      http://192.168.1.144:3000\n\n ✓ Starting...\nSomething else...\n ✓ Ready in 1009ms\nBoth background and foreground!\nEven more...\n",
+                editor
+                    .read(cx)
+                    .text(cx)
+                    .as_str()
+            );
+
+            let text_highlights = editor.update(cx, |editor, cx| {
+                let mut text_highlights = editor.all_text_highlights(window, cx).into_iter().flat_map(|(_, ranges)| ranges).collect::<Vec<_>>();
+                text_highlights.sort_by(|a, b| a.start.cmp(&b.start));
+                text_highlights
+            });
+            pretty_assertions::assert_eq!(
+                text_highlights,
+                [
+                    DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 21),
+                    DisplayPoint::new(DisplayRow(1), 21)..DisplayPoint::new(DisplayRow(2), 0),
+                    DisplayPoint::new(DisplayRow(5), 1)..DisplayPoint::new(DisplayRow(5), 4),
+                    DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(6), 0),
+                    DisplayPoint::new(DisplayRow(7), 1)..DisplayPoint::new(DisplayRow(7), 4),
+                    DisplayPoint::new(DisplayRow(7), 4)..DisplayPoint::new(DisplayRow(8), 0),
+                    DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(9), 0),
+                ]
+            );
+
+            let background_highlights = editor.update(cx, |editor, cx| {
+                editor.all_text_background_highlights(window, cx).into_iter().map(|(range, _)| range).collect::<Vec<_>>()
+            });
+            pretty_assertions::assert_eq!(
+                background_highlights,
+                [
+                    DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(9), 0),
+                ]
+            )
+        })
+        .unwrap();
+}
+
 // #[gpui::test]
 // async fn test_grouped_output(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 //     init_test(cx);

crates/editor/src/display_map.rs 🔗

@@ -76,11 +76,17 @@ pub enum FoldStatus {
     Foldable,
 }
 
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub enum HighlightKey {
+    Type(TypeId),
+    TypePlus(TypeId, usize),
+}
+
 pub trait ToDisplayPoint {
     fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
 }
 
-type TextHighlights = TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
+type TextHighlights = TreeMap<HighlightKey, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
 type InlayHighlights = TreeMap<TypeId, TreeMap<InlayId, (HighlightStyle, InlayHighlight)>>;
 
 /// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints,
@@ -473,12 +479,11 @@ impl DisplayMap {
 
     pub fn highlight_text(
         &mut self,
-        type_id: TypeId,
+        key: HighlightKey,
         ranges: Vec<Range<Anchor>>,
         style: HighlightStyle,
     ) {
-        self.text_highlights
-            .insert(type_id, Arc::new((style, ranges)));
+        self.text_highlights.insert(key, Arc::new((style, ranges)));
     }
 
     pub(crate) fn highlight_inlays(
@@ -501,11 +506,22 @@ impl DisplayMap {
     }
 
     pub fn text_highlights(&self, type_id: TypeId) -> Option<(HighlightStyle, &[Range<Anchor>])> {
-        let highlights = self.text_highlights.get(&type_id)?;
+        let highlights = self.text_highlights.get(&HighlightKey::Type(type_id))?;
         Some((highlights.0, &highlights.1))
     }
+
+    #[cfg(feature = "test-support")]
+    pub fn all_text_highlights(
+        &self,
+    ) -> impl Iterator<Item = &Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
+        self.text_highlights.values()
+    }
+
     pub fn clear_highlights(&mut self, type_id: TypeId) -> bool {
-        let mut cleared = self.text_highlights.remove(&type_id).is_some();
+        let mut cleared = self
+            .text_highlights
+            .remove(&HighlightKey::Type(type_id))
+            .is_some();
         cleared |= self.inlay_highlights.remove(&type_id).is_some();
         cleared
     }
@@ -1333,7 +1349,9 @@ impl DisplaySnapshot {
         &self,
     ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
         let type_id = TypeId::of::<Tag>();
-        self.text_highlights.get(&type_id).cloned()
+        self.text_highlights
+            .get(&HighlightKey::Type(type_id))
+            .cloned()
     }
 
     #[allow(unused)]
@@ -2294,7 +2312,7 @@ pub mod tests {
         // Insert a block in the middle of a multi-line diagnostic.
         map.update(cx, |map, cx| {
             map.highlight_text(
-                TypeId::of::<usize>(),
+                HighlightKey::Type(TypeId::of::<usize>()),
                 vec![
                     buffer_snapshot.anchor_before(Point::new(3, 9))
                         ..buffer_snapshot.anchor_after(Point::new(3, 14)),
@@ -2616,7 +2634,7 @@ pub mod tests {
 
         map.update(cx, |map, _cx| {
             map.highlight_text(
-                TypeId::of::<MyType>(),
+                HighlightKey::Type(TypeId::of::<MyType>()),
                 highlighted_ranges
                     .into_iter()
                     .map(|range| {

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

@@ -1,16 +1,15 @@
 use collections::BTreeMap;
 use gpui::HighlightStyle;
 use language::Chunk;
-use multi_buffer::{Anchor, MultiBufferChunks, MultiBufferSnapshot, ToOffset as _};
+use multi_buffer::{MultiBufferChunks, MultiBufferSnapshot, ToOffset as _};
 use std::{
-    any::TypeId,
     cmp,
     iter::{self, Peekable},
     ops::Range,
-    sync::Arc,
     vec,
 };
-use sum_tree::TreeMap;
+
+use crate::display_map::{HighlightKey, TextHighlights};
 
 pub struct CustomHighlightsChunks<'a> {
     buffer_chunks: MultiBufferChunks<'a>,
@@ -19,15 +18,15 @@ pub struct CustomHighlightsChunks<'a> {
     multibuffer_snapshot: &'a MultiBufferSnapshot,
 
     highlight_endpoints: Peekable<vec::IntoIter<HighlightEndpoint>>,
-    active_highlights: BTreeMap<TypeId, HighlightStyle>,
-    text_highlights: Option<&'a TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>>,
+    active_highlights: BTreeMap<HighlightKey, HighlightStyle>,
+    text_highlights: Option<&'a TextHighlights>,
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
 struct HighlightEndpoint {
     offset: usize,
     is_start: bool,
-    tag: TypeId,
+    tag: HighlightKey,
     style: HighlightStyle,
 }
 
@@ -35,7 +34,7 @@ impl<'a> CustomHighlightsChunks<'a> {
     pub fn new(
         range: Range<usize>,
         language_aware: bool,
-        text_highlights: Option<&'a TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>>,
+        text_highlights: Option<&'a TextHighlights>,
         multibuffer_snapshot: &'a MultiBufferSnapshot,
     ) -> Self {
         Self {
@@ -66,7 +65,7 @@ impl<'a> CustomHighlightsChunks<'a> {
 
 fn create_highlight_endpoints(
     range: &Range<usize>,
-    text_highlights: Option<&TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>>,
+    text_highlights: Option<&TextHighlights>,
     buffer: &MultiBufferSnapshot,
 ) -> iter::Peekable<vec::IntoIter<HighlightEndpoint>> {
     let mut highlight_endpoints = Vec::new();

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

@@ -1115,7 +1115,7 @@ mod tests {
     use super::*;
     use crate::{
         InlayId, MultiBuffer,
-        display_map::{InlayHighlights, TextHighlights},
+        display_map::{HighlightKey, InlayHighlights, TextHighlights},
         hover_links::InlayHighlight,
     };
     use gpui::{App, HighlightStyle};
@@ -1629,7 +1629,7 @@ mod tests {
             text_highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
             log::info!("highlighting text ranges {text_highlight_ranges:?}");
             text_highlights.insert(
-                TypeId::of::<()>(),
+                HighlightKey::Type(TypeId::of::<()>()),
                 Arc::new((
                     HighlightStyle::default(),
                     text_highlight_ranges

crates/editor/src/editor.rs 🔗

@@ -197,7 +197,7 @@ pub use sum_tree::Bias;
 use sum_tree::TreeMap;
 use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
 use theme::{
-    ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
+    ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
     observe_buffer_font_size_adjustment,
 };
 use ui::{
@@ -708,7 +708,7 @@ impl EditorActionId {
 // type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
 // type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
 
-type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
+type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
 type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
 
 #[derive(Default)]
@@ -1017,7 +1017,7 @@ pub struct Editor {
     placeholder_text: Option<Arc<str>>,
     highlight_order: usize,
     highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
-    background_highlights: TreeMap<TypeId, BackgroundHighlight>,
+    background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
     gutter_highlights: TreeMap<TypeId, GutterHighlight>,
     scrollbar_marker_state: ScrollbarMarkerState,
     active_indent_guides_state: ActiveIndentGuidesState,
@@ -6180,7 +6180,7 @@ impl Editor {
             editor.update(cx, |editor, cx| {
                 editor.highlight_background::<Self>(
                     &ranges_to_highlight,
-                    |theme| theme.editor_highlighted_line_background,
+                    |theme| theme.colors().editor_highlighted_line_background,
                     cx,
                 );
             });
@@ -6535,12 +6535,12 @@ impl Editor {
 
                     this.highlight_background::<DocumentHighlightRead>(
                         &read_ranges,
-                        |theme| theme.editor_document_highlight_read_background,
+                        |theme| theme.colors().editor_document_highlight_read_background,
                         cx,
                     );
                     this.highlight_background::<DocumentHighlightWrite>(
                         &write_ranges,
-                        |theme| theme.editor_document_highlight_write_background,
+                        |theme| theme.colors().editor_document_highlight_write_background,
                         cx,
                     );
                     cx.notify();
@@ -6642,7 +6642,7 @@ impl Editor {
                     if !match_ranges.is_empty() {
                         editor.highlight_background::<SelectedTextHighlight>(
                             &match_ranges,
-                            |theme| theme.editor_document_highlight_bracket_background,
+                            |theme| theme.colors().editor_document_highlight_bracket_background,
                             cx,
                         )
                     }
@@ -15397,7 +15397,7 @@ impl Editor {
                     }
                     editor.highlight_background::<Self>(
                         &ranges,
-                        |theme| theme.editor_highlighted_line_background,
+                        |theme| theme.colors().editor_highlighted_line_background,
                         cx,
                     );
                 }
@@ -18552,7 +18552,7 @@ impl Editor {
     pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
         self.highlight_background::<SearchWithinRange>(
             ranges,
-            |colors| colors.editor_document_highlight_read_background,
+            |colors| colors.colors().editor_document_highlight_read_background,
             cx,
         )
     }
@@ -18568,11 +18568,28 @@ impl Editor {
     pub fn highlight_background<T: 'static>(
         &mut self,
         ranges: &[Range<Anchor>],
-        color_fetcher: fn(&ThemeColors) -> Hsla,
+        color_fetcher: fn(&Theme) -> Hsla,
         cx: &mut Context<Self>,
     ) {
-        self.background_highlights
-            .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
+        self.background_highlights.insert(
+            HighlightKey::Type(TypeId::of::<T>()),
+            (color_fetcher, Arc::from(ranges)),
+        );
+        self.scrollbar_marker_state.dirty = true;
+        cx.notify();
+    }
+
+    pub fn highlight_background_key<T: 'static>(
+        &mut self,
+        key: usize,
+        ranges: &[Range<Anchor>],
+        color_fetcher: fn(&Theme) -> Hsla,
+        cx: &mut Context<Self>,
+    ) {
+        self.background_highlights.insert(
+            HighlightKey::TypePlus(TypeId::of::<T>(), key),
+            (color_fetcher, Arc::from(ranges)),
+        );
         self.scrollbar_marker_state.dirty = true;
         cx.notify();
     }
@@ -18581,7 +18598,9 @@ impl Editor {
         &mut self,
         cx: &mut Context<Self>,
     ) -> Option<BackgroundHighlight> {
-        let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
+        let text_highlights = self
+            .background_highlights
+            .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
         if !text_highlights.1.is_empty() {
             self.scrollbar_marker_state.dirty = true;
             cx.notify();
@@ -18667,6 +18686,30 @@ impl Editor {
             .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
     }
 
+    #[cfg(feature = "test-support")]
+    pub fn all_text_highlights(
+        &self,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
+        let snapshot = self.snapshot(window, cx);
+        self.display_map.update(cx, |display_map, _| {
+            display_map
+                .all_text_highlights()
+                .map(|highlight| {
+                    let (style, ranges) = highlight.as_ref();
+                    (
+                        *style,
+                        ranges
+                            .iter()
+                            .map(|range| range.clone().to_display_points(&snapshot))
+                            .collect(),
+                    )
+                })
+                .collect()
+        })
+    }
+
     #[cfg(feature = "test-support")]
     pub fn all_text_background_highlights(
         &self,
@@ -18677,8 +18720,7 @@ impl Editor {
         let buffer = &snapshot.buffer_snapshot;
         let start = buffer.anchor_before(0);
         let end = buffer.anchor_after(buffer.len());
-        let theme = cx.theme().colors();
-        self.background_highlights_in_range(start..end, &snapshot, theme)
+        self.background_highlights_in_range(start..end, &snapshot, cx.theme())
     }
 
     #[cfg(feature = "test-support")]
@@ -18687,7 +18729,9 @@ impl Editor {
 
         let highlights = self
             .background_highlights
-            .get(&TypeId::of::<items::BufferSearchHighlights>());
+            .get(&HighlightKey::Type(TypeId::of::<
+                items::BufferSearchHighlights,
+            >()));
 
         if let Some((_color, ranges)) = highlights {
             ranges
@@ -18706,11 +18750,11 @@ impl Editor {
     ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
         let read_highlights = self
             .background_highlights
-            .get(&TypeId::of::<DocumentHighlightRead>())
+            .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
             .map(|h| &h.1);
         let write_highlights = self
             .background_highlights
-            .get(&TypeId::of::<DocumentHighlightWrite>())
+            .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
             .map(|h| &h.1);
         let left_position = position.bias_left(buffer);
         let right_position = position.bias_right(buffer);
@@ -18737,7 +18781,7 @@ impl Editor {
 
     pub fn has_background_highlights<T: 'static>(&self) -> bool {
         self.background_highlights
-            .get(&TypeId::of::<T>())
+            .get(&HighlightKey::Type(TypeId::of::<T>()))
             .map_or(false, |(_, highlights)| !highlights.is_empty())
     }
 
@@ -18745,7 +18789,7 @@ impl Editor {
         &self,
         search_range: Range<Anchor>,
         display_snapshot: &DisplaySnapshot,
-        theme: &ThemeColors,
+        theme: &Theme,
     ) -> Vec<(Range<DisplayPoint>, Hsla)> {
         let mut results = Vec::new();
         for (color_fetcher, ranges) in self.background_highlights.values() {
@@ -18786,7 +18830,10 @@ impl Editor {
         count: usize,
     ) -> Vec<RangeInclusive<DisplayPoint>> {
         let mut results = Vec::new();
-        let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
+        let Some((_, ranges)) = self
+            .background_highlights
+            .get(&HighlightKey::Type(TypeId::of::<T>()))
+        else {
             return vec![];
         };
 
@@ -18923,6 +18970,23 @@ impl Editor {
             .collect()
     }
 
+    pub fn highlight_text_key<T: 'static>(
+        &mut self,
+        key: usize,
+        ranges: Vec<Range<Anchor>>,
+        style: HighlightStyle,
+        cx: &mut Context<Self>,
+    ) {
+        self.display_map.update(cx, |map, _| {
+            map.highlight_text(
+                HighlightKey::TypePlus(TypeId::of::<T>(), key),
+                ranges,
+                style,
+            );
+        });
+        cx.notify();
+    }
+
     pub fn highlight_text<T: 'static>(
         &mut self,
         ranges: Vec<Range<Anchor>>,
@@ -18930,7 +18994,7 @@ impl Editor {
         cx: &mut Context<Self>,
     ) {
         self.display_map.update(cx, |map, _| {
-            map.highlight_text(TypeId::of::<T>(), ranges, style)
+            map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
         });
         cx.notify();
     }

crates/editor/src/editor_tests.rs 🔗

@@ -13697,7 +13697,7 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) {
         let mut highlighted_ranges = editor.background_highlights_in_range(
             anchor_range(Point::new(3, 4)..Point::new(7, 4)),
             &snapshot,
-            cx.theme().colors(),
+            cx.theme(),
         );
         // Enforce a consistent ordering based on color without relying on the ordering of the
         // highlight's `TypeId` which is non-executor.
@@ -13727,7 +13727,7 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) {
             editor.background_highlights_in_range(
                 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
                 &snapshot,
-                cx.theme().colors(),
+                cx.theme(),
             ),
             &[(
                 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
@@ -20334,7 +20334,7 @@ async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
         let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
         editor.highlight_background::<DocumentHighlightRead>(
             &[highlight_range],
-            |c| c.editor_document_highlight_read_background,
+            |theme| theme.colors().editor_document_highlight_read_background,
             cx,
         );
     });
@@ -20412,7 +20412,7 @@ async fn test_rename_without_prepare(cx: &mut TestAppContext) {
         let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
         editor.highlight_background::<DocumentHighlightRead>(
             &[highlight_range],
-            |c| c.editor_document_highlight_read_background,
+            |theme| theme.colors().editor_document_highlight_read_background,
             cx,
         );
     });

crates/editor/src/element.rs 🔗

@@ -12,8 +12,8 @@ use crate::{
     ToggleFold,
     code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP},
     display_map::{
-        Block, BlockContext, BlockStyle, DisplaySnapshot, EditorMargins, FoldId, HighlightedChunk,
-        ToDisplayPoint,
+        Block, BlockContext, BlockStyle, DisplaySnapshot, EditorMargins, FoldId, HighlightKey,
+        HighlightedChunk, ToDisplayPoint,
     },
     editor_settings::{
         CurrentLineHighlight, DocumentColorsRenderMode, DoubleClickInMultibuffer, MinimapThumb,
@@ -6166,13 +6166,15 @@ impl EditorElement {
                                 background_highlights.iter()
                             {
                                 let is_search_highlights = *background_highlight_id
-                                    == TypeId::of::<BufferSearchHighlights>();
+                                    == HighlightKey::Type(TypeId::of::<BufferSearchHighlights>());
                                 let is_text_highlights = *background_highlight_id
-                                    == TypeId::of::<SelectedTextHighlight>();
+                                    == HighlightKey::Type(TypeId::of::<SelectedTextHighlight>());
                                 let is_symbol_occurrences = *background_highlight_id
-                                    == TypeId::of::<DocumentHighlightRead>()
+                                    == HighlightKey::Type(TypeId::of::<DocumentHighlightRead>())
                                     || *background_highlight_id
-                                        == TypeId::of::<DocumentHighlightWrite>();
+                                        == HighlightKey::Type(
+                                            TypeId::of::<DocumentHighlightWrite>(),
+                                        );
                                 if (is_search_highlights && scrollbar_settings.search_results)
                                     || (is_text_highlights && scrollbar_settings.selected_text)
                                     || (is_symbol_occurrences && scrollbar_settings.selected_symbol)
@@ -8091,7 +8093,7 @@ impl Element for EditorElement {
                             editor.read(cx).background_highlights_in_range(
                                 start_anchor..end_anchor,
                                 &snapshot.display_snapshot,
-                                cx.theme().colors(),
+                                cx.theme(),
                             )
                         })
                         .unwrap_or_default();

crates/editor/src/highlight_matching_bracket.rs 🔗

@@ -40,7 +40,7 @@ pub fn refresh_matching_bracket_highlights(
                 opening_range.to_anchors(&snapshot.buffer_snapshot),
                 closing_range.to_anchors(&snapshot.buffer_snapshot),
             ],
-            |theme| theme.editor_document_highlight_bracket_background,
+            |theme| theme.colors().editor_document_highlight_bracket_background,
             cx,
         )
     }

crates/editor/src/hover_popover.rs 🔗

@@ -520,7 +520,7 @@ fn show_hover(
                     // Highlight the selected symbol using a background highlight
                     editor.highlight_background::<HoverState>(
                         &hover_highlights,
-                        |theme| theme.element_hover, // todo update theme
+                        |theme| theme.colors().element_hover, // todo update theme
                         cx,
                     );
                 }

crates/editor/src/items.rs 🔗

@@ -2,6 +2,7 @@ use crate::{
     Anchor, Autoscroll, Editor, EditorEvent, EditorSettings, ExcerptId, ExcerptRange, FormatTarget,
     MultiBuffer, MultiBufferSnapshot, NavigationData, SearchWithinRange, SelectionEffects,
     ToPoint as _,
+    display_map::HighlightKey,
     editor_settings::SeedQuerySetting,
     persistence::{DB, SerializedEditor},
     scroll::ScrollAnchor,
@@ -1431,7 +1432,7 @@ impl SearchableItem for Editor {
 
     fn get_matches(&self, _window: &mut Window, _: &mut App) -> Vec<Range<Anchor>> {
         self.background_highlights
-            .get(&TypeId::of::<BufferSearchHighlights>())
+            .get(&HighlightKey::Type(TypeId::of::<BufferSearchHighlights>()))
             .map_or(Vec::new(), |(_color, ranges)| {
                 ranges.iter().cloned().collect()
             })
@@ -1454,12 +1455,12 @@ impl SearchableItem for Editor {
     ) {
         let existing_range = self
             .background_highlights
-            .get(&TypeId::of::<BufferSearchHighlights>())
+            .get(&HighlightKey::Type(TypeId::of::<BufferSearchHighlights>()))
             .map(|(_, range)| range.as_ref());
         let updated = existing_range != Some(matches);
         self.highlight_background::<BufferSearchHighlights>(
             matches,
-            |theme| theme.search_match_background,
+            |theme| theme.colors().search_match_background,
             cx,
         );
         if updated {
@@ -1701,7 +1702,7 @@ impl SearchableItem for Editor {
         let buffer = self.buffer().read(cx).snapshot(cx);
         let search_within_ranges = self
             .background_highlights
-            .get(&TypeId::of::<SearchWithinRange>())
+            .get(&HighlightKey::Type(TypeId::of::<SearchWithinRange>()))
             .map_or(vec![], |(_color, ranges)| {
                 ranges.iter().cloned().collect::<Vec<_>>()
             });

crates/editor/src/test/editor_test_context.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
     AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer, RowExt,
-    display_map::ToDisplayPoint,
+    display_map::{HighlightKey, ToDisplayPoint},
 };
 use buffer_diff::DiffHunkStatusKind;
 use collections::BTreeMap;
@@ -509,7 +509,7 @@ impl EditorTestContext {
             let snapshot = editor.snapshot(window, cx);
             editor
                 .background_highlights
-                .get(&TypeId::of::<Tag>())
+                .get(&HighlightKey::Type(TypeId::of::<Tag>()))
                 .map(|h| h.1.clone())
                 .unwrap_or_default()
                 .iter()

crates/language_tools/src/syntax_tree_view.rs 🔗

@@ -358,7 +358,7 @@ impl Render for SyntaxTreeView {
                                                 editor.clear_background_highlights::<Self>( cx);
                                                 editor.highlight_background::<Self>(
                                                     &[range],
-                                                    |theme| theme.editor_document_highlight_write_background,
+                                                    |theme| theme.colors().editor_document_highlight_write_background,
                                                      cx,
                                                 );
                                             });

crates/project/src/debugger/dap_command.rs 🔗

@@ -1547,7 +1547,7 @@ fn dap_client_capabilities(adapter_id: String) -> InitializeRequestArguments {
         supports_memory_event: Some(false),
         supports_args_can_be_interpreted_by_shell: Some(false),
         supports_start_debugging_request: Some(true),
-        supports_ansistyling: Some(false),
+        supports_ansistyling: Some(true),
     }
 }
 

crates/search/src/project_search.rs 🔗

@@ -1377,7 +1377,7 @@ impl ProjectSearchView {
                 }
                 editor.highlight_background::<Self>(
                     &match_ranges,
-                    |theme| theme.search_match_background,
+                    |theme| theme.colors().search_match_background,
                     cx,
                 );
             });

crates/vim/src/normal/yank.rs 🔗

@@ -222,7 +222,7 @@ impl Vim {
 
         editor.highlight_background::<HighlightOnYank>(
             &ranges_to_highlight,
-            |colors| colors.editor_document_highlight_read_background,
+            |colors| colors.colors().editor_document_highlight_read_background,
             cx,
         );
         cx.spawn(async move |this, cx| {

crates/vim/src/replace.rs 🔗

@@ -261,7 +261,7 @@ impl Vim {
             let ranges = [new_range];
             editor.highlight_background::<VimExchange>(
                 &ranges,
-                |theme| theme.editor_document_highlight_read_background,
+                |theme| theme.colors().editor_document_highlight_read_background,
                 cx,
             );
         }