Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Diff View

Group: Data

Code

const LEFT: &str = "\
fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

fn main() {
    println!(\"{}\", add(3, 4));
}";

const RIGHT: &str = "\
fn add(a: i32, b: i32) -> i64 {
    (a + b) as i64
}

fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

fn subtract(a: i32, b: i32) -> i32 {
    a - b
}

fn main() {
    println!(\"{}\", add(3, 4));
    println!(\"{}\", subtract(10, 3));
}";

pub struct DiffViewDemo {
    views: Vec<DiffView>,
    last_click: Vec<Option<String>>,
    last_visible_rows: Vec<usize>,
    last_total_rows: Vec<usize>,
}

impl DiffViewDemo {
    pub fn new() -> Self {
        let hunks = quadraui::compute_hunks(LEFT, RIGHT);
        let side_by_side = DiffView {
            id: WidgetId::new("gallery:diff-view:side-by-side"),
            left: LEFT.to_string(),
            right: RIGHT.to_string(),
            left_label: Some("original".to_string()),
            right_label: Some("modified".to_string()),
            hunks: hunks.clone(),
            mode: DiffMode::SideBySide,
            editability: DiffEditability::ReadOnly,
            scroll_offset: 0,
            focused_pane: DiffPane::Left,
            has_focus: true,
        };
        let mut unified = side_by_side.clone();
        unified.id = WidgetId::new("gallery:diff-view:unified");
        unified.mode = DiffMode::Unified;
        Self {
            views: vec![side_by_side, unified],
            last_click: vec![None, None],
            last_visible_rows: vec![24, 24],
            last_total_rows: vec![0, 0],
        }
    }

    fn diff_rect(area: Rect, backend: &dyn Backend) -> Rect {
        let lh = backend.line_height();
        Rect::new(area.x, area.y, area.width, (area.height - lh).max(0.0))
    }

    fn status_rect(area: Rect, backend: &dyn Backend) -> Rect {
        let lh = backend.line_height();
        Rect::new(area.x, area.y + area.height - lh, area.width, lh)
    }

    fn status(&self, variant: usize) -> StatusBar {
        let msg = match &self.last_click[variant] {
            Some(m) => format!(" {m} "),
            None => " click a row — j/k scroll ".into(),
        };
        StatusBar {
            id: WidgetId::new("gallery:diff-view:status"),
            left_segments: vec![StatusBarSegment {
                text: msg,
                fg: Color::rgb(255, 255, 255),
                bg: Color::rgb(40, 80, 120),
                bold: false,
                action_id: None,
            }],
            right_segments: vec![],
        }
    }
}

impl Default for DiffViewDemo {
    fn default() -> Self {
        Self::new()
    }
}

impl Demo for DiffViewDemo {
    fn name(&self) -> &'static str {
        "Diff View"
    }

    fn group(&self) -> &'static str {
        "Data"
    }

    fn variants(&self) -> &'static [&'static str] {
        &["Side by side", "Unified"]
    }

    fn render(&self, variant: usize, backend: &mut dyn Backend, area: Rect) {
        let rect = Self::diff_rect(area, backend);
        let _layout = backend.draw_diff_view(rect, &self.views[variant]);

        let status_rect = Self::status_rect(area, backend);
        let _ = backend.draw_status_bar_interactive(
            status_rect,
            &self.status(variant),
            &InteractionState::new(),
        );
    }

    fn handle(
        &mut self,
        variant: usize,
        event: &UiEvent,
        backend: &mut dyn Backend,
        area: Rect,
    ) -> Reaction {
        let rect = Self::diff_rect(area, backend);
        // Refresh the cached layout extent (visible/total rows) before
        // acting on a scroll key — mirrors `DiffViewApp::last_layout`,
        // but recomputed per-call since `Demo::handle` takes `&mut self`
        // without a cached-layout slot of its own.
        let lh = backend.line_height();
        let geometry = self.views[variant].layout(rect, lh);
        self.last_visible_rows[variant] = geometry.visible_rows.max(1);
        self.last_total_rows[variant] = geometry.total_rows;
        let visible = self.last_visible_rows[variant];
        let total = self.last_total_rows[variant];

        match event {
            UiEvent::MouseDown {
                button: MouseButton::Left,
                position,
                ..
            } => {
                self.last_click[variant] = Some(match geometry.hit_test(position.x, position.y) {
                    DiffViewHit::Row { row_idx, pane } => {
                        let rows = self.views[variant].flat_rows();
                        let kind = rows
                            .get(row_idx)
                            .map(|r| format!("{:?}", r.kind))
                            .unwrap_or_else(|| "?".into());
                        match pane {
                            Some(DiffPane::Left) => {
                                format!("clicked row {row_idx} ({kind}) [left pane]")
                            }
                            Some(DiffPane::Right) => {
                                format!("clicked row {row_idx} ({kind}) [right pane]")
                            }
                            None => format!("clicked row {row_idx} ({kind})"),
                        }
                    }
                    DiffViewHit::UnifiedHeader { hunk_idx } => {
                        format!("clicked hunk header {hunk_idx}")
                    }
                    DiffViewHit::Empty => "clicked empty area".into(),
                });
                Reaction::Redraw
            }
            UiEvent::KeyPressed {
                key: Key::Char('j'),
                ..
            } => {
                if total > visible {
                    self.views[variant].scroll_offset =
                        (self.views[variant].scroll_offset + 1).min(total - visible);
                }
                Reaction::Redraw
            }
            UiEvent::KeyPressed {
                key: Key::Char('k'),
                ..
            } => {
                self.views[variant].scroll_offset =
                    self.views[variant].scroll_offset.saturating_sub(1);
                Reaction::Redraw
            }
            _ => Reaction::Continue,
        }
    }

    fn source(&self) -> &'static str {
        extract_region(SOURCE)
    }

    fn data(&self, variant: usize) -> serde_json::Value {
        serde_json::to_value(&self.views[variant]).unwrap_or(serde_json::Value::Null)
    }

    fn caps_note(&self, _variant: usize, _caps: &BackendCaps) -> Option<String> {
        None
    }
}

Backends

VariantTUIGTKmacOSWindows
Side by side
Unified