Move task centering code closer to user input (#22082)

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

* Reuse center terminals for tasks, when requested
* Extend task templates with `RevealTarget`, moving it from
`TaskSpawnTarget` into the core library
* Use `reveal_target` instead of `target` to avoid misinterpretations in
the task template context
* Do not expose `SpawnInTerminal` to user interface, avoid it
implementing `Serialize` and `Deserialize`
* Remove `NewCenterTask` action, extending `task::Spawn` interface
instead
* Do not require any extra unrelated parameters during task resolution,
instead, use task overrides on the resolved tasks on the modal side
* Add keybindings for opening the task modal in the
`RevealTarget::Center` mode

Release Notes:

- N/A
This commit is contained in:
Kirill Bulatov 2024-12-16 16:15:58 +02:00 committed by GitHub
parent ea012075fc
commit bc113e4b51
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 356 additions and 285 deletions

View file

@ -12,7 +12,7 @@ use collections::HashMap;
use db::kvp::KEY_VALUE_STORE;
use futures::future::join_all;
use gpui::{
actions, Action, AnchorCorner, AnyView, AppContext, AsyncWindowContext, EventEmitter,
actions, Action, AnchorCorner, AnyView, AppContext, AsyncWindowContext, Entity, EventEmitter,
ExternalPaths, FocusHandle, FocusableView, IntoElement, Model, ParentElement, Pixels, Render,
Styled, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
};
@ -20,7 +20,7 @@ use itertools::Itertools;
use project::{terminals::TerminalKind, Fs, Project, ProjectEntryId};
use search::{buffer_search::DivRegistrar, BufferSearchBar};
use settings::Settings;
use task::{RevealStrategy, Shell, SpawnInTerminal, TaskId};
use task::{RevealStrategy, RevealTarget, Shell, SpawnInTerminal, TaskId};
use terminal::{
terminal_settings::{TerminalDockPosition, TerminalSettings},
Terminal,
@ -40,7 +40,7 @@ use workspace::{
SplitUp, SwapPaneInDirection, ToggleZoom, Workspace,
};
use anyhow::Result;
use anyhow::{anyhow, Context, Result};
use zed_actions::InlineAssist;
const TERMINAL_PANEL_KEY: &str = "TerminalPanel";
@ -53,11 +53,7 @@ pub fn init(cx: &mut AppContext) {
workspace.register_action(TerminalPanel::new_terminal);
workspace.register_action(TerminalPanel::open_terminal);
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
if workspace
.panel::<TerminalPanel>(cx)
.as_ref()
.is_some_and(|panel| panel.read(cx).enabled)
{
if is_enabled_in_workspace(workspace, cx) {
workspace.toggle_panel_focus::<TerminalPanel>(cx);
}
});
@ -76,7 +72,6 @@ pub struct TerminalPanel {
pending_serialization: Task<Option<()>>,
pending_terminals_to_add: usize,
deferred_tasks: HashMap<TaskId, Task<()>>,
enabled: bool,
assistant_enabled: bool,
assistant_tab_bar_button: Option<AnyView>,
}
@ -86,7 +81,6 @@ impl TerminalPanel {
let project = workspace.project();
let pane = new_terminal_pane(workspace.weak_handle(), project.clone(), false, cx);
let center = PaneGroup::new(pane.clone());
let enabled = project.read(cx).supports_terminal(cx);
cx.focus_view(&pane);
let terminal_panel = Self {
center,
@ -98,7 +92,6 @@ impl TerminalPanel {
height: None,
pending_terminals_to_add: 0,
deferred_tasks: HashMap::default(),
enabled,
assistant_enabled: false,
assistant_tab_bar_button: None,
};
@ -492,8 +485,8 @@ impl TerminalPanel {
!use_new_terminal,
"Should have handled 'allow_concurrent_runs && use_new_terminal' case above"
);
this.update(&mut cx, |this, cx| {
this.replace_terminal(
this.update(&mut cx, |terminal_panel, cx| {
terminal_panel.replace_terminal(
spawn_task,
task_pane,
existing_item_index,
@ -620,7 +613,17 @@ impl TerminalPanel {
cx: &mut ViewContext<Self>,
) -> Task<Result<Model<Terminal>>> {
let reveal = spawn_task.reveal;
self.add_terminal(TerminalKind::Task(spawn_task), reveal, cx)
let reveal_target = spawn_task.reveal_target;
let kind = TerminalKind::Task(spawn_task);
match reveal_target {
RevealTarget::Center => self
.workspace
.update(cx, |workspace, cx| {
Self::add_center_terminal(workspace, kind, cx)
})
.unwrap_or_else(|e| Task::ready(Err(e))),
RevealTarget::Dock => self.add_terminal(kind, reveal, cx),
}
}
/// Create a new Terminal in the current working directory or the user's home directory
@ -647,24 +650,40 @@ impl TerminalPanel {
label: &str,
cx: &mut AppContext,
) -> Vec<(usize, View<Pane>, View<TerminalView>)> {
let Some(workspace) = self.workspace.upgrade() else {
return Vec::new();
};
let pane_terminal_views = |pane: View<Pane>| {
pane.read(cx)
.items()
.enumerate()
.filter_map(|(index, item)| Some((index, item.act_as::<TerminalView>(cx)?)))
.filter_map(|(index, terminal_view)| {
let task_state = terminal_view.read(cx).terminal().read(cx).task()?;
if &task_state.full_label == label {
Some((index, terminal_view))
} else {
None
}
})
.map(move |(index, terminal_view)| (index, pane.clone(), terminal_view))
};
self.center
.panes()
.into_iter()
.flat_map(|pane| {
pane.read(cx)
.items()
.enumerate()
.filter_map(|(index, item)| Some((index, item.act_as::<TerminalView>(cx)?)))
.filter_map(|(index, terminal_view)| {
let task_state = terminal_view.read(cx).terminal().read(cx).task()?;
if &task_state.full_label == label {
Some((index, terminal_view))
} else {
None
}
})
.map(|(index, terminal_view)| (index, pane.clone(), terminal_view))
})
.cloned()
.flat_map(pane_terminal_views)
.chain(
workspace
.read(cx)
.panes()
.into_iter()
.cloned()
.flat_map(pane_terminal_views),
)
.sorted_by_key(|(_, _, terminal_view)| terminal_view.entity_id())
.collect()
}
@ -680,14 +699,48 @@ impl TerminalPanel {
})
}
pub fn add_center_terminal(
workspace: &mut Workspace,
kind: TerminalKind,
cx: &mut ViewContext<Workspace>,
) -> Task<Result<Model<Terminal>>> {
if !is_enabled_in_workspace(workspace, cx) {
return Task::ready(Err(anyhow!(
"terminal not yet supported for remote projects"
)));
}
let window = cx.window_handle();
let project = workspace.project().downgrade();
cx.spawn(move |workspace, mut cx| async move {
let terminal = project
.update(&mut cx, |project, cx| {
project.create_terminal(kind, window, cx)
})?
.await?;
workspace.update(&mut cx, |workspace, cx| {
let view = cx.new_view(|cx| {
TerminalView::new(
terminal.clone(),
workspace.weak_handle(),
workspace.database_id(),
cx,
)
});
workspace.add_item_to_active_pane(Box::new(view), None, true, cx);
})?;
Ok(terminal)
})
}
fn add_terminal(
&mut self,
kind: TerminalKind,
reveal_strategy: RevealStrategy,
cx: &mut ViewContext<Self>,
) -> Task<Result<Model<Terminal>>> {
if !self.enabled {
return Task::ready(Err(anyhow::anyhow!(
if !self.is_enabled(cx) {
return Task::ready(Err(anyhow!(
"terminal not yet supported for remote projects"
)));
}
@ -786,10 +839,11 @@ impl TerminalPanel {
cx: &mut ViewContext<'_, Self>,
) -> Task<Option<()>> {
let reveal = spawn_task.reveal;
let reveal_target = spawn_task.reveal_target;
let window = cx.window_handle();
let task_workspace = self.workspace.clone();
cx.spawn(move |this, mut cx| async move {
let project = this
cx.spawn(move |terminal_panel, mut cx| async move {
let project = terminal_panel
.update(&mut cx, |this, cx| {
this.workspace
.update(cx, |workspace, _| workspace.project().clone())
@ -811,32 +865,68 @@ impl TerminalPanel {
.ok()?;
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 {
RevealStrategy::Always => match reveal_target {
RevealTarget::Center => {
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()?;
.update(&mut cx, |workspace, cx| {
workspace
.active_item(cx)
.context("retrieving active terminal item in the workspace")
.log_err()?
.focus_handle(cx)
.focus(cx);
Some(())
})
.ok()??;
}
RevealTarget::Dock => {
terminal_panel
.update(&mut cx, |terminal_panel, cx| {
terminal_panel.activate_terminal_view(
&task_pane,
terminal_item_index,
true,
cx,
)
})
.ok()?;
cx.spawn(|mut cx| async move {
cx.spawn(|mut cx| async move {
task_workspace
.update(&mut cx, |workspace, cx| workspace.focus_panel::<Self>(cx))
.ok()
})
.detach();
}
},
RevealStrategy::NoFocus => match reveal_target {
RevealTarget::Center => {
task_workspace
.update(&mut cx, |workspace, cx| workspace.open_panel::<Self>(cx))
.ok()
})
.detach();
}
.update(&mut cx, |workspace, cx| {
workspace.active_pane().focus_handle(cx).focus(cx);
})
.ok()?;
}
RevealTarget::Dock => {
terminal_panel
.update(&mut cx, |terminal_panel, cx| {
terminal_panel.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 => {}
}
@ -851,6 +941,16 @@ impl TerminalPanel {
pub fn assistant_enabled(&self) -> bool {
self.assistant_enabled
}
fn is_enabled(&self, cx: &WindowContext) -> bool {
self.workspace.upgrade().map_or(false, |workspace| {
is_enabled_in_workspace(workspace.read(cx), cx)
})
}
}
fn is_enabled_in_workspace(workspace: &Workspace, cx: &WindowContext) -> bool {
workspace.project().read(cx).supports_terminal(cx)
}
pub fn new_terminal_pane(
@ -1235,7 +1335,7 @@ impl Panel for TerminalPanel {
return;
};
this.add_terminal(kind, RevealStrategy::Never, cx)
this.add_terminal(kind, RevealStrategy::Always, cx)
.detach_and_log_err(cx)
})
}
@ -1259,7 +1359,9 @@ impl Panel for TerminalPanel {
}
fn icon(&self, cx: &WindowContext) -> Option<IconName> {
if (self.enabled || !self.has_no_terminals(cx)) && TerminalSettings::get_global(cx).button {
if (self.is_enabled(cx) || !self.has_no_terminals(cx))
&& TerminalSettings::get_global(cx).button
{
Some(IconName::Terminal)
} else {
None