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:
Mikayla Maki 2024-12-13 19:39:46 -08:00 committed by GitHub
parent 85c3aec6e7
commit 4f96706161
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 263 additions and 106 deletions

View file

@ -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"] }

View file

@ -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 {

View file

@ -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();