Extensions registering tasks (#9572)
This PR also introduces built-in tasks for Rust and Elixir. Note that this is not a precedent for future PRs to include tasks for more languages; we simply want to find the rough edges with tasks & language integrations before proceeding to task contexts provided by extensions. As is, we'll load tasks for all loaded languages, so in order to get Elixir tasks, you have to open an Elixir buffer first. I think it sort of makes sense (though it's not ideal), as in the future where extensions do provide their own tasks.json, we'd like to limit the # of tasks surfaced to the user to make them as relevant to the project at hand as possible. Release Notes: - Added built-in tasks for Rust and Elixir files.
This commit is contained in:
parent
cb4f868815
commit
4dc61f7ccd
19 changed files with 416 additions and 169 deletions
|
@ -41,13 +41,26 @@ pub struct SpawnInTerminal {
|
|||
pub reveal: RevealStrategy,
|
||||
}
|
||||
|
||||
type VariableName = String;
|
||||
type VariableValue = String;
|
||||
|
||||
/// Container for predefined environment variables that describe state of Zed at the time the task was spawned.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct TaskVariables(pub HashMap<VariableName, VariableValue>);
|
||||
|
||||
impl FromIterator<(String, String)> for TaskVariables {
|
||||
fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
|
||||
Self(HashMap::from_iter(iter))
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps track of the file associated with a task and context of tasks execution (i.e. current file or current function)
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct TaskContext {
|
||||
/// A path to a directory in which the task should be executed.
|
||||
pub cwd: Option<PathBuf>,
|
||||
/// Additional environment variables associated with a given task.
|
||||
pub env: HashMap<String, String>,
|
||||
pub task_variables: TaskVariables,
|
||||
}
|
||||
|
||||
/// Represents a short lived recipe of a task, whose main purpose
|
||||
|
|
|
@ -40,14 +40,17 @@ impl Task for OneshotTask {
|
|||
if self.id().0.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let TaskContext { cwd, env } = cx;
|
||||
let TaskContext {
|
||||
cwd,
|
||||
task_variables,
|
||||
} = cx;
|
||||
Some(SpawnInTerminal {
|
||||
id: self.id().clone(),
|
||||
label: self.name().to_owned(),
|
||||
command: self.id().0.clone(),
|
||||
args: vec![],
|
||||
cwd,
|
||||
env,
|
||||
env: task_variables.0,
|
||||
use_new_terminal: Default::default(),
|
||||
allow_concurrent_runs: Default::default(),
|
||||
reveal: RevealStrategy::default(),
|
||||
|
|
|
@ -19,17 +19,46 @@ struct StaticTask {
|
|||
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 exec(&self, cx: TaskContext) -> Option<SpawnInTerminal> {
|
||||
let TaskContext { cwd, env } = cx;
|
||||
let TaskContext {
|
||||
cwd,
|
||||
task_variables,
|
||||
} = cx;
|
||||
let cwd = self
|
||||
.definition
|
||||
.cwd
|
||||
.clone()
|
||||
.and_then(|path| subst::substitute(&path, &env).map(Into::into).ok())
|
||||
.and_then(|path| {
|
||||
subst::substitute(&path, &task_variables.0)
|
||||
.map(Into::into)
|
||||
.ok()
|
||||
})
|
||||
.or(cwd);
|
||||
let mut definition_env = self.definition.env.clone();
|
||||
definition_env.extend(env);
|
||||
definition_env.extend(task_variables.0);
|
||||
Some(SpawnInTerminal {
|
||||
id: self.id.clone(),
|
||||
cwd,
|
||||
|
@ -58,15 +87,15 @@ impl Task for StaticTask {
|
|||
|
||||
/// The source of tasks defined in a tasks config file.
|
||||
pub struct StaticSource {
|
||||
tasks: Vec<StaticTask>,
|
||||
_definitions: Model<TrackedFile<DefinitionProvider>>,
|
||||
tasks: Vec<Arc<StaticTask>>,
|
||||
_definitions: Model<TrackedFile<TaskDefinitions>>,
|
||||
_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(crate) struct Definition {
|
||||
pub struct Definition {
|
||||
/// Human readable name of the task to display in the UI.
|
||||
pub label: String,
|
||||
/// Executable command to spawn.
|
||||
|
@ -106,9 +135,9 @@ pub enum RevealStrategy {
|
|||
|
||||
/// A group of Tasks defined in a JSON file.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DefinitionProvider(pub(crate) Vec<Definition>);
|
||||
pub struct TaskDefinitions(pub Vec<Definition>);
|
||||
|
||||
impl DefinitionProvider {
|
||||
impl TaskDefinitions {
|
||||
/// Generates JSON schema of Tasks JSON definition format.
|
||||
pub fn generate_json_schema() -> serde_json_lenient::Value {
|
||||
let schema = SchemaSettings::draft07()
|
||||
|
@ -206,7 +235,7 @@ impl StaticSource {
|
|||
/// Initializes the static source, reacting on tasks config changes.
|
||||
pub fn new(
|
||||
id_base: impl Into<Cow<'static, str>>,
|
||||
definitions: Model<TrackedFile<DefinitionProvider>>,
|
||||
definitions: Model<TrackedFile<TaskDefinitions>>,
|
||||
cx: &mut AppContext,
|
||||
) -> Model<Box<dyn TaskSource>> {
|
||||
cx.new_model(|cx| {
|
||||
|
@ -222,10 +251,7 @@ impl StaticSource {
|
|||
.clone()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, definition)| StaticTask {
|
||||
id: TaskId(format!("static_{id_base}_{i}_{}", definition.label)),
|
||||
definition,
|
||||
})
|
||||
.map(|(i, definition)| StaticTask::new(definition, (&id_base, i)))
|
||||
.collect();
|
||||
cx.notify();
|
||||
}
|
||||
|
@ -247,9 +273,8 @@ impl TaskSource for StaticSource {
|
|||
_: &mut ModelContext<Box<dyn TaskSource>>,
|
||||
) -> Vec<Arc<dyn Task>> {
|
||||
self.tasks
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|task| Arc::new(task) as Arc<dyn Task>)
|
||||
.iter()
|
||||
.map(|task| task.clone() as Arc<dyn Task>)
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use collections::HashMap;
|
|||
use serde::Deserialize;
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::static_source::{Definition, DefinitionProvider};
|
||||
use crate::static_source::{Definition, TaskDefinitions};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -124,7 +124,7 @@ pub struct VsCodeTaskFile {
|
|||
tasks: Vec<VsCodeTaskDefinition>,
|
||||
}
|
||||
|
||||
impl TryFrom<VsCodeTaskFile> for DefinitionProvider {
|
||||
impl TryFrom<VsCodeTaskFile> for TaskDefinitions {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: VsCodeTaskFile) -> Result<Self, Self::Error> {
|
||||
|
@ -148,7 +148,7 @@ mod tests {
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
static_source::{Definition, DefinitionProvider},
|
||||
static_source::{Definition, TaskDefinitions},
|
||||
vscode_format::{Command, VsCodeTaskDefinition},
|
||||
VsCodeTaskFile,
|
||||
};
|
||||
|
@ -279,7 +279,7 @@ mod tests {
|
|||
},
|
||||
];
|
||||
|
||||
let tasks: DefinitionProvider = vscode_definitions.try_into().unwrap();
|
||||
let tasks: TaskDefinitions = vscode_definitions.try_into().unwrap();
|
||||
assert_eq!(tasks.0, expected);
|
||||
}
|
||||
|
||||
|
@ -380,7 +380,7 @@ mod tests {
|
|||
..Default::default()
|
||||
},
|
||||
];
|
||||
let tasks: DefinitionProvider = vscode_definitions.try_into().unwrap();
|
||||
let tasks: TaskDefinitions = vscode_definitions.try_into().unwrap();
|
||||
assert_eq!(tasks.0, expected);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue