Skip to content

Commit bb039a1

Browse files
committed
Add "Open source file" option
Available when right-clicking an object in the object list or when viewing an object Resolves #99
1 parent 8fc142d commit bb039a1

File tree

8 files changed

+142
-13
lines changed

8 files changed

+142
-13
lines changed

Cargo.lock

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

objdiff-core/src/config/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ impl ProjectObject {
124124
pub fn hidden(&self) -> bool {
125125
self.metadata.as_ref().and_then(|m| m.auto_generated).unwrap_or(false)
126126
}
127+
128+
pub fn source_path(&self) -> Option<&String> {
129+
self.metadata.as_ref().and_then(|m| m.source_path.as_ref())
130+
}
127131
}
128132

129133
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]

objdiff-gui/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ globset = { version = "0.4", features = ["serde1"] }
4040
log = "0.4"
4141
notify = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39dbb7f301ff7b20e594e34c3a2" }
4242
objdiff-core = { path = "../objdiff-core", features = ["all"] }
43+
open = "5.3"
4344
png = "0.17"
4445
pollster = "0.3"
4546
regex = "1.10"

objdiff-gui/src/app.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ pub struct ObjectConfig {
7373
pub reverse_fn_order: Option<bool>,
7474
pub complete: Option<bool>,
7575
pub scratch: Option<ScratchConfig>,
76+
pub source_path: Option<String>,
7677
}
7778

7879
#[inline]

objdiff-gui/src/app_config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ impl ObjectConfigV0 {
6161
reverse_fn_order: self.reverse_fn_order,
6262
complete: None,
6363
scratch: None,
64+
source_path: None,
6465
}
6566
}
6667
}

objdiff-gui/src/views/config.rs

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
use std::string::FromUtf16Error;
33
use std::{
44
mem::take,
5-
path::{PathBuf, MAIN_SEPARATOR},
5+
path::{Path, PathBuf, MAIN_SEPARATOR},
66
};
77

88
#[cfg(all(windows, feature = "wsl"))]
@@ -96,6 +96,7 @@ impl ConfigViewState {
9696
reverse_fn_order: None,
9797
complete: None,
9898
scratch: None,
99+
source_path: None,
99100
});
100101
} else if let Ok(obj_path) = path.strip_prefix(target_dir) {
101102
let base_path = base_dir.join(obj_path);
@@ -106,6 +107,7 @@ impl ConfigViewState {
106107
reverse_fn_order: None,
107108
complete: None,
108109
scratch: None,
110+
source_path: None,
109111
});
110112
}
111113
}
@@ -174,7 +176,10 @@ pub fn config_ui(
174176
) {
175177
let mut state_guard = state.write().unwrap();
176178
let AppState {
177-
config: AppConfig { target_obj_dir, base_obj_dir, selected_obj, auto_update_check, .. },
179+
config:
180+
AppConfig {
181+
project_dir, target_obj_dir, base_obj_dir, selected_obj, auto_update_check, ..
182+
},
178183
objects,
179184
object_nodes,
180185
..
@@ -318,7 +323,14 @@ pub fn config_ui(
318323
config_state.show_hidden,
319324
)
320325
}) {
321-
display_node(ui, &mut new_selected_obj, &node, appearance, node_open);
326+
display_node(
327+
ui,
328+
&mut new_selected_obj,
329+
project_dir.as_deref(),
330+
&node,
331+
appearance,
332+
node_open,
333+
);
322334
}
323335
});
324336
}
@@ -333,13 +345,12 @@ pub fn config_ui(
333345
{
334346
config_state.queue_build = true;
335347
}
336-
337-
ui.separator();
338348
}
339349

