Add language::BufferSnapshot::highlighted_text_for_range (#24060)

In support of work on

https://github.com/zed-industries/zed/tree/new-ui-for-edit-prediction-with-lsp-completions,
where we want to be able to extract a range of the buffer as
`HighlightedText`.

Release Notes:

- N/A
This commit is contained in:
Michael Sloan 2025-01-31 16:36:24 -07:00 committed by GitHub
parent 9a6b9e3124
commit 17872260e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -588,134 +588,70 @@ pub struct Runnable {
pub buffer: BufferId, pub buffer: BufferId,
} }
#[derive(Clone)]
pub struct EditPreview {
old_snapshot: text::BufferSnapshot,
applied_edits_snapshot: text::BufferSnapshot,
syntax_snapshot: SyntaxSnapshot,
}
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
pub struct HighlightedText { pub struct HighlightedText {
pub text: SharedString, pub text: SharedString,
pub highlights: Vec<(Range<usize>, HighlightStyle)>, pub highlights: Vec<(Range<usize>, HighlightStyle)>,
} }
impl EditPreview { #[derive(Default, Debug)]
pub fn highlight_edits( struct HighlightedTextBuilder {
&self, pub text: String,
current_snapshot: &BufferSnapshot, pub highlights: Vec<(Range<usize>, HighlightStyle)>,
edits: &[(Range<Anchor>, String)], }
include_deletions: bool,
cx: &App,
) -> HighlightedText {
let Some(visible_range_in_preview_snapshot) = self.compute_visible_range(edits) else {
return HighlightedText::default();
};
let mut text = String::new(); impl HighlightedText {
let mut highlights = Vec::new(); pub fn from_buffer_range<T: ToOffset>(
range: Range<T>,
let mut offset_in_preview_snapshot = visible_range_in_preview_snapshot.start; snapshot: &text::BufferSnapshot,
syntax_snapshot: &SyntaxSnapshot,
let insertion_highlight_style = HighlightStyle { override_style: Option<HighlightStyle>,
background_color: Some(cx.theme().status().created_background), syntax_theme: &SyntaxTheme,
..Default::default() ) -> Self {
}; let mut highlighted_text = HighlightedTextBuilder::default();
let deletion_highlight_style = HighlightStyle { highlighted_text.add_text_from_buffer_range(
background_color: Some(cx.theme().status().deleted_background), range,
..Default::default() snapshot,
}; syntax_snapshot,
override_style,
for (range, edit_text) in edits { syntax_theme,
let edit_new_end_in_preview_snapshot = range
.end
.bias_right(&self.old_snapshot)
.to_offset(&self.applied_edits_snapshot);
let edit_start_in_preview_snapshot = edit_new_end_in_preview_snapshot - edit_text.len();
let unchanged_range_in_preview_snapshot =
offset_in_preview_snapshot..edit_start_in_preview_snapshot;
if !unchanged_range_in_preview_snapshot.is_empty() {
Self::highlight_text(
unchanged_range_in_preview_snapshot.clone(),
&mut text,
&mut highlights,
None,
&self.applied_edits_snapshot,
&self.syntax_snapshot,
cx,
);
}
let range_in_current_snapshot = range.to_offset(current_snapshot);
if include_deletions && !range_in_current_snapshot.is_empty() {
Self::highlight_text(
range_in_current_snapshot.clone(),
&mut text,
&mut highlights,
Some(deletion_highlight_style),
&current_snapshot.text,
&current_snapshot.syntax,
cx,
);
}
if !edit_text.is_empty() {
Self::highlight_text(
edit_start_in_preview_snapshot..edit_new_end_in_preview_snapshot,
&mut text,
&mut highlights,
Some(insertion_highlight_style),
&self.applied_edits_snapshot,
&self.syntax_snapshot,
cx,
);
}
offset_in_preview_snapshot = edit_new_end_in_preview_snapshot;
}
Self::highlight_text(
offset_in_preview_snapshot..visible_range_in_preview_snapshot.end,
&mut text,
&mut highlights,
None,
&self.applied_edits_snapshot,
&self.syntax_snapshot,
cx,
); );
highlighted_text.build()
}
}
impl HighlightedTextBuilder {
pub fn build(self) -> HighlightedText {
HighlightedText { HighlightedText {
text: text.into(), text: self.text.into(),
highlights, highlights: self.highlights,
} }
} }
fn highlight_text( pub fn add_text_from_buffer_range<T: ToOffset>(
range: Range<usize>, &mut self,
text: &mut String, range: Range<T>,
highlights: &mut Vec<(Range<usize>, HighlightStyle)>,
override_style: Option<HighlightStyle>,
snapshot: &text::BufferSnapshot, snapshot: &text::BufferSnapshot,
syntax_snapshot: &SyntaxSnapshot, syntax_snapshot: &SyntaxSnapshot,
cx: &App, override_style: Option<HighlightStyle>,
syntax_theme: &SyntaxTheme,
) { ) {
let range = range.to_offset(snapshot);
for chunk in Self::highlighted_chunks(range, snapshot, syntax_snapshot) { for chunk in Self::highlighted_chunks(range, snapshot, syntax_snapshot) {
let start = text.len(); let start = self.text.len();
text.push_str(chunk.text); self.text.push_str(chunk.text);
let end = text.len(); let end = self.text.len();
if let Some(mut highlight_style) = chunk if let Some(mut highlight_style) = chunk
.syntax_highlight_id .syntax_highlight_id
.and_then(|id| id.style(cx.theme().syntax())) .and_then(|id| id.style(syntax_theme))
{ {
if let Some(override_style) = override_style { if let Some(override_style) = override_style {
highlight_style.highlight(override_style); highlight_style.highlight(override_style);
} }
highlights.push((start..end, highlight_style)); self.highlights.push((start..end, highlight_style));
} else if let Some(override_style) = override_style { } else if let Some(override_style) = override_style {
highlights.push((start..end, override_style)); self.highlights.push((start..end, override_style));
} }
} }
} }
@ -743,6 +679,94 @@ impl EditPreview {
None, None,
) )
} }
}
#[derive(Clone)]
pub struct EditPreview {
old_snapshot: text::BufferSnapshot,
applied_edits_snapshot: text::BufferSnapshot,
syntax_snapshot: SyntaxSnapshot,
}
impl EditPreview {
pub fn highlight_edits(
&self,
current_snapshot: &BufferSnapshot,
edits: &[(Range<Anchor>, String)],
include_deletions: bool,
cx: &App,
) -> HighlightedText {
let Some(visible_range_in_preview_snapshot) = self.compute_visible_range(edits) else {
return HighlightedText::default();
};
let mut highlighted_text = HighlightedTextBuilder::default();
let mut offset_in_preview_snapshot = visible_range_in_preview_snapshot.start;
let insertion_highlight_style = HighlightStyle {
background_color: Some(cx.theme().status().created_background),
..Default::default()
};
let deletion_highlight_style = HighlightStyle {
background_color: Some(cx.theme().status().deleted_background),
..Default::default()
};
let syntax_theme = cx.theme().syntax();
for (range, edit_text) in edits {
let edit_new_end_in_preview_snapshot = range
.end
.bias_right(&self.old_snapshot)
.to_offset(&self.applied_edits_snapshot);
let edit_start_in_preview_snapshot = edit_new_end_in_preview_snapshot - edit_text.len();
let unchanged_range_in_preview_snapshot =
offset_in_preview_snapshot..edit_start_in_preview_snapshot;
if !unchanged_range_in_preview_snapshot.is_empty() {
highlighted_text.add_text_from_buffer_range(
unchanged_range_in_preview_snapshot,
&self.applied_edits_snapshot,
&self.syntax_snapshot,
None,
&syntax_theme,
);
}
let range_in_current_snapshot = range.to_offset(current_snapshot);
if include_deletions && !range_in_current_snapshot.is_empty() {
highlighted_text.add_text_from_buffer_range(
range_in_current_snapshot,
&current_snapshot.text,
&current_snapshot.syntax,
Some(deletion_highlight_style),
&syntax_theme,
);
}
if !edit_text.is_empty() {
highlighted_text.add_text_from_buffer_range(
edit_start_in_preview_snapshot..edit_new_end_in_preview_snapshot,
&self.applied_edits_snapshot,
&self.syntax_snapshot,
Some(insertion_highlight_style),
&syntax_theme,
);
}
offset_in_preview_snapshot = edit_new_end_in_preview_snapshot;
}
highlighted_text.add_text_from_buffer_range(
offset_in_preview_snapshot..visible_range_in_preview_snapshot.end,
&self.applied_edits_snapshot,
&self.syntax_snapshot,
None,
&syntax_theme,
);
highlighted_text.build()
}
fn compute_visible_range(&self, edits: &[(Range<Anchor>, String)]) -> Option<Range<usize>> { fn compute_visible_range(&self, edits: &[(Range<Anchor>, String)]) -> Option<Range<usize>> {
let (first, _) = edits.first()?; let (first, _) = edits.first()?;
@ -2982,6 +3006,21 @@ impl BufferSnapshot {
BufferChunks::new(self.text.as_rope(), range, syntax, diagnostics, Some(self)) BufferChunks::new(self.text.as_rope(), range, syntax, diagnostics, Some(self))
} }
pub fn highlighted_text_for_range<T: ToOffset>(
&self,
range: Range<T>,
override_style: Option<HighlightStyle>,
syntax_theme: &SyntaxTheme,
) -> HighlightedText {
HighlightedText::from_buffer_range(
range,
&self.text,
&self.syntax,
override_style,
syntax_theme,
)
}
/// Invokes the given callback for each line of text in the given range of the buffer. /// Invokes the given callback for each line of text in the given range of the buffer.
/// Uses callback to avoid allocating a string for each line. /// Uses callback to avoid allocating a string for each line.
fn for_each_line(&self, range: Range<Point>, mut callback: impl FnMut(u32, &str)) { fn for_each_line(&self, range: Range<Point>, mut callback: impl FnMut(u32, &str)) {