diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index 133f5d3e54..ead0bd64f4 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -1,9 +1,8 @@ -use crate::Project; - use anyhow::Result; use collections::HashMap; -use gpui::{App, AppContext as _, Context, Entity, WeakEntity}; -use itertools::Itertools; +use gpui::{App, AppContext as _, Context, Entity, Task, WeakEntity}; +use itertools::Itertools as _; +use language::LanguageName; use remote::{SshInfo, ssh_session::SshArgs}; use settings::{Settings, SettingsLocation}; use smol::channel::bounded; @@ -16,7 +15,12 @@ use task::{Shell, ShellBuilder, SpawnInTerminal}; use terminal::{ TaskState, TaskStatus, Terminal, TerminalBuilder, terminal_settings::TerminalSettings, }; -use util::paths::{PathStyle, RemotePathBuf}; +use util::{ + maybe, + paths::{PathStyle, RemotePathBuf}, +}; + +use crate::{Project, ProjectPath}; pub struct Terminals { pub(crate) local_handles: Vec>, @@ -239,7 +243,8 @@ impl Project { &mut self, cwd: Option, cx: &mut Context, - ) -> Result> { + project_path_context: Option, + ) -> Task>> { let path = cwd.map(|p| Arc::from(&*p)); let this = &mut *self; let ssh_details = this.ssh_details(cx); @@ -305,23 +310,66 @@ impl Project { None => (None, settings.shell), } }; - 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, - is_ssh_terminal, - cx.entity_id().as_u64(), - None, - cx, - ) - .map(|builder| { + let toolchain = + project_path_context.map(|p| self.active_toolchain(p, LanguageName::new("Python"), cx)); + cx.spawn(async move |project, cx| { + let toolchain = maybe!(async { + let toolchain = toolchain?.await?; + + Some(()) + }) + .await; + project.update(cx, move |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, + is_ssh_terminal, + cx.entity_id().as_u64(), + None, + cx, + ) + .map(|builder| { + let terminal_handle = cx.new(|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_handle + }) + })? + }) + } + + pub fn clone_terminal( + &mut self, + terminal: &Entity, + cx: &mut Context<'_, Project>, + cwd: impl FnOnce() -> Option, + ) -> Result> { + terminal.read(cx).clone_builder(cx, cwd).map(|builder| { let terminal_handle = cx.new(|cx| builder.subscribe(cx)); - this.terminals + self.terminals .local_handles .push(terminal_handle.downgrade()); diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index ab6b6eaa73..292b9f8728 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -427,13 +427,10 @@ impl TerminalBuilder { .clone() .or_else(|| Some(home_dir().to_path_buf())), drain_on_exit: true, - env: env.into_iter().collect(), + env: env.clone().into_iter().collect(), } }; - // Setup Alacritty's env, which modifies the current process's environment - alacritty_terminal::tty::setup_env(); - let default_cursor_style = AlacCursorStyle::from(cursor_shape); let scrolling_history = if task.is_some() { // Tasks like `cargo build --all` may produce a lot of output, ergo allow maximum scrolling. @@ -520,6 +517,14 @@ impl TerminalBuilder { last_hyperlink_search_position: None, #[cfg(windows)] shell_program, + template: CopyTemplate { + shell, + env, + cursor_shape, + alternate_scroll, + max_scroll_history_lines, + window_id, + }, }; Ok(TerminalBuilder { @@ -704,6 +709,16 @@ pub struct Terminal { last_hyperlink_search_position: Option>, #[cfg(windows)] shell_program: Option, + template: CopyTemplate, +} + +struct CopyTemplate { + shell: Shell, + env: HashMap, + cursor_shape: CursorShape, + alternate_scroll: AlternateScroll, + max_scroll_history_lines: Option, + window_id: u64, } pub struct TaskState { @@ -1949,6 +1964,27 @@ impl Terminal { pub fn vi_mode_enabled(&self) -> bool { self.vi_mode_enabled } + + pub fn clone_builder( + &self, + cx: &App, + cwd: impl FnOnce() -> Option, + ) -> Result { + let working_directory = self.working_directory().or_else(cwd); + TerminalBuilder::new( + working_directory, + None, + self.template.shell.clone(), + self.template.env.clone(), + self.template.cursor_shape, + self.template.alternate_scroll, + self.template.max_scroll_history_lines, + self.is_ssh_terminal, + self.template.window_id, + None, + cx, + ) + } } // Helper function to convert a grid row to a string diff --git a/crates/terminal_view/src/persistence.rs b/crates/terminal_view/src/persistence.rs index ab3aafd955..328468dfb8 100644 --- a/crates/terminal_view/src/persistence.rs +++ b/crates/terminal_view/src/persistence.rs @@ -5,7 +5,7 @@ use futures::{StreamExt as _, stream::FuturesUnordered}; use gpui::{AppContext as _, AsyncWindowContext, Axis, Entity, Task, WeakEntity}; use project::Project; use serde::{Deserialize, Serialize}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use ui::{App, Context, Pixels, Window}; use util::ResultExt as _; @@ -243,10 +243,7 @@ async fn deserialize_pane_group( .ok() .flatten(); let terminal = project.update(cx, |project, cx| { - project.create_terminal_shell( - working_directory.as_deref().map(Path::to_path_buf), - cx, - ) + project.create_terminal_shell(working_directory, cx, None) }); Some(Some(terminal)) } else { @@ -256,7 +253,7 @@ async fn deserialize_pane_group( .ok() .flatten()?; if let Some(terminal) = terminal { - let terminal = terminal.ok()?; + let terminal = terminal.await.ok()?; pane.update_in(cx, |pane, window, cx| { let terminal_view = Box::new(cx.new(|cx| { TerminalView::new( diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index ba70440612..e50582222f 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -376,14 +376,19 @@ impl TerminalPanel { } self.serialize(cx); } - pane::Event::Split(direction) => { - let Some(new_pane) = self.new_pane_with_cloned_active_terminal(window, cx) else { - return; - }; + &pane::Event::Split(direction) => { + let fut = self.new_pane_with_cloned_active_terminal(window, cx); let pane = pane.clone(); - let direction = *direction; - self.center.split(&pane, &new_pane, direction).log_err(); - window.focus(&new_pane.focus_handle(cx)); + cx.spawn_in(window, async move |panel, cx| { + let Some(new_pane) = fut.await else { + return; + }; + _ = panel.update_in(cx, |panel, window, cx| { + panel.center.split(&pane, &new_pane, direction).log_err(); + window.focus(&new_pane.focus_handle(cx)); + }); + }) + .detach(); } pane::Event::Focus => { self.active_pane = pane.clone(); @@ -400,14 +405,16 @@ impl TerminalPanel { &mut self, window: &mut Window, cx: &mut Context, - ) -> Option> { - let workspace = self.workspace.upgrade()?; + ) -> Task>> { + let Some(workspace) = self.workspace.upgrade() else { + return Task::ready(None); + }; let workspace = workspace.read(cx); let database_id = workspace.database_id(); let weak_workspace = self.workspace.clone(); let project = workspace.project().clone(); - let working_directory = self - .active_pane + let active_pane = &self.active_pane; + let working_directory = active_pane .read(cx) .active_item() .and_then(|item| item.downcast::()) @@ -418,35 +425,38 @@ impl TerminalPanel { .or_else(|| default_working_directory(workspace, cx)) }) .unwrap_or(None); - let terminal = project - .update(cx, |project, cx| { - project.create_terminal_shell(working_directory, cx) - }) - .ok()?; + let is_zoomed = active_pane.read(cx).is_zoomed(); + cx.spawn_in(window, async move |panel, cx| { + let terminal = project + .update(cx, |project, cx| { + project.create_terminal_shell(working_directory, cx, None) + }) + .ok()? + .await + .ok()?; - let terminal_view = Box::new(cx.new(|cx| { - TerminalView::new( - terminal.clone(), - weak_workspace.clone(), - database_id, - project.downgrade(), - window, - cx, - ) - })); - let pane = new_terminal_pane( - weak_workspace, - project, - self.active_pane.read(cx).is_zoomed(), - window, - cx, - ); - self.apply_tab_bar_buttons(&pane, cx); - pane.update(cx, |pane, cx| { - pane.add_item(terminal_view, true, true, None, window, cx); - }); - - Some(pane) + panel + .update_in(cx, move |terminal_panel, window, cx| { + let terminal_view = Box::new(cx.new(|cx| { + TerminalView::new( + terminal.clone(), + weak_workspace.clone(), + database_id, + project.downgrade(), + window, + cx, + ) + })); + let pane = new_terminal_pane(weak_workspace, project, is_zoomed, window, cx); + terminal_panel.apply_tab_bar_buttons(&pane, cx); + pane.update(cx, |pane, cx| { + pane.add_item(terminal_view, true, true, None, window, cx); + }); + Some(pane) + }) + .ok() + .flatten() + }) } pub fn open_terminal( @@ -561,7 +571,7 @@ impl TerminalPanel { .workspace .update(cx, |workspace, cx| { Self::add_center_terminal(workspace, window, cx, |project, cx| { - project.create_terminal_task(spawn_task, cx) + Task::ready(project.create_terminal_task(spawn_task, cx)) }) }) .unwrap_or_else(|e| Task::ready(Err(e))), @@ -651,7 +661,10 @@ impl TerminalPanel { workspace: &mut Workspace, window: &mut Window, cx: &mut Context, - create_terminal: impl FnOnce(&mut Project, &mut Context) -> Result> + create_terminal: impl FnOnce( + &mut Project, + &mut Context, + ) -> Task>> + 'static, ) -> Task>> { if !is_enabled_in_workspace(workspace, cx) { @@ -661,7 +674,7 @@ impl TerminalPanel { } let project = workspace.project().downgrade(); cx.spawn_in(window, async move |workspace, cx| { - let terminal = project.update(cx, create_terminal)??; + let terminal = project.update(cx, create_terminal)?.await?; workspace.update_in(cx, |workspace, window, cx| { let terminal_view = cx.new(|cx| { @@ -755,8 +768,11 @@ impl TerminalPanel { terminal_panel.active_pane.clone() })?; let project = workspace.read_with(cx, |workspace, _| workspace.project().clone())?; - let terminal = - project.update(cx, |project, cx| project.create_terminal_shell(cwd, cx))??; + let terminal = project + .update(cx, |project, cx| { + project.create_terminal_shell(cwd, cx, None) + })? + .await?; let result = workspace.update_in(cx, |workspace, window, cx| { let terminal_view = Box::new(cx.new(|cx| { TerminalView::new( @@ -1291,18 +1307,29 @@ impl Render for TerminalPanel { let panes = terminal_panel.center.panes(); if let Some(&pane) = panes.get(action.0) { window.focus(&pane.read(cx).focus_handle(cx)); - } else if let Some(new_pane) = - terminal_panel.new_pane_with_cloned_active_terminal(window, cx) - { - terminal_panel - .center - .split( - &terminal_panel.active_pane, - &new_pane, - SplitDirection::Right, - ) - .log_err(); - window.focus(&new_pane.focus_handle(cx)); + } else { + let future = + terminal_panel.new_pane_with_cloned_active_terminal(window, cx); + cx.spawn_in(window, async move |terminal_panel, cx| { + if let Some(new_pane) = future.await { + _ = terminal_panel.update_in( + cx, + |terminal_panel, window, cx| { + terminal_panel + .center + .split( + &terminal_panel.active_pane, + &new_pane, + SplitDirection::Right, + ) + .log_err(); + let new_pane = new_pane.read(cx); + window.focus(&new_pane.focus_handle(cx)); + }, + ); + } + }) + .detach(); } }), ) diff --git a/crates/terminal_view/src/terminal_path_like_target.rs b/crates/terminal_view/src/terminal_path_like_target.rs index caaf3261d5..f65cb482f7 100644 --- a/crates/terminal_view/src/terminal_path_like_target.rs +++ b/crates/terminal_view/src/terminal_path_like_target.rs @@ -408,6 +408,7 @@ mod tests { .update(cx, |project: &mut Project, cx| { project.create_terminal_shell(None, cx, None) }) + .await .expect("Failed to create a terminal"); let workspace_a = workspace.clone(); diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index c9adb8c18a..36938142d0 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -205,7 +205,7 @@ impl TerminalView { ) { let working_directory = default_working_directory(workspace, cx); TerminalPanel::add_center_terminal(workspace, window, cx, |project, cx| { - project.create_terminal_shell(working_directory, cx) + project.create_terminal_shell(working_directory, cx, None) }) .detach_and_log_err(cx); } @@ -1330,11 +1330,10 @@ impl Item for TerminalView { 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())); - project.create_terminal_shell(working_directory, cx) + let cwd = project + .active_project_directory(cx) + .map(|it| it.to_path_buf()); + project.clone_terminal(self.terminal(), cx, || cwd) }) .ok()? .log_err()?; @@ -1489,8 +1488,11 @@ impl SerializableItem for TerminalView { .ok() .flatten(); - let terminal = - project.update(cx, |project, cx| project.create_terminal_shell(cwd, cx))??; + let terminal = project + .update(cx, |project, cx| { + project.create_terminal_shell(cwd, cx, None) + })? + .await?; cx.update(|window, cx| { cx.new(|cx| { TerminalView::new(