feat: add diff view component

This commit is contained in:
2026-11-06 08:01:23 +07:00
parent f272f80d09
commit 7214c70ea7
11 changed files with 1067 additions and 35 deletions
+187 -1
View File
@@ -41,6 +41,7 @@ use gpui_native_data::sidebar::{Sidebar, SidebarEntry};
use gpui_native_data::tabs::{Tab, Tabs, TabsVariant};
use gpui_native_data::tree::{Tree, TreeItem};
use gpui_native_editor::code_view::CodeViewState;
use gpui_native_editor::diff_view::{DiffRowKind, DiffViewState};
use gpui_native_editor::highlight::Language;
use gpui_native_icons::{IconAssets, IconRole, icon};
use gpui_native_layout::dock::{Dock, DockSidebar, DockState, PanelDescriptor, SplitDirection};
@@ -84,7 +85,10 @@ const CATALOG: &[(&str, &[(&str, &str)])] = &[
("tabs", "Tabs"),
],
),
("Editor", &[("code-view", "Code view")]),
(
"Editor",
&[("code-view", "Code view"), ("diff-view", "Diff view")],
),
(
"Feedback",
&[
@@ -632,6 +636,12 @@ impl Gallery {
}),
cx,
);
dock.register_panel(
PanelDescriptor::new("diff-view", "Diff view", |_, cx| {
cx.new(DiffViewDemo::new).into()
}),
cx,
);
dock.register_panel(
PanelDescriptor::new("sidebar", "Sidebar", |_, cx| {
cx.new(|_| SidebarDemo { selected: None }).into()
@@ -2916,6 +2926,182 @@ impl Render for CodeViewDemo {
}
}
/// The diff view demo's old version.
const DIFF_OLD: &str = r#"// The relay pool keeps every relay warm.
struct RelayPool {
relays: Vec<Relay>,
timeout: u64,
}
impl RelayPool {
fn new(timeout: u64) -> Self {
Self {
relays: Vec::new(),
timeout,
}
}
fn send(&self, message: &str) -> bool {
message.len() < self.timeout as usize
}
}
"#;
/// The diff view demo's new version.
const DIFF_NEW: &str = r#"// The relay pool keeps every relay warm and retrying.
struct RelayPool {
relays: Vec<Relay>,
timeout: u64,
retries: u8,
}
impl RelayPool {
fn new(timeout: u64) -> Self {
Self {
relays: Vec::new(),
timeout,
retries: 3,
}
}
fn send(&self, message: &str) -> bool {
!message.is_empty() && message.len() < self.timeout as usize
}
}
"#;
struct DiffViewDemo {
diff: Entity<DiffViewState>,
scenario: DiffScenario,
large_old: SharedString,
large_new: SharedString,
_subscription: gpui::Subscription,
}
/// The diff view demo's scenario choice.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum DiffScenario {
#[default]
Small,
/// Thousands of rows with changes sprinkled through, to test scroll
/// performance.
Large,
}
/// The line count of each version in the large-diff sample.
const LARGE_DIFF_LINES: usize = 5000;
/// Generates a matched old/new pair of roughly `lines` lines each, with a
/// change every few lines so the diff has spread-out hunks.
fn large_diff_pair(lines: usize) -> (String, String) {
let mut old = String::with_capacity(lines * 48);
let mut new = String::with_capacity(lines * 48);
let mut index = 0usize;
while old.lines().count() < lines {
let i = index;
let comment = format!("// Handler {i} in the generated dispatch table.\n");
old.push_str(&comment);
new.push_str(&comment);
old.push_str(&format!("fn handle_{i}(payload: &str) -> bool {{\n"));
new.push_str(&format!("fn handle_{i}(payload: &Payload) -> bool {{\n"));
old.push_str(&format!(" payload.len() < {i} + 16\n"));
new.push_str(&format!(
" payload.size() < {i} + 16 && !payload.is_empty()\n"
));
if i.is_multiple_of(5) {
old.push_str(&format!(" log::debug(\"handler {i} ran\");\n"));
}
if i.is_multiple_of(7) {
new.push_str(" metrics::count(\"handle\");\n");
}
old.push_str("}\n\n");
new.push_str("}\n\n");
index += 1;
}
(old, new)
}
impl DiffViewDemo {
fn new(cx: &mut Context<Self>) -> Self {
let diff =
cx.new(|_| DiffViewState::new("diff-view-demo", DIFF_OLD, DIFF_NEW, Language::Rust));
// The panel is a cached view in the dock: it re-renders only when
// the diff state it renders is notified.
let subscription = cx.observe(&diff, |_, _, cx| cx.notify());
let (large_old, large_new) = large_diff_pair(LARGE_DIFF_LINES);
Self {
diff,
scenario: DiffScenario::Small,
large_old: large_old.into(),
large_new: large_new.into(),
_subscription: subscription,
}
}
}
impl Render for DiffViewDemo {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let colors = cx.theme().colors;
let radius = cx.theme().radius;
let view = cx.entity();
let rows = self.diff.read(cx).rows();
let added = rows
.iter()
.filter(|row| row.kind == DiffRowKind::Added)
.count();
let removed = rows
.iter()
.filter(|row| row.kind == DiffRowKind::Removed)
.count();
ScrollArea::new("diff-view-demo").size_full().p_4().child(
v_flex()
.gap_3()
.child(demo_section(
"scenario",
colors,
Segmented::new("diff-view-demo-scenario", self.scenario)
.options(vec![(DiffScenario::Small, "Small"), (DiffScenario::Large, "Large file")])
.on_change(move |next, _, cx| {
view.update(cx, |demo, cx| {
demo.scenario = next;
let (old, new) = match next {
DiffScenario::Small => (DIFF_OLD.into(), DIFF_NEW.into()),
DiffScenario::Large => (
demo.large_old.clone(),
demo.large_new.clone(),
),
};
demo.diff.update(cx, |diff, cx| {
diff.set_versions(old, new, Language::Rust, cx)
});
});
}),
))
.child(demo_section(
"unified, highlighted, virtualized",
colors,
div()
.h(px(480.))
.rounded(radius.control)
.border_1()
.border_color(colors.border)
.overflow_hidden()
.child(self.diff.clone()),
))
.child(
div()
.text_xs()
.text_color(colors.muted_foreground)
.child(format!(
"+{added} −{removed} across {} rows — long lines scroll horizontally, scroll to test performance",
rows.len()
)),
),
)
}
}
struct SidebarDemo {
selected: Option<usize>,
}