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:
parent
9a6b9e3124
commit
17872260e6
1 changed files with 142 additions and 103 deletions
|
@ -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),
|
|
||||||
¤t_snapshot.text,
|
|
||||||
¤t_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,
|
||||||
|
¤t_snapshot.text,
|
||||||
|
¤t_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)) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue