Fix task terminal split (#41218)

Kirill Bulatov created

Re-lands https://github.com/zed-industries/zed/pull/40824

Release Notes:

- N/A

Change summary

crates/project/src/terminals.rs            | 12 ++++-
crates/terminal/src/terminal.rs            |  2 
crates/terminal_view/src/terminal_panel.rs |  4 +-
crates/terminal_view/src/terminal_view.rs  | 47 ++++++++++++-----------
4 files changed, 36 insertions(+), 29 deletions(-)

Detailed changes

crates/project/src/terminals.rs 🔗

@@ -410,14 +410,19 @@ impl Project {
         terminal: &Entity<Terminal>,
         cx: &mut Context<'_, Project>,
         cwd: Option<PathBuf>,
-    ) -> Result<Entity<Terminal>> {
+    ) -> Task<Result<Entity<Terminal>>> {
+        // We cannot clone the task's terminal, as it will effectively re-spawn the task, which might not be desirable.
+        // For now, create a new shell instead.
+        if terminal.read(cx).task().is_some() {
+            return self.create_terminal_shell(cwd, cx);
+        }
         let local_path = if self.is_via_remote_server() {
             None
         } else {
             cwd
         };
 
-        terminal
+        let new_terminal = terminal
             .read(cx)
             .clone_builder(cx, local_path)
             .map(|builder| {
@@ -442,7 +447,8 @@ impl Project {
                 .detach();
 
                 terminal_handle
-            })
+            });
+        Task::ready(new_terminal)
     }
 
     pub fn terminal_settings<'a>(

crates/terminal/src/terminal.rs 🔗

@@ -455,7 +455,7 @@ impl TerminalBuilder {
                 args: Option<Vec<String>>,
                 title_override: Option<String>,
             ) -> Self {
-                log::info!("Using {program} as shell");
+                log::debug!("Using {program} as shell");
                 Self {
                     program,
                     args,

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -461,11 +461,11 @@ impl TerminalPanel {
         cx.spawn_in(window, async move |panel, cx| {
             let terminal = project
                 .update(cx, |project, cx| match terminal_view {
-                    Some(view) => Task::ready(project.clone_terminal(
+                    Some(view) => project.clone_terminal(
                         &view.read(cx).terminal.clone(),
                         cx,
                         working_directory,
-                    )),
+                    ),
                     None => project.create_terminal_shell(working_directory, cx),
                 })
                 .ok()?

crates/terminal_view/src/terminal_view.rs 🔗

@@ -38,7 +38,7 @@ use ui::{
     prelude::*,
     scrollbars::{self, GlobalSetting, ScrollbarVisibility},
 };
-use util::{ResultExt, maybe};
+use util::ResultExt;
 use workspace::{
     CloseActiveItem, NewCenterTerminal, NewTerminal, ToolbarItemLocation, Workspace, WorkspaceId,
     delete_unloaded_items,
@@ -1219,29 +1219,30 @@ impl Item for TerminalView {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Task<Option<Entity<Self>>> {
-        Task::ready(maybe!({
-            let terminal = self
-                .project
-                .update(cx, |project, cx| {
-                    let cwd = project
-                        .active_project_directory(cx)
-                        .map(|it| it.to_path_buf());
-                    project.clone_terminal(self.terminal(), cx, cwd)
+        let Some(terminal_task) = self
+            .project
+            .update(cx, |project, cx| {
+                let cwd = project
+                    .active_project_directory(cx)
+                    .map(|it| it.to_path_buf());
+                project.clone_terminal(self.terminal(), cx, cwd)
+            })
+            .ok()
+        else {
+            return Task::ready(None);
+        };
+
+        let workspace = self.workspace.clone();
+        let project = self.project.clone();
+        cx.spawn_in(window, async move |_, cx| {
+            let terminal = terminal_task.await.log_err()?;
+            cx.update(|window, cx| {
+                cx.new(|cx| {
+                    TerminalView::new(terminal, workspace, workspace_id, project, window, cx)
                 })
-                .ok()?
-                .log_err()?;
-
-            Some(cx.new(|cx| {
-                TerminalView::new(
-                    terminal,
-                    self.workspace.clone(),
-                    workspace_id,
-                    self.project.clone(),
-                    window,
-                    cx,
-                )
-            }))
-        }))
+            })
+            .ok()
+        })
     }
 
     fn is_dirty(&self, cx: &gpui::App) -> bool {