mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3970bc8acf | |||
| eaf0fabc2d | |||
| 91d11c83d6 | |||
| 94924047b7 | |||
| f5f6869029 | |||
| b02e32f2b7 | |||
| c7a326b160 |
Generated
+492
-314
File diff suppressed because it is too large
Load Diff
+25
-21
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "objdiff"
|
||||
version = "0.3.2"
|
||||
version = "0.4.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
authors = ["Luke Street <luke@street.dev>"]
|
||||
@@ -23,39 +23,43 @@ wgpu = ["eframe/wgpu"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.71"
|
||||
byteorder = "1.4.3"
|
||||
bytes = "1.4.0"
|
||||
cfg-if = "1.0.0"
|
||||
const_format = "0.2.30"
|
||||
const_format = "0.2.31"
|
||||
cwdemangle = "0.1.5"
|
||||
eframe = { version = "0.21.3", features = ["persistence"] }
|
||||
egui = "0.21.0"
|
||||
egui_extras = "0.21.0"
|
||||
dirs = "5.0.1"
|
||||
eframe = { version = "0.22.0", features = ["persistence"] }
|
||||
egui = "0.22.0"
|
||||
egui_extras = "0.22.0"
|
||||
flagset = "0.4.3"
|
||||
log = "0.4.17"
|
||||
memmap2 = "0.6.1"
|
||||
notify = "5.1.0"
|
||||
globset = { version = "0.4.13", features = ["serde1"] }
|
||||
log = "0.4.19"
|
||||
memmap2 = "0.7.1"
|
||||
notify = "6.0.1"
|
||||
object = { version = "0.31.1", features = ["read_core", "std", "elf"], default-features = false }
|
||||
png = "0.17.8"
|
||||
png = "0.17.9"
|
||||
ppc750cl = { git = "https://github.com/terorie/ppc750cl", rev = "9ae36eef34aa6d74e00972c7671f547a2acfd0aa" }
|
||||
rabbitizer = "1.7.1"
|
||||
rfd = { version = "0.11.3" } #, default-features = false, features = ['xdg-portal']
|
||||
rabbitizer = "1.7.4"
|
||||
rfd = { version = "0.11.4" } #, default-features = false, features = ['xdg-portal']
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
tempfile = "3.5.0"
|
||||
thiserror = "1.0.40"
|
||||
time = { version = "0.3.21", features = ["formatting", "local-offset"] }
|
||||
toml = "0.7.3"
|
||||
serde_json = "1.0.104"
|
||||
serde_yaml = "0.9.25"
|
||||
tempfile = "3.6.0"
|
||||
thiserror = "1.0.41"
|
||||
time = { version = "0.3.22", features = ["formatting", "local-offset"] }
|
||||
toml = "0.7.6"
|
||||
twox-hash = "1.6.3"
|
||||
byteorder = "1.4.3"
|
||||
|
||||
# For Linux static binaries, use rustls
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
reqwest = { version = "0.11.17", default-features = false, features = ["blocking", "json", "rustls"] }
|
||||
self_update = { version = "0.36.0", default-features = false, features = ["rustls"] }
|
||||
reqwest = { version = "0.11.18", default-features = false, features = ["blocking", "json", "rustls"] }
|
||||
self_update = { version = "0.37.0", default-features = false, features = ["rustls"] }
|
||||
|
||||
# For all other platforms, use native TLS
|
||||
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
||||
reqwest = "0.11.17"
|
||||
self_update = "0.36.0"
|
||||
reqwest = "0.11.18"
|
||||
self_update = "0.37.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
path-slash = "0.2.1"
|
||||
@@ -78,4 +82,4 @@ tracing-wasm = "0.2"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.71"
|
||||
vergen = { version = "8.1.3", features = ["build", "cargo", "git", "gitcl"] }
|
||||
vergen = { version = "8.2.4", features = ["build", "cargo", "git", "gitcl"] }
|
||||
|
||||
@@ -5,14 +5,87 @@
|
||||
|
||||
A local diffing tool for decompilation projects.
|
||||
|
||||
Currently supports:
|
||||
Supports:
|
||||
- PowerPC 750CL (GameCube & Wii)
|
||||
- MIPS (Nintendo 64)
|
||||
|
||||
See [Usage](#usage) for more information.
|
||||
|
||||

|
||||

|
||||
|
||||
### 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
|
||||
|
||||
|
||||
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 |
@@ -48,9 +48,11 @@ notice = "warn"
|
||||
# A list of advisory IDs to ignore. Note that ignored advisories will still
|
||||
# output a note when they are encountered.
|
||||
ignore = [
|
||||
# git2 (build dependency)
|
||||
"RUSTSEC-2023-0002",
|
||||
"RUSTSEC-2023-0003",
|
||||
"RUSTSEC-2023-0022",
|
||||
"RUSTSEC-2023-0023",
|
||||
"RUSTSEC-2023-0024",
|
||||
"RUSTSEC-2023-0034",
|
||||
"RUSTSEC-2023-0044",
|
||||
]
|
||||
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
|
||||
# lower than the range specified will be ignored. Note that ignored advisories
|
||||
@@ -86,6 +88,7 @@ allow = [
|
||||
"OFL-1.1",
|
||||
"LicenseRef-UFL-1.0",
|
||||
"OpenSSL",
|
||||
"GPL-3.0",
|
||||
]
|
||||
# List of explictly disallowed licenses
|
||||
# See https://spdx.org/licenses/ for list of possible licenses
|
||||
|
||||
+271
-410
File diff suppressed because it is too large
Load Diff
+126
@@ -0,0 +1,126 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
path::{Component, Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||
|
||||
use crate::app::AppConfig;
|
||||
|
||||
#[derive(Default, Clone, serde::Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct ProjectConfig {
|
||||
pub custom_make: Option<String>,
|
||||
pub target_dir: Option<PathBuf>,
|
||||
pub base_dir: Option<PathBuf>,
|
||||
pub build_target: bool,
|
||||
pub watch_patterns: Vec<Glob>,
|
||||
#[serde(alias = "units")]
|
||||
pub objects: Vec<ProjectObject>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, serde::Deserialize)]
|
||||
pub struct ProjectObject {
|
||||
pub name: Option<String>,
|
||||
pub path: PathBuf,
|
||||
pub reverse_fn_order: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ProjectObjectNode {
|
||||
File(String, ProjectObject),
|
||||
Dir(String, Vec<ProjectObjectNode>),
|
||||
}
|
||||
|
||||
fn find_dir<'a>(
|
||||
name: &str,
|
||||
nodes: &'a mut Vec<ProjectObjectNode>,
|
||||
) -> &'a mut Vec<ProjectObjectNode> {
|
||||
if let Some(index) = nodes
|
||||
.iter()
|
||||
.position(|node| matches!(node, ProjectObjectNode::Dir(dir_name, _) if dir_name == name))
|
||||
{
|
||||
if let ProjectObjectNode::Dir(_, children) = &mut nodes[index] {
|
||||
return children;
|
||||
}
|
||||
} else {
|
||||
nodes.push(ProjectObjectNode::Dir(name.to_string(), vec![]));
|
||||
if let Some(ProjectObjectNode::Dir(_, children)) = nodes.last_mut() {
|
||||
return children;
|
||||
}
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
fn build_nodes(objects: &[ProjectObject]) -> Vec<ProjectObjectNode> {
|
||||
let mut nodes = vec![];
|
||||
for object in objects {
|
||||
let mut out_nodes = &mut nodes;
|
||||
let path = object.name.as_ref().map(Path::new).unwrap_or(&object.path);
|
||||
if let Some(parent) = path.parent() {
|
||||
for component in parent.components() {
|
||||
if let Component::Normal(name) = component {
|
||||
let name = name.to_str().unwrap();
|
||||
out_nodes = find_dir(name, out_nodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
let filename = path.file_name().unwrap().to_str().unwrap().to_string();
|
||||
out_nodes.push(ProjectObjectNode::File(filename, object.clone()));
|
||||
}
|
||||
nodes
|
||||
}
|
||||
|
||||
pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.yml", "objdiff.yaml", "objdiff.json"];
|
||||
|
||||
pub fn load_project_config(config: &mut AppConfig) -> Result<()> {
|
||||
let Some(project_dir) = &config.project_dir else {
|
||||
return Ok(());
|
||||
};
|
||||
if let Some(result) = try_project_config(project_dir) {
|
||||
let project_config = result?;
|
||||
config.custom_make = project_config.custom_make;
|
||||
config.target_obj_dir = project_config.target_dir.map(|p| project_dir.join(p));
|
||||
config.base_obj_dir = project_config.base_dir.map(|p| project_dir.join(p));
|
||||
config.build_target = project_config.build_target;
|
||||
config.watch_patterns = project_config.watch_patterns;
|
||||
config.watcher_change = true;
|
||||
config.objects = project_config.objects;
|
||||
config.object_nodes = build_nodes(&config.objects);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn try_project_config(dir: &Path) -> Option<Result<ProjectConfig>> {
|
||||
for filename in CONFIG_FILENAMES.iter() {
|
||||
let config_path = dir.join(filename);
|
||||
if config_path.is_file() {
|
||||
return match filename.contains("json") {
|
||||
true => Some(read_json_config(&config_path)),
|
||||
false => Some(read_yml_config(&config_path)),
|
||||
};
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn read_yml_config(config_path: &Path) -> Result<ProjectConfig> {
|
||||
let mut reader = File::open(config_path)
|
||||
.with_context(|| format!("Failed to open config file '{}'", config_path.display()))?;
|
||||
Ok(serde_yaml::from_reader(&mut reader)?)
|
||||
}
|
||||
|
||||
fn read_json_config(config_path: &Path) -> Result<ProjectConfig> {
|
||||
let mut reader = File::open(config_path)
|
||||
.with_context(|| format!("Failed to open config file '{}'", config_path.display()))?;
|
||||
Ok(serde_json::from_reader(&mut reader)?)
|
||||
}
|
||||
|
||||
pub fn build_globset(vec: &[Glob]) -> std::result::Result<GlobSet, globset::Error> {
|
||||
let mut builder = GlobSetBuilder::new();
|
||||
for glob in vec {
|
||||
builder.add(glob.clone());
|
||||
}
|
||||
builder.build()
|
||||
}
|
||||
+4
-3
@@ -3,7 +3,6 @@ use std::{collections::BTreeMap, mem::take};
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
app::DiffConfig,
|
||||
editops::{editops_find, LevEditType},
|
||||
obj::{
|
||||
mips, ppc, ObjArchitecture, ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjInsArg,
|
||||
@@ -373,7 +372,7 @@ fn find_section_and_symbol(obj: &ObjInfo, name: &str) -> Option<(usize, usize)>
|
||||
None
|
||||
}
|
||||
|
||||
pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo, _diff_config: &DiffConfig) -> Result<()> {
|
||||
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 {
|
||||
@@ -406,7 +405,9 @@ pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo, _diff_config: &DiffCon
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let Some(right_section) = right.sections.iter_mut().find(|s| s.name == left_section.name) else {
|
||||
let Some(right_section) =
|
||||
right.sections.iter_mut().find(|s| s.name == left_section.name)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if left_section.kind == ObjSectionKind::Data {
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
use std::sync::{mpsc::Receiver, Arc, RwLock};
|
||||
|
||||
use anyhow::{Error, Result};
|
||||
|
||||
use crate::{
|
||||
app::{AppConfig, DiffConfig},
|
||||
diff::diff_objs,
|
||||
jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
|
||||
obj::{elf, ObjInfo},
|
||||
};
|
||||
|
||||
pub struct BinDiffResult {
|
||||
pub first_obj: ObjInfo,
|
||||
pub second_obj: ObjInfo,
|
||||
}
|
||||
|
||||
fn run_build(
|
||||
status: &Status,
|
||||
cancel: Receiver<()>,
|
||||
config: Arc<RwLock<AppConfig>>,
|
||||
) -> Result<Box<BinDiffResult>> {
|
||||
let config = config.read().map_err(|_| Error::msg("Failed to lock app config"))?.clone();
|
||||
let target_path =
|
||||
config.left_obj.as_ref().ok_or_else(|| Error::msg("Missing target obj path"))?;
|
||||
let base_path = config.right_obj.as_ref().ok_or_else(|| Error::msg("Missing base obj path"))?;
|
||||
|
||||
update_status(status, "Loading target obj".to_string(), 0, 3, &cancel)?;
|
||||
let mut left_obj = elf::read(target_path)?;
|
||||
|
||||
update_status(status, "Loading base obj".to_string(), 1, 3, &cancel)?;
|
||||
let mut right_obj = elf::read(base_path)?;
|
||||
|
||||
update_status(status, "Performing diff".to_string(), 2, 3, &cancel)?;
|
||||
diff_objs(&mut left_obj, &mut right_obj, &DiffConfig::default() /* TODO */)?;
|
||||
|
||||
update_status(status, "Complete".to_string(), 3, 3, &cancel)?;
|
||||
Ok(Box::new(BinDiffResult { first_obj: left_obj, second_obj: right_obj }))
|
||||
}
|
||||
|
||||
pub fn queue_bindiff(config: Arc<RwLock<AppConfig>>) -> JobState {
|
||||
queue_job("Binary diff", Job::BinDiff, move |status, cancel| {
|
||||
run_build(status, cancel, config).map(JobResult::BinDiff)
|
||||
})
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use anyhow::{Context, Result};
|
||||
use self_update::{cargo_crate_version, update::Release};
|
||||
|
||||
use crate::{
|
||||
jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
|
||||
jobs::{start_job, update_status, Job, JobResult, JobState, JobStatusRef},
|
||||
update::{build_updater, BIN_NAME},
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ pub struct CheckUpdateResult {
|
||||
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)?;
|
||||
let updater = build_updater().context("Failed to create release updater")?;
|
||||
let latest_release = updater.get_latest_release()?;
|
||||
@@ -26,8 +26,8 @@ fn run_check_update(status: &Status, cancel: Receiver<()>) -> Result<Box<CheckUp
|
||||
Ok(Box::new(CheckUpdateResult { update_available, latest_release, found_binary }))
|
||||
}
|
||||
|
||||
pub fn queue_check_update() -> JobState {
|
||||
queue_job("Check for updates", Job::CheckUpdate, move |status, cancel| {
|
||||
run_check_update(status, cancel).map(JobResult::CheckUpdate)
|
||||
pub fn start_check_update() -> JobState {
|
||||
start_job("Check for updates", Job::CheckUpdate, move |status, cancel| {
|
||||
run_check_update(status, cancel).map(|result| JobResult::CheckUpdate(Some(result)))
|
||||
})
|
||||
}
|
||||
|
||||
+81
-18
@@ -9,12 +9,8 @@ use std::{
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::jobs::{
|
||||
bindiff::BinDiffResult, check_update::CheckUpdateResult, objdiff::ObjDiffResult,
|
||||
update::UpdateResult,
|
||||
};
|
||||
use crate::jobs::{check_update::CheckUpdateResult, objdiff::ObjDiffResult, update::UpdateResult};
|
||||
|
||||
pub mod bindiff;
|
||||
pub mod check_update;
|
||||
pub mod objdiff;
|
||||
pub mod update;
|
||||
@@ -22,19 +18,88 @@ pub mod update;
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||
pub enum Job {
|
||||
ObjDiff,
|
||||
BinDiff,
|
||||
CheckUpdate,
|
||||
Update,
|
||||
}
|
||||
pub static JOB_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct JobQueue {
|
||||
pub jobs: Vec<JobState>,
|
||||
pub results: Vec<JobResult>,
|
||||
}
|
||||
|
||||
impl JobQueue {
|
||||
/// Adds a job to the queue.
|
||||
#[inline]
|
||||
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.
|
||||
pub fn is_running(&self, kind: Job) -> bool {
|
||||
self.jobs.iter().any(|j| j.kind == kind && j.handle.is_some())
|
||||
}
|
||||
|
||||
/// Returns whether any job is running.
|
||||
pub fn any_running(&self) -> bool {
|
||||
self.jobs.iter().any(|job| {
|
||||
if let Some(handle) = &job.handle {
|
||||
return !handle.is_finished();
|
||||
}
|
||||
false
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterates over all jobs mutably.
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut JobState> + '_ { self.jobs.iter_mut() }
|
||||
|
||||
/// Iterates over all finished jobs, returning the job state and the result.
|
||||
pub fn iter_finished(
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = (&mut JobState, std::thread::Result<JobResult>)> + '_ {
|
||||
self.jobs.iter_mut().filter_map(|job| {
|
||||
if let Some(handle) = &job.handle {
|
||||
if !handle.is_finished() {
|
||||
return None;
|
||||
}
|
||||
let result = job.handle.take().unwrap().join();
|
||||
return Some((job, result));
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Clears all finished jobs.
|
||||
pub fn clear_finished(&mut self) {
|
||||
self.jobs.retain(|job| {
|
||||
!(job.should_remove
|
||||
&& job.handle.is_none()
|
||||
&& job.status.read().unwrap().error.is_none())
|
||||
});
|
||||
}
|
||||
|
||||
/// Removes a job from the queue given its 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 id: usize,
|
||||
pub job_type: Job,
|
||||
pub kind: Job,
|
||||
pub handle: Option<JoinHandle<JobResult>>,
|
||||
pub status: Arc<RwLock<JobStatus>>,
|
||||
pub status: JobStatusRef,
|
||||
pub cancel: Sender<()>,
|
||||
pub should_remove: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct JobStatus {
|
||||
pub title: String,
|
||||
@@ -43,11 +108,11 @@ pub struct JobStatus {
|
||||
pub status: String,
|
||||
pub error: Option<anyhow::Error>,
|
||||
}
|
||||
|
||||
pub enum JobResult {
|
||||
None,
|
||||
ObjDiff(Box<ObjDiffResult>),
|
||||
BinDiff(Box<BinDiffResult>),
|
||||
CheckUpdate(Box<CheckUpdateResult>),
|
||||
ObjDiff(Option<Box<ObjDiffResult>>),
|
||||
CheckUpdate(Option<Box<CheckUpdateResult>>),
|
||||
Update(Box<UpdateResult>),
|
||||
}
|
||||
|
||||
@@ -58,12 +123,10 @@ fn should_cancel(rx: &Receiver<()>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
type Status = Arc<RwLock<JobStatus>>;
|
||||
|
||||
fn queue_job(
|
||||
fn start_job(
|
||||
title: &str,
|
||||
job_type: Job,
|
||||
run: impl FnOnce(&Status, Receiver<()>) -> Result<JobResult> + Send + 'static,
|
||||
kind: Job,
|
||||
run: impl FnOnce(&JobStatusRef, Receiver<()>) -> Result<JobResult> + Send + 'static,
|
||||
) -> JobState {
|
||||
let status = Arc::new(RwLock::new(JobStatus {
|
||||
title: title.to_string(),
|
||||
@@ -89,7 +152,7 @@ fn queue_job(
|
||||
log::info!("Started job {}", id);
|
||||
JobState {
|
||||
id,
|
||||
job_type,
|
||||
kind,
|
||||
handle: Some(handle),
|
||||
status: status_clone,
|
||||
cancel: tx,
|
||||
@@ -98,7 +161,7 @@ fn queue_job(
|
||||
}
|
||||
|
||||
fn update_status(
|
||||
status: &Status,
|
||||
status: &JobStatusRef,
|
||||
str: String,
|
||||
count: u32,
|
||||
total: u32,
|
||||
|
||||
+9
-15
@@ -1,17 +1,12 @@
|
||||
use std::{
|
||||
path::Path,
|
||||
process::Command,
|
||||
str::from_utf8,
|
||||
sync::{mpsc::Receiver, Arc, RwLock},
|
||||
};
|
||||
use std::{path::Path, process::Command, str::from_utf8, sync::mpsc::Receiver};
|
||||
|
||||
use anyhow::{Context, Error, Result};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::{
|
||||
app::{AppConfig, DiffConfig},
|
||||
app::{AppConfig, AppConfigRef},
|
||||
diff::diff_objs,
|
||||
jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
|
||||
jobs::{start_job, update_status, Job, JobResult, JobState, JobStatusRef},
|
||||
obj::{elf, ObjInfo},
|
||||
};
|
||||
|
||||
@@ -76,10 +71,9 @@ fn run_make(cwd: &Path, arg: &Path, config: &AppConfig) -> BuildStatus {
|
||||
}
|
||||
|
||||
fn run_build(
|
||||
status: &Status,
|
||||
status: &JobStatusRef,
|
||||
cancel: Receiver<()>,
|
||||
config: Arc<RwLock<AppConfig>>,
|
||||
diff_config: DiffConfig,
|
||||
config: AppConfigRef,
|
||||
) -> Result<Box<ObjDiffResult>> {
|
||||
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"))?;
|
||||
@@ -129,15 +123,15 @@ fn run_build(
|
||||
|
||||
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, &diff_config)?;
|
||||
diff_objs(first_obj, second_obj)?;
|
||||
}
|
||||
|
||||
update_status(status, "Complete".to_string(), total, total, &cancel)?;
|
||||
Ok(Box::new(ObjDiffResult { first_status, second_status, first_obj, second_obj, time }))
|
||||
}
|
||||
|
||||
pub fn queue_build(config: Arc<RwLock<AppConfig>>, diff_config: DiffConfig) -> JobState {
|
||||
queue_job("Object diff", Job::ObjDiff, move |status, cancel| {
|
||||
run_build(status, cancel, config, diff_config).map(JobResult::ObjDiff)
|
||||
pub fn start_build(config: AppConfigRef) -> JobState {
|
||||
start_job("Object diff", Job::ObjDiff, move |status, cancel| {
|
||||
run_build(status, cancel, config).map(|result| JobResult::ObjDiff(Some(result)))
|
||||
})
|
||||
}
|
||||
|
||||
+4
-4
@@ -9,7 +9,7 @@ use anyhow::{Context, Result};
|
||||
use const_format::formatcp;
|
||||
|
||||
use crate::{
|
||||
jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
|
||||
jobs::{start_job, update_status, Job, JobResult, JobState, JobStatusRef},
|
||||
update::{build_updater, BIN_NAME},
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ pub struct UpdateResult {
|
||||
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)?;
|
||||
let updater = build_updater().context("Failed to create release updater")?;
|
||||
let latest_release = updater.get_latest_release()?;
|
||||
@@ -53,8 +53,8 @@ fn run_update(status: &Status, cancel: Receiver<()>) -> Result<Box<UpdateResult>
|
||||
Ok(Box::from(UpdateResult { exe_path: target_file }))
|
||||
}
|
||||
|
||||
pub fn queue_update() -> JobState {
|
||||
queue_job("Update app", Job::Update, move |status, cancel| {
|
||||
pub fn start_update() -> JobState {
|
||||
start_job("Update app", Job::Update, move |status, cancel| {
|
||||
run_update(status, cancel).map(JobResult::Update)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
pub use app::App;
|
||||
|
||||
mod app;
|
||||
mod config;
|
||||
mod diff;
|
||||
mod editops;
|
||||
mod jobs;
|
||||
|
||||
+4
-3
@@ -37,7 +37,8 @@ fn main() {
|
||||
|
||||
let exec_path: Rc<Mutex<Option<PathBuf>>> = Rc::new(Mutex::new(None));
|
||||
let exec_path_clone = exec_path.clone();
|
||||
let mut native_options = eframe::NativeOptions::default();
|
||||
let mut native_options =
|
||||
eframe::NativeOptions { follow_system_theme: false, ..Default::default() };
|
||||
match load_icon() {
|
||||
Ok(data) => {
|
||||
native_options.icon_data = Some(data);
|
||||
@@ -65,7 +66,7 @@ fn main() {
|
||||
let result = exec::Command::new(path)
|
||||
.args(&std::env::args().collect::<Vec<String>>())
|
||||
.exec();
|
||||
eprintln!("Failed to relaunch: {result:?}");
|
||||
log::error!("Failed to relaunch: {result:?}");
|
||||
} else {
|
||||
let result = std::process::Command::new(path)
|
||||
.args(std::env::args())
|
||||
@@ -73,7 +74,7 @@ fn main() {
|
||||
.unwrap()
|
||||
.wait();
|
||||
if let Err(e) = result {
|
||||
eprintln!("Failed to relaunch: {:?}", e);
|
||||
log::error!("Failed to relaunch: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -26,7 +26,7 @@ fn to_obj_section_kind(kind: SectionKind) -> Option<ObjSectionKind> {
|
||||
fn to_obj_symbol(obj_file: &File<'_>, symbol: &Symbol<'_, '_>, addend: i64) -> Result<ObjSymbol> {
|
||||
let mut name = symbol.name().context("Failed to process symbol name")?;
|
||||
if name.is_empty() {
|
||||
println!("Found empty sym: {symbol:?}");
|
||||
log::warn!("Found empty sym: {symbol:?}");
|
||||
name = "?";
|
||||
}
|
||||
let mut flags = ObjSymbolFlagSet(ObjSymbolFlags::none());
|
||||
@@ -181,7 +181,7 @@ fn find_section_symbol(
|
||||
fn relocations_by_section(
|
||||
arch: ObjArchitecture,
|
||||
obj_file: &File<'_>,
|
||||
section: &mut ObjSection,
|
||||
section: &ObjSection,
|
||||
) -> Result<Vec<ObjReloc>> {
|
||||
let obj_section = obj_file.section_by_index(SectionIndex(section.index))?;
|
||||
let mut relocations = Vec::<ObjReloc>::new();
|
||||
@@ -294,7 +294,7 @@ fn line_info(obj_file: &File<'_>) -> Result<Option<BTreeMap<u32, u32>>> {
|
||||
let address_delta = reader.read_u32::<BigEndian>()?;
|
||||
map.insert(base_address + address_delta, line_number);
|
||||
}
|
||||
println!("Line info: {map:#X?}");
|
||||
log::debug!("Line info: {map:#X?}");
|
||||
return Ok(Some(map));
|
||||
}
|
||||
Ok(None)
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
use egui::{Color32, FontFamily, FontId, TextStyle};
|
||||
use time::UtcOffset;
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct Appearance {
|
||||
pub ui_font: FontId,
|
||||
pub code_font: FontId,
|
||||
pub diff_colors: Vec<Color32>,
|
||||
pub theme: eframe::Theme,
|
||||
|
||||
// Applied by theme
|
||||
#[serde(skip)]
|
||||
pub text_color: Color32, // GRAY
|
||||
#[serde(skip)]
|
||||
pub emphasized_text_color: Color32, // LIGHT_GRAY
|
||||
#[serde(skip)]
|
||||
pub deemphasized_text_color: Color32, // DARK_GRAY
|
||||
#[serde(skip)]
|
||||
pub highlight_color: Color32, // WHITE
|
||||
#[serde(skip)]
|
||||
pub replace_color: Color32, // LIGHT_BLUE
|
||||
#[serde(skip)]
|
||||
pub insert_color: Color32, // GREEN
|
||||
#[serde(skip)]
|
||||
pub delete_color: Color32, // RED
|
||||
|
||||
// Global
|
||||
#[serde(skip)]
|
||||
pub utc_offset: UtcOffset,
|
||||
}
|
||||
|
||||
impl Default for Appearance {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ui_font: FontId { size: 12.0, family: FontFamily::Proportional },
|
||||
code_font: FontId { size: 14.0, family: FontFamily::Monospace },
|
||||
diff_colors: DEFAULT_COLOR_ROTATION.to_vec(),
|
||||
theme: eframe::Theme::Dark,
|
||||
text_color: Color32::GRAY,
|
||||
emphasized_text_color: Color32::LIGHT_GRAY,
|
||||
deemphasized_text_color: Color32::DARK_GRAY,
|
||||
highlight_color: Color32::WHITE,
|
||||
replace_color: Color32::LIGHT_BLUE,
|
||||
insert_color: Color32::GREEN,
|
||||
delete_color: Color32::from_rgb(200, 40, 41),
|
||||
utc_offset: UtcOffset::UTC,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Appearance {
|
||||
pub fn apply(&mut self, style: &egui::Style) -> egui::Style {
|
||||
let mut style = style.clone();
|
||||
style.text_styles.insert(TextStyle::Body, FontId {
|
||||
size: (self.ui_font.size * 0.75).floor(),
|
||||
family: self.ui_font.family.clone(),
|
||||
});
|
||||
style.text_styles.insert(TextStyle::Body, self.ui_font.clone());
|
||||
style.text_styles.insert(TextStyle::Button, self.ui_font.clone());
|
||||
style.text_styles.insert(TextStyle::Heading, FontId {
|
||||
size: (self.ui_font.size * 1.5).floor(),
|
||||
family: self.ui_font.family.clone(),
|
||||
});
|
||||
style.text_styles.insert(TextStyle::Monospace, self.code_font.clone());
|
||||
match self.theme {
|
||||
eframe::Theme::Dark => {
|
||||
style.visuals = egui::Visuals::dark();
|
||||
self.text_color = Color32::GRAY;
|
||||
self.emphasized_text_color = Color32::LIGHT_GRAY;
|
||||
self.deemphasized_text_color = Color32::DARK_GRAY;
|
||||
self.highlight_color = Color32::WHITE;
|
||||
self.replace_color = Color32::LIGHT_BLUE;
|
||||
self.insert_color = Color32::GREEN;
|
||||
self.delete_color = Color32::from_rgb(200, 40, 41);
|
||||
}
|
||||
eframe::Theme::Light => {
|
||||
style.visuals = egui::Visuals::light();
|
||||
self.text_color = Color32::GRAY;
|
||||
self.emphasized_text_color = Color32::DARK_GRAY;
|
||||
self.deemphasized_text_color = Color32::LIGHT_GRAY;
|
||||
self.highlight_color = Color32::BLACK;
|
||||
self.replace_color = Color32::DARK_BLUE;
|
||||
self.insert_color = Color32::DARK_GREEN;
|
||||
self.delete_color = Color32::from_rgb(200, 40, 41);
|
||||
}
|
||||
}
|
||||
style
|
||||
}
|
||||
}
|
||||
|
||||
pub const DEFAULT_COLOR_ROTATION: [Color32; 9] = [
|
||||
Color32::from_rgb(255, 0, 255),
|
||||
Color32::from_rgb(0, 255, 255),
|
||||
Color32::from_rgb(0, 128, 0),
|
||||
Color32::from_rgb(255, 0, 0),
|
||||
Color32::from_rgb(255, 255, 0),
|
||||
Color32::from_rgb(255, 192, 203),
|
||||
Color32::from_rgb(0, 0, 255),
|
||||
Color32::from_rgb(0, 255, 0),
|
||||
Color32::from_rgb(213, 138, 138),
|
||||
];
|
||||
|
||||
pub fn appearance_window(ctx: &egui::Context, show: &mut bool, appearance: &mut Appearance) {
|
||||
egui::Window::new("Appearance").open(show).show(ctx, |ui| {
|
||||
egui::ComboBox::from_label("Theme")
|
||||
.selected_text(format!("{:?}", appearance.theme))
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(&mut appearance.theme, eframe::Theme::Dark, "Dark");
|
||||
ui.selectable_value(&mut appearance.theme, eframe::Theme::Light, "Light");
|
||||
});
|
||||
ui.label("UI font:");
|
||||
egui::introspection::font_id_ui(ui, &mut appearance.ui_font);
|
||||
ui.separator();
|
||||
ui.label("Code font:");
|
||||
egui::introspection::font_id_ui(ui, &mut appearance.code_font);
|
||||
ui.separator();
|
||||
ui.label("Diff colors:");
|
||||
if ui.button("Reset").clicked() {
|
||||
appearance.diff_colors = DEFAULT_COLOR_ROTATION.to_vec();
|
||||
}
|
||||
let mut remove_at: Option<usize> = None;
|
||||
let num_colors = appearance.diff_colors.len();
|
||||
for (idx, color) in appearance.diff_colors.iter_mut().enumerate() {
|
||||
ui.horizontal(|ui| {
|
||||
ui.color_edit_button_srgba(color);
|
||||
if num_colors > 1 && ui.small_button("-").clicked() {
|
||||
remove_at = Some(idx);
|
||||
}
|
||||
});
|
||||
}
|
||||
if let Some(idx) = remove_at {
|
||||
appearance.diff_colors.remove(idx);
|
||||
}
|
||||
if ui.small_button("+").clicked() {
|
||||
appearance.diff_colors.push(Color32::BLACK);
|
||||
}
|
||||
});
|
||||
}
|
||||
+547
-124
File diff suppressed because it is too large
Load Diff
+39
-36
@@ -1,14 +1,16 @@
|
||||
use std::{cmp::min, default::Default, mem::take};
|
||||
|
||||
use egui::{text::LayoutJob, Align, Color32, Label, Layout, Sense, Vec2};
|
||||
use egui::{text::LayoutJob, Align, Label, Layout, Sense, Vec2};
|
||||
use egui_extras::{Column, TableBuilder};
|
||||
use time::format_description;
|
||||
|
||||
use crate::{
|
||||
app::{SymbolReference, View, ViewConfig, ViewState},
|
||||
jobs::Job,
|
||||
obj::{ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjSection},
|
||||
views::{write_text, COLOR_RED},
|
||||
views::{
|
||||
appearance::Appearance,
|
||||
symbol_diff::{DiffViewState, SymbolReference, View},
|
||||
write_text,
|
||||
},
|
||||
};
|
||||
|
||||
const BYTES_PER_ROW: usize = 16;
|
||||
@@ -17,29 +19,29 @@ fn find_section<'a>(obj: &'a ObjInfo, selected_symbol: &SymbolReference) -> Opti
|
||||
obj.sections.iter().find(|section| section.name == selected_symbol.section_name)
|
||||
}
|
||||
|
||||
fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], config: &ViewConfig) {
|
||||
fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], appearance: &Appearance) {
|
||||
if diffs.iter().any(|d| d.kind != ObjDataDiffKind::None) {
|
||||
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
|
||||
}
|
||||
let mut job = LayoutJob::default();
|
||||
write_text(
|
||||
format!("{address:08X}: ").as_str(),
|
||||
Color32::GRAY,
|
||||
appearance.text_color,
|
||||
&mut job,
|
||||
config.code_font.clone(),
|
||||
appearance.code_font.clone(),
|
||||
);
|
||||
let mut cur_addr = 0usize;
|
||||
for diff in diffs {
|
||||
let base_color = match diff.kind {
|
||||
ObjDataDiffKind::None => Color32::GRAY,
|
||||
ObjDataDiffKind::Replace => Color32::LIGHT_BLUE,
|
||||
ObjDataDiffKind::Delete => COLOR_RED,
|
||||
ObjDataDiffKind::Insert => Color32::GREEN,
|
||||
ObjDataDiffKind::None => appearance.text_color,
|
||||
ObjDataDiffKind::Replace => appearance.replace_color,
|
||||
ObjDataDiffKind::Delete => appearance.delete_color,
|
||||
ObjDataDiffKind::Insert => appearance.insert_color,
|
||||
};
|
||||
if diff.data.is_empty() {
|
||||
let mut str = " ".repeat(diff.len);
|
||||
str.push_str(" ".repeat(diff.len / 8).as_str());
|
||||
write_text(str.as_str(), base_color, &mut job, config.code_font.clone());
|
||||
write_text(str.as_str(), base_color, &mut job, appearance.code_font.clone());
|
||||
cur_addr += diff.len;
|
||||
} else {
|
||||
let mut text = String::new();
|
||||
@@ -50,7 +52,7 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], config:
|
||||
text.push(' ');
|
||||
}
|
||||
}
|
||||
write_text(text.as_str(), base_color, &mut job, config.code_font.clone());
|
||||
write_text(text.as_str(), base_color, &mut job, appearance.code_font.clone());
|
||||
}
|
||||
}
|
||||
if cur_addr < BYTES_PER_ROW {
|
||||
@@ -58,22 +60,22 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], config:
|
||||
let mut str = " ".to_string();
|
||||
str.push_str(" ".repeat(n).as_str());
|
||||
str.push_str(" ".repeat(n / 8).as_str());
|
||||
write_text(str.as_str(), Color32::GRAY, &mut job, config.code_font.clone());
|
||||
write_text(str.as_str(), appearance.text_color, &mut job, appearance.code_font.clone());
|
||||
}
|
||||
write_text(" ", Color32::GRAY, &mut job, config.code_font.clone());
|
||||
write_text(" ", appearance.text_color, &mut job, appearance.code_font.clone());
|
||||
for diff in diffs {
|
||||
let base_color = match diff.kind {
|
||||
ObjDataDiffKind::None => Color32::GRAY,
|
||||
ObjDataDiffKind::Replace => Color32::LIGHT_BLUE,
|
||||
ObjDataDiffKind::Delete => COLOR_RED,
|
||||
ObjDataDiffKind::Insert => Color32::GREEN,
|
||||
ObjDataDiffKind::None => appearance.text_color,
|
||||
ObjDataDiffKind::Replace => appearance.replace_color,
|
||||
ObjDataDiffKind::Delete => appearance.delete_color,
|
||||
ObjDataDiffKind::Insert => appearance.insert_color,
|
||||
};
|
||||
if diff.data.is_empty() {
|
||||
write_text(
|
||||
" ".repeat(diff.len).as_str(),
|
||||
base_color,
|
||||
&mut job,
|
||||
config.code_font.clone(),
|
||||
appearance.code_font.clone(),
|
||||
);
|
||||
} else {
|
||||
let mut text = String::new();
|
||||
@@ -85,7 +87,7 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], config:
|
||||
text.push('.');
|
||||
}
|
||||
}
|
||||
write_text(text.as_str(), base_color, &mut job, config.code_font.clone());
|
||||
write_text(text.as_str(), base_color, &mut job, appearance.code_font.clone());
|
||||
}
|
||||
}
|
||||
ui.add(Label::new(job).sense(Sense::click()));
|
||||
@@ -133,7 +135,7 @@ fn data_table_ui(
|
||||
left_obj: &ObjInfo,
|
||||
right_obj: &ObjInfo,
|
||||
selected_symbol: &SymbolReference,
|
||||
config: &ViewConfig,
|
||||
config: &Appearance,
|
||||
) -> Option<()> {
|
||||
let left_section = find_section(left_obj, selected_symbol)?;
|
||||
let right_section = find_section(right_obj, selected_symbol)?;
|
||||
@@ -161,10 +163,10 @@ fn data_table_ui(
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
|
||||
let mut rebuild = false;
|
||||
let (Some(result), Some(selected_symbol)) = (&view_state.build, &view_state.selected_symbol) else {
|
||||
return rebuild;
|
||||
pub fn data_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;
|
||||
};
|
||||
|
||||
// Header
|
||||
@@ -182,13 +184,13 @@ pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
|
||||
ui.set_width(column_width);
|
||||
|
||||
if ui.button("Back").clicked() {
|
||||
view_state.current_view = View::SymbolDiff;
|
||||
state.current_view = View::SymbolDiff;
|
||||
}
|
||||
|
||||
ui.scope(|ui| {
|
||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||
ui.style_mut().wrap = Some(false);
|
||||
ui.colored_label(Color32::WHITE, &selected_symbol.symbol_name);
|
||||
ui.colored_label(appearance.highlight_color, &selected_symbol.symbol_name);
|
||||
ui.label("Diff target:");
|
||||
});
|
||||
},
|
||||
@@ -202,14 +204,17 @@ pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
|
||||
ui.set_width(column_width);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Build").clicked() {
|
||||
rebuild = true;
|
||||
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);
|
||||
ui.style_mut().wrap = Some(false);
|
||||
if view_state.jobs.iter().any(|job| job.job_type == Job::ObjDiff) {
|
||||
ui.label("Building...");
|
||||
if state.build_running {
|
||||
ui.colored_label(appearance.replace_color, "Building…");
|
||||
} else {
|
||||
ui.label("Last built:");
|
||||
let format =
|
||||
@@ -217,7 +222,7 @@ pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
|
||||
ui.label(
|
||||
result
|
||||
.time
|
||||
.to_offset(view_state.utc_offset)
|
||||
.to_offset(appearance.utc_offset)
|
||||
.format(&format)
|
||||
.unwrap(),
|
||||
);
|
||||
@@ -247,8 +252,6 @@ pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
|
||||
.resizable(false)
|
||||
.auto_shrink([false, false])
|
||||
.min_scrolled_height(available_height);
|
||||
data_table_ui(table, left_obj, right_obj, selected_symbol, &view_state.view_config);
|
||||
data_table_ui(table, left_obj, right_obj, selected_symbol, appearance);
|
||||
}
|
||||
|
||||
rebuild
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user