From 244b4ae14a260ccbbf1daf7361bc14ed0a164f19 Mon Sep 17 00:00:00 2001 From: LagoLunatic Date: Tue, 30 Dec 2025 20:01:48 -0500 Subject: [PATCH] Implement go to next/previous difference --- objdiff-gui/src/hotkeys.rs | 16 ++++- objdiff-gui/src/views/diff.rs | 96 ++++++++++++++++++++++++-- objdiff-gui/src/views/function_diff.rs | 1 - objdiff-gui/src/views/symbol_diff.rs | 5 +- 4 files changed, 108 insertions(+), 10 deletions(-) diff --git a/objdiff-gui/src/hotkeys.rs b/objdiff-gui/src/hotkeys.rs index 80d8d5dc..1fab90ad 100644 --- a/objdiff-gui/src/hotkeys.rs +++ b/objdiff-gui/src/hotkeys.rs @@ -50,9 +50,9 @@ pub fn end_pressed(ctx: &Context) -> bool { ctx.input_mut(|i| i.key_pressed(Key: pub fn check_scroll_hotkeys(ui: &mut egui::Ui, include_small_increments: bool) { let ui_height = ui.available_rect_before_wrap().height(); - if up_pressed(ui.ctx()) && include_small_increments { + if include_small_increments && consume_up_key(ui.ctx()) { ui.scroll_with_delta_animation(vec2(0.0, ui_height / 10.0), ScrollAnimation::none()); - } else if down_pressed(ui.ctx()) && include_small_increments { + } else if include_small_increments && consume_down_key(ui.ctx()) { ui.scroll_with_delta_animation(vec2(0.0, -ui_height / 10.0), ScrollAnimation::none()); } else if page_up_pressed(ui.ctx()) { ui.scroll_with_delta_animation(vec2(0.0, ui_height), ScrollAnimation::none()); @@ -106,3 +106,15 @@ const CHANGE_BASE_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers:: pub fn consume_change_base_shortcut(ctx: &Context) -> bool { ctx.input_mut(|i| i.consume_shortcut(&CHANGE_BASE_SHORTCUT)) } + +const PREV_DIFF_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::ArrowUp); + +pub fn consume_prev_diff_shortcut(ctx: &Context) -> bool { + ctx.input_mut(|i| i.consume_shortcut(&PREV_DIFF_SHORTCUT)) +} + +const NEXT_DIFF_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::ArrowDown); + +pub fn consume_next_diff_shortcut(ctx: &Context) -> bool { + ctx.input_mut(|i| i.consume_shortcut(&NEXT_DIFF_SHORTCUT)) +} diff --git a/objdiff-gui/src/views/diff.rs b/objdiff-gui/src/views/diff.rs index f7c4a678..366da051 100644 --- a/objdiff-gui/src/views/diff.rs +++ b/objdiff-gui/src/views/diff.rs @@ -4,7 +4,7 @@ use egui::{Id, Layout, RichText, ScrollArea, TextEdit, Ui, Widget, text::LayoutJ use objdiff_core::{ build::BuildStatus, diff::{ - DiffObjConfig, ObjectDiff, SymbolDiff, + DataDiffKind, DiffObjConfig, InstructionDiffKind, ObjectDiff, SymbolDiff, data::BYTES_PER_ROW, display::{ ContextItem, DiffText, HoverItem, HoverItemColor, SymbolFilter, SymbolNavigationKind, @@ -185,6 +185,9 @@ pub fn diff_view_ui( let available_width = ui.available_width(); let mut open_sections = (None, None); + let mut scroll_to_prev_diff = false; + let mut scroll_to_next_diff = false; + render_header(ui, available_width, 2, |ui, column| { if column == 0 { // Left column @@ -450,6 +453,31 @@ pub fn diff_view_ui( { ret = Some(DiffViewAction::SelectingRight(symbol_ref.clone())); } + needs_separator = true; + } + + if state.current_view == View::FunctionDiff + || state.current_view == View::DataDiff + { + if needs_separator { + ui.separator(); + } + if ui + .button("⏴ Prev diff") + .on_hover_text_at_pointer("Scroll to the previous difference (Ctrl+Up)") + .clicked() + || hotkeys::consume_prev_diff_shortcut(ui.ctx()) + { + scroll_to_prev_diff = true; + } + if ui + .button("Next diff ⏵") + .on_hover_text_at_pointer("Scroll to the next difference (Ctrl+Down)") + .clicked() + || hotkeys::consume_next_diff_shortcut(ui.ctx()) + { + scroll_to_next_diff = true; + } } } else if right_ctx.status.success && !right_ctx.has_symbol() { let mut search = state.search.clone(); @@ -493,14 +521,21 @@ pub fn diff_view_ui( return; } let instructions_len = left_symbol_diff.instruction_rows.len(); + let mut min_row = None; + let mut max_row = None; render_table( ui, available_width, 2, appearance.code_font.size, instructions_len, - state.function_state.scroll_to_row, + state.scroll_to_diff_row, |row, column| { + if min_row.is_none() { + min_row = Some(row.index()); + } + max_row = Some(row.index()); + if column == 0 { if let Some(action) = asm_col_ui( row, @@ -537,6 +572,27 @@ pub fn diff_view_ui( } }, ); + + if scroll_to_prev_diff && let Some(min_row) = min_row { + for (ins_idx, ins_diff) in + right_diff.symbols[right_symbol_idx].instruction_rows.iter().enumerate().rev() + { + if ins_idx <= min_row && ins_diff.kind != InstructionDiffKind::None { + ret = Some(DiffViewAction::ScrollToRow(ins_idx)); + break; + } + } + } + if scroll_to_next_diff && let Some(max_row) = max_row { + for (ins_idx, ins_diff) in + right_diff.symbols[right_symbol_idx].instruction_rows.iter().enumerate() + { + if ins_idx >= max_row - 1 && ins_diff.kind != InstructionDiffKind::None { + ret = Some(DiffViewAction::ScrollToRow(ins_idx)); + break; + } + } + } } else if let ( View::DataDiff, Some((left_obj, _left_diff)), @@ -556,14 +612,21 @@ pub fn diff_view_ui( if total_rows == 0 { return; } + let mut min_row = None; + let mut max_row = None; render_table( ui, available_width, 2, appearance.code_font.size, total_rows, - None, + state.scroll_to_diff_row, |row, column| { + if min_row.is_none() { + min_row = Some(row.index()); + } + max_row = Some(row.index()); + let i = row.index(); let row_offset = i as u64 * BYTES_PER_ROW as u64; row.col(|ui| { @@ -591,6 +654,29 @@ pub fn diff_view_ui( }); }, ); + + if scroll_to_prev_diff && let Some(min_row) = min_row { + for (row_idx, diff_row) in right_symbol_diff.data_rows.iter().enumerate().rev() { + if row_idx <= min_row + && (diff_row.segments.iter().any(|dd| dd.kind != DataDiffKind::None) + || diff_row.relocations.iter().any(|rd| rd.kind != DataDiffKind::None)) + { + ret = Some(DiffViewAction::ScrollToRow(row_idx)); + break; + } + } + } + if scroll_to_next_diff && let Some(max_row) = max_row { + for (row_idx, diff_row) in right_symbol_diff.data_rows.iter().enumerate() { + if row_idx >= max_row - 1 + && (diff_row.segments.iter().any(|dd| dd.kind != DataDiffKind::None) + || diff_row.relocations.iter().any(|rd| rd.kind != DataDiffKind::None)) + { + ret = Some(DiffViewAction::ScrollToRow(row_idx)); + break; + } + } + } } else { // Split view render_strips(ui, available_width, 2, |ui, column| { @@ -693,7 +779,7 @@ fn diff_col_ui( 1, appearance.code_font.size, total_rows, - None, + state.scroll_to_diff_row, |row, _column| { let i = row.index(); let row_offset = i as u64 * BYTES_PER_ROW as u64; @@ -717,7 +803,7 @@ fn diff_col_ui( 1, appearance.code_font.size, symbol_diff.instruction_rows.len(), - state.function_state.scroll_to_row, + state.scroll_to_diff_row, |row, column| { if let Some(action) = asm_col_ui( row, diff --git a/objdiff-gui/src/views/function_diff.rs b/objdiff-gui/src/views/function_diff.rs index 2ee8a147..eba7f181 100644 --- a/objdiff-gui/src/views/function_diff.rs +++ b/objdiff-gui/src/views/function_diff.rs @@ -24,7 +24,6 @@ use crate::views::{ pub struct FunctionViewState { left_highlight: HighlightKind, right_highlight: HighlightKind, - pub scroll_to_row: Option, } impl FunctionViewState { diff --git a/objdiff-gui/src/views/symbol_diff.rs b/objdiff-gui/src/views/symbol_diff.rs index bb40f21b..3daf9f40 100644 --- a/objdiff-gui/src/views/symbol_diff.rs +++ b/objdiff-gui/src/views/symbol_diff.rs @@ -118,6 +118,7 @@ pub struct DiffViewState { pub function_state: FunctionViewState, pub search: String, pub search_regex: Option, + pub scroll_to_diff_row: Option, pub build_running: bool, pub scratch_available: bool, pub scratch_running: bool, @@ -197,7 +198,7 @@ impl DiffViewState { // Clear the scroll flags to prevent it from scrolling continuously. self.symbol_state.autoscroll_to_highlighted_symbols = false; - self.function_state.scroll_to_row = None; + self.scroll_to_diff_row = None; let Some(action) = action else { return; @@ -366,7 +367,7 @@ impl DiffViewState { state.config.diff_obj_config.show_data_flow = value; } DiffViewAction::ScrollToRow(row) => { - self.function_state.scroll_to_row = Some(row); + self.scroll_to_diff_row = Some(row); } } }