Make objdiff-core no_std + huge WASM rework

This commit is contained in:
2025-02-07 00:10:49 -07:00
parent d938988d43
commit e8de35b78e
49 changed files with 1463 additions and 1046 deletions

View File

@@ -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]

View File

@@ -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

View File

@@ -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,

View File

@@ -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();
}

View File

@@ -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,
})
}

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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}");
});