mirror of https://github.com/encounter/objdiff.git
Compare commits
3 Commits
bf3ba48539
...
5bfa47fce9
Author | SHA1 | Date |
---|---|---|
Luke Street | 5bfa47fce9 | |
Luke Street | 1d9b9b6893 | |
Luke Street | 6b8e469261 |
|
@ -840,9 +840,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cwdemangle"
|
name = "cwdemangle"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b58d34a3a03cfe0a4ebfd03aeda6ee8a0f2e99bd3308476a8a89815add3ec373"
|
checksum = "c251bc5553377b3dc85c7b9b3955cfc2eb5a7b5544cf65adc2d53c2a4c2f4162"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argh",
|
"argh",
|
||||||
]
|
]
|
||||||
|
@ -2457,7 +2457,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objdiff"
|
name = "objdiff"
|
||||||
version = "0.4.0"
|
version = "0.4.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
@ -3044,9 +3044,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.101.1"
|
version = "0.101.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e"
|
checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring",
|
||||||
"untrusted",
|
"untrusted",
|
||||||
|
@ -4051,9 +4051,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki"
|
name = "webpki"
|
||||||
version = "0.22.0"
|
version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
|
checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring",
|
||||||
"untrusted",
|
"untrusted",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "objdiff"
|
name = "objdiff"
|
||||||
version = "0.4.0"
|
version = "0.4.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.65"
|
rust-version = "1.65"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
|
@ -27,7 +27,7 @@ byteorder = "1.4.3"
|
||||||
bytes = "1.4.0"
|
bytes = "1.4.0"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
const_format = "0.2.31"
|
const_format = "0.2.31"
|
||||||
cwdemangle = "0.1.5"
|
cwdemangle = "0.1.6"
|
||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
eframe = { version = "0.22.0", features = ["persistence"] }
|
eframe = { version = "0.22.0", features = ["persistence"] }
|
||||||
egui = "0.22.0"
|
egui = "0.22.0"
|
||||||
|
|
66
README.md
66
README.md
|
@ -50,8 +50,11 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
|
||||||
// objdiff.json
|
// objdiff.json
|
||||||
{
|
{
|
||||||
"custom_make": "ninja",
|
"custom_make": "ninja",
|
||||||
|
|
||||||
|
// Only required if objects use "path" instead of "target_path" and "base_path".
|
||||||
"target_dir": "build/asm",
|
"target_dir": "build/asm",
|
||||||
"base_dir": "build/src",
|
"base_dir": "build/src",
|
||||||
|
|
||||||
"build_target": true,
|
"build_target": true,
|
||||||
"watch_patterns": [
|
"watch_patterns": [
|
||||||
"*.c",
|
"*.c",
|
||||||
|
@ -63,8 +66,15 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
|
||||||
],
|
],
|
||||||
"objects": [
|
"objects": [
|
||||||
{
|
{
|
||||||
|
"name": "main/MetroTRK/mslsupp",
|
||||||
|
|
||||||
|
// Option 1: Relative to target_dir and base_dir
|
||||||
"path": "MetroTRK/mslsupp.o",
|
"path": "MetroTRK/mslsupp.o",
|
||||||
"name": "MetroTRK/mslsupp",
|
// Option 2: Explicit paths from project root
|
||||||
|
// Useful for more complex directory layouts
|
||||||
|
"target_path": "build/asm/MetroTRK/mslsupp.o",
|
||||||
|
"base_path": "build/src/MetroTRK/mslsupp.o",
|
||||||
|
|
||||||
"reverse_fn_order": false
|
"reverse_fn_order": false
|
||||||
},
|
},
|
||||||
// ...
|
// ...
|
||||||
|
@ -72,25 +82,43 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `custom_make` _(optional)_: By default, objdiff will use `make` to build the project.
|
`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.
|
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.
|
`target_dir` _(optional)_: Relative from the root of the project, this where the "target" or "expected" objects are located.
|
||||||
- `base_dir`: Relative from the root of the project, this is where the "base" or "actual" objects are located.
|
These are the **intended result** of the match.
|
||||||
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.
|
`base_dir` _(optional)_: 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`).
|
`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
|
This is useful if the target objects are not built by default or can change based on project configuration or edits
|
||||||
to assembly files.
|
to assembly files.
|
||||||
Requires the build system to be configured properly.
|
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))
|
`watch_patterns` _(optional)_: A list of glob patterns to watch for changes.
|
||||||
If any of these files change, objdiff will automatically rebuild the objects and re-compare them.
|
([Supported syntax](https://docs.rs/globset/latest/globset/#syntax))
|
||||||
- `objects` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
|
If any of these files change, objdiff will automatically rebuild the objects and re-compare them.
|
||||||
- `path`: Relative path to the object from the `target_dir` and `base_dir`.
|
If not specified, objdiff will use the default patterns listed above.
|
||||||
- `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.
|
`objects` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
|
||||||
Used to support MWCC's `-inline deferred` option, which reverses the order of functions in the object file.
|
|
||||||
|
> `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
|
||||||
|
>
|
||||||
|
> `path`: Relative path to the object from the `target_dir` and `base_dir`.
|
||||||
|
> Requires `target_dir` and `base_dir` to be specified.
|
||||||
|
>
|
||||||
|
> `target_path`: Path to the target object from the project root.
|
||||||
|
> Required if `path` is not specified.
|
||||||
|
>
|
||||||
|
> `base_path`: Path to the base object from the project root.
|
||||||
|
> Required if `path` is not specified.
|
||||||
|
>
|
||||||
|
> `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
|
## License
|
||||||
|
|
||||||
|
|
36
src/app.rs
36
src/app.rs
|
@ -40,6 +40,18 @@ pub struct ViewState {
|
||||||
pub show_project_config: bool,
|
pub show_project_config: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The configuration for a single object file.
|
||||||
|
#[derive(Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct ObjectConfig {
|
||||||
|
pub name: String,
|
||||||
|
pub target_path: PathBuf,
|
||||||
|
pub base_path: PathBuf,
|
||||||
|
pub reverse_fn_order: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn bool_true() -> bool { true }
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Deserialize, serde::Serialize)]
|
#[derive(Default, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
pub custom_make: Option<String>,
|
pub custom_make: Option<String>,
|
||||||
|
@ -47,9 +59,10 @@ pub struct AppConfig {
|
||||||
pub project_dir: Option<PathBuf>,
|
pub project_dir: Option<PathBuf>,
|
||||||
pub target_obj_dir: Option<PathBuf>,
|
pub target_obj_dir: Option<PathBuf>,
|
||||||
pub base_obj_dir: Option<PathBuf>,
|
pub base_obj_dir: Option<PathBuf>,
|
||||||
pub obj_path: Option<String>,
|
pub selected_obj: Option<ObjectConfig>,
|
||||||
pub build_target: bool,
|
pub build_target: bool,
|
||||||
pub watcher_enabled: bool,
|
#[serde(default = "bool_true")]
|
||||||
|
pub rebuild_on_changes: bool,
|
||||||
pub auto_update_check: bool,
|
pub auto_update_check: bool,
|
||||||
pub watch_patterns: Vec<Glob>,
|
pub watch_patterns: Vec<Glob>,
|
||||||
|
|
||||||
|
@ -65,6 +78,8 @@ pub struct AppConfig {
|
||||||
pub obj_change: bool,
|
pub obj_change: bool,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub queue_build: bool,
|
pub queue_build: bool,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub project_config_loaded: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppConfig {
|
impl AppConfig {
|
||||||
|
@ -72,7 +87,7 @@ impl AppConfig {
|
||||||
self.project_dir = Some(path);
|
self.project_dir = Some(path);
|
||||||
self.target_obj_dir = None;
|
self.target_obj_dir = None;
|
||||||
self.base_obj_dir = None;
|
self.base_obj_dir = None;
|
||||||
self.obj_path = None;
|
self.selected_obj = None;
|
||||||
self.build_target = false;
|
self.build_target = false;
|
||||||
self.objects.clear();
|
self.objects.clear();
|
||||||
self.object_nodes.clear();
|
self.object_nodes.clear();
|
||||||
|
@ -80,24 +95,25 @@ impl AppConfig {
|
||||||
self.config_change = true;
|
self.config_change = true;
|
||||||
self.obj_change = true;
|
self.obj_change = true;
|
||||||
self.queue_build = false;
|
self.queue_build = false;
|
||||||
|
self.project_config_loaded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_target_obj_dir(&mut self, path: PathBuf) {
|
pub fn set_target_obj_dir(&mut self, path: PathBuf) {
|
||||||
self.target_obj_dir = Some(path);
|
self.target_obj_dir = Some(path);
|
||||||
self.obj_path = None;
|
self.selected_obj = None;
|
||||||
self.obj_change = true;
|
self.obj_change = true;
|
||||||
self.queue_build = false;
|
self.queue_build = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_base_obj_dir(&mut self, path: PathBuf) {
|
pub fn set_base_obj_dir(&mut self, path: PathBuf) {
|
||||||
self.base_obj_dir = Some(path);
|
self.base_obj_dir = Some(path);
|
||||||
self.obj_path = None;
|
self.selected_obj = None;
|
||||||
self.obj_change = true;
|
self.obj_change = true;
|
||||||
self.queue_build = false;
|
self.queue_build = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_obj_path(&mut self, path: String) {
|
pub fn set_selected_obj(&mut self, object: ObjectConfig) {
|
||||||
self.obj_path = Some(path);
|
self.selected_obj = Some(object);
|
||||||
self.obj_change = true;
|
self.obj_change = true;
|
||||||
self.queue_build = false;
|
self.queue_build = false;
|
||||||
}
|
}
|
||||||
|
@ -258,19 +274,19 @@ impl App {
|
||||||
|
|
||||||
if config.obj_change {
|
if config.obj_change {
|
||||||
*diff_state = Default::default();
|
*diff_state = Default::default();
|
||||||
if config.obj_path.is_some() {
|
if config.selected_obj.is_some() {
|
||||||
config.queue_build = true;
|
config.queue_build = true;
|
||||||
}
|
}
|
||||||
config.obj_change = false;
|
config.obj_change = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.modified.swap(false, Ordering::Relaxed) {
|
if self.modified.swap(false, Ordering::Relaxed) && config.rebuild_on_changes {
|
||||||
config.queue_build = true;
|
config.queue_build = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't clear `queue_build` if a build is running. A file may have been modified during
|
// Don't clear `queue_build` if a build is running. A file may have been modified during
|
||||||
// the build, so we'll start another build after the current one finishes.
|
// the build, so we'll start another build after the current one finishes.
|
||||||
if config.queue_build && config.obj_path.is_some() && !jobs.is_running(Job::ObjDiff) {
|
if config.queue_build && config.selected_obj.is_some() && !jobs.is_running(Job::ObjDiff) {
|
||||||
jobs.push(start_build(self.config.clone()));
|
jobs.push(start_build(self.config.clone()));
|
||||||
config.queue_build = false;
|
config.queue_build = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::{
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
|
|
||||||
use crate::app::AppConfig;
|
use crate::{app::AppConfig, views::config::DEFAULT_WATCH_PATTERNS};
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Deserialize)]
|
#[derive(Default, Clone, serde::Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -15,7 +15,7 @@ pub struct ProjectConfig {
|
||||||
pub target_dir: Option<PathBuf>,
|
pub target_dir: Option<PathBuf>,
|
||||||
pub base_dir: Option<PathBuf>,
|
pub base_dir: Option<PathBuf>,
|
||||||
pub build_target: bool,
|
pub build_target: bool,
|
||||||
pub watch_patterns: Vec<Glob>,
|
pub watch_patterns: Option<Vec<Glob>>,
|
||||||
#[serde(alias = "units")]
|
#[serde(alias = "units")]
|
||||||
pub objects: Vec<ProjectObject>,
|
pub objects: Vec<ProjectObject>,
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,24 @@ pub struct ProjectConfig {
|
||||||
#[derive(Default, Clone, serde::Deserialize)]
|
#[derive(Default, Clone, serde::Deserialize)]
|
||||||
pub struct ProjectObject {
|
pub struct ProjectObject {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub path: PathBuf,
|
pub path: Option<PathBuf>,
|
||||||
|
pub target_path: Option<PathBuf>,
|
||||||
|
pub base_path: Option<PathBuf>,
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ProjectObject {
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
if let Some(name) = &self.name {
|
||||||
|
name
|
||||||
|
} else if let Some(path) = &self.path {
|
||||||
|
path.to_str().unwrap_or("[invalid path]")
|
||||||
|
} else {
|
||||||
|
"[unknown]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum ProjectObjectNode {
|
pub enum ProjectObjectNode {
|
||||||
File(String, ProjectObject),
|
File(String, ProjectObject),
|
||||||
|
@ -53,11 +67,22 @@ fn find_dir<'a>(
|
||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_nodes(objects: &[ProjectObject]) -> Vec<ProjectObjectNode> {
|
fn build_nodes(
|
||||||
|
objects: &[ProjectObject],
|
||||||
|
project_dir: &Path,
|
||||||
|
target_obj_dir: &Option<PathBuf>,
|
||||||
|
base_obj_dir: &Option<PathBuf>,
|
||||||
|
) -> Vec<ProjectObjectNode> {
|
||||||
let mut nodes = vec![];
|
let mut nodes = vec![];
|
||||||
for object in objects {
|
for object in objects {
|
||||||
let mut out_nodes = &mut nodes;
|
let mut out_nodes = &mut nodes;
|
||||||
let path = object.name.as_ref().map(Path::new).unwrap_or(&object.path);
|
let path = if let Some(name) = &object.name {
|
||||||
|
Path::new(name)
|
||||||
|
} else if let Some(path) = &object.path {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
if let Some(parent) = path.parent() {
|
if let Some(parent) = path.parent() {
|
||||||
for component in parent.components() {
|
for component in parent.components() {
|
||||||
if let Component::Normal(name) = component {
|
if let Component::Normal(name) = component {
|
||||||
|
@ -66,8 +91,23 @@ fn build_nodes(objects: &[ProjectObject]) -> Vec<ProjectObjectNode> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut object = object.clone();
|
||||||
|
if let (Some(target_obj_dir), Some(path), None) =
|
||||||
|
(target_obj_dir, &object.path, &object.target_path)
|
||||||
|
{
|
||||||
|
object.target_path = Some(target_obj_dir.join(path));
|
||||||
|
} else if let Some(path) = &object.target_path {
|
||||||
|
object.target_path = Some(project_dir.join(path));
|
||||||
|
}
|
||||||
|
if let (Some(base_obj_dir), Some(path), None) =
|
||||||
|
(base_obj_dir, &object.path, &object.base_path)
|
||||||
|
{
|
||||||
|
object.base_path = Some(base_obj_dir.join(path));
|
||||||
|
} else if let Some(path) = &object.base_path {
|
||||||
|
object.base_path = Some(project_dir.join(path));
|
||||||
|
}
|
||||||
let filename = path.file_name().unwrap().to_str().unwrap().to_string();
|
let filename = path.file_name().unwrap().to_str().unwrap().to_string();
|
||||||
out_nodes.push(ProjectObjectNode::File(filename, object.clone()));
|
out_nodes.push(ProjectObjectNode::File(filename, object));
|
||||||
}
|
}
|
||||||
nodes
|
nodes
|
||||||
}
|
}
|
||||||
|
@ -84,10 +124,14 @@ pub fn load_project_config(config: &mut AppConfig) -> Result<()> {
|
||||||
config.target_obj_dir = project_config.target_dir.map(|p| project_dir.join(p));
|
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.base_obj_dir = project_config.base_dir.map(|p| project_dir.join(p));
|
||||||
config.build_target = project_config.build_target;
|
config.build_target = project_config.build_target;
|
||||||
config.watch_patterns = project_config.watch_patterns;
|
config.watch_patterns = project_config.watch_patterns.unwrap_or_else(|| {
|
||||||
|
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
|
||||||
|
});
|
||||||
config.watcher_change = true;
|
config.watcher_change = true;
|
||||||
config.objects = project_config.objects;
|
config.objects = project_config.objects;
|
||||||
config.object_nodes = build_nodes(&config.objects);
|
config.object_nodes =
|
||||||
|
build_nodes(&config.objects, project_dir, &config.target_obj_dir, &config.base_obj_dir);
|
||||||
|
config.project_config_loaded = true;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{path::Path, process::Command, str::from_utf8, sync::mpsc::Receiver};
|
use std::{path::Path, process::Command, str::from_utf8, sync::mpsc::Receiver};
|
||||||
|
|
||||||
use anyhow::{Context, Error, Result};
|
use anyhow::{anyhow, Context, Error, Result};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -76,47 +76,51 @@ fn run_build(
|
||||||
config: AppConfigRef,
|
config: AppConfigRef,
|
||||||
) -> Result<Box<ObjDiffResult>> {
|
) -> Result<Box<ObjDiffResult>> {
|
||||||
let config = config.read().map_err(|_| Error::msg("Failed to lock app config"))?.clone();
|
let config = config.read().map_err(|_| Error::msg("Failed to lock app config"))?.clone();
|
||||||
let obj_path = config.obj_path.as_ref().ok_or_else(|| Error::msg("Missing obj path"))?;
|
let obj_config = config.selected_obj.as_ref().ok_or_else(|| Error::msg("Missing obj path"))?;
|
||||||
let project_dir =
|
let project_dir =
|
||||||
config.project_dir.as_ref().ok_or_else(|| Error::msg("Missing project dir"))?;
|
config.project_dir.as_ref().ok_or_else(|| Error::msg("Missing project dir"))?;
|
||||||
let mut target_path = config
|
let target_path_rel = obj_config.target_path.strip_prefix(project_dir).map_err(|_| {
|
||||||
.target_obj_dir
|
anyhow!(
|
||||||
.as_ref()
|
"Target path '{}' doesn't begin with '{}'",
|
||||||
.ok_or_else(|| Error::msg("Missing target obj dir"))?
|
obj_config.target_path.display(),
|
||||||
.to_owned();
|
project_dir.display()
|
||||||
target_path.push(obj_path);
|
)
|
||||||
let mut base_path =
|
})?;
|
||||||
config.base_obj_dir.as_ref().ok_or_else(|| Error::msg("Missing base obj dir"))?.to_owned();
|
let base_path_rel = obj_config.base_path.strip_prefix(project_dir).map_err(|_| {
|
||||||
base_path.push(obj_path);
|
anyhow!(
|
||||||
let target_path_rel = target_path
|
"Base path '{}' doesn't begin with '{}'",
|
||||||
.strip_prefix(project_dir)
|
obj_config.base_path.display(),
|
||||||
.context("Failed to create relative target obj path")?;
|
project_dir.display()
|
||||||
let base_path_rel =
|
)
|
||||||
base_path.strip_prefix(project_dir).context("Failed to create relative base obj path")?;
|
})?;
|
||||||
|
|
||||||
let total = if config.build_target { 5 } else { 4 };
|
let total = if config.build_target { 5 } else { 4 };
|
||||||
let first_status = if config.build_target {
|
let first_status = if config.build_target {
|
||||||
update_status(status, format!("Building target {obj_path}"), 0, total, &cancel)?;
|
update_status(status, format!("Building target {}", target_path_rel.display()), 0, total, &cancel)?;
|
||||||
run_make(project_dir, target_path_rel, &config)
|
run_make(project_dir, target_path_rel, &config)
|
||||||
} else {
|
} else {
|
||||||
BuildStatus { success: true, log: String::new() }
|
BuildStatus { success: true, log: String::new() }
|
||||||
};
|
};
|
||||||
|
|
||||||
update_status(status, format!("Building base {obj_path}"), 1, total, &cancel)?;
|
update_status(status, format!("Building base {}", base_path_rel.display()), 1, total, &cancel)?;
|
||||||
let second_status = run_make(project_dir, base_path_rel, &config);
|
let second_status = run_make(project_dir, base_path_rel, &config);
|
||||||
|
|
||||||
let time = OffsetDateTime::now_utc();
|
let time = OffsetDateTime::now_utc();
|
||||||
|
|
||||||
let mut first_obj = if first_status.success {
|
let mut first_obj = if first_status.success {
|
||||||
update_status(status, format!("Loading target {obj_path}"), 2, total, &cancel)?;
|
update_status(status, format!("Loading target {}", target_path_rel.display()), 2, total, &cancel)?;
|
||||||
Some(elf::read(&target_path)?)
|
Some(elf::read(&obj_config.target_path).with_context(|| {
|
||||||
|
format!("Failed to read object '{}'", obj_config.target_path.display())
|
||||||
|
})?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut second_obj = if second_status.success {
|
let mut second_obj = if second_status.success {
|
||||||
update_status(status, format!("Loading base {obj_path}"), 3, total, &cancel)?;
|
update_status(status, format!("Loading base {}", base_path_rel.display()), 3, total, &cancel)?;
|
||||||
Some(elf::read(&base_path)?)
|
Some(elf::read(&obj_config.base_path).with_context(|| {
|
||||||
|
format!("Failed to read object '{}'", obj_config.base_path.display())
|
||||||
|
})?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,7 +17,7 @@ use globset::Glob;
|
||||||
use self_update::cargo_crate_version;
|
use self_update::cargo_crate_version;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{AppConfig, AppConfigRef},
|
app::{AppConfig, AppConfigRef, ObjectConfig},
|
||||||
config::{ProjectObject, ProjectObjectNode},
|
config::{ProjectObject, ProjectObjectNode},
|
||||||
jobs::{
|
jobs::{
|
||||||
check_update::{start_check_update, CheckUpdateResult},
|
check_update::{start_check_update, CheckUpdateResult},
|
||||||
|
@ -79,7 +79,7 @@ impl ConfigViewState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_WATCH_PATTERNS: &[&str] = &[
|
pub const DEFAULT_WATCH_PATTERNS: &[&str] = &[
|
||||||
"*.c", "*.cp", "*.cpp", "*.cxx", "*.h", "*.hp", "*.hpp", "*.hxx", "*.s", "*.S", "*.asm",
|
"*.c", "*.cp", "*.cpp", "*.cxx", "*.h", "*.hp", "*.hpp", "*.hxx", "*.s", "*.S", "*.asm",
|
||||||
"*.inc", "*.py", "*.yml", "*.txt", "*.json",
|
"*.inc", "*.py", "*.yml", "*.txt", "*.json",
|
||||||
];
|
];
|
||||||
|
@ -129,7 +129,7 @@ pub fn config_ui(
|
||||||
selected_wsl_distro,
|
selected_wsl_distro,
|
||||||
target_obj_dir,
|
target_obj_dir,
|
||||||
base_obj_dir,
|
base_obj_dir,
|
||||||
obj_path,
|
selected_obj,
|
||||||
auto_update_check,
|
auto_update_check,
|
||||||
objects,
|
objects,
|
||||||
object_nodes,
|
object_nodes,
|
||||||
|
@ -205,9 +205,9 @@ pub fn config_ui(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if let (Some(base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) {
|
let mut new_selected_obj = selected_obj.clone();
|
||||||
let mut new_build_obj = obj_path.clone();
|
if objects.is_empty() {
|
||||||
if objects.is_empty() {
|
if let (Some(base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) {
|
||||||
if ui.button("Select object").clicked() {
|
if ui.button("Select object").clicked() {
|
||||||
if let Some(path) = rfd::FileDialog::new()
|
if let Some(path) = rfd::FileDialog::new()
|
||||||
.set_directory(&target_dir)
|
.set_directory(&target_dir)
|
||||||
|
@ -215,88 +215,99 @@ pub fn config_ui(
|
||||||
.pick_file()
|
.pick_file()
|
||||||
{
|
{
|
||||||
if let Ok(obj_path) = path.strip_prefix(&base_dir) {
|
if let Ok(obj_path) = path.strip_prefix(&base_dir) {
|
||||||
new_build_obj = Some(obj_path.display().to_string());
|
let target_path = target_dir.join(obj_path);
|
||||||
|
new_selected_obj = Some(ObjectConfig {
|
||||||
|
name: obj_path.display().to_string(),
|
||||||
|
target_path,
|
||||||
|
base_path: path,
|
||||||
|
reverse_fn_order: None,
|
||||||
|
});
|
||||||
} else if let Ok(obj_path) = path.strip_prefix(&target_dir) {
|
} else if let Ok(obj_path) = path.strip_prefix(&target_dir) {
|
||||||
new_build_obj = Some(obj_path.display().to_string());
|
let base_path = base_dir.join(obj_path);
|
||||||
|
new_selected_obj = Some(ObjectConfig {
|
||||||
|
name: obj_path.display().to_string(),
|
||||||
|
target_path: path,
|
||||||
|
base_path,
|
||||||
|
reverse_fn_order: None,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(obj) = obj_path {
|
if let Some(obj) = selected_obj {
|
||||||
ui.label(
|
ui.label(
|
||||||
RichText::new(&*obj)
|
RichText::new(&obj.name)
|
||||||
.color(appearance.replace_color)
|
.color(appearance.replace_color)
|
||||||
.family(FontFamily::Monospace),
|
.family(FontFamily::Monospace),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let had_search = !state.object_search.is_empty();
|
ui.colored_label(appearance.delete_color, "Missing project settings");
|
||||||
egui::TextEdit::singleline(&mut state.object_search).hint_text("Filter").ui(ui);
|
}
|
||||||
|
} else {
|
||||||
|
let had_search = !state.object_search.is_empty();
|
||||||
|
egui::TextEdit::singleline(&mut state.object_search).hint_text("Filter").ui(ui);
|
||||||
|
|
||||||
let mut root_open = None;
|
let mut root_open = None;
|
||||||
let mut node_open = NodeOpen::Default;
|
let mut node_open = NodeOpen::Default;
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked() {
|
if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked() {
|
||||||
root_open = Some(false);
|
root_open = Some(false);
|
||||||
node_open = NodeOpen::Close;
|
node_open = NodeOpen::Close;
|
||||||
}
|
}
|
||||||
if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() {
|
if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() {
|
||||||
root_open = Some(true);
|
|
||||||
node_open = NodeOpen::Open;
|
|
||||||
}
|
|
||||||
if ui
|
|
||||||
.add_enabled(obj_path.is_some(), egui::Button::new("⌖").small())
|
|
||||||
.on_hover_text_at_pointer("Current object")
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
root_open = Some(true);
|
|
||||||
node_open = NodeOpen::Object;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if state.object_search.is_empty() {
|
|
||||||
if had_search {
|
|
||||||
root_open = Some(true);
|
|
||||||
node_open = NodeOpen::Object;
|
|
||||||
}
|
|
||||||
} else if !had_search {
|
|
||||||
root_open = Some(true);
|
root_open = Some(true);
|
||||||
node_open = NodeOpen::Open;
|
node_open = NodeOpen::Open;
|
||||||
}
|
}
|
||||||
|
if ui
|
||||||
CollapsingHeader::new(RichText::new("🗀 Objects").font(FontId {
|
.add_enabled(selected_obj.is_some(), egui::Button::new("⌖").small())
|
||||||
size: appearance.ui_font.size,
|
.on_hover_text_at_pointer("Current object")
|
||||||
family: appearance.code_font.family.clone(),
|
.clicked()
|
||||||
}))
|
{
|
||||||
.open(root_open)
|
root_open = Some(true);
|
||||||
.default_open(true)
|
node_open = NodeOpen::Object;
|
||||||
.show(ui, |ui| {
|
|
||||||
let mut nodes = Cow::Borrowed(object_nodes);
|
|
||||||
if !state.object_search.is_empty() {
|
|
||||||
let search = state.object_search.to_ascii_lowercase();
|
|
||||||
nodes = Cow::Owned(
|
|
||||||
object_nodes.iter().filter_map(|node| filter_node(node, &search)).collect(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.style_mut().wrap = Some(false);
|
|
||||||
for node in nodes.iter() {
|
|
||||||
display_node(ui, &mut new_build_obj, node, appearance, node_open);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if new_build_obj != *obj_path {
|
|
||||||
if let Some(obj) = new_build_obj {
|
|
||||||
// Will set obj_changed, which will trigger a rebuild
|
|
||||||
config_guard.set_obj_path(obj);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
if state.object_search.is_empty() {
|
||||||
|
if had_search {
|
||||||
|
root_open = Some(true);
|
||||||
|
node_open = NodeOpen::Object;
|
||||||
|
}
|
||||||
|
} else if !had_search {
|
||||||
|
root_open = Some(true);
|
||||||
|
node_open = NodeOpen::Open;
|
||||||
}
|
}
|
||||||
if config_guard.obj_path.is_some()
|
|
||||||
&& ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked()
|
CollapsingHeader::new(RichText::new("🗀 Objects").font(FontId {
|
||||||
{
|
size: appearance.ui_font.size,
|
||||||
state.queue_build = true;
|
family: appearance.code_font.family.clone(),
|
||||||
|
}))
|
||||||
|
.open(root_open)
|
||||||
|
.default_open(true)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
let mut nodes = Cow::Borrowed(object_nodes);
|
||||||
|
if !state.object_search.is_empty() {
|
||||||
|
let search = state.object_search.to_ascii_lowercase();
|
||||||
|
nodes = Cow::Owned(
|
||||||
|
object_nodes.iter().filter_map(|node| filter_node(node, &search)).collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.style_mut().wrap = Some(false);
|
||||||
|
for node in nodes.iter() {
|
||||||
|
display_node(ui, &mut new_selected_obj, node, appearance, node_open);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if new_selected_obj != *selected_obj {
|
||||||
|
if let Some(obj) = new_selected_obj {
|
||||||
|
// Will set obj_changed, which will trigger a rebuild
|
||||||
|
config_guard.set_selected_obj(obj);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
ui.colored_label(appearance.delete_color, "Missing project settings");
|
if config_guard.selected_obj.is_some()
|
||||||
|
&& ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked()
|
||||||
|
{
|
||||||
|
state.queue_build = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
@ -304,13 +315,13 @@ pub fn config_ui(
|
||||||
|
|
||||||
fn display_object(
|
fn display_object(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
obj_path: &mut Option<String>,
|
selected_obj: &mut Option<ObjectConfig>,
|
||||||
name: &str,
|
name: &str,
|
||||||
object: &ProjectObject,
|
object: &ProjectObject,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
) {
|
) {
|
||||||
let path_string = object.path.to_string_lossy().to_string();
|
let object_name = object.name();
|
||||||
let selected = matches!(obj_path, Some(path) if path == &path_string);
|
let selected = matches!(selected_obj, Some(obj) if obj.name == object_name);
|
||||||
let color = if selected { appearance.emphasized_text_color } else { appearance.text_color };
|
let color = if selected { appearance.emphasized_text_color } else { appearance.text_color };
|
||||||
if SelectableLabel::new(
|
if SelectableLabel::new(
|
||||||
selected,
|
selected,
|
||||||
|
@ -324,7 +335,12 @@ fn display_object(
|
||||||
.ui(ui)
|
.ui(ui)
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
*obj_path = Some(path_string);
|
*selected_obj = Some(ObjectConfig {
|
||||||
|
name: object_name.to_string(),
|
||||||
|
target_path: object.target_path.clone().unwrap_or_default(),
|
||||||
|
base_path: object.base_path.clone().unwrap_or_default(),
|
||||||
|
reverse_fn_order: object.reverse_fn_order,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,17 +355,17 @@ enum NodeOpen {
|
||||||
|
|
||||||
fn display_node(
|
fn display_node(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
obj_path: &mut Option<String>,
|
selected_obj: &mut Option<ObjectConfig>,
|
||||||
node: &ProjectObjectNode,
|
node: &ProjectObjectNode,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
node_open: NodeOpen,
|
node_open: NodeOpen,
|
||||||
) {
|
) {
|
||||||
match node {
|
match node {
|
||||||
ProjectObjectNode::File(name, object) => {
|
ProjectObjectNode::File(name, object) => {
|
||||||
display_object(ui, obj_path, name, object, appearance);
|
display_object(ui, selected_obj, name, object, appearance);
|
||||||
}
|
}
|
||||||
ProjectObjectNode::Dir(name, children) => {
|
ProjectObjectNode::Dir(name, children) => {
|
||||||
let contains_obj = obj_path.as_ref().map(|path| contains_node(node, path));
|
let contains_obj = selected_obj.as_ref().map(|path| contains_node(node, path));
|
||||||
let open = match node_open {
|
let open = match node_open {
|
||||||
NodeOpen::Default => None,
|
NodeOpen::Default => None,
|
||||||
NodeOpen::Open => Some(true),
|
NodeOpen::Open => Some(true),
|
||||||
|
@ -372,21 +388,18 @@ fn display_node(
|
||||||
.open(open)
|
.open(open)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
for node in children {
|
for node in children {
|
||||||
display_node(ui, obj_path, node, appearance, node_open);
|
display_node(ui, selected_obj, node, appearance, node_open);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contains_node(node: &ProjectObjectNode, path: &str) -> bool {
|
fn contains_node(node: &ProjectObjectNode, selected_obj: &ObjectConfig) -> bool {
|
||||||
match node {
|
match node {
|
||||||
ProjectObjectNode::File(_, object) => {
|
ProjectObjectNode::File(_, object) => object.name() == selected_obj.name,
|
||||||
let path_string = object.path.to_string_lossy().to_string();
|
|
||||||
path == path_string
|
|
||||||
}
|
|
||||||
ProjectObjectNode::Dir(_, children) => {
|
ProjectObjectNode::Dir(_, children) => {
|
||||||
children.iter().any(|node| contains_node(node, path))
|
children.iter().any(|node| contains_node(node, selected_obj))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -444,11 +457,12 @@ fn pick_folder_ui(
|
||||||
label: &str,
|
label: &str,
|
||||||
tooltip: impl FnOnce(&mut egui::Ui),
|
tooltip: impl FnOnce(&mut egui::Ui),
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
|
enabled: bool,
|
||||||
) -> egui::Response {
|
) -> egui::Response {
|
||||||
let response = ui.horizontal(|ui| {
|
let response = ui.horizontal(|ui| {
|
||||||
subheading(ui, label, appearance);
|
subheading(ui, label, appearance);
|
||||||
ui.link(HELP_ICON).on_hover_ui(tooltip);
|
ui.link(HELP_ICON).on_hover_ui(tooltip);
|
||||||
ui.button("Select")
|
ui.add_enabled(enabled, egui::Button::new("Select"))
|
||||||
});
|
});
|
||||||
ui.label(format_path(dir, appearance));
|
ui.label(format_path(dir, appearance));
|
||||||
response.inner
|
response.inner
|
||||||
|
@ -506,6 +520,7 @@ fn split_obj_config_ui(
|
||||||
ui.label(job);
|
ui.label(job);
|
||||||
},
|
},
|
||||||
appearance,
|
appearance,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
if let Some(path) = rfd::FileDialog::new().pick_folder() {
|
if let Some(path) = rfd::FileDialog::new().pick_folder() {
|
||||||
|
@ -566,6 +581,7 @@ fn split_obj_config_ui(
|
||||||
ui.label(job);
|
ui.label(job);
|
||||||
},
|
},
|
||||||
appearance,
|
appearance,
|
||||||
|
!config.project_config_loaded,
|
||||||
);
|
);
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
if let Some(path) = rfd::FileDialog::new().set_directory(&project_dir).pick_folder() {
|
if let Some(path) = rfd::FileDialog::new().set_directory(&project_dir).pick_folder() {
|
||||||
|
@ -615,6 +631,7 @@ fn split_obj_config_ui(
|
||||||
ui.label(job);
|
ui.label(job);
|
||||||
},
|
},
|
||||||
appearance,
|
appearance,
|
||||||
|
!config.project_config_loaded,
|
||||||
);
|
);
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
if let Some(path) = rfd::FileDialog::new().set_directory(&project_dir).pick_folder() {
|
if let Some(path) = rfd::FileDialog::new().set_directory(&project_dir).pick_folder() {
|
||||||
|
@ -626,7 +643,7 @@ fn split_obj_config_ui(
|
||||||
|
|
||||||
subheading(ui, "Watch settings", appearance);
|
subheading(ui, "Watch settings", appearance);
|
||||||
let response =
|
let response =
|
||||||
ui.checkbox(&mut config.watcher_enabled, "Rebuild on changes").on_hover_ui(|ui| {
|
ui.checkbox(&mut config.rebuild_on_changes, "Rebuild on changes").on_hover_ui(|ui| {
|
||||||
let mut job = LayoutJob::default();
|
let mut job = LayoutJob::default();
|
||||||
job.append(
|
job.append(
|
||||||
"Automatically re-run the build & diff when files change.",
|
"Automatically re-run the build & diff when files change.",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use egui::{ProgressBar, Widget};
|
use egui::{ProgressBar, RichText, Widget};
|
||||||
|
|
||||||
use crate::{jobs::JobQueue, views::appearance::Appearance};
|
use crate::{jobs::JobQueue, views::appearance::Appearance};
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ pub fn jobs_ui(ui: &mut egui::Ui, jobs: &mut JobQueue, appearance: &Appearance)
|
||||||
bar.ui(ui);
|
bar.ui(ui);
|
||||||
const STATUS_LENGTH: usize = 80;
|
const STATUS_LENGTH: usize = 80;
|
||||||
if let Some(err) = &status.error {
|
if let Some(err) = &status.error {
|
||||||
let err_string = err.to_string();
|
let err_string = format!("{:#}", err);
|
||||||
ui.colored_label(
|
ui.colored_label(
|
||||||
appearance.delete_color,
|
appearance.delete_color,
|
||||||
if err_string.len() > STATUS_LENGTH - 10 {
|
if err_string.len() > STATUS_LENGTH - 10 {
|
||||||
|
@ -39,13 +39,15 @@ pub fn jobs_ui(ui: &mut egui::Ui, jobs: &mut JobQueue, appearance: &Appearance)
|
||||||
} else {
|
} else {
|
||||||
format!("Error: {:width$}", err_string, width = STATUS_LENGTH - 7)
|
format!("Error: {:width$}", err_string, width = STATUS_LENGTH - 7)
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
|
.on_hover_text_at_pointer(RichText::new(err_string).color(appearance.delete_color));
|
||||||
} else {
|
} else {
|
||||||
ui.label(if status.status.len() > STATUS_LENGTH - 3 {
|
ui.label(if status.status.len() > STATUS_LENGTH - 3 {
|
||||||
format!("{}…", &status.status[0..STATUS_LENGTH - 3])
|
format!("{}…", &status.status[0..STATUS_LENGTH - 3])
|
||||||
} else {
|
} else {
|
||||||
format!("{:width$}", &status.status, width = STATUS_LENGTH)
|
format!("{:width$}", &status.status, width = STATUS_LENGTH)
|
||||||
});
|
})
|
||||||
|
.on_hover_text_at_pointer(&status.status);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,15 +62,10 @@ impl DiffViewState {
|
||||||
|
|
||||||
self.symbol_state.disable_reverse_fn_order = false;
|
self.symbol_state.disable_reverse_fn_order = false;
|
||||||
if let Ok(config) = config.read() {
|
if let Ok(config) = config.read() {
|
||||||
if let Some(obj_path) = &config.obj_path {
|
if let Some(obj_config) = &config.selected_obj {
|
||||||
if let Some(object) = config.objects.iter().find(|object| {
|
if let Some(value) = obj_config.reverse_fn_order {
|
||||||
let path_string = object.path.to_string_lossy().to_string();
|
self.symbol_state.reverse_fn_order = value;
|
||||||
&path_string == obj_path
|
self.symbol_state.disable_reverse_fn_order = true;
|
||||||
}) {
|
|
||||||
if let Some(value) = object.reverse_fn_order {
|
|
||||||
self.symbol_state.reverse_fn_order = value;
|
|
||||||
self.symbol_state.disable_reverse_fn_order = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue