Allow .zed/tasks.json local configs (#8536)
 Follow-up of https://github.com/zed-industries/zed/issues/7108#issuecomment-1960746397 Makes more clear where each task came from, auto (re)load .zed/config.json changes, properly filtering out other worktree tasks. Release Notes: - Added local task configurations
This commit is contained in:
parent
7f954cbbb8
commit
ac30ded80e
12 changed files with 715 additions and 281 deletions
|
@ -1,4 +1,4 @@
|
|||
use std::sync::Arc;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
|
@ -6,11 +6,14 @@ use gpui::{
|
|||
Model, ParentElement, Render, SharedString, Styled, Subscription, View, ViewContext,
|
||||
VisualContext, WeakView,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::Inventory;
|
||||
use picker::{
|
||||
highlighted_match_with_paths::{HighlightedMatchWithPaths, HighlightedText},
|
||||
Picker, PickerDelegate,
|
||||
};
|
||||
use project::{Inventory, ProjectPath, TaskSourceKind};
|
||||
use task::{oneshot_source::OneshotSource, Task};
|
||||
use ui::{v_flex, HighlightedLabel, ListItem, ListItemSpacing, Selectable, WindowContext};
|
||||
use util::ResultExt;
|
||||
use ui::{v_flex, ListItem, ListItemSpacing, RenderOnce, Selectable, WindowContext};
|
||||
use util::{paths::PathExt, ResultExt};
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
use crate::schedule_task;
|
||||
|
@ -20,7 +23,7 @@ actions!(task, [Spawn, Rerun]);
|
|||
/// A modal used to spawn new tasks.
|
||||
pub(crate) struct TasksModalDelegate {
|
||||
inventory: Model<Inventory>,
|
||||
candidates: Vec<Arc<dyn Task>>,
|
||||
candidates: Vec<(TaskSourceKind, Arc<dyn Task>)>,
|
||||
matches: Vec<StringMatch>,
|
||||
selected_index: usize,
|
||||
workspace: WeakView<Workspace>,
|
||||
|
@ -51,6 +54,21 @@ impl TasksModalDelegate {
|
|||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn active_item_path(
|
||||
&mut self,
|
||||
cx: &mut ViewContext<'_, Picker<Self>>,
|
||||
) -> Option<(PathBuf, ProjectPath)> {
|
||||
let workspace = self.workspace.upgrade()?.read(cx);
|
||||
let project = workspace.project().read(cx);
|
||||
let active_item = workspace.active_item(cx)?;
|
||||
active_item.project_path(cx).and_then(|project_path| {
|
||||
project
|
||||
.worktree_for_id(project_path.worktree_id, cx)
|
||||
.map(|worktree| worktree.read(cx).abs_path().join(&project_path.path))
|
||||
.zip(Some(project_path))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TasksModal {
|
||||
|
@ -130,16 +148,22 @@ impl PickerDelegate for TasksModalDelegate {
|
|||
cx.spawn(move |picker, mut cx| async move {
|
||||
let Some(candidates) = picker
|
||||
.update(&mut cx, |picker, cx| {
|
||||
picker.delegate.candidates = picker
|
||||
.delegate
|
||||
.inventory
|
||||
.update(cx, |inventory, cx| inventory.list_tasks(None, true, cx));
|
||||
let (path, worktree) = match picker.delegate.active_item_path(cx) {
|
||||
Some((abs_path, project_path)) => {
|
||||
(Some(abs_path), Some(project_path.worktree_id))
|
||||
}
|
||||
None => (None, None),
|
||||
};
|
||||
picker.delegate.candidates =
|
||||
picker.delegate.inventory.update(cx, |inventory, cx| {
|
||||
inventory.list_tasks(path.as_deref(), worktree, true, cx)
|
||||
});
|
||||
picker
|
||||
.delegate
|
||||
.candidates
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, candidate)| StringMatchCandidate {
|
||||
.map(|(index, (_, candidate))| StringMatchCandidate {
|
||||
id: index,
|
||||
char_bag: candidate.name().chars().collect(),
|
||||
string: candidate.name().into(),
|
||||
|
@ -178,7 +202,6 @@ impl PickerDelegate for TasksModalDelegate {
|
|||
|
||||
fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<picker::Picker<Self>>) {
|
||||
let current_match_index = self.selected_index();
|
||||
|
||||
let task = if secondary {
|
||||
if !self.prompt.trim().is_empty() {
|
||||
self.spawn_oneshot(cx)
|
||||
|
@ -188,7 +211,7 @@ impl PickerDelegate for TasksModalDelegate {
|
|||
} else {
|
||||
self.matches.get(current_match_index).map(|current_match| {
|
||||
let ix = current_match.candidate_id;
|
||||
self.candidates[ix].clone()
|
||||
self.candidates[ix].1.clone()
|
||||
})
|
||||
};
|
||||
|
||||
|
@ -212,16 +235,35 @@ impl PickerDelegate for TasksModalDelegate {
|
|||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
_cx: &mut ViewContext<picker::Picker<Self>>,
|
||||
cx: &mut ViewContext<picker::Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let hit = &self.matches[ix];
|
||||
let highlights: Vec<_> = hit.positions.iter().copied().collect();
|
||||
let (source_kind, _) = &self.candidates[hit.candidate_id];
|
||||
let details = match source_kind {
|
||||
TaskSourceKind::UserInput => "user input".to_string(),
|
||||
TaskSourceKind::Worktree { abs_path, .. } | TaskSourceKind::AbsPath(abs_path) => {
|
||||
abs_path.compact().to_string_lossy().to_string()
|
||||
}
|
||||
};
|
||||
|
||||
let highlighted_location = HighlightedMatchWithPaths {
|
||||
match_label: HighlightedText {
|
||||
text: hit.string.clone(),
|
||||
highlight_positions: hit.positions.clone(),
|
||||
char_count: hit.string.chars().count(),
|
||||
},
|
||||
paths: vec![HighlightedText {
|
||||
char_count: details.chars().count(),
|
||||
highlight_positions: Vec::new(),
|
||||
text: details,
|
||||
}],
|
||||
};
|
||||
Some(
|
||||
ListItem::new(SharedString::from(format!("tasks-modal-{ix}")))
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.selected(selected)
|
||||
.start_slot(HighlightedLabel::new(hit.string.clone(), highlights)),
|
||||
.child(highlighted_location.render(cx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue