.zed/tasks.json 🔗
Anthony Eid , Piotr Osiewicz , and Piotr Osiewicz created
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>
.zed/tasks.json | 0
Cargo.lock | 1
crates/project/src/project.rs | 38 ++
crates/task/Cargo.toml | 1
crates/task/src/lib.rs | 2
crates/task/src/static_source.rs | 62 +++
crates/task/src/vscode_format.rs | 386 ++++++++++++++++++++++++++
crates/task/test_data/rust-analyzer.json | 67 ++++
crates/task/test_data/typescript.json | 51 +++
crates/util/src/paths.rs | 1
crates/zed/src/zed.rs | 11
11 files changed, 604 insertions(+), 16 deletions(-)
@@ -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,25 +122,64 @@ 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,
+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 {
+ 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(),
+ }
+ })
+ }
+
+ /// 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> {
+ ) -> 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(&new_contents).log_err()
+ 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;
@@ -152,7 +191,9 @@ 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(),
+ }
})
}
@@ -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(
@@ -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);
+ }
+}
@@ -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"
+ }
+ ]
+}
@@ -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,
);