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

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

Release Notes:

- N/A
This commit is contained in:
Cole Miller 2025-06-17 23:39:31 -04:00 committed by GitHub
parent 4da58188fb
commit bfffc293a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 606 additions and 80 deletions

View file

@ -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| {

View file

@ -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();

View file

@ -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

View file

@ -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();
}

View file

@ -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,
);
});

View file

@ -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();

View file

@ -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,
)
}

View file

@ -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,
);
}

View file

@ -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<_>>()
});

View file

@ -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()