Sort tasks modal entries by last used time
This commit is contained in:
parent
9f7e625d37
commit
96d9df073e
3 changed files with 82 additions and 32 deletions
|
@ -2,13 +2,16 @@
|
||||||
|
|
||||||
use std::{any::TypeId, path::Path, sync::Arc};
|
use std::{any::TypeId, path::Path, sync::Arc};
|
||||||
|
|
||||||
|
use collections::{HashMap, VecDeque};
|
||||||
use gpui::{AppContext, Context, Model, ModelContext, Subscription};
|
use gpui::{AppContext, Context, Model, ModelContext, Subscription};
|
||||||
|
use itertools::Itertools;
|
||||||
use task::{Source, Task, TaskId};
|
use task::{Source, Task, TaskId};
|
||||||
|
use util::post_inc;
|
||||||
|
|
||||||
/// Inventory tracks available tasks for a given project.
|
/// Inventory tracks available tasks for a given project.
|
||||||
pub struct Inventory {
|
pub struct Inventory {
|
||||||
sources: Vec<SourceInInventory>,
|
sources: Vec<SourceInInventory>,
|
||||||
pub last_scheduled_task: Option<TaskId>,
|
last_scheduled_tasks: VecDeque<TaskId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SourceInInventory {
|
struct SourceInInventory {
|
||||||
|
@ -21,7 +24,7 @@ impl Inventory {
|
||||||
pub(crate) fn new(cx: &mut AppContext) -> Model<Self> {
|
pub(crate) fn new(cx: &mut AppContext) -> Model<Self> {
|
||||||
cx.new_model(|_| Self {
|
cx.new_model(|_| Self {
|
||||||
sources: Vec::new(),
|
sources: Vec::new(),
|
||||||
last_scheduled_task: None,
|
last_scheduled_tasks: VecDeque::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +42,7 @@ impl Inventory {
|
||||||
self.sources.push(source);
|
self.sources.push(source);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source<T: Source>(&self) -> Option<Model<Box<dyn Source>>> {
|
pub fn source<T: Source>(&self) -> Option<Model<Box<dyn Source>>> {
|
||||||
let target_type_id = std::any::TypeId::of::<T>();
|
let target_type_id = std::any::TypeId::of::<T>();
|
||||||
self.sources.iter().find_map(
|
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).
|
/// 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>> {
|
pub fn list_tasks(
|
||||||
let mut tasks = Vec::new();
|
&self,
|
||||||
for source in &self.sources {
|
path: Option<&Path>,
|
||||||
tasks.extend(
|
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
|
||||||
.source
|
.source
|
||||||
.update(cx, |source, cx| source.tasks_for_path(path, cx)),
|
.update(cx, |source, cx| source.tasks_for_path(path, cx))
|
||||||
);
|
})
|
||||||
}
|
.map(|task| {
|
||||||
tasks
|
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.
|
/// 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>> {
|
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.
|
// 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()
|
.into_iter()
|
||||||
.find(|task| task.id() == id)
|
.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 {
|
if let Some(spawn_in_terminal) = spawn_in_terminal {
|
||||||
workspace.project().update(cx, |project, cx| {
|
workspace.project().update(cx, |project, cx| {
|
||||||
project.task_inventory().update(cx, |inventory, _| {
|
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));
|
cx.emit(workspace::Event::SpawnTask(spawn_in_terminal));
|
||||||
|
|
|
@ -24,7 +24,7 @@ pub(crate) struct TasksModalDelegate {
|
||||||
matches: Vec<StringMatch>,
|
matches: Vec<StringMatch>,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
last_prompt: String,
|
prompt: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TasksModalDelegate {
|
impl TasksModalDelegate {
|
||||||
|
@ -35,20 +35,21 @@ impl TasksModalDelegate {
|
||||||
candidates: Vec::new(),
|
candidates: Vec::new(),
|
||||||
matches: Vec::new(),
|
matches: Vec::new(),
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
last_prompt: String::default(),
|
prompt: String::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_oneshot(&mut self, cx: &mut AppContext) -> Option<Arc<dyn Task>> {
|
fn spawn_oneshot(&mut self, cx: &mut AppContext) -> Option<Arc<dyn Task>> {
|
||||||
let oneshot_source = self
|
self.inventory
|
||||||
.inventory
|
.update(cx, |inventory, _| inventory.source::<OneshotSource>())?
|
||||||
.update(cx, |this, _| this.source::<OneshotSource>())?;
|
.update(cx, |oneshot_source, _| {
|
||||||
oneshot_source.update(cx, |this, _| {
|
Some(
|
||||||
let Some(this) = this.as_any().downcast_mut::<OneshotSource>() else {
|
oneshot_source
|
||||||
return None;
|
.as_any()
|
||||||
};
|
.downcast_mut::<OneshotSource>()?
|
||||||
Some(this.spawn(self.last_prompt.clone()))
|
.spawn(self.prompt.clone()),
|
||||||
})
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,12 +133,7 @@ impl PickerDelegate for TasksModalDelegate {
|
||||||
picker.delegate.candidates = picker
|
picker.delegate.candidates = picker
|
||||||
.delegate
|
.delegate
|
||||||
.inventory
|
.inventory
|
||||||
.update(cx, |inventory, cx| inventory.list_tasks(None, cx));
|
.update(cx, |inventory, cx| inventory.list_tasks(None, true, cx));
|
||||||
picker
|
|
||||||
.delegate
|
|
||||||
.candidates
|
|
||||||
.sort_by(|a, b| a.name().cmp(&b.name()));
|
|
||||||
|
|
||||||
picker
|
picker
|
||||||
.delegate
|
.delegate
|
||||||
.candidates
|
.candidates
|
||||||
|
@ -167,7 +163,7 @@ impl PickerDelegate for TasksModalDelegate {
|
||||||
.update(&mut cx, |picker, _| {
|
.update(&mut cx, |picker, _| {
|
||||||
let delegate = &mut picker.delegate;
|
let delegate = &mut picker.delegate;
|
||||||
delegate.matches = matches;
|
delegate.matches = matches;
|
||||||
delegate.last_prompt = query;
|
delegate.prompt = query;
|
||||||
|
|
||||||
if delegate.matches.is_empty() {
|
if delegate.matches.is_empty() {
|
||||||
delegate.selected_index = 0;
|
delegate.selected_index = 0;
|
||||||
|
@ -184,7 +180,7 @@ impl PickerDelegate for TasksModalDelegate {
|
||||||
let current_match_index = self.selected_index();
|
let current_match_index = self.selected_index();
|
||||||
|
|
||||||
let task = if secondary {
|
let task = if secondary {
|
||||||
if !self.last_prompt.trim().is_empty() {
|
if !self.prompt.trim().is_empty() {
|
||||||
self.spawn_oneshot(cx)
|
self.spawn_oneshot(cx)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue