Detailed changes
@@ -342,6 +342,14 @@ pub fn local_vscode_tasks_file_relative_path() -> &'static Path {
Path::new(".vscode/tasks.json")
}
+pub fn debug_task_file_name() -> &'static str {
+ "debug.json"
+}
+
+pub fn task_file_name() -> &'static str {
+ "tasks.json"
+}
+
/// Returns the relative path to a `launch.json` file within a project.
pub fn local_debug_file_relative_path() -> &'static Path {
Path::new(".zed/debug.json")
@@ -865,7 +865,6 @@ impl Project {
let task_store = cx.new(|cx| {
TaskStore::local(
- fs.clone(),
buffer_store.downgrade(),
worktree_store.clone(),
toolchain_store.read(cx).as_language_toolchain_store(),
@@ -997,7 +996,6 @@ impl Project {
.new(|cx| ToolchainStore::remote(SSH_PROJECT_ID, ssh.read(cx).proto_client(), cx));
let task_store = cx.new(|cx| {
TaskStore::remote(
- fs.clone(),
buffer_store.downgrade(),
worktree_store.clone(),
toolchain_store.read(cx).as_language_toolchain_store(),
@@ -1008,7 +1006,12 @@ impl Project {
});
let settings_observer = cx.new(|cx| {
- SettingsObserver::new_remote(worktree_store.clone(), task_store.clone(), cx)
+ SettingsObserver::new_remote(
+ fs.clone(),
+ worktree_store.clone(),
+ task_store.clone(),
+ cx,
+ )
});
cx.subscribe(&settings_observer, Self::on_settings_observer_event)
.detach();
@@ -1244,7 +1247,6 @@ impl Project {
let task_store = cx.new(|cx| {
if run_tasks {
TaskStore::remote(
- fs.clone(),
buffer_store.downgrade(),
worktree_store.clone(),
Arc::new(EmptyToolchainStore),
@@ -1258,7 +1260,7 @@ impl Project {
})?;
let settings_observer = cx.new(|cx| {
- SettingsObserver::new_remote(worktree_store.clone(), task_store.clone(), cx)
+ SettingsObserver::new_remote(fs.clone(), worktree_store.clone(), task_store.clone(), cx)
})?;
let git_store = cx.new(|cx| {
@@ -2720,15 +2722,27 @@ impl Project {
match event {
SettingsObserverEvent::LocalSettingsUpdated(result) => match result {
Err(InvalidSettingsError::LocalSettings { message, path }) => {
- let message =
- format!("Failed to set local settings in {:?}:\n{}", path, message);
+ let message = format!("Failed to set local settings in {path:?}:\n{message}");
+ cx.emit(Event::Toast {
+ notification_id: format!("local-settings-{path:?}").into(),
+ message,
+ });
+ }
+ Ok(path) => cx.emit(Event::HideToast {
+ notification_id: format!("local-settings-{path:?}").into(),
+ }),
+ Err(_) => {}
+ },
+ SettingsObserverEvent::LocalTasksUpdated(result) => match result {
+ Err(InvalidSettingsError::Tasks { message, path }) => {
+ let message = format!("Failed to set local tasks in {path:?}:\n{message}");
cx.emit(Event::Toast {
- notification_id: "local-settings".into(),
+ notification_id: format!("local-tasks-{path:?}").into(),
message,
});
}
- Ok(_) => cx.emit(Event::HideToast {
- notification_id: "local-settings".into(),
+ Ok(path) => cx.emit(Event::HideToast {
+ notification_id: format!("local-tasks-{path:?}").into(),
}),
Err(_) => {}
},
@@ -2,7 +2,8 @@ use anyhow::Context as _;
use collections::HashMap;
use dap::adapters::DebugAdapterName;
use fs::Fs;
-use gpui::{App, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter};
+use futures::StreamExt as _;
+use gpui::{App, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter, Task};
use lsp::LanguageServerName;
use paths::{
local_debug_file_relative_path, local_settings_file_relative_path,
@@ -15,10 +16,14 @@ use rpc::{
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{
- parse_json_with_comments, InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation,
- SettingsSources, SettingsStore, TaskKind,
+ parse_json_with_comments, watch_config_file, InvalidSettingsError, LocalSettingsKind, Settings,
+ SettingsLocation, SettingsSources, SettingsStore, TaskKind,
+};
+use std::{
+ path::{Path, PathBuf},
+ sync::Arc,
+ time::Duration,
};
-use std::{path::Path, sync::Arc, time::Duration};
use task::{TaskTemplates, VsCodeTaskFile};
use util::ResultExt;
use worktree::{PathChange, UpdatedEntriesSet, Worktree, WorktreeId};
@@ -315,7 +320,8 @@ pub enum SettingsObserverMode {
#[derive(Clone, Debug, PartialEq)]
pub enum SettingsObserverEvent {
- LocalSettingsUpdated(Result<(), InvalidSettingsError>),
+ LocalSettingsUpdated(Result<PathBuf, InvalidSettingsError>),
+ LocalTasksUpdated(Result<PathBuf, InvalidSettingsError>),
}
impl EventEmitter<SettingsObserverEvent> for SettingsObserver {}
@@ -326,6 +332,7 @@ pub struct SettingsObserver {
worktree_store: Entity<WorktreeStore>,
project_id: u64,
task_store: Entity<TaskStore>,
+ _global_task_config_watchers: (Task<()>, Task<()>),
}
/// SettingsObserver observers changes to .zed/{settings, task}.json files in local worktrees
@@ -350,16 +357,31 @@ impl SettingsObserver {
Self {
worktree_store,
task_store,
- mode: SettingsObserverMode::Local(fs),
+ mode: SettingsObserverMode::Local(fs.clone()),
downstream_client: None,
project_id: 0,
+ _global_task_config_watchers: (
+ Self::subscribe_to_global_task_file_changes(
+ fs.clone(),
+ TaskKind::Script,
+ paths::tasks_file().clone(),
+ cx,
+ ),
+ Self::subscribe_to_global_task_file_changes(
+ fs,
+ TaskKind::Debug,
+ paths::debug_tasks_file().clone(),
+ cx,
+ ),
+ ),
}
}
pub fn new_remote(
+ fs: Arc<dyn Fs>,
worktree_store: Entity<WorktreeStore>,
task_store: Entity<TaskStore>,
- _: &mut Context<Self>,
+ cx: &mut Context<Self>,
) -> Self {
Self {
worktree_store,
@@ -367,6 +389,20 @@ impl SettingsObserver {
mode: SettingsObserverMode::Remote,
downstream_client: None,
project_id: 0,
+ _global_task_config_watchers: (
+ Self::subscribe_to_global_task_file_changes(
+ fs.clone(),
+ TaskKind::Script,
+ paths::tasks_file().clone(),
+ cx,
+ ),
+ Self::subscribe_to_global_task_file_changes(
+ fs.clone(),
+ TaskKind::Debug,
+ paths::debug_tasks_file().clone(),
+ cx,
+ ),
+ ),
}
}
@@ -622,11 +658,7 @@ impl SettingsObserver {
match result {
Err(InvalidSettingsError::LocalSettings { path, message }) => {
- log::error!(
- "Failed to set local settings in {:?}: {:?}",
- path,
- message
- );
+ log::error!("Failed to set local settings in {path:?}: {message}");
cx.emit(SettingsObserverEvent::LocalSettingsUpdated(Err(
InvalidSettingsError::LocalSettings { path, message },
)));
@@ -634,14 +666,16 @@ impl SettingsObserver {
Err(e) => {
log::error!("Failed to set local settings: {e}");
}
- Ok(_) => {
- cx.emit(SettingsObserverEvent::LocalSettingsUpdated(Ok(())));
+ Ok(()) => {
+ cx.emit(SettingsObserverEvent::LocalSettingsUpdated(Ok(
+ directory.join(local_settings_file_relative_path())
+ )));
}
}
}),
- LocalSettingsKind::Tasks(task_kind) => task_store.update(cx, |task_store, cx| {
- task_store
- .update_user_tasks(
+ LocalSettingsKind::Tasks(task_kind) => {
+ let result = task_store.update(cx, |task_store, cx| {
+ task_store.update_user_tasks(
TaskSettingsLocation::Worktree(SettingsLocation {
worktree_id,
path: directory.as_ref(),
@@ -650,8 +684,25 @@ impl SettingsObserver {
task_kind,
cx,
)
- .log_err();
- }),
+ });
+
+ match result {
+ Err(InvalidSettingsError::Tasks { path, message }) => {
+ log::error!("Failed to set local tasks in {path:?}: {message:?}");
+ cx.emit(SettingsObserverEvent::LocalTasksUpdated(Err(
+ InvalidSettingsError::Tasks { path, message },
+ )));
+ }
+ Err(e) => {
+ log::error!("Failed to set local tasks: {e}");
+ }
+ Ok(()) => {
+ cx.emit(SettingsObserverEvent::LocalTasksUpdated(Ok(
+ task_kind.config_in_dir(&directory)
+ )));
+ }
+ }
+ }
};
if let Some(downstream_client) = &self.downstream_client {
@@ -667,6 +718,65 @@ impl SettingsObserver {
}
}
}
+
+ fn subscribe_to_global_task_file_changes(
+ fs: Arc<dyn Fs>,
+ task_kind: TaskKind,
+ file_path: PathBuf,
+ cx: &mut Context<'_, Self>,
+ ) -> Task<()> {
+ let mut user_tasks_file_rx =
+ watch_config_file(&cx.background_executor(), fs, file_path.clone());
+ let user_tasks_content = cx.background_executor().block(user_tasks_file_rx.next());
+ let weak_entry = cx.weak_entity();
+ cx.spawn(async move |settings_observer, cx| {
+ let Ok(task_store) = settings_observer.update(cx, |settings_observer, _| {
+ settings_observer.task_store.clone()
+ }) else {
+ return;
+ };
+ if let Some(user_tasks_content) = user_tasks_content {
+ let Ok(()) = task_store.update(cx, |task_store, cx| {
+ task_store
+ .update_user_tasks(
+ TaskSettingsLocation::Global(&file_path),
+ Some(&user_tasks_content),
+ task_kind,
+ cx,
+ )
+ .log_err();
+ }) else {
+ return;
+ };
+ }
+ while let Some(user_tasks_content) = user_tasks_file_rx.next().await {
+ let Ok(result) = task_store.update(cx, |task_store, cx| {
+ task_store.update_user_tasks(
+ TaskSettingsLocation::Global(&file_path),
+ Some(&user_tasks_content),
+ task_kind,
+ cx,
+ )
+ }) else {
+ break;
+ };
+
+ weak_entry
+ .update(cx, |_, cx| match result {
+ Ok(()) => cx.emit(SettingsObserverEvent::LocalTasksUpdated(Ok(
+ file_path.clone()
+ ))),
+ Err(err) => cx.emit(SettingsObserverEvent::LocalTasksUpdated(Err(
+ InvalidSettingsError::Tasks {
+ path: file_path.clone(),
+ message: err.to_string(),
+ },
+ ))),
+ })
+ .ok();
+ }
+ })
+ }
}
pub fn local_settings_kind_from_proto(kind: proto::LocalSettingsKind) -> LocalSettingsKind {
@@ -8,12 +8,12 @@ use std::{
sync::Arc,
};
-use anyhow::{Context as _, Result};
+use anyhow::Result;
use collections::{HashMap, HashSet, VecDeque};
use gpui::{App, AppContext as _, Entity, SharedString, Task};
use itertools::Itertools;
use language::{ContextProvider, File, Language, LanguageToolchainStore, Location};
-use settings::{parse_json_with_comments, TaskKind};
+use settings::{parse_json_with_comments, InvalidSettingsError, TaskKind};
use task::{
DebugTaskDefinition, ResolvedTask, TaskContext, TaskId, TaskTemplate, TaskTemplates,
TaskVariables, VariableName,
@@ -378,10 +378,23 @@ impl Inventory {
location: TaskSettingsLocation<'_>,
raw_tasks_json: Option<&str>,
task_kind: TaskKind,
- ) -> anyhow::Result<()> {
- let raw_tasks =
- parse_json_with_comments::<Vec<serde_json::Value>>(raw_tasks_json.unwrap_or("[]"))
- .context("parsing tasks file content as a JSON array")?;
+ ) -> Result<(), InvalidSettingsError> {
+ let raw_tasks = match parse_json_with_comments::<Vec<serde_json::Value>>(
+ raw_tasks_json.unwrap_or("[]"),
+ ) {
+ Ok(tasks) => tasks,
+ Err(e) => {
+ return Err(InvalidSettingsError::Tasks {
+ path: match location {
+ TaskSettingsLocation::Global(path) => path.to_owned(),
+ TaskSettingsLocation::Worktree(settings_location) => {
+ task_kind.config_in_dir(settings_location.path)
+ }
+ },
+ message: format!("Failed to parse tasks file content as a JSON array: {e}"),
+ })
+ }
+ };
let new_templates = raw_tasks
.into_iter()
.filter_map(|raw_template| match &task_kind {
@@ -5,15 +5,13 @@ use std::{
use anyhow::Context as _;
use collections::HashMap;
-use fs::Fs;
-use futures::StreamExt as _;
use gpui::{App, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity};
use language::{
proto::{deserialize_anchor, serialize_anchor},
ContextProvider as _, LanguageToolchainStore, Location,
};
use rpc::{proto, AnyProtoClient, TypedEnvelope};
-use settings::{watch_config_file, SettingsLocation, TaskKind};
+use settings::{InvalidSettingsError, SettingsLocation, TaskKind};
use task::{TaskContext, TaskVariables, VariableName};
use text::{BufferId, OffsetRangeExt};
use util::ResultExt;
@@ -35,7 +33,6 @@ pub struct StoreState {
buffer_store: WeakEntity<BufferStore>,
worktree_store: Entity<WorktreeStore>,
toolchain_store: Arc<dyn LanguageToolchainStore>,
- _global_task_config_watchers: (Task<()>, Task<()>),
}
enum StoreMode {
@@ -161,7 +158,6 @@ impl TaskStore {
}
pub fn local(
- fs: Arc<dyn Fs>,
buffer_store: WeakEntity<BufferStore>,
worktree_store: Entity<WorktreeStore>,
toolchain_store: Arc<dyn LanguageToolchainStore>,
@@ -177,25 +173,10 @@ impl TaskStore {
buffer_store,
toolchain_store,
worktree_store,
- _global_task_config_watchers: (
- Self::subscribe_to_global_task_file_changes(
- fs.clone(),
- TaskKind::Script,
- paths::tasks_file().clone(),
- cx,
- ),
- Self::subscribe_to_global_task_file_changes(
- fs.clone(),
- TaskKind::Debug,
- paths::debug_tasks_file().clone(),
- cx,
- ),
- ),
})
}
pub fn remote(
- fs: Arc<dyn Fs>,
buffer_store: WeakEntity<BufferStore>,
worktree_store: Entity<WorktreeStore>,
toolchain_store: Arc<dyn LanguageToolchainStore>,
@@ -212,20 +193,6 @@ impl TaskStore {
buffer_store,
toolchain_store,
worktree_store,
- _global_task_config_watchers: (
- Self::subscribe_to_global_task_file_changes(
- fs.clone(),
- TaskKind::Script,
- paths::tasks_file().clone(),
- cx,
- ),
- Self::subscribe_to_global_task_file_changes(
- fs.clone(),
- TaskKind::Debug,
- paths::debug_tasks_file().clone(),
- cx,
- ),
- ),
})
}
@@ -299,7 +266,7 @@ impl TaskStore {
raw_tasks_json: Option<&str>,
task_type: TaskKind,
cx: &mut Context<'_, Self>,
- ) -> anyhow::Result<()> {
+ ) -> Result<(), InvalidSettingsError> {
let task_inventory = match self {
TaskStore::Functional(state) => &state.task_inventory,
TaskStore::Noop => return Ok(()),
@@ -312,53 +279,6 @@ impl TaskStore {
inventory.update_file_based_tasks(location, raw_tasks_json, task_type)
})
}
-
- fn subscribe_to_global_task_file_changes(
- fs: Arc<dyn Fs>,
- task_kind: TaskKind,
- file_path: PathBuf,
- cx: &mut Context<'_, Self>,
- ) -> Task<()> {
- let mut user_tasks_file_rx =
- watch_config_file(&cx.background_executor(), fs, file_path.clone());
- let user_tasks_content = cx.background_executor().block(user_tasks_file_rx.next());
- cx.spawn(async move |task_store, cx| {
- if let Some(user_tasks_content) = user_tasks_content {
- let Ok(_) = task_store.update(cx, |task_store, cx| {
- task_store
- .update_user_tasks(
- TaskSettingsLocation::Global(&file_path),
- Some(&user_tasks_content),
- task_kind,
- cx,
- )
- .log_err();
- }) else {
- return;
- };
- }
- while let Some(user_tasks_content) = user_tasks_file_rx.next().await {
- let Ok(()) = task_store.update(cx, |task_store, cx| {
- let result = task_store.update_user_tasks(
- TaskSettingsLocation::Global(&file_path),
- Some(&user_tasks_content),
- task_kind,
- cx,
- );
- if let Err(err) = &result {
- log::error!("Failed to load user {:?} tasks: {err}", task_kind);
- cx.emit(crate::Event::Toast {
- notification_id: format!("load-user-{:?}-tasks", task_kind).into(),
- message: format!("Invalid global {:?} tasks file\n{err}", task_kind),
- });
- }
- cx.refresh_windows();
- }) else {
- break; // App dropped
- };
- }
- })
- }
}
fn local_task_context_for_location(
@@ -140,7 +140,6 @@ impl HeadlessProject {
let task_store = cx.new(|cx| {
let mut task_store = TaskStore::local(
- fs.clone(),
buffer_store.downgrade(),
worktree_store.clone(),
toolchain_store.read(cx).as_language_toolchain_store(),
@@ -5,7 +5,9 @@ use fs::Fs;
use futures::{channel::mpsc, future::LocalBoxFuture, FutureExt, StreamExt};
use gpui::{App, AsyncApp, BorrowAppContext, Global, Task, UpdateGlobal};
-use paths::{local_settings_file_relative_path, EDITORCONFIG_NAME};
+use paths::{
+ debug_task_file_name, local_settings_file_relative_path, task_file_name, EDITORCONFIG_NAME,
+};
use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use smallvec::SmallVec;
@@ -250,6 +252,16 @@ trait AnySettingValue: 'static + Send + Sync {
struct DeserializedSetting(Box<dyn Any>);
+impl TaskKind {
+ /// Returns a file path of a task configuration file of this kind within the given directory.
+ pub fn config_in_dir(&self, dir: &Path) -> PathBuf {
+ dir.join(match self {
+ Self::Debug => debug_task_file_name(),
+ Self::Script => task_file_name(),
+ })
+ }
+}
+
impl SettingsStore {
pub fn new(cx: &App) -> Self {
let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded();
@@ -610,10 +622,11 @@ impl SettingsStore {
.map(|content| content.trim())
.filter(|content| !content.is_empty()),
) {
- (LocalSettingsKind::Tasks(_), _) => {
+ (LocalSettingsKind::Tasks(task_kind), _) => {
return Err(InvalidSettingsError::Tasks {
message: "Attempted to submit tasks into the settings store".to_string(),
- })
+ path: task_kind.config_in_dir(&directory_path),
+ });
}
(LocalSettingsKind::Settings, None) => {
zed_settings_changed = self
@@ -1011,7 +1024,7 @@ pub enum InvalidSettingsError {
ServerSettings { message: String },
DefaultSettings { message: String },
Editorconfig { path: PathBuf, message: String },
- Tasks { message: String },
+ Tasks { path: PathBuf, message: String },
}
impl std::fmt::Display for InvalidSettingsError {
@@ -1021,7 +1034,7 @@ impl std::fmt::Display for InvalidSettingsError {
| InvalidSettingsError::UserSettings { message }
| InvalidSettingsError::ServerSettings { message }
| InvalidSettingsError::DefaultSettings { message }
- | InvalidSettingsError::Tasks { message }
+ | InvalidSettingsError::Tasks { message, .. }
| InvalidSettingsError::Editorconfig { message, .. } => {
write!(f, "{message}")
}