mirror of
https://github.com/encounter/objdiff.git
synced 2025-12-19 09:55:28 +00:00
Add symbol mapping feature (#118)
This allows users to "map" (or "link") symbols with different names so that they can be compared without having to update either the target or base objects. Symbol mappings are persisted in objdiff.json, so generators will need to ensure that they're preserved when updating. (Example: d1334bb79e)
Resolves #117
This commit is contained in:
@@ -1,28 +1,29 @@
|
||||
use std::default::Default;
|
||||
|
||||
use egui::{text::LayoutJob, Align, Label, Layout, Response, Sense, Vec2, Widget};
|
||||
use egui_extras::{Column, TableBuilder, TableRow};
|
||||
use egui::{text::LayoutJob, Id, Label, Response, RichText, Sense, Widget};
|
||||
use egui_extras::TableRow;
|
||||
use objdiff_core::{
|
||||
arch::ObjArch,
|
||||
diff::{
|
||||
display::{display_diff, DiffText, HighlightKind},
|
||||
ObjDiff, ObjInsDiff, ObjInsDiffKind,
|
||||
},
|
||||
obj::{ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjSection, ObjSymbol, SymbolRef},
|
||||
obj::{
|
||||
ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjSection, ObjSectionKind, ObjSymbol,
|
||||
SymbolRef,
|
||||
},
|
||||
};
|
||||
use time::format_description;
|
||||
|
||||
use crate::views::{
|
||||
appearance::Appearance,
|
||||
symbol_diff::{match_color_for_symbol, DiffViewState, SymbolRefByName, View},
|
||||
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(Copy, Clone, Eq, PartialEq)]
|
||||
enum ColumnId {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FunctionViewState {
|
||||
left_highlight: HighlightKind,
|
||||
@@ -30,16 +31,17 @@ pub struct FunctionViewState {
|
||||
}
|
||||
|
||||
impl FunctionViewState {
|
||||
fn highlight(&self, column: ColumnId) -> &HighlightKind {
|
||||
pub fn highlight(&self, column: usize) -> &HighlightKind {
|
||||
match column {
|
||||
ColumnId::Left => &self.left_highlight,
|
||||
ColumnId::Right => &self.right_highlight,
|
||||
0 => &self.left_highlight,
|
||||
1 => &self.right_highlight,
|
||||
_ => &HighlightKind::None,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_highlight(&mut self, column: ColumnId, highlight: HighlightKind) {
|
||||
pub fn set_highlight(&mut self, column: usize, highlight: HighlightKind) {
|
||||
match column {
|
||||
ColumnId::Left => {
|
||||
0 => {
|
||||
if highlight == self.left_highlight {
|
||||
if highlight == self.right_highlight {
|
||||
self.left_highlight = HighlightKind::None;
|
||||
@@ -51,7 +53,7 @@ impl FunctionViewState {
|
||||
self.left_highlight = highlight;
|
||||
}
|
||||
}
|
||||
ColumnId::Right => {
|
||||
1 => {
|
||||
if highlight == self.right_highlight {
|
||||
if highlight == self.left_highlight {
|
||||
self.left_highlight = HighlightKind::None;
|
||||
@@ -63,10 +65,11 @@ impl FunctionViewState {
|
||||
self.right_highlight = highlight;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_highlight(&mut self) {
|
||||
pub fn clear_highlight(&mut self) {
|
||||
self.left_highlight = HighlightKind::None;
|
||||
self.right_highlight = HighlightKind::None;
|
||||
}
|
||||
@@ -223,17 +226,19 @@ fn find_symbol(obj: &ObjInfo, selected_symbol: &SymbolRefByName) -> Option<Symbo
|
||||
None
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[must_use]
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn diff_text_ui(
|
||||
ui: &mut egui::Ui,
|
||||
text: DiffText<'_>,
|
||||
ins_diff: &ObjInsDiff,
|
||||
appearance: &Appearance,
|
||||
ins_view_state: &mut FunctionViewState,
|
||||
column: ColumnId,
|
||||
ins_view_state: &FunctionViewState,
|
||||
column: usize,
|
||||
space_width: f32,
|
||||
response_cb: impl Fn(Response) -> Response,
|
||||
) {
|
||||
) -> Option<DiffViewAction> {
|
||||
let mut ret = None;
|
||||
let label_text;
|
||||
let mut base_color = match ins_diff.kind {
|
||||
ObjInsDiffKind::None | ObjInsDiffKind::OpMismatch | ObjInsDiffKind::ArgMismatch => {
|
||||
@@ -287,7 +292,7 @@ fn diff_text_ui(
|
||||
}
|
||||
DiffText::Spacing(n) => {
|
||||
ui.add_space(n as f32 * space_width);
|
||||
return;
|
||||
return ret;
|
||||
}
|
||||
DiffText::Eol => {
|
||||
label_text = "\n".to_string();
|
||||
@@ -304,22 +309,25 @@ fn diff_text_ui(
|
||||
.ui(ui);
|
||||
response = response_cb(response);
|
||||
if response.clicked() {
|
||||
ins_view_state.set_highlight(column, text.into());
|
||||
ret = Some(DiffViewAction::SetDiffHighlight(column, text.into()));
|
||||
}
|
||||
if len < pad_to {
|
||||
ui.add_space((pad_to - len) as f32 * space_width);
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn asm_row_ui(
|
||||
ui: &mut egui::Ui,
|
||||
ins_diff: &ObjInsDiff,
|
||||
symbol: &ObjSymbol,
|
||||
appearance: &Appearance,
|
||||
ins_view_state: &mut FunctionViewState,
|
||||
column: ColumnId,
|
||||
ins_view_state: &FunctionViewState,
|
||||
column: usize,
|
||||
response_cb: impl Fn(Response) -> Response,
|
||||
) {
|
||||
) -> Option<DiffViewAction> {
|
||||
let mut ret = None;
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||
if ins_diff.kind != ObjInsDiffKind::None {
|
||||
@@ -327,7 +335,7 @@ fn asm_row_ui(
|
||||
}
|
||||
let space_width = ui.fonts(|f| f.glyph_width(&appearance.code_font, ' '));
|
||||
display_diff(ins_diff, symbol.address, |text| {
|
||||
diff_text_ui(
|
||||
if let Some(action) = diff_text_ui(
|
||||
ui,
|
||||
text,
|
||||
ins_diff,
|
||||
@@ -336,246 +344,476 @@ fn asm_row_ui(
|
||||
column,
|
||||
space_width,
|
||||
&response_cb,
|
||||
);
|
||||
) {
|
||||
ret = Some(action);
|
||||
}
|
||||
Ok::<_, ()>(())
|
||||
})
|
||||
.unwrap();
|
||||
ret
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn asm_col_ui(
|
||||
row: &mut TableRow<'_, '_>,
|
||||
obj: &(ObjInfo, ObjDiff),
|
||||
symbol_ref: SymbolRef,
|
||||
ctx: FunctionDiffContext<'_>,
|
||||
appearance: &Appearance,
|
||||
ins_view_state: &mut FunctionViewState,
|
||||
column: ColumnId,
|
||||
) {
|
||||
let (section, symbol) = obj.0.section_symbol(symbol_ref);
|
||||
let section = section.unwrap();
|
||||
let ins_diff = &obj.1.symbol_diff(symbol_ref).instructions[row.index()];
|
||||
ins_view_state: &FunctionViewState,
|
||||
column: usize,
|
||||
) -> Option<DiffViewAction> {
|
||||
let mut ret = None;
|
||||
let symbol_ref = ctx.symbol_ref?;
|
||||
let (section, symbol) = ctx.obj.section_symbol(symbol_ref);
|
||||
let section = section?;
|
||||
let ins_diff = &ctx.diff.symbol_diff(symbol_ref).instructions[row.index()];
|
||||
let response_cb = |response: Response| {
|
||||
if let Some(ins) = &ins_diff.ins {
|
||||
response.context_menu(|ui| ins_context_menu(ui, section, ins, symbol));
|
||||
response.on_hover_ui_at_pointer(|ui| {
|
||||
ins_hover_ui(ui, obj.0.arch.as_ref(), section, ins, symbol, appearance)
|
||||
ins_hover_ui(ui, ctx.obj.arch.as_ref(), section, ins, symbol, appearance)
|
||||
})
|
||||
} else {
|
||||
response
|
||||
}
|
||||
};
|
||||
let (_, response) = row.col(|ui| {
|
||||
asm_row_ui(ui, ins_diff, symbol, appearance, ins_view_state, column, response_cb);
|
||||
if let Some(action) =
|
||||
asm_row_ui(ui, ins_diff, symbol, appearance, ins_view_state, column, response_cb)
|
||||
{
|
||||
ret = Some(action);
|
||||
}
|
||||
});
|
||||
response_cb(response);
|
||||
ret
|
||||
}
|
||||
|
||||
fn empty_col_ui(row: &mut TableRow<'_, '_>) {
|
||||
row.col(|ui| {
|
||||
ui.label("");
|
||||
});
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn asm_table_ui(
|
||||
table: TableBuilder<'_>,
|
||||
left_obj: Option<&(ObjInfo, ObjDiff)>,
|
||||
right_obj: Option<&(ObjInfo, ObjDiff)>,
|
||||
selected_symbol: &SymbolRefByName,
|
||||
ui: &mut egui::Ui,
|
||||
available_width: f32,
|
||||
left_ctx: Option<FunctionDiffContext<'_>>,
|
||||
right_ctx: Option<FunctionDiffContext<'_>>,
|
||||
appearance: &Appearance,
|
||||
ins_view_state: &mut FunctionViewState,
|
||||
) -> Option<()> {
|
||||
let left_symbol = left_obj.and_then(|(obj, _)| find_symbol(obj, selected_symbol));
|
||||
let right_symbol = right_obj.and_then(|(obj, _)| find_symbol(obj, selected_symbol));
|
||||
let instructions_len = match (left_symbol, right_symbol) {
|
||||
(Some(left_symbol_ref), Some(right_symbol_ref)) => {
|
||||
let left_len = left_obj.unwrap().1.symbol_diff(left_symbol_ref).instructions.len();
|
||||
let right_len = right_obj.unwrap().1.symbol_diff(right_symbol_ref).instructions.len();
|
||||
debug_assert_eq!(left_len, right_len);
|
||||
ins_view_state: &FunctionViewState,
|
||||
symbol_state: &SymbolViewState,
|
||||
) -> 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_symbol_ref), None) => {
|
||||
left_obj.unwrap().1.symbol_diff(left_symbol_ref).instructions.len()
|
||||
(Some(left_len), None) => left_len,
|
||||
(None, Some(right_len)) => right_len,
|
||||
(None, None) => {
|
||||
ui.label("No symbol selected");
|
||||
return None;
|
||||
}
|
||||
(None, Some(right_symbol_ref)) => {
|
||||
right_obj.unwrap().1.symbol_diff(right_symbol_ref).instructions.len()
|
||||
}
|
||||
(None, None) => return None,
|
||||
};
|
||||
table.body(|body| {
|
||||
body.rows(appearance.code_font.size, instructions_len, |mut row| {
|
||||
row.set_hovered(false); // Disable row hover effect
|
||||
if let (Some(left_obj), Some(left_symbol_ref)) = (left_obj, left_symbol) {
|
||||
asm_col_ui(
|
||||
&mut row,
|
||||
left_obj,
|
||||
left_symbol_ref,
|
||||
appearance,
|
||||
ins_view_state,
|
||||
ColumnId::Left,
|
||||
);
|
||||
} else {
|
||||
empty_col_ui(&mut row);
|
||||
}
|
||||
if let (Some(right_obj), Some(right_symbol_ref)) = (right_obj, right_symbol) {
|
||||
asm_col_ui(
|
||||
&mut row,
|
||||
right_obj,
|
||||
right_symbol_ref,
|
||||
appearance,
|
||||
ins_view_state,
|
||||
ColumnId::Right,
|
||||
);
|
||||
} else {
|
||||
empty_col_ui(&mut row);
|
||||
}
|
||||
if row.response().clicked() {
|
||||
ins_view_state.clear_highlight();
|
||||
if left_len.is_some() && right_len.is_some() {
|
||||
// Joint view
|
||||
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() {
|
||||
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,
|
||||
) {
|
||||
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),
|
||||
));
|
||||
}
|
||||
DiffViewAction::SetSymbolHighlight(_, _) => {
|
||||
// Ignore
|
||||
}
|
||||
_ => {
|
||||
ret = Some(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ui.label("No left object");
|
||||
}
|
||||
} else if column == 1 {
|
||||
if let Some(ctx) = right_ctx {
|
||||
if ctx.has_symbol() {
|
||||
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,
|
||||
) {
|
||||
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,
|
||||
));
|
||||
}
|
||||
DiffViewAction::SetSymbolHighlight(_, _) => {
|
||||
// Ignore
|
||||
}
|
||||
_ => {
|
||||
ret = Some(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ui.label("No right object");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Some(())
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn function_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance: &Appearance) {
|
||||
let (Some(result), Some(selected_symbol)) = (&state.build, &state.symbol_state.selected_symbol)
|
||||
else {
|
||||
return;
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct FunctionDiffContext<'a> {
|
||||
pub obj: &'a ObjInfo,
|
||||
pub diff: &'a ObjDiff,
|
||||
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.map_or(false, |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.map_or(false, |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.map_or(false, |ctx| !ctx.has_symbol())
|
||||
&& left_ctx.map_or(false, |ctx| !ctx.has_symbol())
|
||||
{
|
||||
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
||||
}
|
||||
|
||||
// Header
|
||||
let available_width = ui.available_width();
|
||||
let column_width = available_width / 2.0;
|
||||
ui.allocate_ui_with_layout(
|
||||
Vec2 { x: available_width, y: 100.0 },
|
||||
Layout::left_to_right(Align::Min),
|
||||
|ui| {
|
||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate);
|
||||
|
||||
render_header(ui, available_width, 2, |ui, column| {
|
||||
if column == 0 {
|
||||
// Left column
|
||||
ui.allocate_ui_with_layout(
|
||||
Vec2 { x: column_width, y: 100.0 },
|
||||
Layout::top_down(Align::Min),
|
||||
|ui| {
|
||||
ui.set_width(column_width);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("⏴ Back").clicked() {
|
||||
state.current_view = View::SymbolDiff;
|
||||
}
|
||||
ui.separator();
|
||||
if ui
|
||||
.add_enabled(
|
||||
!state.scratch_running && state.scratch_available,
|
||||
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()
|
||||
{
|
||||
state.queue_scratch = true;
|
||||
}
|
||||
});
|
||||
|
||||
let name = selected_symbol
|
||||
.demangled_symbol_name
|
||||
.as_deref()
|
||||
.unwrap_or(&selected_symbol.symbol_name);
|
||||
ui.scope(|ui| {
|
||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||
ui.colored_label(appearance.highlight_color, name);
|
||||
ui.label("Diff target:");
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("⏴ Back").clicked() {
|
||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
||||
}
|
||||
ui.separator();
|
||||
if ui
|
||||
.add_enabled(
|
||||
!state.scratch_running
|
||||
&& state.scratch_available
|
||||
&& left_ctx.map_or(false, |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.map_or(false, |m| m.has_symbol())
|
||||
&& ui
|
||||
.button("Change target")
|
||||
.on_hover_text_at_pointer("Choose a different symbol to use as the target")
|
||||
.clicked()
|
||||
{
|
||||
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.label(
|
||||
RichText::new("Choose target symbol")
|
||||
.font(appearance.code_font.clone())
|
||||
.color(appearance.highlight_color),
|
||||
);
|
||||
}
|
||||
} else if column == 1 {
|
||||
// Right column
|
||||
ui.allocate_ui_with_layout(
|
||||
Vec2 { x: column_width, y: 100.0 },
|
||||
Layout::top_down(Align::Min),
|
||||
|ui| {
|
||||
ui.set_width(column_width);
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
if ui
|
||||
.add_enabled(!state.build_running, egui::Button::new("Build"))
|
||||
.clicked()
|
||||
{
|
||||
state.queue_build = true;
|
||||
}
|
||||
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, 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.map_or(false, |m| m.has_symbol()) {
|
||||
ui.separator();
|
||||
if ui
|
||||
.add_enabled(
|
||||
state.source_path_available,
|
||||
egui::Button::new("🖹 Source file"),
|
||||
.button("Change base")
|
||||
.on_hover_text_at_pointer(
|
||||
"Choose a different symbol to use as the base",
|
||||
)
|
||||
.on_hover_text_at_pointer("Open the source file in the default editor")
|
||||
.on_disabled_hover_text("Source file metadata missing")
|
||||
.clicked()
|
||||
{
|
||||
state.queue_open_source_path = true;
|
||||
if let Some(symbol_ref) = state.symbol_state.left_symbol.as_ref() {
|
||||
ret = Some(DiffViewAction::SelectingRight(symbol_ref.clone()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.scope(|ui| {
|
||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||
if let Some(match_percent) = result
|
||||
.second_obj
|
||||
.as_ref()
|
||||
.and_then(|(obj, diff)| {
|
||||
find_symbol(obj, selected_symbol).map(|sref| {
|
||||
&diff.sections[sref.section_idx].symbols[sref.symbol_idx]
|
||||
})
|
||||
})
|
||||
.and_then(|symbol| symbol.match_percent)
|
||||
{
|
||||
ui.colored_label(
|
||||
match_color_for_symbol(match_percent, appearance),
|
||||
format!("{:.0}%", match_percent.floor()),
|
||||
);
|
||||
} else {
|
||||
ui.colored_label(appearance.replace_color, "Missing");
|
||||
}
|
||||
ui.label("Diff base:");
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
ui.separator();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ui.label(
|
||||
RichText::new("Missing")
|
||||
.font(appearance.code_font.clone())
|
||||
.color(appearance.replace_color),
|
||||
);
|
||||
ui.label(
|
||||
RichText::new("Choose base symbol")
|
||||
.font(appearance.code_font.clone())
|
||||
.color(appearance.highlight_color),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Table
|
||||
ui.style_mut().interaction.selectable_labels = false;
|
||||
let available_height = ui.available_height();
|
||||
let table = TableBuilder::new(ui)
|
||||
.striped(false)
|
||||
.cell_layout(Layout::left_to_right(Align::Min))
|
||||
.columns(Column::exact(column_width).clip(true), 2)
|
||||
.resizable(false)
|
||||
.auto_shrink([false, false])
|
||||
.min_scrolled_height(available_height)
|
||||
.sense(Sense::click());
|
||||
asm_table_ui(
|
||||
table,
|
||||
result.first_obj.as_ref(),
|
||||
result.second_obj.as_ref(),
|
||||
selected_symbol,
|
||||
appearance,
|
||||
&mut state.function_state,
|
||||
);
|
||||
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,
|
||||
)
|
||||
})
|
||||
.inner
|
||||
{
|
||||
ret = Some(action);
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user