Push entry changes out further

Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
Cole Miller 2024-12-20 19:38:21 -05:00
parent 0d8b5e1981
commit 942bedb855
10 changed files with 838 additions and 511 deletions

1
Cargo.lock generated
View file

@ -8430,6 +8430,7 @@ dependencies = [
"editor",
"file_icons",
"fuzzy",
"git",
"gpui",
"itertools 0.13.0",
"language",

View file

@ -19,7 +19,7 @@ use gpui::{
};
use language::{Buffer, BufferRow};
use multi_buffer::{ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer};
use project::{Entry, Project, ProjectEntryId, ProjectPath, WorktreeId};
use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
use text::{OffsetRangeExt, ToPoint};
use theme::ActiveTheme;
use ui::prelude::*;
@ -194,14 +194,27 @@ impl ProjectDiffEditor {
let open_tasks = project
.update(&mut cx, |project, cx| {
let worktree = project.worktree_for_id(id, cx)?;
let applicable_entries = worktree
.read(cx)
.entries(false, 0)
.filter(|entry| !entry.is_external)
.filter(|entry| entry.is_file())
.filter_map(|entry| Some((entry.git_status?, entry)))
.filter_map(|(git_status, entry)| {
Some((git_status, entry.id, project.path_for_entry(entry.id, cx)?))
let snapshot = worktree.read(cx).snapshot();
let applicable_entries = snapshot
.repositories()
.filter_map(|(work_dir, _entry)| {
Some((work_dir, snapshot.git_status(work_dir)?))
})
.flat_map(|(work_dir, statuses)| {
statuses.into_iter().map(|git_entry| {
(git_entry.git_status, work_dir.join(git_entry.path))
})
})
.filter_map(|(status, path)| {
let id = snapshot.entry_for_path(&path)?.id;
Some((
status,
id,
ProjectPath {
worktree_id: snapshot.id(),
path: path.into(),
},
))
})
.collect::<Vec<_>>();
Some(

View file

@ -96,12 +96,18 @@ impl Item for ImageView {
fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement {
let project_path = self.image_item.read(cx).project_path(cx);
let label_color = if ItemSettings::get_global(cx).git_status {
let git_status = self
.project
.read(cx)
.project_path_git_status(&project_path, cx);
self.project
.read(cx)
.entry_for_path(&project_path, cx)
.map(|entry| {
entry_git_aware_label_color(entry.git_status, entry.is_ignored, params.selected)
entry_git_aware_label_color(git_status, entry.is_ignored, params.selected)
})
.unwrap_or_else(|| params.text_color())
} else {

View file

@ -19,8 +19,9 @@ db.workspace = true
editor.workspace = true
file_icons.workspace = true
fuzzy.workspace = true
itertools.workspace = true
git.workspace = true
gpui.workspace = true
itertools.workspace = true
language.workspace = true
log.workspace = true
menu.workspace = true
@ -36,8 +37,8 @@ smol.workspace = true
theme.workspace = true
ui.workspace = true
util.workspace = true
worktree.workspace = true
workspace.workspace = true
worktree.workspace = true
[dev-dependencies]
search = { workspace = true, features = ["test-support"] }

File diff suppressed because it is too large Load diff

View file

@ -39,7 +39,10 @@ use futures::{
pub use image_store::{ImageItem, ImageStore};
use image_store::{ImageItemEvent, ImageStoreEvent};
use git::{blame::Blame, repository::GitRepository};
use git::{
blame::Blame,
repository::{GitFileStatus, GitRepository},
};
use gpui::{
AnyModel, AppContext, AsyncAppContext, BorrowAppContext, Context as _, EventEmitter, Hsla,
Model, ModelContext, SharedString, Task, WeakModel, WindowContext,
@ -1432,6 +1435,15 @@ impl Project {
.unwrap_or(false)
}
pub fn project_path_git_status(
&self,
project_path: &ProjectPath,
cx: &AppContext,
) -> Option<GitFileStatus> {
self.worktree_for_id(project_path.worktree_id, cx)
.and_then(|worktree| worktree.read(cx).status_for_file(&project_path.path))
}
pub fn visibility_for_paths(&self, paths: &[PathBuf], cx: &AppContext) -> Option<bool> {
paths
.iter()
@ -4444,11 +4456,11 @@ impl Completion {
}
}
pub fn sort_worktree_entries(entries: &mut [Entry]) {
pub fn sort_worktree_entries(entries: &mut [(Entry, Option<GitFileStatus>)]) {
entries.sort_by(|entry_a, entry_b| {
compare_paths(
(&entry_a.path, entry_a.is_file()),
(&entry_b.path, entry_b.is_file()),
(&entry_a.0.path, entry_a.0.is_file()),
(&entry_b.0.path, entry_b.0.is_file()),
)
});
}

View file

@ -379,7 +379,7 @@ impl WorktreeStore {
cx.subscribe(
worktree,
|this, _, event: &worktree::Event, cx| match event {
|_this, _, event: &worktree::Event, cx| match event {
worktree::Event::UpdatedGitRepositories(_) => {
cx.emit(WorktreeStoreEvent::GitRepositoryUpdated);
}

View file

@ -76,7 +76,11 @@ pub struct ProjectPanel {
// An update loop that keeps incrementing/decrementing scroll offset while there is a dragged entry that's
// hovered over the start/end of a list.
hover_scroll_task: Option<Task<()>>,
visible_entries: Vec<(WorktreeId, Vec<Entry>, OnceCell<HashSet<Arc<Path>>>)>,
visible_entries: Vec<(
WorktreeId,
Vec<(Entry, Option<GitFileStatus>)>,
OnceCell<HashSet<Arc<Path>>>,
)>,
/// Maps from leaf project entry ID to the currently selected ancestor.
/// Relevant only for auto-fold dirs, where a single project panel entry may actually consist of several
/// project entries (and all non-leaf nodes are guaranteed to be directories).
@ -889,7 +893,7 @@ impl ProjectPanel {
let (worktree_id, worktree_entries, _) = &self.visible_entries[worktree_ix];
let selection = SelectedEntry {
worktree_id: *worktree_id,
entry_id: worktree_entries[entry_ix].id,
entry_id: worktree_entries[entry_ix].0.id,
};
self.selection = Some(selection);
if cx.modifiers().shift {
@ -1359,7 +1363,7 @@ impl ProjectPanel {
let parent_entry = worktree.entry_for_path(parent_path)?;
// Remove all siblings that are being deleted except the last marked entry
let mut siblings: Vec<Entry> = worktree
let mut siblings: Vec<_> = worktree
.snapshot()
.child_entries(parent_path)
.filter(|sibling| {
@ -1369,13 +1373,14 @@ impl ProjectPanel {
entry_id: sibling.id,
})
})
.map(|sibling| &(*sibling, None))
.cloned()
.collect();
project::sort_worktree_entries(&mut siblings);
let sibling_entry_index = siblings
.iter()
.position(|sibling| sibling.id == latest_entry.id)?;
.position(|sibling| sibling.0.id == latest_entry.0.id)?;
if let Some(next_sibling) = sibling_entry_index
.checked_add(1)
@ -1383,7 +1388,7 @@ impl ProjectPanel {
{
return Some(SelectedEntry {
worktree_id,
entry_id: next_sibling.id,
entry_id: next_sibling.0.id,
});
}
if let Some(prev_sibling) = sibling_entry_index
@ -1392,7 +1397,7 @@ impl ProjectPanel {
{
return Some(SelectedEntry {
worktree_id,
entry_id: prev_sibling.id,
entry_id: prev_sibling.0.id,
});
}
// No neighbour sibling found, fall back to parent
@ -1486,7 +1491,7 @@ impl ProjectPanel {
if let Some(entry) = worktree_entries.get(entry_ix) {
let selection = SelectedEntry {
worktree_id: *worktree_id,
entry_id: entry.id,
entry_id: entry.0.id,
};
self.selection = Some(selection);
if cx.modifiers().shift {
@ -1506,7 +1511,7 @@ impl ProjectPanel {
let selection = self.find_entry(
self.selection.as_ref(),
true,
|entry, worktree_id| {
|entry, _, worktree_id| {
(self.selection.is_none()
|| self.selection.is_some_and(|selection| {
if selection.worktree_id == worktree_id {
@ -1536,7 +1541,7 @@ impl ProjectPanel {
let selection = self.find_entry(
self.selection.as_ref(),
false,
|entry, worktree_id| {
|entry, _, worktree_id| {
(self.selection.is_none()
|| self.selection.is_some_and(|selection| {
if selection.worktree_id == worktree_id {
@ -1566,7 +1571,7 @@ impl ProjectPanel {
let selection = self.find_entry(
self.selection.as_ref(),
true,
|entry, worktree_id| {
|entry, git_status, worktree_id| {
(self.selection.is_none()
|| self.selection.is_some_and(|selection| {
if selection.worktree_id == worktree_id {
@ -1576,9 +1581,7 @@ impl ProjectPanel {
}
}))
&& entry.is_file()
&& entry
.git_status
.is_some_and(|status| matches!(status, GitFileStatus::Modified))
&& git_status.is_some_and(|status| matches!(status, GitFileStatus::Modified))
},
cx,
);
@ -1646,7 +1649,7 @@ impl ProjectPanel {
let selection = self.find_entry(
self.selection.as_ref(),
true,
|entry, worktree_id| {
|entry, git_status, worktree_id| {
(self.selection.is_none()
|| self.selection.is_some_and(|selection| {
if selection.worktree_id == worktree_id {
@ -1656,9 +1659,7 @@ impl ProjectPanel {
}
}))
&& entry.is_file()
&& entry
.git_status
.is_some_and(|status| matches!(status, GitFileStatus::Modified))
&& git_status.is_some_and(|status| matches!(status, GitFileStatus::Modified))
},
cx,
);
@ -2109,7 +2110,7 @@ impl ProjectPanel {
{
if *worktree_id == selection.worktree_id {
for entry in worktree_entries {
if entry.id == selection.entry_id {
if entry.0.id == selection.entry_id {
return Some((worktree_index, entry_index, visible_entries_index));
} else {
visible_entries_index += 1;
@ -2457,8 +2458,13 @@ impl ProjectPanel {
entry_iter.advance();
}
snapshot.propagate_git_statuses(&mut visible_worktree_entries);
let git_statuses = snapshot.propagate_git_statuses(&mut visible_worktree_entries);
let mut visible_worktree_entries = visible_worktree_entries
.into_iter()
.zip(git_statuses.into_iter())
.collect::<Vec<_>>();
project::sort_worktree_entries(&mut visible_worktree_entries);
self.visible_entries
.push((worktree_id, visible_worktree_entries, OnceCell::new()));
}
@ -2469,7 +2475,7 @@ impl ProjectPanel {
if worktree_id == *id {
entries
.iter()
.position(|entry| entry.id == project_entry_id)
.position(|entry| entry.0.id == project_entry_id)
} else {
visited_worktrees_length += entries.len();
None
@ -2648,19 +2654,19 @@ impl ProjectPanel {
return visible_worktree_entries
.iter()
.enumerate()
.find(|(_, entry)| entry.id == entry_id)
.find(|(_, entry)| entry.0.id == entry_id)
.map(|(ix, _)| (worktree_ix, ix, total_ix + ix));
}
None
}
fn entry_at_index(&self, index: usize) -> Option<(WorktreeId, &Entry)> {
fn entry_at_index(&self, index: usize) -> Option<(WorktreeId, Option<GitFileStatus>, &Entry)> {
let mut offset = 0;
for (worktree_id, visible_worktree_entries, _) in &self.visible_entries {
if visible_worktree_entries.len() > offset + index {
return visible_worktree_entries
.get(index)
.map(|entry| (*worktree_id, entry));
.map(|(entry, git_file_status)| (*worktree_id, *git_file_status, entry));
}
offset += visible_worktree_entries.len();
}
@ -2689,11 +2695,11 @@ impl ProjectPanel {
let entries = entries_paths.get_or_init(|| {
visible_worktree_entries
.iter()
.map(|e| (e.path.clone()))
.map(|e| (e.0.path.clone()))
.collect()
});
for entry in visible_worktree_entries[entry_range].iter() {
callback(entry, entries, cx);
callback(&entry.0, entries, cx);
}
ix = end_ix;
}
@ -2738,11 +2744,11 @@ impl ProjectPanel {
let entries = entries_paths.get_or_init(|| {
visible_worktree_entries
.iter()
.map(|e| (e.path.clone()))
.map(|e| (e.0.path.clone()))
.collect()
});
for entry in visible_worktree_entries[entry_range].iter() {
let status = git_status_setting.then_some(entry.git_status).flatten();
for (entry, git_status) in visible_worktree_entries[entry_range].iter() {
let status = git_status_setting.then_some(*git_status).flatten();
let is_expanded = expanded_entry_ids.binary_search(&entry.id).is_ok();
let icon = match entry.kind {
EntryKind::File => {
@ -2762,7 +2768,7 @@ impl ProjectPanel {
};
let (depth, difference) =
ProjectPanel::calculate_depth_and_difference(entry, entries);
ProjectPanel::calculate_depth_and_difference(&entry, entries);
let filename = match difference {
diff if diff > 1 => entry
@ -2891,7 +2897,7 @@ impl ProjectPanel {
worktree_id: WorktreeId,
reverse_search: bool,
only_visible_entries: bool,
predicate: impl Fn(&Entry, WorktreeId) -> bool,
predicate: impl Fn(&Entry, Option<GitFileStatus>, WorktreeId) -> bool,
cx: &mut ViewContext<Self>,
) -> Option<Entry> {
if only_visible_entries {
@ -2908,15 +2914,19 @@ impl ProjectPanel {
.clone();
return utils::ReversibleIterable::new(entries.iter(), reverse_search)
.find(|ele| predicate(ele, worktree_id))
.cloned();
.find(|ele| predicate(&ele.0, ele.1, worktree_id))
.map(|ele| ele.0);
}
let worktree = self.project.read(cx).worktree_for_id(worktree_id, cx)?;
worktree.update(cx, |tree, _| {
utils::ReversibleIterable::new(tree.entries(true, 0usize), reverse_search)
.find_single_ended(|ele| predicate(ele, worktree_id))
.cloned()
utils::ReversibleIterable::new(
tree.entries(true, 0usize)
.with_git_statuses(&tree.snapshot()),
reverse_search,
)
.find_single_ended(|ele| predicate(&ele.0, ele.1, worktree_id))
.map(|ele| ele.0)
})
}
@ -2924,7 +2934,7 @@ impl ProjectPanel {
&self,
start: Option<&SelectedEntry>,
reverse_search: bool,
predicate: impl Fn(&Entry, WorktreeId) -> bool,
predicate: impl Fn(&Entry, Option<GitFileStatus>, WorktreeId) -> bool,
cx: &mut ViewContext<Self>,
) -> Option<SelectedEntry> {
let mut worktree_ids: Vec<_> = self
@ -2946,7 +2956,11 @@ impl ProjectPanel {
let root_entry = tree.root_entry()?;
let tree_id = tree.id();
let mut first_iter = tree.traverse_from_path(true, true, true, entry.path.as_ref());
// TOOD: Expose the all statuses cursor, as a wrapper over a Traversal
// The co-iterates the GitEntries as the file entries come through
let mut first_iter = tree
.traverse_from_path(true, true, true, entry.path.as_ref())
.with_git_statuses(tree);
if reverse_search {
first_iter.next();
@ -2954,9 +2968,9 @@ impl ProjectPanel {
let first = first_iter
.enumerate()
.take_until(|(count, ele)| *ele == root_entry && *count != 0usize)
.take_until(|(count, ele)| ele.0 == root_entry && *count != 0usize)
.map(|(_, ele)| ele)
.find(|ele| predicate(ele, tree_id))
.find(|ele| predicate(ele.0, ele.1 tree_id))
.cloned();
let second_iter = tree.entries(true, 0usize);
@ -4012,7 +4026,8 @@ impl Render for ProjectPanel {
if cx.modifiers().secondary() {
let ix = active_indent_guide.offset.y;
let Some((target_entry, worktree)) = maybe!({
let (worktree_id, entry) = this.entry_at_index(ix)?;
let (worktree_id, _git_status, entry) =
this.entry_at_index(ix)?;
let worktree = this
.project
.read(cx)

View file

@ -358,13 +358,14 @@ impl PickerDelegate for TabSwitcherDelegate {
.item
.project_path(cx)
.as_ref()
.and_then(|path| self.project.read(cx).entry_for_path(path, cx))
.map(|entry| {
entry_git_aware_label_color(
entry.git_status,
entry.is_ignored,
selected,
)
.and_then(|path| {
let project = self.project.read(cx);
let entry = project.entry_for_path(path, cx)?;
let git_status = project.project_path_git_status(path, cx);
Some((entry, git_status))
})
.map(|(entry, git_status)| {
entry_git_aware_label_color(git_status, entry.is_ignored, selected)
})
})
.flatten();

View file

@ -284,6 +284,14 @@ impl Default for RepositoryWorkDirectory {
}
}
impl Deref for RepositoryWorkDirectory {
type Target = Path;
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl AsRef<Path> for RepositoryWorkDirectory {
fn as_ref(&self) -> &Path {
self.0.as_ref()
@ -5631,6 +5639,45 @@ impl<'a> Default for TraversalProgress<'a> {
}
}
pub struct GitTraversal<'a> {
// statuses: /AllStatusesCursor<'a, I>,
traversal: Traversal<'a>,
}
pub struct EntryWithGitStatus {
entry: Entry,
git_status: Option<GitFileStatus>,
}
impl Deref for EntryWithGitStatus {
type Target = Entry;
fn deref(&self) -> &Self::Target {
&self.entry
}
}
impl<
'a,
// I: Iterator<Item = (&'a RepositoryWorkDirectory, &'a RepositoryEntry)> + FusedIterator,
> Iterator for GitTraversal<'a>
{
type Item = (Entry, Option<GitFileStatus>);
fn next(&mut self) -> Option<Self::Item> {
todo!()
}
}
impl<
'a,
// I: Iterator<Item = (&'a RepositoryWorkDirectory, &'a RepositoryEntry)> + FusedIterator,
> DoubleEndedIterator for GitTraversal<'a>
{
fn next_back(&mut self) -> Option<Self::Item> {
todo!()
}
}
pub struct Traversal<'a> {
cursor: sum_tree::Cursor<'a, Entry, TraversalProgress<'a>>,
include_ignored: bool,
@ -5659,6 +5706,14 @@ impl<'a> Traversal<'a> {
}
traversal
}
pub fn with_git_statuses(self, snapshot: &'a Snapshot) -> GitTraversal<'a> {
GitTraversal {
// statuses: all_statuses_cursor(snapshot),
traversal: self,
}
}
pub fn advance(&mut self) -> bool {
self.advance_by(1)
}