chore: audit components

This commit is contained in:
2026-11-10 15:06:16 +07:00
parent af2177c6c8
commit 03369f501e
92 changed files with 2744 additions and 1947 deletions
+243 -144
View File
@@ -3,7 +3,7 @@
//! bars instead of markers, tinted number gutters, word-level intra-line
//! highlights and hunk separators. Rows reuse the code view's highlighting;
//! consumers that already hold a git diff can build [`DiffRow`]s themselves
//! instead of calling [`diff_rows`].
//! instead of calling [`DiffViewState::diff_rows`].
use gpui::prelude::FluentBuilder as _;
use gpui::{
@@ -14,12 +14,12 @@ use gpui::{
};
use gpui_native_core::scrollbar::{Scrollbar, ScrollbarHandle, ScrollbarVisibility};
use gpui_native_theme::{ActiveTheme as _, MONO_ADVANCE_RATIO};
use similar::{ChangeTag, TextDiff};
use similar::{Change, ChangeTag, TextDiff};
use std::ops::Range;
use crate::highlight::{Language, Line, Role, highlight};
use crate::utils::{gutter_cell, line_row, role_color, wash, wash_gutter, wash_strong};
use crate::highlight::{HighlightedCode, Language, Line, Role, highlight};
use crate::utils::{gutter_cell, wash, wash_gutter, wash_strong};
/// What a diff row did to the file.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -96,147 +96,82 @@ impl Default for DiffViewOptions {
}
impl DiffRow {
/// Assembles a diff line row from a `similar` change.
pub(crate) fn from_change(
change: &Change<&str>,
code: &HighlightedCode,
changed_ranges: Vec<Range<usize>>,
) -> DiffRow {
let content = match change.tag() {
ChangeTag::Equal => change
.new_index()
.and_then(|ix| code.lines.get(ix))
.cloned()
.unwrap_or_default(),
ChangeTag::Delete => change
.old_index()
.and_then(|ix| code.lines.get(ix))
.cloned()
.unwrap_or_default(),
ChangeTag::Insert => change
.new_index()
.and_then(|ix| code.lines.get(ix))
.cloned()
.unwrap_or_default(),
};
let (old_number, new_number) = match change.tag() {
ChangeTag::Equal => (
change.old_index().map(|ix| ix + 1),
change.new_index().map(|ix| ix + 1),
),
ChangeTag::Delete => (change.old_index().map(|ix| ix + 1), None),
ChangeTag::Insert => (None, change.new_index().map(|ix| ix + 1)),
};
DiffRow {
kind: match change.tag() {
ChangeTag::Equal => DiffRowKind::Context,
ChangeTag::Delete => DiffRowKind::Removed,
ChangeTag::Insert => DiffRowKind::Added,
},
content,
old_number,
new_number,
changed_ranges,
header: Default::default(),
hidden_lines: 0,
}
}
/// The row's text length, in characters.
fn char_count(&self) -> usize {
self.content
.spans
.iter()
.map(|span| span.text.chars().count())
.sum()
self.content.text().chars().count()
}
}
/// Computes unified rows between two texts, highlighting every line with `language`.
pub fn diff_rows(old: &str, new: &str, language: Language) -> Vec<DiffRow> {
let old_code = highlight(old, language);
let new_code = highlight(new, language);
let diff = TextDiff::from_lines(old, new);
let mut rows = Vec::new();
let mut collapsed_before = 0usize;
for group in diff.grouped_ops(3) {
let first = group.first().expect("hunk groups are never empty");
let last = group.last().expect("hunk groups are never empty");
let old_range = first.old_range().start..last.old_range().end;
let new_range = first.new_range().start..last.new_range().end;
// git prints the line before a pure insertion when the count is zero.
let old_start = if old_range.is_empty() {
old_range.start
} else {
old_range.start + 1
};
let new_start = if new_range.is_empty() {
new_range.start
} else {
new_range.start + 1
};
let collapsed = first.old_range().start - collapsed_before;
if collapsed > 0 {
rows.push(DiffRow {
kind: DiffRowKind::Hunk,
content: Line::default(),
old_number: None,
new_number: None,
changed_ranges: Vec::new(),
header: format!(
"@@ -{},{} +{},{} @@",
old_start,
old_range.len(),
new_start,
new_range.len()
)
.into(),
hidden_lines: collapsed,
});
}
collapsed_before = last.old_range().end;
for op in &group {
let changes: Vec<_> = diff.iter_changes(op).collect();
let mut ix = 0;
while ix < changes.len() {
if changes[ix].tag() == ChangeTag::Equal {
rows.push(line_row(&changes[ix], &new_code, Vec::new()));
ix += 1;
continue;
}
let deletions = changes[ix..]
.iter()
.take_while(|change| change.tag() == ChangeTag::Delete)
.count();
let insertions = changes[ix + deletions..]
.iter()
.take_while(|change| change.tag() == ChangeTag::Insert)
.count();
let mut deletion_ranges = vec![Vec::new(); deletions];
let mut insertion_ranges = vec![Vec::new(); insertions];
for pair in 0..deletions.min(insertions) {
let old_text = changes[ix + pair].value().trim_end_matches('\n');
let new_text = changes[ix + deletions + pair]
.value()
.trim_end_matches('\n');
let words = TextDiff::from_words(old_text, new_text);
let (mut old_offset, mut new_offset) = (0usize, 0usize);
for word in words.iter_all_changes() {
let len = word.value().len();
match word.tag() {
ChangeTag::Delete => {
deletion_ranges[pair].push(old_offset..old_offset + len);
old_offset += len;
}
ChangeTag::Insert => {
insertion_ranges[pair].push(new_offset..new_offset + len);
new_offset += len;
}
ChangeTag::Equal => {
old_offset += len;
new_offset += len;
}
}
}
}
for pair in 0..deletions {
rows.push(line_row(
&changes[ix + pair],
&old_code,
deletion_ranges[pair].clone(),
));
}
for pair in 0..insertions {
rows.push(line_row(
&changes[ix + deletions + pair],
&new_code,
insertion_ranges[pair].clone(),
));
}
ix += deletions + insertions;
/// Stable element id: the kind and line numbers packed into one integer.
fn element_id(&self, hunk_ordinal: usize) -> ElementId {
let kind = match self.kind {
DiffRowKind::Context => 0u64,
DiffRowKind::Added => 1,
DiffRowKind::Removed => 2,
DiffRowKind::Hunk => {
return ElementId::named_usize("diff-hunk", hunk_ordinal);
}
}
};
// Line numbers saturate at 2^31 so the two fields never overlap.
let old = self.old_number.unwrap_or(0).min((1 << 31) - 1) as u64;
let new = self.new_number.unwrap_or(0).min((1 << 31) - 1) as u64;
ElementId::named_usize("diff-row", ((kind << 62) | (old << 31) | new) as usize)
}
rows
}
/// Retained state for a diff view.
pub struct DiffViewState {
id: ElementId,
rows: Vec<DiffRow>,
/// Cached per-row id and text length, parallel to `rows`.
row_layouts: Vec<RowLayout>,
/// The row whose width drives horizontal scroll extent, `rows`-derived.
widest_row: Option<usize>,
options: DiffViewOptions,
@@ -245,6 +180,12 @@ pub struct DiffViewState {
a11y_label: Option<SharedString>,
}
/// Cached per-row render data, computed once when the rows are set.
struct RowLayout {
id: ElementId,
char_count: usize,
}
impl DiffViewState {
/// Builds the state, diffing `old` against `new`.
pub fn new<I, O, N>(id: I, old: O, new: N, language: Language) -> Self
@@ -256,6 +197,7 @@ impl DiffViewState {
let mut state = Self {
id: id.into(),
rows: Vec::new(),
row_layouts: Vec::new(),
widest_row: None,
options: DiffViewOptions::default(),
handle: UniformListScrollHandle::new(),
@@ -265,20 +207,168 @@ impl DiffViewState {
state
}
/// Replaces the rows, recomputing `widest_row` with them.
/// Replaces the rows, recomputing the per-row layout cache with them.
fn set_rows<O, N>(&mut self, old: O, new: N, language: Language)
where
O: AsRef<str>,
N: AsRef<str>,
{
self.rows = diff_rows(old.as_ref(), new.as_ref(), language);
self.rows = Self::diff_rows(old.as_ref(), new.as_ref(), language);
let mut hunk_ordinal = 0usize;
self.row_layouts = self
.rows
.iter()
.map(|row| {
let ordinal = if row.kind == DiffRowKind::Hunk {
hunk_ordinal += 1;
hunk_ordinal
} else {
0
};
RowLayout {
id: row.element_id(ordinal),
char_count: row.char_count(),
}
})
.collect();
self.widest_row = self
.rows
.iter()
.zip(&self.row_layouts)
.enumerate()
.filter(|(_, row)| row.kind != DiffRowKind::Hunk)
.max_by_key(|(_, row)| row.char_count())
.map(|(ix, _)| ix);
.filter(|(_, (row, _))| row.kind != DiffRowKind::Hunk)
.max_by_key(|(_, (_, layout))| layout.char_count)
.map(|(widest, _)| widest);
}
/// Computes unified rows between two texts, highlighting every line with `language`.
pub fn diff_rows(old: &str, new: &str, language: Language) -> Vec<DiffRow> {
let old_code = highlight(old, language);
let new_code = highlight(new, language);
let diff = TextDiff::from_lines(old, new);
let mut rows = Vec::new();
let mut collapsed_before = 0usize;
for group in diff.grouped_ops(3) {
let (Some(first), Some(last)) = (group.first(), group.last()) else {
continue;
};
let old_range = first.old_range().start..last.old_range().end;
let new_range = first.new_range().start..last.new_range().end;
// git prints the line before a pure insertion when the count is zero.
let old_start = if old_range.is_empty() {
old_range.start
} else {
old_range.start + 1
};
let new_start = if new_range.is_empty() {
new_range.start
} else {
new_range.start + 1
};
let collapsed = first.old_range().start.saturating_sub(collapsed_before);
if collapsed > 0 {
rows.push(DiffRow {
kind: DiffRowKind::Hunk,
content: Line::default(),
old_number: None,
new_number: None,
changed_ranges: Vec::new(),
header: format!(
"@@ -{},{} +{},{} @@",
old_start,
old_range.len(),
new_start,
new_range.len()
)
.into(),
hidden_lines: collapsed,
});
}
collapsed_before = last.old_range().end;
for op in &group {
let changes: Vec<_> = diff.iter_changes(op).collect();
let mut index = 0;
while index < changes.len() {
if changes[index].tag() == ChangeTag::Equal {
rows.push(DiffRow::from_change(&changes[index], &new_code, Vec::new()));
index += 1;
continue;
}
let deletions = changes[index..]
.iter()
.take_while(|change| change.tag() == ChangeTag::Delete)
.count();
let insertions = changes[index + deletions..]
.iter()
.take_while(|change| change.tag() == ChangeTag::Insert)
.count();
let mut deletion_ranges = vec![Vec::new(); deletions];
let mut insertion_ranges = vec![Vec::new(); insertions];
for pair in 0..deletions.min(insertions) {
let old_text = changes[index + pair].value().trim_end_matches('\n');
let new_text = changes[index + deletions + pair]
.value()
.trim_end_matches('\n');
let words = TextDiff::from_words(old_text, new_text);
let (mut old_offset, mut new_offset) = (0usize, 0usize);
for word in words.iter_all_changes() {
let len = word.value().len();
match word.tag() {
ChangeTag::Delete => {
deletion_ranges[pair].push(old_offset..old_offset + len);
old_offset += len;
}
ChangeTag::Insert => {
insertion_ranges[pair].push(new_offset..new_offset + len);
new_offset += len;
}
ChangeTag::Equal => {
old_offset += len;
new_offset += len;
}
}
}
}
for pair in 0..deletions {
rows.push(DiffRow::from_change(
&changes[index + pair],
&old_code,
deletion_ranges[pair].clone(),
));
}
for pair in 0..insertions {
rows.push(DiffRow::from_change(
&changes[index + deletions + pair],
&new_code,
insertion_ranges[pair].clone(),
));
}
index += deletions + insertions;
}
}
}
rows
}
/// Sets how changed rows are marked.
@@ -355,7 +445,15 @@ impl Render for DiffViewState {
move |range, _window, cx| {
let diff = state.read(cx);
range
.map(|row_ix| render_row(&diff.rows[row_ix], row_ix, options, cx))
.map(|row_index| {
render_row(
&diff.rows[row_index],
&diff.row_layouts[row_index],
row_index,
options,
cx,
)
})
.collect()
}
})
@@ -389,7 +487,8 @@ impl Render for DiffViewState {
fn render_row(
row: &DiffRow,
row_ix: usize,
layout: &RowLayout,
row_index: usize,
options: DiffViewOptions,
cx: &App,
) -> gpui::AnyElement {
@@ -402,8 +501,8 @@ fn render_row(
let char_width = text_size * MONO_ADVANCE_RATIO;
let frame = div()
.debug_selector(move || format!("diff-row-{row_ix}"))
.id(("diff-row", row_ix))
.debug_selector(move || format!("diff-row-{row_index}"))
.id(layout.id.clone())
.w_full()
.h(line_height)
.font_family(mono_font)
@@ -496,7 +595,7 @@ fn render_row(
&& *role != Role::Text
{
segment = segment.highlight(HighlightStyle {
color: Some(role_color(*role, colors, ramps)),
color: Some(role.color(colors, ramps)),
..Default::default()
});
}
@@ -582,7 +681,7 @@ fn render_row(
div()
.flex_none()
.ml_2()
.w(char_width * row.char_count() as f32)
.w(char_width * layout.char_count as f32)
.whitespace_nowrap()
.child(gpui::StyledText::new(text).with_default_highlights(&style, highlights)),
)