tasks: Refresh available tasks in editor when tasks.json changes (#11811)

Release Notes:

- N/A
This commit is contained in:
Piotr Osiewicz 2024-05-14 21:26:35 +02:00 committed by GitHub
parent 0ae0b08c38
commit 1db136ff65
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 126 additions and 50 deletions

View file

@ -1581,6 +1581,10 @@ impl Editor {
editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
};
}));
let task_inventory = project.read(cx).task_inventory().clone();
project_subscriptions.push(cx.observe(&task_inventory, |editor, _, cx| {
editor.tasks_update_task = Some(editor.refresh_runnables(cx));
}));
}
}

View file

@ -7655,7 +7655,7 @@ impl Project {
abs_path,
id_base: "local_tasks_for_worktree",
},
StaticSource::new(TrackedFile::new(tasks_file_rx, cx)),
|tx, cx| StaticSource::new(TrackedFile::new(tasks_file_rx, tx, cx)),
cx,
);
}
@ -7675,12 +7675,13 @@ impl Project {
abs_path,
id_base: "local_vscode_tasks_for_worktree",
},
StaticSource::new(
TrackedFile::new_convertible::<task::VsCodeTaskFile>(
tasks_file_rx,
cx,
),
),
|tx, cx| {
StaticSource::new(TrackedFile::new_convertible::<
task::VsCodeTaskFile,
>(
tasks_file_rx, tx, cx
))
},
cx,
);
}

View file