340350
fn display_object(
341351
ui: &mut egui::Ui,
342352
selected_obj: &mut Option<ObjectConfig>,
353+
project_dir: Option<&Path>,
343354
name: &str,
344355
object: &ProjectObject,
345356
appearance: &Appearance,
@@ -357,7 +368,7 @@ fn display_object(
357368
} else {
358369
appearance.text_color
359370
};
360-
let clicked = SelectableLabel::new(
371+
let response = SelectableLabel::new(
361372
selected,
362373
RichText::new(name)
363374
.font(FontId {
@@ -366,22 +377,45 @@ fn display_object(
366377
})
367378
.color(color),
368379
)
369-
.ui(ui)
370-
.clicked();
380+
.ui(ui);
381+
if get_source_path(project_dir, object).is_some() {
382+
response.context_menu(|ui| object_context_ui(ui, object, project_dir));
383+
}
371384
// Always recreate ObjectConfig if selected, in case the project config changed.
372385
// ObjectConfig is compared using equality, so this won't unnecessarily trigger a rebuild.
373-
if selected || clicked {
386+
if selected || response.clicked() {
374387
*selected_obj = Some(ObjectConfig {
375388
name: object_name.to_string(),
376389
target_path: object.target_path.clone(),
377390
base_path: object.base_path.clone(),
378391
reverse_fn_order: object.reverse_fn_order(),
379392
complete: object.complete(),
380393
scratch: object.scratch.clone(),
394+
source_path: object.source_path().cloned(),
381395
});
382396
}
383397
}
384398

399+
fn get_source_path(project_dir: Option<&Path>, object: &ProjectObject) -> Option<PathBuf> {
400+
project_dir.and_then(|dir| object.source_path().map(|path| dir.join(path)))
401+
}
402+
403+
fn object_context_ui(ui: &mut egui::Ui, object: &ProjectObject, project_dir: Option<&Path>) {
404+
if let Some(source_path) = get_source_path(project_dir, object) {
405+
if ui
406+
.button("Open source file")
407+
.on_hover_text("Open the source file in the default editor")
408+
.clicked()
409+
{
410+
log::info!("Opening file {}", source_path.display());
411+
if let Err(e) = open::that_detached(&source_path) {
412+
log::error!("Failed to open source file: {e}");
413+
}
414+
ui.close_menu();
415+
}
416+
}
417+
}
418+
385419
#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)]
386420
enum NodeOpen {
387421
#[default]
@@ -394,13 +428,14 @@ enum NodeOpen {
394428
fn display_node(
395429
ui: &mut egui::Ui,
396430
selected_obj: &mut Option<ObjectConfig>,
431+
project_dir: Option<&Path>,
397432
node: &ProjectObjectNode,
398433
appearance: &Appearance,
399434
node_open: NodeOpen,
400435
) {
401436
match node {
402437
ProjectObjectNode::File(name, object) => {
403-
display_object(ui, selected_obj, name, object, appearance);
438+
display_object(ui, selected_obj, project_dir, name, object, appearance);
404439
}
405440
ProjectObjectNode::Dir(name, children) => {
406441
let contains_obj = selected_obj.as_ref().map(|path| contains_node(node, path));
@@ -426,7 +461,7 @@ fn display_node(
426461
.open(open)
427462
.show(ui, |ui| {
428463
for node in children {
429-
display_node(ui, selected_obj, node, appearance, node_open);
464+
display_node(ui, selected_obj, project_dir, node, appearance, node_open);
430465
}
431466
});
432467
}

objdiff-gui/src/views/function_diff.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,18 @@ pub fn function_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance
509509
);
510510
}
511511
});
512+
ui.separator();
513+
if ui
514+
.add_enabled(
515+
state.source_path_available,
516+
egui::Button::new("🖹 Source file"),
517+
)
518+
.on_hover_text_at_pointer("Open the source file in the default editor")
519+
.on_disabled_hover_text("Source file metadata missing")
520+
.clicked()
521+
{
522+
state.queue_open_source_path = true;
523+
}
512524
});
513525

514526
ui.scope(|ui| {

objdiff-gui/src/views/symbol_diff.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ pub struct DiffViewState {
5252
pub scratch_available: bool,
5353
pub queue_scratch: bool,
5454
pub scratch_running: bool,
55+
pub source_path_available: bool,
56+
pub queue_open_source_path: bool,
5557
}
5658

5759
#[derive(Default)]
@@ -86,6 +88,9 @@ impl DiffViewState {
8688
self.symbol_state.reverse_fn_order = value;
8789
self.symbol_state.disable_reverse_fn_order = true;
8890
}
91+
self.source_path_available = obj_config.source_path.is_some();
92+
} else {
93+
self.source_path_available = false;
8994
}
9095
self.scratch_available = CreateScratchConfig::is_available(&state.config);
9196
}
@@ -122,6 +127,22 @@ impl DiffViewState {
122127
}
123128
}
124129
}
130+
131+
if self.queue_open_source_path {
132+
self.queue_open_source_path = false;
133+
if let Ok(state) = state.read() {
134+
if let (Some(project_dir), Some(source_path)) = (
135+
&state.config.project_dir,
136+
state.config.selected_obj.as_ref().and_then(|obj| obj.source_path.as_ref()),
137+
) {
138+
let source_path = project_dir.join(source_path);
139+
log::info!("Opening file {}", source_path.display());
140+
open::that_detached(source_path).unwrap_or_else(|err| {
141+
log::error!("Failed to open source file: {err}");
142+
});
143+
}
144+
}
145+
}
125146
}
126147
}
127148

@@ -518,10 +539,27 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea
518539
|ui| {
519540
ui.set_width(column_width);
520541

542+
ui.horizontal(|ui| {
543+
ui.scope(|ui| {
544+
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
545+
ui.label("Build base:");
546+
});
547+
ui.separator();
548+
if ui
549+
.add_enabled(
550+
state.source_path_available,
551+
egui::Button::new("🖹 Source file"),
552+
)
553+
.on_hover_text_at_pointer("Open the source file in the default editor")
554+
.on_disabled_hover_text("Source file metadata missing")
555+
.clicked()
556+
{
557+
state.queue_open_source_path = true;
558+
}
559+
});
560+
521561
ui.scope(|ui| {
522562
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
523-
524-
ui.label("Build base:");
525563
if result.second_status.success {
526564
if result.second_obj.is_none() {
527565
ui.colored_label(appearance.replace_color, "Missing");

0 commit comments

Comments
 (0)