tasks: Refresh available tasks in editor when tasks.json changes (#11811)
Release Notes: - N/A
This commit is contained in:
parent
0ae0b08c38
commit
1db136ff65
6 changed files with 126 additions and 50 deletions
|
@ -1581,6 +1581,10 @@ impl Editor {
|
||||||
editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
|
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));
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7655,7 +7655,7 @@ impl Project {
|
||||||
abs_path,
|
abs_path,
|
||||||
id_base: "local_tasks_for_worktree",
|
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,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7675,12 +7675,13 @@ impl Project {
|
||||||
abs_path,
|
abs_path,
|
||||||
id_base: "local_vscode_tasks_for_worktree",
|
id_base: "local_vscode_tasks_for_worktree",
|
||||||
},
|
},
|
||||||
StaticSource::new(
|
|tx, cx| {
|
||||||
TrackedFile::new_convertible::<task::VsCodeTaskFile>(
|
StaticSource::new(TrackedFile::new_convertible::<
|
||||||
tasks_file_rx,
|
task::VsCodeTaskFile,
|
||||||
cx,
|
>(
|
||||||
),
|
tasks_file_rx, tx, cx
|
||||||
),
|
))
|
||||||
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -242,19 +242,25 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
|
||||||
}]))
|
}]))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (tx, rx) = futures::channel::mpsc::unbounded();
|
let (tx, rx) = futures::channel::mpsc::unbounded();
|
||||||
|
cx.update(|cx| {
|
||||||
let templates = cx.update(|cx| TrackedFile::new(rx, 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();
|
tx.unbounded_send(tasks).unwrap();
|
||||||
|
|
||||||
let source = StaticSource::new(templates);
|
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
let all_tasks = project
|
let all_tasks = project
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| {
|
||||||
project.task_inventory().update(cx, |inventory, cx| {
|
project.task_inventory().update(cx, |inventory, _| {
|
||||||
inventory.remove_local_static_source(Path::new("/the-root/.zed/tasks.json"));
|
|
||||||
inventory.add_source(global_task_source_kind.clone(), source, cx);
|
|
||||||
let (mut old, new) = inventory.used_and_current_resolved_tasks(
|
let (mut old, new) = inventory.used_and_current_resolved_tasks(
|
||||||
None,
|
None,
|
||||||
Some(workree_id),
|
Some(workree_id),
|
||||||
|
|
|
@ -7,7 +7,11 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use collections::{btree_map, BTreeMap, VecDeque};
|
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 itertools::Itertools;
|
||||||
use language::Language;
|
use language::Language;
|
||||||
use task::{
|
use task::{
|
||||||
|
@ -20,6 +24,8 @@ use worktree::WorktreeId;
|
||||||
pub struct Inventory {
|
pub struct Inventory {
|
||||||
sources: Vec<SourceInInventory>,
|
sources: Vec<SourceInInventory>,
|
||||||
last_scheduled_tasks: VecDeque<(TaskSourceKind, ResolvedTask)>,
|
last_scheduled_tasks: VecDeque<(TaskSourceKind, ResolvedTask)>,
|
||||||
|
update_sender: UnboundedSender<()>,
|
||||||
|
_update_pooler: Task<anyhow::Result<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SourceInInventory {
|
struct SourceInInventory {
|
||||||
|
@ -82,9 +88,22 @@ impl TaskSourceKind {
|
||||||
|
|
||||||
impl Inventory {
|
impl Inventory {
|
||||||
pub fn new(cx: &mut AppContext) -> Model<Self> {
|
pub fn new(cx: &mut AppContext) -> Model<Self> {
|
||||||
cx.new_model(|_| Self {
|
cx.new_model(|cx| {
|
||||||
sources: Vec::new(),
|
let (update_sender, mut rx) = unbounded();
|
||||||
last_scheduled_tasks: VecDeque::new(),
|
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(
|
pub fn add_source(
|
||||||
&mut self,
|
&mut self,
|
||||||
kind: TaskSourceKind,
|
kind: TaskSourceKind,
|
||||||
source: StaticSource,
|
create_source: impl FnOnce(UnboundedSender<()>, &mut AppContext) -> StaticSource,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let abs_path = kind.abs_path();
|
let abs_path = kind.abs_path();
|
||||||
|
@ -104,7 +123,7 @@ impl Inventory {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let source = create_source(self.update_sender.clone(), cx);
|
||||||
let source = SourceInInventory { source, kind };
|
let source = SourceInInventory { source, kind };
|
||||||
self.sources.push(source);
|
self.sources.push(source);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
@ -375,7 +394,7 @@ mod test_inventory {
|
||||||
|
|
||||||
use crate::Inventory;
|
use crate::Inventory;
|
||||||
|
|
||||||
use super::{task_source_kind_preference, TaskSourceKind};
|
use super::{task_source_kind_preference, TaskSourceKind, UnboundedSender};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct TestTask {
|
pub struct TestTask {
|
||||||
|
@ -384,6 +403,7 @@ mod test_inventory {
|
||||||
|
|
||||||
pub(super) fn static_test_source(
|
pub(super) fn static_test_source(
|
||||||
task_names: impl IntoIterator<Item = String>,
|
task_names: impl IntoIterator<Item = String>,
|
||||||
|
updates: UnboundedSender<()>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> StaticSource {
|
) -> StaticSource {
|
||||||
let tasks = TaskTemplates(
|
let tasks = TaskTemplates(
|
||||||
|
@ -397,7 +417,7 @@ mod test_inventory {
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
let (tx, rx) = futures::channel::mpsc::unbounded();
|
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())
|
tx.unbounded_send(serde_json::to_string(&tasks).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
StaticSource::new(file)
|
StaticSource::new(file)
|
||||||
|
@ -495,21 +515,24 @@ mod tests {
|
||||||
inventory.update(cx, |inventory, cx| {
|
inventory.update(cx, |inventory, cx| {
|
||||||
inventory.add_source(
|
inventory.add_source(
|
||||||
TaskSourceKind::UserInput,
|
TaskSourceKind::UserInput,
|
||||||
static_test_source(vec!["3_task".to_string()], cx),
|
|tx, cx| static_test_source(vec!["3_task".to_string()], tx, cx),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
inventory.update(cx, |inventory, cx| {
|
inventory.update(cx, |inventory, cx| {
|
||||||
inventory.add_source(
|
inventory.add_source(
|
||||||
TaskSourceKind::UserInput,
|
TaskSourceKind::UserInput,
|
||||||
static_test_source(
|
|tx, cx| {
|
||||||
vec![
|
static_test_source(
|
||||||
"1_task".to_string(),
|
vec![
|
||||||
"2_task".to_string(),
|
"1_task".to_string(),
|
||||||
"1_a_task".to_string(),
|
"2_task".to_string(),
|
||||||
],
|
"1_a_task".to_string(),
|
||||||
cx,
|
],
|
||||||
),
|
tx,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -570,7 +593,9 @@ mod tests {
|
||||||
inventory.update(cx, |inventory, cx| {
|
inventory.update(cx, |inventory, cx| {
|
||||||
inventory.add_source(
|
inventory.add_source(
|
||||||
TaskSourceKind::UserInput,
|
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,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -638,7 +663,13 @@ mod tests {
|
||||||
inventory_with_statics.update(cx, |inventory, cx| {
|
inventory_with_statics.update(cx, |inventory, cx| {
|
||||||
inventory.add_source(
|
inventory.add_source(
|
||||||
TaskSourceKind::UserInput,
|
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,
|
cx,
|
||||||
);
|
);
|
||||||
inventory.add_source(
|
inventory.add_source(
|
||||||
|
@ -646,10 +677,13 @@ mod tests {
|
||||||
id_base: "test source",
|
id_base: "test source",
|
||||||
abs_path: path_1.to_path_buf(),
|
abs_path: path_1.to_path_buf(),
|
||||||
},
|
},
|
||||||
static_test_source(
|
|tx, cx| {
|
||||||
vec!["static_source_1".to_string(), common_name.to_string()],
|
static_test_source(
|
||||||
cx,
|
vec!["static_source_1".to_string(), common_name.to_string()],
|
||||||
),
|
tx,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
inventory.add_source(
|
inventory.add_source(
|
||||||
|
@ -657,10 +691,13 @@ mod tests {
|
||||||
id_base: "test source",
|
id_base: "test source",
|
||||||
abs_path: path_2.to_path_buf(),
|
abs_path: path_2.to_path_buf(),
|
||||||
},
|
},
|
||||||
static_test_source(
|
|tx, cx| {
|
||||||
vec!["static_source_2".to_string(), common_name.to_string()],
|
static_test_source(
|
||||||
cx,
|
vec!["static_source_2".to_string(), common_name.to_string()],
|
||||||
),
|
tx,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
inventory.add_source(
|
inventory.add_source(
|
||||||
|
@ -669,7 +706,13 @@ mod tests {
|
||||||
abs_path: worktree_path_1.to_path_buf(),
|
abs_path: worktree_path_1.to_path_buf(),
|
||||||
id_base: "test_source",
|
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,
|
cx,
|
||||||
);
|
);
|
||||||
inventory.add_source(
|
inventory.add_source(
|
||||||
|
@ -678,7 +721,13 @@ mod tests {
|
||||||
abs_path: worktree_path_2.to_path_buf(),
|
abs_path: worktree_path_2.to_path_buf(),
|
||||||
id_base: "test_source",
|
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,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use futures::StreamExt;
|
use futures::{channel::mpsc::UnboundedSender, StreamExt};
|
||||||
use gpui::AppContext;
|
use gpui::AppContext;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -17,15 +17,18 @@ pub struct StaticSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Wrapper around deserializable T that keeps track of its contents
|
/// A Wrapper around deserializable T that keeps track of its contents
|
||||||
/// via a provided channel. Once T value changes, the observers of [`TrackedFile`] are
|
/// via a provided channel.
|
||||||
/// notified.
|
|
||||||
pub struct TrackedFile<T> {
|
pub struct TrackedFile<T> {
|
||||||
parsed_contents: Arc<RwLock<T>>,
|
parsed_contents: Arc<RwLock<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: PartialEq + 'static + Sync> TrackedFile<T> {
|
impl<T: PartialEq + 'static + Sync> TrackedFile<T> {
|
||||||
/// Initializes new [`TrackedFile`] with a type that's deserializable.
|
/// 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
|
where
|
||||||
T: for<'a> Deserialize<'a> + Default + Send,
|
T: for<'a> Deserialize<'a> + Default + Send,
|
||||||
{
|
{
|
||||||
|
@ -46,7 +49,13 @@ impl<T: PartialEq + 'static + Sync> TrackedFile<T> {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let mut contents = parsed_contents.write();
|
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(())
|
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.
|
/// 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>>(
|
pub fn new_convertible<U: for<'a> Deserialize<'a> + TryInto<T, Error = anyhow::Error>>(
|
||||||
mut tracker: UnboundedReceiver<String>,
|
mut tracker: UnboundedReceiver<String>,
|
||||||
|
notification_outlet: UnboundedSender<()>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Self
|
) -> Self
|
||||||
where
|
where
|
||||||
|
@ -85,7 +95,13 @@ impl<T: PartialEq + 'static + Sync> TrackedFile<T> {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let mut contents = parsed_contents.write();
|
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(())
|
anyhow::Ok(())
|
||||||
|
|
|
@ -174,7 +174,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||||
id_base: "global_tasks",
|
id_base: "global_tasks",
|
||||||
abs_path: paths::TASKS.clone(),
|
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,
|
cx,
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue