mirror of
https://github.com/zed-industries/zed.git
synced 2025-02-03 08:54:04 +00:00
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:
parent
ea012075fc
commit
bc113e4b51
17 changed files with 356 additions and 285 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -15603,7 +15603,6 @@ dependencies = [
|
||||||
"ui",
|
"ui",
|
||||||
"util",
|
"util",
|
||||||
"uuid",
|
"uuid",
|
||||||
"zed_actions",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -16108,6 +16107,7 @@ name = "zed_actions"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gpui",
|
"gpui",
|
||||||
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -426,7 +426,10 @@
|
||||||
"ctrl-shift-r": "task::Rerun",
|
"ctrl-shift-r": "task::Rerun",
|
||||||
"ctrl-alt-r": "task::Rerun",
|
"ctrl-alt-r": "task::Rerun",
|
||||||
"alt-t": "task::Rerun",
|
"alt-t": "task::Rerun",
|
||||||
"alt-shift-t": "task::Spawn"
|
"alt-shift-t": "task::Spawn",
|
||||||
|
"alt-shift-r": ["task::Spawn", { "reveal_target": "center" }]
|
||||||
|
// also possible to spawn tasks by name:
|
||||||
|
// "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Bindings from Sublime Text
|
// Bindings from Sublime Text
|
||||||
|
|
|
@ -495,8 +495,9 @@
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-shift-r": "task::Spawn",
|
"cmd-shift-r": "task::Spawn",
|
||||||
"cmd-alt-r": "task::Rerun",
|
"cmd-alt-r": "task::Rerun",
|
||||||
"alt-t": "task::Spawn",
|
"ctrl-alt-shift-r": ["task::Spawn", { "reveal_target": "center" }]
|
||||||
"alt-shift-t": "task::Spawn"
|
// also possible to spawn tasks by name:
|
||||||
|
// "foo-bar": ["task_name::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Bindings from Sublime Text
|
// Bindings from Sublime Text
|
||||||
|
|
|
@ -15,10 +15,14 @@
|
||||||
// Whether to allow multiple instances of the same task to be run, or rather wait for the existing ones to finish, defaults to `false`.
|
// Whether to allow multiple instances of the same task to be run, or rather wait for the existing ones to finish, defaults to `false`.
|
||||||
"allow_concurrent_runs": false,
|
"allow_concurrent_runs": false,
|
||||||
// What to do with the terminal pane and tab, after the command was started:
|
// What to do with the terminal pane and tab, after the command was started:
|
||||||
// * `always` — always show the terminal pane, add and focus the corresponding task's tab in it (default)
|
// * `always` — always show the task's pane, and focus the corresponding tab in it (default)
|
||||||
// * `no_focus` — always show the terminal pane, add/reuse the task's tab there, but don't focus it
|
// * `no_focus` — always show the task's pane, add the task's tab in it, but don't focus it
|
||||||
// * `never` — avoid changing current terminal pane focus, but still add/reuse the task's tab there
|
// * `never` — do not alter focus, but still add/reuse the task's tab in its pane
|
||||||
"reveal": "always",
|
"reveal": "always",
|
||||||
|
// Where to place the task's terminal item after starting the task:
|
||||||
|
// * `dock` — in the terminal dock, "regular" terminal items' place (default)
|
||||||
|
// * `center` — in the central pane group, "main" editor area
|
||||||
|
"reveal_target": "dock",
|
||||||
// What to do with the terminal pane and tab, after the command had finished:
|
// What to do with the terminal pane and tab, after the command had finished:
|
||||||
// * `never` — Do nothing when the command finishes (default)
|
// * `never` — Do nothing when the command finishes (default)
|
||||||
// * `always` — always hide the terminal tab, hide the pane also if it was the last tab in it
|
// * `always` — always hide the terminal tab, hide the pane also if it was the last tab in it
|
||||||
|
|
|
@ -522,7 +522,7 @@ impl RunnableTasks {
|
||||||
) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
|
) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
|
||||||
self.templates.iter().filter_map(|(kind, template)| {
|
self.templates.iter().filter_map(|(kind, template)| {
|
||||||
template
|
template
|
||||||
.resolve_task(&kind.to_id_base(), Default::default(), cx)
|
.resolve_task(&kind.to_id_base(), cx)
|
||||||
.map(|task| (kind.clone(), task))
|
.map(|task| (kind.clone(), task))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,7 +184,7 @@ impl Inventory {
|
||||||
let id_base = kind.to_id_base();
|
let id_base = kind.to_id_base();
|
||||||
Some((
|
Some((
|
||||||
kind,
|
kind,
|
||||||
task.resolve_task(&id_base, Default::default(), task_context)?,
|
task.resolve_task(&id_base, task_context)?,
|
||||||
not_used_score,
|
not_used_score,
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
|
@ -378,7 +378,7 @@ mod test_inventory {
|
||||||
|
|
||||||
use crate::Inventory;
|
use crate::Inventory;
|
||||||
|
|
||||||
use super::{task_source_kind_preference, TaskSourceKind};
|
use super::TaskSourceKind;
|
||||||
|
|
||||||
pub(super) fn task_template_names(
|
pub(super) fn task_template_names(
|
||||||
inventory: &Model<Inventory>,
|
inventory: &Model<Inventory>,
|
||||||
|
@ -409,7 +409,7 @@ mod test_inventory {
|
||||||
let id_base = task_source_kind.to_id_base();
|
let id_base = task_source_kind.to_id_base();
|
||||||
inventory.task_scheduled(
|
inventory.task_scheduled(
|
||||||
task_source_kind.clone(),
|
task_source_kind.clone(),
|
||||||
task.resolve_task(&id_base, Default::default(), &TaskContext::default())
|
task.resolve_task(&id_base, &TaskContext::default())
|
||||||
.unwrap_or_else(|| panic!("Failed to resolve task with name {task_name}")),
|
.unwrap_or_else(|| panic!("Failed to resolve task with name {task_name}")),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -427,31 +427,12 @@ mod test_inventory {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|(source_kind, task)| {
|
.filter_map(|(source_kind, task)| {
|
||||||
let id_base = source_kind.to_id_base();
|
let id_base = source_kind.to_id_base();
|
||||||
Some((
|
Some((source_kind, task.resolve_task(&id_base, task_context)?))
|
||||||
source_kind,
|
|
||||||
task.resolve_task(&id_base, Default::default(), task_context)?,
|
|
||||||
))
|
|
||||||
})
|
})
|
||||||
.map(|(source_kind, resolved_task)| (source_kind, resolved_task.resolved_label))
|
.map(|(source_kind, resolved_task)| (source_kind, resolved_task.resolved_label))
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn list_tasks_sorted_by_last_used(
|
|
||||||
inventory: &Model<Inventory>,
|
|
||||||
worktree: Option<WorktreeId>,
|
|
||||||
cx: &mut TestAppContext,
|
|
||||||
) -> Vec<(TaskSourceKind, String)> {
|
|
||||||
let (used, current) = inventory.update(cx, |inventory, cx| {
|
|
||||||
inventory.used_and_current_resolved_tasks(worktree, None, &TaskContext::default(), cx)
|
|
||||||
});
|
|
||||||
let mut all = used;
|
|
||||||
all.extend(current);
|
|
||||||
all.into_iter()
|
|
||||||
.map(|(source_kind, task)| (source_kind, task.resolved_label))
|
|
||||||
.sorted_by_key(|(kind, label)| (task_source_kind_preference(kind), label.clone()))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A context provided that tries to provide values for all non-custom [`VariableName`] variants for a currently opened file.
|
/// A context provided that tries to provide values for all non-custom [`VariableName`] variants for a currently opened file.
|
||||||
|
@ -877,7 +858,7 @@ mod tests {
|
||||||
TaskStore::init(None);
|
TaskStore::init(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn resolved_task_names(
|
async fn resolved_task_names(
|
||||||
inventory: &Model<Inventory>,
|
inventory: &Model<Inventory>,
|
||||||
worktree: Option<WorktreeId>,
|
worktree: Option<WorktreeId>,
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
|
@ -905,4 +886,20 @@ mod tests {
|
||||||
))
|
))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn list_tasks_sorted_by_last_used(
|
||||||
|
inventory: &Model<Inventory>,
|
||||||
|
worktree: Option<WorktreeId>,
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
) -> Vec<(TaskSourceKind, String)> {
|
||||||
|
let (used, current) = inventory.update(cx, |inventory, cx| {
|
||||||
|
inventory.used_and_current_resolved_tasks(worktree, None, &TaskContext::default(), cx)
|
||||||
|
});
|
||||||
|
let mut all = used;
|
||||||
|
all.extend(current);
|
||||||
|
all.into_iter()
|
||||||
|
.map(|(source_kind, task)| (source_kind, task.resolved_label))
|
||||||
|
.sorted_by_key(|(kind, label)| (task_source_kind_preference(kind), label.clone()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,14 +15,15 @@ use std::str::FromStr;
|
||||||
|
|
||||||
pub use task_template::{HideStrategy, RevealStrategy, TaskTemplate, TaskTemplates};
|
pub use task_template::{HideStrategy, RevealStrategy, TaskTemplate, TaskTemplates};
|
||||||
pub use vscode_format::VsCodeTaskFile;
|
pub use vscode_format::VsCodeTaskFile;
|
||||||
|
pub use zed_actions::RevealTarget;
|
||||||
|
|
||||||
/// Task identifier, unique within the application.
|
/// Task identifier, unique within the application.
|
||||||
/// Based on it, task reruns and terminal tabs are managed.
|
/// Based on it, task reruns and terminal tabs are managed.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize)]
|
||||||
pub struct TaskId(pub String);
|
pub struct TaskId(pub String);
|
||||||
|
|
||||||
/// Contains all information needed by Zed to spawn a new terminal tab for the given task.
|
/// Contains all information needed by Zed to spawn a new terminal tab for the given task.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct SpawnInTerminal {
|
pub struct SpawnInTerminal {
|
||||||
/// Id of the task to use when determining task tab affinity.
|
/// Id of the task to use when determining task tab affinity.
|
||||||
pub id: TaskId,
|
pub id: TaskId,
|
||||||
|
@ -47,6 +48,8 @@ pub struct SpawnInTerminal {
|
||||||
pub allow_concurrent_runs: bool,
|
pub allow_concurrent_runs: bool,
|
||||||
/// What to do with the terminal pane and tab, after the command was started.
|
/// What to do with the terminal pane and tab, after the command was started.
|
||||||
pub reveal: RevealStrategy,
|
pub reveal: RevealStrategy,
|
||||||
|
/// Where to show tasks' terminal output.
|
||||||
|
pub reveal_target: RevealTarget,
|
||||||
/// What to do with the terminal pane and tab, after the command had finished.
|
/// What to do with the terminal pane and tab, after the command had finished.
|
||||||
pub hide: HideStrategy,
|
pub hide: HideStrategy,
|
||||||
/// Which shell to use when spawning the task.
|
/// Which shell to use when spawning the task.
|
||||||
|
@ -57,15 +60,6 @@ pub struct SpawnInTerminal {
|
||||||
pub show_command: bool,
|
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.
|
/// 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)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct ResolvedTask {
|
pub struct ResolvedTask {
|
||||||
|
@ -84,9 +78,6 @@ pub struct ResolvedTask {
|
||||||
/// Further actions that need to take place after the resolved task is spawned,
|
/// Further actions that need to take place after the resolved task is spawned,
|
||||||
/// with all task variables resolved.
|
/// with all task variables resolved.
|
||||||
pub resolved: Option<SpawnInTerminal>,
|
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 {
|
impl ResolvedTask {
|
||||||
|
|
|
@ -9,7 +9,7 @@ use sha2::{Digest, Sha256};
|
||||||
use util::{truncate_and_remove_front, ResultExt};
|
use util::{truncate_and_remove_front, ResultExt};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ResolvedTask, Shell, SpawnInTerminal, TaskContext, TaskId, VariableName,
|
ResolvedTask, RevealTarget, Shell, SpawnInTerminal, TaskContext, TaskId, VariableName,
|
||||||
ZED_VARIABLE_NAME_PREFIX,
|
ZED_VARIABLE_NAME_PREFIX,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -42,10 +42,16 @@ pub struct TaskTemplate {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub allow_concurrent_runs: bool,
|
pub allow_concurrent_runs: bool,
|
||||||
/// What to do with the terminal pane and tab, after the command was started:
|
/// What to do with the terminal pane and tab, after the command was started:
|
||||||
/// * `always` — always show the terminal pane, add and focus the corresponding task's tab in it (default)
|
/// * `always` — always show the task's pane, and focus the corresponding tab in it (default)
|
||||||
/// * `never` — avoid changing current terminal pane focus, but still add/reuse the task's tab there
|
// * `no_focus` — always show the task's pane, add the task's tab in it, but don't focus it
|
||||||
|
// * `never` — do not alter focus, but still add/reuse the task's tab in its pane
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub reveal: RevealStrategy,
|
pub reveal: RevealStrategy,
|
||||||
|
/// Where to place the task's terminal item after starting the task.
|
||||||
|
/// * `dock` — in the terminal dock, "regular" terminal items' place (default).
|
||||||
|
/// * `center` — in the central pane group, "main" editor area.
|
||||||
|
#[serde(default)]
|
||||||
|
pub reveal_target: RevealTarget,
|
||||||
/// What to do with the terminal pane and tab, after the command had finished:
|
/// What to do with the terminal pane and tab, after the command had finished:
|
||||||
/// * `never` — do nothing when the command finishes (default)
|
/// * `never` — do nothing when the command finishes (default)
|
||||||
/// * `always` — always hide the terminal tab, hide the pane also if it was the last tab in it
|
/// * `always` — always hide the terminal tab, hide the pane also if it was the last tab in it
|
||||||
|
@ -70,12 +76,12 @@ pub struct TaskTemplate {
|
||||||
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum RevealStrategy {
|
pub enum RevealStrategy {
|
||||||
/// Always show the terminal pane, add and focus the corresponding task's tab in it.
|
/// Always show the task's pane, and focus the corresponding tab in it.
|
||||||
#[default]
|
#[default]
|
||||||
Always,
|
Always,
|
||||||
/// Always show the terminal pane, add the task's tab in it, but don't focus it.
|
/// Always show the task's pane, add the task's tab in it, but don't focus it.
|
||||||
NoFocus,
|
NoFocus,
|
||||||
/// Do not change terminal pane focus, but still add/reuse the task's tab there.
|
/// Do not alter focus, but still add/reuse the task's tab in its pane.
|
||||||
Never,
|
Never,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,12 +121,7 @@ impl TaskTemplate {
|
||||||
///
|
///
|
||||||
/// Every [`ResolvedTask`] gets a [`TaskId`], based on the `id_base` (to avoid collision with various task sources),
|
/// 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.
|
/// and hashes of its template and [`TaskContext`], see [`ResolvedTask`] fields' documentation for more details.
|
||||||
pub fn resolve_task(
|
pub fn resolve_task(&self, id_base: &str, cx: &TaskContext) -> Option<ResolvedTask> {
|
||||||
&self,
|
|
||||||
id_base: &str,
|
|
||||||
target: zed_actions::TaskSpawnTarget,
|
|
||||||
cx: &TaskContext,
|
|
||||||
) -> Option<ResolvedTask> {
|
|
||||||
if self.label.trim().is_empty() || self.command.trim().is_empty() {
|
if self.label.trim().is_empty() || self.command.trim().is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -219,7 +220,6 @@ impl TaskTemplate {
|
||||||
Some(ResolvedTask {
|
Some(ResolvedTask {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
substituted_variables,
|
substituted_variables,
|
||||||
target,
|
|
||||||
original_task: self.clone(),
|
original_task: self.clone(),
|
||||||
resolved_label: full_label.clone(),
|
resolved_label: full_label.clone(),
|
||||||
resolved: Some(SpawnInTerminal {
|
resolved: Some(SpawnInTerminal {
|
||||||
|
@ -241,6 +241,7 @@ impl TaskTemplate {
|
||||||
use_new_terminal: self.use_new_terminal,
|
use_new_terminal: self.use_new_terminal,
|
||||||
allow_concurrent_runs: self.allow_concurrent_runs,
|
allow_concurrent_runs: self.allow_concurrent_runs,
|
||||||
reveal: self.reveal,
|
reveal: self.reveal,
|
||||||
|
reveal_target: self.reveal_target,
|
||||||
hide: self.hide,
|
hide: self.hide,
|
||||||
shell: self.shell.clone(),
|
shell: self.shell.clone(),
|
||||||
show_summary: self.show_summary,
|
show_summary: self.show_summary,
|
||||||
|
@ -388,7 +389,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
] {
|
] {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
task_with_blank_property.resolve_task(TEST_ID_BASE, Default::default(), &TaskContext::default()),
|
task_with_blank_property.resolve_task(TEST_ID_BASE, &TaskContext::default()),
|
||||||
None,
|
None,
|
||||||
"should not resolve task with blank label and/or command: {task_with_blank_property:?}"
|
"should not resolve task with blank label and/or command: {task_with_blank_property:?}"
|
||||||
);
|
);
|
||||||
|
@ -406,7 +407,7 @@ mod tests {
|
||||||
|
|
||||||
let resolved_task = |task_template: &TaskTemplate, task_cx| {
|
let resolved_task = |task_template: &TaskTemplate, task_cx| {
|
||||||
let resolved_task = task_template
|
let resolved_task = task_template
|
||||||
.resolve_task(TEST_ID_BASE, Default::default(), task_cx)
|
.resolve_task(TEST_ID_BASE, task_cx)
|
||||||
.unwrap_or_else(|| panic!("failed to resolve task {task_without_cwd:?}"));
|
.unwrap_or_else(|| panic!("failed to resolve task {task_without_cwd:?}"));
|
||||||
assert_substituted_variables(&resolved_task, Vec::new());
|
assert_substituted_variables(&resolved_task, Vec::new());
|
||||||
resolved_task
|
resolved_task
|
||||||
|
@ -532,7 +533,6 @@ mod tests {
|
||||||
for i in 0..15 {
|
for i in 0..15 {
|
||||||
let resolved_task = task_with_all_variables.resolve_task(
|
let resolved_task = task_with_all_variables.resolve_task(
|
||||||
TEST_ID_BASE,
|
TEST_ID_BASE,
|
||||||
Default::default(),
|
|
||||||
&TaskContext {
|
&TaskContext {
|
||||||
cwd: None,
|
cwd: None,
|
||||||
task_variables: TaskVariables::from_iter(all_variables.clone()),
|
task_variables: TaskVariables::from_iter(all_variables.clone()),
|
||||||
|
@ -621,7 +621,6 @@ mod tests {
|
||||||
let removed_variable = not_all_variables.remove(i);
|
let removed_variable = not_all_variables.remove(i);
|
||||||
let resolved_task_attempt = task_with_all_variables.resolve_task(
|
let resolved_task_attempt = task_with_all_variables.resolve_task(
|
||||||
TEST_ID_BASE,
|
TEST_ID_BASE,
|
||||||
Default::default(),
|
|
||||||
&TaskContext {
|
&TaskContext {
|
||||||
cwd: None,
|
cwd: None,
|
||||||
task_variables: TaskVariables::from_iter(not_all_variables),
|
task_variables: TaskVariables::from_iter(not_all_variables),
|
||||||
|
@ -638,10 +637,10 @@ mod tests {
|
||||||
label: "My task".into(),
|
label: "My task".into(),
|
||||||
command: "echo".into(),
|
command: "echo".into(),
|
||||||
args: vec!["$PATH".into()],
|
args: vec!["$PATH".into()],
|
||||||
..Default::default()
|
..TaskTemplate::default()
|
||||||
};
|
};
|
||||||
let resolved_task = task
|
let resolved_task = task
|
||||||
.resolve_task(TEST_ID_BASE, Default::default(), &TaskContext::default())
|
.resolve_task(TEST_ID_BASE, &TaskContext::default())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_substituted_variables(&resolved_task, Vec::new());
|
assert_substituted_variables(&resolved_task, Vec::new());
|
||||||
let resolved = resolved_task.resolved.unwrap();
|
let resolved = resolved_task.resolved.unwrap();
|
||||||
|
@ -656,10 +655,10 @@ mod tests {
|
||||||
label: "My task".into(),
|
label: "My task".into(),
|
||||||
command: "echo".into(),
|
command: "echo".into(),
|
||||||
args: vec!["$ZED_VARIABLE".into()],
|
args: vec!["$ZED_VARIABLE".into()],
|
||||||
..Default::default()
|
..TaskTemplate::default()
|
||||||
};
|
};
|
||||||
assert!(task
|
assert!(task
|
||||||
.resolve_task(TEST_ID_BASE, Default::default(), &TaskContext::default())
|
.resolve_task(TEST_ID_BASE, &TaskContext::default())
|
||||||
.is_none());
|
.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -709,7 +708,7 @@ mod tests {
|
||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
let resolved = symbol_dependent_task
|
let resolved = symbol_dependent_task
|
||||||
.resolve_task(TEST_ID_BASE, Default::default(), &cx)
|
.resolve_task(TEST_ID_BASE, &cx)
|
||||||
.unwrap_or_else(|| panic!("Failed to resolve task {symbol_dependent_task:?}"));
|
.unwrap_or_else(|| panic!("Failed to resolve task {symbol_dependent_task:?}"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolved.substituted_variables,
|
resolved.substituted_variables,
|
||||||
|
@ -751,9 +750,7 @@ mod tests {
|
||||||
context
|
context
|
||||||
.task_variables
|
.task_variables
|
||||||
.insert(VariableName::Symbol, "my-symbol".to_string());
|
.insert(VariableName::Symbol, "my-symbol".to_string());
|
||||||
assert!(faulty_go_test
|
assert!(faulty_go_test.resolve_task("base", &context).is_some());
|
||||||
.resolve_task("base", Default::default(), &context)
|
|
||||||
.is_some());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -812,7 +809,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
let resolved = template
|
let resolved = template
|
||||||
.resolve_task(TEST_ID_BASE, Default::default(), &context)
|
.resolve_task(TEST_ID_BASE, &context)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.resolved
|
.resolved
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use ::settings::Settings;
|
use ::settings::Settings;
|
||||||
use editor::{tasks::task_context, Editor};
|
use editor::{tasks::task_context, Editor};
|
||||||
use gpui::{AppContext, Task as AsyncTask, ViewContext, WindowContext};
|
use gpui::{AppContext, Task as AsyncTask, ViewContext, WindowContext};
|
||||||
use modal::TasksModal;
|
use modal::{TaskOverrides, TasksModal};
|
||||||
use project::{Location, WorktreeId};
|
use project::{Location, WorktreeId};
|
||||||
use task::TaskId;
|
use task::{RevealTarget, TaskId};
|
||||||
use workspace::tasks::schedule_task;
|
use workspace::tasks::schedule_task;
|
||||||
use workspace::{tasks::schedule_resolved_task, Workspace};
|
use workspace::{tasks::schedule_resolved_task, Workspace};
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ mod modal;
|
||||||
mod settings;
|
mod settings;
|
||||||
|
|
||||||
pub use modal::{Rerun, Spawn};
|
pub use modal::{Rerun, Spawn};
|
||||||
use zed_actions::TaskSpawnTarget;
|
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
settings::TaskSettings::register(cx);
|
settings::TaskSettings::register(cx);
|
||||||
|
@ -54,7 +53,6 @@ pub fn init(cx: &mut AppContext) {
|
||||||
task_source_kind,
|
task_source_kind,
|
||||||
&original_task,
|
&original_task,
|
||||||
&task_context,
|
&task_context,
|
||||||
Default::default(),
|
|
||||||
false,
|
false,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -81,7 +79,7 @@ pub fn init(cx: &mut AppContext) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toggle_modal(workspace, cx).detach();
|
toggle_modal(workspace, None, cx).detach();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -90,14 +88,25 @@ pub fn init(cx: &mut AppContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_task_or_modal(workspace: &mut Workspace, action: &Spawn, cx: &mut ViewContext<Workspace>) {
|
fn spawn_task_or_modal(workspace: &mut Workspace, action: &Spawn, cx: &mut ViewContext<Workspace>) {
|
||||||
match &action.task_name {
|
match action {
|
||||||
Some(name) => spawn_task_with_name(name.clone(), action.target.unwrap_or_default(), cx)
|
Spawn::ByName {
|
||||||
.detach_and_log_err(cx),
|
task_name,
|
||||||
None => toggle_modal(workspace, cx).detach(),
|
reveal_target,
|
||||||
|
} => {
|
||||||
|
let overrides = reveal_target.map(|reveal_target| TaskOverrides {
|
||||||
|
reveal_target: Some(reveal_target),
|
||||||
|
});
|
||||||
|
spawn_task_with_name(task_name.clone(), overrides, cx).detach_and_log_err(cx)
|
||||||
|
}
|
||||||
|
Spawn::ViaModal { reveal_target } => toggle_modal(workspace, *reveal_target, cx).detach(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_modal(workspace: &mut Workspace, cx: &mut ViewContext<'_, Workspace>) -> AsyncTask<()> {
|
fn toggle_modal(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
reveal_target: Option<RevealTarget>,
|
||||||
|
cx: &mut ViewContext<'_, Workspace>,
|
||||||
|
) -> AsyncTask<()> {
|
||||||
let task_store = workspace.project().read(cx).task_store().clone();
|
let task_store = workspace.project().read(cx).task_store().clone();
|
||||||
let workspace_handle = workspace.weak_handle();
|
let workspace_handle = workspace.weak_handle();
|
||||||
let can_open_modal = workspace.project().update(cx, |project, cx| {
|
let can_open_modal = workspace.project().update(cx, |project, cx| {
|
||||||
|
@ -110,7 +119,15 @@ fn toggle_modal(workspace: &mut Workspace, cx: &mut ViewContext<'_, Workspace>)
|
||||||
workspace
|
workspace
|
||||||
.update(&mut cx, |workspace, cx| {
|
.update(&mut cx, |workspace, cx| {
|
||||||
workspace.toggle_modal(cx, |cx| {
|
workspace.toggle_modal(cx, |cx| {
|
||||||
TasksModal::new(task_store.clone(), task_context, workspace_handle, cx)
|
TasksModal::new(
|
||||||
|
task_store.clone(),
|
||||||
|
task_context,
|
||||||
|
reveal_target.map(|target| TaskOverrides {
|
||||||
|
reveal_target: Some(target),
|
||||||
|
}),
|
||||||
|
workspace_handle,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
@ -122,7 +139,7 @@ fn toggle_modal(workspace: &mut Workspace, cx: &mut ViewContext<'_, Workspace>)
|
||||||
|
|
||||||
fn spawn_task_with_name(
|
fn spawn_task_with_name(
|
||||||
name: String,
|
name: String,
|
||||||
task_target: TaskSpawnTarget,
|
overrides: Option<TaskOverrides>,
|
||||||
cx: &mut ViewContext<Workspace>,
|
cx: &mut ViewContext<Workspace>,
|
||||||
) -> AsyncTask<anyhow::Result<()>> {
|
) -> AsyncTask<anyhow::Result<()>> {
|
||||||
cx.spawn(|workspace, mut cx| async move {
|
cx.spawn(|workspace, mut cx| async move {
|
||||||
|
@ -157,14 +174,18 @@ fn spawn_task_with_name(
|
||||||
|
|
||||||
let did_spawn = workspace
|
let did_spawn = workspace
|
||||||
.update(&mut cx, |workspace, cx| {
|
.update(&mut cx, |workspace, cx| {
|
||||||
let (task_source_kind, target_task) =
|
let (task_source_kind, mut target_task) =
|
||||||
tasks.into_iter().find(|(_, task)| task.label == name)?;
|
tasks.into_iter().find(|(_, task)| task.label == name)?;
|
||||||
|
if let Some(overrides) = &overrides {
|
||||||
|
if let Some(target_override) = overrides.reveal_target {
|
||||||
|
target_task.reveal_target = target_override;
|
||||||
|
}
|
||||||
|
}
|
||||||
schedule_task(
|
schedule_task(
|
||||||
workspace,
|
workspace,
|
||||||
task_source_kind,
|
task_source_kind,
|
||||||
&target_task,
|
&target_task,
|
||||||
&task_context,
|
&task_context,
|
||||||
task_target,
|
|
||||||
false,
|
false,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -174,7 +195,13 @@ fn spawn_task_with_name(
|
||||||
if !did_spawn {
|
if !did_spawn {
|
||||||
workspace
|
workspace
|
||||||
.update(&mut cx, |workspace, cx| {
|
.update(&mut cx, |workspace, cx| {
|
||||||
spawn_task_or_modal(workspace, &Spawn::default(), cx);
|
spawn_task_or_modal(
|
||||||
|
workspace,
|
||||||
|
&Spawn::ViaModal {
|
||||||
|
reveal_target: overrides.and_then(|overrides| overrides.reveal_target),
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use picker::{highlighted_match_with_paths::HighlightedText, Picker, PickerDelegate};
|
use picker::{highlighted_match_with_paths::HighlightedText, Picker, PickerDelegate};
|
||||||
use project::{task_store::TaskStore, TaskSourceKind};
|
use project::{task_store::TaskStore, TaskSourceKind};
|
||||||
use task::{ResolvedTask, TaskContext, TaskTemplate};
|
use task::{ResolvedTask, RevealTarget, TaskContext, TaskTemplate};
|
||||||
use ui::{
|
use ui::{
|
||||||
div, h_flex, v_flex, ActiveTheme, Button, ButtonCommon, ButtonSize, Clickable, Color,
|
div, h_flex, v_flex, ActiveTheme, Button, ButtonCommon, ButtonSize, Clickable, Color,
|
||||||
FluentBuilder as _, Icon, IconButton, IconButtonShape, IconName, IconSize, IntoElement,
|
FluentBuilder as _, Icon, IconButton, IconButtonShape, IconName, IconSize, IntoElement,
|
||||||
|
@ -24,6 +24,7 @@ pub use zed_actions::{Rerun, Spawn};
|
||||||
pub(crate) struct TasksModalDelegate {
|
pub(crate) struct TasksModalDelegate {
|
||||||
task_store: Model<TaskStore>,
|
task_store: Model<TaskStore>,
|
||||||
candidates: Option<Vec<(TaskSourceKind, ResolvedTask)>>,
|
candidates: Option<Vec<(TaskSourceKind, ResolvedTask)>>,
|
||||||
|
task_overrides: Option<TaskOverrides>,
|
||||||
last_used_candidate_index: Option<usize>,
|
last_used_candidate_index: Option<usize>,
|
||||||
divider_index: Option<usize>,
|
divider_index: Option<usize>,
|
||||||
matches: Vec<StringMatch>,
|
matches: Vec<StringMatch>,
|
||||||
|
@ -34,12 +35,28 @@ pub(crate) struct TasksModalDelegate {
|
||||||
placeholder_text: Arc<str>,
|
placeholder_text: Arc<str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Task template amendments to do before resolving the context.
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub(crate) struct TaskOverrides {
|
||||||
|
/// See [`RevealTarget`].
|
||||||
|
pub(crate) reveal_target: Option<RevealTarget>,
|
||||||
|
}
|
||||||
|
|
||||||
impl TasksModalDelegate {
|
impl TasksModalDelegate {
|
||||||
fn new(
|
fn new(
|
||||||
task_store: Model<TaskStore>,
|
task_store: Model<TaskStore>,
|
||||||
task_context: TaskContext,
|
task_context: TaskContext,
|
||||||
|
task_overrides: Option<TaskOverrides>,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let placeholder_text = if let Some(TaskOverrides {
|
||||||
|
reveal_target: Some(RevealTarget::Center),
|
||||||
|
}) = &task_overrides
|
||||||
|
{
|
||||||
|
Arc::from("Find a task, or run a command in the central pane")
|
||||||
|
} else {
|
||||||
|
Arc::from("Find a task, or run a command")
|
||||||
|
};
|
||||||
Self {
|
Self {
|
||||||
task_store,
|
task_store,
|
||||||
workspace,
|
workspace,
|
||||||
|
@ -50,7 +67,8 @@ impl TasksModalDelegate {
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
prompt: String::default(),
|
prompt: String::default(),
|
||||||
task_context,
|
task_context,
|
||||||
placeholder_text: Arc::from("Find a task, or run a command"),
|
task_overrides,
|
||||||
|
placeholder_text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,14 +79,20 @@ impl TasksModalDelegate {
|
||||||
|
|
||||||
let source_kind = TaskSourceKind::UserInput;
|
let source_kind = TaskSourceKind::UserInput;
|
||||||
let id_base = source_kind.to_id_base();
|
let id_base = source_kind.to_id_base();
|
||||||
let new_oneshot = TaskTemplate {
|
let mut new_oneshot = TaskTemplate {
|
||||||
label: self.prompt.clone(),
|
label: self.prompt.clone(),
|
||||||
command: self.prompt.clone(),
|
command: self.prompt.clone(),
|
||||||
..TaskTemplate::default()
|
..TaskTemplate::default()
|
||||||
};
|
};
|
||||||
|
if let Some(TaskOverrides {
|
||||||
|
reveal_target: Some(reveal_target),
|
||||||
|
}) = &self.task_overrides
|
||||||
|
{
|
||||||
|
new_oneshot.reveal_target = *reveal_target;
|
||||||
|
}
|
||||||
Some((
|
Some((
|
||||||
source_kind,
|
source_kind,
|
||||||
new_oneshot.resolve_task(&id_base, Default::default(), &self.task_context)?,
|
new_oneshot.resolve_task(&id_base, &self.task_context)?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,12 +124,13 @@ impl TasksModal {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
task_store: Model<TaskStore>,
|
task_store: Model<TaskStore>,
|
||||||
task_context: TaskContext,
|
task_context: TaskContext,
|
||||||
|
task_overrides: Option<TaskOverrides>,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let picker = cx.new_view(|cx| {
|
let picker = cx.new_view(|cx| {
|
||||||
Picker::uniform_list(
|
Picker::uniform_list(
|
||||||
TasksModalDelegate::new(task_store, task_context, workspace),
|
TasksModalDelegate::new(task_store, task_context, task_overrides, workspace),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -257,9 +282,17 @@ impl PickerDelegate for TasksModalDelegate {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|candidates| candidates[ix].clone())
|
.map(|candidates| candidates[ix].clone())
|
||||||
});
|
});
|
||||||
let Some((task_source_kind, task)) = task else {
|
let Some((task_source_kind, mut task)) = task else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
if let Some(TaskOverrides {
|
||||||
|
reveal_target: Some(reveal_target),
|
||||||
|
}) = &self.task_overrides
|
||||||
|
{
|
||||||
|
if let Some(resolved_task) = &mut task.resolved {
|
||||||
|
resolved_task.reveal_target = *reveal_target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.workspace
|
self.workspace
|
||||||
.update(cx, |workspace, cx| {
|
.update(cx, |workspace, cx| {
|
||||||
|
@ -396,9 +429,18 @@ impl PickerDelegate for TasksModalDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm_input(&mut self, omit_history_entry: bool, cx: &mut ViewContext<Picker<Self>>) {
|
fn confirm_input(&mut self, omit_history_entry: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||||
let Some((task_source_kind, task)) = self.spawn_oneshot() else {
|
let Some((task_source_kind, mut task)) = self.spawn_oneshot() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(TaskOverrides {
|
||||||
|
reveal_target: Some(reveal_target),
|
||||||
|
}) = self.task_overrides
|
||||||
|
{
|
||||||
|
if let Some(resolved_task) = &mut task.resolved {
|
||||||
|
resolved_task.reveal_target = reveal_target;
|
||||||
|
}
|
||||||
|
}
|
||||||
self.workspace
|
self.workspace
|
||||||
.update(cx, |workspace, cx| {
|
.update(cx, |workspace, cx| {
|
||||||
schedule_resolved_task(workspace, task_source_kind, task, omit_history_entry, cx);
|
schedule_resolved_task(workspace, task_source_kind, task, omit_history_entry, cx);
|
||||||
|
@ -682,9 +724,9 @@ mod tests {
|
||||||
"No query should be added to the list, as it was submitted with secondary action (that maps to omit_history = true)"
|
"No query should be added to the list, as it was submitted with secondary action (that maps to omit_history = true)"
|
||||||
);
|
);
|
||||||
|
|
||||||
cx.dispatch_action(Spawn {
|
cx.dispatch_action(Spawn::ByName {
|
||||||
task_name: Some("example task".to_string()),
|
task_name: "example task".to_string(),
|
||||||
target: None,
|
reveal_target: None,
|
||||||
});
|
});
|
||||||
let tasks_picker = workspace.update(cx, |workspace, cx| {
|
let tasks_picker = workspace.update(cx, |workspace, cx| {
|
||||||
workspace
|
workspace
|
||||||
|
@ -995,7 +1037,7 @@ mod tests {
|
||||||
workspace: &View<Workspace>,
|
workspace: &View<Workspace>,
|
||||||
cx: &mut VisualTestContext,
|
cx: &mut VisualTestContext,
|
||||||
) -> View<Picker<TasksModalDelegate>> {
|
) -> View<Picker<TasksModalDelegate>> {
|
||||||
cx.dispatch_action(Spawn::default());
|
cx.dispatch_action(Spawn::modal());
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
workspace
|
workspace
|
||||||
.active_modal::<TasksModal>(cx)
|
.active_modal::<TasksModal>(cx)
|
||||||
|
|
|
@ -12,7 +12,7 @@ use collections::HashMap;
|
||||||
use db::kvp::KEY_VALUE_STORE;
|
use db::kvp::KEY_VALUE_STORE;
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use gpui::{
|
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,
|
ExternalPaths, FocusHandle, FocusableView, IntoElement, Model, ParentElement, Pixels, Render,
|
||||||
Styled, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
|
Styled, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
|
@ -20,7 +20,7 @@ use itertools::Itertools;
|
||||||
use project::{terminals::TerminalKind, Fs, Project, ProjectEntryId};
|
use project::{terminals::TerminalKind, Fs, Project, ProjectEntryId};
|
||||||
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use task::{RevealStrategy, Shell, SpawnInTerminal, TaskId};
|
use task::{RevealStrategy, RevealTarget, Shell, SpawnInTerminal, TaskId};
|
||||||
use terminal::{
|
use terminal::{
|
||||||
terminal_settings::{TerminalDockPosition, TerminalSettings},
|
terminal_settings::{TerminalDockPosition, TerminalSettings},
|
||||||
Terminal,
|
Terminal,
|
||||||
|
@ -40,7 +40,7 @@ use workspace::{
|
||||||
SplitUp, SwapPaneInDirection, ToggleZoom, Workspace,
|
SplitUp, SwapPaneInDirection, ToggleZoom, Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{anyhow, Context, Result};
|
||||||
use zed_actions::InlineAssist;
|
use zed_actions::InlineAssist;
|
||||||
|
|
||||||
const TERMINAL_PANEL_KEY: &str = "TerminalPanel";
|
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::new_terminal);
|
||||||
workspace.register_action(TerminalPanel::open_terminal);
|
workspace.register_action(TerminalPanel::open_terminal);
|
||||||
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
|
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
|
||||||
if workspace
|
if is_enabled_in_workspace(workspace, cx) {
|
||||||
.panel::<TerminalPanel>(cx)
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|panel| panel.read(cx).enabled)
|
|
||||||
{
|
|
||||||
workspace.toggle_panel_focus::<TerminalPanel>(cx);
|
workspace.toggle_panel_focus::<TerminalPanel>(cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -76,7 +72,6 @@ pub struct TerminalPanel {
|
||||||
pending_serialization: Task<Option<()>>,
|
pending_serialization: Task<Option<()>>,
|
||||||
pending_terminals_to_add: usize,
|
pending_terminals_to_add: usize,
|
||||||
deferred_tasks: HashMap<TaskId, Task<()>>,
|
deferred_tasks: HashMap<TaskId, Task<()>>,
|
||||||
enabled: bool,
|
|
||||||
assistant_enabled: bool,
|
assistant_enabled: bool,
|
||||||
assistant_tab_bar_button: Option<AnyView>,
|
assistant_tab_bar_button: Option<AnyView>,
|
||||||
}
|
}
|
||||||
|
@ -86,7 +81,6 @@ impl TerminalPanel {
|
||||||
let project = workspace.project();
|
let project = workspace.project();
|
||||||
let pane = new_terminal_pane(workspace.weak_handle(), project.clone(), false, cx);
|
let pane = new_terminal_pane(workspace.weak_handle(), project.clone(), false, cx);
|
||||||
let center = PaneGroup::new(pane.clone());
|
let center = PaneGroup::new(pane.clone());
|
||||||
let enabled = project.read(cx).supports_terminal(cx);
|
|
||||||
cx.focus_view(&pane);
|
cx.focus_view(&pane);
|
||||||
let terminal_panel = Self {
|
let terminal_panel = Self {
|
||||||
center,
|
center,
|
||||||
|
@ -98,7 +92,6 @@ impl TerminalPanel {
|
||||||
height: None,
|
height: None,
|
||||||
pending_terminals_to_add: 0,
|
pending_terminals_to_add: 0,
|
||||||
deferred_tasks: HashMap::default(),
|
deferred_tasks: HashMap::default(),
|
||||||
enabled,
|
|
||||||
assistant_enabled: false,
|
assistant_enabled: false,
|
||||||
assistant_tab_bar_button: None,
|
assistant_tab_bar_button: None,
|
||||||
};
|
};
|
||||||
|
@ -492,8 +485,8 @@ impl TerminalPanel {
|
||||||
!use_new_terminal,
|
!use_new_terminal,
|
||||||
"Should have handled 'allow_concurrent_runs && use_new_terminal' case above"
|
"Should have handled 'allow_concurrent_runs && use_new_terminal' case above"
|
||||||
);
|
);
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |terminal_panel, cx| {
|
||||||
this.replace_terminal(
|
terminal_panel.replace_terminal(
|
||||||
spawn_task,
|
spawn_task,
|
||||||
task_pane,
|
task_pane,
|
||||||
existing_item_index,
|
existing_item_index,
|
||||||
|
@ -620,7 +613,17 @@ impl TerminalPanel {
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Task<Result<Model<Terminal>>> {
|
) -> Task<Result<Model<Terminal>>> {
|
||||||
let reveal = spawn_task.reveal;
|
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
|
/// Create a new Terminal in the current working directory or the user's home directory
|
||||||
|
@ -647,10 +650,11 @@ impl TerminalPanel {
|
||||||
label: &str,
|
label: &str,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Vec<(usize, View<Pane>, View<TerminalView>)> {
|
) -> Vec<(usize, View<Pane>, View<TerminalView>)> {
|
||||||
self.center
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
.panes()
|
return Vec::new();
|
||||||
.into_iter()
|
};
|
||||||
.flat_map(|pane| {
|
|
||||||
|
let pane_terminal_views = |pane: View<Pane>| {
|
||||||
pane.read(cx)
|
pane.read(cx)
|
||||||
.items()
|
.items()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
|
@ -663,8 +667,23 @@ impl TerminalPanel {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|(index, terminal_view)| (index, pane.clone(), terminal_view))
|
.map(move |(index, terminal_view)| (index, pane.clone(), terminal_view))
|
||||||
})
|
};
|
||||||
|
|
||||||
|
self.center
|
||||||
|
.panes()
|
||||||
|
.into_iter()
|
||||||
|
.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()
|
.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(
|
fn add_terminal(
|
||||||
&mut self,
|
&mut self,
|
||||||
kind: TerminalKind,
|
kind: TerminalKind,
|
||||||
reveal_strategy: RevealStrategy,
|
reveal_strategy: RevealStrategy,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Task<Result<Model<Terminal>>> {
|
) -> Task<Result<Model<Terminal>>> {
|
||||||
if !self.enabled {
|
if !self.is_enabled(cx) {
|
||||||
return Task::ready(Err(anyhow::anyhow!(
|
return Task::ready(Err(anyhow!(
|
||||||
"terminal not yet supported for remote projects"
|
"terminal not yet supported for remote projects"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
@ -786,10 +839,11 @@ impl TerminalPanel {
|
||||||
cx: &mut ViewContext<'_, Self>,
|
cx: &mut ViewContext<'_, Self>,
|
||||||
) -> Task<Option<()>> {
|
) -> Task<Option<()>> {
|
||||||
let reveal = spawn_task.reveal;
|
let reveal = spawn_task.reveal;
|
||||||
|
let reveal_target = spawn_task.reveal_target;
|
||||||
let window = cx.window_handle();
|
let window = cx.window_handle();
|
||||||
let task_workspace = self.workspace.clone();
|
let task_workspace = self.workspace.clone();
|
||||||
cx.spawn(move |this, mut cx| async move {
|
cx.spawn(move |terminal_panel, mut cx| async move {
|
||||||
let project = this
|
let project = terminal_panel
|
||||||
.update(&mut cx, |this, cx| {
|
.update(&mut cx, |this, cx| {
|
||||||
this.workspace
|
this.workspace
|
||||||
.update(cx, |workspace, _| workspace.project().clone())
|
.update(cx, |workspace, _| workspace.project().clone())
|
||||||
|
@ -811,9 +865,29 @@ impl TerminalPanel {
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
||||||
match reveal {
|
match reveal {
|
||||||
RevealStrategy::Always => {
|
RevealStrategy::Always => match reveal_target {
|
||||||
this.update(&mut cx, |this, cx| {
|
RevealTarget::Center => {
|
||||||
this.activate_terminal_view(&task_pane, terminal_item_index, true, cx)
|
task_workspace
|
||||||
|
.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()?;
|
.ok()?;
|
||||||
|
|
||||||
|
@ -824,9 +898,24 @@ impl TerminalPanel {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
RevealStrategy::NoFocus => {
|
},
|
||||||
this.update(&mut cx, |this, cx| {
|
RevealStrategy::NoFocus => match reveal_target {
|
||||||
this.activate_terminal_view(&task_pane, terminal_item_index, false, cx)
|
RevealTarget::Center => {
|
||||||
|
task_workspace
|
||||||
|
.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()?;
|
.ok()?;
|
||||||
|
|
||||||
|
@ -837,6 +926,7 @@ impl TerminalPanel {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
RevealStrategy::Never => {}
|
RevealStrategy::Never => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -851,6 +941,16 @@ impl TerminalPanel {
|
||||||
pub fn assistant_enabled(&self) -> bool {
|
pub fn assistant_enabled(&self) -> bool {
|
||||||
self.assistant_enabled
|
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(
|
pub fn new_terminal_pane(
|
||||||
|
@ -1235,7 +1335,7 @@ impl Panel for TerminalPanel {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.add_terminal(kind, RevealStrategy::Never, cx)
|
this.add_terminal(kind, RevealStrategy::Always, cx)
|
||||||
.detach_and_log_err(cx)
|
.detach_and_log_err(cx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1259,7 +1359,9 @@ impl Panel for TerminalPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn icon(&self, cx: &WindowContext) -> Option<IconName> {
|
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)
|
Some(IconName::Terminal)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
|
@ -14,7 +14,6 @@ use gpui::{
|
||||||
use language::Bias;
|
use language::Bias;
|
||||||
use persistence::TERMINAL_DB;
|
use persistence::TERMINAL_DB;
|
||||||
use project::{search::SearchQuery, terminals::TerminalKind, Fs, Metadata, Project};
|
use project::{search::SearchQuery, terminals::TerminalKind, Fs, Metadata, Project};
|
||||||
use task::{NewCenterTask, RevealStrategy};
|
|
||||||
use terminal::{
|
use terminal::{
|
||||||
alacritty_terminal::{
|
alacritty_terminal::{
|
||||||
index::Point,
|
index::Point,
|
||||||
|
@ -31,7 +30,6 @@ use ui::{h_flex, prelude::*, ContextMenu, Icon, IconName, Label, Tooltip};
|
||||||
use util::{paths::PathWithPosition, ResultExt};
|
use util::{paths::PathWithPosition, ResultExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{BreadcrumbText, Item, ItemEvent, SerializableItem, TabContentParams},
|
item::{BreadcrumbText, Item, ItemEvent, SerializableItem, TabContentParams},
|
||||||
notifications::NotifyResultExt,
|
|
||||||
register_serializable_item,
|
register_serializable_item,
|
||||||
searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle},
|
searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle},
|
||||||
CloseActiveItem, NewCenterTerminal, NewTerminal, OpenVisible, ToolbarItemLocation, Workspace,
|
CloseActiveItem, NewCenterTerminal, NewTerminal, OpenVisible, ToolbarItemLocation, Workspace,
|
||||||
|
@ -46,7 +44,7 @@ use zed_actions::InlineAssist;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cmp,
|
cmp,
|
||||||
ops::{ControlFlow, RangeInclusive},
|
ops::RangeInclusive,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -81,7 +79,6 @@ pub fn init(cx: &mut AppContext) {
|
||||||
|
|
||||||
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
||||||
workspace.register_action(TerminalView::deploy);
|
workspace.register_action(TerminalView::deploy);
|
||||||
workspace.register_action(TerminalView::deploy_center_task);
|
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
@ -129,61 +126,6 @@ impl FocusableView for TerminalView {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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
|
///Create a new Terminal in the current working directory or the user's home directory
|
||||||
pub fn deploy(
|
pub fn deploy(
|
||||||
workspace: &mut Workspace,
|
workspace: &mut Workspace,
|
||||||
|
@ -191,38 +133,8 @@ impl TerminalView {
|
||||||
cx: &mut ViewContext<Workspace>,
|
cx: &mut ViewContext<Workspace>,
|
||||||
) {
|
) {
|
||||||
let working_directory = default_working_directory(workspace, cx);
|
let working_directory = default_working_directory(workspace, cx);
|
||||||
|
TerminalPanel::add_center_terminal(workspace, TerminalKind::Shell(working_directory), cx)
|
||||||
let window = cx.window_handle();
|
.detach_and_log_err(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()?;
|
|
||||||
|
|
||||||
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(
|
pub fn new(
|
||||||
|
|
|
@ -61,7 +61,6 @@ ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
strum.workspace = true
|
strum.workspace = true
|
||||||
zed_actions.workspace = true
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
call = { workspace = true, features = ["test-support"] }
|
call = { workspace = true, features = ["test-support"] }
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use project::TaskSourceKind;
|
use project::TaskSourceKind;
|
||||||
use remote::ConnectionState;
|
use remote::ConnectionState;
|
||||||
use task::{NewCenterTask, ResolvedTask, TaskContext, TaskTemplate};
|
use task::{ResolvedTask, TaskContext, TaskTemplate};
|
||||||
use ui::ViewContext;
|
use ui::ViewContext;
|
||||||
use zed_actions::TaskSpawnTarget;
|
|
||||||
|
|
||||||
use crate::Workspace;
|
use crate::Workspace;
|
||||||
|
|
||||||
|
@ -11,7 +10,6 @@ pub fn schedule_task(
|
||||||
task_source_kind: TaskSourceKind,
|
task_source_kind: TaskSourceKind,
|
||||||
task_to_resolve: &TaskTemplate,
|
task_to_resolve: &TaskTemplate,
|
||||||
task_cx: &TaskContext,
|
task_cx: &TaskContext,
|
||||||
task_target: zed_actions::TaskSpawnTarget,
|
|
||||||
omit_history: bool,
|
omit_history: bool,
|
||||||
cx: &mut ViewContext<'_, Workspace>,
|
cx: &mut ViewContext<'_, Workspace>,
|
||||||
) {
|
) {
|
||||||
|
@ -29,7 +27,7 @@ pub fn schedule_task(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(spawn_in_terminal) =
|
if let Some(spawn_in_terminal) =
|
||||||
task_to_resolve.resolve_task(&task_source_kind.to_id_base(), task_target, task_cx)
|
task_to_resolve.resolve_task(&task_source_kind.to_id_base(), task_cx)
|
||||||
{
|
{
|
||||||
schedule_resolved_task(
|
schedule_resolved_task(
|
||||||
workspace,
|
workspace,
|
||||||
|
@ -48,7 +46,6 @@ pub fn schedule_resolved_task(
|
||||||
omit_history: bool,
|
omit_history: bool,
|
||||||
cx: &mut ViewContext<'_, Workspace>,
|
cx: &mut ViewContext<'_, Workspace>,
|
||||||
) {
|
) {
|
||||||
let target = resolved_task.target;
|
|
||||||
if let Some(spawn_in_terminal) = resolved_task.resolved.take() {
|
if let Some(spawn_in_terminal) = resolved_task.resolved.take() {
|
||||||
if !omit_history {
|
if !omit_history {
|
||||||
resolved_task.resolved = Some(spawn_in_terminal.clone());
|
resolved_task.resolved = Some(spawn_in_terminal.clone());
|
||||||
|
@ -63,17 +60,8 @@ pub fn schedule_resolved_task(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
match target {
|
|
||||||
TaskSpawnTarget::Center => {
|
|
||||||
cx.dispatch_action(Box::new(NewCenterTask {
|
|
||||||
action: spawn_in_terminal,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
TaskSpawnTarget::Dock => {
|
|
||||||
cx.emit(crate::Event::SpawnTask {
|
cx.emit(crate::Event::SpawnTask {
|
||||||
action: Box::new(spawn_in_terminal),
|
action: Box::new(spawn_in_terminal),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,4 +10,5 @@ workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
|
schemars.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use gpui::{actions, impl_actions};
|
use gpui::{actions, impl_actions};
|
||||||
use serde::Deserialize;
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
// If the zed binary doesn't use anything in this crate, it will be optimized away
|
// If the zed binary doesn't use anything in this crate, it will be optimized away
|
||||||
// and the actions won't initialize. So we just provide an empty initialization function
|
// and the actions won't initialize. So we just provide an empty initialization function
|
||||||
|
@ -90,33 +91,39 @@ pub struct OpenRecent {
|
||||||
gpui::impl_actions!(projects, [OpenRecent]);
|
gpui::impl_actions!(projects, [OpenRecent]);
|
||||||
gpui::actions!(projects, [OpenRemote]);
|
gpui::actions!(projects, [OpenRemote]);
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, Deserialize, Default, Debug)]
|
/// Where to spawn the task in the UI.
|
||||||
|
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum TaskSpawnTarget {
|
pub enum RevealTarget {
|
||||||
|
/// In the central pane group, "main" editor area.
|
||||||
Center,
|
Center,
|
||||||
|
/// In the terminal dock, "regular" terminal items' place.
|
||||||
#[default]
|
#[default]
|
||||||
Dock,
|
Dock,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawn a task with name or open tasks modal
|
/// Spawn a task with name or open tasks modal
|
||||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
#[derive(Debug, PartialEq, Clone, Deserialize)]
|
||||||
pub struct Spawn {
|
#[serde(untagged)]
|
||||||
|
pub enum Spawn {
|
||||||
|
/// Spawns a task by the name given.
|
||||||
|
ByName {
|
||||||
|
task_name: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
/// Name of the task to spawn.
|
reveal_target: Option<RevealTarget>,
|
||||||
/// If it is not set, a modal with a list of available tasks is opened instead.
|
},
|
||||||
/// Defaults to None.
|
/// Spawns a task via modal's selection.
|
||||||
pub task_name: Option<String>,
|
ViaModal {
|
||||||
/// Which part of the UI the task should be spawned in.
|
/// Selected task's `reveal_target` property override.
|
||||||
/// Defaults to Dock.
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub target: Option<TaskSpawnTarget>,
|
reveal_target: Option<RevealTarget>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Spawn {
|
impl Spawn {
|
||||||
pub fn modal() -> Self {
|
pub fn modal() -> Self {
|
||||||
Self {
|
Self::ViaModal {
|
||||||
task_name: None,
|
reveal_target: None,
|
||||||
target: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,9 @@ Zed supports ways to spawn (and rerun) commands using its integrated terminal to
|
||||||
// Whether to allow multiple instances of the same task to be run, or rather wait for the existing ones to finish, defaults to `false`.
|
// Whether to allow multiple instances of the same task to be run, or rather wait for the existing ones to finish, defaults to `false`.
|
||||||
"allow_concurrent_runs": false,
|
"allow_concurrent_runs": false,
|
||||||
// What to do with the terminal pane and tab, after the command was started:
|
// What to do with the terminal pane and tab, after the command was started:
|
||||||
// * `always` — always show the terminal pane, add and focus the corresponding task's tab in it (default)
|
// * `always` — always show the task's pane, and focus the corresponding tab in it (default)
|
||||||
// * `no_focus` — always show the terminal pane, add/reuse the task's tab there, but don't focus it
|
// * `no_focus` — always show the task's pane, add the task's tab in it, but don't focus it
|
||||||
// * `never` — avoid changing current terminal pane focus, but still add/reuse the task's tab there
|
// * `never` — do not alter focus, but still add/reuse the task's tab in its pane
|
||||||
"reveal": "always",
|
"reveal": "always",
|
||||||
// What to do with the terminal pane and tab, after the command had finished:
|
// What to do with the terminal pane and tab, after the command had finished:
|
||||||
// * `never` — Do nothing when the command finishes (default)
|
// * `never` — Do nothing when the command finishes (default)
|
||||||
|
|
Loading…
Reference in a new issue