Enable toolchain venv in new terminals (#21388)

Fixes part of issue #7808 

> This venv should be the one we automatically activate when opening new
terminals, if the detect_venv setting is on.

Release Notes:

- Selected Python toolchains (virtual environments) are now automatically activated in new terminals.

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
This commit is contained in:
Sebastian Nickels 2024-12-03 16:24:30 +01:00 committed by GitHub
parent a76cd778c4
commit 1270ef3ea5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 441 additions and 307 deletions

View file

@ -5,7 +5,7 @@ use futures::{stream::FuturesUnordered, StreamExt as _};
use gpui::{AsyncWindowContext, Axis, Model, Task, View, WeakView};
use project::{terminals::TerminalKind, Project};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use ui::{Pixels, ViewContext, VisualContext as _, WindowContext};
use util::ResultExt as _;
@ -219,33 +219,39 @@ async fn deserialize_pane_group(
})
.log_err()?;
let active_item = serialized_pane.active_item;
pane.update(cx, |pane, cx| {
populate_pane_items(pane, new_items, active_item, cx);
// Avoid blank panes in splits
if pane.items_len() == 0 {
let working_directory = workspace
.update(cx, |workspace, cx| default_working_directory(workspace, cx))
.ok()
.flatten();
let kind = TerminalKind::Shell(working_directory);
let window = cx.window_handle();
let terminal = project
.update(cx, |project, cx| project.create_terminal(kind, window, cx))
.log_err()?;
let terminal = pane
.update(cx, |pane, cx| {
populate_pane_items(pane, new_items, active_item, cx);
// Avoid blank panes in splits
if pane.items_len() == 0 {
let working_directory = workspace
.update(cx, |workspace, cx| default_working_directory(workspace, cx))
.ok()
.flatten();
let kind = TerminalKind::Shell(
working_directory.as_deref().map(Path::to_path_buf),
);
let window = cx.window_handle();
let terminal = project
.update(cx, |project, cx| project.create_terminal(kind, window, cx));
Some(Some(terminal))
} else {
Some(None)
}
})
.ok()
.flatten()?;
if let Some(terminal) = terminal {
let terminal = terminal.await.ok()?;
pane.update(cx, |pane, cx| {
let terminal_view = Box::new(cx.new_view(|cx| {
TerminalView::new(
terminal.clone(),
workspace.clone(),
Some(workspace_id),
cx,
)
TerminalView::new(terminal, workspace.clone(), Some(workspace_id), cx)
}));
pane.add_item(terminal_view, true, false, None, cx);
}
Some(())
})
.ok()
.flatten()?;
})
.ok()?;
}
Some((Member::Pane(pane.clone()), active.then_some(pane)))
}
}

View file

