Rework task modal (#10341)
New list (used tasks are above the separator line, sorted by the usage recency), then all language tasks, then project-local and global tasks are listed. Note that there are two test tasks (for `test_name_1` and `test_name_2` functions) that are created from the same task template: <img width="563" alt="Screenshot 2024-04-10 at 01 00 46" src="https://github.com/zed-industries/zed/assets/2690773/7455a82f-2af2-47bf-99bd-d9c5a36e64ab"> Tasks are deduplicated by labels, with the used tasks left in case of the conflict with the new tasks from the template: <img width="555" alt="Screenshot 2024-04-10 at 01 01 06" src="https://github.com/zed-industries/zed/assets/2690773/8f5a249e-abec-46ef-a991-08c6d0348648"> Regular recent tasks can be now removed too: <img width="565" alt="Screenshot 2024-04-10 at 01 00 55" src="https://github.com/zed-industries/zed/assets/2690773/0976b8fe-b5d7-4d2a-953d-1d8b1f216192"> When the caret is in the place where no function symbol could be retrieved, no cargo tests for function are listed in tasks: <img width="556" alt="image" src="https://github.com/zed-industries/zed/assets/2690773/df30feba-fe27-4645-8be9-02afc70f02da"> Part of https://github.com/zed-industries/zed/issues/10132 Reworks the task code to simplify it and enable proper task labels. * removes `trait Task`, renames `Definition` into `TaskTemplate` and use that instead of `Arc<dyn Task>` everywhere * implement more generic `TaskId` generation that depends on the `TaskContext` and `TaskTemplate` * remove `TaskId` out of the template and only create it after "resolving" the template into the `ResolvedTask`: this way, task templates, task state (`TaskContext`) and task "result" (resolved state) are clearly separated and are not mixed * implement the logic for filtering out non-related language tasks and tasks that have non-resolved Zed task variables * rework Zed template-vs-resolved-task display in modal: now all reruns and recently used tasks are resolved tasks with "fixed" context (unless configured otherwise in the task json) that are always shown, and Zed can add on top tasks with different context that are derived from the same template as the used, resolved tasks * sort the tasks list better, showing more specific and least recently used tasks higher * shows a separator between used and unused tasks, allow removing the used tasks same as the oneshot ones * remote the Oneshot task source as redundant: all oneshot tasks are now stored in the inventory's history * when reusing the tasks as query in the modal, paste the expanded task label now, show trimmed resolved label in the modal * adjusts Rust and Elixir task labels to be more descriptive and closer to bash scripts Release Notes: - Improved task modal ordering, run and deletion capabilities
This commit is contained in:
parent
b0eda77d73
commit
d1ad96782c
21 changed files with 1103 additions and 671 deletions
|
@ -1,154 +1,20 @@
|
|||
//! A source of tasks, based on a static configuration, deserialized from the tasks config file, and related infrastructure for tracking changes to the file.
|
||||
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
use collections::HashMap;
|
||||
use futures::StreamExt;
|
||||
use gpui::{AppContext, Context, Model, ModelContext, Subscription};
|
||||
use schemars::{gen::SchemaSettings, JsonSchema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::{SpawnInTerminal, Task, TaskContext, TaskId, TaskSource};
|
||||
use crate::{TaskSource, TaskTemplates};
|
||||
use futures::channel::mpsc::UnboundedReceiver;
|
||||
|
||||
/// A single config file entry with the deserialized task definition.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct StaticTask {
|
||||
id: TaskId,
|
||||
definition: Definition,
|
||||
}
|
||||
|
||||
impl StaticTask {
|
||||
fn new(definition: Definition, (id_base, index_in_file): (&str, usize)) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
id: TaskId(format!(
|
||||
"static_{id_base}_{index_in_file}_{}",
|
||||
definition.label
|
||||
)),
|
||||
definition,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO: doc
|
||||
pub fn tasks_for(tasks: TaskDefinitions, id_base: &str) -> Vec<Arc<dyn Task>> {
|
||||
tasks
|
||||
.0
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, task)| StaticTask::new(task, (id_base, index)) as Arc<_>)
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl Task for StaticTask {
|
||||
fn prepare_exec(&self, cx: TaskContext) -> Option<SpawnInTerminal> {
|
||||
let TaskContext {
|
||||
cwd,
|
||||
task_variables,
|
||||
} = cx;
|
||||
let task_variables = task_variables.into_env_variables();
|
||||
let cwd = self
|
||||
.definition
|
||||
.cwd
|
||||
.clone()
|
||||
.and_then(|path| {
|
||||
subst::substitute(&path, &task_variables)
|
||||
.map(Into::into)
|
||||
.ok()
|
||||
})
|
||||
.or(cwd);
|
||||
let mut definition_env = self.definition.env.clone();
|
||||
definition_env.extend(task_variables);
|
||||
Some(SpawnInTerminal {
|
||||
id: self.id.clone(),
|
||||
cwd,
|
||||
use_new_terminal: self.definition.use_new_terminal,
|
||||
allow_concurrent_runs: self.definition.allow_concurrent_runs,
|
||||
label: self.definition.label.clone(),
|
||||
command: self.definition.command.clone(),
|
||||
args: self.definition.args.clone(),
|
||||
reveal: self.definition.reveal,
|
||||
env: definition_env,
|
||||
})
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.definition.label
|
||||
}
|
||||
|
||||
fn id(&self) -> &TaskId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn cwd(&self) -> Option<&str> {
|
||||
self.definition.cwd.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
/// The source of tasks defined in a tasks config file.
|
||||
pub struct StaticSource {
|
||||
tasks: Vec<Arc<StaticTask>>,
|
||||
_definitions: Model<TrackedFile<TaskDefinitions>>,
|
||||
tasks: TaskTemplates,
|
||||
_templates: Model<TrackedFile<TaskTemplates>>,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
/// Static task definition from the tasks config file.
|
||||
#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct Definition {
|
||||
/// Human readable name of the task to display in the UI.
|
||||
pub label: String,
|
||||
/// Executable command to spawn.
|
||||
pub command: String,
|
||||
/// Arguments to the command.
|
||||
#[serde(default)]
|
||||
pub args: Vec<String>,
|
||||
/// Env overrides for the command, will be appended to the terminal's environment from the settings.
|
||||
#[serde(default)]
|
||||
pub env: HashMap<String, String>,
|
||||
/// Current working directory to spawn the command into, defaults to current project root.
|
||||
#[serde(default)]
|
||||
pub cwd: Option<String>,
|
||||
/// Whether to use a new terminal tab or reuse the existing one to spawn the process.
|
||||
#[serde(default)]
|
||||
pub use_new_terminal: bool,
|
||||
/// Whether to allow multiple instances of the same task to be run, or rather wait for the existing ones to finish.
|
||||
#[serde(default)]
|
||||
pub allow_concurrent_runs: bool,
|
||||
/// 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)
|
||||
/// * `never` — avoid changing current terminal pane focus, but still add/reuse the task's tab there
|
||||
#[serde(default)]
|
||||
pub reveal: RevealStrategy,
|
||||
}
|
||||
|
||||
/// What to do with the terminal pane and tab, after the command was started.
|
||||
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RevealStrategy {
|
||||
/// Always show the terminal pane, add and focus the corresponding task's tab in it.
|
||||
#[default]
|
||||
Always,
|
||||
/// Do not change terminal pane focus, but still add/reuse the task's tab there.
|
||||
Never,
|
||||
}
|
||||
|
||||
/// A group of Tasks defined in a JSON file.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct TaskDefinitions(pub Vec<Definition>);
|
||||
|
||||
impl TaskDefinitions {
|
||||
/// Generates JSON schema of Tasks JSON definition format.
|
||||
pub fn generate_json_schema() -> serde_json_lenient::Value {
|
||||
let schema = SchemaSettings::draft07()
|
||||
.with(|settings| settings.option_add_null_type = false)
|
||||
.into_generator()
|
||||
.into_root_schema_for::<Self>();
|
||||
|
||||
serde_json_lenient::to_value(schema).unwrap()
|
||||
}
|
||||
}
|
||||
/// A Wrapper around deserializable T that keeps track of its contents
|
||||
/// via a provided channel. Once T value changes, the observers of [`TrackedFile`] are
|
||||
/// notified.
|
||||
|
@ -235,32 +101,22 @@ impl<T: PartialEq + 'static> TrackedFile<T> {
|
|||
impl StaticSource {
|
||||
/// Initializes the static source, reacting on tasks config changes.
|
||||
pub fn new(
|
||||
id_base: impl Into<Cow<'static, str>>,
|
||||
definitions: Model<TrackedFile<TaskDefinitions>>,
|
||||
templates: Model<TrackedFile<TaskTemplates>>,
|
||||
cx: &mut AppContext,
|
||||
) -> Model<Box<dyn TaskSource>> {
|
||||
cx.new_model(|cx| {
|
||||
let id_base = id_base.into();
|
||||
let _subscription = cx.observe(
|
||||
&definitions,
|
||||
move |source: &mut Box<(dyn TaskSource + 'static)>, new_definitions, cx| {
|
||||
&templates,
|
||||
move |source: &mut Box<(dyn TaskSource + 'static)>, new_templates, cx| {
|
||||
if let Some(static_source) = source.as_any().downcast_mut::<Self>() {
|
||||
static_source.tasks = new_definitions
|
||||
.read(cx)
|
||||
.get()
|
||||
.0
|
||||
.clone()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, definition)| StaticTask::new(definition, (&id_base, i)))
|
||||
.collect();
|
||||
static_source.tasks = new_templates.read(cx).get().clone();
|
||||
cx.notify();
|
||||
}
|
||||
},
|
||||
);
|
||||
Box::new(Self {
|
||||
tasks: Vec::new(),
|
||||
_definitions: definitions,
|
||||
tasks: TaskTemplates::default(),
|
||||
_templates: templates,
|
||||
_subscription,
|
||||
})
|
||||
})
|
||||
|
@ -268,14 +124,8 @@ impl StaticSource {
|
|||
}
|
||||
|
||||
impl TaskSource for StaticSource {
|
||||
fn tasks_to_schedule(
|
||||
&mut self,
|
||||
_: &mut ModelContext<Box<dyn TaskSource>>,
|
||||
) -> Vec<Arc<dyn Task>> {
|
||||
self.tasks
|
||||
.iter()
|
||||
.map(|task| task.clone() as Arc<dyn Task>)
|
||||
.collect()
|
||||
fn tasks_to_schedule(&mut self, _: &mut ModelContext<Box<dyn TaskSource>>) -> TaskTemplates {
|
||||
self.tasks.clone()
|
||||
}
|
||||
|
||||
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue