mirror of https://github.com/encounter/objdiff.git
Add "Open source file" option
Available when right-clicking an object in the object list or when viewing an object Resolves #99
This commit is contained in:
parent
8fc142d316
commit
bb039a1445
|
@ -2084,6 +2084,25 @@ version = "2.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4"
|
checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-docker"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-wsl"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
|
||||||
|
dependencies = [
|
||||||
|
"is-docker",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_ci"
|
name = "is_ci"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
@ -2904,6 +2923,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"notify",
|
"notify",
|
||||||
"objdiff-core",
|
"objdiff-core",
|
||||||
|
"open",
|
||||||
"path-slash",
|
"path-slash",
|
||||||
"png",
|
"png",
|
||||||
"pollster",
|
"pollster",
|
||||||
|
@ -2941,6 +2961,17 @@ version = "1.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "open"
|
||||||
|
version = "5.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3"
|
||||||
|
dependencies = [
|
||||||
|
"is-wsl",
|
||||||
|
"libc",
|
||||||
|
"pathdiff",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.66"
|
version = "0.10.66"
|
||||||
|
@ -3066,6 +3097,12 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42"
|
checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathdiff"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathfinder_geometry"
|
name = "pathfinder_geometry"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
|
|
@ -124,6 +124,10 @@ impl ProjectObject {
|
||||||
pub fn hidden(&self) -> bool {
|
pub fn hidden(&self) -> bool {
|
||||||
self.metadata.as_ref().and_then(|m| m.auto_generated).unwrap_or(false)
|
self.metadata.as_ref().and_then(|m| m.auto_generated).unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn source_path(&self) -> Option<&String> {
|
||||||
|
self.metadata.as_ref().and_then(|m| m.source_path.as_ref())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
|
|
|
@ -40,6 +40,7 @@ globset = { version = "0.4", features = ["serde1"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
notify = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39dbb7f301ff7b20e594e34c3a2" }
|
notify = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39dbb7f301ff7b20e594e34c3a2" }
|
||||||
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
||||||
|
open = "5.3"
|
||||||
png = "0.17"
|
png = "0.17"
|
||||||
pollster = "0.3"
|
pollster = "0.3"
|
||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
|
|
|
@ -73,6 +73,7 @@ pub struct ObjectConfig {
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
pub complete: Option<bool>,
|
pub complete: Option<bool>,
|
||||||
pub scratch: Option<ScratchConfig>,
|
pub scratch: Option<ScratchConfig>,
|
||||||
|
pub source_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -61,6 +61,7 @@ impl ObjectConfigV0 {
|
||||||
reverse_fn_order: self.reverse_fn_order,
|
reverse_fn_order: self.reverse_fn_order,
|
||||||
complete: None,
|
complete: None,
|
||||||
scratch: None,
|
scratch: None,
|
||||||
|
source_path: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
use std::string::FromUtf16Error;
|
use std::string::FromUtf16Error;
|
||||||
use std::{
|
use std::{
|
||||||
mem::take,
|
mem::take,
|
||||||
path::{PathBuf, MAIN_SEPARATOR},
|
path::{Path, PathBuf, MAIN_SEPARATOR},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(all(windows, feature = "wsl"))]
|
#[cfg(all(windows, feature = "wsl"))]
|
||||||
|
@ -96,6 +96,7 @@ impl ConfigViewState {
|
||||||
reverse_fn_order: None,
|
reverse_fn_order: None,
|
||||||
complete: None,
|
complete: None,
|
||||||
scratch: None,
|
scratch: None,
|
||||||
|
source_path: None,
|
||||||
});
|
});
|
||||||
} else if let Ok(obj_path) = path.strip_prefix(target_dir) {
|
} else if let Ok(obj_path) = path.strip_prefix(target_dir) {
|
||||||
let base_path = base_dir.join(obj_path);
|
let base_path = base_dir.join(obj_path);
|
||||||
|
@ -106,6 +107,7 @@ impl ConfigViewState {
|
||||||
reverse_fn_order: None,
|
reverse_fn_order: None,
|
||||||
complete: None,
|
complete: None,
|
||||||
scratch: None,
|
scratch: None,
|
||||||
|
source_path: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,7 +176,10 @@ pub fn config_ui(
|
||||||
) {
|
) {
|
||||||
let mut state_guard = state.write().unwrap();
|
let mut state_guard = state.write().unwrap();
|
||||||
let AppState {
|
let AppState {
|
||||||
config: AppConfig { target_obj_dir, base_obj_dir, selected_obj, auto_update_check, .. },
|
config:
|
||||||
|
AppConfig {
|
||||||
|
project_dir, target_obj_dir, base_obj_dir, selected_obj, auto_update_check, ..
|
||||||
|
},
|
||||||
objects,
|
objects,
|
||||||
object_nodes,
|
object_nodes,
|
||||||
..
|
..
|
||||||
|
@ -318,7 +323,14 @@ pub fn config_ui(
|
||||||
config_state.show_hidden,
|
config_state.show_hidden,
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
display_node(ui, &mut new_selected_obj, &node, appearance, node_open);
|
display_node(
|
||||||
|
ui,
|
||||||
|
&mut new_selected_obj,
|
||||||
|
project_dir.as_deref(),
|
||||||
|
&node,
|
||||||
|
appearance,
|
||||||
|
node_open,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -333,13 +345,12 @@ pub fn config_ui(
|
||||||
{
|
{
|
||||||
config_state.queue_build = true;
|
config_state.queue_build = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_object(
|
fn display_object(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
selected_obj: &mut Option<ObjectConfig>,
|
selected_obj: &mut Option<ObjectConfig>,
|
||||||
|
project_dir: Option<&Path>,
|
||||||
name: &str,
|
name: &str,
|
||||||
object: &ProjectObject,
|
object: &ProjectObject,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
|
@ -357,7 +368,7 @@ fn display_object(
|
||||||
} else {
|
} else {
|
||||||
appearance.text_color
|
appearance.text_color
|
||||||
};
|
};
|
||||||
let clicked = SelectableLabel::new(
|
let response = SelectableLabel::new(
|
||||||
selected,
|
selected,
|
||||||
RichText::new(name)
|
RichText::new(name)
|
||||||
.font(FontId {
|
.font(FontId {
|
||||||
|
@ -366,11 +377,13 @@ fn display_object(
|
||||||
})
|
})
|
||||||
.color(color),
|
.color(color),
|
||||||
)
|
)
|
||||||
.ui(ui)
|
.ui(ui);
|
||||||
.clicked();
|
if get_source_path(project_dir, object).is_some() {
|
||||||
|
response.context_menu(|ui| object_context_ui(ui, object, project_dir));
|
||||||
|
}
|
||||||
// Always recreate ObjectConfig if selected, in case the project config changed.
|
// Always recreate ObjectConfig if selected, in case the project config changed.
|
||||||
// ObjectConfig is compared using equality, so this won't unnecessarily trigger a rebuild.
|
// ObjectConfig is compared using equality, so this won't unnecessarily trigger a rebuild.
|
||||||
if selected || clicked {
|
if selected || response.clicked() {
|
||||||
*selected_obj = Some(ObjectConfig {
|
*selected_obj = Some(ObjectConfig {
|
||||||
name: object_name.to_string(),
|
name: object_name.to_string(),
|
||||||
target_path: object.target_path.clone(),
|
target_path: object.target_path.clone(),
|
||||||
|
@ -378,10 +391,31 @@ fn display_object(
|
||||||
reverse_fn_order: object.reverse_fn_order(),
|
reverse_fn_order: object.reverse_fn_order(),
|
||||||
complete: object.complete(),
|
complete: object.complete(),
|
||||||
scratch: object.scratch.clone(),
|
scratch: object.scratch.clone(),
|
||||||
|
source_path: object.source_path().cloned(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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());
|
||||||
|
if let Err(e) = open::that_detached(&source_path) {
|
||||||
|
log::error!("Failed to open source file: {e}");
|
||||||
|
}
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
enum NodeOpen {
|
enum NodeOpen {
|
||||||
#[default]
|
#[default]
|
||||||
|
@ -394,13 +428,14 @@ enum NodeOpen {
|
||||||
fn display_node(
|
fn display_node(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
selected_obj: &mut Option<ObjectConfig>,
|
selected_obj: &mut Option<ObjectConfig>,
|
||||||
|
project_dir: Option<&Path>,
|
||||||
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, selected_obj, name, object, appearance);
|
display_object(ui, selected_obj, project_dir, name, object, appearance);
|
||||||
}
|
}
|
||||||
ProjectObjectNode::Dir(name, children) => {
|
ProjectObjectNode::Dir(name, children) => {
|
||||||
let contains_obj = selected_obj.as_ref().map(|path| contains_node(node, path));
|
let contains_obj = selected_obj.as_ref().map(|path| contains_node(node, path));
|
||||||
|
@ -426,7 +461,7 @@ fn display_node(
|
||||||
.open(open)
|
.open(open)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
for node in children {
|
for node in children {
|
||||||
display_node(ui, selected_obj, node, appearance, node_open);
|
display_node(ui, selected_obj, project_dir, node, appearance, node_open);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -509,6 +509,18 @@ pub fn function_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
ui.separator();
|
||||||
|
if ui
|
||||||
|
.add_enabled(
|
||||||
|
state.source_path_available,
|
||||||
|
egui::Button::new("🖹 Source file"),
|
||||||
|
)
|
||||||
|
.on_hover_text_at_pointer("Open the source file in the default editor")
|
||||||
|
.on_disabled_hover_text("Source file metadata missing")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
state.queue_open_source_path = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.scope(|ui| {
|
ui.scope(|ui| {
|
||||||
|
|
|
@ -52,6 +52,8 @@ pub struct DiffViewState {
|
||||||
pub scratch_available: bool,
|
pub scratch_available: bool,
|
||||||
pub queue_scratch: bool,
|
pub queue_scratch: bool,
|
||||||
pub scratch_running: bool,
|
pub scratch_running: bool,
|
||||||
|
pub source_path_available: bool,
|
||||||
|
pub queue_open_source_path: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -86,6 +88,9 @@ impl DiffViewState {
|
||||||
self.symbol_state.reverse_fn_order = value;
|
self.symbol_state.reverse_fn_order = value;
|
||||||
self.symbol_state.disable_reverse_fn_order = true;
|
self.symbol_state.disable_reverse_fn_order = true;
|
||||||
}
|
}
|
||||||
|
self.source_path_available = obj_config.source_path.is_some();
|
||||||
|
} else {
|
||||||
|
self.source_path_available = false;
|
||||||
}
|
}
|
||||||
self.scratch_available = CreateScratchConfig::is_available(&state.config);
|
self.scratch_available = CreateScratchConfig::is_available(&state.config);
|
||||||
}
|
}
|
||||||
|
@ -122,6 +127,22 @@ impl DiffViewState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.queue_open_source_path {
|
||||||
|
self.queue_open_source_path = false;
|
||||||
|
if let Ok(state) = state.read() {
|
||||||
|
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());
|
||||||
|
open::that_detached(source_path).unwrap_or_else(|err| {
|
||||||
|
log::error!("Failed to open source file: {err}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -518,10 +539,27 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea
|
||||||
|ui| {
|
|ui| {
|
||||||
ui.set_width(column_width);
|
ui.set_width(column_width);
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
ui.scope(|ui| {
|
ui.scope(|ui| {
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||||
|
|
||||||
ui.label("Build base:");
|
ui.label("Build base:");
|
||||||
|
});
|
||||||
|
ui.separator();
|
||||||
|
if ui
|
||||||
|
.add_enabled(
|
||||||
|
state.source_path_available,
|
||||||
|
egui::Button::new("🖹 Source file"),
|
||||||
|
)
|
||||||
|
.on_hover_text_at_pointer("Open the source file in the default editor")
|
||||||
|
.on_disabled_hover_text("Source file metadata missing")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
state.queue_open_source_path = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.scope(|ui| {
|
||||||
|
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||||
if result.second_status.success {
|
if result.second_status.success {
|
||||||
if result.second_obj.is_none() {
|
if result.second_obj.is_none() {
|
||||||
ui.colored_label(appearance.replace_color, "Missing");
|
ui.colored_label(appearance.replace_color, "Missing");
|
||||||
|
|
Loading…
Reference in New Issue