Allow .zed/tasks.json local configs (#8536)

![image](https://github.com/zed-industries/zed/assets/2690773/e1511777-b4ca-469e-8636-1e513b615368)

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:
Kirill Bulatov 2024-02-29 01:18:13 +02:00 committed by GitHub
parent 7f954cbbb8
commit ac30ded80e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 715 additions and 281 deletions

View file

@ -1,130 +0,0 @@
use std::path::Path;
use fuzzy::StringMatch;
use ui::{prelude::*, HighlightedLabel};
use util::paths::PathExt;
use workspace::WorkspaceLocation;
#[derive(Clone, IntoElement)]
pub struct HighlightedText {
pub text: String,
pub highlight_positions: Vec<usize>,
char_count: usize,
}
impl HighlightedText {
fn join(components: impl Iterator<Item = Self>, separator: &str) -> Self {
let mut char_count = 0;
let separator_char_count = separator.chars().count();
let mut text = String::new();
let mut highlight_positions = Vec::new();
for component in components {
if char_count != 0 {
text.push_str(separator);
char_count += separator_char_count;
}
highlight_positions.extend(
component
.highlight_positions
.iter()
.map(|position| position + char_count),
);
text.push_str(&component.text);
char_count += component.text.chars().count();
}
Self {
text,
highlight_positions,
char_count,
}
}
}
impl RenderOnce for HighlightedText {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
HighlightedLabel::new(self.text, self.highlight_positions)
}
}
#[derive(Clone)]
pub struct HighlightedWorkspaceLocation {
pub names: HighlightedText,
pub paths: Vec<HighlightedText>,
}
impl HighlightedWorkspaceLocation {
pub fn new(string_match: &StringMatch, location: &WorkspaceLocation) -> Self {
let mut path_start_offset = 0;
let (names, paths): (Vec<_>, Vec<_>) = location
.paths()
.iter()
.map(|path| {
let path = path.compact();
let highlighted_text = Self::highlights_for_path(
path.as_ref(),
&string_match.positions,
path_start_offset,
);
path_start_offset += highlighted_text.1.char_count;
highlighted_text
})
.unzip();
Self {
names: HighlightedText::join(names.into_iter().filter_map(|name| name), ", "),
paths,
}
}
// Compute the highlighted text for the name and path
fn highlights_for_path(
path: &Path,
match_positions: &Vec<usize>,
path_start_offset: usize,
) -> (Option<HighlightedText>, HighlightedText) {
let path_string = path.to_string_lossy();
let path_char_count = path_string.chars().count();
// Get the subset of match highlight positions that line up with the given path.
// Also adjusts them to start at the path start
let path_positions = match_positions
.iter()
.copied()
.skip_while(|position| *position < path_start_offset)
.take_while(|position| *position < path_start_offset + path_char_count)
.map(|position| position - path_start_offset)
.collect::<Vec<_>>();
// Again subset the highlight positions to just those that line up with the file_name
// again adjusted to the start of the file_name
let file_name_text_and_positions = path.file_name().map(|file_name| {
let text = file_name.to_string_lossy();
let char_count = text.chars().count();
let file_name_start = path_char_count - char_count;
let highlight_positions = path_positions
.iter()
.copied()
.skip_while(|position| *position < file_name_start)
.take_while(|position| *position < file_name_start + char_count)
.map(|position| position - file_name_start)
.collect::<Vec<_>>();
HighlightedText {
text: text.to_string(),
highlight_positions,
char_count,
}
});
(
file_name_text_and_positions,
HighlightedText {
text: path_string.to_string(),
highlight_positions: path_positions,
char_count: path_char_count,
},
)
}
}

View file

@ -1,15 +1,15 @@
mod highlighted_workspace_location;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Result,
Subscription, Task, View, ViewContext, WeakView,
};
use highlighted_workspace_location::HighlightedWorkspaceLocation;
use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate};
use std::sync::Arc;
use ui::{prelude::*, tooltip_container, HighlightedLabel, ListItem, ListItemSpacing, Tooltip};
use picker::{
highlighted_match_with_paths::{HighlightedMatchWithPaths, HighlightedText},
Picker, PickerDelegate,
};
use std::{path::Path, sync::Arc};
use ui::{prelude::*, tooltip_container, ListItem, ListItemSpacing, Tooltip};
use util::paths::PathExt;
use workspace::{ModalView, Workspace, WorkspaceId, WorkspaceLocation, WORKSPACE_DB};
@ -245,32 +245,40 @@ impl PickerDelegate for RecentProjectsDelegate {
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let Some(r#match) = self.matches.get(ix) else {
let Some(hit) = self.matches.get(ix) else {
return None;
};
let (workspace_id, location) = &self.workspaces[r#match.candidate_id];
let highlighted_location: HighlightedWorkspaceLocation =
HighlightedWorkspaceLocation::new(&r#match, location);
let tooltip_highlighted_location = highlighted_location.clone();
let (workspace_id, location) = &self.workspaces[hit.candidate_id];
let is_current_workspace = self.is_current_workspace(*workspace_id, cx);
let mut path_start_offset = 0;
let (match_labels, paths): (Vec<_>, Vec<_>) = location
.paths()
.iter()
.map(|path| {
let path = path.compact();
let highlighted_text =
highlights_for_path(path.as_ref(), &hit.positions, path_start_offset);
path_start_offset += highlighted_text.1.char_count;
highlighted_text
})
.unzip();
let highlighted_match = HighlightedMatchWithPaths {
match_label: HighlightedText::join(
match_labels.into_iter().filter_map(|name| name),
", ",
),
paths: if self.render_paths { paths } else { Vec::new() },
};
Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.selected(selected)
.child(
v_flex()
.child(highlighted_location.names)
.when(self.render_paths, |this| {
this.children(highlighted_location.paths.into_iter().map(|path| {
HighlightedLabel::new(path.text, path.highlight_positions)
.size(LabelSize::Small)
.color(Color::Muted)
}))
}),
)
.child(highlighted_match.clone().render(cx))
.when(!is_current_workspace, |el| {
let delete_button = div()
.child(
@ -293,7 +301,7 @@ impl PickerDelegate for RecentProjectsDelegate {
}
})
.tooltip(move |cx| {
let tooltip_highlighted_location = tooltip_highlighted_location.clone();
let tooltip_highlighted_location = highlighted_match.clone();
cx.new_view(move |_| MatchTooltip {
highlighted_location: tooltip_highlighted_location,
})
@ -303,6 +311,54 @@ impl PickerDelegate for RecentProjectsDelegate {
}
}
// Compute the highlighted text for the name and path
fn highlights_for_path(
path: &Path,
match_positions: &Vec<usize>,
path_start_offset: usize,
) -> (Option<HighlightedText>, HighlightedText) {
let path_string = path.to_string_lossy();
let path_char_count = path_string.chars().count();
// Get the subset of match highlight positions that line up with the given path.
// Also adjusts them to start at the path start
let path_positions = match_positions
.iter()
.copied()
.skip_while(|position| *position < path_start_offset)
.take_while(|position| *position < path_start_offset + path_char_count)
.map(|position| position - path_start_offset)
.collect::<Vec<_>>();
// Again subset the highlight positions to just those that line up with the file_name
// again adjusted to the start of the file_name
let file_name_text_and_positions = path.file_name().map(|file_name| {
let text = file_name.to_string_lossy();
let char_count = text.chars().count();
let file_name_start = path_char_count - char_count;
let highlight_positions = path_positions
.iter()
.copied()
.skip_while(|position| *position < file_name_start)
.take_while(|position| *position < file_name_start + char_count)
.map(|position| position - file_name_start)
.collect::<Vec<_>>();
HighlightedText {
text: text.to_string(),
highlight_positions,
char_count,
}
});
(
file_name_text_and_positions,
HighlightedText {
text: path_string.to_string(),
highlight_positions: path_positions,
char_count: path_char_count,
},
)
}
impl RecentProjectsDelegate {
fn delete_recent_project(&self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
if let Some(selected_match) = self.matches.get(ix) {
@ -340,23 +396,13 @@ impl RecentProjectsDelegate {
}
}
struct MatchTooltip {
highlighted_location: HighlightedWorkspaceLocation,
highlighted_location: HighlightedMatchWithPaths,
}
impl Render for MatchTooltip {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
tooltip_container(cx, |div, _| {
div.children(
self.highlighted_location
.paths
.clone()
.into_iter()
.map(|path| {
HighlightedLabel::new(path.text, path.highlight_positions)
.size(LabelSize::Small)
.color(Color::Muted)
}),
)
self.highlighted_location.render_paths_children(div)
})
}
}