22use std:: string:: FromUtf16Error ;
33use 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
340350fn 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 ) ]
386420enum NodeOpen {
387421 #[ default]
@@ -394,13 +428,14 @@ enum NodeOpen {
394428fn 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 }
0 commit comments