1//! Project-wide storage of the tasks available, capable of updating itself from the sources set.
2
3use std::{any::TypeId, path::Path, sync::Arc};
4
5use collections::{HashMap, VecDeque};
6use gpui::{AppContext, Context, Model, ModelContext, Subscription};
7use itertools::Itertools;
8use task::{Source, Task, TaskId};
9use util::post_inc;
10
11/// Inventory tracks available tasks for a given project.
12pub struct Inventory {
13 sources: Vec<SourceInInventory>,
14 last_scheduled_tasks: VecDeque<TaskId>,
15}
16
17struct SourceInInventory {
18 source: Model<Box<dyn Source>>,
19 _subscription: Subscription,
20 type_id: TypeId,
21}
22
23impl Inventory {
24 pub(crate) fn new(cx: &mut AppContext) -> Model<Self> {
25 cx.new_model(|_| Self {
26 sources: Vec::new(),
27 last_scheduled_tasks: VecDeque::new(),
28 })
29 }
30
31 /// Registers a new tasks source, that would be fetched for available tasks.
32 pub fn add_source(&mut self, source: Model<Box<dyn Source>>, cx: &mut ModelContext<Self>) {
33 let _subscription = cx.observe(&source, |_, _, cx| {
34 cx.notify();
35 });
36 let type_id = source.read(cx).type_id();
37 let source = SourceInInventory {
38 source,
39 _subscription,
40 type_id,
41 };
42 self.sources.push(source);
43 cx.notify();
44 }
45
46 pub fn source<T: Source>(&self) -> Option<Model<Box<dyn Source>>> {
47 let target_type_id = std::any::TypeId::of::<T>();
48 self.sources.iter().find_map(
49 |SourceInInventory {
50 type_id, source, ..
51 }| {
52 if &target_type_id == type_id {
53 Some(source.clone())
54 } else {
55 None
56 }
57 },
58 )
59 }
60
61 /// Pulls its sources to list runanbles for the path given (up to the source to decide what to return for no path).
62 pub fn list_tasks(
63 &self,
64 path: Option<&Path>,
65 lru: bool,
66 cx: &mut AppContext,
67 ) -> Vec<Arc<dyn Task>> {
68 let mut lru_score = 0_u32;
69 let tasks_by_usage = if lru {
70 self.last_scheduled_tasks
71 .iter()
72 .rev()
73 .fold(HashMap::default(), |mut tasks, id| {
74 tasks.entry(id).or_insert_with(|| post_inc(&mut lru_score));
75 tasks
76 })
77 } else {
78 HashMap::default()
79 };
80 self.sources
81 .iter()
82 .flat_map(|source| {
83 source
84 .source
85 .update(cx, |source, cx| source.tasks_for_path(path, cx))
86 })
87 .map(|task| {
88 let usages = if lru {
89 tasks_by_usage
90 .get(&task.id())
91 .copied()
92 .unwrap_or_else(|| post_inc(&mut lru_score))
93 } else {
94 post_inc(&mut lru_score)
95 };
96 (task, usages)
97 })
98 .sorted_unstable_by(|(task_a, usages_a), (task_b, usages_b)| {
99 usages_a
100 .cmp(usages_b)
101 .then(task_a.name().cmp(task_b.name()))
102 })
103 .map(|(task, _)| task)
104 .collect()
105 }
106
107 /// Returns the last scheduled task, if any of the sources contains one with the matching id.
108 pub fn last_scheduled_task(&self, cx: &mut AppContext) -> Option<Arc<dyn Task>> {
109 self.last_scheduled_tasks.back().and_then(|id| {
110 // TODO straighten the `Path` story to understand what has to be passed here: or it will break in the future.
111 self.list_tasks(None, false, cx)
112 .into_iter()
113 .find(|task| task.id() == id)
114 })
115 }
116
117 pub fn task_scheduled(&mut self, id: TaskId) {
118 self.last_scheduled_tasks.push_back(id);
119 if self.last_scheduled_tasks.len() > 5_000 {
120 self.last_scheduled_tasks.pop_front();
121 }
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn todo_kb() {
131 todo!("TODO kb LRU tests")
132 }
133}