@ -242,19 +242,25 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
}]))
.unwrap();
let (tx, rx) = futures::channel::mpsc::unbounded();
let templates = cx.update(|cx| TrackedFile::new(rx, cx));
cx.update(|cx| {
project.update(cx, |project, cx| {
project.task_inventory().update(cx, |inventory, cx| {
inventory.remove_local_static_source(Path::new("/the-root/.zed/tasks.json"));
inventory.add_source(
global_task_source_kind.clone(),
|tx, cx| StaticSource::new(TrackedFile::new(rx, tx, cx)),
cx,
);
});
})
});
tx.unbounded_send(tasks).unwrap();
let source = StaticSource::new(templates);
cx.run_until_parked();
cx.update(|cx| {
let all_tasks = project
.update(cx, |project, cx| {
project.task_inventory().update(cx, |inventory, cx| {
inventory.remove_local_static_source(Path::new("/the-root/.zed/tasks.json"));
inventory.add_source(global_task_source_kind.clone(), source, cx);
project.task_inventory().update(cx, |inventory, _| {
let (mut old, new) = inventory.used_and_current_resolved_tasks(
None,
Some(workree_id),

View file

@ -7,7 +7,11 @@ use std::{
};
use collections::{btree_map, BTreeMap, VecDeque};
use gpui::{AppContext, Context, Model, ModelContext};
use futures::{
channel::mpsc::{unbounded, UnboundedSender},
StreamExt,
};
use gpui::{AppContext, Context, Model, ModelContext, Task};
use itertools::Itertools;
use language::Language;
use task::{
@ -20,6 +24,8 @@ use worktree::WorktreeId;
pub struct Inventory {
sources: Vec<SourceInInventory>,
last_scheduled_tasks: VecDeque<(TaskSourceKind, ResolvedTask)>,
update_sender: UnboundedSender<()>,
_update_pooler: Task<anyhow::Result<()>>,
}
struct SourceInInventory {
@ -82,9 +88,22 @@ impl TaskSourceKind {
impl Inventory {
pub fn new(cx: &mut AppContext) -> Model<Self> {
cx.new_model(|_| Self {
sources: Vec::new(),
last_scheduled_tasks: VecDeque::new(),
cx.new_model(|cx| {
let (update_sender, mut rx) = unbounded();
let _update_pooler = cx.spawn(|this, mut cx| async move {
while let Some(()) = rx.next().await {
this.update(&mut cx, |_, cx| {
cx.notify();
})?;
}
Ok(())
});
Self {
sources: Vec::new(),
last_scheduled_tasks: VecDeque::new(),
update_sender,
_update_pooler,
}
})
}
@ -94,7 +113,7 @@ impl Inventory {
pub fn add_source(
&mut self,
kind: TaskSourceKind,
source: StaticSource,
create_source: impl FnOnce(UnboundedSender<()>, &mut AppContext) -> StaticSource,
cx: &mut ModelContext<Self>,
) {
let abs_path = kind.abs_path();
@ -104,7 +123,7 @@ impl Inventory {
return;
}
}
let source = create_source(self.update_sender.clone(), cx);
let source = SourceInInventory { source, kind };
self.sources.push(source);
cx.notify();
@ -375,7 +394,7 @@ mod test_inventory {
use crate::Inventory;
use super::{task_source_kind_preference, TaskSourceKind};
use super::{task_source_kind_preference, TaskSourceKind, UnboundedSender};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TestTask {
@ -384,6 +403,7 @@ mod test_inventory {
pub(super) fn static_test_source(
task_names: impl IntoIterator<Item = String>,
updates: UnboundedSender<()>,
cx: &mut AppContext,
) -> StaticSource {
let tasks = TaskTemplates(
@ -397,7 +417,7 @@ mod test_inventory {
.collect(),
);
let (tx, rx) = futures::channel::mpsc::unbounded();
let file = TrackedFile::new(rx, cx);
let file = TrackedFile::new(rx, updates, cx);
tx.unbounded_send(serde_json::to_string(&tasks).unwrap())
.unwrap();
StaticSource::new(file)
@ -495,21 +515,24 @@ mod tests {
inventory.update(cx, |inventory, cx| {
inventory.add_source(
TaskSourceKind::UserInput,
static_test_source(vec!["3_task".to_string()], cx),
|tx, cx| static_test_source(vec!["3_task".to_string()], tx, cx),
cx,
);
});
inventory.update(cx, |inventory, cx| {
inventory.add_source(
TaskSourceKind::UserInput,
static_test_source(
vec![
"1_task".to_string(),
"2_task".to_string(),
"1_a_task".to_string(),
],
cx,
),
|tx, cx| {
static_test_source(
vec![
"1_task".to_string(),
"2_task".to_string(),
"1_a_task".to_string(),
],
tx,
cx,
)
},
cx,
);
});
@ -570,7 +593,9 @@ mod tests {
inventory.update(cx, |inventory, cx| {
inventory.add_source(
TaskSourceKind::UserInput,
static_test_source(vec!["10_hello".to_string(), "11_hello".to_string()], cx),
|tx, cx| {
static_test_source(vec!["10_hello".to_string(), "11_hello".to_string()], tx, cx)
},
cx,
);
});
@ -638,7 +663,13 @@ mod tests {
inventory_with_statics.update(cx, |inventory, cx| {
inventory.add_source(
TaskSourceKind::UserInput,
static_test_source(vec!["user_input".to_string(), common_name.to_string()], cx),
|tx, cx| {
static_test_source(
vec!["user_input".to_string(), common_name.to_string()],
tx,
cx,
)
},
cx,
);
inventory.add_source(
@ -646,10 +677,13 @@ mod tests {
id_base: "test source",
abs_path: path_1.to_path_buf(),
},
static_test_source(
vec!["static_source_1".to_string(), common_name.to_string()],
cx,
),
|tx, cx| {
static_test_source(
vec!["static_source_1".to_string(), common_name.to_string()],
tx,
cx,
)
},
cx,
);
inventory.add_source(
@ -657,10 +691,13 @@ mod tests {
id_base: "test source",
abs_path: path_2.to_path_buf(),
},
static_test_source(
vec!["static_source_2".to_string(), common_name.to_string()],
cx,
),
|tx, cx| {
static_test_source(
vec!["static_source_2".to_string(), common_name.to_string()],
tx,
cx,
)
},
cx,
);
inventory.add_source(
@ -669,7 +706,13 @@ mod tests {
abs_path: worktree_path_1.to_path_buf(),
id_base: "test_source",
},
static_test_source(vec!["worktree_1".to_string(), common_name.to_string()], cx),
|tx, cx| {
static_test_source(
vec!["worktree_1".to_string(), common_name.to_string()],
tx,
cx,
)
},
cx,
);
inventory.add_source(
@ -678,7 +721,13 @@ mod tests {
abs_path: worktree_path_2.to_path_buf(),
id_base: "test_source",
},
static_test_source(vec!["worktree_2".to_string(), common_name.to_string()], cx),
|tx, cx| {
static_test_source(
vec!["worktree_2".to_string(), common_name.to_string()],
tx,
cx,
)
},
cx,
);
});

View file

@ -2,7 +2,7 @@
use std::sync::Arc;
use futures::StreamExt;
use futures::{channel::mpsc::UnboundedSender, StreamExt};
use gpui::AppContext;
use parking_lot::RwLock;
use serde::Deserialize;
@ -17,15 +17,18 @@ pub struct StaticSource {
}
/// 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.
/// via a provided channel.
pub struct TrackedFile<T> {
parsed_contents: Arc<RwLock<T>>,
}
impl<T: PartialEq + 'static + Sync> TrackedFile<T> {
/// Initializes new [`TrackedFile`] with a type that's deserializable.
pub fn new(mut tracker: UnboundedReceiver<String>, cx: &mut AppContext) -> Self
pub fn new(
mut tracker: UnboundedReceiver<String>,
notification_outlet: UnboundedSender<()>,
cx: &mut AppContext,
) -> Self
where
T: for<'a> Deserialize<'a> + Default + Send,
{
@ -46,7 +49,13 @@ impl<T: PartialEq + 'static + Sync> TrackedFile<T> {
continue;
};
let mut contents = parsed_contents.write();
*contents = new_contents;
if *contents != new_contents {
*contents = new_contents;
if notification_outlet.unbounded_send(()).is_err() {
// Whoever cared about contents is not around anymore.
break;
}
}
}
}
anyhow::Ok(())
@ -59,6 +68,7 @@ impl<T: PartialEq + 'static + Sync> TrackedFile<T> {
/// 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>,
notification_outlet: UnboundedSender<()>,
cx: &mut AppContext,
) -> Self
where
@ -85,7 +95,13 @@ impl<T: PartialEq + 'static + Sync> TrackedFile<T> {
continue;
};
let mut contents = parsed_contents.write();
*contents = new_contents;
if *contents != new_contents {
*contents = new_contents;
if notification_outlet.unbounded_send(()).is_err() {
// Whoever cared about contents is not around anymore.
break;
}
}
}
}
anyhow::Ok(())

View file

@ -174,7 +174,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
id_base: "global_tasks",
abs_path: paths::TASKS.clone(),
},
StaticSource::new(TrackedFile::new(tasks_file_rx, cx)),
|tx, cx| StaticSource::new(TrackedFile::new(tasks_file_rx, tx, cx)),
cx,
);
})