From 192a06bc0b39d43b30bbbf6401ac133ee2483843 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sat, 9 Sep 2023 23:43:12 -0400 Subject: [PATCH] Project configuration improvements - Support `completed` field for objects in project config. In object tree, displays red for incomplete, green for complete. - Add support for one-sided diffs. A project can include objects without an associated source file for viewing. - Add versioning to AppConfig, supporting upgrades without losing user configuration. --- Cargo.lock | 1 + Cargo.toml | 1 + src/app.rs | 45 +++++++++++-- src/app_config.rs | 96 +++++++++++++++++++++++++++ src/config.rs | 1 + src/diff.rs | 129 ++++++++++++++++++++++--------------- src/jobs/objdiff.rs | 127 ++++++++++++++++++++++++------------ src/lib.rs | 1 + src/views/config.rs | 71 ++++++++++++++------ src/views/data_diff.rs | 52 +++++++++------ src/views/function_diff.rs | 36 ++++++----- src/views/symbol_diff.rs | 31 +++++++-- 12 files changed, 431 insertions(+), 160 deletions(-) create mode 100644 src/app_config.rs diff --git a/Cargo.lock b/Cargo.lock index 4dd9729..30775c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2483,6 +2483,7 @@ dependencies = [ "rabbitizer", "reqwest", "rfd", + "ron", "self_update", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 0acde2c..beecfe4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ png = "0.17.9" ppc750cl = { git = "https://github.com/terorie/ppc750cl", rev = "9ae36eef34aa6d74e00972c7671f547a2acfd0aa" } rabbitizer = "1.7.4" rfd = { version = "0.11.4" } #, default-features = false, features = ['xdg-portal'] +ron = "0.8.0" serde = { version = "1", features = ["derive"] } serde_json = "1.0.104" serde_yaml = "0.9.25" diff --git a/src/app.rs b/src/app.rs index ebf8075..197d960 100644 --- a/src/app.rs +++ b/src/app.rs @@ -14,13 +14,14 @@ use notify::{RecursiveMode, Watcher}; use time::UtcOffset; use crate::{ + app_config::{deserialize_config, AppConfigVersion}, config::{ build_globset, load_project_config, ProjectObject, ProjectObjectNode, CONFIG_FILENAMES, }, jobs::{objdiff::start_build, Job, JobQueue, JobResult, JobStatus}, views::{ appearance::{appearance_window, Appearance}, - config::{config_ui, project_window, ConfigViewState}, + config::{config_ui, project_window, ConfigViewState, DEFAULT_WATCH_PATTERNS}, data_diff::data_diff_ui, demangle::{demangle_window, DemangleViewState}, function_diff::function_diff_ui, @@ -44,16 +45,21 @@ pub struct ViewState { #[derive(Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct ObjectConfig { pub name: String, - pub target_path: PathBuf, - pub base_path: PathBuf, + pub target_path: Option, + pub base_path: Option, pub reverse_fn_order: Option, + pub complete: Option, } #[inline] fn bool_true() -> bool { true } -#[derive(Default, Clone, serde::Deserialize, serde::Serialize)] +#[derive(Clone, serde::Deserialize, serde::Serialize)] pub struct AppConfig { + // TODO: https://github.com/ron-rs/ron/pull/455 + // #[serde(flatten)] + // pub version: AppConfigVersion, + pub version: u32, pub custom_make: Option, pub selected_wsl_distro: Option, pub project_dir: Option, @@ -82,6 +88,31 @@ pub struct AppConfig { pub project_config_loaded: bool, } +impl Default for AppConfig { + fn default() -> Self { + Self { + version: AppConfigVersion::default().version, + custom_make: None, + selected_wsl_distro: None, + project_dir: None, + target_obj_dir: None, + base_obj_dir: None, + selected_obj: None, + build_target: false, + rebuild_on_changes: true, + auto_update_check: true, + watch_patterns: DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect(), + objects: vec![], + object_nodes: vec![], + watcher_change: false, + config_change: false, + obj_change: false, + queue_build: false, + project_config_loaded: false, + } + } +} + impl AppConfig { pub fn set_project_dir(&mut self, path: PathBuf) { self.project_dir = Some(path); @@ -133,8 +164,8 @@ pub struct App { should_relaunch: bool, } -const APPEARANCE_KEY: &str = "appearance"; -const CONFIG_KEY: &str = "app_config"; +pub const APPEARANCE_KEY: &str = "appearance"; +pub const CONFIG_KEY: &str = "app_config"; impl App { /// Called once before the first frame. @@ -153,7 +184,7 @@ impl App { if let Some(appearance) = eframe::get_value::(storage, APPEARANCE_KEY) { app.appearance = appearance; } - if let Some(mut config) = eframe::get_value::(storage, CONFIG_KEY) { + if let Some(mut config) = deserialize_config(storage) { if config.project_dir.is_some() { config.config_change = true; config.watcher_change = true; diff --git a/src/app_config.rs b/src/app_config.rs new file mode 100644 index 0000000..e7df92b --- /dev/null +++ b/src/app_config.rs @@ -0,0 +1,96 @@ +use std::path::PathBuf; + +use eframe::Storage; +use globset::Glob; + +use crate::app::{AppConfig, ObjectConfig, CONFIG_KEY}; + +#[derive(Clone, serde::Deserialize, serde::Serialize)] +pub struct AppConfigVersion { + pub version: u32, +} + +impl Default for AppConfigVersion { + fn default() -> Self { Self { version: 1 } } +} + +/// Deserialize the AppConfig from storage, handling upgrades from older versions. +pub fn deserialize_config(storage: &dyn Storage) -> Option { + let str = storage.get_string(CONFIG_KEY)?; + match ron::from_str::(&str) { + Ok(version) => match version.version { + 1 => from_str::(&str), + _ => { + log::warn!("Unknown config version: {}", version.version); + None + } + }, + Err(e) => { + log::warn!("Failed to decode config version: {e}"); + // Try to decode as v0 + from_str::(&str).map(|c| c.into_config()) + } + } +} + +fn from_str(str: &str) -> Option +where T: serde::de::DeserializeOwned { + match ron::from_str(str) { + Ok(config) => Some(config), + Err(err) => { + log::warn!("Failed to decode config: {err}"); + None + } + } +} + +#[derive(serde::Deserialize, serde::Serialize)] +pub struct ObjectConfigV0 { + pub name: String, + pub target_path: PathBuf, + pub base_path: PathBuf, + pub reverse_fn_order: Option, +} + +impl ObjectConfigV0 { + fn into_config(self) -> ObjectConfig { + ObjectConfig { + name: self.name, + target_path: Some(self.target_path), + base_path: Some(self.base_path), + reverse_fn_order: self.reverse_fn_order, + complete: None, + } + } +} + +#[derive(serde::Deserialize, serde::Serialize)] +pub struct AppConfigV0 { + pub custom_make: Option, + pub selected_wsl_distro: Option, + pub project_dir: Option, + pub target_obj_dir: Option, + pub base_obj_dir: Option, + pub selected_obj: Option, + pub build_target: bool, + pub auto_update_check: bool, + pub watch_patterns: Vec, +} + +impl AppConfigV0 { + fn into_config(self) -> AppConfig { + log::info!("Upgrading configuration from v0"); + AppConfig { + custom_make: self.custom_make, + selected_wsl_distro: self.selected_wsl_distro, + project_dir: self.project_dir, + target_obj_dir: self.target_obj_dir, + base_obj_dir: self.base_obj_dir, + selected_obj: self.selected_obj.map(|obj| obj.into_config()), + build_target: self.build_target, + auto_update_check: self.auto_update_check, + watch_patterns: self.watch_patterns, + ..Default::default() + } + } +} diff --git a/src/config.rs b/src/config.rs index b2827ec..841a9fb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -27,6 +27,7 @@ pub struct ProjectObject { pub target_path: Option, pub base_path: Option, pub reverse_fn_order: Option, + pub complete: Option, } impl ProjectObject { diff --git a/src/diff.rs b/src/diff.rs index d373207..718b989 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -372,66 +372,80 @@ fn find_section_and_symbol(obj: &ObjInfo, name: &str) -> Option<(usize, usize)> None } -pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo) -> Result<()> { - for left_section in &mut left.sections { - if left_section.kind == ObjSectionKind::Code { - for left_symbol in &mut left_section.symbols { - if let Some((right_section_idx, right_symbol_idx)) = - find_section_and_symbol(right, &left_symbol.name) - { - let right_section = &mut right.sections[right_section_idx]; - let right_symbol = &mut right_section.symbols[right_symbol_idx]; - left_symbol.diff_symbol = Some(right_symbol.name.clone()); - right_symbol.diff_symbol = Some(left_symbol.name.clone()); - diff_code( - left.architecture, - &left_section.data, - &right_section.data, - left_symbol, - right_symbol, - &left_section.relocations, - &right_section.relocations, - &left.line_info, - &right.line_info, - )?; - } else { - no_diff_code( - left.architecture, - &left_section.data, - left_symbol, - &left_section.relocations, - &left.line_info, - )?; +pub fn diff_objs(mut left: Option<&mut ObjInfo>, mut right: Option<&mut ObjInfo>) -> Result<()> { + if let Some(left) = left.as_mut() { + for left_section in &mut left.sections { + if left_section.kind == ObjSectionKind::Code { + for left_symbol in &mut left_section.symbols { + if let Some((right, (right_section_idx, right_symbol_idx))) = + right.as_mut().and_then(|obj| { + find_section_and_symbol(obj, &left_symbol.name).map(|s| (obj, s)) + }) + { + let right_section = &mut right.sections[right_section_idx]; + let right_symbol = &mut right_section.symbols[right_symbol_idx]; + left_symbol.diff_symbol = Some(right_symbol.name.clone()); + right_symbol.diff_symbol = Some(left_symbol.name.clone()); + diff_code( + left.architecture, + &left_section.data, + &right_section.data, + left_symbol, + right_symbol, + &left_section.relocations, + &right_section.relocations, + &left.line_info, + &right.line_info, + )?; + } else { + no_diff_code( + left.architecture, + &left_section.data, + left_symbol, + &left_section.relocations, + &left.line_info, + )?; + } } - } - } else { - let Some(right_section) = - right.sections.iter_mut().find(|s| s.name == left_section.name) - else { - continue; - }; - if left_section.kind == ObjSectionKind::Data { - diff_data(left_section, right_section); - // diff_data_symbols(left_section, right_section)?; - } else if left_section.kind == ObjSectionKind::Bss { - diff_bss_symbols(&mut left_section.symbols, &mut right_section.symbols)?; + } else if let Some(right_section) = right + .as_mut() + .and_then(|obj| obj.sections.iter_mut().find(|s| s.name == left_section.name)) + { + if left_section.kind == ObjSectionKind::Data { + diff_data(left_section, right_section); + // diff_data_symbols(left_section, right_section)?; + } else if left_section.kind == ObjSectionKind::Bss { + diff_bss_symbols(&mut left_section.symbols, &mut right_section.symbols)?; + } + } else if left_section.kind == ObjSectionKind::Data { + no_diff_data(left_section); } } } - for right_section in right.sections.iter_mut().filter(|s| s.kind == ObjSectionKind::Code) { - for right_symbol in &mut right_section.symbols { - if right_symbol.instructions.is_empty() { - no_diff_code( - right.architecture, - &right_section.data, - right_symbol, - &right_section.relocations, - &right.line_info, - )?; + if let Some(right) = right.as_mut() { + for right_section in right.sections.iter_mut() { + if right_section.kind == ObjSectionKind::Code { + for right_symbol in &mut right_section.symbols { + if right_symbol.instructions.is_empty() { + no_diff_code( + right.architecture, + &right_section.data, + right_symbol, + &right_section.relocations, + &right.line_info, + )?; + } + } + } else if right_section.kind == ObjSectionKind::Data + && right_section.data_diff.is_empty() + { + no_diff_data(right_section); } } } - diff_bss_symbols(&mut left.common, &mut right.common)?; + if let (Some(left), Some(right)) = (left, right) { + diff_bss_symbols(&mut left.common, &mut right.common)?; + } Ok(()) } @@ -710,3 +724,12 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) { left.data_diff = left_diff; right.data_diff = right_diff; } + +fn no_diff_data(section: &mut ObjSection) { + section.data_diff = vec![ObjDataDiff { + data: section.data.clone(), + kind: ObjDataDiffKind::None, + len: section.data.len(), + symbol: String::new(), + }]; +} diff --git a/src/jobs/objdiff.rs b/src/jobs/objdiff.rs index 149ce99..54e3c36 100644 --- a/src/jobs/objdiff.rs +++ b/src/jobs/objdiff.rs @@ -79,56 +79,101 @@ fn run_build( let obj_config = config.selected_obj.as_ref().ok_or_else(|| Error::msg("Missing obj path"))?; let project_dir = config.project_dir.as_ref().ok_or_else(|| Error::msg("Missing project dir"))?; - let target_path_rel = obj_config.target_path.strip_prefix(project_dir).map_err(|_| { - anyhow!( - "Target path '{}' doesn't begin with '{}'", - obj_config.target_path.display(), - project_dir.display() - ) - })?; - let base_path_rel = obj_config.base_path.strip_prefix(project_dir).map_err(|_| { - anyhow!( - "Base path '{}' doesn't begin with '{}'", - obj_config.base_path.display(), - project_dir.display() - ) - })?; + let target_path_rel = if let Some(target_path) = &obj_config.target_path { + Some(target_path.strip_prefix(project_dir).map_err(|_| { + anyhow!( + "Target path '{}' doesn't begin with '{}'", + target_path.display(), + project_dir.display() + ) + })?) + } else { + None + }; + let base_path_rel = if let Some(base_path) = &obj_config.base_path { + Some(base_path.strip_prefix(project_dir).map_err(|_| { + anyhow!( + "Base path '{}' doesn't begin with '{}'", + base_path.display(), + project_dir.display() + ) + })?) + } else { + None + }; - let total = if config.build_target { 5 } else { 4 }; - let first_status = if config.build_target { - update_status(status, format!("Building target {}", target_path_rel.display()), 0, total, &cancel)?; - run_make(project_dir, target_path_rel, &config) + let mut total = 3; + if config.build_target && target_path_rel.is_some() { + total += 1; + } + if base_path_rel.is_some() { + total += 1; + } + let first_status = match target_path_rel { + Some(target_path_rel) if config.build_target => { + update_status( + status, + format!("Building target {}", target_path_rel.display()), + 0, + total, + &cancel, + )?; + run_make(project_dir, target_path_rel, &config) + } + _ => BuildStatus { success: true, log: String::new() }, + }; + + let second_status = if let Some(base_path_rel) = base_path_rel { + update_status( + status, + format!("Building base {}", base_path_rel.display()), + 1, + total, + &cancel, + )?; + run_make(project_dir, base_path_rel, &config) } else { BuildStatus { success: true, log: String::new() } }; - update_status(status, format!("Building base {}", base_path_rel.display()), 1, total, &cancel)?; - let second_status = run_make(project_dir, base_path_rel, &config); - let time = OffsetDateTime::now_utc(); - let mut first_obj = if first_status.success { - update_status(status, format!("Loading target {}", target_path_rel.display()), 2, total, &cancel)?; - Some(elf::read(&obj_config.target_path).with_context(|| { - format!("Failed to read object '{}'", obj_config.target_path.display()) - })?) - } else { - None + let mut first_obj = + match &obj_config.target_path { + Some(target_path) if first_status.success => { + update_status( + status, + format!("Loading target {}", target_path_rel.unwrap().display()), + 2, + total, + &cancel, + )?; + Some(elf::read(target_path).with_context(|| { + format!("Failed to read object '{}'", target_path.display()) + })?) + } + _ => None, + }; + + let mut second_obj = match &obj_config.base_path { + Some(base_path) if second_status.success => { + update_status( + status, + format!("Loading base {}", base_path_rel.unwrap().display()), + 3, + total, + &cancel, + )?; + Some( + elf::read(base_path) + .with_context(|| format!("Failed to read object '{}'", base_path.display()))?, + ) + } + _ => None, }; - let mut second_obj = if second_status.success { - update_status(status, format!("Loading base {}", base_path_rel.display()), 3, total, &cancel)?; - Some(elf::read(&obj_config.base_path).with_context(|| { - format!("Failed to read object '{}'", obj_config.base_path.display()) - })?) - } else { - None - }; - - if let (Some(first_obj), Some(second_obj)) = (&mut first_obj, &mut second_obj) { - update_status(status, "Performing diff".to_string(), 4, total, &cancel)?; - diff_objs(first_obj, second_obj)?; - } + update_status(status, "Performing diff".to_string(), 4, total, &cancel)?; + diff_objs(first_obj.as_mut(), second_obj.as_mut())?; update_status(status, "Complete".to_string(), total, total, &cancel)?; Ok(Box::new(ObjDiffResult { first_status, second_status, first_obj, second_obj, time })) diff --git a/src/lib.rs b/src/lib.rs index fb48b8f..d2266d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub use app::App; mod app; +mod app_config; mod config; mod diff; mod editops; diff --git a/src/views/config.rs b/src/views/config.rs index 261c693..974315e 100644 --- a/src/views/config.rs +++ b/src/views/config.rs @@ -40,6 +40,7 @@ pub struct ConfigViewState { pub watch_pattern_text: String, pub load_error: Option, pub object_search: String, + pub filter_diffable: bool, #[cfg(windows)] pub available_wsl_distros: Option>, } @@ -218,17 +219,19 @@ pub fn config_ui( let target_path = target_dir.join(obj_path); new_selected_obj = Some(ObjectConfig { name: obj_path.display().to_string(), - target_path, - base_path: path, + target_path: Some(target_path), + base_path: Some(path), reverse_fn_order: None, + complete: None, }); } else if let Ok(obj_path) = path.strip_prefix(&target_dir) { let base_path = base_dir.join(obj_path); new_selected_obj = Some(ObjectConfig { name: obj_path.display().to_string(), - target_path: path, - base_path, + target_path: Some(path), + base_path: Some(base_path), reverse_fn_order: None, + complete: None, }); } } @@ -266,6 +269,13 @@ pub fn config_ui( root_open = Some(true); node_open = NodeOpen::Object; } + if ui + .selectable_label(state.filter_diffable, "Diffable") + .on_hover_text_at_pointer("Only show objects with a source file") + .clicked() + { + state.filter_diffable = !state.filter_diffable; + } }); if state.object_search.is_empty() { if had_search { @@ -285,10 +295,13 @@ pub fn config_ui( .default_open(true) .show(ui, |ui| { let mut nodes = Cow::Borrowed(object_nodes); - if !state.object_search.is_empty() { + if !state.object_search.is_empty() || state.filter_diffable { let search = state.object_search.to_ascii_lowercase(); nodes = Cow::Owned( - object_nodes.iter().filter_map(|node| filter_node(node, &search)).collect(), + object_nodes + .iter() + .filter_map(|node| filter_node(node, &search, state.filter_diffable)) + .collect(), ); } @@ -322,8 +335,18 @@ fn display_object( ) { let object_name = object.name(); let selected = matches!(selected_obj, Some(obj) if obj.name == object_name); - let color = if selected { appearance.emphasized_text_color } else { appearance.text_color }; - if SelectableLabel::new( + let color = if selected { + appearance.emphasized_text_color + } else if let Some(complete) = object.complete { + if complete { + appearance.insert_color + } else { + appearance.delete_color + } + } else { + appearance.text_color + }; + let clicked = SelectableLabel::new( selected, RichText::new(name) .font(FontId { @@ -333,13 +356,16 @@ fn display_object( .color(color), ) .ui(ui) - .clicked() - { + .clicked(); + // Always recreate ObjectConfig if selected, in case the project config changed. + // ObjectConfig is compared using equality, so this won't unnecessarily trigger a rebuild. + if selected || clicked { *selected_obj = Some(ObjectConfig { name: object_name.to_string(), - target_path: object.target_path.clone().unwrap_or_default(), - base_path: object.base_path.clone().unwrap_or_default(), + target_path: object.target_path.clone(), + base_path: object.base_path.clone(), reverse_fn_order: object.reverse_fn_order, + complete: object.complete, }); } } @@ -404,21 +430,30 @@ fn contains_node(node: &ProjectObjectNode, selected_obj: &ObjectConfig) -> bool } } -fn filter_node(node: &ProjectObjectNode, search: &str) -> Option { +fn filter_node( + node: &ProjectObjectNode, + search: &str, + filter_diffable: bool, +) -> Option { match node { - ProjectObjectNode::File(name, _) => { - if name.to_ascii_lowercase().contains(search) { + ProjectObjectNode::File(name, object) => { + if (search.is_empty() || name.to_ascii_lowercase().contains(search)) + && (!filter_diffable || object.base_path.is_some()) + { Some(node.clone()) } else { None } } ProjectObjectNode::Dir(name, children) => { - if name.to_ascii_lowercase().contains(search) { + if (search.is_empty() || name.to_ascii_lowercase().contains(search)) && !filter_diffable + { return Some(node.clone()); } - let new_children = - children.iter().filter_map(|child| filter_node(child, search)).collect::>(); + let new_children = children + .iter() + .filter_map(|child| filter_node(child, search, filter_diffable)) + .collect::>(); if !new_children.is_empty() { Some(ProjectObjectNode::Dir(name.clone(), new_children)) } else { diff --git a/src/views/data_diff.rs b/src/views/data_diff.rs index 2b2adb8..0a709f2 100644 --- a/src/views/data_diff.rs +++ b/src/views/data_diff.rs @@ -132,31 +132,39 @@ fn split_diffs(diffs: &[ObjDataDiff]) -> Vec> { fn data_table_ui( table: TableBuilder<'_>, - left_obj: &ObjInfo, - right_obj: &ObjInfo, + left_obj: Option<&ObjInfo>, + right_obj: Option<&ObjInfo>, selected_symbol: &SymbolReference, config: &Appearance, ) -> Option<()> { - let left_section = find_section(left_obj, selected_symbol)?; - let right_section = find_section(right_obj, selected_symbol)?; + let left_section = left_obj.and_then(|obj| find_section(obj, selected_symbol)); + let right_section = right_obj.and_then(|obj| find_section(obj, selected_symbol)); - let total_bytes = left_section.data_diff.iter().fold(0usize, |accum, item| accum + item.len); + let total_bytes = left_section + .or(right_section)? + .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 = split_diffs(&left_section.data_diff); - let right_diffs = split_diffs(&right_section.data_diff); + let left_diffs = left_section.map(|section| split_diffs(§ion.data_diff)); + let right_diffs = right_section.map(|section| split_diffs(§ion.data_diff)); table.body(|body| { body.rows(config.code_font.size, total_rows, |row_index, mut row| { let address = row_index * BYTES_PER_ROW; row.col(|ui| { - data_row_ui(ui, address, &left_diffs[row_index], config); + if let Some(left_diffs) = &left_diffs { + data_row_ui(ui, address, &left_diffs[row_index], config); + } }); row.col(|ui| { - data_row_ui(ui, address, &right_diffs[row_index], config); + if let Some(right_diffs) = &right_diffs { + data_row_ui(ui, address, &right_diffs[row_index], config); + } }); }); }); @@ -243,15 +251,19 @@ pub fn data_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance: &A ui.separator(); // Table - if let (Some(left_obj), Some(right_obj)) = (&result.first_obj, &result.second_obj) { - 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); - data_table_ui(table, left_obj, right_obj, selected_symbol, appearance); - } + 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); + data_table_ui( + table, + result.first_obj.as_ref(), + result.second_obj.as_ref(), + selected_symbol, + appearance, + ); } diff --git a/src/views/function_diff.rs b/src/views/function_diff.rs index ec92731..c827c1f 100644 --- a/src/views/function_diff.rs +++ b/src/views/function_diff.rs @@ -377,13 +377,13 @@ fn asm_row_ui( fn asm_table_ui( table: TableBuilder<'_>, - left_obj: &ObjInfo, - right_obj: &ObjInfo, + left_obj: Option<&ObjInfo>, + right_obj: Option<&ObjInfo>, selected_symbol: &SymbolReference, appearance: &Appearance, ) -> Option<()> { - let left_symbol = find_symbol(left_obj, selected_symbol); - let right_symbol = find_symbol(right_obj, selected_symbol); + 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 = left_symbol.or(right_symbol).map(|s| s.instructions.len())?; table.body(|body| { body.rows(appearance.code_font.size, instructions_len, |row_index, mut row| { @@ -492,7 +492,7 @@ pub fn function_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance &format!("{match_percent:.0}%"), ); } else { - ui.label(""); + ui.colored_label(appearance.replace_color, "Missing"); } ui.label("Diff base:"); }); @@ -503,15 +503,19 @@ pub fn function_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance ui.separator(); // Table - if let (Some(left_obj), Some(right_obj)) = (&result.first_obj, &result.second_obj) { - 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); - asm_table_ui(table, left_obj, right_obj, selected_symbol, appearance); - } + 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); + asm_table_ui( + table, + result.first_obj.as_ref(), + result.second_obj.as_ref(), + selected_symbol, + appearance, + ); } diff --git a/src/views/symbol_diff.rs b/src/views/symbol_diff.rs index 8a4a76d..35b74f1 100644 --- a/src/views/symbol_diff.rs +++ b/src/views/symbol_diff.rs @@ -1,7 +1,7 @@ use std::mem::take; use egui::{ - text::LayoutJob, Align, CollapsingHeader, Color32, Layout, Rgba, ScrollArea, SelectableLabel, + text::LayoutJob, Align, CollapsingHeader, Color32, Layout, ScrollArea, SelectableLabel, TextEdit, Ui, Vec2, Widget, }; use egui_extras::{Size, StripBuilder}; @@ -263,6 +263,15 @@ fn build_log_ui(ui: &mut Ui, status: &BuildStatus, appearance: &Appearance) { }); } +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 = Some(false); + + ui.colored_label(appearance.replace_color, "No object configured"); + }); +} + pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appearance) { let DiffViewState { build, current_view, symbol_state, search, .. } = state; let Some(result) = build else { @@ -289,9 +298,13 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea ui.label("Build target:"); if result.first_status.success { - ui.label("OK"); + if result.first_obj.is_none() { + ui.colored_label(appearance.replace_color, "Missing"); + } else { + ui.label("OK"); + } } else { - ui.colored_label(Rgba::from_rgb(1.0, 0.0, 0.0), "Fail"); + ui.colored_label(appearance.delete_color, "Fail"); } }); @@ -312,9 +325,13 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea ui.label("Build base:"); if result.second_status.success { - ui.label("OK"); + if result.second_obj.is_none() { + ui.colored_label(appearance.replace_color, "Missing"); + } else { + ui.label("OK"); + } } else { - ui.colored_label(Rgba::from_rgb(1.0, 0.0, 0.0), "Fail"); + ui.colored_label(appearance.delete_color, "Fail"); } }); @@ -348,6 +365,8 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea &lower_search, appearance, )); + } else { + missing_obj_ui(ui, appearance); } } else { build_log_ui(ui, &result.first_status, appearance); @@ -365,6 +384,8 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea &lower_search, appearance, )); + } else { + missing_obj_ui(ui, appearance); } } else { build_log_ui(ui, &result.second_status, appearance);