feat: add diff view component
This commit is contained in:
@@ -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>,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user