Allow splitting terminal items in the central pane group (#22088)

Kirill Bulatov created

Follow-up of https://github.com/zed-industries/zed/pull/22004
Closes https://github.com/zed-industries/zed/issues/22078

Release Notes:

- Fixed splitting terminal items in the center

Change summary

crates/project/src/terminals.rs            | 332 ++++++++++++-----------
crates/terminal/src/terminal.rs            |   3 
crates/terminal_view/src/persistence.rs    |   8 
crates/terminal_view/src/terminal_panel.rs | 156 ++++------
crates/terminal_view/src/terminal_view.rs  |  59 +++-
docs/src/tasks.md                          |   2 
6 files changed, 299 insertions(+), 261 deletions(-)

Detailed changes

crates/project/src/terminals.rs 🔗

@@ -94,7 +94,6 @@ impl Project {
                 }
             }
         };
-        let ssh_details = self.ssh_details(cx);
 
         let mut settings_location = None;
         if let Some(path) = path.as_ref() {
@@ -107,10 +106,57 @@ impl Project {
         }
         let settings = TerminalSettings::get(settings_location, cx).clone();
 
+        cx.spawn(move |project, mut cx| async move {
+            let python_venv_directory = if let Some(path) = path.clone() {
+                project
+                    .update(&mut cx, |this, cx| {
+                        this.python_venv_directory(path, settings.detect_venv.clone(), cx)
+                    })?
+                    .await
+            } else {
+                None
+            };
+            project.update(&mut cx, |project, cx| {
+                project.create_terminal_with_venv(kind, python_venv_directory, window, cx)
+            })?
+        })
+    }
+
+    pub fn create_terminal_with_venv(
+        &mut self,
+        kind: TerminalKind,
+        python_venv_directory: Option<PathBuf>,
+        window: AnyWindowHandle,
+        cx: &mut ModelContext<Self>,
+    ) -> Result<Model<Terminal>> {
+        let this = &mut *self;
+        let path: Option<Arc<Path>> = match &kind {
+            TerminalKind::Shell(path) => path.as_ref().map(|path| Arc::from(path.as_ref())),
+            TerminalKind::Task(spawn_task) => {
+                if let Some(cwd) = &spawn_task.cwd {
+                    Some(Arc::from(cwd.as_ref()))
+                } else {
+                    this.active_project_directory(cx)
+                }
+            }
+        };
+        let ssh_details = this.ssh_details(cx);
+
+        let mut settings_location = None;
+        if let Some(path) = path.as_ref() {
+            if let Some((worktree, _)) = this.find_worktree(path, cx) {
+                settings_location = Some(SettingsLocation {
+                    worktree_id: worktree.read(cx).id(),
+                    path,
+                });
+            }
+        }
+        let settings = TerminalSettings::get(settings_location, cx).clone();
+
         let (completion_tx, completion_rx) = bounded(1);
 
         // Start with the environment that we might have inherited from the Zed CLI.
-        let mut env = self
+        let mut env = this
             .environment
             .read(cx)
             .get_cli_environment()
@@ -125,165 +171,141 @@ impl Project {
             None
         };
 
-        cx.spawn(move |this, mut cx| async move {
-            let python_venv_directory = if let Some(path) = path.clone() {
-                this.update(&mut cx, |this, cx| {
-                    this.python_venv_directory(path, settings.detect_venv.clone(), cx)
-                })?
-                .await
-            } else {
-                None
-            };
-            let mut python_venv_activate_command = None;
-
-            let (spawn_task, shell) = match kind {
-                TerminalKind::Shell(_) => {
-                    if let Some(python_venv_directory) = python_venv_directory {
-                        python_venv_activate_command = this
-                            .update(&mut cx, |this, _| {
-                                this.python_activate_command(
-                                    &python_venv_directory,
-                                    &settings.detect_venv,
-                                )
-                            })
-                            .ok()
-                            .flatten();
-                    }
+        let mut python_venv_activate_command = None;
 
-                    match &ssh_details {
-                        Some((host, ssh_command)) => {
-                            log::debug!("Connecting to a remote server: {ssh_command:?}");
-
-                            // Alacritty sets its terminfo to `alacritty`, this requiring hosts to have it installed
-                            // to properly display colors.
-                            // We do not have the luxury of assuming the host has it installed,
-                            // so we set it to a default that does not break the highlighting via ssh.
-                            env.entry("TERM".to_string())
-                                .or_insert_with(|| "xterm-256color".to_string());
-
-                            let (program, args) =
-                                wrap_for_ssh(ssh_command, None, path.as_deref(), env, None);
-                            env = HashMap::default();
-                            (
-                                Option::<TaskState>::None,
-                                Shell::WithArguments {
-                                    program,
-                                    args,
-                                    title_override: Some(format!("{} — Terminal", host).into()),
-                                },
-                            )
-                        }
-                        None => (None, settings.shell.clone()),
-                    }
+        let (spawn_task, shell) = match kind {
+            TerminalKind::Shell(_) => {
+                if let Some(python_venv_directory) = &python_venv_directory {
+                    python_venv_activate_command =
+                        this.python_activate_command(python_venv_directory, &settings.detect_venv);
                 }
-                TerminalKind::Task(spawn_task) => {
-                    let task_state = Some(TaskState {
-                        id: spawn_task.id,
-                        full_label: spawn_task.full_label,
-                        label: spawn_task.label,
-                        command_label: spawn_task.command_label,
-                        hide: spawn_task.hide,
-                        status: TaskStatus::Running,
-                        show_summary: spawn_task.show_summary,
-                        show_command: spawn_task.show_command,
-                        completion_rx,
-                    });
-
-                    env.extend(spawn_task.env);
-
-                    if let Some(venv_path) = &python_venv_directory {
-                        env.insert(
-                            "VIRTUAL_ENV".to_string(),
-                            venv_path.to_string_lossy().to_string(),
-                        );
-                    }
 
-                    match &ssh_details {
-                        Some((host, ssh_command)) => {
-                            log::debug!("Connecting to a remote server: {ssh_command:?}");
-                            env.entry("TERM".to_string())
-                                .or_insert_with(|| "xterm-256color".to_string());
-                            let (program, args) = wrap_for_ssh(
-                                ssh_command,
-                                Some((&spawn_task.command, &spawn_task.args)),
-                                path.as_deref(),
-                                env,
-                                python_venv_directory,
-                            );
-                            env = HashMap::default();
-                            (
-                                task_state,
-                                Shell::WithArguments {
-                                    program,
-                                    args,
-                                    title_override: Some(format!("{} — Terminal", host).into()),
-                                },
-                            )
-                        }
-                        None => {
-                            if let Some(venv_path) = &python_venv_directory {
-                                add_environment_path(&mut env, &venv_path.join("bin")).log_err();
-                            }
-
-                            (
-                                task_state,
-                                Shell::WithArguments {
-                                    program: spawn_task.command,
-                                    args: spawn_task.args,
-                                    title_override: None,
-                                },
-                            )
-                        }
+                match &ssh_details {
+                    Some((host, ssh_command)) => {
+                        log::debug!("Connecting to a remote server: {ssh_command:?}");
+
+                        // Alacritty sets its terminfo to `alacritty`, this requiring hosts to have it installed
+                        // to properly display colors.
+                        // We do not have the luxury of assuming the host has it installed,
+                        // so we set it to a default that does not break the highlighting via ssh.
+                        env.entry("TERM".to_string())
+                            .or_insert_with(|| "xterm-256color".to_string());
+
+                        let (program, args) =
+                            wrap_for_ssh(&ssh_command, None, path.as_deref(), env, None);
+                        env = HashMap::default();
+                        (
+                            Option::<TaskState>::None,
+                            Shell::WithArguments {
+                                program,
+                                args,
+                                title_override: Some(format!("{} — Terminal", host).into()),
+                            },
+                        )
                     }
+                    None => (None, settings.shell.clone()),
                 }
-            };
-            let terminal = this.update(&mut cx, |this, cx| {
-                TerminalBuilder::new(
-                    local_path.map(|path| path.to_path_buf()),
-                    spawn_task,
-                    shell,
-                    env,
-                    settings.cursor_shape.unwrap_or_default(),
-                    settings.alternate_scroll,
-                    settings.max_scroll_history_lines,
-                    ssh_details.is_some(),
-                    window,
-                    completion_tx,
-                    cx,
-                )
-                .map(|builder| {
-                    let terminal_handle = cx.new_model(|cx| builder.subscribe(cx));
-
-                    this.terminals
-                        .local_handles
-                        .push(terminal_handle.downgrade());
-
-                    let id = terminal_handle.entity_id();
-                    cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
-                        let handles = &mut project.terminals.local_handles;
-
-                        if let Some(index) = handles
-                            .iter()
-                            .position(|terminal| terminal.entity_id() == id)
-                        {
-                            handles.remove(index);
-                            cx.notify();
-                        }
-                    })
-                    .detach();
+            }
+            TerminalKind::Task(spawn_task) => {
+                let task_state = Some(TaskState {
+                    id: spawn_task.id,
+                    full_label: spawn_task.full_label,
+                    label: spawn_task.label,
+                    command_label: spawn_task.command_label,
+                    hide: spawn_task.hide,
+                    status: TaskStatus::Running,
+                    show_summary: spawn_task.show_summary,
+                    show_command: spawn_task.show_command,
+                    completion_rx,
+                });
 
-                    if let Some(activate_command) = python_venv_activate_command {
-                        this.activate_python_virtual_environment(
-                            activate_command,
-                            &terminal_handle,
-                            cx,
+                env.extend(spawn_task.env);
+
+                if let Some(venv_path) = &python_venv_directory {
+                    env.insert(
+                        "VIRTUAL_ENV".to_string(),
+                        venv_path.to_string_lossy().to_string(),
+                    );
+                }
+
+                match &ssh_details {
+                    Some((host, ssh_command)) => {
+                        log::debug!("Connecting to a remote server: {ssh_command:?}");
+                        env.entry("TERM".to_string())
+                            .or_insert_with(|| "xterm-256color".to_string());
+                        let (program, args) = wrap_for_ssh(
+                            &ssh_command,
+                            Some((&spawn_task.command, &spawn_task.args)),
+                            path.as_deref(),
+                            env,
+                            python_venv_directory.as_deref(),
                         );
+                        env = HashMap::default();
+                        (
+                            task_state,
+                            Shell::WithArguments {
+                                program,
+                                args,
+                                title_override: Some(format!("{} — Terminal", host).into()),
+                            },
+                        )
+                    }
+                    None => {
+                        if let Some(venv_path) = &python_venv_directory {
+                            add_environment_path(&mut env, &venv_path.join("bin")).log_err();
+                        }
+
+                        (
+                            task_state,
+                            Shell::WithArguments {
+                                program: spawn_task.command,
+                                args: spawn_task.args,
+                                title_override: None,
+                            },
+                        )
                     }
-                    terminal_handle
-                })
-            })?;
+                }
+            }
+        };
+        TerminalBuilder::new(
+            local_path.map(|path| path.to_path_buf()),
+            python_venv_directory,
+            spawn_task,
+            shell,
+            env,
+            settings.cursor_shape.unwrap_or_default(),
+            settings.alternate_scroll,
+            settings.max_scroll_history_lines,
+            ssh_details.is_some(),
+            window,
+            completion_tx,
+            cx,
+        )
+        .map(|builder| {
+            let terminal_handle = cx.new_model(|cx| builder.subscribe(cx));
+
+            this.terminals
+                .local_handles
+                .push(terminal_handle.downgrade());
+
+            let id = terminal_handle.entity_id();
+            cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
+                let handles = &mut project.terminals.local_handles;
+
+                if let Some(index) = handles
+                    .iter()
+                    .position(|terminal| terminal.entity_id() == id)
+                {
+                    handles.remove(index);
+                    cx.notify();
+                }
+            })
+            .detach();
 
-            terminal
+            if let Some(activate_command) = python_venv_activate_command {
+                this.activate_python_virtual_environment(activate_command, &terminal_handle, cx);
+            }
+            terminal_handle
         })
     }
 
@@ -418,9 +440,9 @@ impl Project {
         &self,
         command: String,
         terminal_handle: &Model<Terminal>,
-        cx: &mut ModelContext<Project>,
+        cx: &mut AppContext,
     ) {
-        terminal_handle.update(cx, |this, _| this.input_bytes(command.into_bytes()));
+        terminal_handle.update(cx, |terminal, _| terminal.input_bytes(command.into_bytes()));
     }
 
     pub fn local_terminal_handles(&self) -> &Vec<WeakModel<terminal::Terminal>> {
@@ -433,7 +455,7 @@ pub fn wrap_for_ssh(
     command: Option<(&String, &Vec<String>)>,
     path: Option<&Path>,
     env: HashMap<String, String>,
-    venv_directory: Option<PathBuf>,
+    venv_directory: Option<&Path>,
 ) -> (String, Vec<String>) {
     let to_run = if let Some((command, args)) = command {
         let command = Cow::Borrowed(command.as_str());

crates/terminal/src/terminal.rs 🔗

@@ -324,6 +324,7 @@ impl TerminalBuilder {
     #[allow(clippy::too_many_arguments)]
     pub fn new(
         working_directory: Option<PathBuf>,
+        python_venv_directory: Option<PathBuf>,
         task: Option<TaskState>,
         shell: Shell,
         mut env: HashMap<String, String>,
@@ -471,6 +472,7 @@ impl TerminalBuilder {
             word_regex: RegexSearch::new(WORD_REGEX).unwrap(),
             vi_mode_enabled: false,
             is_ssh_terminal,
+            python_venv_directory,
         };
 
         Ok(TerminalBuilder {
@@ -619,6 +621,7 @@ pub struct Terminal {
     pub breadcrumb_text: String,
     pub pty_info: PtyProcessInfo,
     title_override: Option<SharedString>,
+    pub python_venv_directory: Option<PathBuf>,
     scroll_px: Pixels,
     next_link_id: usize,
     selection_phase: SelectionPhase,

crates/terminal_view/src/persistence.rs 🔗

@@ -251,7 +251,13 @@ async fn deserialize_pane_group(
                 let terminal = terminal.await.ok()?;
                 pane.update(cx, |pane, cx| {
                     let terminal_view = Box::new(cx.new_view(|cx| {
-                        TerminalView::new(terminal, workspace.clone(), Some(workspace_id), cx)
+                        TerminalView::new(
+                            terminal,
+                            workspace.clone(),
+                            Some(workspace_id),
+                            project.downgrade(),
+                            cx,
+                        )
                     }));
                     pane.add_item(terminal_view, true, false, None, cx);
                 })

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -335,24 +335,13 @@ impl TerminalPanel {
                 self.serialize(cx);
             }
             pane::Event::Split(direction) => {
-                let new_pane = self.new_pane_with_cloned_active_terminal(cx);
+                let Some(new_pane) = self.new_pane_with_cloned_active_terminal(cx) else {
+                    return;
+                };
                 let pane = pane.clone();
                 let direction = *direction;
-                cx.spawn(move |terminal_panel, mut cx| async move {
-                    let Some(new_pane) = new_pane.await else {
-                        return;
-                    };
-                    terminal_panel
-                        .update(&mut cx, |terminal_panel, cx| {
-                            terminal_panel
-                                .center
-                                .split(&pane, &new_pane, direction)
-                                .log_err();
-                            cx.focus_view(&new_pane);
-                        })
-                        .ok();
-                })
-                .detach();
+                self.center.split(&pane, &new_pane, direction).log_err();
+                cx.focus_view(&new_pane);
             }
             pane::Event::Focus => {
                 self.active_pane = pane.clone();
@@ -365,63 +354,56 @@ impl TerminalPanel {
     fn new_pane_with_cloned_active_terminal(
         &mut self,
         cx: &mut ViewContext<Self>,
-    ) -> Task<Option<View<Pane>>> {
-        let Some(workspace) = self.workspace.clone().upgrade() else {
-            return Task::ready(None);
-        };
-        let database_id = workspace.read(cx).database_id();
+    ) -> Option<View<Pane>> {
+        let workspace = self.workspace.clone().upgrade()?;
+        let workspace = workspace.read(cx);
+        let database_id = workspace.database_id();
         let weak_workspace = self.workspace.clone();
-        let project = workspace.read(cx).project().clone();
-        let working_directory = self
+        let project = workspace.project().clone();
+        let (working_directory, python_venv_directory) = self
             .active_pane
             .read(cx)
             .active_item()
             .and_then(|item| item.downcast::<TerminalView>())
-            .and_then(|terminal_view| {
-                terminal_view
-                    .read(cx)
-                    .terminal()
-                    .read(cx)
-                    .working_directory()
+            .map(|terminal_view| {
+                let terminal = terminal_view.read(cx).terminal().read(cx);
+                (
+                    terminal
+                        .working_directory()
+                        .or_else(|| default_working_directory(workspace, cx)),
+                    terminal.python_venv_directory.clone(),
+                )
             })
-            .or_else(|| default_working_directory(workspace.read(cx), cx));
+            .unwrap_or((None, None));
         let kind = TerminalKind::Shell(working_directory);
         let window = cx.window_handle();
-        cx.spawn(move |terminal_panel, mut cx| async move {
-            let terminal = project
-                .update(&mut cx, |project, cx| {
-                    project.create_terminal(kind, window, cx)
-                })
-                .log_err()?
-                .await
-                .log_err()?;
-
-            let terminal_view = Box::new(
-                cx.new_view(|cx| {
-                    TerminalView::new(terminal.clone(), weak_workspace.clone(), database_id, cx)
-                })
-                .ok()?,
-            );
-            let pane = terminal_panel
-                .update(&mut cx, |terminal_panel, cx| {
-                    let pane = new_terminal_pane(
-                        weak_workspace,
-                        project,
-                        terminal_panel.active_pane.read(cx).is_zoomed(),
-                        cx,
-                    );
-                    terminal_panel.apply_tab_bar_buttons(&pane, cx);
-                    pane
-                })
-                .ok()?;
-
-            pane.update(&mut cx, |pane, cx| {
-                pane.add_item(terminal_view, true, true, None, cx);
+        let terminal = project
+            .update(cx, |project, cx| {
+                project.create_terminal_with_venv(kind, python_venv_directory, window, cx)
             })
             .ok()?;
 
-            Some(pane)
-        })
+        let terminal_view = Box::new(cx.new_view(|cx| {
+            TerminalView::new(
+                terminal.clone(),
+                weak_workspace.clone(),
+                database_id,
+                project.downgrade(),
+                cx,
+            )
+        }));
+        let pane = new_terminal_pane(
+            weak_workspace,
+            project,
+            self.active_pane.read(cx).is_zoomed(),
+            cx,
+        );
+        self.apply_tab_bar_buttons(&pane, cx);
+        pane.update(cx, |pane, cx| {
+            pane.add_item(terminal_view, true, true, None, cx);
+        });
+
+        Some(pane)
     }
 
     pub fn open_terminal(
@@ -724,6 +706,7 @@ impl TerminalPanel {
                         terminal.clone(),
                         workspace.weak_handle(),
                         workspace.database_id(),
+                        workspace.project().downgrade(),
                         cx,
                     )
                 });
@@ -739,17 +722,19 @@ impl TerminalPanel {
         reveal_strategy: RevealStrategy,
         cx: &mut ViewContext<Self>,
     ) -> Task<Result<Model<Terminal>>> {
-        if !self.is_enabled(cx) {
-            return Task::ready(Err(anyhow!(
-                "terminal not yet supported for remote projects"
-            )));
-        }
-
         let workspace = self.workspace.clone();
         self.pending_terminals_to_add += 1;
 
         cx.spawn(|terminal_panel, mut cx| async move {
-            let pane = terminal_panel.update(&mut cx, |this, _| this.active_pane.clone())?;
+            if workspace.update(&mut cx, |workspace, cx| {
+                !is_enabled_in_workspace(workspace, cx)
+            })? {
+                anyhow::bail!("terminal not yet supported for remote projects");
+            }
+            let pane = terminal_panel.update(&mut cx, |terminal_panel, _| {
+                terminal_panel.pending_terminals_to_add += 1;
+                terminal_panel.active_pane.clone()
+            })?;
             let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
             let window = cx.window_handle();
             let terminal = project
@@ -763,6 +748,7 @@ impl TerminalPanel {
                         terminal.clone(),
                         workspace.weak_handle(),
                         workspace.database_id(),
+                        workspace.project().downgrade(),
                         cx,
                     )
                 }));
@@ -1218,25 +1204,19 @@ impl Render for TerminalPanel {
                     if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
                         cx.focus_view(&pane);
                     } else {
-                        let new_pane = terminal_panel.new_pane_with_cloned_active_terminal(cx);
-                        cx.spawn(|terminal_panel, mut cx| async move {
-                            if let Some(new_pane) = new_pane.await {
-                                terminal_panel
-                                    .update(&mut cx, |terminal_panel, cx| {
-                                        terminal_panel
-                                            .center
-                                            .split(
-                                                &terminal_panel.active_pane,
-                                                &new_pane,
-                                                SplitDirection::Right,
-                                            )
-                                            .log_err();
-                                        cx.focus_view(&new_pane);
-                                    })
-                                    .ok();
-                            }
-                        })
-                        .detach();
+                        if let Some(new_pane) =
+                            terminal_panel.new_pane_with_cloned_active_terminal(cx)
+                        {
+                            terminal_panel
+                                .center
+                                .split(
+                                    &terminal_panel.active_pane,
+                                    &new_pane,
+                                    SplitDirection::Right,
+                                )
+                                .log_err();
+                            cx.focus_view(&new_pane);
+                        }
                     }
                 }))
                 .on_action(cx.listener(

crates/terminal_view/src/terminal_view.rs 🔗

@@ -9,7 +9,7 @@ use gpui::{
     anchored, deferred, div, impl_actions, AnyElement, AppContext, DismissEvent, EventEmitter,
     FocusHandle, FocusableView, KeyContext, KeyDownEvent, Keystroke, Model, MouseButton,
     MouseDownEvent, Pixels, Render, ScrollWheelEvent, Styled, Subscription, Task, View,
-    VisualContext, WeakView,
+    VisualContext, WeakModel, WeakView,
 };
 use language::Bias;
 use persistence::TERMINAL_DB;
@@ -97,6 +97,7 @@ pub struct BlockContext<'a, 'b> {
 pub struct TerminalView {
     terminal: Model<Terminal>,
     workspace: WeakView<Workspace>,
+    project: WeakModel<Project>,
     focus_handle: FocusHandle,
     //Currently using iTerm bell, show bell emoji in tab until input is received
     has_bell: bool,
@@ -141,6 +142,7 @@ impl TerminalView {
         terminal: Model<Terminal>,
         workspace: WeakView<Workspace>,
         workspace_id: Option<WorkspaceId>,
+        project: WeakModel<Project>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
         let workspace_handle = workspace.clone();
@@ -160,6 +162,7 @@ impl TerminalView {
         Self {
             terminal,
             workspace: workspace_handle,
+            project,
             has_bell: false,
             focus_handle,
             context_menu: None,
@@ -1075,21 +1078,37 @@ impl Item for TerminalView {
 
     fn clone_on_split(
         &self,
-        _workspace_id: Option<WorkspaceId>,
-        _cx: &mut ViewContext<Self>,
+        workspace_id: Option<WorkspaceId>,
+        cx: &mut ViewContext<Self>,
     ) -> Option<View<Self>> {
-        //From what I can tell, there's no  way to tell the current working
-        //Directory of the terminal from outside the shell. There might be
-        //solutions to this, but they are non-trivial and require more IPC
-
-        // Some(TerminalContainer::new(
-        //     Err(anyhow::anyhow!("failed to instantiate terminal")),
-        //     workspace_id,
-        //     cx,
-        // ))
-
-        // TODO
-        None
+        let window = cx.window_handle();
+        let terminal = self
+            .project
+            .update(cx, |project, cx| {
+                let terminal = self.terminal().read(cx);
+                let working_directory = terminal
+                    .working_directory()
+                    .or_else(|| Some(project.active_project_directory(cx)?.to_path_buf()));
+                let python_venv_directory = terminal.python_venv_directory.clone();
+                project.create_terminal_with_venv(
+                    TerminalKind::Shell(working_directory),
+                    python_venv_directory,
+                    window,
+                    cx,
+                )
+            })
+            .ok()?
+            .log_err()?;
+
+        Some(cx.new_view(|cx| {
+            TerminalView::new(
+                terminal,
+                self.workspace.clone(),
+                workspace_id,
+                self.project.clone(),
+                cx,
+            )
+        }))
     }
 
     fn is_dirty(&self, cx: &gpui::AppContext) -> bool {
@@ -1218,7 +1237,15 @@ impl SerializableItem for TerminalView {
                 })?
                 .await?;
             cx.update(|cx| {
-                cx.new_view(|cx| TerminalView::new(terminal, workspace, Some(workspace_id), cx))
+                cx.new_view(|cx| {
+                    TerminalView::new(
+                        terminal,
+                        workspace,
+                        Some(workspace_id),
+                        project.downgrade(),
+                        cx,
+                    )
+                })
             })
         })
     }

docs/src/tasks.md 🔗

@@ -173,7 +173,7 @@ This could be useful for launching a terminal application that you want to use i
   "bindings": {
     "alt-g": [
       "task::Spawn",
-      { "task_name": "start lazygit", "target": "center" }
+      { "task_name": "start lazygit", "reveal_target": "center" }
     ]
   }
 }