Document configuration file & more cleanup

This commit is contained in:
Luke Street 2023-08-12 14:18:09 -04:00
parent eaf0fabc2d
commit 3970bc8acf
16 changed files with 389 additions and 232 deletions

2
Cargo.lock generated
View File

@ -2457,7 +2457,7 @@ dependencies = [
[[package]] [[package]]
name = "objdiff" name = "objdiff"
version = "0.3.4" version = "0.4.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"byteorder", "byteorder",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "objdiff" name = "objdiff"
version = "0.3.4" version = "0.4.0"
edition = "2021" edition = "2021"
rust-version = "1.65" rust-version = "1.65"
authors = ["Luke Street <luke@street.dev>"] authors = ["Luke Street <luke@street.dev>"]

View File

@ -5,14 +5,87 @@
A local diffing tool for decompilation projects. A local diffing tool for decompilation projects.
Currently supports: Supports:
- PowerPC 750CL (GameCube & Wii) - PowerPC 750CL (GameCube & Wii)
- MIPS (Nintendo 64) - MIPS (Nintendo 64)
See [Usage](#usage) for more information.
![Symbol Screenshot](assets/screen-symbols.png) ![Symbol Screenshot](assets/screen-symbols.png)
![Diff Screenshot](assets/screen-diff.png) ![Diff Screenshot](assets/screen-diff.png)
### License ## Usage
objdiff works by comparing two relocatable object files (`.o`). The objects are expected to have the same relative path from the "target" and "base" directories.
For example, if the target ("expected") object is located at `build/asm/MetroTRK/mslsupp.o` and the base ("actual") object
is located at `build/src/MetroTRK/mslsupp.o`, the following configuration would be used:
- Target build directory: `build/asm`
- Base build directory: `build/src`
- Object: `MetroTRK/mslsupp.o`
objdiff will then execute the build system from the project directory to build both objects:
```sh
$ make build/asm/MetroTRK/mslsupp.o # Only if "Build target object" is enabled
$ make build/src/MetroTRK/mslsupp.o
```
The objects will then be compared and the results will be displayed in the UI.
See [Configuration](#configuration) for more information.
## Configuration
While **not required** (most settings can be specified in the UI), projects can add an `objdiff.json` (or `objdiff.yaml`, `objdiff.yml`) file to configure the tool automatically. The configuration file must be located in the root project directory.
```json5
// objdiff.json
{
"custom_make": "ninja",
"target_dir": "build/mp1.0/asm",
"base_dir": "build/mp1.0/src",
"build_target": true,
"watch_patterns": [
"*.c",
"*.cp",
"*.cpp",
"*.h",
"*.hpp",
"*.py"
],
"objects": [
{
"path": "MetroTRK/mslsupp.o",
"name": "MetroTRK/mslsupp",
"reverse_fn_order": false
},
// ...
]
}
```
- `custom_make` _(optional)_: By default, objdiff will use `make` to build the project.
If the project uses a different build system (e.g. `ninja`), specify it here.
- `target_dir`: Relative from the root of the project, this where the "target" or "expected" objects are located.
These are the **intended result** of the match.
- `base_dir`: Relative from the root of the project, this is where the "base" or "actual" objects are located.
These are objects built from the **current source code**.
- `build_target`: If true, objdiff will tell the build system to build the target objects before diffing (e.g. `make path/to/target.o`).
This is useful if the target objects are not built by default or can change based on project configuration or edits to assembly files.
Requires the build system to be configured properly.
- `watch_patterns` _(optional)_: A list of glob patterns to watch for changes. ([Supported syntax](https://docs.rs/globset/latest/globset/#syntax))
If any of these files change, objdiff will automatically rebuild the objects and re-compare them.
- `objects` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
- `path`: Relative path to the object from the `target_dir` and `base_dir`.
- `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
- `reverse_fn_order` _(optional)_: Displays function symbols in reversed order.
Used to support MWCC's `-inline deferred` option, which reverses the order of functions in the object file.
## License
Licensed under either of Licensed under either of

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 144 KiB

View File

@ -14,10 +14,10 @@ use notify::{RecursiveMode, Watcher};
use time::UtcOffset; use time::UtcOffset;
use crate::{ use crate::{
config::{build_globset, load_project_config, ProjectUnit, ProjectUnitNode, CONFIG_FILENAMES}, config::{
jobs::{ build_globset, load_project_config, ProjectObject, ProjectObjectNode, CONFIG_FILENAMES,
check_update::start_check_update, objdiff::start_build, Job, JobQueue, JobResult, JobStatus,
}, },
jobs::{objdiff::start_build, Job, JobQueue, JobResult, JobStatus},
views::{ views::{
appearance::{appearance_window, Appearance}, appearance::{appearance_window, Appearance},
config::{config_ui, project_window, ConfigViewState}, config::{config_ui, project_window, ConfigViewState},
@ -32,11 +32,11 @@ use crate::{
#[derive(Default)] #[derive(Default)]
pub struct ViewState { pub struct ViewState {
pub jobs: JobQueue, pub jobs: JobQueue,
pub show_appearance_config: bool,
pub demangle_state: DemangleViewState,
pub show_demangle: bool,
pub diff_state: DiffViewState,
pub config_state: ConfigViewState, pub config_state: ConfigViewState,
pub demangle_state: DemangleViewState,
pub diff_state: DiffViewState,
pub show_appearance_config: bool,
pub show_demangle: bool,
pub show_project_config: bool, pub show_project_config: bool,
} }
@ -54,15 +54,17 @@ pub struct AppConfig {
pub watch_patterns: Vec<Glob>, pub watch_patterns: Vec<Glob>,
#[serde(skip)] #[serde(skip)]
pub units: Vec<ProjectUnit>, pub objects: Vec<ProjectObject>,
#[serde(skip)] #[serde(skip)]
pub unit_nodes: Vec<ProjectUnitNode>, pub object_nodes: Vec<ProjectObjectNode>,
#[serde(skip)] #[serde(skip)]
pub watcher_change: bool, pub watcher_change: bool,
#[serde(skip)] #[serde(skip)]
pub config_change: bool, pub config_change: bool,
#[serde(skip)] #[serde(skip)]
pub obj_change: bool, pub obj_change: bool,
#[serde(skip)]
pub queue_build: bool,
} }
impl AppConfig { impl AppConfig {
@ -72,36 +74,42 @@ impl AppConfig {
self.base_obj_dir = None; self.base_obj_dir = None;
self.obj_path = None; self.obj_path = None;
self.build_target = false; self.build_target = false;
self.units.clear(); self.objects.clear();
self.unit_nodes.clear(); self.object_nodes.clear();
self.watcher_change = true; self.watcher_change = true;
self.config_change = true; self.config_change = true;
self.obj_change = true; self.obj_change = true;
self.queue_build = false;
} }
pub fn set_target_obj_dir(&mut self, path: PathBuf) { pub fn set_target_obj_dir(&mut self, path: PathBuf) {
self.target_obj_dir = Some(path); self.target_obj_dir = Some(path);
self.obj_path = None; self.obj_path = None;
self.obj_change = true; self.obj_change = true;
self.queue_build = false;
} }
pub fn set_base_obj_dir(&mut self, path: PathBuf) { pub fn set_base_obj_dir(&mut self, path: PathBuf) {
self.base_obj_dir = Some(path); self.base_obj_dir = Some(path);
self.obj_path = None; self.obj_path = None;
self.obj_change = true; self.obj_change = true;
self.queue_build = false;
} }
pub fn set_obj_path(&mut self, path: String) { pub fn set_obj_path(&mut self, path: String) {
self.obj_path = Some(path); self.obj_path = Some(path);
self.obj_change = true; self.obj_change = true;
self.queue_build = false;
} }
} }
pub type AppConfigRef = Arc<RwLock<AppConfig>>;
#[derive(Default)] #[derive(Default)]
pub struct App { pub struct App {
appearance: Appearance, appearance: Appearance,
view_state: ViewState, view_state: ViewState,
config: Arc<RwLock<AppConfig>>, config: AppConfigRef,
modified: Arc<AtomicBool>, modified: Arc<AtomicBool>,
config_modified: Arc<AtomicBool>, config_modified: Arc<AtomicBool>,
watcher: Option<notify::RecommendedWatcher>, watcher: Option<notify::RecommendedWatcher>,
@ -135,7 +143,7 @@ impl App {
config.watcher_change = true; config.watcher_change = true;
app.modified.store(true, Ordering::Relaxed); app.modified.store(true, Ordering::Relaxed);
} }
app.view_state.config_state.queue_update_check = config.auto_update_check; app.view_state.config_state.queue_check_update = config.auto_update_check;
app.config = Arc::new(RwLock::new(config)); app.config = Arc::new(RwLock::new(config));
} }
} }
@ -147,6 +155,7 @@ impl App {
fn pre_update(&mut self) { fn pre_update(&mut self) {
let ViewState { jobs, diff_state, config_state, .. } = &mut self.view_state; let ViewState { jobs, diff_state, config_state, .. } = &mut self.view_state;
let mut results = vec![];
for (job, result) in jobs.iter_finished() { for (job, result) in jobs.iter_finished() {
match result { match result {
Ok(result) => { Ok(result) => {
@ -157,18 +166,13 @@ impl App {
log::error!("{:?}", err); log::error!("{:?}", err);
} }
} }
JobResult::ObjDiff(state) => {
diff_state.build = Some(state);
}
JobResult::CheckUpdate(state) => {
config_state.check_update = Some(state);
}
JobResult::Update(state) => { JobResult::Update(state) => {
if let Ok(mut guard) = self.relaunch_path.lock() { if let Ok(mut guard) = self.relaunch_path.lock() {
*guard = Some(state.exe_path); *guard = Some(state.exe_path);
} }
self.should_relaunch = true; self.should_relaunch = true;
} }
_ => results.push(result),
} }
} }
Err(err) => { Err(err) => {
@ -195,11 +199,19 @@ impl App {
} }
} }
} }
jobs.results.append(&mut results);
jobs.clear_finished(); jobs.clear_finished();
diff_state.pre_update(jobs, &self.config);
config_state.pre_update(jobs);
debug_assert!(jobs.results.is_empty());
} }
fn post_update(&mut self) { fn post_update(&mut self) {
let ViewState { jobs, diff_state, config_state, .. } = &mut self.view_state; let ViewState { jobs, diff_state, config_state, .. } = &mut self.view_state;
config_state.post_update(jobs, &self.config);
diff_state.post_update(jobs, &self.config);
let Ok(mut config) = self.config.write() else { let Ok(mut config) = self.config.write() else {
return; return;
}; };
@ -244,22 +256,23 @@ impl App {
} }
} }
if config.obj_path.is_some()
&& self.modified.swap(false, Ordering::Relaxed)
&& !jobs.is_running(Job::ObjDiff)
{
jobs.push(start_build(self.config.clone()));
}
if config.obj_change { if config.obj_change {
*diff_state = Default::default(); *diff_state = Default::default();
jobs.push(start_build(self.config.clone())); if config.obj_path.is_some() {
config.queue_build = true;
}
config.obj_change = false; config.obj_change = false;
} }
if config_state.queue_update_check { if self.modified.swap(false, Ordering::Relaxed) {
jobs.push(start_check_update()); config.queue_build = true;
config_state.queue_update_check = false; }
// Don't clear `queue_build` if a build is running. A file may have been modified during
// the build, so we'll start another build after the current one finishes.
if config.queue_build && config.obj_path.is_some() && !jobs.is_running(Job::ObjDiff) {
jobs.push(start_build(self.config.clone()));
config.queue_build = false;
} }
} }
} }
@ -308,26 +321,19 @@ impl eframe::App for App {
}); });
}); });
if diff_state.current_view == View::FunctionDiff let build_success = matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success);
&& matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success) if diff_state.current_view == View::FunctionDiff && build_success {
{
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
if function_diff_ui(ui, jobs, diff_state, appearance) { function_diff_ui(ui, diff_state, appearance);
jobs.push(start_build(config.clone()));
}
}); });
} else if diff_state.current_view == View::DataDiff } else if diff_state.current_view == View::DataDiff && build_success {
&& matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success)
{
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
if data_diff_ui(ui, jobs, diff_state, appearance) { data_diff_ui(ui, diff_state, appearance);
jobs.push(start_build(config.clone()));
}
}); });
} else { } else {
egui::SidePanel::left("side_panel").show(ctx, |ui| { egui::SidePanel::left("side_panel").show(ctx, |ui| {
egui::ScrollArea::both().show(ui, |ui| { egui::ScrollArea::both().show(ui, |ui| {
config_ui(ui, config, jobs, show_project_config, config_state, appearance); config_ui(ui, config, show_project_config, config_state, appearance);
jobs_ui(ui, jobs, appearance); jobs_ui(ui, jobs, appearance);
}); });
}); });

View File

@ -16,45 +16,48 @@ pub struct ProjectConfig {
pub base_dir: Option<PathBuf>, pub base_dir: Option<PathBuf>,
pub build_target: bool, pub build_target: bool,
pub watch_patterns: Vec<Glob>, pub watch_patterns: Vec<Glob>,
pub units: Vec<ProjectUnit>, #[serde(alias = "units")]
pub objects: Vec<ProjectObject>,
} }
#[derive(Default, Clone, serde::Deserialize)] #[derive(Default, Clone, serde::Deserialize)]
pub struct ProjectUnit { pub struct ProjectObject {
pub name: String, pub name: Option<String>,
pub path: PathBuf, pub path: PathBuf,
#[serde(default)] pub reverse_fn_order: Option<bool>,
pub reverse_fn_order: bool,
} }
#[derive(Clone)] #[derive(Clone)]
pub enum ProjectUnitNode { pub enum ProjectObjectNode {
File(String, ProjectUnit), File(String, ProjectObject),
Dir(String, Vec<ProjectUnitNode>), Dir(String, Vec<ProjectObjectNode>),
} }
fn find_dir<'a>(name: &str, nodes: &'a mut Vec<ProjectUnitNode>) -> &'a mut Vec<ProjectUnitNode> { fn find_dir<'a>(
name: &str,
nodes: &'a mut Vec<ProjectObjectNode>,
) -> &'a mut Vec<ProjectObjectNode> {
if let Some(index) = nodes if let Some(index) = nodes
.iter() .iter()
.position(|node| matches!(node, ProjectUnitNode::Dir(dir_name, _) if dir_name == name)) .position(|node| matches!(node, ProjectObjectNode::Dir(dir_name, _) if dir_name == name))
{ {
if let ProjectUnitNode::Dir(_, children) = &mut nodes[index] { if let ProjectObjectNode::Dir(_, children) = &mut nodes[index] {
return children; return children;
} }
} else { } else {
nodes.push(ProjectUnitNode::Dir(name.to_string(), vec![])); nodes.push(ProjectObjectNode::Dir(name.to_string(), vec![]));
if let Some(ProjectUnitNode::Dir(_, children)) = nodes.last_mut() { if let Some(ProjectObjectNode::Dir(_, children)) = nodes.last_mut() {
return children; return children;
} }
} }
unreachable!(); unreachable!();
} }
fn build_nodes(units: &[ProjectUnit]) -> Vec<ProjectUnitNode> { fn build_nodes(objects: &[ProjectObject]) -> Vec<ProjectObjectNode> {
let mut nodes = vec![]; let mut nodes = vec![];
for unit in units { for object in objects {
let mut out_nodes = &mut nodes; let mut out_nodes = &mut nodes;
let path = Path::new(&unit.name); let path = object.name.as_ref().map(Path::new).unwrap_or(&object.path);
if let Some(parent) = path.parent() { if let Some(parent) = path.parent() {
for component in parent.components() { for component in parent.components() {
if let Component::Normal(name) = component { if let Component::Normal(name) = component {
@ -64,7 +67,7 @@ fn build_nodes(units: &[ProjectUnit]) -> Vec<ProjectUnitNode> {
} }
} }
let filename = path.file_name().unwrap().to_str().unwrap().to_string(); let filename = path.file_name().unwrap().to_str().unwrap().to_string();
out_nodes.push(ProjectUnitNode::File(filename, unit.clone())); out_nodes.push(ProjectObjectNode::File(filename, object.clone()));
} }
nodes nodes
} }
@ -83,8 +86,8 @@ pub fn load_project_config(config: &mut AppConfig) -> Result<()> {
config.build_target = project_config.build_target; config.build_target = project_config.build_target;
config.watch_patterns = project_config.watch_patterns; config.watch_patterns = project_config.watch_patterns;
config.watcher_change = true; config.watcher_change = true;
config.units = project_config.units; config.objects = project_config.objects;
config.unit_nodes = build_nodes(&config.units); config.object_nodes = build_nodes(&config.objects);
} }
Ok(()) Ok(())
} }

View File

@ -4,7 +4,7 @@ use anyhow::{Context, Result};
use self_update::{cargo_crate_version, update::Release}; use self_update::{cargo_crate_version, update::Release};
use crate::{ use crate::{
jobs::{start_job, update_status, Job, JobResult, JobState, Status}, jobs::{start_job, update_status, Job, JobResult, JobState, JobStatusRef},
update::{build_updater, BIN_NAME}, update::{build_updater, BIN_NAME},
}; };
@ -14,7 +14,7 @@ pub struct CheckUpdateResult {
pub found_binary: bool, pub found_binary: bool,
} }
fn run_check_update(status: &Status, cancel: Receiver<()>) -> Result<Box<CheckUpdateResult>> { fn run_check_update(status: &JobStatusRef, cancel: Receiver<()>) -> Result<Box<CheckUpdateResult>> {
update_status(status, "Fetching latest release".to_string(), 0, 1, &cancel)?; update_status(status, "Fetching latest release".to_string(), 0, 1, &cancel)?;
let updater = build_updater().context("Failed to create release updater")?; let updater = build_updater().context("Failed to create release updater")?;
let latest_release = updater.get_latest_release()?; let latest_release = updater.get_latest_release()?;
@ -28,6 +28,6 @@ fn run_check_update(status: &Status, cancel: Receiver<()>) -> Result<Box<CheckUp
pub fn start_check_update() -> JobState { pub fn start_check_update() -> JobState {
start_job("Check for updates", Job::CheckUpdate, move |status, cancel| { start_job("Check for updates", Job::CheckUpdate, move |status, cancel| {
run_check_update(status, cancel).map(JobResult::CheckUpdate) run_check_update(status, cancel).map(|result| JobResult::CheckUpdate(Some(result)))
}) })
} }

View File

@ -26,12 +26,22 @@ pub static JOB_ID: AtomicUsize = AtomicUsize::new(0);
#[derive(Default)] #[derive(Default)]
pub struct JobQueue { pub struct JobQueue {
pub jobs: Vec<JobState>, pub jobs: Vec<JobState>,
pub results: Vec<JobResult>,
} }
impl JobQueue { impl JobQueue {
/// Adds a job to the queue. /// Adds a job to the queue.
#[inline]
pub fn push(&mut self, state: JobState) { self.jobs.push(state); } pub fn push(&mut self, state: JobState) { self.jobs.push(state); }
/// Adds a job to the queue if a job of the given kind is not already running.
#[inline]
pub fn push_once(&mut self, job: Job, func: impl FnOnce() -> JobState) {
if !self.is_running(job) {
self.push(func());
}
}
/// Returns whether a job of the given kind is running. /// Returns whether a job of the given kind is running.
pub fn is_running(&self, kind: Job) -> bool { pub fn is_running(&self, kind: Job) -> bool {
self.jobs.iter().any(|j| j.kind == kind && j.handle.is_some()) self.jobs.iter().any(|j| j.kind == kind && j.handle.is_some())
@ -79,11 +89,13 @@ impl JobQueue {
pub fn remove(&mut self, id: usize) { self.jobs.retain(|job| job.id != id); } pub fn remove(&mut self, id: usize) { self.jobs.retain(|job| job.id != id); }
} }
pub type JobStatusRef = Arc<RwLock<JobStatus>>;
pub struct JobState { pub struct JobState {
pub id: usize, pub id: usize,
pub kind: Job, pub kind: Job,
pub handle: Option<JoinHandle<JobResult>>, pub handle: Option<JoinHandle<JobResult>>,
pub status: Arc<RwLock<JobStatus>>, pub status: JobStatusRef,
pub cancel: Sender<()>, pub cancel: Sender<()>,
pub should_remove: bool, pub should_remove: bool,
} }
@ -99,8 +111,8 @@ pub struct JobStatus {
pub enum JobResult { pub enum JobResult {
None, None,
ObjDiff(Box<ObjDiffResult>), ObjDiff(Option<Box<ObjDiffResult>>),
CheckUpdate(Box<CheckUpdateResult>), CheckUpdate(Option<Box<CheckUpdateResult>>),
Update(Box<UpdateResult>), Update(Box<UpdateResult>),
} }
@ -111,12 +123,10 @@ fn should_cancel(rx: &Receiver<()>) -> bool {
} }
} }
type Status = Arc<RwLock<JobStatus>>;
fn start_job( fn start_job(
title: &str, title: &str,
kind: Job, kind: Job,
run: impl FnOnce(&Status, Receiver<()>) -> Result<JobResult> + Send + 'static, run: impl FnOnce(&JobStatusRef, Receiver<()>) -> Result<JobResult> + Send + 'static,
) -> JobState { ) -> JobState {
let status = Arc::new(RwLock::new(JobStatus { let status = Arc::new(RwLock::new(JobStatus {
title: title.to_string(), title: title.to_string(),
@ -151,7 +161,7 @@ fn start_job(
} }
fn update_status( fn update_status(
status: &Status, status: &JobStatusRef,
str: String, str: String,
count: u32, count: u32,
total: u32, total: u32,

View File

@ -1,17 +1,12 @@
use std::{ use std::{path::Path, process::Command, str::from_utf8, sync::mpsc::Receiver};
path::Path,
process::Command,
str::from_utf8,
sync::{mpsc::Receiver, Arc, RwLock},
};
use anyhow::{Context, Error, Result}; use anyhow::{Context, Error, Result};
use time::OffsetDateTime; use time::OffsetDateTime;
use crate::{ use crate::{
app::AppConfig, app::{AppConfig, AppConfigRef},
diff::diff_objs, diff::diff_objs,
jobs::{start_job, update_status, Job, JobResult, JobState, Status}, jobs::{start_job, update_status, Job, JobResult, JobState, JobStatusRef},
obj::{elf, ObjInfo}, obj::{elf, ObjInfo},
}; };
@ -76,9 +71,9 @@ fn run_make(cwd: &Path, arg: &Path, config: &AppConfig) -> BuildStatus {
} }
fn run_build( fn run_build(
status: &Status, status: &JobStatusRef,
cancel: Receiver<()>, cancel: Receiver<()>,
config: Arc<RwLock<AppConfig>>, config: AppConfigRef,
) -> Result<Box<ObjDiffResult>> { ) -> Result<Box<ObjDiffResult>> {
let config = config.read().map_err(|_| Error::msg("Failed to lock app config"))?.clone(); let config = config.read().map_err(|_| Error::msg("Failed to lock app config"))?.clone();
let obj_path = config.obj_path.as_ref().ok_or_else(|| Error::msg("Missing obj path"))?; let obj_path = config.obj_path.as_ref().ok_or_else(|| Error::msg("Missing obj path"))?;
@ -135,8 +130,8 @@ fn run_build(
Ok(Box::new(ObjDiffResult { first_status, second_status, first_obj, second_obj, time })) Ok(Box::new(ObjDiffResult { first_status, second_status, first_obj, second_obj, time }))
} }
pub fn start_build(config: Arc<RwLock<AppConfig>>) -> JobState { pub fn start_build(config: AppConfigRef) -> JobState {
start_job("Object diff", Job::ObjDiff, move |status, cancel| { start_job("Object diff", Job::ObjDiff, move |status, cancel| {
run_build(status, cancel, config).map(JobResult::ObjDiff) run_build(status, cancel, config).map(|result| JobResult::ObjDiff(Some(result)))
}) })
} }

View File

@ -9,7 +9,7 @@ use anyhow::{Context, Result};
use const_format::formatcp; use const_format::formatcp;
use crate::{ use crate::{
jobs::{start_job, update_status, Job, JobResult, JobState, Status}, jobs::{start_job, update_status, Job, JobResult, JobState, JobStatusRef},
update::{build_updater, BIN_NAME}, update::{build_updater, BIN_NAME},
}; };
@ -17,7 +17,7 @@ pub struct UpdateResult {
pub exe_path: PathBuf, pub exe_path: PathBuf,
} }
fn run_update(status: &Status, cancel: Receiver<()>) -> Result<Box<UpdateResult>> { fn run_update(status: &JobStatusRef, cancel: Receiver<()>) -> Result<Box<UpdateResult>> {
update_status(status, "Fetching latest release".to_string(), 0, 3, &cancel)?; update_status(status, "Fetching latest release".to_string(), 0, 3, &cancel)?;
let updater = build_updater().context("Failed to create release updater")?; let updater = build_updater().context("Failed to create release updater")?;
let latest_release = updater.get_latest_release()?; let latest_release = updater.get_latest_release()?;

View File

@ -7,7 +7,6 @@ pub struct Appearance {
pub ui_font: FontId, pub ui_font: FontId,
pub code_font: FontId, pub code_font: FontId,
pub diff_colors: Vec<Color32>, pub diff_colors: Vec<Color32>,
pub reverse_fn_order: bool,
pub theme: eframe::Theme, pub theme: eframe::Theme,
// Applied by theme // Applied by theme
@ -37,7 +36,6 @@ impl Default for Appearance {
ui_font: FontId { size: 12.0, family: FontFamily::Proportional }, ui_font: FontId { size: 12.0, family: FontFamily::Proportional },
code_font: FontId { size: 14.0, family: FontFamily::Monospace }, code_font: FontId { size: 14.0, family: FontFamily::Monospace },
diff_colors: DEFAULT_COLOR_ROTATION.to_vec(), diff_colors: DEFAULT_COLOR_ROTATION.to_vec(),
reverse_fn_order: false,
theme: eframe::Theme::Dark, theme: eframe::Theme::Dark,
text_color: Color32::GRAY, text_color: Color32::GRAY,
emphasized_text_color: Color32::LIGHT_GRAY, emphasized_text_color: Color32::LIGHT_GRAY,

View File

@ -2,8 +2,8 @@
use std::string::FromUtf16Error; use std::string::FromUtf16Error;
use std::{ use std::{
borrow::Cow, borrow::Cow,
mem::take,
path::{PathBuf, MAIN_SEPARATOR}, path::{PathBuf, MAIN_SEPARATOR},
sync::{Arc, RwLock},
}; };
#[cfg(windows)] #[cfg(windows)]
@ -17,9 +17,13 @@ use globset::Glob;
use self_update::cargo_crate_version; use self_update::cargo_crate_version;
use crate::{ use crate::{
app::AppConfig, app::{AppConfig, AppConfigRef},
config::{ProjectUnit, ProjectUnitNode}, config::{ProjectObject, ProjectObjectNode},
jobs::{check_update::CheckUpdateResult, objdiff::start_build, update::start_update, JobQueue}, jobs::{
check_update::{start_check_update, CheckUpdateResult},
update::start_update,
Job, JobQueue, JobResult,
},
update::RELEASE_URL, update::RELEASE_URL,
views::appearance::Appearance, views::appearance::Appearance,
}; };
@ -27,14 +31,54 @@ use crate::{
#[derive(Default)] #[derive(Default)]
pub struct ConfigViewState { pub struct ConfigViewState {
pub check_update: Option<Box<CheckUpdateResult>>, pub check_update: Option<Box<CheckUpdateResult>>,
pub check_update_running: bool,
pub queue_check_update: bool,
pub update_running: bool,
pub queue_update: bool,
pub build_running: bool,
pub queue_build: bool,
pub watch_pattern_text: String, pub watch_pattern_text: String,
pub queue_update_check: bool,
pub load_error: Option<String>, pub load_error: Option<String>,
pub unit_search: String, pub object_search: String,
#[cfg(windows)] #[cfg(windows)]
pub available_wsl_distros: Option<Vec<String>>, pub available_wsl_distros: Option<Vec<String>>,
} }
impl ConfigViewState {
pub fn pre_update(&mut self, jobs: &mut JobQueue) {
jobs.results.retain_mut(|result| {
if let JobResult::CheckUpdate(result) = result {
self.check_update = take(result);
false
} else {
true
}
});
self.build_running = jobs.is_running(Job::ObjDiff);
self.check_update_running = jobs.is_running(Job::CheckUpdate);
self.update_running = jobs.is_running(Job::Update);
}
pub fn post_update(&mut self, jobs: &mut JobQueue, config: &AppConfigRef) {
if self.queue_build {
self.queue_build = false;
if let Ok(mut config) = config.write() {
config.queue_build = true;
}
}
if self.queue_check_update {
self.queue_check_update = false;
jobs.push_once(Job::CheckUpdate, start_check_update);
}
if self.queue_update {
self.queue_update = false;
jobs.push_once(Job::Update, start_update);
}
}
}
const DEFAULT_WATCH_PATTERNS: &[&str] = &[ const DEFAULT_WATCH_PATTERNS: &[&str] = &[
"*.c", "*.cp", "*.cpp", "*.cxx", "*.h", "*.hp", "*.hpp", "*.hxx", "*.s", "*.S", "*.asm", "*.c", "*.cp", "*.cpp", "*.cxx", "*.h", "*.hp", "*.hpp", "*.hxx", "*.s", "*.S", "*.asm",
"*.inc", "*.py", "*.yml", "*.txt", "*.json", "*.inc", "*.py", "*.yml", "*.txt", "*.json",
@ -75,8 +119,7 @@ fn fetch_wsl2_distros() -> Vec<String> {
pub fn config_ui( pub fn config_ui(
ui: &mut egui::Ui, ui: &mut egui::Ui,
config: &Arc<RwLock<AppConfig>>, config: &AppConfigRef,
jobs: &mut JobQueue,
show_config_window: &mut bool, show_config_window: &mut bool,
state: &mut ConfigViewState, state: &mut ConfigViewState,
appearance: &Appearance, appearance: &Appearance,
@ -88,15 +131,15 @@ pub fn config_ui(
base_obj_dir, base_obj_dir,
obj_path, obj_path,
auto_update_check, auto_update_check,
units, objects,
unit_nodes, object_nodes,
.. ..
} = &mut *config_guard; } = &mut *config_guard;
ui.heading("Updates"); ui.heading("Updates");
ui.checkbox(auto_update_check, "Check for updates on startup"); ui.checkbox(auto_update_check, "Check for updates on startup");
if ui.button("Check now").clicked() { if ui.add_enabled(!state.check_update_running, egui::Button::new("Check now")).clicked() {
state.queue_update_check = true; state.queue_check_update = true;
} }
ui.label(format!("Current version: {}", cargo_crate_version!())).on_hover_ui_at_pointer(|ui| { ui.label(format!("Current version: {}", cargo_crate_version!())).on_hover_ui_at_pointer(|ui| {
ui.label(formatcp!("Git branch: {}", env!("VERGEN_GIT_BRANCH"))); ui.label(formatcp!("Git branch: {}", env!("VERGEN_GIT_BRANCH")));
@ -104,20 +147,20 @@ pub fn config_ui(
ui.label(formatcp!("Build target: {}", env!("VERGEN_CARGO_TARGET_TRIPLE"))); ui.label(formatcp!("Build target: {}", env!("VERGEN_CARGO_TARGET_TRIPLE")));
ui.label(formatcp!("Debug: {}", env!("VERGEN_CARGO_DEBUG"))); ui.label(formatcp!("Debug: {}", env!("VERGEN_CARGO_DEBUG")));
}); });
if let Some(state) = &state.check_update { if let Some(result) = &state.check_update {
ui.label(format!("Latest version: {}", state.latest_release.version)); ui.label(format!("Latest version: {}", result.latest_release.version));
if state.update_available { if result.update_available {
ui.colored_label(appearance.insert_color, "Update available"); ui.colored_label(appearance.insert_color, "Update available");
ui.horizontal(|ui| { ui.horizontal(|ui| {
if state.found_binary if result.found_binary
&& ui && ui
.button("Automatic") .add_enabled(!state.update_running, egui::Button::new("Automatic"))
.on_hover_text_at_pointer( .on_hover_text_at_pointer(
"Automatically download and replace the current build", "Automatically download and replace the current build",
) )
.clicked() .clicked()
{ {
jobs.push(start_update()); state.queue_update = true;
} }
if ui if ui
.button("Manual") .button("Manual")
@ -164,7 +207,7 @@ pub fn config_ui(
if let (Some(base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) { if let (Some(base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) {
let mut new_build_obj = obj_path.clone(); let mut new_build_obj = obj_path.clone();
if units.is_empty() { if objects.is_empty() {
if ui.button("Select object").clicked() { if ui.button("Select object").clicked() {
if let Some(path) = rfd::FileDialog::new() if let Some(path) = rfd::FileDialog::new()
.set_directory(&target_dir) .set_directory(&target_dir)
@ -186,8 +229,8 @@ pub fn config_ui(
); );
} }
} else { } else {
let had_search = !state.unit_search.is_empty(); let had_search = !state.object_search.is_empty();
egui::TextEdit::singleline(&mut state.unit_search).hint_text("Filter").ui(ui); egui::TextEdit::singleline(&mut state.object_search).hint_text("Filter").ui(ui);
let mut root_open = None; let mut root_open = None;
let mut node_open = NodeOpen::Default; let mut node_open = NodeOpen::Default;
@ -209,7 +252,7 @@ pub fn config_ui(
node_open = NodeOpen::Object; node_open = NodeOpen::Object;
} }
}); });
if state.unit_search.is_empty() { if state.object_search.is_empty() {
if had_search { if had_search {
root_open = Some(true); root_open = Some(true);
node_open = NodeOpen::Object; node_open = NodeOpen::Object;
@ -226,11 +269,11 @@ pub fn config_ui(
.open(root_open) .open(root_open)
.default_open(true) .default_open(true)
.show(ui, |ui| { .show(ui, |ui| {
let mut nodes = Cow::Borrowed(unit_nodes); let mut nodes = Cow::Borrowed(object_nodes);
if !state.unit_search.is_empty() { if !state.object_search.is_empty() {
let search = state.unit_search.to_ascii_lowercase(); let search = state.object_search.to_ascii_lowercase();
nodes = Cow::Owned( nodes = Cow::Owned(
unit_nodes.iter().filter_map(|node| filter_node(node, &search)).collect(), object_nodes.iter().filter_map(|node| filter_node(node, &search)).collect(),
); );
} }
@ -245,30 +288,30 @@ pub fn config_ui(
if let Some(obj) = new_build_obj { if let Some(obj) = new_build_obj {
// Will set obj_changed, which will trigger a rebuild // Will set obj_changed, which will trigger a rebuild
config_guard.set_obj_path(obj); config_guard.set_obj_path(obj);
// TODO apply reverse_fn_order
} }
} }
if config_guard.obj_path.is_some() && ui.button("Build").clicked() { if config_guard.obj_path.is_some()
// Rebuild immediately && ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked()
jobs.push(start_build(config.clone())); {
state.queue_build = true;
} }
} else { } else {
ui.colored_label(appearance.delete_color, "Missing project settings"); ui.colored_label(appearance.delete_color, "Missing project settings");
} }
// ui.checkbox(&mut view_config.reverse_fn_order, "Reverse function order (deferred)");
ui.separator(); ui.separator();
} }
fn display_unit( fn display_object(
ui: &mut egui::Ui, ui: &mut egui::Ui,
obj_path: &mut Option<String>, obj_path: &mut Option<String>,
name: &str, name: &str,
unit: &ProjectUnit, object: &ProjectObject,
appearance: &Appearance, appearance: &Appearance,
) { ) {
let path_string = unit.path.to_string_lossy().to_string(); let path_string = object.path.to_string_lossy().to_string();
let selected = matches!(obj_path, Some(path) if path == &path_string); let selected = matches!(obj_path, Some(path) if path == &path_string);
let color = if selected { appearance.emphasized_text_color } else { appearance.text_color };
if SelectableLabel::new( if SelectableLabel::new(
selected, selected,
RichText::new(name) RichText::new(name)
@ -276,7 +319,7 @@ fn display_unit(
size: appearance.ui_font.size, size: appearance.ui_font.size,
family: appearance.code_font.family.clone(), family: appearance.code_font.family.clone(),
}) })
.color(appearance.text_color), .color(color),
) )
.ui(ui) .ui(ui)
.clicked() .clicked()
@ -297,15 +340,15 @@ enum NodeOpen {
fn display_node( fn display_node(
ui: &mut egui::Ui, ui: &mut egui::Ui,
obj_path: &mut Option<String>, obj_path: &mut Option<String>,
node: &ProjectUnitNode, node: &ProjectObjectNode,
appearance: &Appearance, appearance: &Appearance,
node_open: NodeOpen, node_open: NodeOpen,
) { ) {
match node { match node {
ProjectUnitNode::File(name, unit) => { ProjectObjectNode::File(name, object) => {
display_unit(ui, obj_path, name, unit, appearance); display_object(ui, obj_path, name, object, appearance);
} }
ProjectUnitNode::Dir(name, children) => { ProjectObjectNode::Dir(name, children) => {
let contains_obj = obj_path.as_ref().map(|path| contains_node(node, path)); let contains_obj = obj_path.as_ref().map(|path| contains_node(node, path));
let open = match node_open { let open = match node_open {
NodeOpen::Default => None, NodeOpen::Default => None,
@ -336,33 +379,35 @@ fn display_node(
} }
} }
fn contains_node(node: &ProjectUnitNode, path: &str) -> bool { fn contains_node(node: &ProjectObjectNode, path: &str) -> bool {
match node { match node {
ProjectUnitNode::File(_, unit) => { ProjectObjectNode::File(_, object) => {
let path_string = unit.path.to_string_lossy().to_string(); let path_string = object.path.to_string_lossy().to_string();
path == path_string path == path_string
} }
ProjectUnitNode::Dir(_, children) => children.iter().any(|node| contains_node(node, path)), ProjectObjectNode::Dir(_, children) => {
children.iter().any(|node| contains_node(node, path))
}
} }
} }
fn filter_node(node: &ProjectUnitNode, search: &str) -> Option<ProjectUnitNode> { fn filter_node(node: &ProjectObjectNode, search: &str) -> Option<ProjectObjectNode> {
match node { match node {
ProjectUnitNode::File(name, _) => { ProjectObjectNode::File(name, _) => {
if name.to_ascii_lowercase().contains(search) { if name.to_ascii_lowercase().contains(search) {
Some(node.clone()) Some(node.clone())
} else { } else {
None None
} }
} }
ProjectUnitNode::Dir(name, children) => { ProjectObjectNode::Dir(name, children) => {
if name.to_ascii_lowercase().contains(search) { if name.to_ascii_lowercase().contains(search) {
return Some(node.clone()); return Some(node.clone());
} }
let new_children = let new_children =
children.iter().filter_map(|child| filter_node(child, search)).collect::<Vec<_>>(); children.iter().filter_map(|child| filter_node(child, search)).collect::<Vec<_>>();
if !new_children.is_empty() { if !new_children.is_empty() {
Some(ProjectUnitNode::Dir(name.clone(), new_children)) Some(ProjectObjectNode::Dir(name.clone(), new_children))
} else { } else {
None None
} }
@ -411,7 +456,7 @@ fn pick_folder_ui(
pub fn project_window( pub fn project_window(
ctx: &egui::Context, ctx: &egui::Context,
config: &Arc<RwLock<AppConfig>>, config: &AppConfigRef,
show: &mut bool, show: &mut bool,
state: &mut ConfigViewState, state: &mut ConfigViewState,
appearance: &Appearance, appearance: &Appearance,

View File

@ -5,7 +5,6 @@ use egui_extras::{Column, TableBuilder};
use time::format_description; use time::format_description;
use crate::{ use crate::{
jobs::{Job, JobQueue},
obj::{ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjSection}, obj::{ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjSection},
views::{ views::{
appearance::Appearance, appearance::Appearance,
@ -164,15 +163,10 @@ fn data_table_ui(
Some(()) Some(())
} }
pub fn data_diff_ui( pub fn data_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance: &Appearance) {
ui: &mut egui::Ui, let (Some(result), Some(selected_symbol)) = (&state.build, &state.symbol_state.selected_symbol)
jobs: &JobQueue, else {
state: &mut DiffViewState, return;
appearance: &Appearance,
) -> bool {
let mut rebuild = false;
let (Some(result), Some(selected_symbol)) = (&state.build, &state.selected_symbol) else {
return rebuild;
}; };
// Header // Header
@ -210,13 +204,16 @@ pub fn data_diff_ui(
ui.set_width(column_width); ui.set_width(column_width);
ui.horizontal(|ui| { ui.horizontal(|ui| {
if ui.button("Build").clicked() { if ui
rebuild = true; .add_enabled(!state.build_running, egui::Button::new("Build"))
.clicked()
{
state.queue_build = true;
} }
ui.scope(|ui| { ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false); ui.style_mut().wrap = Some(false);
if jobs.is_running(Job::ObjDiff) { if state.build_running {
ui.colored_label(appearance.replace_color, "Building…"); ui.colored_label(appearance.replace_color, "Building…");
} else { } else {
ui.label("Last built:"); ui.label("Last built:");
@ -257,6 +254,4 @@ pub fn data_diff_ui(
.min_scrolled_height(available_height); .min_scrolled_height(available_height);
data_table_ui(table, left_obj, right_obj, selected_symbol, appearance); data_table_ui(table, left_obj, right_obj, selected_symbol, appearance);
} }
rebuild
} }

View File

@ -8,7 +8,6 @@ use ppc750cl::Argument;
use time::format_description; use time::format_description;
use crate::{ use crate::{
jobs::{Job, JobQueue},
obj::{ obj::{
ObjInfo, ObjIns, ObjInsArg, ObjInsArgDiff, ObjInsDiff, ObjInsDiffKind, ObjReloc, ObjInfo, ObjIns, ObjInsArg, ObjInsArgDiff, ObjInsDiff, ObjInsDiffKind, ObjReloc,
ObjRelocKind, ObjSymbol, ObjRelocKind, ObjSymbol,
@ -403,15 +402,10 @@ fn asm_table_ui(
Some(()) Some(())
} }
pub fn function_diff_ui( pub fn function_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance: &Appearance) {
ui: &mut egui::Ui, let (Some(result), Some(selected_symbol)) = (&state.build, &state.symbol_state.selected_symbol)
jobs: &JobQueue, else {
state: &mut DiffViewState, return;
appearance: &Appearance,
) -> bool {
let mut rebuild = false;
let (Some(result), Some(selected_symbol)) = (&state.build, &state.selected_symbol) else {
return rebuild;
}; };
// Header // Header
@ -459,13 +453,16 @@ pub fn function_diff_ui(
ui.set_width(column_width); ui.set_width(column_width);
ui.horizontal(|ui| { ui.horizontal(|ui| {
if ui.button("Build").clicked() { if ui
rebuild = true; .add_enabled(!state.build_running, egui::Button::new("Build"))
.clicked()
{
state.queue_build = true;
} }
ui.scope(|ui| { ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false); ui.style_mut().wrap = Some(false);
if jobs.is_running(Job::ObjDiff) { if state.build_running {
ui.colored_label(appearance.replace_color, "Building…"); ui.colored_label(appearance.replace_color, "Building…");
} else { } else {
ui.label("Last built:"); ui.label("Last built:");
@ -517,5 +514,4 @@ pub fn function_diff_ui(
.min_scrolled_height(available_height); .min_scrolled_height(available_height);
asm_table_ui(table, left_obj, right_obj, selected_symbol, appearance); asm_table_ui(table, left_obj, right_obj, selected_symbol, appearance);
} }
rebuild
} }

View File

@ -1,3 +1,5 @@
use std::mem::take;
use egui::{ use egui::{
text::LayoutJob, Align, CollapsingHeader, Color32, Layout, Rgba, ScrollArea, SelectableLabel, text::LayoutJob, Align, CollapsingHeader, Color32, Layout, Rgba, ScrollArea, SelectableLabel,
TextEdit, Ui, Vec2, Widget, TextEdit, Ui, Vec2, Widget,
@ -5,7 +7,11 @@ use egui::{
use egui_extras::{Size, StripBuilder}; use egui_extras::{Size, StripBuilder};
use crate::{ use crate::{
jobs::objdiff::{BuildStatus, ObjDiffResult}, app::AppConfigRef,
jobs::{
objdiff::{BuildStatus, ObjDiffResult},
Job, JobQueue, JobResult,
},
obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags}, obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags},
views::{appearance::Appearance, write_text}, views::{appearance::Appearance, write_text},
}; };
@ -16,7 +22,7 @@ pub struct SymbolReference {
} }
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
#[derive(Default, Eq, PartialEq)] #[derive(Default, Eq, PartialEq, Copy, Clone)]
pub enum View { pub enum View {
#[default] #[default]
SymbolDiff, SymbolDiff,
@ -28,9 +34,56 @@ pub enum View {
pub struct DiffViewState { pub struct DiffViewState {
pub build: Option<Box<ObjDiffResult>>, pub build: Option<Box<ObjDiffResult>>,
pub current_view: View, pub current_view: View,
pub symbol_state: SymbolViewState,
pub search: String,
pub queue_build: bool,
pub build_running: bool,
}
#[derive(Default)]
pub struct SymbolViewState {
pub highlighted_symbol: Option<String>, pub highlighted_symbol: Option<String>,
pub selected_symbol: Option<SymbolReference>, pub selected_symbol: Option<SymbolReference>,
pub search: String, pub reverse_fn_order: bool,
pub disable_reverse_fn_order: bool,
}
impl DiffViewState {
pub fn pre_update(&mut self, jobs: &mut JobQueue, config: &AppConfigRef) {
jobs.results.retain_mut(|result| {
if let JobResult::ObjDiff(result) = result {
self.build = take(result);
false
} else {
true
}
});
self.build_running = jobs.is_running(Job::ObjDiff);
self.symbol_state.disable_reverse_fn_order = false;
if let Ok(config) = config.read() {
if let Some(obj_path) = &config.obj_path {
if let Some(object) = config.objects.iter().find(|object| {
let path_string = object.path.to_string_lossy().to_string();
&path_string == obj_path
}) {
if let Some(value) = object.reverse_fn_order {
self.symbol_state.reverse_fn_order = value;
self.symbol_state.disable_reverse_fn_order = true;
}
}
}
}
}
pub fn post_update(&mut self, _jobs: &mut JobQueue, config: &AppConfigRef) {
if self.queue_build {
self.queue_build = false;
if let Ok(mut config) = config.write() {
config.queue_build = true;
}
}
}
} }
pub fn match_color_for_symbol(match_percent: f32, appearance: &Appearance) -> Color32 { pub fn match_color_for_symbol(match_percent: f32, appearance: &Appearance) -> Color32 {
@ -79,30 +132,25 @@ fn symbol_hover_ui(ui: &mut Ui, symbol: &ObjSymbol, appearance: &Appearance) {
}); });
} }
#[must_use]
fn symbol_ui( fn symbol_ui(
ui: &mut Ui, ui: &mut Ui,
symbol: &ObjSymbol, symbol: &ObjSymbol,
section: Option<&ObjSection>, section: Option<&ObjSection>,
highlighted_symbol: &mut Option<String>, state: &mut SymbolViewState,
selected_symbol: &mut Option<SymbolReference>,
current_view: &mut View,
appearance: &Appearance, appearance: &Appearance,
) { ) -> Option<View> {
let mut ret = None;
let mut job = LayoutJob::default(); let mut job = LayoutJob::default();
let name: &str = let name: &str =
if let Some(demangled) = &symbol.demangled_name { demangled } else { &symbol.name }; if let Some(demangled) = &symbol.demangled_name { demangled } else { &symbol.name };
let mut selected = false; let mut selected = false;
if let Some(sym) = highlighted_symbol { if let Some(sym) = &state.highlighted_symbol {
selected = sym == &symbol.name; selected = sym == &symbol.name;
} }
write_text("[", appearance.text_color, &mut job, appearance.code_font.clone()); write_text("[", appearance.text_color, &mut job, appearance.code_font.clone());
if symbol.flags.0.contains(ObjSymbolFlags::Common) { if symbol.flags.0.contains(ObjSymbolFlags::Common) {
write_text( write_text("c", appearance.replace_color, &mut job, appearance.code_font.clone());
"c",
appearance.replace_color, /* Color32::from_rgb(0, 255, 255) */
&mut job,
appearance.code_font.clone(),
);
} else if symbol.flags.0.contains(ObjSymbolFlags::Global) { } else if symbol.flags.0.contains(ObjSymbolFlags::Global) {
write_text("g", appearance.insert_color, &mut job, appearance.code_font.clone()); write_text("g", appearance.insert_color, &mut job, appearance.code_font.clone());
} else if symbol.flags.0.contains(ObjSymbolFlags::Local) { } else if symbol.flags.0.contains(ObjSymbolFlags::Local) {
@ -130,22 +178,23 @@ fn symbol_ui(
if response.clicked() { if response.clicked() {
if let Some(section) = section { if let Some(section) = section {
if section.kind == ObjSectionKind::Code { if section.kind == ObjSectionKind::Code {
*selected_symbol = Some(SymbolReference { state.selected_symbol = Some(SymbolReference {
symbol_name: symbol.name.clone(), symbol_name: symbol.name.clone(),
section_name: section.name.clone(), section_name: section.name.clone(),
}); });
*current_view = View::FunctionDiff; ret = Some(View::FunctionDiff);
} else if section.kind == ObjSectionKind::Data { } else if section.kind == ObjSectionKind::Data {
*selected_symbol = Some(SymbolReference { state.selected_symbol = Some(SymbolReference {
symbol_name: section.name.clone(), symbol_name: section.name.clone(),
section_name: section.name.clone(), section_name: section.name.clone(),
}); });
*current_view = View::DataDiff; ret = Some(View::DataDiff);
} }
} }
} else if response.hovered() { } else if response.hovered() {
*highlighted_symbol = Some(symbol.name.clone()); state.highlighted_symbol = Some(symbol.name.clone());
} }
ret
} }
fn symbol_matches_search(symbol: &ObjSymbol, search_str: &str) -> bool { fn symbol_matches_search(symbol: &ObjSymbol, search_str: &str) -> bool {
@ -158,16 +207,15 @@ fn symbol_matches_search(symbol: &ObjSymbol, search_str: &str) -> bool {
.unwrap_or(false) .unwrap_or(false)
} }
#[allow(clippy::too_many_arguments)] #[must_use]
fn symbol_list_ui( fn symbol_list_ui(
ui: &mut Ui, ui: &mut Ui,
obj: &ObjInfo, obj: &ObjInfo,
highlighted_symbol: &mut Option<String>, state: &mut SymbolViewState,
selected_symbol: &mut Option<SymbolReference>,
current_view: &mut View,
lower_search: &str, lower_search: &str,
appearance: &Appearance, appearance: &Appearance,
) { ) -> Option<View> {
let mut ret = None;
ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| { ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| {
ui.scope(|ui| { ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
@ -176,15 +224,7 @@ fn symbol_list_ui(
if !obj.common.is_empty() { if !obj.common.is_empty() {
CollapsingHeader::new(".comm").default_open(true).show(ui, |ui| { CollapsingHeader::new(".comm").default_open(true).show(ui, |ui| {
for symbol in &obj.common { for symbol in &obj.common {
symbol_ui( ret = ret.or(symbol_ui(ui, symbol, None, state, appearance));
ui,
symbol,
None,
highlighted_symbol,
selected_symbol,
current_view,
appearance,
);
} }
}); });
} }
@ -193,41 +233,28 @@ fn symbol_list_ui(
CollapsingHeader::new(format!("{} ({:x})", section.name, section.size)) CollapsingHeader::new(format!("{} ({:x})", section.name, section.size))
.default_open(true) .default_open(true)
.show(ui, |ui| { .show(ui, |ui| {
if section.kind == ObjSectionKind::Code && appearance.reverse_fn_order { if section.kind == ObjSectionKind::Code && state.reverse_fn_order {
for symbol in section.symbols.iter().rev() { for symbol in section.symbols.iter().rev() {
if !symbol_matches_search(symbol, lower_search) { if !symbol_matches_search(symbol, lower_search) {
continue; continue;
} }
symbol_ui( ret =
ui, ret.or(symbol_ui(ui, symbol, Some(section), state, appearance));
symbol,
Some(section),
highlighted_symbol,
selected_symbol,
current_view,
appearance,
);
} }
} else { } else {
for symbol in &section.symbols { for symbol in &section.symbols {
if !symbol_matches_search(symbol, lower_search) { if !symbol_matches_search(symbol, lower_search) {
continue; continue;
} }
symbol_ui( ret =
ui, ret.or(symbol_ui(ui, symbol, Some(section), state, appearance));
symbol,
Some(section),
highlighted_symbol,
selected_symbol,
current_view,
appearance,
);
} }
} }
}); });
} }
}); });
}); });
ret
} }
fn build_log_ui(ui: &mut Ui, status: &BuildStatus, appearance: &Appearance) { fn build_log_ui(ui: &mut Ui, status: &BuildStatus, appearance: &Appearance) {
@ -242,7 +269,7 @@ fn build_log_ui(ui: &mut Ui, status: &BuildStatus, appearance: &Appearance) {
} }
pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appearance) { pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appearance) {
let DiffViewState { build, current_view, highlighted_symbol, selected_symbol, search } = state; let DiffViewState { build, current_view, symbol_state, search, .. } = state;
let Some(result) = build else { let Some(result) = build else {
return; return;
}; };
@ -295,6 +322,14 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea
ui.colored_label(Rgba::from_rgb(1.0, 0.0, 0.0), "Fail"); ui.colored_label(Rgba::from_rgb(1.0, 0.0, 0.0), "Fail");
} }
}); });
ui.add_enabled(
!symbol_state.disable_reverse_fn_order,
egui::Checkbox::new(
&mut symbol_state.reverse_fn_order,
"Reverse function order (-inline deferred)",
),
);
}, },
); );
}, },
@ -302,6 +337,7 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea
ui.separator(); ui.separator();
// Table // Table
let mut ret = None;
let lower_search = search.to_ascii_lowercase(); let lower_search = search.to_ascii_lowercase();
StripBuilder::new(ui).size(Size::remainder()).vertical(|mut strip| { StripBuilder::new(ui).size(Size::remainder()).vertical(|mut strip| {
strip.strip(|builder| { strip.strip(|builder| {
@ -310,15 +346,13 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea
ui.push_id("left", |ui| { ui.push_id("left", |ui| {
if result.first_status.success { if result.first_status.success {
if let Some(obj) = &result.first_obj { if let Some(obj) = &result.first_obj {
symbol_list_ui( ret = ret.or(symbol_list_ui(
ui, ui,
obj, obj,
highlighted_symbol, symbol_state,
selected_symbol,
current_view,
&lower_search, &lower_search,
appearance, appearance,
); ));
} }
} else { } else {
build_log_ui(ui, &result.first_status, appearance); build_log_ui(ui, &result.first_status, appearance);
@ -329,15 +363,13 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea
ui.push_id("right", |ui| { ui.push_id("right", |ui| {
if result.second_status.success { if result.second_status.success {
if let Some(obj) = &result.second_obj { if let Some(obj) = &result.second_obj {
symbol_list_ui( ret = ret.or(symbol_list_ui(
ui, ui,
obj, obj,
highlighted_symbol, symbol_state,
selected_symbol,
current_view,
&lower_search, &lower_search,
appearance, appearance,
); ));
} }
} else { } else {
build_log_ui(ui, &result.second_status, appearance); build_log_ui(ui, &result.second_status, appearance);
@ -347,4 +379,8 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea
}); });
}); });
}); });
if let Some(view) = ret {
*current_view = view;
}
} }