Rerun command if it is a file and the file changes

Michael Sloan created

Change summary

crates/zed/src/main.rs | 101 ++++++++++++++++++++++++++++++++-----------
1 file changed, 75 insertions(+), 26 deletions(-)

Detailed changes

crates/zed/src/main.rs 🔗

@@ -14,7 +14,7 @@ use editor::Editor;
 use extension::ExtensionHostProxy;
 use extension_host::ExtensionStore;
 use fs::{Fs, RealFs};
-use futures::{StreamExt, channel::oneshot, future};
+use futures::{FutureExt, StreamExt, channel::oneshot, future, select_biased};
 use git::GitHostingProviderRegistry;
 use gpui::{App, AppContext as _, Application, AsyncApp, Focusable as _, UpdateGlobal as _};
 use postage::stream::Stream as _;
@@ -733,6 +733,8 @@ pub fn main() {
             }
         }
 
+        // todo! cleanup
+        let fs = app_state.fs.clone();
         let app_state = app_state.clone();
 
         crate::zed::component_preview::init(app_state.clone(), cx);
@@ -754,38 +756,85 @@ pub fn main() {
                 "Will run {} when the selection changes",
                 selection_change_command
             );
-            let mut cursor_reciever = editor::LAST_CURSOR_POSITION_WATCH.1.clone();
+
+            let mut cursor_receiver = editor::LAST_CURSOR_POSITION_WATCH.1.clone();
             cx.background_spawn(async move {
-                while let Some(mut cursor) = cursor_reciever.recv().await {
+                // Set up file watcher for the command file
+                let command_path = PathBuf::from(&selection_change_command);
+                let mut file_changes = if command_path.exists() {
+                    let (events, _) = fs
+                        .watch(&command_path, std::time::Duration::from_millis(100))
+                        .await;
+                    Some(events)
+                } else {
+                    log::warn!(
+                        "Command file {} does not exist, only watching selection changes",
+                        command_path.display()
+                    );
+                    None
+                };
+
+                loop {
+                    select_biased! {
+                        // Handle cursor position changes
+                        cursor_update = cursor_receiver.recv().fuse() => {
+                            if cursor_update.is_none() {
+                                // Cursor watcher ended
+                                log::warn!("Cursor watcher for {} ended", command_path.display());
+                                break;
+                            }
+                        },
+
+                        // Handle file changes to the command file
+                        file_change = async {
+                            if let Some(ref mut events) = file_changes {
+                                events.next().await
+                            } else {
+                                future::pending().await
+                            }
+                        }.fuse() => {
+                            if file_change.is_none() {
+                                // File watcher ended
+                                log::warn!("File watcher for {} ended", command_path.display());
+                                file_changes = None;
+                            }
+                        }
+                    }
+
+                    // TODO: Could be more efficient
+                    let Some(mut cursor) = cursor_receiver.borrow().clone() else {
+                        continue;
+                    };
+
                     loop {
-                        // todo! Check if it's changed meanwhile and refresh.
-                        if let Some(cursor) = dbg!(&cursor) {
-                            let status = smol::process::Command::new(&selection_change_command)
-                                .arg(cursor.worktree_path.as_ref())
-                                .arg(format!(
-                                    "{}:{}:{}",
-                                    cursor.path.display(),
-                                    cursor.point.row + 1,
-                                    cursor.point.column + 1
-                                ))
-                                .status()
-                                .await;
-                            match status {
-                                Ok(status) => {
-                                    if !status.success() {
-                                        log::error!("Command failed with status {}", status);
-                                    }
-                                }
-                                Err(err) => {
-                                    log::error!("Command failed with error {}", err);
+                        let status = smol::process::Command::new(&selection_change_command)
+                            .arg(cursor.worktree_path.as_ref())
+                            .arg(format!(
+                                "{}:{}:{}",
+                                cursor.path.display(),
+                                cursor.point.row + 1,
+                                cursor.point.column + 1
+                            ))
+                            .status()
+                            .await;
+                        match status {
+                            Ok(status) => {
+                                if !status.success() {
+                                    log::error!("Command failed with status {}", status);
                                 }
                             }
+                            Err(err) => {
+                                log::error!("Command failed with error {}", err);
+                            }
                         }
-                        let new_cursor = cursor_reciever.borrow();
-                        if *new_cursor == cursor {
+
+                        let Some(new_cursor) = cursor_receiver.borrow().clone() else {
+                            break;
+                        };
+                        if new_cursor == cursor {
                             break;
                         }
-                        cursor = new_cursor.clone();
+                        cursor = new_cursor;
                     }
                 }
             })