mirror of
https://github.com/encounter/objdiff.git
synced 2025-12-08 21:17:59 +00:00
Make objdiff-core no_std + huge WASM rework
This commit is contained in:
@@ -29,7 +29,7 @@ bytes = "1.9"
|
||||
cfg-if = "1.0"
|
||||
const_format = "0.2"
|
||||
cwdemangle = "1.0"
|
||||
cwextab = "1.0"
|
||||
cwextab = { version = "1.0", git = "https://github.com/encounter/cwextab.git" }
|
||||
dirs = "5.0"
|
||||
egui = "0.30"
|
||||
egui_extras = "0.30"
|
||||
@@ -51,6 +51,7 @@ serde_json = "1.0"
|
||||
shell-escape = "0.1"
|
||||
strum = { version = "0.26", features = ["derive"] }
|
||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||
typed-path = "0.10"
|
||||
|
||||
# Keep version in sync with egui
|
||||
[dependencies.eframe]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
default::Default,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
@@ -15,13 +16,15 @@ use globset::Glob;
|
||||
use objdiff_core::{
|
||||
build::watcher::{create_watcher, Watcher},
|
||||
config::{
|
||||
build_globset, default_watch_patterns, save_project_config, ProjectConfig,
|
||||
ProjectConfigInfo, ProjectObject, ScratchConfig, SymbolMappings, DEFAULT_WATCH_PATTERNS,
|
||||
build_globset, default_watch_patterns, path::platform_path_serde_option,
|
||||
save_project_config, ProjectConfig, ProjectConfigInfo, ProjectObject, ScratchConfig,
|
||||
DEFAULT_WATCH_PATTERNS,
|
||||
},
|
||||
diff::DiffObjConfig,
|
||||
jobs::{Job, JobQueue, JobResult},
|
||||
};
|
||||
use time::UtcOffset;
|
||||
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||
|
||||
use crate::{
|
||||
app_config::{deserialize_config, AppConfigVersion},
|
||||
@@ -90,26 +93,57 @@ impl Default for ViewState {
|
||||
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct ObjectConfig {
|
||||
pub name: String,
|
||||
pub target_path: Option<PathBuf>,
|
||||
pub base_path: Option<PathBuf>,
|
||||
#[serde(default, with = "platform_path_serde_option")]
|
||||
pub target_path: Option<Utf8PlatformPathBuf>,
|
||||
#[serde(default, with = "platform_path_serde_option")]
|
||||
pub base_path: Option<Utf8PlatformPathBuf>,
|
||||
pub reverse_fn_order: Option<bool>,
|
||||
pub complete: Option<bool>,
|
||||
pub scratch: Option<ScratchConfig>,
|
||||
pub source_path: Option<String>,
|
||||
#[serde(default)]
|
||||
pub symbol_mappings: SymbolMappings,
|
||||
pub hidden: bool,
|
||||
pub scratch: Option<ScratchConfig>,
|
||||
#[serde(default, with = "platform_path_serde_option")]
|
||||
pub source_path: Option<Utf8PlatformPathBuf>,
|
||||
#[serde(default)]
|
||||
pub symbol_mappings: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
impl From<&ProjectObject> for ObjectConfig {
|
||||
fn from(object: &ProjectObject) -> Self {
|
||||
impl ObjectConfig {
|
||||
pub fn new(
|
||||
object: &ProjectObject,
|
||||
project_dir: &Utf8PlatformPath,
|
||||
target_obj_dir: Option<&Utf8PlatformPath>,
|
||||
base_obj_dir: Option<&Utf8PlatformPath>,
|
||||
) -> Self {
|
||||
let target_path = if let (Some(target_obj_dir), Some(path), None) =
|
||||
(target_obj_dir, &object.path, &object.target_path)
|
||||
{
|
||||
Some(target_obj_dir.join(path.with_platform_encoding()))
|
||||
} else if let Some(path) = &object.target_path {
|
||||
Some(project_dir.join(path.with_platform_encoding()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let base_path = if let (Some(base_obj_dir), Some(path), None) =
|
||||
(base_obj_dir, &object.path, &object.base_path)
|
||||
{
|
||||
Some(base_obj_dir.join(path.with_platform_encoding()))
|
||||
} else if let Some(path) = &object.base_path {
|
||||
Some(project_dir.join(path.with_platform_encoding()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let source_path =
|
||||
object.source_path().map(|s| project_dir.join(s.with_platform_encoding()));
|
||||
Self {
|
||||
name: object.name().to_string(),
|
||||
target_path: object.target_path.clone(),
|
||||
base_path: object.base_path.clone(),
|
||||
target_path,
|
||||
base_path,
|
||||
reverse_fn_order: object.reverse_fn_order(),
|
||||
complete: object.complete(),
|
||||
hidden: object.hidden(),
|
||||
scratch: object.scratch.clone(),
|
||||
source_path: object.source_path().cloned(),
|
||||
source_path,
|
||||
symbol_mappings: object.symbol_mappings.clone().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
@@ -120,7 +154,7 @@ fn bool_true() -> bool { true }
|
||||
|
||||
pub struct AppState {
|
||||
pub config: AppConfig,
|
||||
pub objects: Vec<ProjectObject>,
|
||||
pub objects: Vec<ObjectConfig>,
|
||||
pub object_nodes: Vec<ProjectObjectNode>,
|
||||
pub watcher_change: bool,
|
||||
pub config_change: bool,
|
||||
@@ -170,12 +204,12 @@ pub struct AppConfig {
|
||||
pub custom_args: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub selected_wsl_distro: Option<String>,
|
||||
#[serde(default)]
|
||||
pub project_dir: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub target_obj_dir: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub base_obj_dir: Option<PathBuf>,
|
||||
#[serde(default, with = "platform_path_serde_option")]
|
||||
pub project_dir: Option<Utf8PlatformPathBuf>,
|
||||
#[serde(default, with = "platform_path_serde_option")]
|
||||
pub target_obj_dir: Option<Utf8PlatformPathBuf>,
|
||||
#[serde(default, with = "platform_path_serde_option")]
|
||||
pub base_obj_dir: Option<Utf8PlatformPathBuf>,
|
||||
#[serde(default)]
|
||||
pub selected_obj: Option<ObjectConfig>,
|
||||
#[serde(default = "bool_true")]
|
||||
@@ -189,7 +223,7 @@ pub struct AppConfig {
|
||||
#[serde(default = "default_watch_patterns")]
|
||||
pub watch_patterns: Vec<Glob>,
|
||||
#[serde(default)]
|
||||
pub recent_projects: Vec<PathBuf>,
|
||||
pub recent_projects: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub diff_obj_config: DiffObjConfig,
|
||||
}
|
||||
@@ -217,12 +251,12 @@ impl Default for AppConfig {
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn set_project_dir(&mut self, path: PathBuf) {
|
||||
pub fn set_project_dir(&mut self, path: Utf8PlatformPathBuf) {
|
||||
self.config.recent_projects.retain(|p| p != &path);
|
||||
if self.config.recent_projects.len() > 9 {
|
||||
self.config.recent_projects.truncate(9);
|
||||
}
|
||||
self.config.recent_projects.insert(0, path.clone());
|
||||
self.config.recent_projects.insert(0, path.to_string());
|
||||
self.config.project_dir = Some(path);
|
||||
self.config.target_obj_dir = None;
|
||||
self.config.base_obj_dir = None;
|
||||
@@ -240,7 +274,7 @@ impl AppState {
|
||||
self.selecting_right = None;
|
||||
}
|
||||
|
||||
pub fn set_target_obj_dir(&mut self, path: PathBuf) {
|
||||
pub fn set_target_obj_dir(&mut self, path: Utf8PlatformPathBuf) {
|
||||
self.config.target_obj_dir = Some(path);
|
||||
self.config.selected_obj = None;
|
||||
self.obj_change = true;
|
||||
@@ -249,7 +283,7 @@ impl AppState {
|
||||
self.selecting_right = None;
|
||||
}
|
||||
|
||||
pub fn set_base_obj_dir(&mut self, path: PathBuf) {
|
||||
pub fn set_base_obj_dir(&mut self, path: Utf8PlatformPathBuf) {
|
||||
self.config.base_obj_dir = Some(path);
|
||||
self.config.selected_obj = None;
|
||||
self.obj_change = true;
|
||||
@@ -360,14 +394,8 @@ impl AppState {
|
||||
Some(object.symbol_mappings.clone())
|
||||
};
|
||||
}
|
||||
if let Some(existing) =
|
||||
self.objects.iter_mut().find(|u| u.name.as_ref().is_some_and(|n| n == &object.name))
|
||||
{
|
||||
existing.symbol_mappings = if object.symbol_mappings.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(object.symbol_mappings.clone())
|
||||
};
|
||||
if let Some(existing) = self.objects.iter_mut().find(|u| u.name == object.name) {
|
||||
existing.symbol_mappings = object.symbol_mappings.clone();
|
||||
}
|
||||
}
|
||||
// Save the updated project config
|
||||
@@ -530,8 +558,13 @@ impl App {
|
||||
match build_globset(&state.config.watch_patterns)
|
||||
.map_err(anyhow::Error::new)
|
||||
.and_then(|globset| {
|
||||
create_watcher(self.modified.clone(), project_dir, globset, egui_waker(ctx))
|
||||
.map_err(anyhow::Error::new)
|
||||
create_watcher(
|
||||
self.modified.clone(),
|
||||
project_dir.as_ref(),
|
||||
globset,
|
||||
egui_waker(ctx),
|
||||
)
|
||||
.map_err(anyhow::Error::new)
|
||||
}) {
|
||||
Ok(watcher) => self.watcher = Some(watcher),
|
||||
Err(e) => log::error!("Failed to create watcher: {e}"),
|
||||
@@ -672,8 +705,11 @@ impl eframe::App for App {
|
||||
};
|
||||
ui.separator();
|
||||
for path in recent_projects {
|
||||
if ui.button(format!("{}", path.display())).clicked() {
|
||||
state.write().unwrap().set_project_dir(path);
|
||||
if ui.button(&path).clicked() {
|
||||
state
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_project_dir(Utf8PlatformPathBuf::from(path));
|
||||
ui.close_menu();
|
||||
}
|
||||
}
|
||||
@@ -776,8 +812,8 @@ impl eframe::App for App {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn file_modified(path: &Path, last_ts: FileTime) -> bool {
|
||||
if let Ok(metadata) = fs::metadata(path) {
|
||||
fn file_modified<P: AsRef<Path>>(path: P, last_ts: FileTime) -> bool {
|
||||
if let Ok(metadata) = fs::metadata(path.as_ref()) {
|
||||
FileTime::from_last_modification_time(&metadata) != last_ts
|
||||
} else {
|
||||
false
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use std::path::PathBuf;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use eframe::Storage;
|
||||
use globset::Glob;
|
||||
use objdiff_core::{
|
||||
config::{ScratchConfig, SymbolMappings},
|
||||
config::ScratchConfig,
|
||||
diff::{
|
||||
ArmArchVersion, ArmR9Usage, DiffObjConfig, FunctionRelocDiffs, MipsAbi, MipsInstrCategory,
|
||||
X86Formatter,
|
||||
},
|
||||
};
|
||||
use typed_path::{Utf8PlatformPathBuf, Utf8UnixPathBuf};
|
||||
|
||||
use crate::app::{AppConfig, ObjectConfig, CONFIG_KEY};
|
||||
|
||||
@@ -62,7 +63,7 @@ pub struct ScratchConfigV2 {
|
||||
#[serde(default)]
|
||||
pub c_flags: Option<String>,
|
||||
#[serde(default)]
|
||||
pub ctx_path: Option<PathBuf>,
|
||||
pub ctx_path: Option<String>,
|
||||
#[serde(default)]
|
||||
pub build_ctx: Option<bool>,
|
||||
#[serde(default)]
|
||||
@@ -75,7 +76,7 @@ impl ScratchConfigV2 {
|
||||
platform: self.platform,
|
||||
compiler: self.compiler,
|
||||
c_flags: self.c_flags,
|
||||
ctx_path: self.ctx_path,
|
||||
ctx_path: self.ctx_path.map(Utf8UnixPathBuf::from),
|
||||
build_ctx: self.build_ctx,
|
||||
preset_id: self.preset_id,
|
||||
}
|
||||
@@ -85,26 +86,27 @@ impl ScratchConfigV2 {
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct ObjectConfigV2 {
|
||||
pub name: String,
|
||||
pub target_path: Option<PathBuf>,
|
||||
pub base_path: Option<PathBuf>,
|
||||
pub target_path: Option<String>,
|
||||
pub base_path: Option<String>,
|
||||
pub reverse_fn_order: Option<bool>,
|
||||
pub complete: Option<bool>,
|
||||
pub scratch: Option<ScratchConfigV2>,
|
||||
pub source_path: Option<String>,
|
||||
#[serde(default)]
|
||||
pub symbol_mappings: SymbolMappings,
|
||||
pub symbol_mappings: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
impl ObjectConfigV2 {
|
||||
fn into_config(self) -> ObjectConfig {
|
||||
ObjectConfig {
|
||||
name: self.name,
|
||||
target_path: self.target_path,
|
||||
base_path: self.base_path,
|
||||
target_path: self.target_path.map(Utf8PlatformPathBuf::from),
|
||||
base_path: self.base_path.map(Utf8PlatformPathBuf::from),
|
||||
reverse_fn_order: self.reverse_fn_order,
|
||||
complete: self.complete,
|
||||
hidden: false,
|
||||
scratch: self.scratch.map(|scratch| scratch.into_config()),
|
||||
source_path: self.source_path,
|
||||
source_path: None,
|
||||
symbol_mappings: self.symbol_mappings,
|
||||
}
|
||||
}
|
||||
@@ -120,11 +122,11 @@ pub struct AppConfigV2 {
|
||||
#[serde(default)]
|
||||
pub selected_wsl_distro: Option<String>,
|
||||
#[serde(default)]
|
||||
pub project_dir: Option<PathBuf>,
|
||||
pub project_dir: Option<String>,
|
||||
#[serde(default)]
|
||||
pub target_obj_dir: Option<PathBuf>,
|
||||
pub target_obj_dir: Option<String>,
|
||||
#[serde(default)]
|
||||
pub base_obj_dir: Option<PathBuf>,
|
||||
pub base_obj_dir: Option<String>,
|
||||
#[serde(default)]
|
||||
pub selected_obj: Option<ObjectConfigV2>,
|
||||
#[serde(default = "bool_true")]
|
||||
@@ -138,7 +140,7 @@ pub struct AppConfigV2 {
|
||||
#[serde(default)]
|
||||
pub watch_patterns: Vec<Glob>,
|
||||
#[serde(default)]
|
||||
pub recent_projects: Vec<PathBuf>,
|
||||
pub recent_projects: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub diff_obj_config: DiffObjConfigV1,
|
||||
}
|
||||
@@ -150,9 +152,9 @@ impl AppConfigV2 {
|
||||
custom_make: self.custom_make,
|
||||
custom_args: self.custom_args,
|
||||
selected_wsl_distro: self.selected_wsl_distro,
|
||||
project_dir: self.project_dir,
|
||||
target_obj_dir: self.target_obj_dir,
|
||||
base_obj_dir: self.base_obj_dir,
|
||||
project_dir: self.project_dir.map(Utf8PlatformPathBuf::from),
|
||||
target_obj_dir: self.target_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||
base_obj_dir: self.base_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
|
||||
build_base: self.build_base,
|
||||
build_target: self.build_target,
|
||||
@@ -175,7 +177,7 @@ pub struct ScratchConfigV1 {
|
||||
#[serde(default)]
|
||||
pub c_flags: Option<String>,
|
||||
#[serde(default)]
|
||||
pub ctx_path: Option<PathBuf>,
|
||||
pub ctx_path: Option<String>,
|
||||
#[serde(default)]
|
||||
pub build_ctx: bool,
|
||||
}
|
||||
@@ -186,7 +188,7 @@ impl ScratchConfigV1 {
|
||||
platform: self.platform,
|
||||
compiler: self.compiler,
|
||||
c_flags: self.c_flags,
|
||||
ctx_path: self.ctx_path,
|
||||
ctx_path: self.ctx_path.map(Utf8UnixPathBuf::from),
|
||||
build_ctx: self.build_ctx.then_some(true),
|
||||
preset_id: None,
|
||||
}
|
||||
@@ -196,8 +198,8 @@ impl ScratchConfigV1 {
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct ObjectConfigV1 {
|
||||
pub name: String,
|
||||
pub target_path: Option<PathBuf>,
|
||||
pub base_path: Option<PathBuf>,
|
||||
pub target_path: Option<String>,
|
||||
pub base_path: Option<String>,
|
||||
pub reverse_fn_order: Option<bool>,
|
||||
pub complete: Option<bool>,
|
||||
pub scratch: Option<ScratchConfigV1>,
|
||||
@@ -208,12 +210,12 @@ impl ObjectConfigV1 {
|
||||
fn into_config(self) -> ObjectConfig {
|
||||
ObjectConfig {
|
||||
name: self.name,
|
||||
target_path: self.target_path,
|
||||
base_path: self.base_path,
|
||||
target_path: self.target_path.map(Utf8PlatformPathBuf::from),
|
||||
base_path: self.base_path.map(Utf8PlatformPathBuf::from),
|
||||
reverse_fn_order: self.reverse_fn_order,
|
||||
complete: self.complete,
|
||||
scratch: self.scratch.map(|scratch| scratch.into_config()),
|
||||
source_path: self.source_path,
|
||||
source_path: None,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -298,11 +300,11 @@ pub struct AppConfigV1 {
|
||||
#[serde(default)]
|
||||
pub selected_wsl_distro: Option<String>,
|
||||
#[serde(default)]
|
||||
pub project_dir: Option<PathBuf>,
|
||||
pub project_dir: Option<String>,
|
||||
#[serde(default)]
|
||||
pub target_obj_dir: Option<PathBuf>,
|
||||
pub target_obj_dir: Option<String>,
|
||||
#[serde(default)]
|
||||
pub base_obj_dir: Option<PathBuf>,
|
||||
pub base_obj_dir: Option<String>,
|
||||
#[serde(default)]
|
||||
pub selected_obj: Option<ObjectConfigV1>,
|
||||
#[serde(default = "bool_true")]
|
||||
@@ -316,7 +318,7 @@ pub struct AppConfigV1 {
|
||||
#[serde(default)]
|
||||
pub watch_patterns: Vec<Glob>,
|
||||
#[serde(default)]
|
||||
pub recent_projects: Vec<PathBuf>,
|
||||
pub recent_projects: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub diff_obj_config: DiffObjConfigV1,
|
||||
}
|
||||
@@ -328,9 +330,9 @@ impl AppConfigV1 {
|
||||
custom_make: self.custom_make,
|
||||
custom_args: self.custom_args,
|
||||
selected_wsl_distro: self.selected_wsl_distro,
|
||||
project_dir: self.project_dir,
|
||||
target_obj_dir: self.target_obj_dir,
|
||||
base_obj_dir: self.base_obj_dir,
|
||||
project_dir: self.project_dir.map(Utf8PlatformPathBuf::from),
|
||||
target_obj_dir: self.target_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||
base_obj_dir: self.base_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
|
||||
build_base: self.build_base,
|
||||
build_target: self.build_target,
|
||||
@@ -347,8 +349,8 @@ impl AppConfigV1 {
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct ObjectConfigV0 {
|
||||
pub name: String,
|
||||
pub target_path: PathBuf,
|
||||
pub base_path: PathBuf,
|
||||
pub target_path: String,
|
||||
pub base_path: String,
|
||||
pub reverse_fn_order: Option<bool>,
|
||||
}
|
||||
|
||||
@@ -356,8 +358,8 @@ impl ObjectConfigV0 {
|
||||
fn into_config(self) -> ObjectConfig {
|
||||
ObjectConfig {
|
||||
name: self.name,
|
||||
target_path: Some(self.target_path),
|
||||
base_path: Some(self.base_path),
|
||||
target_path: Some(Utf8PlatformPathBuf::from(self.target_path)),
|
||||
base_path: Some(Utf8PlatformPathBuf::from(self.base_path)),
|
||||
reverse_fn_order: self.reverse_fn_order,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -368,9 +370,9 @@ impl ObjectConfigV0 {
|
||||
pub struct AppConfigV0 {
|
||||
pub custom_make: Option<String>,
|
||||
pub selected_wsl_distro: Option<String>,
|
||||
pub project_dir: Option<PathBuf>,
|
||||
pub target_obj_dir: Option<PathBuf>,
|
||||
pub base_obj_dir: Option<PathBuf>,
|
||||
pub project_dir: Option<String>,
|
||||
pub target_obj_dir: Option<String>,
|
||||
pub base_obj_dir: Option<String>,
|
||||
pub selected_obj: Option<ObjectConfigV0>,
|
||||
pub build_target: bool,
|
||||
pub auto_update_check: bool,
|
||||
@@ -383,9 +385,9 @@ impl AppConfigV0 {
|
||||
AppConfig {
|
||||
custom_make: self.custom_make,
|
||||
selected_wsl_distro: self.selected_wsl_distro,
|
||||
project_dir: self.project_dir,
|
||||
target_obj_dir: self.target_obj_dir,
|
||||
base_obj_dir: self.base_obj_dir,
|
||||
project_dir: self.project_dir.map(Utf8PlatformPathBuf::from),
|
||||
target_obj_dir: self.target_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||
base_obj_dir: self.base_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
|
||||
build_target: self.build_target,
|
||||
auto_update_check: self.auto_update_check,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use std::path::{Component, Path};
|
||||
|
||||
use anyhow::Result;
|
||||
use globset::Glob;
|
||||
use objdiff_core::config::{try_project_config, ProjectObject, DEFAULT_WATCH_PATTERNS};
|
||||
use objdiff_core::config::{try_project_config, DEFAULT_WATCH_PATTERNS};
|
||||
use typed_path::{Utf8UnixComponent, Utf8UnixPath};
|
||||
|
||||
use crate::app::{AppState, ObjectConfig};
|
||||
|
||||
@@ -47,32 +46,19 @@ fn find_dir<'a>(
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
fn build_nodes(
|
||||
units: &mut [ProjectObject],
|
||||
project_dir: &Path,
|
||||
target_obj_dir: Option<&Path>,
|
||||
base_obj_dir: Option<&Path>,
|
||||
) -> Vec<ProjectObjectNode> {
|
||||
fn build_nodes(units: &mut [ObjectConfig]) -> Vec<ProjectObjectNode> {
|
||||
let mut nodes = vec![];
|
||||
for (idx, unit) in units.iter_mut().enumerate() {
|
||||
unit.resolve_paths(project_dir, target_obj_dir, base_obj_dir);
|
||||
let mut out_nodes = &mut nodes;
|
||||
let path = if let Some(name) = &unit.name {
|
||||
Path::new(name)
|
||||
} else if let Some(path) = &unit.path {
|
||||
path
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let path = Utf8UnixPath::new(&unit.name);
|
||||
if let Some(parent) = path.parent() {
|
||||
for component in parent.components() {
|
||||
if let Component::Normal(name) = component {
|
||||
let name = name.to_str().unwrap();
|
||||
if let Utf8UnixComponent::Normal(name) = component {
|
||||
out_nodes = find_dir(name, out_nodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
let filename = path.file_name().unwrap().to_str().unwrap().to_string();
|
||||
let filename = path.file_name().unwrap().to_string();
|
||||
out_nodes.push(ProjectObjectNode::Unit(filename, idx));
|
||||
}
|
||||
// Within the top-level module directories, join paths. Leave the
|
||||
@@ -90,13 +76,18 @@ pub fn load_project_config(state: &mut AppState) -> Result<()> {
|
||||
let Some(project_dir) = &state.config.project_dir else {
|
||||
return Ok(());
|
||||
};
|
||||
if let Some((result, info)) = try_project_config(project_dir) {
|
||||
if let Some((result, info)) = try_project_config(project_dir.as_ref()) {
|
||||
let project_config = result?;
|
||||
state.config.custom_make = project_config.custom_make.clone();
|
||||
state.config.custom_args = project_config.custom_args.clone();
|
||||
state.config.target_obj_dir =
|
||||
project_config.target_dir.as_deref().map(|p| project_dir.join(p));
|
||||
state.config.base_obj_dir = project_config.base_dir.as_deref().map(|p| project_dir.join(p));
|
||||
state.config.target_obj_dir = project_config
|
||||
.target_dir
|
||||
.as_deref()
|
||||
.map(|p| project_dir.join(p.with_platform_encoding()));
|
||||
state.config.base_obj_dir = project_config
|
||||
.base_dir
|
||||
.as_deref()
|
||||
.map(|p| project_dir.join(p.with_platform_encoding()));
|
||||
state.config.build_base = project_config.build_base.unwrap_or(true);
|
||||
state.config.build_target = project_config.build_target.unwrap_or(false);
|
||||
if let Some(watch_patterns) = &project_config.watch_patterns {
|
||||
@@ -109,21 +100,28 @@ pub fn load_project_config(state: &mut AppState) -> Result<()> {
|
||||
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect();
|
||||
}
|
||||
state.watcher_change = true;
|
||||
state.objects = project_config.units.clone().unwrap_or_default();
|
||||
state.object_nodes = build_nodes(
|
||||
&mut state.objects,
|
||||
project_dir,
|
||||
state.config.target_obj_dir.as_deref(),
|
||||
state.config.base_obj_dir.as_deref(),
|
||||
);
|
||||
state.objects = project_config
|
||||
.units
|
||||
.as_deref()
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|o| {
|
||||
ObjectConfig::new(
|
||||
o,
|
||||
project_dir,
|
||||
state.config.target_obj_dir.as_deref(),
|
||||
state.config.base_obj_dir.as_deref(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
state.object_nodes = build_nodes(&mut state.objects);
|
||||
state.current_project_config = Some(project_config);
|
||||
state.project_config_info = Some(info);
|
||||
|
||||
// Reload selected object
|
||||
if let Some(selected_obj) = &state.config.selected_obj {
|
||||
if let Some(obj) = state.objects.iter().find(|o| o.name() == selected_obj.name) {
|
||||
let config = ObjectConfig::from(obj);
|
||||
state.set_selected_obj(config);
|
||||
if let Some(obj) = state.objects.iter().find(|o| o.name == selected_obj.name) {
|
||||
state.set_selected_obj(obj.clone());
|
||||
} else {
|
||||
state.clear_selected_obj();
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ fn create_scratch_config(
|
||||
platform: scratch_config.platform.clone().unwrap_or_default(),
|
||||
compiler_flags: scratch_config.c_flags.clone().unwrap_or_default(),
|
||||
function_name,
|
||||
target_obj: target_path.to_path_buf(),
|
||||
target_obj: target_path.clone(),
|
||||
preset_id: scratch_config.preset_id,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
#[cfg(all(windows, feature = "wsl"))]
|
||||
use std::string::FromUtf16Error;
|
||||
use std::{
|
||||
mem::take,
|
||||
path::{Path, PathBuf, MAIN_SEPARATOR},
|
||||
};
|
||||
use std::{mem::take, path::MAIN_SEPARATOR};
|
||||
|
||||
#[cfg(all(windows, feature = "wsl"))]
|
||||
use anyhow::{Context, Result};
|
||||
@@ -13,13 +10,14 @@ use egui::{
|
||||
};
|
||||
use globset::Glob;
|
||||
use objdiff_core::{
|
||||
config::{ProjectObject, DEFAULT_WATCH_PATTERNS},
|
||||
config::{path::check_path_buf, DEFAULT_WATCH_PATTERNS},
|
||||
diff::{
|
||||
ConfigEnum, ConfigEnumVariantInfo, ConfigPropertyId, ConfigPropertyKind,
|
||||
ConfigPropertyValue, CONFIG_GROUPS,
|
||||
},
|
||||
jobs::{check_update::CheckUpdateResult, Job, JobQueue, JobResult},
|
||||
};
|
||||
use typed_path::Utf8PlatformPathBuf;
|
||||
|
||||
use crate::{
|
||||
app::{AppConfig, AppState, AppStateRef, ObjectConfig},
|
||||
@@ -89,7 +87,7 @@ impl ConfigViewState {
|
||||
if let Ok(obj_path) = path.strip_prefix(base_dir) {
|
||||
let target_path = target_dir.join(obj_path);
|
||||
guard.set_selected_obj(ObjectConfig {
|
||||
name: obj_path.display().to_string(),
|
||||
name: obj_path.to_string(),
|
||||
target_path: Some(target_path),
|
||||
base_path: Some(path),
|
||||
..Default::default()
|
||||
@@ -97,7 +95,7 @@ impl ConfigViewState {
|
||||
} else if let Ok(obj_path) = path.strip_prefix(target_dir) {
|
||||
let base_path = base_dir.join(obj_path);
|
||||
guard.set_selected_obj(ObjectConfig {
|
||||
name: obj_path.display().to_string(),
|
||||
name: obj_path.to_string(),
|
||||
target_path: Some(path),
|
||||
base_path: Some(base_path),
|
||||
..Default::default()
|
||||
@@ -169,10 +167,7 @@ pub fn config_ui(
|
||||
) {
|
||||
let mut state_guard = state.write().unwrap();
|
||||
let AppState {
|
||||
config:
|
||||
AppConfig {
|
||||
project_dir, target_obj_dir, base_obj_dir, selected_obj, auto_update_check, ..
|
||||
},
|
||||
config: AppConfig { target_obj_dir, base_obj_dir, selected_obj, auto_update_check, .. },
|
||||
objects,
|
||||
object_nodes,
|
||||
..
|
||||
@@ -223,9 +218,9 @@ pub fn config_ui(
|
||||
}
|
||||
});
|
||||
|
||||
let selected_index = selected_obj.as_ref().and_then(|selected_obj| {
|
||||
objects.iter().position(|obj| obj.name.as_ref() == Some(&selected_obj.name))
|
||||
});
|
||||
let selected_index = selected_obj
|
||||
.as_ref()
|
||||
.and_then(|selected_obj| objects.iter().position(|obj| obj.name == selected_obj.name));
|
||||
let mut new_selected_index = selected_index;
|
||||
if objects.is_empty() {
|
||||
if let (Some(_base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) {
|
||||
@@ -324,22 +319,14 @@ pub fn config_ui(
|
||||
config_state.show_hidden,
|
||||
)
|
||||
}) {
|
||||
display_node(
|
||||
ui,
|
||||
&mut new_selected_index,
|
||||
project_dir.as_deref(),
|
||||
objects,
|
||||
&node,
|
||||
appearance,
|
||||
node_open,
|
||||
);
|
||||
display_node(ui, &mut new_selected_index, objects, &node, appearance, node_open);
|
||||
}
|
||||
});
|
||||
}
|
||||
if new_selected_index != selected_index {
|
||||
if let Some(idx) = new_selected_index {
|
||||
// Will set obj_changed, which will trigger a rebuild
|
||||
let config = ObjectConfig::from(&objects[idx]);
|
||||
let config = objects[idx].clone();
|
||||
state_guard.set_selected_obj(config);
|
||||
}
|
||||
}
|
||||
@@ -353,9 +340,8 @@ pub fn config_ui(
|
||||
fn display_unit(
|
||||
ui: &mut egui::Ui,
|
||||
selected_obj: &mut Option<usize>,
|
||||
project_dir: Option<&Path>,
|
||||
name: &str,
|
||||
units: &[ProjectObject],
|
||||
units: &[ObjectConfig],
|
||||
index: usize,
|
||||
appearance: &Appearance,
|
||||
) {
|
||||
@@ -363,7 +349,7 @@ fn display_unit(
|
||||
let selected = *selected_obj == Some(index);
|
||||
let color = if selected {
|
||||
appearance.emphasized_text_color
|
||||
} else if let Some(complete) = object.complete() {
|
||||
} else if let Some(complete) = object.complete {
|
||||
if complete {
|
||||
appearance.insert_color
|
||||
} else {
|
||||
@@ -382,26 +368,22 @@ fn display_unit(
|
||||
.color(color),
|
||||
)
|
||||
.ui(ui);
|
||||
if get_source_path(project_dir, object).is_some() {
|
||||
response.context_menu(|ui| object_context_ui(ui, object, project_dir));
|
||||
if object.source_path.is_some() {
|
||||
response.context_menu(|ui| object_context_ui(ui, object));
|
||||
}
|
||||
if response.clicked() {
|
||||
*selected_obj = Some(index);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_source_path(project_dir: Option<&Path>, object: &ProjectObject) -> Option<PathBuf> {
|
||||
project_dir.and_then(|dir| object.source_path().map(|path| dir.join(path)))
|
||||
}
|
||||
|
||||
fn object_context_ui(ui: &mut egui::Ui, object: &ProjectObject, project_dir: Option<&Path>) {
|
||||
if let Some(source_path) = get_source_path(project_dir, object) {
|
||||
fn object_context_ui(ui: &mut egui::Ui, object: &ObjectConfig) {
|
||||
if let Some(source_path) = &object.source_path {
|
||||
if ui
|
||||
.button("Open source file")
|
||||
.on_hover_text("Open the source file in the default editor")
|
||||
.clicked()
|
||||
{
|
||||
log::info!("Opening file {}", source_path.display());
|
||||
log::info!("Opening file {}", source_path);
|
||||
if let Err(e) = open::that_detached(&source_path) {
|
||||
log::error!("Failed to open source file: {e}");
|
||||
}
|
||||
@@ -422,15 +404,14 @@ enum NodeOpen {
|
||||
fn display_node(
|
||||
ui: &mut egui::Ui,
|
||||
selected_obj: &mut Option<usize>,
|
||||
project_dir: Option<&Path>,
|
||||
units: &[ProjectObject],
|
||||
units: &[ObjectConfig],
|
||||
node: &ProjectObjectNode,
|
||||
appearance: &Appearance,
|
||||
node_open: NodeOpen,
|
||||
) {
|
||||
match node {
|
||||
ProjectObjectNode::Unit(name, idx) => {
|
||||
display_unit(ui, selected_obj, project_dir, name, units, *idx, appearance);
|
||||
display_unit(ui, selected_obj, name, units, *idx, appearance);
|
||||
}
|
||||
ProjectObjectNode::Dir(name, children) => {
|
||||
let contains_obj = selected_obj.map(|idx| contains_node(node, idx));
|
||||
@@ -456,7 +437,7 @@ fn display_node(
|
||||
.open(open)
|
||||
.show(ui, |ui| {
|
||||
for node in children {
|
||||
display_node(ui, selected_obj, project_dir, units, node, appearance, node_open);
|
||||
display_node(ui, selected_obj, units, node, appearance, node_open);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -473,7 +454,7 @@ fn contains_node(node: &ProjectObjectNode, selected_obj: usize) -> bool {
|
||||
}
|
||||
|
||||
fn filter_node(
|
||||
units: &[ProjectObject],
|
||||
units: &[ObjectConfig],
|
||||
node: &ProjectObjectNode,
|
||||
search: &str,
|
||||
filter_diffable: bool,
|
||||
@@ -485,8 +466,8 @@ fn filter_node(
|
||||
let unit = &units[*idx];
|
||||
if (search.is_empty() || name.to_ascii_lowercase().contains(search))
|
||||
&& (!filter_diffable || (unit.base_path.is_some() && unit.target_path.is_some()))
|
||||
&& (!filter_incomplete || matches!(unit.complete(), None | Some(false)))
|
||||
&& (show_hidden || !unit.hidden())
|
||||
&& (!filter_incomplete || matches!(unit.complete, None | Some(false)))
|
||||
&& (show_hidden || !unit.hidden)
|
||||
{
|
||||
Some(node.clone())
|
||||
} else {
|
||||
@@ -524,13 +505,16 @@ fn subheading(ui: &mut egui::Ui, text: &str, appearance: &Appearance) {
|
||||
);
|
||||
}
|
||||
|
||||
fn format_path(path: &Option<PathBuf>, appearance: &Appearance) -> RichText {
|
||||
fn format_path(path: &Option<Utf8PlatformPathBuf>, appearance: &Appearance) -> RichText {
|
||||
let mut color = appearance.replace_color;
|
||||
let text = if let Some(dir) = path {
|
||||
if let Some(rel) = dirs::home_dir().and_then(|home| dir.strip_prefix(&home).ok()) {
|
||||
format!("~{}{}", MAIN_SEPARATOR, rel.display())
|
||||
if let Some(rel) = dirs::home_dir()
|
||||
.and_then(|home| check_path_buf(home).ok())
|
||||
.and_then(|home| dir.strip_prefix(&home).ok())
|
||||
{
|
||||
format!("~{}{}", MAIN_SEPARATOR, rel)
|
||||
} else {
|
||||
format!("{}", dir.display())
|
||||
dir.to_string()
|
||||
}
|
||||
} else {
|
||||
color = appearance.delete_color;
|
||||
@@ -544,7 +528,7 @@ pub const CONFIG_DISABLED_TEXT: &str =
|
||||
|
||||
fn pick_folder_ui(
|
||||
ui: &mut egui::Ui,
|
||||
dir: &Option<PathBuf>,
|
||||
dir: &Option<Utf8PlatformPathBuf>,
|
||||
label: &str,
|
||||
tooltip: impl FnOnce(&mut egui::Ui),
|
||||
appearance: &Appearance,
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
use std::{future::Future, path::PathBuf, pin::Pin, thread::JoinHandle};
|
||||
|
||||
use objdiff_core::config::path::check_path_buf;
|
||||
use pollster::FutureExt;
|
||||
use rfd::FileHandle;
|
||||
use typed_path::Utf8PlatformPathBuf;
|
||||
|
||||
#[derive(Default)]
|
||||
pub enum FileDialogResult {
|
||||
#[default]
|
||||
None,
|
||||
ProjectDir(PathBuf),
|
||||
TargetDir(PathBuf),
|
||||
BaseDir(PathBuf),
|
||||
Object(PathBuf),
|
||||
ProjectDir(Utf8PlatformPathBuf),
|
||||
TargetDir(Utf8PlatformPathBuf),
|
||||
BaseDir(Utf8PlatformPathBuf),
|
||||
Object(Utf8PlatformPathBuf),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -22,7 +24,7 @@ impl FileDialogState {
|
||||
pub fn queue<InitCb, ResultCb>(&mut self, init: InitCb, result_cb: ResultCb)
|
||||
where
|
||||
InitCb: FnOnce() -> Pin<Box<dyn Future<Output = Option<FileHandle>> + Send>>,
|
||||
ResultCb: FnOnce(PathBuf) -> FileDialogResult + Send + 'static,
|
||||
ResultCb: FnOnce(Utf8PlatformPathBuf) -> FileDialogResult + Send + 'static,
|
||||
{
|
||||
if self.thread.is_some() {
|
||||
return;
|
||||
@@ -30,7 +32,8 @@ impl FileDialogState {
|
||||
let future = init();
|
||||
self.thread = Some(std::thread::spawn(move || {
|
||||
if let Some(handle) = future.block_on() {
|
||||
result_cb(PathBuf::from(handle))
|
||||
let path = PathBuf::from(handle);
|
||||
check_path_buf(path).map(result_cb).unwrap_or(FileDialogResult::None)
|
||||
} else {
|
||||
FileDialogResult::None
|
||||
}
|
||||
|
||||
@@ -280,12 +280,10 @@ impl DiffViewState {
|
||||
let Ok(state) = state.read() else {
|
||||
return;
|
||||
};
|
||||
if let (Some(project_dir), Some(source_path)) = (
|
||||
&state.config.project_dir,
|
||||
state.config.selected_obj.as_ref().and_then(|obj| obj.source_path.as_ref()),
|
||||
) {
|
||||
let source_path = project_dir.join(source_path);
|
||||
log::info!("Opening file {}", source_path.display());
|
||||
if let Some(source_path) =
|
||||
state.config.selected_obj.as_ref().and_then(|obj| obj.source_path.as_ref())
|
||||
{
|
||||
log::info!("Opening file {}", source_path);
|
||||
open::that_detached(source_path).unwrap_or_else(|err| {
|
||||
log::error!("Failed to open source file: {err}");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user