@@ -2,13 +2,16 @@
use std::{any::TypeId, path::Path, sync::Arc};
+use collections::{HashMap, VecDeque};
use gpui::{AppContext, Context, Model, ModelContext, Subscription};
+use itertools::Itertools;
use task::{Source, Task, TaskId};
+use util::post_inc;
/// Inventory tracks available tasks for a given project.
pub struct Inventory {
sources: Vec<SourceInInventory>,
- pub last_scheduled_task: Option<TaskId>,
+ last_scheduled_tasks: VecDeque<TaskId>,
}
struct SourceInInventory {
@@ -21,7 +24,7 @@ impl Inventory {
pub(crate) fn new(cx: &mut AppContext) -> Model<Self> {
cx.new_model(|_| Self {
sources: Vec::new(),
- last_scheduled_task: None,
+ last_scheduled_tasks: VecDeque::new(),
})
}
@@ -39,6 +42,7 @@ impl Inventory {
self.sources.push(source);
cx.notify();
}
+
pub fn source<T: Source>(&self) -> Option<Model<Box<dyn Source>>> {
let target_type_id = std::any::TypeId::of::<T>();
self.sources.iter().find_map(
@@ -55,25 +59,75 @@ impl Inventory {
}
/// Pulls its sources to list runanbles for the path given (up to the source to decide what to return for no path).
- pub fn list_tasks(&self, path: Option<&Path>, cx: &mut AppContext) -> Vec<Arc<dyn Task>> {
- let mut tasks = Vec::new();
- for source in &self.sources {
- tasks.extend(
+ pub fn list_tasks(
+ &self,
+ path: Option<&Path>,
+ lru: bool,
+ cx: &mut AppContext,
+ ) -> Vec<Arc<dyn Task>> {
+ let mut lru_score = 0_u32;
+ let tasks_by_usage = if lru {
+ self.last_scheduled_tasks
+ .iter()
+ .rev()
+ .fold(HashMap::default(), |mut tasks, id| {
+ tasks.entry(id).or_insert_with(|| post_inc(&mut lru_score));
+ tasks
+ })
+ } else {
+ HashMap::default()
+ };
+ self.sources
+ .iter()
+ .flat_map(|source| {
source
.source
- .update(cx, |source, cx| source.tasks_for_path(path, cx)),
- );
- }
- tasks
+ .update(cx, |source, cx| source.tasks_for_path(path, cx))
+ })
+ .map(|task| {
+ let usages = if lru {
+ tasks_by_usage
+ .get(&task.id())
+ .copied()
+ .unwrap_or_else(|| post_inc(&mut lru_score))
+ } else {
+ post_inc(&mut lru_score)
+ };
+ (task, usages)
+ })
+ .sorted_unstable_by(|(task_a, usages_a), (task_b, usages_b)| {
+ usages_a
+ .cmp(usages_b)
+ .then(task_a.name().cmp(task_b.name()))
+ })
+ .map(|(task, _)| task)
+ .collect()
}
/// Returns the last scheduled task, if any of the sources contains one with the matching id.
pub fn last_scheduled_task(&self, cx: &mut AppContext) -> Option<Arc<dyn Task>> {
- self.last_scheduled_task.as_ref().and_then(|id| {
+ self.last_scheduled_tasks.back().and_then(|id| {
// TODO straighten the `Path` story to understand what has to be passed here: or it will break in the future.
- self.list_tasks(None, cx)
+ self.list_tasks(None, false, cx)
.into_iter()
.find(|task| task.id() == id)
})
}
+
+ pub fn task_scheduled(&mut self, id: TaskId) {
+ self.last_scheduled_tasks.push_back(id);
+ if self.last_scheduled_tasks.len() > 5_000 {
+ self.last_scheduled_tasks.pop_front();
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn todo_kb() {
+ todo!("TODO kb LRU tests")
+ }
}
@@ -41,7 +41,7 @@ fn schedule_task(workspace: &Workspace, task: &dyn Task, cx: &mut ViewContext<'_
if let Some(spawn_in_terminal) = spawn_in_terminal {
workspace.project().update(cx, |project, cx| {
project.task_inventory().update(cx, |inventory, _| {
- inventory.last_scheduled_task = Some(task.id().clone());
+ inventory.task_scheduled(task.id().clone());
})
});
cx.emit(workspace::Event::SpawnTask(spawn_in_terminal));
@@ -24,7 +24,7 @@ pub(crate) struct TasksModalDelegate {
matches: Vec<StringMatch>,
selected_index: usize,
workspace: WeakView<Workspace>,
- last_prompt: String,
+ prompt: String,
}
impl TasksModalDelegate {
@@ -35,20 +35,21 @@ impl TasksModalDelegate {
candidates: Vec::new(),
matches: Vec::new(),
selected_index: 0,
- last_prompt: String::default(),
+ prompt: String::default(),
}
}
fn spawn_oneshot(&mut self, cx: &mut AppContext) -> Option<Arc<dyn Task>> {
- let oneshot_source = self
- .inventory
- .update(cx, |this, _| this.source::<OneshotSource>())?;
- oneshot_source.update(cx, |this, _| {
- let Some(this) = this.as_any().downcast_mut::<OneshotSource>() else {
- return None;
- };
- Some(this.spawn(self.last_prompt.clone()))
- })
+ self.inventory
+ .update(cx, |inventory, _| inventory.source::<OneshotSource>())?
+ .update(cx, |oneshot_source, _| {
+ Some(
+ oneshot_source
+ .as_any()
+ .downcast_mut::<OneshotSource>()?
+ .spawn(self.prompt.clone()),
+ )
+ })
}
}
@@ -132,12 +133,7 @@ impl PickerDelegate for TasksModalDelegate {
picker.delegate.candidates = picker
.delegate
.inventory
- .update(cx, |inventory, cx| inventory.list_tasks(None, cx));
- picker
- .delegate
- .candidates
- .sort_by(|a, b| a.name().cmp(&b.name()));
-
+ .update(cx, |inventory, cx| inventory.list_tasks(None, true, cx));
picker
.delegate
.candidates
@@ -167,7 +163,7 @@ impl PickerDelegate for TasksModalDelegate {
.update(&mut cx, |picker, _| {
let delegate = &mut picker.delegate;
delegate.matches = matches;
- delegate.last_prompt = query;
+ delegate.prompt = query;
if delegate.matches.is_empty() {
delegate.selected_index = 0;
@@ -184,7 +180,7 @@ impl PickerDelegate for TasksModalDelegate {
let current_match_index = self.selected_index();
let task = if secondary {
- if !self.last_prompt.trim().is_empty() {
+ if !self.prompt.trim().is_empty() {
self.spawn_oneshot(cx)
} else {
None