Add the ability for tasks to target the center pane (#22004)
Closes #20060 Closes #20720 Closes #19873 Closes #9445 Release Notes: - Fixed a bug where tasks would be spawned with their working directory set to a file in some cases - Added the ability to spawn tasks in the center pane, when spawning from a keybinding: ```json5 [ { // Assuming you have a task labeled "echo hello" "ctrl--": [ "task::Spawn", { "task_name": "echo hello", "target": "center" } ] } ] ```
This commit is contained in:
parent
85c3aec6e7
commit
4f96706161
18 changed files with 263 additions and 106 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -12635,6 +12635,7 @@ dependencies = [
|
|||
"sha2",
|
||||
"shellexpand 2.1.2",
|
||||
"util",
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -15603,6 +15604,7 @@ dependencies = [
|
|||
"ui",
|
||||
"util",
|
||||
"uuid",
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -523,7 +523,7 @@ impl RunnableTasks {
|
|||
) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
|
||||
self.templates.iter().filter_map(|(kind, template)| {
|
||||
template
|
||||
.resolve_task(&kind.to_id_base(), cx)
|
||||
.resolve_task(&kind.to_id_base(), Default::default(), cx)
|
||||
.map(|task| (kind.clone(), task))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -177,7 +177,7 @@ impl Inventory {
|
|||
let id_base = kind.to_id_base();
|
||||
Some((
|
||||
kind,
|
||||
task.resolve_task(&id_base, task_context)?,
|
||||
task.resolve_task(&id_base, Default::default(), task_context)?,
|
||||
not_used_score,
|
||||
))
|
||||
})
|
||||
|
@ -397,7 +397,7 @@ mod test_inventory {
|
|||
let id_base = task_source_kind.to_id_base();
|
||||
inventory.task_scheduled(
|
||||
task_source_kind.clone(),
|
||||
task.resolve_task(&id_base, &TaskContext::default())
|
||||
task.resolve_task(&id_base, Default::default(), &TaskContext::default())
|
||||
.unwrap_or_else(|| panic!("Failed to resolve task with name {task_name}")),
|
||||
);
|
||||
});
|
||||
|
|
|
@ -331,7 +331,7 @@ fn local_task_context_for_location(
|
|||
let worktree_id = location.buffer.read(cx).file().map(|f| f.worktree_id(cx));
|
||||
let worktree_abs_path = worktree_id
|
||||
.and_then(|worktree_id| worktree_store.read(cx).worktree_for_id(worktree_id, cx))
|
||||
.map(|worktree| worktree.read(cx).abs_path());
|
||||
.and_then(|worktree| worktree.read(cx).root_dir());
|
||||
|
||||
cx.spawn(|mut cx| async move {
|
||||
let worktree_abs_path = worktree_abs_path.clone();
|
||||
|
|
|
@ -50,13 +50,7 @@ impl Project {
|
|||
.and_then(|entry_id| self.worktree_for_entry(entry_id, cx))
|
||||
.into_iter()
|
||||
.chain(self.worktrees(cx))
|
||||
.find_map(|tree| {
|
||||
let worktree = tree.read(cx);
|
||||
worktree
|
||||
.root_entry()
|
||||
.filter(|entry| entry.is_dir())
|
||||
.map(|_| worktree.abs_path().clone())
|
||||
});
|
||||
.find_map(|tree| tree.read(cx).root_dir());
|
||||
worktree
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ serde_json_lenient.workspace = true
|
|||
sha2.workspace = true
|
||||
shellexpand.workspace = true
|
||||
util.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
|
|
|
@ -18,11 +18,11 @@ pub use vscode_format::VsCodeTaskFile;
|
|||
|
||||
/// Task identifier, unique within the application.
|
||||
/// Based on it, task reruns and terminal tabs are managed.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize, Serialize)]
|
||||
pub struct TaskId(pub String);
|
||||
|
||||
/// Contains all information needed by Zed to spawn a new terminal tab for the given task.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SpawnInTerminal {
|
||||
/// Id of the task to use when determining task tab affinity.
|
||||
pub id: TaskId,
|
||||
|
@ -57,6 +57,15 @@ pub struct SpawnInTerminal {
|
|||
pub show_command: bool,
|
||||
}
|
||||
|
||||
/// An action for spawning a specific task
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct NewCenterTask {
|
||||
/// The specification of the task to spawn.
|
||||
pub action: SpawnInTerminal,
|
||||
}
|
||||
|
||||
gpui::impl_actions!(tasks, [NewCenterTask]);
|
||||
|
||||
/// A final form of the [`TaskTemplate`], that got resolved with a particualar [`TaskContext`] and now is ready to spawn the actual task.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ResolvedTask {
|
||||
|
@ -75,6 +84,9 @@ pub struct ResolvedTask {
|
|||
/// Further actions that need to take place after the resolved task is spawned,
|
||||
/// with all task variables resolved.
|
||||
pub resolved: Option<SpawnInTerminal>,
|
||||
|
||||
/// where to sawn the task in the UI, either in the terminal panel or in the center pane
|
||||
pub target: zed_actions::TaskSpawnTarget,
|
||||
}
|
||||
|
||||
impl ResolvedTask {
|
||||
|
|
|
@ -115,7 +115,12 @@ impl TaskTemplate {
|
|||
///
|
||||
/// Every [`ResolvedTask`] gets a [`TaskId`], based on the `id_base` (to avoid collision with various task sources),
|
||||
/// and hashes of its template and [`TaskContext`], see [`ResolvedTask`] fields' documentation for more details.
|
||||
pub fn resolve_task(&self, id_base: &str, cx: &TaskContext) -> Option<ResolvedTask> {
|
||||
pub fn resolve_task(
|
||||
&self,
|
||||
id_base: &str,
|
||||
target: zed_actions::TaskSpawnTarget,
|
||||
cx: &TaskContext,
|
||||
) -> Option<ResolvedTask> {
|
||||
if self.label.trim().is_empty() || self.command.trim().is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
@ -214,6 +219,7 @@ impl TaskTemplate {
|
|||
Some(ResolvedTask {
|
||||
id: id.clone(),
|
||||
substituted_variables,
|
||||
target,
|
||||
original_task: self.clone(),
|
||||
resolved_label: full_label.clone(),
|
||||
resolved: Some(SpawnInTerminal {
|
||||
|
@ -382,7 +388,7 @@ mod tests {
|
|||
},
|
||||
] {
|
||||
assert_eq!(
|
||||
task_with_blank_property.resolve_task(TEST_ID_BASE, &TaskContext::default()),
|
||||
task_with_blank_property.resolve_task(TEST_ID_BASE, Default::default(), &TaskContext::default()),
|
||||
None,
|
||||
"should not resolve task with blank label and/or command: {task_with_blank_property:?}"
|
||||
);
|
||||
|
@ -400,7 +406,7 @@ mod tests {
|
|||
|
||||
let resolved_task = |task_template: &TaskTemplate, task_cx| {
|
||||
let resolved_task = task_template
|
||||
.resolve_task(TEST_ID_BASE, task_cx)
|
||||
.resolve_task(TEST_ID_BASE, Default::default(), task_cx)
|
||||
.unwrap_or_else(|| panic!("failed to resolve task {task_without_cwd:?}"));
|
||||
assert_substituted_variables(&resolved_task, Vec::new());
|
||||
resolved_task
|
||||
|
@ -526,6 +532,7 @@ mod tests {
|
|||
for i in 0..15 {
|
||||
let resolved_task = task_with_all_variables.resolve_task(
|
||||
TEST_ID_BASE,
|
||||
Default::default(),
|
||||
&TaskContext {
|
||||
cwd: None,
|
||||
task_variables: TaskVariables::from_iter(all_variables.clone()),
|
||||
|
@ -614,6 +621,7 @@ mod tests {
|
|||
let removed_variable = not_all_variables.remove(i);
|
||||
let resolved_task_attempt = task_with_all_variables.resolve_task(
|
||||
TEST_ID_BASE,
|
||||
Default::default(),
|
||||
&TaskContext {
|
||||
cwd: None,
|
||||
task_variables: TaskVariables::from_iter(not_all_variables),
|
||||
|
@ -633,7 +641,7 @@ mod tests {
|
|||
..Default::default()
|
||||
};
|
||||
let resolved_task = task
|
||||
.resolve_task(TEST_ID_BASE, &TaskContext::default())
|
||||
.resolve_task(TEST_ID_BASE, Default::default(), &TaskContext::default())
|
||||
.unwrap();
|
||||
assert_substituted_variables(&resolved_task, Vec::new());
|
||||
let resolved = resolved_task.resolved.unwrap();
|
||||
|
@ -651,7 +659,7 @@ mod tests {
|
|||
..Default::default()
|
||||
};
|
||||
assert!(task
|
||||
.resolve_task(TEST_ID_BASE, &TaskContext::default())
|
||||
.resolve_task(TEST_ID_BASE, Default::default(), &TaskContext::default())
|
||||
.is_none());
|
||||
}
|
||||
|
||||
|
@ -701,7 +709,7 @@ mod tests {
|
|||
.enumerate()
|
||||
{
|
||||
let resolved = symbol_dependent_task
|
||||
.resolve_task(TEST_ID_BASE, &cx)
|
||||
.resolve_task(TEST_ID_BASE, Default::default(), &cx)
|
||||
.unwrap_or_else(|| panic!("Failed to resolve task {symbol_dependent_task:?}"));
|
||||
assert_eq!(
|
||||
resolved.substituted_variables,
|
||||
|
@ -743,7 +751,9 @@ mod tests {
|
|||
context
|
||||
.task_variables
|
||||
.insert(VariableName::Symbol, "my-symbol".to_string());
|
||||
assert!(faulty_go_test.resolve_task("base", &context).is_some());
|
||||
assert!(faulty_go_test
|
||||
.resolve_task("base", Default::default(), &context)
|
||||
.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -802,7 +812,7 @@ mod tests {
|
|||
};
|
||||
|
||||
let resolved = template
|
||||
.resolve_task(TEST_ID_BASE, &context)
|
||||
.resolve_task(TEST_ID_BASE, Default::default(), &context)
|
||||
.unwrap()
|
||||
.resolved
|
||||
.unwrap();
|
||||
|
|
|
@ -11,6 +11,7 @@ mod modal;
|
|||
mod settings;
|
||||
|
||||
pub use modal::{Rerun, Spawn};
|
||||
use zed_actions::TaskSpawnTarget;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
settings::TaskSettings::register(cx);
|
||||
|
@ -53,6 +54,7 @@ pub fn init(cx: &mut AppContext) {
|
|||
task_source_kind,
|
||||
&original_task,
|
||||
&task_context,
|
||||
Default::default(),
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
|
@ -89,7 +91,8 @@ pub fn init(cx: &mut AppContext) {
|
|||
|
||||
fn spawn_task_or_modal(workspace: &mut Workspace, action: &Spawn, cx: &mut ViewContext<Workspace>) {
|
||||
match &action.task_name {
|
||||
Some(name) => spawn_task_with_name(name.clone(), cx).detach_and_log_err(cx),
|
||||
Some(name) => spawn_task_with_name(name.clone(), action.target.unwrap_or_default(), cx)
|
||||
.detach_and_log_err(cx),
|
||||
None => toggle_modal(workspace, cx).detach(),
|
||||
}
|
||||
}
|
||||
|
@ -119,6 +122,7 @@ fn toggle_modal(workspace: &mut Workspace, cx: &mut ViewContext<'_, Workspace>)
|
|||
|
||||
fn spawn_task_with_name(
|
||||
name: String,
|
||||
task_target: TaskSpawnTarget,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> AsyncTask<anyhow::Result<()>> {
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
|
@ -160,6 +164,7 @@ fn spawn_task_with_name(
|
|||
task_source_kind,
|
||||
&target_task,
|
||||
&task_context,
|
||||
task_target,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
|
|
|
@ -68,7 +68,7 @@ impl TasksModalDelegate {
|
|||
};
|
||||
Some((
|
||||
source_kind,
|
||||
new_oneshot.resolve_task(&id_base, &self.task_context)?,
|
||||
new_oneshot.resolve_task(&id_base, Default::default(), &self.task_context)?,
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -684,6 +684,7 @@ mod tests {
|
|||
|
||||
cx.dispatch_action(Spawn {
|
||||
task_name: Some("example task".to_string()),
|
||||
target: None,
|
||||
});
|
||||
let tasks_picker = workspace.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
|
|
|
@ -255,7 +255,10 @@ impl TerminalPanel {
|
|||
terminal_panel
|
||||
.update(&mut cx, |_, cx| {
|
||||
cx.subscribe(&workspace, |terminal_panel, _, e, cx| {
|
||||
if let workspace::Event::SpawnTask(spawn_in_terminal) = e {
|
||||
if let workspace::Event::SpawnTask {
|
||||
action: spawn_in_terminal,
|
||||
} = e
|
||||
{
|
||||
terminal_panel.spawn_task(spawn_in_terminal, cx);
|
||||
};
|
||||
})
|
||||
|
@ -450,83 +453,17 @@ impl TerminalPanel {
|
|||
|
||||
fn spawn_task(&mut self, spawn_in_terminal: &SpawnInTerminal, cx: &mut ViewContext<Self>) {
|
||||
let mut spawn_task = spawn_in_terminal.clone();
|
||||
// Set up shell args unconditionally, as tasks are always spawned inside of a shell.
|
||||
let Some((shell, mut user_args)) = (match spawn_in_terminal.shell.clone() {
|
||||
Shell::System => {
|
||||
match self
|
||||
let Ok(is_local) = self
|
||||
.workspace
|
||||
.update(cx, |workspace, cx| workspace.project().read(cx).is_local())
|
||||
{
|
||||
Ok(local) => {
|
||||
if local {
|
||||
retrieve_system_shell().map(|shell| (shell, Vec::new()))
|
||||
} else {
|
||||
Some(("\"${SHELL:-sh}\"".to_string(), Vec::new()))
|
||||
}
|
||||
}
|
||||
Err(_no_window_e) => return,
|
||||
}
|
||||
}
|
||||
Shell::Program(shell) => Some((shell, Vec::new())),
|
||||
Shell::WithArguments { program, args, .. } => Some((program, args)),
|
||||
}) else {
|
||||
else {
|
||||
return;
|
||||
};
|
||||
#[cfg(target_os = "windows")]
|
||||
let windows_shell_type = to_windows_shell_type(&shell);
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if let ControlFlow::Break(_) =
|
||||
Self::fill_command(is_local, spawn_in_terminal, &mut spawn_task)
|
||||
{
|
||||
spawn_task.command_label = format!("{shell} -i -c '{}'", spawn_task.command_label);
|
||||
return;
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use crate::terminal_panel::WindowsShellType;
|
||||
|
||||
match windows_shell_type {
|
||||
WindowsShellType::Powershell => {
|
||||
spawn_task.command_label = format!("{shell} -C '{}'", spawn_task.command_label)
|
||||
}
|
||||
WindowsShellType::Cmd => {
|
||||
spawn_task.command_label = format!("{shell} /C '{}'", spawn_task.command_label)
|
||||
}
|
||||
WindowsShellType::Other => {
|
||||
spawn_task.command_label =
|
||||
format!("{shell} -i -c '{}'", spawn_task.command_label)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let task_command = std::mem::replace(&mut spawn_task.command, shell);
|
||||
let task_args = std::mem::take(&mut spawn_task.args);
|
||||
let combined_command = task_args
|
||||
.into_iter()
|
||||
.fold(task_command, |mut command, arg| {
|
||||
command.push(' ');
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
command.push_str(&arg);
|
||||
#[cfg(target_os = "windows")]
|
||||
command.push_str(&to_windows_shell_variable(windows_shell_type, arg));
|
||||
command
|
||||
});
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
user_args.extend(["-i".to_owned(), "-c".to_owned(), combined_command]);
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use crate::terminal_panel::WindowsShellType;
|
||||
|
||||
match windows_shell_type {
|
||||
WindowsShellType::Powershell => {
|
||||
user_args.extend(["-C".to_owned(), combined_command])
|
||||
}
|
||||
WindowsShellType::Cmd => user_args.extend(["/C".to_owned(), combined_command]),
|
||||
WindowsShellType::Other => {
|
||||
user_args.extend(["-i".to_owned(), "-c".to_owned(), combined_command])
|
||||
}
|
||||
}
|
||||
}
|
||||
spawn_task.args = user_args;
|
||||
let spawn_task = spawn_task;
|
||||
|
||||
let allow_concurrent_runs = spawn_in_terminal.allow_concurrent_runs;
|
||||
|
@ -602,6 +539,81 @@ impl TerminalPanel {
|
|||
.detach()
|
||||
}
|
||||
|
||||
pub fn fill_command(
|
||||
is_local: bool,
|
||||
spawn_in_terminal: &SpawnInTerminal,
|
||||
spawn_task: &mut SpawnInTerminal,
|
||||
) -> ControlFlow<()> {
|
||||
let Some((shell, mut user_args)) = (match spawn_in_terminal.shell.clone() {
|
||||
Shell::System => {
|
||||
if is_local {
|
||||
retrieve_system_shell().map(|shell| (shell, Vec::new()))
|
||||
} else {
|
||||
Some(("\"${SHELL:-sh}\"".to_string(), Vec::new()))
|
||||
}
|
||||
}
|
||||
Shell::Program(shell) => Some((shell, Vec::new())),
|
||||
Shell::WithArguments { program, args, .. } => Some((program, args)),
|
||||
}) else {
|
||||
return ControlFlow::Break(());
|
||||
};
|
||||
#[cfg(target_os = "windows")]
|
||||
let windows_shell_type = to_windows_shell_type(&shell);
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
spawn_task.command_label = format!("{shell} -i -c '{}'", spawn_task.command_label);
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use crate::terminal_panel::WindowsShellType;
|
||||
|
||||
match windows_shell_type {
|
||||
WindowsShellType::Powershell => {
|
||||
spawn_task.command_label = format!("{shell} -C '{}'", spawn_task.command_label)
|
||||
}
|
||||
WindowsShellType::Cmd => {
|
||||
spawn_task.command_label = format!("{shell} /C '{}'", spawn_task.command_label)
|
||||
}
|
||||
WindowsShellType::Other => {
|
||||
spawn_task.command_label =
|
||||
format!("{shell} -i -c '{}'", spawn_task.command_label)
|
||||
}
|
||||
}
|
||||
}
|
||||
let task_command = std::mem::replace(&mut spawn_task.command, shell);
|
||||
let task_args = std::mem::take(&mut spawn_task.args);
|
||||
let combined_command = task_args
|
||||
.into_iter()
|
||||
.fold(task_command, |mut command, arg| {
|
||||
command.push(' ');
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
command.push_str(&arg);
|
||||
#[cfg(target_os = "windows")]
|
||||
command.push_str(&to_windows_shell_variable(windows_shell_type, arg));
|
||||
command
|
||||
});
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
user_args.extend(["-i".to_owned(), "-c".to_owned(), combined_command]);
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use crate::terminal_panel::WindowsShellType;
|
||||
|
||||
match windows_shell_type {
|
||||
WindowsShellType::Powershell => {
|
||||
user_args.extend(["-C".to_owned(), combined_command])
|
||||
}
|
||||
WindowsShellType::Cmd => user_args.extend(["/C".to_owned(), combined_command]),
|
||||
WindowsShellType::Other => {
|
||||
user_args.extend(["-i".to_owned(), "-c".to_owned(), combined_command])
|
||||
}
|
||||
}
|
||||
}
|
||||
spawn_task.args = user_args;
|
||||
// Set up shell args unconditionally, as tasks are always spawned inside of a shell.
|
||||
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
pub fn spawn_in_new_terminal(
|
||||
&mut self,
|
||||
spawn_task: SpawnInTerminal,
|
||||
|
|
|
@ -14,6 +14,7 @@ use gpui::{
|
|||
use language::Bias;
|
||||
use persistence::TERMINAL_DB;
|
||||
use project::{search::SearchQuery, terminals::TerminalKind, Fs, Metadata, Project};
|
||||
use task::{NewCenterTask, RevealStrategy};
|
||||
use terminal::{
|
||||
alacritty_terminal::{
|
||||
index::Point,
|
||||
|
@ -45,7 +46,7 @@ use zed_actions::InlineAssist;
|
|||
|
||||
use std::{
|
||||
cmp,
|
||||
ops::RangeInclusive,
|
||||
ops::{ControlFlow, RangeInclusive},
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
|
@ -78,8 +79,9 @@ pub fn init(cx: &mut AppContext) {
|
|||
|
||||
register_serializable_item::<TerminalView>(cx);
|
||||
|
||||
cx.observe_new_views(|workspace: &mut Workspace, _| {
|
||||
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
||||
workspace.register_action(TerminalView::deploy);
|
||||
workspace.register_action(TerminalView::deploy_center_task);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
@ -127,6 +129,61 @@ impl FocusableView for TerminalView {
|
|||
}
|
||||
|
||||
impl TerminalView {
|
||||
pub fn deploy_center_task(
|
||||
workspace: &mut Workspace,
|
||||
task: &NewCenterTask,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let reveal_strategy: RevealStrategy = task.action.reveal;
|
||||
let mut spawn_task = task.action.clone();
|
||||
|
||||
let is_local = workspace.project().read(cx).is_local();
|
||||
|
||||
if let ControlFlow::Break(_) =
|
||||
TerminalPanel::fill_command(is_local, &task.action, &mut spawn_task)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let kind = TerminalKind::Task(spawn_task);
|
||||
|
||||
let project = workspace.project().clone();
|
||||
let database_id = workspace.database_id();
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
let terminal = cx
|
||||
.update(|cx| {
|
||||
let window = cx.window_handle();
|
||||
project.update(cx, |project, cx| project.create_terminal(kind, window, cx))
|
||||
})?
|
||||
.await?;
|
||||
|
||||
let terminal_view = cx.new_view(|cx| {
|
||||
TerminalView::new(terminal.clone(), workspace.clone(), database_id, cx)
|
||||
})?;
|
||||
|
||||
cx.update(|cx| {
|
||||
let focus_item = match reveal_strategy {
|
||||
RevealStrategy::Always => true,
|
||||
RevealStrategy::Never | RevealStrategy::NoFocus => false,
|
||||
};
|
||||
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(terminal_view),
|
||||
None,
|
||||
focus_item,
|
||||
cx,
|
||||
);
|
||||
})?;
|
||||
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
///Create a new Terminal in the current working directory or the user's home directory
|
||||
pub fn deploy(
|
||||
workspace: &mut Workspace,
|
||||
|
|
|
@ -61,6 +61,7 @@ ui.workspace = true
|
|||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
strum.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
call = { workspace = true, features = ["test-support"] }
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
use project::TaskSourceKind;
|
||||
use remote::ConnectionState;
|
||||
use task::{ResolvedTask, TaskContext, TaskTemplate};
|
||||
use task::{NewCenterTask, ResolvedTask, TaskContext, TaskTemplate};
|
||||
use ui::ViewContext;
|
||||
use zed_actions::TaskSpawnTarget;
|
||||
|
||||
use crate::Workspace;
|
||||
|
||||
pub fn schedule_task(
|
||||
workspace: &Workspace,
|
||||
workspace: &mut Workspace,
|
||||
task_source_kind: TaskSourceKind,
|
||||
task_to_resolve: &TaskTemplate,
|
||||
task_cx: &TaskContext,
|
||||
task_target: zed_actions::TaskSpawnTarget,
|
||||
omit_history: bool,
|
||||
cx: &mut ViewContext<'_, Workspace>,
|
||||
) {
|
||||
|
@ -27,7 +29,7 @@ pub fn schedule_task(
|
|||
}
|
||||
|
||||
if let Some(spawn_in_terminal) =
|
||||
task_to_resolve.resolve_task(&task_source_kind.to_id_base(), task_cx)
|
||||
task_to_resolve.resolve_task(&task_source_kind.to_id_base(), task_target, task_cx)
|
||||
{
|
||||
schedule_resolved_task(
|
||||
workspace,
|
||||
|
@ -40,12 +42,13 @@ pub fn schedule_task(
|
|||
}
|
||||
|
||||
pub fn schedule_resolved_task(
|
||||
workspace: &Workspace,
|
||||
workspace: &mut Workspace,
|
||||
task_source_kind: TaskSourceKind,
|
||||
mut resolved_task: ResolvedTask,
|
||||
omit_history: bool,
|
||||
cx: &mut ViewContext<'_, Workspace>,
|
||||
) {
|
||||
let target = resolved_task.target;
|
||||
if let Some(spawn_in_terminal) = resolved_task.resolved.take() {
|
||||
if !omit_history {
|
||||
resolved_task.resolved = Some(spawn_in_terminal.clone());
|
||||
|
@ -59,6 +62,18 @@ pub fn schedule_resolved_task(
|
|||
}
|
||||
});
|
||||
}
|
||||
cx.emit(crate::Event::SpawnTask(Box::new(spawn_in_terminal)));
|
||||
|
||||
match target {
|
||||
TaskSpawnTarget::Center => {
|
||||
cx.dispatch_action(Box::new(NewCenterTask {
|
||||
action: spawn_in_terminal,
|
||||
}));
|
||||
}
|
||||
TaskSpawnTarget::Dock => {
|
||||
cx.emit(crate::Event::SpawnTask {
|
||||
action: Box::new(spawn_in_terminal),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -686,7 +686,9 @@ pub enum Event {
|
|||
},
|
||||
ContactRequestedJoin(u64),
|
||||
WorkspaceCreated(WeakView<Workspace>),
|
||||
SpawnTask(Box<SpawnInTerminal>),
|
||||
SpawnTask {
|
||||
action: Box<SpawnInTerminal>,
|
||||
},
|
||||
OpenBundledFile {
|
||||
text: Cow<'static, str>,
|
||||
title: &'static str,
|
||||
|
|
|
@ -2533,6 +2533,12 @@ impl Snapshot {
|
|||
self.entry_for_path("")
|
||||
}
|
||||
|
||||
pub fn root_dir(&self) -> Option<Arc<Path>> {
|
||||
self.root_entry()
|
||||
.filter(|entry| entry.is_dir())
|
||||
.map(|_| self.abs_path().clone())
|
||||
}
|
||||
|
||||
pub fn root_name(&self) -> &str {
|
||||
&self.root_name
|
||||
}
|
||||
|
|
|
@ -90,6 +90,14 @@ pub struct OpenRecent {
|
|||
gpui::impl_actions!(projects, [OpenRecent]);
|
||||
gpui::actions!(projects, [OpenRemote]);
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Deserialize, Default, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TaskSpawnTarget {
|
||||
Center,
|
||||
#[default]
|
||||
Dock,
|
||||
}
|
||||
|
||||
/// Spawn a task with name or open tasks modal
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct Spawn {
|
||||
|
@ -98,11 +106,18 @@ pub struct Spawn {
|
|||
/// If it is not set, a modal with a list of available tasks is opened instead.
|
||||
/// Defaults to None.
|
||||
pub task_name: Option<String>,
|
||||
/// Which part of the UI the task should be spawned in.
|
||||
/// Defaults to Dock.
|
||||
#[serde(default)]
|
||||
pub target: Option<TaskSpawnTarget>,
|
||||
}
|
||||
|
||||
impl Spawn {
|
||||
pub fn modal() -> Self {
|
||||
Self { task_name: None }
|
||||
Self {
|
||||
task_name: None,
|
||||
target: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -155,6 +155,30 @@ You can define your own keybindings for your tasks via additional argument to `t
|
|||
}
|
||||
```
|
||||
|
||||
Note that these tasks can also have a 'target' specified to control where the spawned task should show up.
|
||||
This could be useful for launching a terminal application that you want to use in the center area:
|
||||
|
||||
```json
|
||||
// In tasks.json
|
||||
{
|
||||
"label": "start lazygit",
|
||||
"command": "lazygit -p $ZED_WORKTREE_ROOT"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// In keymap.json
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
"alt-g": [
|
||||
"task::Spawn",
|
||||
{ "task_name": "start lazygit", "target": "center" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Binding runnable tags to task templates
|
||||
|
||||
Zed supports overriding default action for inline runnable indicators via workspace-local and global `tasks.json` file with the following precedence hierarchy:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue