@@ -30,7 +30,7 @@ use slotmap::Key as SlotMapKey;
3030use std:: any:: TypeId ;
3131use std:: collections:: { HashMap , VecDeque } ;
3232use std:: path:: { Path , PathBuf } ;
33- use std:: sync:: Arc ;
33+ use std:: sync:: { Arc , Mutex } ;
3434use widgets:: { TextEditor , text_editor} ;
3535
3636pub mod app_menu;
@@ -162,9 +162,11 @@ pub enum Message {
162162 /// Callback after opening a new file
163163 OpenFile ( Result < ( PathBuf , Arc < String > ) , anywho:: Error > ) ,
164164 /// Callback after saving the current file
165- FileSaved ( Result < PathBuf , anywho:: Error > ) ,
165+ FileSaved ( Option < Result < PathBuf , anywho:: Error > > ) ,
166166 /// Callback after asking to close a file discarding changes
167167 DiscardChanges ( DiscardChangesAction ) ,
168+ /// Fired when the watcher detects an external change to the open file
169+ ExternalFileChanged ( PathBuf ) ,
168170
169171 /// Deletes the given node entity of the navbar folder or file
170172 DeleteNode ( cosmic:: widget:: segmented_button:: Entity ) ,
@@ -649,6 +651,11 @@ impl cosmic::Application for AppModel {
649651 struct ConfigSubscription ;
650652 struct ThemeSubscription ;
651653
654+ let watched_path = match & self . state {
655+ State :: Ready { editor, .. } => editor. path . clone ( ) ,
656+ _ => None ,
657+ } ;
658+
652659 // Add subscriptions which are always active.
653660 let subscriptions = vec ! [
654661 // Watch for key_bind inputs
@@ -695,6 +702,8 @@ impl cosmic::Application for AppModel {
695702 }
696703 Message :: ConfigInput ( ConfigInput :: SystemThemeModeChange )
697704 } ) ,
705+ // Watch for external file changes
706+ file_watch_subscription( watched_path) ,
698707 ] ;
699708
700709 Subscription :: batch ( subscriptions)
@@ -736,6 +745,7 @@ impl cosmic::Application for AppModel {
736745 Message :: OpenFile ( result) => self . handle_open_file ( result) ,
737746 Message :: FileSaved ( result) => self . handle_file_saved ( result) ,
738747 Message :: DiscardChanges ( action) => self . handle_discard_changes ( action) ,
748+ Message :: ExternalFileChanged ( path) => self . handle_external_file_changed ( path) ,
739749
740750 // Vault / Node
741751 Message :: DeleteNode ( entity) => self . handle_delete_node ( entity) ,
@@ -1320,6 +1330,74 @@ fn cedilla_main_view<'a>(
13201330 }
13211331}
13221332
1333+ /// Watches for external changes on the currently open file
1334+ fn file_watch_subscription ( path : Option < PathBuf > ) -> Subscription < Message > {
1335+ use cosmic:: iced:: futures:: SinkExt ;
1336+ use cosmic:: iced_futures:: futures:: channel:: mpsc;
1337+ use notify:: { EventKind , RecursiveMode , Watcher , recommended_watcher} ;
1338+
1339+ let Some ( path) = path else {
1340+ return Subscription :: none ( ) ;
1341+ } ;
1342+
1343+ Subscription :: run_with ( path, |path| {
1344+ let path_owned = path. clone ( ) ;
1345+
1346+ cosmic:: iced_futures:: stream:: channel (
1347+ 16 ,
1348+ move |mut output : mpsc:: Sender < Message > | async move {
1349+ let ( tx, rx) = std:: sync:: mpsc:: channel :: < PathBuf > ( ) ;
1350+ let rx = Arc :: new ( Mutex :: new ( rx) ) ;
1351+
1352+ let mut watcher =
1353+ match recommended_watcher ( move |res : notify:: Result < notify:: Event > | {
1354+ if let Ok ( event) = res {
1355+ let is_relevant =
1356+ matches ! ( event. kind, EventKind :: Modify ( _) | EventKind :: Create ( _) ) ;
1357+ if is_relevant {
1358+ for p in event. paths {
1359+ let _ = tx. send ( p) ;
1360+ }
1361+ }
1362+ }
1363+ } ) {
1364+ Ok ( w) => w,
1365+ Err ( e) => {
1366+ eprintln ! ( "file_watcher: failed to create watcher: {e}" ) ;
1367+ return ;
1368+ }
1369+ } ;
1370+
1371+ if let Err ( e) = watcher. watch ( & path_owned, RecursiveMode :: NonRecursive ) {
1372+ eprintln ! ( "file_watcher: failed to watch {path_owned:?}: {e}" ) ;
1373+ return ;
1374+ }
1375+
1376+ let _watcher = watcher;
1377+
1378+ loop {
1379+ let rx2 = Arc :: clone ( & rx) ;
1380+ let changed_path =
1381+ match tokio:: task:: spawn_blocking ( move || rx2. lock ( ) . unwrap ( ) . recv ( ) ) . await
1382+ {
1383+ Ok ( Ok ( p) ) => p,
1384+ _ => return ,
1385+ } ;
1386+
1387+ if changed_path == path_owned {
1388+ let _ = output
1389+ . send ( Message :: ExternalFileChanged ( changed_path) )
1390+ . await ;
1391+
1392+ tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 300 ) ) . await ;
1393+ while rx. lock ( ) . unwrap ( ) . try_recv ( ) . is_ok ( ) { }
1394+ }
1395+ }
1396+ } ,
1397+ )
1398+ } )
1399+ }
1400+
13231401/// Returns the text editor scrollable Id
13241402fn editor_scrollable_id ( ) -> widget:: Id {
13251403 widget:: Id :: new ( "editor_scroll" )
0 commit comments