@ -318,10 +318,19 @@ impl TerminalPanel {
}
}
pane::Event::Split(direction) => {
let Some(new_pane) = self.new_pane_with_cloned_active_terminal(cx) else {
return;
};
self.center.split(&pane, &new_pane, *direction).log_err();
let new_pane = self.new_pane_with_cloned_active_terminal(cx);
let pane = pane.clone();
let direction = *direction;
cx.spawn(move |this, mut cx| async move {
let Some(new_pane) = new_pane.await else {
return;
};
this.update(&mut cx, |this, _| {
this.center.split(&pane, &new_pane, direction).log_err();
})
.ok();
})
.detach();
}
pane::Event::Focus => {
self.active_pane = pane.clone();
@ -334,8 +343,12 @@ impl TerminalPanel {
fn new_pane_with_cloned_active_terminal(
&mut self,
cx: &mut ViewContext<Self>,
) -> Option<View<Pane>> {
let workspace = self.workspace.clone().upgrade()?;
) -> Task<Option<View<Pane>>> {
let Some(workspace) = self.workspace.clone().upgrade() else {
return Task::ready(None);
};
let database_id = workspace.read(cx).database_id();
let weak_workspace = self.workspace.clone();
let project = workspace.read(cx).project().clone();
let working_directory = self
.active_pane
@ -352,21 +365,37 @@ impl TerminalPanel {
.or_else(|| default_working_directory(workspace.read(cx), cx));
let kind = TerminalKind::Shell(working_directory);
let window = cx.window_handle();
let terminal = project
.update(cx, |project, cx| project.create_terminal(kind, window, cx))
.log_err()?;
let database_id = workspace.read(cx).database_id();
let terminal_view = Box::new(cx.new_view(|cx| {
TerminalView::new(terminal.clone(), self.workspace.clone(), database_id, cx)
}));
let pane = new_terminal_pane(self.workspace.clone(), project, cx);
self.apply_tab_bar_buttons(&pane, cx);
pane.update(cx, |pane, cx| {
pane.add_item(terminal_view, true, true, None, cx);
});
cx.focus_view(&pane);
cx.spawn(move |this, mut cx| async move {
let terminal = project
.update(&mut cx, |project, cx| {
project.create_terminal(kind, window, cx)
})
.log_err()?
.await
.log_err()?;
Some(pane)
let terminal_view = Box::new(
cx.new_view(|cx| {
TerminalView::new(terminal.clone(), weak_workspace.clone(), database_id, cx)
})
.ok()?,
);
let pane = this
.update(&mut cx, |this, cx| {
let pane = new_terminal_pane(weak_workspace, project, cx);
this.apply_tab_bar_buttons(&pane, cx);
pane
})
.ok()?;
pane.update(&mut cx, |pane, cx| {
pane.add_item(terminal_view, true, true, None, cx);
})
.ok()?;
cx.focus_view(&pane).ok()?;
Some(pane)
})
}
pub fn open_terminal(
@ -489,43 +518,58 @@ impl TerminalPanel {
.last()
.expect("covered no terminals case above")
.clone();
if allow_concurrent_runs {
debug_assert!(
!use_new_terminal,
"Should have handled 'allow_concurrent_runs && use_new_terminal' case above"
);
self.replace_terminal(
spawn_task,
task_pane,
existing_item_index,
existing_terminal,
cx,
);
} else {
self.deferred_tasks.insert(
spawn_in_terminal.id.clone(),
cx.spawn(|terminal_panel, mut cx| async move {
wait_for_terminals_tasks(terminals_for_task, &mut cx).await;
terminal_panel
.update(&mut cx, |terminal_panel, cx| {
if use_new_terminal {
terminal_panel
.spawn_in_new_terminal(spawn_task, cx)
.detach_and_log_err(cx);
} else {
terminal_panel.replace_terminal(
spawn_task,
task_pane,
existing_item_index,
existing_terminal,
cx,
);
}
})
.ok();
}),
);
}
let id = spawn_in_terminal.id.clone();
cx.spawn(move |this, mut cx| async move {
if allow_concurrent_runs {
debug_assert!(
!use_new_terminal,
"Should have handled 'allow_concurrent_runs && use_new_terminal' case above"
);
this.update(&mut cx, |this, cx| {
this.replace_terminal(
spawn_task,
task_pane,
existing_item_index,
existing_terminal,
cx,
)
})?
.await;
} else {
this.update(&mut cx, |this, cx| {
this.deferred_tasks.insert(
id,
cx.spawn(|terminal_panel, mut cx| async move {
wait_for_terminals_tasks(terminals_for_task, &mut cx).await;
let Ok(Some(new_terminal_task)) =
terminal_panel.update(&mut cx, |terminal_panel, cx| {
if use_new_terminal {
terminal_panel
.spawn_in_new_terminal(spawn_task, cx)
.detach_and_log_err(cx);
None
} else {
Some(terminal_panel.replace_terminal(
spawn_task,
task_pane,
existing_item_index,
existing_terminal,
cx,
))
}
})
else {
return;
};
new_terminal_task.await;
}),
);
})
.ok();
}
anyhow::Result::<_, anyhow::Error>::Ok(())
})
.detach()
}
pub fn spawn_in_new_terminal(
@ -611,11 +655,14 @@ impl TerminalPanel {
cx.spawn(|terminal_panel, mut cx| async move {
let pane = terminal_panel.update(&mut cx, |this, _| this.active_pane.clone())?;
let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
let window = cx.window_handle();
let terminal = project
.update(&mut cx, |project, cx| {
project.create_terminal(kind, window, cx)
})?
.await?;
let result = workspace.update(&mut cx, |workspace, cx| {
let window = cx.window_handle();
let terminal = workspace
.project()
.update(cx, |project, cx| project.create_terminal(kind, window, cx))?;
let terminal_view = Box::new(cx.new_view(|cx| {
TerminalView::new(
terminal.clone(),
@ -695,48 +742,64 @@ impl TerminalPanel {
terminal_item_index: usize,
terminal_to_replace: View<TerminalView>,
cx: &mut ViewContext<'_, Self>,
) -> Option<()> {
let project = self
.workspace
.update(cx, |workspace, _| workspace.project().clone())
.ok()?;
) -> Task<Option<()>> {
let reveal = spawn_task.reveal;
let window = cx.window_handle();
let new_terminal = project.update(cx, |project, cx| {
project
.create_terminal(TerminalKind::Task(spawn_task), window, cx)
.log_err()
})?;
terminal_to_replace.update(cx, |terminal_to_replace, cx| {
terminal_to_replace.set_terminal(new_terminal, cx);
});
match reveal {
RevealStrategy::Always => {
self.activate_terminal_view(&task_pane, terminal_item_index, true, cx);
let task_workspace = self.workspace.clone();
cx.spawn(|_, mut cx| async move {
task_workspace
.update(&mut cx, |workspace, cx| workspace.focus_panel::<Self>(cx))
let task_workspace = self.workspace.clone();
cx.spawn(move |this, mut cx| async move {
let project = this
.update(&mut cx, |this, cx| {
this.workspace
.update(cx, |workspace, _| workspace.project().clone())
.ok()
})
.detach();
}
RevealStrategy::NoFocus => {
self.activate_terminal_view(&task_pane, terminal_item_index, false, cx);
let task_workspace = self.workspace.clone();
cx.spawn(|_, mut cx| async move {
task_workspace
.update(&mut cx, |workspace, cx| workspace.open_panel::<Self>(cx))
.ok()
.ok()
.flatten()?;
let new_terminal = project
.update(&mut cx, |project, cx| {
project.create_terminal(TerminalKind::Task(spawn_task), window, cx)
})
.detach();
}
RevealStrategy::Never => {}
}
.ok()?
.await
.log_err()?;
terminal_to_replace
.update(&mut cx, |terminal_to_replace, cx| {
terminal_to_replace.set_terminal(new_terminal, cx);
})
.ok()?;
Some(())
match reveal {
RevealStrategy::Always => {
this.update(&mut cx, |this, cx| {
this.activate_terminal_view(&task_pane, terminal_item_index, true, cx)
})
.ok()?;
cx.spawn(|mut cx| async move {
task_workspace
.update(&mut cx, |workspace, cx| workspace.focus_panel::<Self>(cx))
.ok()
})
.detach();
}
RevealStrategy::NoFocus => {
this.update(&mut cx, |this, cx| {
this.activate_terminal_view(&task_pane, terminal_item_index, false, cx)
})
.ok()?;
cx.spawn(|mut cx| async move {
task_workspace
.update(&mut cx, |workspace, cx| workspace.open_panel::<Self>(cx))
.ok()
})
.detach();
}
RevealStrategy::Never => {}
}
Some(())
})
}
fn has_no_terminals(&self, cx: &WindowContext) -> bool {
@ -998,18 +1061,18 @@ impl Render for TerminalPanel {
if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
cx.focus_view(&pane);
} else {
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();
}
let new_pane = terminal_panel.new_pane_with_cloned_active_terminal(cx);
cx.spawn(|this, mut cx| async move {
if let Some(new_pane) = new_pane.await {
this.update(&mut cx, |this, _| {
this.center
.split(&this.active_pane, &new_pane, SplitDirection::Right)
.log_err();
})
.ok();
}
})
.detach();
}
}))
.on_action(cx.listener(

View file

@ -136,24 +136,36 @@ impl TerminalView {
let working_directory = default_working_directory(workspace, cx);
let window = cx.window_handle();
let terminal = workspace
.project()
.update(cx, |project, cx| {
project.create_terminal(TerminalKind::Shell(working_directory), window, cx)
})
.notify_err(workspace, cx);
let project = workspace.project().downgrade();
cx.spawn(move |workspace, mut cx| async move {
let terminal = project
.update(&mut cx, |project, cx| {
project.create_terminal(TerminalKind::Shell(working_directory), window, cx)
})
.ok()?
.await;
let terminal = workspace
.update(&mut cx, |workspace, cx| terminal.notify_err(workspace, cx))
.ok()
.flatten()?;
if let Some(terminal) = terminal {
let view = cx.new_view(|cx| {
TerminalView::new(
terminal,
workspace.weak_handle(),
workspace.database_id(),
cx,
)
});
workspace.add_item_to_active_pane(Box::new(view), None, true, cx);
}
workspace
.update(&mut cx, |workspace, cx| {
let view = cx.new_view(|cx| {
TerminalView::new(
terminal,
workspace.weak_handle(),
workspace.database_id(),
cx,
)
});
workspace.add_item_to_active_pane(Box::new(view), None, true, cx);
})
.ok();
Some(())
})
.detach()
}
pub fn new(
@ -1231,9 +1243,11 @@ impl SerializableItem for TerminalView {
.ok()
.flatten();
let terminal = project.update(&mut cx, |project, cx| {
project.create_terminal(TerminalKind::Shell(cwd), window, cx)
})??;
let terminal = project
.update(&mut cx, |project, cx| {
project.create_terminal(TerminalKind::Shell(cwd), window, cx)
})?
.await?;
cx.update(|cx| {
cx.new_view(|cx| TerminalView::new(terminal, workspace, Some(workspace_id), cx))
})
@ -1362,11 +1376,14 @@ impl SearchableItem for TerminalView {
///Gets the working directory for the given workspace, respecting the user's settings.
/// None implies "~" on whichever machine we end up on.
pub fn default_working_directory(workspace: &Workspace, cx: &AppContext) -> Option<PathBuf> {
pub(crate) fn default_working_directory(workspace: &Workspace, cx: &AppContext) -> Option<PathBuf> {
match &TerminalSettings::get_global(cx).working_directory {
WorkingDirectory::CurrentProjectDirectory => {
workspace.project().read(cx).active_project_directory(cx)
}
WorkingDirectory::CurrentProjectDirectory => workspace
.project()
.read(cx)
.active_project_directory(cx)
.as_deref()
.map(Path::to_path_buf),
WorkingDirectory::FirstProjectDirectory => first_project_directory(workspace, cx),
WorkingDirectory::AlwaysHome => None,
WorkingDirectory::Always { directory } => {