VS Code -> Zed tasks converter (#9538)
We can convert shell, npm and gulp tasks to a Zed format. Additionally, we convert a subset of task variables that VsCode supports. Release notes: - Zed can now load tasks in Visual Studio Code task format --------- Co-authored-by: Piotr Osiewicz <piotr@zed.dev> Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
This commit is contained in:
parent
269d2513ca
commit
88857f8149
11 changed files with 605 additions and 17 deletions
0
.zed/tasks.json
Normal file
0
.zed/tasks.json
Normal file
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -9410,6 +9410,7 @@ dependencies = [
|
|||
"schemars",
|
||||
"serde",
|
||||
"serde_json_lenient",
|
||||
"shellexpand",
|
||||
"subst",
|
||||
"util",
|
||||
]
|
||||
|
|
|
@ -87,14 +87,16 @@ use std::{
|
|||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use task::static_source::StaticSource;
|
||||
use task::static_source::{StaticSource, TrackedFile};
|
||||
use terminals::Terminals;
|
||||
use text::{Anchor, BufferId};
|
||||
use util::{
|
||||
debug_panic, defer,
|
||||
http::HttpClient,
|
||||
merge_json_value_into,
|
||||
paths::{LOCAL_SETTINGS_RELATIVE_PATH, LOCAL_TASKS_RELATIVE_PATH},
|
||||
paths::{
|
||||
LOCAL_SETTINGS_RELATIVE_PATH, LOCAL_TASKS_RELATIVE_PATH, LOCAL_VSCODE_TASKS_RELATIVE_PATH,
|
||||
},
|
||||
post_inc, ResultExt, TryFutureExt as _,
|
||||
};
|
||||
use worktree::{Snapshot, Traversal};
|
||||
|
@ -7108,7 +7110,37 @@ impl Project {
|
|||
watch_config_file(&cx.background_executor(), fs, task_abs_path);
|
||||
StaticSource::new(
|
||||
format!("local_tasks_for_workspace_{remote_worktree_id}"),
|
||||
tasks_file_rx,
|
||||
TrackedFile::new(tasks_file_rx, cx),
|
||||
cx,
|
||||
)
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})
|
||||
} else if abs_path.ends_with(&*LOCAL_VSCODE_TASKS_RELATIVE_PATH) {
|
||||
self.task_inventory().update(cx, |task_inventory, cx| {
|
||||
if removed {
|
||||
task_inventory.remove_local_static_source(&abs_path);
|
||||
} else {
|
||||
let fs = self.fs.clone();
|
||||
let task_abs_path = abs_path.clone();
|
||||
task_inventory.add_source(
|
||||
TaskSourceKind::Worktree {
|
||||
id: remote_worktree_id,
|
||||
abs_path,
|
||||
},
|
||||
|cx| {
|
||||
let tasks_file_rx =
|
||||
watch_config_file(&cx.background_executor(), fs, task_abs_path);
|
||||
StaticSource::new(
|
||||
format!(
|
||||
"local_vscode_tasks_for_workspace_{remote_worktree_id}"
|
||||
),
|
||||
TrackedFile::new_convertible::<task::VsCodeTaskFile>(
|
||||
tasks_file_rx,
|
||||
cx,
|
||||
),
|
||||
cx,
|
||||
)
|
||||
},
|
||||
|
|
|
@ -16,6 +16,7 @@ gpui.workspace = true
|
|||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
shellexpand.workspace = true
|
||||
subst = "0.3.0"
|
||||
util.workspace = true
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
pub mod oneshot_source;
|
||||
pub mod static_source;
|
||||
mod vscode_format;
|
||||
|
||||
use collections::HashMap;
|
||||
use gpui::ModelContext;
|
||||
|
@ -10,6 +11,7 @@ use static_source::RevealStrategy;
|
|||
use std::any::Any;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
pub use vscode_format::VsCodeTaskFile;
|
||||
|
||||
/// Task identifier, unique within the application.
|
||||
/// Based on it, task reruns and terminal tabs are managed.
|
||||
|
|
|
@ -64,7 +64,7 @@ pub struct StaticSource {
|
|||
}
|
||||
|
||||
/// Static task definition from the tasks config file.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub(crate) struct Definition {
|
||||
/// Human readable name of the task to display in the UI.
|
||||
|
@ -106,7 +106,7 @@ pub enum RevealStrategy {
|
|||
|
||||
/// A group of Tasks defined in a JSON file.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DefinitionProvider(Vec<Definition>);
|
||||
pub struct DefinitionProvider(pub(crate) Vec<Definition>);
|
||||
|
||||
impl DefinitionProvider {
|
||||
/// Generates JSON schema of Tasks JSON definition format.
|
||||
|
@ -122,20 +122,22 @@ impl DefinitionProvider {
|
|||
/// 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.
|
||||
struct TrackedFile<T> {
|
||||
pub struct TrackedFile<T> {
|
||||
parsed_contents: T,
|
||||
}
|
||||
|
||||
impl<T: for<'a> Deserialize<'a> + PartialEq + 'static> TrackedFile<T> {
|
||||
fn new(
|
||||
parsed_contents: T,
|
||||
mut tracker: UnboundedReceiver<String>,
|
||||
cx: &mut AppContext,
|
||||
) -> Model<Self> {
|
||||
impl<T: PartialEq + 'static> TrackedFile<T> {
|
||||
/// Initializes new [`TrackedFile`] with a type that's deserializable.
|
||||
pub fn new(mut tracker: UnboundedReceiver<String>, cx: &mut AppContext) -> Model<Self>
|
||||
where
|
||||
T: for<'a> Deserialize<'a> + Default,
|
||||
{
|
||||
cx.new_model(move |cx| {
|
||||
cx.spawn(|tracked_file, mut cx| async move {
|
||||
while let Some(new_contents) = tracker.next().await {
|
||||
if !new_contents.trim().is_empty() {
|
||||
// String -> T (ZedTaskFormat)
|
||||
// String -> U (VsCodeFormat) -> Into::into T
|
||||
let Some(new_contents) =
|
||||
serde_json_lenient::from_str(&new_contents).log_err()
|
||||
else {
|
||||
|
@ -152,7 +154,46 @@ impl<T: for<'a> Deserialize<'a> + PartialEq + 'static> TrackedFile<T> {
|
|||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
Self { parsed_contents }
|
||||
Self {
|
||||
parsed_contents: Default::default(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Initializes new [`TrackedFile`] with a type that's convertible from another deserializable type.
|
||||
pub fn new_convertible<U: for<'a> Deserialize<'a> + TryInto<T, Error = anyhow::Error>>(
|
||||
mut tracker: UnboundedReceiver<String>,
|
||||
cx: &mut AppContext,
|
||||
) -> Model<Self>
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
cx.new_model(move |cx| {
|
||||
cx.spawn(|tracked_file, mut cx| async move {
|
||||
while let Some(new_contents) = tracker.next().await {
|
||||
if !new_contents.trim().is_empty() {
|
||||
let Some(new_contents) =
|
||||
serde_json_lenient::from_str::<U>(&new_contents).log_err()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(new_contents) = new_contents.try_into().log_err() else {
|
||||
continue;
|
||||
};
|
||||
tracked_file.update(&mut cx, |tracked_file: &mut TrackedFile<T>, cx| {
|
||||
if tracked_file.parsed_contents != new_contents {
|
||||
tracked_file.parsed_contents = new_contents;
|
||||
cx.notify();
|
||||
};
|
||||
})?;
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
Self {
|
||||
parsed_contents: Default::default(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -165,10 +206,9 @@ impl StaticSource {
|
|||
/// Initializes the static source, reacting on tasks config changes.
|
||||
pub fn new(
|
||||
id_base: impl Into<Cow<'static, str>>,
|
||||
tasks_file_tracker: UnboundedReceiver<String>,
|
||||
definitions: Model<TrackedFile<DefinitionProvider>>,
|
||||
cx: &mut AppContext,
|
||||
) -> Model<Box<dyn TaskSource>> {
|
||||
let definitions = TrackedFile::new(DefinitionProvider::default(), tasks_file_tracker, cx);
|
||||
cx.new_model(|cx| {
|
||||
let id_base = id_base.into();
|
||||
let _subscription = cx.observe(
|
||||
|
|
386
crates/task/src/vscode_format.rs
Normal file
386
crates/task/src/vscode_format.rs
Normal file
|
@ -0,0 +1,386 @@
|
|||
use anyhow::bail;
|
||||
use collections::HashMap;
|
||||
use serde::Deserialize;
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::static_source::{Definition, DefinitionProvider};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct TaskOptions {
|
||||
cwd: Option<String>,
|
||||
#[serde(default)]
|
||||
env: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct VsCodeTaskDefinition {
|
||||
label: String,
|
||||
#[serde(flatten)]
|
||||
command: Option<Command>,
|
||||
#[serde(flatten)]
|
||||
other_attributes: HashMap<String, serde_json_lenient::Value>,
|
||||
options: Option<TaskOptions>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
enum Command {
|
||||
Npm {
|
||||
script: String,
|
||||
},
|
||||
Shell {
|
||||
command: String,
|
||||
#[serde(default)]
|
||||
args: Vec<String>,
|
||||
},
|
||||
Gulp {
|
||||
task: String,
|
||||
},
|
||||
}
|
||||
|
||||
type VsCodeEnvVariable = String;
|
||||
type ZedEnvVariable = String;
|
||||
|
||||
struct EnvVariableReplacer {
|
||||
variables: HashMap<VsCodeEnvVariable, ZedEnvVariable>,
|
||||
}
|
||||
|
||||
impl EnvVariableReplacer {
|
||||
fn new(variables: HashMap<VsCodeEnvVariable, ZedEnvVariable>) -> Self {
|
||||
Self { variables }
|
||||
}
|
||||
// Replaces occurrences of VsCode-specific environment variables with Zed equivalents.
|
||||
fn replace(&self, input: &str) -> String {
|
||||
shellexpand::env_with_context_no_errors(&input, |var: &str| {
|
||||
// Colons denote a default value in case the variable is not set. We want to preserve that default, as otherwise shellexpand will substitute it for us.
|
||||
let colon_position = var.find(':').unwrap_or(var.len());
|
||||
let (variable_name, default) = var.split_at(colon_position);
|
||||
let append_previous_default = |ret: &mut String| {
|
||||
if !default.is_empty() {
|
||||
ret.push_str(default);
|
||||
}
|
||||
};
|
||||
if let Some(substitution) = self.variables.get(variable_name) {
|
||||
// Got a VSCode->Zed hit, perform a substitution
|
||||
let mut name = format!("${{{substitution}");
|
||||
append_previous_default(&mut name);
|
||||
name.push_str("}");
|
||||
return Some(name);
|
||||
}
|
||||
// This is an unknown variable.
|
||||
// We should not error out, as they may come from user environment (e.g. $PATH). That means that the variable substitution might not be perfect.
|
||||
// If there's a default, we need to return the string verbatim as otherwise shellexpand will apply that default for us.
|
||||
if !default.is_empty() {
|
||||
return Some(format!("${{{var}}}"));
|
||||
}
|
||||
// Else we can just return None and that variable will be left as is.
|
||||
None
|
||||
})
|
||||
.into_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl VsCodeTaskDefinition {
|
||||
fn to_zed_format(self, replacer: &EnvVariableReplacer) -> anyhow::Result<Definition> {
|
||||
if self.other_attributes.contains_key("dependsOn") {
|
||||
bail!("Encountered unsupported `dependsOn` key during deserialization");
|
||||
}
|
||||
// `type` might not be set in e.g. tasks that use `dependsOn`; we still want to deserialize the whole object though (hence command is an Option),
|
||||
// as that way we can provide more specific description of why deserialization failed.
|
||||
// E.g. if the command is missing due to `dependsOn` presence, we can check other_attributes first before doing this (and provide nice error message)
|
||||
// catch-all if on value.command presence.
|
||||
let Some(command) = self.command else {
|
||||
bail!("Missing `type` field in task");
|
||||
};
|
||||
|
||||
let (command, args) = match command {
|
||||
Command::Npm { script } => ("npm".to_owned(), vec!["run".to_string(), script]),
|
||||
Command::Shell { command, args } => (command, args),
|
||||
Command::Gulp { task } => ("gulp".to_owned(), vec![task]),
|
||||
};
|
||||
// Per VSC docs, only `command`, `args` and `options` support variable substitution.
|
||||
let command = replacer.replace(&command);
|
||||
let args = args.into_iter().map(|arg| replacer.replace(&arg)).collect();
|
||||
let mut ret = Definition {
|
||||
label: self.label,
|
||||
command,
|
||||
args,
|
||||
..Default::default()
|
||||
};
|
||||
if let Some(options) = self.options {
|
||||
ret.cwd = options.cwd.map(|cwd| replacer.replace(&cwd));
|
||||
ret.env = options.env;
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
/// [`VsCodeTaskFile`] is a superset of Code's task definition format.
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
pub struct VsCodeTaskFile {
|
||||
tasks: Vec<VsCodeTaskDefinition>,
|
||||
}
|
||||
|
||||
impl TryFrom<VsCodeTaskFile> for DefinitionProvider {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
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()),
|
||||
]));
|
||||
let definitions = value
|
||||
.tasks
|
||||
.into_iter()
|
||||
.filter_map(|vscode_definition| vscode_definition.to_zed_format(&replacer).log_err())
|
||||
.collect();
|
||||
Ok(Self(definitions))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
static_source::{Definition, DefinitionProvider},
|
||||
vscode_format::{Command, VsCodeTaskDefinition},
|
||||
VsCodeTaskFile,
|
||||
};
|
||||
|
||||
use super::EnvVariableReplacer;
|
||||
|
||||
fn compare_without_other_attributes(lhs: VsCodeTaskDefinition, rhs: VsCodeTaskDefinition) {
|
||||
assert_eq!(
|
||||
VsCodeTaskDefinition {
|
||||
other_attributes: Default::default(),
|
||||
..lhs
|
||||
},
|
||||
VsCodeTaskDefinition {
|
||||
other_attributes: Default::default(),
|
||||
..rhs
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_variable_substitution() {
|
||||
let replacer = EnvVariableReplacer::new(Default::default());
|
||||
assert_eq!(replacer.replace("Food"), "Food");
|
||||
// Unknown variables are left in tact.
|
||||
assert_eq!(
|
||||
replacer.replace("$PATH is an environment variable"),
|
||||
"$PATH is an environment variable"
|
||||
);
|
||||
assert_eq!(replacer.replace("${PATH}"), "${PATH}");
|
||||
assert_eq!(replacer.replace("${PATH:food}"), "${PATH:food}");
|
||||
// And now, the actual replacing
|
||||
let replacer = EnvVariableReplacer::new(HashMap::from_iter([(
|
||||
"PATH".to_owned(),
|
||||
"ZED_PATH".to_owned(),
|
||||
)]));
|
||||
assert_eq!(replacer.replace("Food"), "Food");
|
||||
assert_eq!(
|
||||
replacer.replace("$PATH is an environment variable"),
|
||||
"${ZED_PATH} is an environment variable"
|
||||
);
|
||||
assert_eq!(replacer.replace("${PATH}"), "${ZED_PATH}");
|
||||
assert_eq!(replacer.replace("${PATH:food}"), "${ZED_PATH:food}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_deserialize_ts_tasks() {
|
||||
static TYPESCRIPT_TASKS: &'static str = include_str!("../test_data/typescript.json");
|
||||
let vscode_definitions: VsCodeTaskFile =
|
||||
serde_json_lenient::from_str(&TYPESCRIPT_TASKS).unwrap();
|
||||
|
||||
let expected = vec![
|
||||
VsCodeTaskDefinition {
|
||||
label: "gulp: tests".to_string(),
|
||||
command: Some(Command::Npm {
|
||||
script: "build:tests:notypecheck".to_string(),
|
||||
}),
|
||||
other_attributes: Default::default(),
|
||||
options: None,
|
||||
},
|
||||
VsCodeTaskDefinition {
|
||||
label: "tsc: watch ./src".to_string(),
|
||||
command: Some(Command::Shell {
|
||||
command: "node".to_string(),
|
||||
args: vec![
|
||||
"${workspaceFolder}/node_modules/typescript/lib/tsc.js".to_string(),
|
||||
"--build".to_string(),
|
||||
"${workspaceFolder}/src".to_string(),
|
||||
"--watch".to_string(),
|
||||
],
|
||||
}),
|
||||
other_attributes: Default::default(),
|
||||
options: None,
|
||||
},
|
||||
VsCodeTaskDefinition {
|
||||
label: "npm: build:compiler".to_string(),
|
||||
command: Some(Command::Npm {
|
||||
script: "build:compiler".to_string(),
|
||||
}),
|
||||
other_attributes: Default::default(),
|
||||
options: None,
|
||||
},
|
||||
VsCodeTaskDefinition {
|
||||
label: "npm: build:tests".to_string(),
|
||||
command: Some(Command::Npm {
|
||||
script: "build:tests:notypecheck".to_string(),
|
||||
}),
|
||||
other_attributes: Default::default(),
|
||||
options: None,
|
||||
},
|
||||
];
|
||||
|
||||
assert_eq!(vscode_definitions.tasks.len(), expected.len());
|
||||
vscode_definitions
|
||||
.tasks
|
||||
.iter()
|
||||
.zip(expected)
|
||||
.for_each(|(lhs, rhs)| compare_without_other_attributes(lhs.clone(), rhs));
|
||||
|
||||
let expected = vec![
|
||||
Definition {
|
||||
label: "gulp: tests".to_string(),
|
||||
command: "npm".to_string(),
|
||||
args: vec!["run".to_string(), "build:tests:notypecheck".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
Definition {
|
||||
label: "tsc: watch ./src".to_string(),
|
||||
command: "node".to_string(),
|
||||
args: vec![
|
||||
"${ZED_WORKTREE_ROOT}/node_modules/typescript/lib/tsc.js".to_string(),
|
||||
"--build".to_string(),
|
||||
"${ZED_WORKTREE_ROOT}/src".to_string(),
|
||||
"--watch".to_string(),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
Definition {
|
||||
label: "npm: build:compiler".to_string(),
|
||||
command: "npm".to_string(),
|
||||
args: vec!["run".to_string(), "build:compiler".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
Definition {
|
||||
label: "npm: build:tests".to_string(),
|
||||
command: "npm".to_string(),
|
||||
args: vec!["run".to_string(), "build:tests:notypecheck".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
|
||||
let tasks: DefinitionProvider = vscode_definitions.try_into().unwrap();
|
||||
assert_eq!(tasks.0, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_deserialize_rust_analyzer_tasks() {
|
||||
static RUST_ANALYZER_TASKS: &'static str = include_str!("../test_data/rust-analyzer.json");
|
||||
let vscode_definitions: VsCodeTaskFile =
|
||||
serde_json_lenient::from_str(&RUST_ANALYZER_TASKS).unwrap();
|
||||
let expected = vec![
|
||||
VsCodeTaskDefinition {
|
||||
label: "Build Extension in Background".to_string(),
|
||||
command: Some(Command::Npm {
|
||||
script: "watch".to_string(),
|
||||
}),
|
||||
options: None,
|
||||
other_attributes: Default::default(),
|
||||
},
|
||||
VsCodeTaskDefinition {
|
||||
label: "Build Extension".to_string(),
|
||||
command: Some(Command::Npm {
|
||||
script: "build".to_string(),
|
||||
}),
|
||||
options: None,
|
||||
other_attributes: Default::default(),
|
||||
},
|
||||
VsCodeTaskDefinition {
|
||||
label: "Build Server".to_string(),
|
||||
command: Some(Command::Shell {
|
||||
command: "cargo build --package rust-analyzer".to_string(),
|
||||
args: Default::default(),
|
||||
}),
|
||||
options: None,
|
||||
other_attributes: Default::default(),
|
||||
},
|
||||
VsCodeTaskDefinition {
|
||||
label: "Build Server (Release)".to_string(),
|
||||
command: Some(Command::Shell {
|
||||
command: "cargo build --release --package rust-analyzer".to_string(),
|
||||
args: Default::default(),
|
||||
}),
|
||||
options: None,
|
||||
other_attributes: Default::default(),
|
||||
},
|
||||
VsCodeTaskDefinition {
|
||||
label: "Pretest".to_string(),
|
||||
command: Some(Command::Npm {
|
||||
script: "pretest".to_string(),
|
||||
}),
|
||||
options: None,
|
||||
other_attributes: Default::default(),
|
||||
},
|
||||
VsCodeTaskDefinition {
|
||||
label: "Build Server and Extension".to_string(),
|
||||
command: None,
|
||||
options: None,
|
||||
other_attributes: Default::default(),
|
||||
},
|
||||
VsCodeTaskDefinition {
|
||||
label: "Build Server (Release) and Extension".to_string(),
|
||||
command: None,
|
||||
options: None,
|
||||
other_attributes: Default::default(),
|
||||
},
|
||||
];
|
||||
assert_eq!(vscode_definitions.tasks.len(), expected.len());
|
||||
vscode_definitions
|
||||
.tasks
|
||||
.iter()
|
||||
.zip(expected)
|
||||
.for_each(|(lhs, rhs)| compare_without_other_attributes(lhs.clone(), rhs));
|
||||
let expected = vec![
|
||||
Definition {
|
||||
label: "Build Extension in Background".to_string(),
|
||||
command: "npm".to_string(),
|
||||
args: vec!["run".to_string(), "watch".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
Definition {
|
||||
label: "Build Extension".to_string(),
|
||||
command: "npm".to_string(),
|
||||
args: vec!["run".to_string(), "build".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
Definition {
|
||||
label: "Build Server".to_string(),
|
||||
command: "cargo build --package rust-analyzer".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
Definition {
|
||||
label: "Build Server (Release)".to_string(),
|
||||
command: "cargo build --release --package rust-analyzer".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
Definition {
|
||||
label: "Pretest".to_string(),
|
||||
command: "npm".to_string(),
|
||||
args: vec!["run".to_string(), "pretest".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
let tasks: DefinitionProvider = vscode_definitions.try_into().unwrap();
|
||||
assert_eq!(tasks.0, expected);
|
||||
}
|
||||
}
|
67
crates/task/test_data/rust-analyzer.json
Normal file
67
crates/task/test_data/rust-analyzer.json
Normal file
|
@ -0,0 +1,67 @@
|
|||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Build Extension in Background",
|
||||
"group": "build",
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"path": "editors/code/",
|
||||
"problemMatcher": {
|
||||
"base": "$tsc-watch",
|
||||
"fileLocation": ["relative", "${workspaceFolder}/editors/code/"]
|
||||
},
|
||||
"isBackground": true
|
||||
},
|
||||
{
|
||||
"label": "Build Extension",
|
||||
"group": "build",
|
||||
"type": "npm",
|
||||
"script": "build",
|
||||
"path": "editors/code/",
|
||||
"problemMatcher": {
|
||||
"base": "$tsc",
|
||||
"fileLocation": ["relative", "${workspaceFolder}/editors/code/"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Build Server",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "cargo build --package rust-analyzer",
|
||||
"problemMatcher": "$rustc"
|
||||
},
|
||||
{
|
||||
"label": "Build Server (Release)",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "cargo build --release --package rust-analyzer",
|
||||
"problemMatcher": "$rustc"
|
||||
},
|
||||
{
|
||||
"label": "Pretest",
|
||||
"group": "build",
|
||||
"isBackground": false,
|
||||
"type": "npm",
|
||||
"script": "pretest",
|
||||
"path": "editors/code/",
|
||||
"problemMatcher": {
|
||||
"base": "$tsc",
|
||||
"fileLocation": ["relative", "${workspaceFolder}/editors/code/"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"label": "Build Server and Extension",
|
||||
"dependsOn": ["Build Server", "Build Extension"],
|
||||
"problemMatcher": "$rustc"
|
||||
},
|
||||
{
|
||||
"label": "Build Server (Release) and Extension",
|
||||
"dependsOn": ["Build Server (Release)", "Build Extension"],
|
||||
"problemMatcher": "$rustc"
|
||||
}
|
||||
]
|
||||
}
|
51
crates/task/test_data/typescript.json
Normal file
51
crates/task/test_data/typescript.json
Normal file
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
// Kept for backwards compat for old launch.json files so it's
|
||||
// less annoying if moving up to the new build or going back to
|
||||
// the old build.
|
||||
//
|
||||
// This is first because the actual "npm: build:tests" task
|
||||
// below has the same script value, and VS Code ignores labels
|
||||
// and deduplicates them.
|
||||
// https://github.com/microsoft/vscode/issues/93001
|
||||
"label": "gulp: tests",
|
||||
"type": "npm",
|
||||
"script": "build:tests:notypecheck",
|
||||
"group": "build",
|
||||
"hide": true,
|
||||
"problemMatcher": ["$tsc"]
|
||||
},
|
||||
{
|
||||
"label": "tsc: watch ./src",
|
||||
"type": "shell",
|
||||
"command": "node",
|
||||
"args": [
|
||||
"${workspaceFolder}/node_modules/typescript/lib/tsc.js",
|
||||
"--build",
|
||||
"${workspaceFolder}/src",
|
||||
"--watch"
|
||||
],
|
||||
"group": "build",
|
||||
"isBackground": true,
|
||||
"problemMatcher": ["$tsc-watch"]
|
||||
},
|
||||
{
|
||||
"label": "npm: build:compiler",
|
||||
"type": "npm",
|
||||
"script": "build:compiler",
|
||||
"group": "build",
|
||||
"problemMatcher": ["$tsc"]
|
||||
},
|
||||
{
|
||||
"label": "npm: build:tests",
|
||||
"type": "npm",
|
||||
"script": "build:tests:notypecheck",
|
||||
"group": "build",
|
||||
"problemMatcher": ["$tsc"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -63,6 +63,7 @@ lazy_static::lazy_static! {
|
|||
pub static ref OLD_LOG: PathBuf = LOGS_DIR.join("Zed.log.old");
|
||||
pub static ref LOCAL_SETTINGS_RELATIVE_PATH: &'static Path = Path::new(".zed/settings.json");
|
||||
pub static ref LOCAL_TASKS_RELATIVE_PATH: &'static Path = Path::new(".zed/tasks.json");
|
||||
pub static ref LOCAL_VSCODE_TASKS_RELATIVE_PATH: &'static Path = Path::new(".vscode/tasks.json");
|
||||
pub static ref TEMP_DIR: PathBuf = if cfg!(target_os = "widows") {
|
||||
dirs::data_local_dir()
|
||||
.expect("failed to determine LocalAppData directory")
|
||||
|
|
|
@ -29,7 +29,10 @@ use settings::{
|
|||
SettingsStore, DEFAULT_KEYMAP_PATH,
|
||||
};
|
||||
use std::{borrow::Cow, ops::Deref, path::Path, sync::Arc};
|
||||
use task::{oneshot_source::OneshotSource, static_source::StaticSource};
|
||||
use task::{
|
||||
oneshot_source::OneshotSource,
|
||||
static_source::{StaticSource, TrackedFile},
|
||||
};
|
||||
use terminal_view::terminal_panel::{self, TerminalPanel};
|
||||
use util::{
|
||||
asset_str,
|
||||
|
@ -166,7 +169,11 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
|
|||
fs,
|
||||
paths::TASKS.clone(),
|
||||
);
|
||||
StaticSource::new("global_tasks", tasks_file_rx, cx)
|
||||
StaticSource::new(
|
||||
"global_tasks",
|
||||
TrackedFile::new(tasks_file_rx, cx),
|
||||
cx,
|
||||
)
|
||||
},
|
||||
cx,
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue