task_inventory.rs

  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}