ZIm/crates/editor/src/display_map/custom_highlights.rs
Cole Miller ffc6218349
debugger: Process ANSI color escape codes in console (#32817)
- [x] foreground highlights
- [x] background highlights
- [x] advertise support in DAP capabilities

Closes #31372

Release Notes:

- Debugger Beta: added basic support for highlighting in the console
based on ANSI escape codes.
2025-06-16 17:39:53 -04:00

171 lines
5.3 KiB
Rust

use collections::BTreeMap;
use gpui::HighlightStyle;
use language::Chunk;
use multi_buffer::{MultiBufferChunks, MultiBufferSnapshot, ToOffset as _};
use std::{
any::TypeId,
cmp,
iter::{self, Peekable},
ops::Range,
vec,
};
use crate::display_map::TextHighlights;
pub struct CustomHighlightsChunks<'a> {
buffer_chunks: MultiBufferChunks<'a>,
buffer_chunk: Option<Chunk<'a>>,
offset: usize,
multibuffer_snapshot: &'a MultiBufferSnapshot,
highlight_endpoints: Peekable<vec::IntoIter<HighlightEndpoint>>,
active_highlights: BTreeMap<(TypeId, usize), HighlightStyle>,
text_highlights: Option<&'a TextHighlights>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
struct HighlightEndpoint {
offset: usize,
is_start: bool,
tag: (TypeId, usize),
style: HighlightStyle,
}
impl<'a> CustomHighlightsChunks<'a> {
pub fn new(
range: Range<usize>,
language_aware: bool,
text_highlights: Option<&'a TextHighlights>,
multibuffer_snapshot: &'a MultiBufferSnapshot,
) -> Self {
Self {
buffer_chunks: multibuffer_snapshot.chunks(range.clone(), language_aware),
buffer_chunk: None,
offset: range.start,
text_highlights,
highlight_endpoints: create_highlight_endpoints(
&range,
text_highlights,
multibuffer_snapshot,
),
active_highlights: Default::default(),
multibuffer_snapshot,
}
}
pub fn seek(&mut self, new_range: Range<usize>) {
self.highlight_endpoints =
create_highlight_endpoints(&new_range, self.text_highlights, self.multibuffer_snapshot);
self.offset = new_range.start;
self.buffer_chunks.seek(new_range);
self.buffer_chunk.take();
self.active_highlights.clear()
}
}
fn create_highlight_endpoints(
range: &Range<usize>,
text_highlights: Option<&TextHighlights>,
buffer: &MultiBufferSnapshot,
) -> iter::Peekable<vec::IntoIter<HighlightEndpoint>> {
let mut highlight_endpoints = Vec::new();
if let Some(text_highlights) = text_highlights {
let start = buffer.anchor_after(range.start);
let end = buffer.anchor_after(range.end);
for (&tag, text_highlights) in text_highlights.iter() {
let start_ix = match text_highlights.binary_search_by(|(probe, _)| {
let cmp = probe.end.cmp(&start, &buffer);
if cmp.is_gt() {
cmp::Ordering::Greater
} else {
cmp::Ordering::Less
}
}) {
Ok(i) | Err(i) => i,
};
for (ix, (range, style)) in text_highlights[start_ix..].iter().enumerate() {
if range.start.cmp(&end, &buffer).is_ge() {
break;
}
highlight_endpoints.push(HighlightEndpoint {
offset: range.start.to_offset(&buffer),
is_start: true,
tag: (tag, ix),
style: *style,
});
highlight_endpoints.push(HighlightEndpoint {
offset: range.end.to_offset(&buffer),
is_start: false,
tag: (tag, ix),
style: *style,
});
}
}
highlight_endpoints.sort();
}
highlight_endpoints.into_iter().peekable()
}
impl<'a> Iterator for CustomHighlightsChunks<'a> {
type Item = Chunk<'a>;
fn next(&mut self) -> Option<Self::Item> {
let mut next_highlight_endpoint = usize::MAX;
while let Some(endpoint) = self.highlight_endpoints.peek().copied() {
if endpoint.offset <= self.offset {
if endpoint.is_start {
self.active_highlights.insert(endpoint.tag, endpoint.style);
} else {
self.active_highlights.remove(&endpoint.tag);
}
self.highlight_endpoints.next();
} else {
next_highlight_endpoint = endpoint.offset;
break;
}
}
let chunk = self
.buffer_chunk
.get_or_insert_with(|| self.buffer_chunks.next().unwrap());
if chunk.text.is_empty() {
*chunk = self.buffer_chunks.next().unwrap();
}
let (prefix, suffix) = chunk
.text
.split_at(chunk.text.len().min(next_highlight_endpoint - self.offset));
chunk.text = suffix;
self.offset += prefix.len();
let mut prefix = Chunk {
text: prefix,
..chunk.clone()
};
if !self.active_highlights.is_empty() {
let mut highlight_style = HighlightStyle::default();
for active_highlight in self.active_highlights.values() {
highlight_style.highlight(*active_highlight);
}
prefix.highlight_style = Some(highlight_style);
}
Some(prefix)
}
}
impl PartialOrd for HighlightEndpoint {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for HighlightEndpoint {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.offset
.cmp(&other.offset)
.then_with(|| other.is_start.cmp(&self.is_start))
}
}