Stricten Zed Task variable API (#10163)
Introduce `VariableName` enum to simplify Zed task templating management: now all the variables can be looked up statically and can be checked/modified in a centralized way: e.g. `ZED_` prefix is now added for all such custom vars. Release Notes: - N/A
This commit is contained in:
parent
ee1b1779f1
commit
1085642c88
10 changed files with 188 additions and 91 deletions
|
@ -9,6 +9,7 @@ use collections::HashMap;
|
|||
use gpui::ModelContext;
|
||||
use static_source::RevealStrategy;
|
||||
use std::any::Any;
|
||||
use std::borrow::Cow;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
pub use vscode_format::VsCodeTaskFile;
|
||||
|
@ -41,15 +42,78 @@ pub struct SpawnInTerminal {
|
|||
pub reveal: RevealStrategy,
|
||||
}
|
||||
|
||||
type VariableName = String;
|
||||
type VariableValue = String;
|
||||
/// Variables, available for use in [`TaskContext`] when a Zed's task gets turned into real command.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum VariableName {
|
||||
/// An absolute path of the currently opened file.
|
||||
File,
|
||||
/// An absolute path of the currently opened worktree, that contains the file.
|
||||
WorktreeRoot,
|
||||
/// A symbol text, that contains latest cursor/selection position.
|
||||
Symbol,
|
||||
/// A row with the latest cursor/selection position.
|
||||
Row,
|
||||
/// A column with the latest cursor/selection position.
|
||||
Column,
|
||||
/// Text from the latest selection.
|
||||
SelectedText,
|
||||
/// Custom variable, provided by the plugin or other external source.
|
||||
/// Will be printed with `ZED_` prefix to avoid potential conflicts with other variables.
|
||||
Custom(Cow<'static, str>),
|
||||
}
|
||||
|
||||
impl VariableName {
|
||||
/// Generates a `$VARIABLE`-like string value to be used in templates.
|
||||
/// Custom variables are wrapped in `${}` to avoid substitution issues with whitespaces.
|
||||
pub fn template_value(&self) -> String {
|
||||
if matches!(self, Self::Custom(_)) {
|
||||
format!("${{{self}}}")
|
||||
} else {
|
||||
format!("${self}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VariableName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::File => write!(f, "ZED_FILE"),
|
||||
Self::WorktreeRoot => write!(f, "ZED_WORKTREE_ROOT"),
|
||||
Self::Symbol => write!(f, "ZED_SYMBOL"),
|
||||
Self::Row => write!(f, "ZED_ROW"),
|
||||
Self::Column => write!(f, "ZED_COLUMN"),
|
||||
Self::SelectedText => write!(f, "ZED_SELECTED_TEXT"),
|
||||
Self::Custom(s) => write!(f, "ZED_{s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>);
|
||||
pub struct TaskVariables(HashMap<VariableName, String>);
|
||||
|
||||
impl FromIterator<(String, String)> for TaskVariables {
|
||||
fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
|
||||
impl TaskVariables {
|
||||
/// Converts the container into a map of environment variables and their values.
|
||||
fn into_env_variables(self) -> HashMap<String, String> {
|
||||
self.0
|
||||
.into_iter()
|
||||
.map(|(name, value)| (name.to_string(), value))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Inserts another variable into the container, overwriting the existing one if it already exists — in this case, the old value is returned.
|
||||
pub fn insert(&mut self, variable: VariableName, value: String) -> Option<String> {
|
||||
self.0.insert(variable, value)
|
||||
}
|
||||
|
||||
/// Extends the container with another one, overwriting the existing variables on collision.
|
||||
pub fn extend(&mut self, other: Self) {
|
||||
self.0.extend(other.0);
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(VariableName, String)> for TaskVariables {
|
||||
fn from_iter<T: IntoIterator<Item = (VariableName, String)>>(iter: T) -> Self {
|
||||
Self(HashMap::from_iter(iter))
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +138,7 @@ pub trait Task {
|
|||
fn cwd(&self) -> Option<&str>;
|
||||
/// Sets up everything needed to spawn the task in the given directory (`cwd`).
|
||||
/// If a task is intended to be spawned in the terminal, it should return the corresponding struct filled with the data necessary.
|
||||
fn exec(&self, cx: TaskContext) -> Option<SpawnInTerminal>;
|
||||
fn prepare_exec(&self, cx: TaskContext) -> Option<SpawnInTerminal>;
|
||||
}
|
||||
|
||||
/// [`Source`] produces tasks that can be scheduled.
|
||||
|
|
|
@ -36,7 +36,7 @@ impl Task for OneshotTask {
|
|||
None
|
||||
}
|
||||
|
||||
fn exec(&self, cx: TaskContext) -> Option<SpawnInTerminal> {
|
||||
fn prepare_exec(&self, cx: TaskContext) -> Option<SpawnInTerminal> {
|
||||
if self.id().0.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ impl Task for OneshotTask {
|
|||
command: self.id().0.clone(),
|
||||
args: vec![],
|
||||
cwd,
|
||||
env: task_variables.0,
|
||||
env: task_variables.into_env_variables(),
|
||||
use_new_terminal: Default::default(),
|
||||
allow_concurrent_runs: Default::default(),
|
||||
reveal: RevealStrategy::default(),
|
||||
|
|
|
@ -42,23 +42,24 @@ pub fn tasks_for(tasks: TaskDefinitions, id_base: &str) -> Vec<Arc<dyn Task>> {
|
|||
}
|
||||
|
||||
impl Task for StaticTask {
|
||||
fn exec(&self, cx: TaskContext) -> Option<SpawnInTerminal> {
|
||||
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.0)
|
||||
subst::substitute(&path, &task_variables)
|
||||
.map(Into::into)
|
||||
.ok()
|
||||
})
|
||||
.or(cwd);
|
||||
let mut definition_env = self.definition.env.clone();
|
||||
definition_env.extend(task_variables.0);
|
||||
definition_env.extend(task_variables);
|
||||
Some(SpawnInTerminal {
|
||||
id: self.id.clone(),
|
||||
cwd,
|
||||
|
|
|
@ -3,7 +3,10 @@ use collections::HashMap;
|
|||
use serde::Deserialize;
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::static_source::{Definition, TaskDefinitions};
|
||||
use crate::{
|
||||
static_source::{Definition, TaskDefinitions},
|
||||
VariableName,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -129,10 +132,16 @@ impl TryFrom<VsCodeTaskFile> for TaskDefinitions {
|
|||
|
||||
fn try_from(value: VsCodeTaskFile) -> Result<Self, Self::Error> {
|
||||
let replacer = EnvVariableReplacer::new(HashMap::from_iter([
|
||||
("workspaceFolder".to_owned(), "ZED_WORKTREE_ROOT".to_owned()),
|
||||
("file".to_owned(), "ZED_FILE".to_owned()),
|
||||
("lineNumber".to_owned(), "ZED_ROW".to_owned()),
|
||||
("selectedText".to_owned(), "ZED_SELECTED_TEXT".to_owned()),
|
||||
(
|
||||
"workspaceFolder".to_owned(),
|
||||
VariableName::WorktreeRoot.to_string(),
|
||||
),
|
||||
("file".to_owned(), VariableName::File.to_string()),
|
||||
("lineNumber".to_owned(), VariableName::Row.to_string()),
|
||||
(
|
||||
"selectedText".to_owned(),
|
||||
VariableName::SelectedText.to_string(),
|
||||
),
|
||||
]));
|
||||
let definitions = value
|
||||
.tasks
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue