mirror of
https://github.com/encounter/objdiff.git
synced 2025-06-07 15:13:47 +00:00
Diff view refactor
This commit is contained in:
parent
3e6efb7736
commit
d938988d43
@ -18,4 +18,4 @@ authors = ["Luke Street <luke@street.dev>"]
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/encounter/objdiff"
|
repository = "https://github.com/encounter/objdiff"
|
||||||
rust-version = "1.81"
|
rust-version = "1.82"
|
||||||
|
@ -33,16 +33,14 @@ use crate::{
|
|||||||
arch_config_window, config_ui, general_config_ui, project_window, ConfigViewState,
|
arch_config_window, config_ui, general_config_ui, project_window, ConfigViewState,
|
||||||
CONFIG_DISABLED_TEXT,
|
CONFIG_DISABLED_TEXT,
|
||||||
},
|
},
|
||||||
data_diff::data_diff_ui,
|
|
||||||
debug::debug_window,
|
debug::debug_window,
|
||||||
demangle::{demangle_window, DemangleViewState},
|
demangle::{demangle_window, DemangleViewState},
|
||||||
extab_diff::extab_diff_ui,
|
diff::diff_view_ui,
|
||||||
frame_history::FrameHistory,
|
frame_history::FrameHistory,
|
||||||
function_diff::function_diff_ui,
|
|
||||||
graphics::{graphics_window, GraphicsConfig, GraphicsViewState},
|
graphics::{graphics_window, GraphicsConfig, GraphicsViewState},
|
||||||
jobs::{jobs_menu_ui, jobs_window},
|
jobs::{jobs_menu_ui, jobs_window},
|
||||||
rlwinm::{rlwinm_decode_window, RlwinmDecodeViewState},
|
rlwinm::{rlwinm_decode_window, RlwinmDecodeViewState},
|
||||||
symbol_diff::{symbol_diff_ui, DiffViewAction, DiffViewNavigation, DiffViewState, View},
|
symbol_diff::{DiffViewAction, DiffViewNavigation, DiffViewState, View},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -753,16 +751,7 @@ impl eframe::App for App {
|
|||||||
|
|
||||||
let mut action = None;
|
let mut action = None;
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
let build_success = matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success);
|
action = diff_view_ui(ui, diff_state, appearance);
|
||||||
action = if diff_state.current_view == View::FunctionDiff && build_success {
|
|
||||||
function_diff_ui(ui, diff_state, appearance)
|
|
||||||
} else if diff_state.current_view == View::DataDiff && build_success {
|
|
||||||
data_diff_ui(ui, diff_state, appearance)
|
|
||||||
} else if diff_state.current_view == View::ExtabDiff && build_success {
|
|
||||||
extab_diff_ui(ui, diff_state, appearance)
|
|
||||||
} else {
|
|
||||||
symbol_diff_ui(ui, diff_state, appearance)
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
project_window(ctx, state, show_project_config, config_state, appearance);
|
project_window(ctx, state, show_project_config, config_state, appearance);
|
||||||
|
@ -4,28 +4,15 @@ use std::{
|
|||||||
mem::take,
|
mem::take,
|
||||||
};
|
};
|
||||||
|
|
||||||
use egui::{text::LayoutJob, Id, Label, RichText, Sense, Widget};
|
use egui::{text::LayoutJob, Label, Sense, Widget};
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
diff::{ObjDataDiff, ObjDataDiffKind, ObjDataRelocDiff, ObjDiff},
|
diff::{ObjDataDiff, ObjDataDiffKind, ObjDataRelocDiff},
|
||||||
obj::ObjInfo,
|
obj::ObjInfo,
|
||||||
};
|
};
|
||||||
use time::format_description;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::views::{appearance::Appearance, write_text};
|
||||||
hotkeys,
|
|
||||||
views::{
|
|
||||||
appearance::Appearance,
|
|
||||||
column_layout::{render_header, render_table},
|
|
||||||
symbol_diff::{DiffViewAction, DiffViewNavigation, DiffViewState},
|
|
||||||
write_text,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const BYTES_PER_ROW: usize = 16;
|
pub(crate) const BYTES_PER_ROW: usize = 16;
|
||||||
|
|
||||||
fn find_section(obj: &ObjInfo, section_name: &str) -> Option<usize> {
|
|
||||||
obj.sections.iter().position(|section| section.name == section_name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data_row_hover_ui(
|
fn data_row_hover_ui(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
@ -122,7 +109,7 @@ fn get_color_for_diff_kind(diff_kind: ObjDataDiffKind, appearance: &Appearance)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data_row_ui(
|
pub(crate) fn data_row_ui(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
obj: Option<&ObjInfo>,
|
obj: Option<&ObjInfo>,
|
||||||
address: usize,
|
address: usize,
|
||||||
@ -212,7 +199,7 @@ fn data_row_ui(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn split_diffs(
|
pub(crate) fn split_diffs(
|
||||||
diffs: &[ObjDataDiff],
|
diffs: &[ObjDataDiff],
|
||||||
reloc_diffs: &[ObjDataRelocDiff],
|
reloc_diffs: &[ObjDataRelocDiff],
|
||||||
) -> Vec<Vec<(ObjDataDiff, Vec<ObjDataRelocDiff>)>> {
|
) -> Vec<Vec<(ObjDataDiff, Vec<ObjDataRelocDiff>)>> {
|
||||||
@ -273,169 +260,3 @@ fn split_diffs(
|
|||||||
}
|
}
|
||||||
split_diffs
|
split_diffs
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
struct SectionDiffContext<'a> {
|
|
||||||
obj: &'a ObjInfo,
|
|
||||||
diff: &'a ObjDiff,
|
|
||||||
section_index: Option<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> SectionDiffContext<'a> {
|
|
||||||
pub fn new(obj: Option<&'a (ObjInfo, ObjDiff)>, section_name: Option<&str>) -> Option<Self> {
|
|
||||||
obj.map(|(obj, diff)| Self {
|
|
||||||
obj,
|
|
||||||
diff,
|
|
||||||
section_index: section_name.and_then(|section_name| find_section(obj, section_name)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn has_section(&self) -> bool { self.section_index.is_some() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data_table_ui(
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
available_width: f32,
|
|
||||||
left_ctx: Option<SectionDiffContext<'_>>,
|
|
||||||
right_ctx: Option<SectionDiffContext<'_>>,
|
|
||||||
config: &Appearance,
|
|
||||||
) -> Option<()> {
|
|
||||||
let left_obj = left_ctx.map(|ctx| ctx.obj);
|
|
||||||
let right_obj = right_ctx.map(|ctx| ctx.obj);
|
|
||||||
let left_section = left_ctx
|
|
||||||
.and_then(|ctx| ctx.section_index.map(|i| (&ctx.obj.sections[i], &ctx.diff.sections[i])));
|
|
||||||
let right_section = right_ctx
|
|
||||||
.and_then(|ctx| ctx.section_index.map(|i| (&ctx.obj.sections[i], &ctx.diff.sections[i])));
|
|
||||||
let total_bytes = left_section
|
|
||||||
.or(right_section)?
|
|
||||||
.1
|
|
||||||
.data_diff
|
|
||||||
.iter()
|
|
||||||
.fold(0usize, |accum, item| accum + item.len);
|
|
||||||
if total_bytes == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let total_rows = (total_bytes - 1) / BYTES_PER_ROW + 1;
|
|
||||||
|
|
||||||
let left_diffs =
|
|
||||||
left_section.map(|(_, section)| split_diffs(§ion.data_diff, §ion.reloc_diff));
|
|
||||||
let right_diffs =
|
|
||||||
right_section.map(|(_, section)| split_diffs(§ion.data_diff, §ion.reloc_diff));
|
|
||||||
|
|
||||||
hotkeys::check_scroll_hotkeys(ui, true);
|
|
||||||
|
|
||||||
render_table(ui, available_width, 2, config.code_font.size, total_rows, |row, column| {
|
|
||||||
let i = row.index();
|
|
||||||
let address = i * BYTES_PER_ROW;
|
|
||||||
row.col(|ui| {
|
|
||||||
if column == 0 {
|
|
||||||
if let Some(left_diffs) = &left_diffs {
|
|
||||||
data_row_ui(ui, left_obj, address, &left_diffs[i], config);
|
|
||||||
}
|
|
||||||
} else if column == 1 {
|
|
||||||
if let Some(right_diffs) = &right_diffs {
|
|
||||||
data_row_ui(ui, right_obj, address, &right_diffs[i], config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Some(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn data_diff_ui(
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
state: &DiffViewState,
|
|
||||||
appearance: &Appearance,
|
|
||||||
) -> Option<DiffViewAction> {
|
|
||||||
let mut ret = None;
|
|
||||||
let Some(result) = &state.build else {
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
let section_name =
|
|
||||||
state.symbol_state.left_symbol.as_ref().and_then(|s| s.section_name.as_deref()).or_else(
|
|
||||||
|| state.symbol_state.right_symbol.as_ref().and_then(|s| s.section_name.as_deref()),
|
|
||||||
);
|
|
||||||
let left_ctx = SectionDiffContext::new(result.first_obj.as_ref(), section_name);
|
|
||||||
let right_ctx = SectionDiffContext::new(result.second_obj.as_ref(), section_name);
|
|
||||||
|
|
||||||
// If both sides are missing a symbol, switch to symbol diff view
|
|
||||||
if !right_ctx.is_some_and(|ctx| ctx.has_section())
|
|
||||||
&& !left_ctx.is_some_and(|ctx| ctx.has_section())
|
|
||||||
{
|
|
||||||
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header
|
|
||||||
let available_width = ui.available_width();
|
|
||||||
render_header(ui, available_width, 2, |ui, column| {
|
|
||||||
if column == 0 {
|
|
||||||
// Left column
|
|
||||||
if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) {
|
|
||||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(section) =
|
|
||||||
left_ctx.and_then(|ctx| ctx.section_index.map(|i| &ctx.obj.sections[i]))
|
|
||||||
{
|
|
||||||
ui.label(
|
|
||||||
RichText::new(section.name.clone())
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.highlight_color),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Missing")
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.replace_color),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if column == 1 {
|
|
||||||
// Right column
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() {
|
|
||||||
ret = Some(DiffViewAction::Build);
|
|
||||||
}
|
|
||||||
ui.scope(|ui| {
|
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
|
||||||
if state.build_running {
|
|
||||||
ui.colored_label(appearance.replace_color, "Building…");
|
|
||||||
} else {
|
|
||||||
ui.label("Last built:");
|
|
||||||
let format = format_description::parse("[hour]:[minute]:[second]").unwrap();
|
|
||||||
ui.label(
|
|
||||||
result.time.to_offset(appearance.utc_offset).format(&format).unwrap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(section) =
|
|
||||||
right_ctx.and_then(|ctx| ctx.section_index.map(|i| &ctx.obj.sections[i]))
|
|
||||||
{
|
|
||||||
ui.label(
|
|
||||||
RichText::new(section.name.clone())
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.highlight_color),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Missing")
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.replace_color),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Table
|
|
||||||
let id =
|
|
||||||
Id::new(state.symbol_state.left_symbol.as_ref().and_then(|s| s.section_name.as_deref()))
|
|
||||||
.with(state.symbol_state.right_symbol.as_ref().and_then(|s| s.section_name.as_deref()));
|
|
||||||
ui.push_id(id, |ui| {
|
|
||||||
data_table_ui(ui, available_width, left_ctx, right_ctx, appearance);
|
|
||||||
});
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
741
objdiff-gui/src/views/diff.rs
Normal file
741
objdiff-gui/src/views/diff.rs
Normal file
@ -0,0 +1,741 @@
|
|||||||
|
use egui::{Id, Layout, RichText, ScrollArea, TextEdit, Ui, Widget};
|
||||||
|
use objdiff_core::{
|
||||||
|
build::BuildStatus,
|
||||||
|
diff::{ObjDiff, ObjSectionDiff, ObjSymbolDiff},
|
||||||
|
obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, SymbolRef},
|
||||||
|
};
|
||||||
|
use time::format_description;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
hotkeys,
|
||||||
|
views::{
|
||||||
|
appearance::Appearance,
|
||||||
|
column_layout::{render_header, render_strips, render_table},
|
||||||
|
data_diff::{data_row_ui, split_diffs, BYTES_PER_ROW},
|
||||||
|
extab_diff::extab_ui,
|
||||||
|
function_diff::{asm_col_ui, FunctionDiffContext},
|
||||||
|
symbol_diff::{
|
||||||
|
match_color_for_symbol, symbol_list_ui, DiffViewAction, DiffViewNavigation,
|
||||||
|
DiffViewState, SymbolDiffContext, SymbolFilter, SymbolRefByName, View,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum SelectedSymbol {
|
||||||
|
Symbol(SymbolRef),
|
||||||
|
Section(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct DiffColumnContext<'a> {
|
||||||
|
status: &'a BuildStatus,
|
||||||
|
obj: Option<&'a (ObjInfo, ObjDiff)>,
|
||||||
|
section: Option<(&'a ObjSection, &'a ObjSectionDiff)>,
|
||||||
|
symbol: Option<(&'a ObjSymbol, &'a ObjSymbolDiff)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DiffColumnContext<'a> {
|
||||||
|
pub fn new(
|
||||||
|
view: View,
|
||||||
|
status: &'a BuildStatus,
|
||||||
|
obj: Option<&'a (ObjInfo, ObjDiff)>,
|
||||||
|
selected_symbol: Option<&SymbolRefByName>,
|
||||||
|
) -> Self {
|
||||||
|
let selected_symbol = match view {
|
||||||
|
View::SymbolDiff => None,
|
||||||
|
View::FunctionDiff | View::ExtabDiff => match (obj, selected_symbol) {
|
||||||
|
(Some(obj), Some(s)) => find_symbol(&obj.0, s).map(SelectedSymbol::Symbol),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
View::DataDiff => match (obj, selected_symbol) {
|
||||||
|
(Some(obj), Some(SymbolRefByName { section_name: Some(section_name), .. })) => {
|
||||||
|
find_section(&obj.0, section_name).map(SelectedSymbol::Section)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let (section, symbol) = match (obj, selected_symbol) {
|
||||||
|
(Some((obj, obj_diff)), Some(SelectedSymbol::Symbol(symbol_ref))) => {
|
||||||
|
let (section, symbol) = obj.section_symbol(symbol_ref);
|
||||||
|
(
|
||||||
|
section.map(|s| (s, obj_diff.section_diff(symbol_ref.section_idx))),
|
||||||
|
Some((symbol, obj_diff.symbol_diff(symbol_ref))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
(Some((obj, obj_diff)), Some(SelectedSymbol::Section(section_idx))) => {
|
||||||
|
(Some((&obj.sections[section_idx], obj_diff.section_diff(section_idx))), None)
|
||||||
|
}
|
||||||
|
_ => (None, None),
|
||||||
|
};
|
||||||
|
Self { status, obj, section, symbol }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn has_symbol(&self) -> bool { self.section.is_some() || self.symbol.is_some() }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn id(&self) -> Option<&str> {
|
||||||
|
self.symbol
|
||||||
|
.map(|(symbol, _)| symbol.name.as_str())
|
||||||
|
.or_else(|| self.section.map(|(section, _)| section.name.as_str()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn diff_view_ui(
|
||||||
|
ui: &mut Ui,
|
||||||
|
state: &DiffViewState,
|
||||||
|
appearance: &Appearance,
|
||||||
|
) -> Option<DiffViewAction> {
|
||||||
|
let mut ret = None;
|
||||||
|
let Some(result) = &state.build else {
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
let left_ctx = DiffColumnContext::new(
|
||||||
|
state.current_view,
|
||||||
|
&result.first_status,
|
||||||
|
result.first_obj.as_ref(),
|
||||||
|
state.symbol_state.left_symbol.as_ref(),
|
||||||
|
);
|
||||||
|
let right_ctx = DiffColumnContext::new(
|
||||||
|
state.current_view,
|
||||||
|
&result.second_status,
|
||||||
|
result.second_obj.as_ref(),
|
||||||
|
state.symbol_state.right_symbol.as_ref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if we need to perform any navigation
|
||||||
|
let current_navigation = DiffViewNavigation {
|
||||||
|
view: state.current_view,
|
||||||
|
left_symbol: state.symbol_state.left_symbol.clone(),
|
||||||
|
right_symbol: state.symbol_state.right_symbol.clone(),
|
||||||
|
};
|
||||||
|
let mut navigation = current_navigation.clone();
|
||||||
|
if let Some((_symbol, symbol_diff)) = left_ctx.symbol {
|
||||||
|
// If a matching symbol appears, select it
|
||||||
|
if !right_ctx.has_symbol() {
|
||||||
|
if let Some(target_symbol_ref) = symbol_diff.target_symbol {
|
||||||
|
let (target_section, target_symbol) =
|
||||||
|
right_ctx.obj.unwrap().0.section_symbol(target_symbol_ref);
|
||||||
|
navigation.right_symbol = Some(SymbolRefByName::new(target_symbol, target_section));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if navigation.left_symbol.is_some()
|
||||||
|
&& left_ctx.obj.is_some()
|
||||||
|
&& left_ctx.section.is_none()
|
||||||
|
{
|
||||||
|
// Clear selection if symbol goes missing
|
||||||
|
navigation.left_symbol = None;
|
||||||
|
}
|
||||||
|
if let Some((_symbol, symbol_diff)) = right_ctx.symbol {
|
||||||
|
// If a matching symbol appears, select it
|
||||||
|
if !left_ctx.has_symbol() {
|
||||||
|
if let Some(target_symbol_ref) = symbol_diff.target_symbol {
|
||||||
|
let (target_section, target_symbol) =
|
||||||
|
left_ctx.obj.unwrap().0.section_symbol(target_symbol_ref);
|
||||||
|
navigation.left_symbol = Some(SymbolRefByName::new(target_symbol, target_section));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if navigation.right_symbol.is_some()
|
||||||
|
&& right_ctx.obj.is_some()
|
||||||
|
&& right_ctx.section.is_none()
|
||||||
|
{
|
||||||
|
// Clear selection if symbol goes missing
|
||||||
|
navigation.right_symbol = None;
|
||||||
|
}
|
||||||
|
// If both sides are missing a symbol, switch to symbol diff view
|
||||||
|
if navigation.left_symbol.is_none() && navigation.right_symbol.is_none() {
|
||||||
|
navigation.view = View::SymbolDiff;
|
||||||
|
}
|
||||||
|
// Execute navigation if it changed
|
||||||
|
if navigation != current_navigation && !state.post_build_nav.is_some() {
|
||||||
|
ret = Some(DiffViewAction::Navigate(navigation));
|
||||||
|
}
|
||||||
|
|
||||||
|
let available_width = ui.available_width();
|
||||||
|
let mut open_sections = (None, None);
|
||||||
|
|
||||||
|
render_header(ui, available_width, 2, |ui, column| {
|
||||||
|
if column == 0 {
|
||||||
|
// Left column
|
||||||
|
|
||||||
|
// First row
|
||||||
|
if state.current_view == View::SymbolDiff {
|
||||||
|
ui.label(RichText::new("Target object").text_style(egui::TextStyle::Monospace));
|
||||||
|
} else {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) {
|
||||||
|
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((symbol, _)) = left_ctx.symbol {
|
||||||
|
ui.separator();
|
||||||
|
if ui
|
||||||
|
.add_enabled(
|
||||||
|
!state.scratch_running
|
||||||
|
&& state.scratch_available
|
||||||
|
&& left_ctx.has_symbol(),
|
||||||
|
egui::Button::new("📲 decomp.me"),
|
||||||
|
)
|
||||||
|
.on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)")
|
||||||
|
.on_disabled_hover_text("Scratch configuration missing")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
ret = Some(DiffViewAction::CreateScratch(symbol.name.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second row
|
||||||
|
if !left_ctx.status.success {
|
||||||
|
ui.label(
|
||||||
|
RichText::new("Fail")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.delete_color),
|
||||||
|
);
|
||||||
|
} else if state.current_view == View::SymbolDiff {
|
||||||
|
if left_ctx.obj.is_some() {
|
||||||
|
ui.label(
|
||||||
|
RichText::new(state.object_name.clone())
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.highlight_color),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ui.label(
|
||||||
|
RichText::new("Missing")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.replace_color),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if let Some((symbol, _)) = left_ctx.symbol {
|
||||||
|
ui.label(
|
||||||
|
RichText::new(symbol.demangled_name.as_deref().unwrap_or(&symbol.name))
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.highlight_color),
|
||||||
|
);
|
||||||
|
} else if let Some((section, _)) = left_ctx.section {
|
||||||
|
ui.label(
|
||||||
|
RichText::new(section.name.clone())
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.highlight_color),
|
||||||
|
);
|
||||||
|
} else if right_ctx.has_symbol() {
|
||||||
|
ui.label(
|
||||||
|
RichText::new("Choose target symbol")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.replace_color),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ui.label(
|
||||||
|
RichText::new("Missing")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.replace_color),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third row
|
||||||
|
if left_ctx.has_symbol() && right_ctx.has_symbol() {
|
||||||
|
if state.current_view == View::FunctionDiff
|
||||||
|
&& ui
|
||||||
|
.button("Change target")
|
||||||
|
.on_hover_text_at_pointer("Choose a different symbol to use as the target")
|
||||||
|
.clicked()
|
||||||
|
|| hotkeys::consume_change_target_shortcut(ui.ctx())
|
||||||
|
{
|
||||||
|
if let Some(symbol_ref) = state.symbol_state.right_symbol.as_ref() {
|
||||||
|
ret = Some(DiffViewAction::SelectingLeft(symbol_ref.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if left_ctx.status.success && !left_ctx.has_symbol() {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let mut search = state.search.clone();
|
||||||
|
let response =
|
||||||
|
TextEdit::singleline(&mut search).hint_text("Filter symbols").ui(ui);
|
||||||
|
if hotkeys::consume_symbol_filter_shortcut(ui.ctx()) {
|
||||||
|
response.request_focus();
|
||||||
|
}
|
||||||
|
if response.changed() {
|
||||||
|
ret = Some(DiffViewAction::SetSearch(search));
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| {
|
||||||
|
if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() {
|
||||||
|
open_sections.0 = Some(true);
|
||||||
|
}
|
||||||
|
if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked()
|
||||||
|
{
|
||||||
|
open_sections.0 = Some(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if column == 1 {
|
||||||
|
// Right column
|
||||||
|
|
||||||
|
// First row
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() {
|
||||||
|
ret = Some(DiffViewAction::Build);
|
||||||
|
}
|
||||||
|
if state.build_running {
|
||||||
|
ui.colored_label(
|
||||||
|
appearance.replace_color,
|
||||||
|
RichText::new("Building…").text_style(egui::TextStyle::Monospace),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ui.label(RichText::new("Last built:").text_style(egui::TextStyle::Monospace));
|
||||||
|
let format = format_description::parse("[hour]:[minute]:[second]").unwrap();
|
||||||
|
ui.label(
|
||||||
|
RichText::new(
|
||||||
|
result.time.to_offset(appearance.utc_offset).format(&format).unwrap(),
|
||||||
|
)
|
||||||
|
.text_style(egui::TextStyle::Monospace),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
if ui
|
||||||
|
.add_enabled(state.source_path_available, egui::Button::new("🖹 Source file"))
|
||||||
|
.on_hover_text_at_pointer("Open the source file in the default editor")
|
||||||
|
.on_disabled_hover_text("Source file metadata missing")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
ret = Some(DiffViewAction::OpenSourcePath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Second row
|
||||||
|
if !right_ctx.status.success {
|
||||||
|
ui.label(
|
||||||
|
RichText::new("Fail")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.delete_color),
|
||||||
|
);
|
||||||
|
} else if state.current_view == View::SymbolDiff {
|
||||||
|
if right_ctx.obj.is_some() {
|
||||||
|
if left_ctx.obj.is_some() {
|
||||||
|
ui.label(RichText::new("Base object").font(appearance.code_font.clone()));
|
||||||
|
} else {
|
||||||
|
ui.label(
|
||||||
|
RichText::new(state.object_name.clone())
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.highlight_color),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ui.label(
|
||||||
|
RichText::new("Missing")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.replace_color),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if let Some((symbol, _)) = right_ctx.symbol {
|
||||||
|
ui.label(
|
||||||
|
RichText::new(symbol.demangled_name.as_deref().unwrap_or(&symbol.name))
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.highlight_color),
|
||||||
|
);
|
||||||
|
} else if let Some((section, _)) = right_ctx.section {
|
||||||
|
ui.label(
|
||||||
|
RichText::new(section.name.clone())
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.highlight_color),
|
||||||
|
);
|
||||||
|
} else if left_ctx.has_symbol() {
|
||||||
|
ui.label(
|
||||||
|
RichText::new("Choose base symbol")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.replace_color),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ui.label(
|
||||||
|
RichText::new("Missing")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.replace_color),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third row
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if let Some((_, symbol_diff)) = right_ctx.symbol {
|
||||||
|
if let Some(match_percent) = symbol_diff.match_percent {
|
||||||
|
ui.label(
|
||||||
|
RichText::new(format!("{:.0}%", match_percent.floor()))
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(match_color_for_symbol(match_percent, appearance)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if state.current_view == View::FunctionDiff && left_ctx.has_symbol() {
|
||||||
|
ui.separator();
|
||||||
|
if ui
|
||||||
|
.button("Change base")
|
||||||
|
.on_hover_text_at_pointer(
|
||||||
|
"Choose a different symbol to use as the base",
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
|| hotkeys::consume_change_base_shortcut(ui.ctx())
|
||||||
|
{
|
||||||
|
if let Some(symbol_ref) = state.symbol_state.left_symbol.as_ref() {
|
||||||
|
ret = Some(DiffViewAction::SelectingRight(symbol_ref.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if right_ctx.status.success && !right_ctx.has_symbol() {
|
||||||
|
let mut search = state.search.clone();
|
||||||
|
let response =
|
||||||
|
TextEdit::singleline(&mut search).hint_text("Filter symbols").ui(ui);
|
||||||
|
if hotkeys::consume_symbol_filter_shortcut(ui.ctx()) {
|
||||||
|
response.request_focus();
|
||||||
|
}
|
||||||
|
if response.changed() {
|
||||||
|
ret = Some(DiffViewAction::SetSearch(search));
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| {
|
||||||
|
if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() {
|
||||||
|
open_sections.1 = Some(true);
|
||||||
|
}
|
||||||
|
if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked()
|
||||||
|
{
|
||||||
|
open_sections.1 = Some(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Table
|
||||||
|
ui.push_id(Id::new(left_ctx.id()).with(right_ctx.id()), |ui| {
|
||||||
|
if let (
|
||||||
|
View::FunctionDiff,
|
||||||
|
Some((left_obj, left_diff)),
|
||||||
|
Some((right_obj, right_diff)),
|
||||||
|
Some((_, left_symbol_diff)),
|
||||||
|
Some((_, right_symbol_diff)),
|
||||||
|
) = (state.current_view, left_ctx.obj, right_ctx.obj, left_ctx.symbol, right_ctx.symbol)
|
||||||
|
{
|
||||||
|
// Joint diff view
|
||||||
|
hotkeys::check_scroll_hotkeys(ui, true);
|
||||||
|
if left_symbol_diff.instructions.len() != right_symbol_diff.instructions.len() {
|
||||||
|
ui.label("Instruction count mismatch");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let instructions_len = left_symbol_diff.instructions.len();
|
||||||
|
render_table(
|
||||||
|
ui,
|
||||||
|
available_width,
|
||||||
|
2,
|
||||||
|
appearance.code_font.size,
|
||||||
|
instructions_len,
|
||||||
|
|row, column| {
|
||||||
|
if column == 0 {
|
||||||
|
if let Some(action) = asm_col_ui(
|
||||||
|
row,
|
||||||
|
FunctionDiffContext {
|
||||||
|
obj: left_obj,
|
||||||
|
diff: left_diff,
|
||||||
|
symbol_ref: Some(left_symbol_diff.symbol_ref),
|
||||||
|
},
|
||||||
|
appearance,
|
||||||
|
&state.function_state,
|
||||||
|
column,
|
||||||
|
) {
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
|
} else if column == 1 {
|
||||||
|
if let Some(action) = asm_col_ui(
|
||||||
|
row,
|
||||||
|
FunctionDiffContext {
|
||||||
|
obj: right_obj,
|
||||||
|
diff: right_diff,
|
||||||
|
symbol_ref: Some(right_symbol_diff.symbol_ref),
|
||||||
|
},
|
||||||
|
appearance,
|
||||||
|
&state.function_state,
|
||||||
|
column,
|
||||||
|
) {
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
|
if row.response().clicked() {
|
||||||
|
ret = Some(DiffViewAction::ClearDiffHighlight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if let (
|
||||||
|
View::DataDiff,
|
||||||
|
Some((left_obj, _left_diff)),
|
||||||
|
Some((right_obj, _right_diff)),
|
||||||
|
Some((_left_section, left_section_diff)),
|
||||||
|
Some((_right_section, right_section_diff)),
|
||||||
|
) =
|
||||||
|
(state.current_view, left_ctx.obj, right_ctx.obj, left_ctx.section, right_ctx.section)
|
||||||
|
{
|
||||||
|
// Joint diff view
|
||||||
|
let left_total_bytes =
|
||||||
|
left_section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
|
||||||
|
let right_total_bytes =
|
||||||
|
right_section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
|
||||||
|
if left_total_bytes != right_total_bytes {
|
||||||
|
ui.label("Data size mismatch");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if left_total_bytes == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let total_rows = (left_total_bytes - 1) / BYTES_PER_ROW + 1;
|
||||||
|
let left_diffs =
|
||||||
|
split_diffs(&left_section_diff.data_diff, &left_section_diff.reloc_diff);
|
||||||
|
let right_diffs =
|
||||||
|
split_diffs(&right_section_diff.data_diff, &right_section_diff.reloc_diff);
|
||||||
|
render_table(
|
||||||
|
ui,
|
||||||
|
available_width,
|
||||||
|
2,
|
||||||
|
appearance.code_font.size,
|
||||||
|
total_rows,
|
||||||
|
|row, column| {
|
||||||
|
let i = row.index();
|
||||||
|
let address = i * BYTES_PER_ROW;
|
||||||
|
row.col(|ui| {
|
||||||
|
if column == 0 {
|
||||||
|
data_row_ui(ui, Some(left_obj), address, &left_diffs[i], appearance);
|
||||||
|
} else if column == 1 {
|
||||||
|
data_row_ui(ui, Some(right_obj), address, &right_diffs[i], appearance);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Split view
|
||||||
|
render_strips(ui, available_width, 2, |ui, column| {
|
||||||
|
if column == 0 {
|
||||||
|
if let Some(action) = diff_col_ui(
|
||||||
|
ui,
|
||||||
|
state,
|
||||||
|
appearance,
|
||||||
|
column,
|
||||||
|
left_ctx,
|
||||||
|
right_ctx,
|
||||||
|
available_width,
|
||||||
|
open_sections.0,
|
||||||
|
) {
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
|
} else if column == 1 {
|
||||||
|
if let Some(action) = diff_col_ui(
|
||||||
|
ui,
|
||||||
|
state,
|
||||||
|
appearance,
|
||||||
|
column,
|
||||||
|
right_ctx,
|
||||||
|
left_ctx,
|
||||||
|
available_width,
|
||||||
|
open_sections.1,
|
||||||
|
) {
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn diff_col_ui(
|
||||||
|
ui: &mut Ui,
|
||||||
|
state: &DiffViewState,
|
||||||
|
appearance: &Appearance,
|
||||||
|
column: usize,
|
||||||
|
ctx: DiffColumnContext,
|
||||||
|
other_ctx: DiffColumnContext,
|
||||||
|
available_width: f32,
|
||||||
|
open_sections: Option<bool>,
|
||||||
|
) -> Option<DiffViewAction> {
|
||||||
|
let mut ret = None;
|
||||||
|
if !ctx.status.success {
|
||||||
|
build_log_ui(ui, ctx.status, appearance);
|
||||||
|
} else if let Some((obj, diff)) = ctx.obj {
|
||||||
|
if let Some((_symbol, symbol_diff)) = ctx.symbol {
|
||||||
|
hotkeys::check_scroll_hotkeys(ui, false);
|
||||||
|
let ctx = FunctionDiffContext { obj, diff, symbol_ref: Some(symbol_diff.symbol_ref) };
|
||||||
|
if state.current_view == View::ExtabDiff {
|
||||||
|
extab_ui(ui, ctx, appearance, column);
|
||||||
|
} else {
|
||||||
|
render_table(
|
||||||
|
ui,
|
||||||
|
available_width / 2.0,
|
||||||
|
1,
|
||||||
|
appearance.code_font.size,
|
||||||
|
symbol_diff.instructions.len(),
|
||||||
|
|row, column| {
|
||||||
|
if let Some(action) =
|
||||||
|
asm_col_ui(row, ctx, appearance, &state.function_state, column)
|
||||||
|
{
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
|
if row.response().clicked() {
|
||||||
|
ret = Some(DiffViewAction::ClearDiffHighlight);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if let Some((_section, section_diff)) = ctx.section {
|
||||||
|
hotkeys::check_scroll_hotkeys(ui, false);
|
||||||
|
let total_bytes =
|
||||||
|
section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
|
||||||
|
if total_bytes == 0 {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
let total_rows = (total_bytes - 1) / BYTES_PER_ROW + 1;
|
||||||
|
let diffs = split_diffs(§ion_diff.data_diff, §ion_diff.reloc_diff);
|
||||||
|
render_table(
|
||||||
|
ui,
|
||||||
|
available_width / 2.0,
|
||||||
|
1,
|
||||||
|
appearance.code_font.size,
|
||||||
|
total_rows,
|
||||||
|
|row, _column| {
|
||||||
|
let i = row.index();
|
||||||
|
let address = i * BYTES_PER_ROW;
|
||||||
|
row.col(|ui| {
|
||||||
|
data_row_ui(ui, Some(obj), address, &diffs[i], appearance);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if let (
|
||||||
|
Some((other_section, _other_section_diff)),
|
||||||
|
Some((other_symbol, other_symbol_diff)),
|
||||||
|
) = (other_ctx.section, other_ctx.symbol)
|
||||||
|
{
|
||||||
|
if let Some(action) = symbol_list_ui(
|
||||||
|
ui,
|
||||||
|
SymbolDiffContext { obj, diff },
|
||||||
|
None,
|
||||||
|
&state.symbol_state,
|
||||||
|
SymbolFilter::Mapping(other_symbol_diff.symbol_ref, None),
|
||||||
|
appearance,
|
||||||
|
column,
|
||||||
|
open_sections,
|
||||||
|
) {
|
||||||
|
match (column, action) {
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
DiffViewAction::Navigate(DiffViewNavigation {
|
||||||
|
left_symbol: Some(left_symbol_ref),
|
||||||
|
..
|
||||||
|
}),
|
||||||
|
) => {
|
||||||
|
ret = Some(DiffViewAction::SetMapping(
|
||||||
|
match other_section.kind {
|
||||||
|
ObjSectionKind::Code => View::FunctionDiff,
|
||||||
|
_ => View::SymbolDiff,
|
||||||
|
},
|
||||||
|
left_symbol_ref,
|
||||||
|
SymbolRefByName::new(other_symbol, Some(other_section)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
(
|
||||||
|
1,
|
||||||
|
DiffViewAction::Navigate(DiffViewNavigation {
|
||||||
|
right_symbol: Some(right_symbol_ref),
|
||||||
|
..
|
||||||
|
}),
|
||||||
|
) => {
|
||||||
|
ret = Some(DiffViewAction::SetMapping(
|
||||||
|
match other_section.kind {
|
||||||
|
ObjSectionKind::Code => View::FunctionDiff,
|
||||||
|
_ => View::SymbolDiff,
|
||||||
|
},
|
||||||
|
SymbolRefByName::new(other_symbol, Some(other_section)),
|
||||||
|
right_symbol_ref,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
(_, action) => {
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let filter = match &state.search_regex {
|
||||||
|
Some(regex) => SymbolFilter::Search(regex),
|
||||||
|
_ => SymbolFilter::None,
|
||||||
|
};
|
||||||
|
if let Some(result) = symbol_list_ui(
|
||||||
|
ui,
|
||||||
|
SymbolDiffContext { obj, diff },
|
||||||
|
other_ctx.obj.map(|(obj, diff)| SymbolDiffContext { obj, diff }),
|
||||||
|
&state.symbol_state,
|
||||||
|
filter,
|
||||||
|
appearance,
|
||||||
|
column,
|
||||||
|
open_sections,
|
||||||
|
) {
|
||||||
|
ret = Some(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
missing_obj_ui(ui, appearance);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_log_ui(ui: &mut Ui, status: &BuildStatus, appearance: &Appearance) {
|
||||||
|
ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if !status.cmdline.is_empty() && ui.button("Copy command").clicked() {
|
||||||
|
ui.output_mut(|output| output.copied_text.clone_from(&status.cmdline));
|
||||||
|
}
|
||||||
|
if ui.button("Copy log").clicked() {
|
||||||
|
ui.output_mut(|output| {
|
||||||
|
output.copied_text = format!("{}\n{}", status.stdout, status.stderr)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ui.scope(|ui| {
|
||||||
|
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||||
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
|
|
||||||
|
if !status.cmdline.is_empty() {
|
||||||
|
ui.label(&status.cmdline);
|
||||||
|
}
|
||||||
|
if !status.stdout.is_empty() {
|
||||||
|
ui.colored_label(appearance.replace_color, &status.stdout);
|
||||||
|
}
|
||||||
|
if !status.stderr.is_empty() {
|
||||||
|
ui.colored_label(appearance.delete_color, &status.stderr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn missing_obj_ui(ui: &mut Ui, appearance: &Appearance) {
|
||||||
|
ui.scope(|ui| {
|
||||||
|
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||||
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
|
|
||||||
|
ui.colored_label(appearance.replace_color, "No object configured");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_symbol(obj: &ObjInfo, selected_symbol: &SymbolRefByName) -> Option<SymbolRef> {
|
||||||
|
for (section_idx, section) in obj.sections.iter().enumerate() {
|
||||||
|
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
|
||||||
|
if symbol.name == selected_symbol.symbol_name {
|
||||||
|
return Some(SymbolRef { section_idx, symbol_idx });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_section(obj: &ObjInfo, section_name: &str) -> Option<usize> {
|
||||||
|
obj.sections.iter().position(|section| section.name == section_name)
|
||||||
|
}
|
@ -1,22 +1,10 @@
|
|||||||
use egui::{RichText, ScrollArea};
|
use egui::ScrollArea;
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
arch::ppc::ExceptionInfo,
|
arch::ppc::ExceptionInfo,
|
||||||
obj::{ObjInfo, ObjSymbol},
|
obj::{ObjInfo, ObjSymbol},
|
||||||
};
|
};
|
||||||
use time::format_description;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::views::{appearance::Appearance, function_diff::FunctionDiffContext};
|
||||||
hotkeys,
|
|
||||||
views::{
|
|
||||||
appearance::Appearance,
|
|
||||||
column_layout::{render_header, render_strips},
|
|
||||||
function_diff::FunctionDiffContext,
|
|
||||||
symbol_diff::{
|
|
||||||
match_color_for_symbol, DiffViewAction, DiffViewNavigation, DiffViewState,
|
|
||||||
SymbolRefByName, View,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn decode_extab(extab: &ExceptionInfo) -> String {
|
fn decode_extab(extab: &ExceptionInfo) -> String {
|
||||||
let mut text = String::from("");
|
let mut text = String::from("");
|
||||||
@ -57,7 +45,7 @@ fn extab_text_ui(
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extab_ui(
|
pub(crate) fn extab_ui(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
ctx: FunctionDiffContext<'_>,
|
ctx: FunctionDiffContext<'_>,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
@ -76,178 +64,3 @@ fn extab_ui(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn extab_diff_ui(
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
state: &DiffViewState,
|
|
||||||
appearance: &Appearance,
|
|
||||||
) -> Option<DiffViewAction> {
|
|
||||||
let mut ret = None;
|
|
||||||
let Some(result) = &state.build else {
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut left_ctx = FunctionDiffContext::new(
|
|
||||||
result.first_obj.as_ref(),
|
|
||||||
state.symbol_state.left_symbol.as_ref(),
|
|
||||||
);
|
|
||||||
let mut right_ctx = FunctionDiffContext::new(
|
|
||||||
result.second_obj.as_ref(),
|
|
||||||
state.symbol_state.right_symbol.as_ref(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// If one side is missing a symbol, but the diff process found a match, use that symbol
|
|
||||||
let left_diff_symbol = left_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol)
|
|
||||||
});
|
|
||||||
let right_diff_symbol = right_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol)
|
|
||||||
});
|
|
||||||
if left_diff_symbol.is_some() && right_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
|
|
||||||
let (right_section, right_symbol) =
|
|
||||||
right_ctx.unwrap().obj.section_symbol(left_diff_symbol.unwrap());
|
|
||||||
let symbol_ref = SymbolRefByName::new(right_symbol, right_section);
|
|
||||||
right_ctx = FunctionDiffContext::new(result.second_obj.as_ref(), Some(&symbol_ref));
|
|
||||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation {
|
|
||||||
view: Some(View::FunctionDiff),
|
|
||||||
left_symbol: state.symbol_state.left_symbol.clone(),
|
|
||||||
right_symbol: Some(symbol_ref),
|
|
||||||
}));
|
|
||||||
} else if right_diff_symbol.is_some() && left_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
|
|
||||||
let (left_section, left_symbol) =
|
|
||||||
left_ctx.unwrap().obj.section_symbol(right_diff_symbol.unwrap());
|
|
||||||
let symbol_ref = SymbolRefByName::new(left_symbol, left_section);
|
|
||||||
left_ctx = FunctionDiffContext::new(result.first_obj.as_ref(), Some(&symbol_ref));
|
|
||||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation {
|
|
||||||
view: Some(View::FunctionDiff),
|
|
||||||
left_symbol: Some(symbol_ref),
|
|
||||||
right_symbol: state.symbol_state.right_symbol.clone(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If both sides are missing a symbol, switch to symbol diff view
|
|
||||||
if right_ctx.is_some_and(|ctx| !ctx.has_symbol())
|
|
||||||
&& left_ctx.is_some_and(|ctx| !ctx.has_symbol())
|
|
||||||
{
|
|
||||||
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header
|
|
||||||
let available_width = ui.available_width();
|
|
||||||
render_header(ui, available_width, 2, |ui, column| {
|
|
||||||
if column == 0 {
|
|
||||||
// Left column
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) {
|
|
||||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
|
||||||
}
|
|
||||||
ui.separator();
|
|
||||||
if ui
|
|
||||||
.add_enabled(
|
|
||||||
!state.scratch_running
|
|
||||||
&& state.scratch_available
|
|
||||||
&& left_ctx.is_some_and(|ctx| ctx.has_symbol()),
|
|
||||||
egui::Button::new("📲 decomp.me"),
|
|
||||||
)
|
|
||||||
.on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)")
|
|
||||||
.on_disabled_hover_text("Scratch configuration missing")
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
if let Some((_section, symbol)) = left_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.map(|symbol_ref| ctx.obj.section_symbol(symbol_ref))
|
|
||||||
}) {
|
|
||||||
ret = Some(DiffViewAction::CreateScratch(symbol.name.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some((_section, symbol)) = left_ctx
|
|
||||||
.and_then(|ctx| ctx.symbol_ref.map(|symbol_ref| ctx.obj.section_symbol(symbol_ref)))
|
|
||||||
{
|
|
||||||
let name = symbol.demangled_name.as_deref().unwrap_or(&symbol.name);
|
|
||||||
ui.label(
|
|
||||||
RichText::new(name)
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.highlight_color),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Missing")
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.replace_color),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if column == 1 {
|
|
||||||
// Right column
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() {
|
|
||||||
ret = Some(DiffViewAction::Build);
|
|
||||||
}
|
|
||||||
ui.scope(|ui| {
|
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
|
||||||
if state.build_running {
|
|
||||||
ui.colored_label(appearance.replace_color, "Building…");
|
|
||||||
} else {
|
|
||||||
ui.label("Last built:");
|
|
||||||
let format = format_description::parse("[hour]:[minute]:[second]").unwrap();
|
|
||||||
ui.label(
|
|
||||||
result.time.to_offset(appearance.utc_offset).format(&format).unwrap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ui.separator();
|
|
||||||
if ui
|
|
||||||
.add_enabled(state.source_path_available, egui::Button::new("🖹 Source file"))
|
|
||||||
.on_hover_text_at_pointer("Open the source file in the default editor")
|
|
||||||
.on_disabled_hover_text("Source file metadata missing")
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
ret = Some(DiffViewAction::OpenSourcePath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(((_section, symbol), symbol_diff)) = right_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.map(|symbol_ref| {
|
|
||||||
(ctx.obj.section_symbol(symbol_ref), ctx.diff.symbol_diff(symbol_ref))
|
|
||||||
})
|
|
||||||
}) {
|
|
||||||
let name = symbol.demangled_name.as_deref().unwrap_or(&symbol.name);
|
|
||||||
ui.label(
|
|
||||||
RichText::new(name)
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.highlight_color),
|
|
||||||
);
|
|
||||||
if let Some(match_percent) = symbol_diff.match_percent {
|
|
||||||
ui.label(
|
|
||||||
RichText::new(format!("{:.0}%", match_percent.floor()))
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(match_color_for_symbol(match_percent, appearance)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Missing")
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.replace_color),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
hotkeys::check_scroll_hotkeys(ui, true);
|
|
||||||
|
|
||||||
// Table
|
|
||||||
render_strips(ui, available_width, 2, |ui, column| {
|
|
||||||
if column == 0 {
|
|
||||||
if let Some(ctx) = left_ctx {
|
|
||||||
extab_ui(ui, ctx, appearance, column);
|
|
||||||
}
|
|
||||||
} else if column == 1 {
|
|
||||||
if let Some(ctx) = right_ctx {
|
|
||||||
extab_ui(ui, ctx, appearance, column);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
@ -1,30 +1,16 @@
|
|||||||
use std::{cmp::Ordering, default::Default};
|
use std::{cmp::Ordering, default::Default};
|
||||||
|
|
||||||
use egui::{text::LayoutJob, Id, Label, Layout, Response, RichText, Sense, Widget};
|
use egui::{text::LayoutJob, Label, Response, Sense, Widget};
|
||||||
use egui_extras::TableRow;
|
use egui_extras::TableRow;
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
diff::{
|
diff::{
|
||||||
display::{display_diff, DiffText, HighlightKind},
|
display::{display_diff, DiffText, HighlightKind},
|
||||||
ObjDiff, ObjInsDiff, ObjInsDiffKind,
|
ObjDiff, ObjInsDiff, ObjInsDiffKind,
|
||||||
},
|
},
|
||||||
obj::{
|
obj::{ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjSection, ObjSymbol, SymbolRef},
|
||||||
ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjSection, ObjSectionKind, ObjSymbol,
|
|
||||||
SymbolRef,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use time::format_description;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::views::{appearance::Appearance, symbol_diff::DiffViewAction};
|
||||||
hotkeys,
|
|
||||||
views::{
|
|
||||||
appearance::Appearance,
|
|
||||||
column_layout::{render_header, render_strips, render_table},
|
|
||||||
symbol_diff::{
|
|
||||||
match_color_for_symbol, symbol_list_ui, DiffViewAction, DiffViewNavigation,
|
|
||||||
DiffViewState, SymbolDiffContext, SymbolFilter, SymbolRefByName, SymbolViewState, View,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct FunctionViewState {
|
pub struct FunctionViewState {
|
||||||
@ -245,17 +231,6 @@ fn ins_context_menu(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_symbol(obj: &ObjInfo, selected_symbol: &SymbolRefByName) -> Option<SymbolRef> {
|
|
||||||
for (section_idx, section) in obj.sections.iter().enumerate() {
|
|
||||||
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
|
|
||||||
if symbol.name == selected_symbol.symbol_name {
|
|
||||||
return Some(SymbolRef { section_idx, symbol_idx });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[expect(clippy::too_many_arguments)]
|
#[expect(clippy::too_many_arguments)]
|
||||||
fn diff_text_ui(
|
fn diff_text_ui(
|
||||||
@ -400,7 +375,7 @@ fn asm_row_ui(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn asm_col_ui(
|
pub(crate) fn asm_col_ui(
|
||||||
row: &mut TableRow<'_, '_>,
|
row: &mut TableRow<'_, '_>,
|
||||||
ctx: FunctionDiffContext<'_>,
|
ctx: FunctionDiffContext<'_>,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
@ -433,464 +408,9 @@ fn asm_col_ui(
|
|||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
#[expect(clippy::too_many_arguments)]
|
|
||||||
fn asm_table_ui(
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
available_width: f32,
|
|
||||||
left_ctx: Option<FunctionDiffContext<'_>>,
|
|
||||||
right_ctx: Option<FunctionDiffContext<'_>>,
|
|
||||||
appearance: &Appearance,
|
|
||||||
ins_view_state: &FunctionViewState,
|
|
||||||
symbol_state: &SymbolViewState,
|
|
||||||
open_sections: (Option<bool>, Option<bool>),
|
|
||||||
) -> Option<DiffViewAction> {
|
|
||||||
let mut ret = None;
|
|
||||||
let left_len = left_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.map(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).instructions.len())
|
|
||||||
});
|
|
||||||
let right_len = right_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.map(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).instructions.len())
|
|
||||||
});
|
|
||||||
let instructions_len = match (left_len, right_len) {
|
|
||||||
(Some(left_len), Some(right_len)) => {
|
|
||||||
if left_len != right_len {
|
|
||||||
ui.label("Instruction count mismatch");
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
left_len
|
|
||||||
}
|
|
||||||
(Some(left_len), None) => left_len,
|
|
||||||
(None, Some(right_len)) => right_len,
|
|
||||||
(None, None) => {
|
|
||||||
ui.label("No symbol selected");
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if left_len.is_some() && right_len.is_some() {
|
|
||||||
// Joint view
|
|
||||||
hotkeys::check_scroll_hotkeys(ui, true);
|
|
||||||
render_table(
|
|
||||||
ui,
|
|
||||||
available_width,
|
|
||||||
2,
|
|
||||||
appearance.code_font.size,
|
|
||||||
instructions_len,
|
|
||||||
|row, column| {
|
|
||||||
if column == 0 {
|
|
||||||
if let Some(ctx) = left_ctx {
|
|
||||||
if let Some(action) =
|
|
||||||
asm_col_ui(row, ctx, appearance, ins_view_state, column)
|
|
||||||
{
|
|
||||||
ret = Some(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if column == 1 {
|
|
||||||
if let Some(ctx) = right_ctx {
|
|
||||||
if let Some(action) =
|
|
||||||
asm_col_ui(row, ctx, appearance, ins_view_state, column)
|
|
||||||
{
|
|
||||||
ret = Some(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if row.response().clicked() {
|
|
||||||
ret = Some(DiffViewAction::ClearDiffHighlight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Split view, one side is the symbol list
|
|
||||||
render_strips(ui, available_width, 2, |ui, column| {
|
|
||||||
if column == 0 {
|
|
||||||
if let Some(ctx) = left_ctx {
|
|
||||||
if ctx.has_symbol() {
|
|
||||||
hotkeys::check_scroll_hotkeys(ui, false);
|
|
||||||
render_table(
|
|
||||||
ui,
|
|
||||||
available_width / 2.0,
|
|
||||||
1,
|
|
||||||
appearance.code_font.size,
|
|
||||||
instructions_len,
|
|
||||||
|row, column| {
|
|
||||||
if let Some(action) =
|
|
||||||
asm_col_ui(row, ctx, appearance, ins_view_state, column)
|
|
||||||
{
|
|
||||||
ret = Some(action);
|
|
||||||
}
|
|
||||||
if row.response().clicked() {
|
|
||||||
ret = Some(DiffViewAction::ClearDiffHighlight);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else if let Some((right_ctx, right_symbol_ref)) =
|
|
||||||
right_ctx.and_then(|ctx| ctx.symbol_ref.map(|symbol_ref| (ctx, symbol_ref)))
|
|
||||||
{
|
|
||||||
if let Some(action) = symbol_list_ui(
|
|
||||||
ui,
|
|
||||||
SymbolDiffContext { obj: ctx.obj, diff: ctx.diff },
|
|
||||||
None,
|
|
||||||
symbol_state,
|
|
||||||
SymbolFilter::Mapping(right_symbol_ref),
|
|
||||||
appearance,
|
|
||||||
column,
|
|
||||||
open_sections.0,
|
|
||||||
) {
|
|
||||||
match action {
|
|
||||||
DiffViewAction::Navigate(DiffViewNavigation {
|
|
||||||
left_symbol: Some(left_symbol_ref),
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
let (right_section, right_symbol) =
|
|
||||||
right_ctx.obj.section_symbol(right_symbol_ref);
|
|
||||||
ret = Some(DiffViewAction::SetMapping(
|
|
||||||
match right_section.map(|s| s.kind) {
|
|
||||||
Some(ObjSectionKind::Code) => View::FunctionDiff,
|
|
||||||
_ => View::SymbolDiff,
|
|
||||||
},
|
|
||||||
left_symbol_ref,
|
|
||||||
SymbolRefByName::new(right_symbol, right_section),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
ret = Some(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ui.label("No left object");
|
|
||||||
}
|
|
||||||
} else if column == 1 {
|
|
||||||
if let Some(ctx) = right_ctx {
|
|
||||||
if ctx.has_symbol() {
|
|
||||||
hotkeys::check_scroll_hotkeys(ui, false);
|
|
||||||
render_table(
|
|
||||||
ui,
|
|
||||||
available_width / 2.0,
|
|
||||||
1,
|
|
||||||
appearance.code_font.size,
|
|
||||||
instructions_len,
|
|
||||||
|row, column| {
|
|
||||||
if let Some(action) =
|
|
||||||
asm_col_ui(row, ctx, appearance, ins_view_state, column)
|
|
||||||
{
|
|
||||||
ret = Some(action);
|
|
||||||
}
|
|
||||||
if row.response().clicked() {
|
|
||||||
ret = Some(DiffViewAction::ClearDiffHighlight);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else if let Some((left_ctx, left_symbol_ref)) =
|
|
||||||
left_ctx.and_then(|ctx| ctx.symbol_ref.map(|symbol_ref| (ctx, symbol_ref)))
|
|
||||||
{
|
|
||||||
if let Some(action) = symbol_list_ui(
|
|
||||||
ui,
|
|
||||||
SymbolDiffContext { obj: ctx.obj, diff: ctx.diff },
|
|
||||||
None,
|
|
||||||
symbol_state,
|
|
||||||
SymbolFilter::Mapping(left_symbol_ref),
|
|
||||||
appearance,
|
|
||||||
column,
|
|
||||||
open_sections.1,
|
|
||||||
) {
|
|
||||||
match action {
|
|
||||||
DiffViewAction::Navigate(DiffViewNavigation {
|
|
||||||
right_symbol: Some(right_symbol_ref),
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
let (left_section, left_symbol) =
|
|
||||||
left_ctx.obj.section_symbol(left_symbol_ref);
|
|
||||||
ret = Some(DiffViewAction::SetMapping(
|
|
||||||
match left_section.map(|s| s.kind) {
|
|
||||||
Some(ObjSectionKind::Code) => View::FunctionDiff,
|
|
||||||
_ => View::SymbolDiff,
|
|
||||||
},
|
|
||||||
SymbolRefByName::new(left_symbol, left_section),
|
|
||||||
right_symbol_ref,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
ret = Some(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ui.label("No right object");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct FunctionDiffContext<'a> {
|
pub struct FunctionDiffContext<'a> {
|
||||||
pub obj: &'a ObjInfo,
|
pub obj: &'a ObjInfo,
|
||||||
pub diff: &'a ObjDiff,
|
pub diff: &'a ObjDiff,
|
||||||
pub symbol_ref: Option<SymbolRef>,
|
pub symbol_ref: Option<SymbolRef>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FunctionDiffContext<'a> {
|
|
||||||
pub fn new(
|
|
||||||
obj: Option<&'a (ObjInfo, ObjDiff)>,
|
|
||||||
selected_symbol: Option<&SymbolRefByName>,
|
|
||||||
) -> Option<Self> {
|
|
||||||
obj.map(|(obj, diff)| Self {
|
|
||||||
obj,
|
|
||||||
diff,
|
|
||||||
symbol_ref: selected_symbol.and_then(|s| find_symbol(obj, s)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn has_symbol(&self) -> bool { self.symbol_ref.is_some() }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn function_diff_ui(
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
state: &DiffViewState,
|
|
||||||
appearance: &Appearance,
|
|
||||||
) -> Option<DiffViewAction> {
|
|
||||||
let mut ret = None;
|
|
||||||
let Some(result) = &state.build else {
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut left_ctx = FunctionDiffContext::new(
|
|
||||||
result.first_obj.as_ref(),
|
|
||||||
state.symbol_state.left_symbol.as_ref(),
|
|
||||||
);
|
|
||||||
let mut right_ctx = FunctionDiffContext::new(
|
|
||||||
result.second_obj.as_ref(),
|
|
||||||
state.symbol_state.right_symbol.as_ref(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// If one side is missing a symbol, but the diff process found a match, use that symbol
|
|
||||||
let left_diff_symbol = left_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol)
|
|
||||||
});
|
|
||||||
let right_diff_symbol = right_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol)
|
|
||||||
});
|
|
||||||
if left_diff_symbol.is_some() && right_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
|
|
||||||
let (right_section, right_symbol) =
|
|
||||||
right_ctx.unwrap().obj.section_symbol(left_diff_symbol.unwrap());
|
|
||||||
let symbol_ref = SymbolRefByName::new(right_symbol, right_section);
|
|
||||||
right_ctx = FunctionDiffContext::new(result.second_obj.as_ref(), Some(&symbol_ref));
|
|
||||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation {
|
|
||||||
view: Some(View::FunctionDiff),
|
|
||||||
left_symbol: state.symbol_state.left_symbol.clone(),
|
|
||||||
right_symbol: Some(symbol_ref),
|
|
||||||
}));
|
|
||||||
} else if right_diff_symbol.is_some() && left_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
|
|
||||||
let (left_section, left_symbol) =
|
|
||||||
left_ctx.unwrap().obj.section_symbol(right_diff_symbol.unwrap());
|
|
||||||
let symbol_ref = SymbolRefByName::new(left_symbol, left_section);
|
|
||||||
left_ctx = FunctionDiffContext::new(result.first_obj.as_ref(), Some(&symbol_ref));
|
|
||||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation {
|
|
||||||
view: Some(View::FunctionDiff),
|
|
||||||
left_symbol: Some(symbol_ref),
|
|
||||||
right_symbol: state.symbol_state.right_symbol.clone(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If both sides are missing a symbol, switch to symbol diff view
|
|
||||||
if right_ctx.is_some_and(|ctx| !ctx.has_symbol())
|
|
||||||
&& left_ctx.is_some_and(|ctx| !ctx.has_symbol())
|
|
||||||
{
|
|
||||||
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header
|
|
||||||
let available_width = ui.available_width();
|
|
||||||
let mut open_sections = (None, None);
|
|
||||||
render_header(ui, available_width, 2, |ui, column| {
|
|
||||||
if column == 0 {
|
|
||||||
// Left column
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) {
|
|
||||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
|
||||||
}
|
|
||||||
ui.separator();
|
|
||||||
if ui
|
|
||||||
.add_enabled(
|
|
||||||
!state.scratch_running
|
|
||||||
&& state.scratch_available
|
|
||||||
&& left_ctx.is_some_and(|ctx| ctx.has_symbol()),
|
|
||||||
egui::Button::new("📲 decomp.me"),
|
|
||||||
)
|
|
||||||
.on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)")
|
|
||||||
.on_disabled_hover_text("Scratch configuration missing")
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
if let Some((_section, symbol)) = left_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.map(|symbol_ref| ctx.obj.section_symbol(symbol_ref))
|
|
||||||
}) {
|
|
||||||
ret = Some(DiffViewAction::CreateScratch(symbol.name.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some((_section, symbol)) = left_ctx
|
|
||||||
.and_then(|ctx| ctx.symbol_ref.map(|symbol_ref| ctx.obj.section_symbol(symbol_ref)))
|
|
||||||
{
|
|
||||||
let name = symbol.demangled_name.as_deref().unwrap_or(&symbol.name);
|
|
||||||
ui.label(
|
|
||||||
RichText::new(name)
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.highlight_color),
|
|
||||||
);
|
|
||||||
if right_ctx.is_some_and(|m| m.has_symbol())
|
|
||||||
&& (ui
|
|
||||||
.button("Change target")
|
|
||||||
.on_hover_text_at_pointer("Choose a different symbol to use as the target")
|
|
||||||
.clicked()
|
|
||||||
|| hotkeys::consume_change_target_shortcut(ui.ctx()))
|
|
||||||
{
|
|
||||||
if let Some(symbol_ref) = state.symbol_state.right_symbol.as_ref() {
|
|
||||||
ret = Some(DiffViewAction::SelectingLeft(symbol_ref.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Missing")
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.replace_color),
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Choose target symbol")
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.highlight_color),
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| {
|
|
||||||
if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() {
|
|
||||||
open_sections.0 = Some(true);
|
|
||||||
}
|
|
||||||
if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked()
|
|
||||||
{
|
|
||||||
open_sections.0 = Some(false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if column == 1 {
|
|
||||||
// Right column
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() {
|
|
||||||
ret = Some(DiffViewAction::Build);
|
|
||||||
}
|
|
||||||
ui.scope(|ui| {
|
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
|
||||||
if state.build_running {
|
|
||||||
ui.colored_label(appearance.replace_color, "Building…");
|
|
||||||
} else {
|
|
||||||
ui.label("Last built:");
|
|
||||||
let format = format_description::parse("[hour]:[minute]:[second]").unwrap();
|
|
||||||
ui.label(
|
|
||||||
result.time.to_offset(appearance.utc_offset).format(&format).unwrap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ui.separator();
|
|
||||||
if ui
|
|
||||||
.add_enabled(state.source_path_available, egui::Button::new("🖹 Source file"))
|
|
||||||
.on_hover_text_at_pointer("Open the source file in the default editor")
|
|
||||||
.on_disabled_hover_text("Source file metadata missing")
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
ret = Some(DiffViewAction::OpenSourcePath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(((_section, symbol), symbol_diff)) = right_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.map(|symbol_ref| {
|
|
||||||
(ctx.obj.section_symbol(symbol_ref), ctx.diff.symbol_diff(symbol_ref))
|
|
||||||
})
|
|
||||||
}) {
|
|
||||||
let name = symbol.demangled_name.as_deref().unwrap_or(&symbol.name);
|
|
||||||
ui.label(
|
|
||||||
RichText::new(name)
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.highlight_color),
|
|
||||||
);
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if let Some(match_percent) = symbol_diff.match_percent {
|
|
||||||
ui.label(
|
|
||||||
RichText::new(format!("{:.0}%", match_percent.floor()))
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(match_color_for_symbol(match_percent, appearance)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if left_ctx.is_some_and(|m| m.has_symbol()) {
|
|
||||||
ui.separator();
|
|
||||||
if ui
|
|
||||||
.button("Change base")
|
|
||||||
.on_hover_text_at_pointer(
|
|
||||||
"Choose a different symbol to use as the base",
|
|
||||||
)
|
|
||||||
.clicked()
|
|
||||||
|| hotkeys::consume_change_base_shortcut(ui.ctx())
|
|
||||||
{
|
|
||||||
if let Some(symbol_ref) = state.symbol_state.left_symbol.as_ref() {
|
|
||||||
ret = Some(DiffViewAction::SelectingRight(symbol_ref.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Missing")
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.replace_color),
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Choose base symbol")
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.highlight_color),
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| {
|
|
||||||
if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() {
|
|
||||||
open_sections.1 = Some(true);
|
|
||||||
}
|
|
||||||
if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked()
|
|
||||||
{
|
|
||||||
open_sections.1 = Some(false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Table
|
|
||||||
let id = Id::new(state.symbol_state.left_symbol.as_ref().map(|s| s.symbol_name.as_str()))
|
|
||||||
.with(state.symbol_state.right_symbol.as_ref().map(|s| s.symbol_name.as_str()));
|
|
||||||
if let Some(action) = ui
|
|
||||||
.push_id(id, |ui| {
|
|
||||||
asm_table_ui(
|
|
||||||
ui,
|
|
||||||
available_width,
|
|
||||||
left_ctx,
|
|
||||||
right_ctx,
|
|
||||||
appearance,
|
|
||||||
&state.function_state,
|
|
||||||
&state.symbol_state,
|
|
||||||
open_sections,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.inner
|
|
||||||
{
|
|
||||||
ret = Some(action);
|
|
||||||
}
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
@ -6,6 +6,7 @@ pub(crate) mod config;
|
|||||||
pub(crate) mod data_diff;
|
pub(crate) mod data_diff;
|
||||||
pub(crate) mod debug;
|
pub(crate) mod debug;
|
||||||
pub(crate) mod demangle;
|
pub(crate) mod demangle;
|
||||||
|
pub(crate) mod diff;
|
||||||
pub(crate) mod extab_diff;
|
pub(crate) mod extab_diff;
|
||||||
pub(crate) mod file;
|
pub(crate) mod file;
|
||||||
pub(crate) mod frame_history;
|
pub(crate) mod frame_history;
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
use std::{collections::BTreeMap, mem::take, ops::Bound};
|
use std::{collections::BTreeMap, mem::take, ops::Bound};
|
||||||
|
|
||||||
use egui::{
|
use egui::{
|
||||||
style::ScrollAnimation, text::LayoutJob, CollapsingHeader, Color32, Id, Layout, OpenUrl,
|
style::ScrollAnimation, text::LayoutJob, CollapsingHeader, Color32, Id, OpenUrl, ScrollArea,
|
||||||
ScrollArea, SelectableLabel, TextEdit, Ui, Widget,
|
SelectableLabel, Ui, Widget,
|
||||||
};
|
};
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
arch::ObjArch,
|
arch::ObjArch,
|
||||||
build::BuildStatus,
|
|
||||||
diff::{display::HighlightKind, ObjDiff, ObjSymbolDiff},
|
diff::{display::HighlightKind, ObjDiff, ObjSymbolDiff},
|
||||||
jobs::{create_scratch::CreateScratchResult, objdiff::ObjDiffResult, Job, JobQueue, JobResult},
|
jobs::{create_scratch::CreateScratchResult, objdiff::ObjDiffResult, Job, JobQueue, JobResult},
|
||||||
obj::{
|
obj::{
|
||||||
@ -19,15 +18,10 @@ use crate::{
|
|||||||
app::AppStateRef,
|
app::AppStateRef,
|
||||||
hotkeys,
|
hotkeys,
|
||||||
jobs::{is_create_scratch_available, start_create_scratch},
|
jobs::{is_create_scratch_available, start_create_scratch},
|
||||||
views::{
|
views::{appearance::Appearance, function_diff::FunctionViewState, write_text},
|
||||||
appearance::Appearance,
|
|
||||||
column_layout::{render_header, render_strips},
|
|
||||||
function_diff::FunctionViewState,
|
|
||||||
write_text,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub struct SymbolRefByName {
|
pub struct SymbolRefByName {
|
||||||
pub symbol_name: String,
|
pub symbol_name: String,
|
||||||
pub section_name: Option<String>,
|
pub section_name: Option<String>,
|
||||||
@ -79,16 +73,16 @@ pub enum DiffViewAction {
|
|||||||
SetShowMappedSymbols(bool),
|
SetShowMappedSymbols(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default, Eq, PartialEq)]
|
||||||
pub struct DiffViewNavigation {
|
pub struct DiffViewNavigation {
|
||||||
pub view: Option<View>,
|
pub view: View,
|
||||||
pub left_symbol: Option<SymbolRefByName>,
|
pub left_symbol: Option<SymbolRefByName>,
|
||||||
pub right_symbol: Option<SymbolRefByName>,
|
pub right_symbol: Option<SymbolRefByName>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiffViewNavigation {
|
impl DiffViewNavigation {
|
||||||
pub fn symbol_diff() -> Self {
|
pub fn symbol_diff() -> Self {
|
||||||
Self { view: Some(View::SymbolDiff), left_symbol: None, right_symbol: None }
|
Self { view: View::SymbolDiff, left_symbol: None, right_symbol: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_symbols(
|
pub fn with_symbols(
|
||||||
@ -107,8 +101,24 @@ impl DiffViewNavigation {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
match column {
|
match column {
|
||||||
0 => Self { view: Some(view), left_symbol: symbol1, right_symbol: symbol2 },
|
0 => Self { view, left_symbol: symbol1, right_symbol: symbol2 },
|
||||||
1 => Self { view: Some(view), left_symbol: symbol2, right_symbol: symbol1 },
|
1 => Self { view, left_symbol: symbol2, right_symbol: symbol1 },
|
||||||
|
_ => unreachable!("Invalid column index"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn data_diff(section: &ObjSection, column: usize) -> Self {
|
||||||
|
let symbol = Some(SymbolRefByName {
|
||||||
|
symbol_name: "".to_string(),
|
||||||
|
section_name: Some(section.name.clone()),
|
||||||
|
});
|
||||||
|
match column {
|
||||||
|
0 => Self {
|
||||||
|
view: View::DataDiff,
|
||||||
|
left_symbol: symbol.clone(),
|
||||||
|
right_symbol: symbol.clone(),
|
||||||
|
},
|
||||||
|
1 => Self { view: View::DataDiff, left_symbol: symbol.clone(), right_symbol: symbol },
|
||||||
_ => unreachable!("Invalid column index"),
|
_ => unreachable!("Invalid column index"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,9 +161,7 @@ impl DiffViewState {
|
|||||||
|
|
||||||
// TODO: where should this go?
|
// TODO: where should this go?
|
||||||
if let Some(result) = self.post_build_nav.take() {
|
if let Some(result) = self.post_build_nav.take() {
|
||||||
if let Some(view) = result.view {
|
self.current_view = result.view;
|
||||||
self.current_view = view;
|
|
||||||
}
|
|
||||||
self.symbol_state.left_symbol = result.left_symbol;
|
self.symbol_state.left_symbol = result.left_symbol;
|
||||||
self.symbol_state.right_symbol = result.right_symbol;
|
self.symbol_state.right_symbol = result.right_symbol;
|
||||||
}
|
}
|
||||||
@ -219,7 +227,7 @@ impl DiffViewState {
|
|||||||
};
|
};
|
||||||
if (nav.left_symbol.is_some() && nav.right_symbol.is_some())
|
if (nav.left_symbol.is_some() && nav.right_symbol.is_some())
|
||||||
|| (nav.left_symbol.is_none() && nav.right_symbol.is_none())
|
|| (nav.left_symbol.is_none() && nav.right_symbol.is_none())
|
||||||
|| nav.view != Some(View::FunctionDiff)
|
|| nav.view != View::FunctionDiff
|
||||||
{
|
{
|
||||||
// Regular navigation
|
// Regular navigation
|
||||||
if state.is_selecting_symbol() {
|
if state.is_selecting_symbol() {
|
||||||
@ -228,9 +236,7 @@ impl DiffViewState {
|
|||||||
self.post_build_nav = Some(nav);
|
self.post_build_nav = Some(nav);
|
||||||
} else {
|
} else {
|
||||||
// Navigate immediately
|
// Navigate immediately
|
||||||
if let Some(view) = nav.view {
|
self.current_view = nav.view;
|
||||||
self.current_view = view;
|
|
||||||
}
|
|
||||||
self.symbol_state.left_symbol = nav.left_symbol;
|
self.symbol_state.left_symbol = nav.left_symbol;
|
||||||
self.symbol_state.right_symbol = nav.right_symbol;
|
self.symbol_state.right_symbol = nav.right_symbol;
|
||||||
}
|
}
|
||||||
@ -301,7 +307,7 @@ impl DiffViewState {
|
|||||||
};
|
};
|
||||||
state.set_selecting_left(&right_ref.symbol_name);
|
state.set_selecting_left(&right_ref.symbol_name);
|
||||||
self.post_build_nav = Some(DiffViewNavigation {
|
self.post_build_nav = Some(DiffViewNavigation {
|
||||||
view: Some(View::FunctionDiff),
|
view: View::FunctionDiff,
|
||||||
left_symbol: None,
|
left_symbol: None,
|
||||||
right_symbol: Some(right_ref),
|
right_symbol: Some(right_ref),
|
||||||
});
|
});
|
||||||
@ -316,7 +322,7 @@ impl DiffViewState {
|
|||||||
};
|
};
|
||||||
state.set_selecting_right(&left_ref.symbol_name);
|
state.set_selecting_right(&left_ref.symbol_name);
|
||||||
self.post_build_nav = Some(DiffViewNavigation {
|
self.post_build_nav = Some(DiffViewNavigation {
|
||||||
view: Some(View::FunctionDiff),
|
view: View::FunctionDiff,
|
||||||
left_symbol: Some(left_ref),
|
left_symbol: Some(left_ref),
|
||||||
right_symbol: None,
|
right_symbol: None,
|
||||||
});
|
});
|
||||||
@ -337,7 +343,7 @@ impl DiffViewState {
|
|||||||
self.post_build_nav = Some(DiffViewNavigation::symbol_diff());
|
self.post_build_nav = Some(DiffViewNavigation::symbol_diff());
|
||||||
} else {
|
} else {
|
||||||
self.post_build_nav = Some(DiffViewNavigation {
|
self.post_build_nav = Some(DiffViewNavigation {
|
||||||
view: Some(view),
|
view,
|
||||||
left_symbol: Some(left_ref),
|
left_symbol: Some(left_ref),
|
||||||
right_symbol: Some(right_ref),
|
right_symbol: Some(right_ref),
|
||||||
});
|
});
|
||||||
@ -409,13 +415,13 @@ fn symbol_context_menu_ui(
|
|||||||
let symbol_ref = SymbolRefByName::new(symbol, Some(section));
|
let symbol_ref = SymbolRefByName::new(symbol, Some(section));
|
||||||
if column == 0 {
|
if column == 0 {
|
||||||
ret = Some(DiffViewNavigation {
|
ret = Some(DiffViewNavigation {
|
||||||
view: Some(View::FunctionDiff),
|
view: View::FunctionDiff,
|
||||||
left_symbol: Some(symbol_ref),
|
left_symbol: Some(symbol_ref),
|
||||||
right_symbol: None,
|
right_symbol: None,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
ret = Some(DiffViewNavigation {
|
ret = Some(DiffViewNavigation {
|
||||||
view: Some(View::FunctionDiff),
|
view: View::FunctionDiff,
|
||||||
left_symbol: None,
|
left_symbol: None,
|
||||||
right_symbol: Some(symbol_ref),
|
right_symbol: Some(symbol_ref),
|
||||||
});
|
});
|
||||||
@ -552,13 +558,8 @@ fn symbol_ui(
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
ObjSectionKind::Data => {
|
ObjSectionKind::Data => {
|
||||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::with_symbols(
|
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::data_diff(
|
||||||
View::DataDiff,
|
section, column,
|
||||||
other_ctx,
|
|
||||||
symbol,
|
|
||||||
section,
|
|
||||||
symbol_diff,
|
|
||||||
column,
|
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
ObjSectionKind::Bss => {}
|
ObjSectionKind::Bss => {}
|
||||||
@ -591,9 +592,15 @@ fn symbol_matches_filter(
|
|||||||
SymbolFilter::None => true,
|
SymbolFilter::None => true,
|
||||||
SymbolFilter::Search(regex) => {
|
SymbolFilter::Search(regex) => {
|
||||||
regex.is_match(&symbol.name)
|
regex.is_match(&symbol.name)
|
||||||
|| symbol.demangled_name.as_ref().map(|s| regex.is_match(s)).unwrap_or(false)
|
|| symbol.demangled_name.as_deref().is_some_and(|s| regex.is_match(s))
|
||||||
|
}
|
||||||
|
SymbolFilter::Mapping(symbol_ref, regex) => {
|
||||||
|
diff.target_symbol == Some(symbol_ref)
|
||||||
|
&& regex.is_none_or(|r| {
|
||||||
|
r.is_match(&symbol.name)
|
||||||
|
|| symbol.demangled_name.as_deref().is_some_and(|s| r.is_match(s))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
SymbolFilter::Mapping(symbol_ref) => diff.target_symbol == Some(symbol_ref),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -601,7 +608,7 @@ fn symbol_matches_filter(
|
|||||||
pub enum SymbolFilter<'a> {
|
pub enum SymbolFilter<'a> {
|
||||||
None,
|
None,
|
||||||
Search(&'a Regex),
|
Search(&'a Regex),
|
||||||
Mapping(SymbolRef),
|
Mapping(SymbolRef, Option<&'a Regex>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -619,21 +626,23 @@ pub fn symbol_list_ui(
|
|||||||
let mut ret = None;
|
let mut ret = None;
|
||||||
ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| {
|
ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| {
|
||||||
let mut mapping = BTreeMap::new();
|
let mut mapping = BTreeMap::new();
|
||||||
if let SymbolFilter::Mapping(target_ref) = filter {
|
if let SymbolFilter::Mapping(_, _) = filter {
|
||||||
let mut show_mapped_symbols = state.show_mapped_symbols;
|
let mut show_mapped_symbols = state.show_mapped_symbols;
|
||||||
if ui.checkbox(&mut show_mapped_symbols, "Show mapped symbols").changed() {
|
if ui.checkbox(&mut show_mapped_symbols, "Show mapped symbols").changed() {
|
||||||
ret = Some(DiffViewAction::SetShowMappedSymbols(show_mapped_symbols));
|
ret = Some(DiffViewAction::SetShowMappedSymbols(show_mapped_symbols));
|
||||||
}
|
}
|
||||||
for mapping_diff in &ctx.diff.mapping_symbols {
|
for mapping_diff in &ctx.diff.mapping_symbols {
|
||||||
if mapping_diff.target_symbol == Some(target_ref) {
|
let symbol = ctx.obj.section_symbol(mapping_diff.symbol_ref).1;
|
||||||
if !show_mapped_symbols {
|
if !symbol_matches_filter(symbol, mapping_diff, filter) {
|
||||||
let symbol_diff = ctx.diff.symbol_diff(mapping_diff.symbol_ref);
|
continue;
|
||||||
if symbol_diff.target_symbol.is_some() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mapping.insert(mapping_diff.symbol_ref, mapping_diff);
|
|
||||||
}
|
}
|
||||||
|
if !show_mapped_symbols {
|
||||||
|
let symbol_diff = ctx.diff.symbol_diff(mapping_diff.symbol_ref);
|
||||||
|
if symbol_diff.target_symbol.is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mapping.insert(mapping_diff.symbol_ref, mapping_diff);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (symbol, diff) in ctx.obj.common.iter().zip(&ctx.diff.common) {
|
for (symbol, diff) in ctx.obj.common.iter().zip(&ctx.diff.common) {
|
||||||
@ -819,206 +828,8 @@ pub fn symbol_list_ui(
|
|||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_log_ui(ui: &mut Ui, status: &BuildStatus, appearance: &Appearance) {
|
|
||||||
ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if !status.cmdline.is_empty() && ui.button("Copy command").clicked() {
|
|
||||||
ui.output_mut(|output| output.copied_text.clone_from(&status.cmdline));
|
|
||||||
}
|
|
||||||
if ui.button("Copy log").clicked() {
|
|
||||||
ui.output_mut(|output| {
|
|
||||||
output.copied_text = format!("{}\n{}", status.stdout, status.stderr)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ui.scope(|ui| {
|
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
|
||||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
|
||||||
|
|
||||||
if !status.cmdline.is_empty() {
|
|
||||||
ui.label(&status.cmdline);
|
|
||||||
}
|
|
||||||
if !status.stdout.is_empty() {
|
|
||||||
ui.colored_label(appearance.replace_color, &status.stdout);
|
|
||||||
}
|
|
||||||
if !status.stderr.is_empty() {
|
|
||||||
ui.colored_label(appearance.delete_color, &status.stderr);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn missing_obj_ui(ui: &mut Ui, appearance: &Appearance) {
|
|
||||||
ui.scope(|ui| {
|
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
|
||||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
|
||||||
|
|
||||||
ui.colored_label(appearance.replace_color, "No object configured");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct SymbolDiffContext<'a> {
|
pub struct SymbolDiffContext<'a> {
|
||||||
pub obj: &'a ObjInfo,
|
pub obj: &'a ObjInfo,
|
||||||
pub diff: &'a ObjDiff,
|
pub diff: &'a ObjDiff,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn symbol_diff_ui(
|
|
||||||
ui: &mut Ui,
|
|
||||||
state: &mut DiffViewState,
|
|
||||||
appearance: &Appearance,
|
|
||||||
) -> Option<DiffViewAction> {
|
|
||||||
let mut ret = None;
|
|
||||||
let Some(result) = &state.build else {
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Header
|
|
||||||
let available_width = ui.available_width();
|
|
||||||
let mut open_sections = (None, None);
|
|
||||||
render_header(ui, available_width, 2, |ui, column| {
|
|
||||||
if column == 0 {
|
|
||||||
// Left column
|
|
||||||
ui.scope(|ui| {
|
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
|
||||||
|
|
||||||
ui.label("Target object");
|
|
||||||
if result.first_status.success {
|
|
||||||
if result.first_obj.is_none() {
|
|
||||||
ui.colored_label(appearance.replace_color, "Missing");
|
|
||||||
} else {
|
|
||||||
ui.colored_label(appearance.highlight_color, state.object_name.clone());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ui.colored_label(appearance.delete_color, "Fail");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let mut search = state.search.clone();
|
|
||||||
let response = TextEdit::singleline(&mut search).hint_text("Filter symbols").ui(ui);
|
|
||||||
if hotkeys::consume_symbol_filter_shortcut(ui.ctx()) {
|
|
||||||
response.request_focus();
|
|
||||||
}
|
|
||||||
if response.changed() {
|
|
||||||
ret = Some(DiffViewAction::SetSearch(search));
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| {
|
|
||||||
if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() {
|
|
||||||
open_sections.0 = Some(true);
|
|
||||||
}
|
|
||||||
if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked() {
|
|
||||||
open_sections.0 = Some(false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
} else if column == 1 {
|
|
||||||
// Right column
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.scope(|ui| {
|
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
|
||||||
ui.label("Base object");
|
|
||||||
});
|
|
||||||
ui.separator();
|
|
||||||
if ui
|
|
||||||
.add_enabled(state.source_path_available, egui::Button::new("🖹 Source file"))
|
|
||||||
.on_hover_text_at_pointer("Open the source file in the default editor")
|
|
||||||
.on_disabled_hover_text("Source file metadata missing")
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
ret = Some(DiffViewAction::OpenSourcePath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.scope(|ui| {
|
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
|
||||||
if result.second_status.success {
|
|
||||||
if result.second_obj.is_none() {
|
|
||||||
ui.colored_label(appearance.replace_color, "Missing");
|
|
||||||
} else {
|
|
||||||
ui.colored_label(appearance.highlight_color, "OK");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ui.colored_label(appearance.delete_color, "Fail");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() {
|
|
||||||
ret = Some(DiffViewAction::Build);
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| {
|
|
||||||
if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() {
|
|
||||||
open_sections.1 = Some(true);
|
|
||||||
}
|
|
||||||
if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked() {
|
|
||||||
open_sections.1 = Some(false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Table
|
|
||||||
let filter = match &state.search_regex {
|
|
||||||
Some(regex) => SymbolFilter::Search(regex),
|
|
||||||
_ => SymbolFilter::None,
|
|
||||||
};
|
|
||||||
render_strips(ui, available_width, 2, |ui, column| {
|
|
||||||
if column == 0 {
|
|
||||||
// Left column
|
|
||||||
if result.first_status.success {
|
|
||||||
if let Some((obj, diff)) = &result.first_obj {
|
|
||||||
if let Some(result) = symbol_list_ui(
|
|
||||||
ui,
|
|
||||||
SymbolDiffContext { obj, diff },
|
|
||||||
result
|
|
||||||
.second_obj
|
|
||||||
.as_ref()
|
|
||||||
.map(|(obj, diff)| SymbolDiffContext { obj, diff }),
|
|
||||||
&state.symbol_state,
|
|
||||||
filter,
|
|
||||||
appearance,
|
|
||||||
column,
|
|
||||||
open_sections.0,
|
|
||||||
) {
|
|
||||||
ret = Some(result);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
missing_obj_ui(ui, appearance);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
build_log_ui(ui, &result.first_status, appearance);
|
|
||||||
}
|
|
||||||
} else if column == 1 {
|
|
||||||
// Right column
|
|
||||||
if result.second_status.success {
|
|
||||||
if let Some((obj, diff)) = &result.second_obj {
|
|
||||||
if let Some(result) = symbol_list_ui(
|
|
||||||
ui,
|
|
||||||
SymbolDiffContext { obj, diff },
|
|
||||||
result
|
|
||||||
.first_obj
|
|
||||||
.as_ref()
|
|
||||||
.map(|(obj, diff)| SymbolDiffContext { obj, diff }),
|
|
||||||
&state.symbol_state,
|
|
||||||
filter,
|
|
||||||
appearance,
|
|
||||||
column,
|
|
||||||
open_sections.1,
|
|
||||||
) {
|
|
||||||
ret = Some(result);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
missing_obj_ui(ui, appearance);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
build_log_ui(ui, &result.second_status, appearance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user