Merge pull request #1304 from zed-industries/deploy-panic

Mikayla Maki created

Fixed working directory issues, added tests.

Change summary

Cargo.lock                      |  2 
crates/gpui/src/app.rs          | 23 ++++++--
crates/terminal/Cargo.toml      |  4 +
crates/terminal/src/terminal.rs | 98 ++++++++++++++++++++++++++++++++++
4 files changed, 119 insertions(+), 8 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -4878,6 +4878,8 @@ name = "terminal"
 version = "0.1.0"
 dependencies = [
  "alacritty_terminal",
+ "client",
+ "dirs 4.0.0",
  "editor",
  "futures",
  "gpui",

crates/gpui/src/app.rs 🔗

@@ -151,6 +151,7 @@ pub struct AsyncAppContext(Rc<RefCell<MutableAppContext>>);
 pub struct TestAppContext {
     cx: Rc<RefCell<MutableAppContext>>,
     foreground_platform: Rc<platform::test::ForegroundPlatform>,
+    condition_duration: Option<Duration>,
 }
 
 impl App {
@@ -337,6 +338,7 @@ impl TestAppContext {
         let cx = TestAppContext {
             cx: Rc::new(RefCell::new(cx)),
             foreground_platform,
+            condition_duration: None,
         };
         cx.cx.borrow_mut().weak_self = Some(Rc::downgrade(&cx.cx));
         cx
@@ -612,6 +614,19 @@ impl TestAppContext {
             test_window
         })
     }
+
+    pub fn set_condition_duration(&mut self, duration: Duration) {
+        self.condition_duration = Some(duration);
+    }
+    pub fn condition_duration(&self) -> Duration {
+        self.condition_duration.unwrap_or_else(|| {
+            if std::env::var("CI").is_ok() {
+                Duration::from_secs(2)
+            } else {
+                Duration::from_millis(500)
+            }
+        })
+    }
 }
 
 impl AsyncAppContext {
@@ -4424,6 +4439,7 @@ impl<T: View> ViewHandle<T> {
         use postage::prelude::{Sink as _, Stream as _};
 
         let (tx, mut rx) = postage::mpsc::channel(1024);
+        let timeout_duration = cx.condition_duration();
 
         let mut cx = cx.cx.borrow_mut();
         let subscriptions = self.update(&mut *cx, |_, cx| {
@@ -4445,14 +4461,9 @@ impl<T: View> ViewHandle<T> {
 
         let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
         let handle = self.downgrade();
-        let duration = if std::env::var("CI").is_ok() {
-            Duration::from_secs(2)
-        } else {
-            Duration::from_millis(500)
-        };
 
         async move {
-            crate::util::timeout(duration, async move {
+            crate::util::timeout(timeout_duration, async move {
                 loop {
                     {
                         let cx = cx.borrow();

crates/terminal/Cargo.toml 🔗

@@ -21,7 +21,11 @@ mio-extras = "2.0.6"
 futures = "0.3"
 ordered-float = "2.1.1"
 itertools = "0.10"
+dirs = "4.0.0"
 
 
 [dev-dependencies]
 gpui = { path = "../gpui", features = ["test-support"] }
+client = { path = "../client", features = ["test-support"]}
+project = { path = "../project", features = ["test-support"]}
+

crates/terminal/src/terminal.rs 🔗

@@ -9,6 +9,7 @@ use alacritty_terminal::{
     Term,
 };
 
+use dirs::home_dir;
 use futures::{
     channel::mpsc::{unbounded, UnboundedSender},
     StreamExt,
@@ -17,7 +18,7 @@ use gpui::{
     actions, color::Color, elements::*, impl_internal_actions, platform::CursorStyle,
     ClipboardItem, Entity, MutableAppContext, View, ViewContext,
 };
-use project::{Project, ProjectPath};
+use project::{LocalWorktree, Project, ProjectPath};
 use settings::Settings;
 use smallvec::SmallVec;
 use std::{collections::HashMap, path::PathBuf, sync::Arc};
@@ -268,11 +269,12 @@ impl Terminal {
     ///Create a new Terminal in the current working directory or the user's home directory
     fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
         let project = workspace.project().read(cx);
+
         let abs_path = project
             .active_entry()
             .and_then(|entry_id| project.worktree_for_entry(entry_id, cx))
             .and_then(|worktree_handle| worktree_handle.read(cx).as_local())
-            .map(|wt| wt.abs_path().to_path_buf());
+            .and_then(get_working_directory);
 
         workspace.add_item(Box::new(cx.add_view(|cx| Terminal::new(cx, abs_path))), cx);
     }
@@ -477,18 +479,29 @@ fn to_alac_rgb(color: Color) -> AlacRgb {
     }
 }
 
+fn get_working_directory(wt: &LocalWorktree) -> Option<PathBuf> {
+    Some(wt.abs_path().to_path_buf())
+        .filter(|path| path.is_dir())
+        .or_else(|| home_dir())
+}
+
 #[cfg(test)]
 mod tests {
+
+    use std::{path::Path, sync::atomic::AtomicUsize, time::Duration};
+
     use super::*;
     use alacritty_terminal::{grid::GridIterator, term::cell::Cell};
     use gpui::TestAppContext;
     use itertools::Itertools;
+    use project::{FakeFs, Fs, RealFs, RemoveOptions, Worktree};
 
     ///Basic integration test, can we get the terminal to show up, execute a command,
     //and produce noticable output?
     #[gpui::test]
     async fn test_terminal(cx: &mut TestAppContext) {
         let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx, None));
+        cx.set_condition_duration(Duration::from_secs(2));
 
         terminal.update(cx, |terminal, cx| {
             terminal.write_to_pty(&Input(("expr 3 + 4".to_string()).to_string()), cx);
@@ -512,4 +525,85 @@ mod tests {
             .collect::<Vec<String>>()
             .join("\n")
     }
+
+    #[gpui::test]
+    async fn single_file_worktree(cx: &mut TestAppContext) {
+        let mut async_cx = cx.to_async();
+        let http_client = client::test::FakeHttpClient::with_404_response();
+        let client = client::Client::new(http_client.clone());
+        let fake_fs = FakeFs::new(cx.background().clone());
+
+        let path = Path::new("/file/");
+        fake_fs.insert_file(path, "a".to_string()).await;
+
+        let worktree_handle = Worktree::local(
+            client,
+            path,
+            true,
+            fake_fs,
+            Arc::new(AtomicUsize::new(0)),
+            &mut async_cx,
+        )
+        .await
+        .ok()
+        .unwrap();
+
+        async_cx.update(|cx| {
+            let wt = worktree_handle.read(cx).as_local().unwrap();
+            let wd = get_working_directory(wt);
+            assert!(wd.is_some());
+            let path = wd.unwrap();
+            //This should be the system's working directory, so querying the real file system is probably ok.
+            assert!(path.is_dir());
+            assert_eq!(path, home_dir().unwrap());
+        });
+    }
+
+    #[gpui::test]
+    async fn test_worktree_directory(cx: &mut TestAppContext) {
+        let mut async_cx = cx.to_async();
+        let http_client = client::test::FakeHttpClient::with_404_response();
+        let client = client::Client::new(http_client.clone());
+
+        let fs = RealFs;
+        let mut test_wd = home_dir().unwrap();
+        test_wd.push("dir");
+
+        fs.create_dir(test_wd.as_path())
+            .await
+            .expect("File could not be created");
+
+        let worktree_handle = Worktree::local(
+            client,
+            test_wd.clone(),
+            true,
+            Arc::new(RealFs),
+            Arc::new(AtomicUsize::new(0)),
+            &mut async_cx,
+        )
+        .await
+        .ok()
+        .unwrap();
+
+        async_cx.update(|cx| {
+            let wt = worktree_handle.read(cx).as_local().unwrap();
+            let wd = get_working_directory(wt);
+            assert!(wd.is_some());
+            let path = wd.unwrap();
+            assert!(path.is_dir());
+            assert_eq!(path, test_wd);
+        });
+
+        //Clean up after ourselves.
+        fs.remove_dir(
+            test_wd.as_path(),
+            RemoveOptions {
+                recursive: false,
+                ignore_if_not_exists: true,
+            },
+        )
+        .await
+        .ok()
+        .expect("Could not remove test directory");
+    }
 }