mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-11 21:13:02 +00:00
speculatively roll this out to API consumers
This commit is contained in:
parent
108a53affc
commit
2ff06cea6e
6 changed files with 199 additions and 156 deletions
|
@ -7,7 +7,7 @@ use std::{
|
|||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use worktree::GitEntry;
|
||||
use worktree::StatusEntry;
|
||||
|
||||
use git::repository::{GitFileStatus, RepoPath};
|
||||
|
||||
|
@ -90,7 +90,7 @@ pub struct GitPanel {
|
|||
// not hidden by folding or such
|
||||
visible_entries: Vec<(
|
||||
WorktreeId,
|
||||
Vec<worktree::GitEntry>,
|
||||
Vec<worktree::StatusEntry>,
|
||||
OnceCell<HashSet<RepoPath>>,
|
||||
)>,
|
||||
width: Option<Pixels>,
|
||||
|
@ -232,7 +232,7 @@ impl GitPanel {
|
|||
}
|
||||
|
||||
fn calculate_depth_and_difference(
|
||||
entry: &GitEntry,
|
||||
entry: &StatusEntry,
|
||||
visible_worktree_entries: &HashSet<RepoPath>,
|
||||
) -> (usize, usize) {
|
||||
let (depth, difference) = entry
|
||||
|
|
|
@ -22,7 +22,6 @@ use editor::{
|
|||
};
|
||||
use file_icons::FileIcons;
|
||||
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
||||
use git::repository::GitFileStatus;
|
||||
use gpui::{
|
||||
actions, anchored, deferred, div, point, px, size, uniform_list, Action, AnyElement,
|
||||
AppContext, AssetSource, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div,
|
||||
|
@ -57,7 +56,7 @@ use workspace::{
|
|||
},
|
||||
OpenInTerminal, WeakItemHandle, Workspace,
|
||||
};
|
||||
use worktree::{Entry, ProjectEntryId, WorktreeId};
|
||||
use worktree::{Entry, GitEntry, ProjectEntryId, WorktreeId};
|
||||
|
||||
actions!(
|
||||
outline_panel,
|
||||
|
@ -352,8 +351,7 @@ enum ExcerptOutlines {
|
|||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
struct FoldedDirsEntry {
|
||||
worktree_id: WorktreeId,
|
||||
entries: Vec<Entry>,
|
||||
git_file_statuses: Vec<Option<GitFileStatus>>,
|
||||
entries: Vec<GitEntry>,
|
||||
}
|
||||
|
||||
// TODO: collapse the inner enums into panel entry
|
||||
|
@ -581,10 +579,9 @@ impl OutlineEntry {
|
|||
#[derive(Debug, Clone, Eq)]
|
||||
struct FsEntryFile {
|
||||
worktree_id: WorktreeId,
|
||||
entry: Entry,
|
||||
entry: GitEntry,
|
||||
buffer_id: BufferId,
|
||||
excerpts: Vec<ExcerptId>,
|
||||
git_status: Option<GitFileStatus>,
|
||||
}
|
||||
|
||||
impl PartialEq for FsEntryFile {
|
||||
|
@ -604,8 +601,7 @@ impl Hash for FsEntryFile {
|
|||
#[derive(Debug, Clone, Eq)]
|
||||
struct FsEntryDirectory {
|
||||
worktree_id: WorktreeId,
|
||||
entry: Entry,
|
||||
git_status: Option<GitFileStatus>,
|
||||
entry: GitEntry,
|
||||
}
|
||||
|
||||
impl PartialEq for FsEntryDirectory {
|
||||
|
@ -2087,13 +2083,11 @@ impl OutlinePanel {
|
|||
};
|
||||
let (item_id, label_element, icon) = match rendered_entry {
|
||||
FsEntry::File(FsEntryFile {
|
||||
worktree_id,
|
||||
entry,
|
||||
git_status,
|
||||
..
|
||||
worktree_id, entry, ..
|
||||
}) => {
|
||||
let name = self.entry_name(worktree_id, entry, cx);
|
||||
let color = entry_git_aware_label_color(*git_status, entry.is_ignored, is_active);
|
||||
let color =
|
||||
entry_git_aware_label_color(entry.git_status, entry.is_ignored, is_active);
|
||||
let icon = if settings.file_icons {
|
||||
FileIcons::get_icon(&entry.path, cx)
|
||||
.map(|icon_path| Icon::from_path(icon_path).color(color).into_any_element())
|
||||
|
@ -2113,18 +2107,18 @@ impl OutlinePanel {
|
|||
icon.unwrap_or_else(empty_icon),
|
||||
)
|
||||
}
|
||||
FsEntry::Directory(FsEntryDirectory {
|
||||
worktree_id,
|
||||
FsEntry::Directory(directory) => {
|
||||
let name = self.entry_name(&directory.worktree_id, &directory.entry, cx);
|
||||
|
||||
entry,
|
||||
git_status,
|
||||
}) => {
|
||||
let name = self.entry_name(worktree_id, entry, cx);
|
||||
|
||||
let is_expanded = !self
|
||||
.collapsed_entries
|
||||
.contains(&CollapsedEntry::Dir(*worktree_id, entry.id));
|
||||
let color = entry_git_aware_label_color(*git_status, entry.is_ignored, is_active);
|
||||
let is_expanded = !self.collapsed_entries.contains(&CollapsedEntry::Dir(
|
||||
directory.worktree_id,
|
||||
directory.entry.id,
|
||||
));
|
||||
let color = entry_git_aware_label_color(
|
||||
directory.entry.git_status,
|
||||
directory.entry.is_ignored,
|
||||
is_active,
|
||||
);
|
||||
let icon = if settings.folder_icons {
|
||||
FileIcons::get_folder_icon(is_expanded, cx)
|
||||
} else {
|
||||
|
@ -2133,7 +2127,7 @@ impl OutlinePanel {
|
|||
.map(Icon::from_path)
|
||||
.map(|icon| icon.color(color).into_any_element());
|
||||
(
|
||||
ElementId::from(entry.id.to_proto() as usize),
|
||||
ElementId::from(directory.entry.id.to_proto() as usize),
|
||||
HighlightedLabel::new(
|
||||
name,
|
||||
string_match
|
||||
|
@ -2214,7 +2208,10 @@ impl OutlinePanel {
|
|||
.contains(&CollapsedEntry::Dir(folded_dir.worktree_id, dir.id))
|
||||
});
|
||||
let is_ignored = folded_dir.entries.iter().any(|entry| entry.is_ignored);
|
||||
let git_status = folded_dir.git_file_statuses.first().cloned().flatten();
|
||||
let git_status = folded_dir
|
||||
.entries
|
||||
.first()
|
||||
.and_then(|entry| entry.git_status);
|
||||
let color = entry_git_aware_label_color(git_status, is_ignored, is_active);
|
||||
let icon = if settings.folder_icons {
|
||||
FileIcons::get_folder_icon(is_expanded, cx)
|
||||
|
@ -2520,7 +2517,7 @@ impl OutlinePanel {
|
|||
let mut processed_external_buffers = HashSet::default();
|
||||
let mut new_worktree_entries = HashMap::<
|
||||
WorktreeId,
|
||||
(worktree::Snapshot, HashMap<ProjectEntryId, Entry>),
|
||||
(worktree::Snapshot, HashMap<ProjectEntryId, GitEntry>),
|
||||
>::default();
|
||||
let mut worktree_excerpts = HashMap::<
|
||||
WorktreeId,
|
||||
|
@ -2561,12 +2558,13 @@ impl OutlinePanel {
|
|||
|
||||
match entry_id.and_then(|id| worktree.entry_for_id(id)).cloned() {
|
||||
Some(entry) => {
|
||||
let mut traversal = worktree.traverse_from_path(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
entry.path.as_ref(),
|
||||
);
|
||||
let entry = GitEntry {
|
||||
git_status: worktree.status_for_file(&entry.path),
|
||||
entry,
|
||||
};
|
||||
let mut traversal = worktree
|
||||
.traverse_from_path(true, true, true, entry.path.as_ref())
|
||||
.with_git_statuses();
|
||||
|
||||
let mut entries_to_add = HashMap::default();
|
||||
worktree_excerpts
|
||||
|
@ -2598,7 +2596,7 @@ impl OutlinePanel {
|
|||
.is_none();
|
||||
if new_entry_added && traversal.back_to_parent() {
|
||||
if let Some(parent_entry) = traversal.entry() {
|
||||
current_entry = parent_entry.clone();
|
||||
current_entry = parent_entry.to_owned();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -2636,15 +2634,14 @@ impl OutlinePanel {
|
|||
let mut entries = entries.into_values().collect::<Vec<_>>();
|
||||
// For a proper git status propagation, we have to keep the entries sorted lexicographically.
|
||||
entries.sort_by(|a, b| a.path.as_ref().cmp(b.path.as_ref()));
|
||||
let statuses = worktree_snapshot.propagate_git_statuses(&entries);
|
||||
let entries = entries.into_iter().zip(statuses).collect::<Vec<_>>();
|
||||
worktree_snapshot.propagate_git_statuses(&mut entries);
|
||||
(worktree_id, entries)
|
||||
})
|
||||
.flat_map(|(worktree_id, entries)| {
|
||||
{
|
||||
entries
|
||||
.into_iter()
|
||||
.filter_map(|(entry, git_status)| {
|
||||
.filter_map(|entry| {
|
||||
if auto_fold_dirs {
|
||||
if let Some(parent) = entry.path.parent() {
|
||||
let children = new_children_count
|
||||
|
@ -2664,7 +2661,6 @@ impl OutlinePanel {
|
|||
Some(FsEntry::Directory(FsEntryDirectory {
|
||||
worktree_id,
|
||||
entry,
|
||||
git_status,
|
||||
}))
|
||||
} else {
|
||||
let (buffer_id, excerpts) = worktree_excerpts
|
||||
|
@ -2677,7 +2673,6 @@ impl OutlinePanel {
|
|||
buffer_id,
|
||||
entry,
|
||||
excerpts,
|
||||
git_status,
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
@ -3461,7 +3456,6 @@ impl OutlinePanel {
|
|||
FoldedDirsEntry {
|
||||
worktree_id: directory_entry.worktree_id,
|
||||
entries: vec![directory_entry.entry.clone()],
|
||||
git_file_statuses: vec![directory_entry.git_status],
|
||||
},
|
||||
))
|
||||
};
|
||||
|
@ -3472,7 +3466,6 @@ impl OutlinePanel {
|
|||
FoldedDirsEntry {
|
||||
worktree_id: directory_entry.worktree_id,
|
||||
entries: vec![directory_entry.entry.clone()],
|
||||
git_file_statuses: vec![directory_entry.git_status],
|
||||
},
|
||||
));
|
||||
}
|
||||
|
@ -3715,7 +3708,6 @@ impl OutlinePanel {
|
|||
1 => PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory {
|
||||
worktree_id: folded_dirs_entry.worktree_id,
|
||||
entry: folded_dirs_entry.entries[0].clone(),
|
||||
git_status: folded_dirs_entry.git_file_statuses[0],
|
||||
})),
|
||||
_ => entry,
|
||||
}
|
||||
|
@ -3778,7 +3770,7 @@ impl OutlinePanel {
|
|||
|
||||
fn dir_names_string(
|
||||
&self,
|
||||
entries: &[Entry],
|
||||
entries: &[GitEntry],
|
||||
worktree_id: WorktreeId,
|
||||
cx: &AppContext,
|
||||
) -> String {
|
||||
|
@ -4521,7 +4513,7 @@ impl OutlinePanel {
|
|||
fn buffers_inside_directory(
|
||||
&self,
|
||||
dir_worktree: WorktreeId,
|
||||
dir_entry: &Entry,
|
||||
dir_entry: &GitEntry,
|
||||
) -> HashSet<BufferId> {
|
||||
if !dir_entry.is_dir() {
|
||||
debug_panic!("buffers_inside_directory called on a non-directory entry {dir_entry:?}");
|
||||
|
|
|
@ -4456,11 +4456,13 @@ impl Completion {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn sort_worktree_entries(entries: &mut [(Entry, Option<GitFileStatus>)]) {
|
||||
pub fn sort_worktree_entries(entries: &mut [impl AsRef<Entry>]) {
|
||||
entries.sort_by(|entry_a, entry_b| {
|
||||
let entry_a = entry_a.as_ref();
|
||||
let entry_b = entry_b.as_ref();
|
||||
compare_paths(
|
||||
(&entry_a.0.path, entry_a.0.is_file()),
|
||||
(&entry_b.0.path, entry_b.0.is_file()),
|
||||
(&entry_a.path, entry_a.is_file()),
|
||||
(&entry_b.path, entry_b.is_file()),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ use workspace::{
|
|||
notifications::{DetachAndPromptErr, NotifyTaskExt},
|
||||
DraggedSelection, OpenInTerminal, PreviewTabsSettings, SelectedEntry, Workspace,
|
||||
};
|
||||
use worktree::CreatedEntry;
|
||||
use worktree::{CreatedEntry, GitEntry, GitEntryRef};
|
||||
|
||||
const PROJECT_PANEL_KEY: &str = "ProjectPanel";
|
||||
const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX;
|
||||
|
@ -76,11 +76,7 @@ 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, Option<GitFileStatus>)>,
|
||||
OnceCell<HashSet<Arc<Path>>>,
|
||||
)>,
|
||||
visible_entries: Vec<(WorktreeId, Vec<GitEntry>, 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).
|
||||
|
@ -893,7 +889,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].0.id,
|
||||
entry_id: worktree_entries[entry_ix].id,
|
||||
};
|
||||
self.selection = Some(selection);
|
||||
if cx.modifiers().shift {
|
||||
|
@ -1366,6 +1362,7 @@ impl ProjectPanel {
|
|||
let mut siblings: Vec<_> = worktree
|
||||
.snapshot()
|
||||
.child_entries(parent_path)
|
||||
.with_git_statuses()
|
||||
.filter(|sibling| {
|
||||
sibling.id == latest_entry.id
|
||||
|| !marked_entries_in_worktree.contains(&&SelectedEntry {
|
||||
|
@ -1373,14 +1370,13 @@ impl ProjectPanel {
|
|||
entry_id: sibling.id,
|
||||
})
|
||||
})
|
||||
.map(|sibling| &(*sibling, None))
|
||||
.cloned()
|
||||
.map(|entry| entry.to_owned())
|
||||
.collect();
|
||||
|
||||
project::sort_worktree_entries(&mut siblings);
|
||||
let sibling_entry_index = siblings
|
||||
.iter()
|
||||
.position(|sibling| sibling.0.id == latest_entry.0.id)?;
|
||||
.position(|sibling| sibling.id == latest_entry.id)?;
|
||||
|
||||
if let Some(next_sibling) = sibling_entry_index
|
||||
.checked_add(1)
|
||||
|
@ -1388,7 +1384,7 @@ impl ProjectPanel {
|
|||
{
|
||||
return Some(SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id: next_sibling.0.id,
|
||||
entry_id: next_sibling.id,
|
||||
});
|
||||
}
|
||||
if let Some(prev_sibling) = sibling_entry_index
|
||||
|
@ -1397,7 +1393,7 @@ impl ProjectPanel {
|
|||
{
|
||||
return Some(SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id: prev_sibling.0.id,
|
||||
entry_id: prev_sibling.id,
|
||||
});
|
||||
}
|
||||
// No neighbour sibling found, fall back to parent
|
||||
|
@ -1491,7 +1487,7 @@ impl ProjectPanel {
|
|||
if let Some(entry) = worktree_entries.get(entry_ix) {
|
||||
let selection = SelectedEntry {
|
||||
worktree_id: *worktree_id,
|
||||
entry_id: entry.0.id,
|
||||
entry_id: entry.id,
|
||||
};
|
||||
self.selection = Some(selection);
|
||||
if cx.modifiers().shift {
|
||||
|
@ -1511,7 +1507,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 {
|
||||
|
@ -1541,7 +1537,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 {
|
||||
|
@ -1571,7 +1567,7 @@ impl ProjectPanel {
|
|||
let selection = self.find_entry(
|
||||
self.selection.as_ref(),
|
||||
true,
|
||||
|entry, git_status, worktree_id| {
|
||||
|entry, worktree_id| {
|
||||
(self.selection.is_none()
|
||||
|| self.selection.is_some_and(|selection| {
|
||||
if selection.worktree_id == worktree_id {
|
||||
|
@ -1581,7 +1577,9 @@ impl ProjectPanel {
|
|||
}
|
||||
}))
|
||||
&& entry.is_file()
|
||||
&& git_status.is_some_and(|status| matches!(status, GitFileStatus::Modified))
|
||||
&& entry
|
||||
.git_status
|
||||
.is_some_and(|status| matches!(status, GitFileStatus::Modified))
|
||||
},
|
||||
cx,
|
||||
);
|
||||
|
@ -1649,7 +1647,7 @@ impl ProjectPanel {
|
|||
let selection = self.find_entry(
|
||||
self.selection.as_ref(),
|
||||
true,
|
||||
|entry, git_status, worktree_id| {
|
||||
|entry, worktree_id| {
|
||||
(self.selection.is_none()
|
||||
|| self.selection.is_some_and(|selection| {
|
||||
if selection.worktree_id == worktree_id {
|
||||
|
@ -1659,7 +1657,9 @@ impl ProjectPanel {
|
|||
}
|
||||
}))
|
||||
&& entry.is_file()
|
||||
&& git_status.is_some_and(|status| matches!(status, GitFileStatus::Modified))
|
||||
&& entry
|
||||
.git_status
|
||||
.is_some_and(|status| matches!(status, GitFileStatus::Modified))
|
||||
},
|
||||
cx,
|
||||
);
|
||||
|
@ -2110,7 +2110,7 @@ impl ProjectPanel {
|
|||
{
|
||||
if *worktree_id == selection.worktree_id {
|
||||
for entry in worktree_entries {
|
||||
if entry.0.id == selection.entry_id {
|
||||
if entry.id == selection.entry_id {
|
||||
return Some((worktree_index, entry_index, visible_entries_index));
|
||||
} else {
|
||||
visible_entries_index += 1;
|
||||
|
@ -2308,7 +2308,7 @@ impl ProjectPanel {
|
|||
}
|
||||
|
||||
let mut visible_worktree_entries = Vec::new();
|
||||
let mut entry_iter = snapshot.entries(true, 0);
|
||||
let mut entry_iter = snapshot.entries(true, 0).with_git_statuses();
|
||||
let mut auto_folded_ancestors = vec![];
|
||||
while let Some(entry) = entry_iter.entry() {
|
||||
if auto_collapse_dirs && entry.kind.is_dir() {
|
||||
|
@ -2350,7 +2350,7 @@ impl ProjectPanel {
|
|||
}
|
||||
}
|
||||
auto_folded_ancestors.clear();
|
||||
visible_worktree_entries.push(entry.clone());
|
||||
visible_worktree_entries.push(entry.to_owned());
|
||||
let precedes_new_entry = if let Some(new_entry_id) = new_entry_parent_id {
|
||||
entry.id == new_entry_id || {
|
||||
self.ancestors.get(&entry.id).map_or(false, |entries| {
|
||||
|
@ -2364,24 +2364,27 @@ impl ProjectPanel {
|
|||
false
|
||||
};
|
||||
if precedes_new_entry {
|
||||
visible_worktree_entries.push(Entry {
|
||||
id: NEW_ENTRY_ID,
|
||||
kind: new_entry_kind,
|
||||
path: entry.path.join("\0").into(),
|
||||
inode: 0,
|
||||
mtime: entry.mtime,
|
||||
size: entry.size,
|
||||
is_ignored: entry.is_ignored,
|
||||
is_external: false,
|
||||
is_private: false,
|
||||
is_always_included: entry.is_always_included,
|
||||
canonical_path: entry.canonical_path.clone(),
|
||||
char_bag: entry.char_bag,
|
||||
is_fifo: entry.is_fifo,
|
||||
visible_worktree_entries.push(GitEntry {
|
||||
entry: Entry {
|
||||
id: NEW_ENTRY_ID,
|
||||
kind: new_entry_kind,
|
||||
path: entry.path.join("\0").into(),
|
||||
inode: 0,
|
||||
mtime: entry.mtime,
|
||||
size: entry.size,
|
||||
is_ignored: entry.is_ignored,
|
||||
is_external: false,
|
||||
is_private: false,
|
||||
is_always_included: entry.is_always_included,
|
||||
canonical_path: entry.canonical_path.clone(),
|
||||
char_bag: entry.char_bag,
|
||||
is_fifo: entry.is_fifo,
|
||||
},
|
||||
git_status: entry.git_status,
|
||||
});
|
||||
}
|
||||
let worktree_abs_path = worktree.read(cx).abs_path();
|
||||
let (depth, path) = if Some(entry) == worktree.read(cx).root_entry() {
|
||||
let (depth, path) = if Some(entry.entry) == worktree.read(cx).root_entry() {
|
||||
let Some(path_name) = worktree_abs_path
|
||||
.file_name()
|
||||
.with_context(|| {
|
||||
|
@ -2458,11 +2461,7 @@ impl ProjectPanel {
|
|||
entry_iter.advance();
|
||||
}
|
||||
|
||||
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<_>>();
|
||||
snapshot.propagate_git_statuses(&mut visible_worktree_entries);
|
||||
project::sort_worktree_entries(&mut visible_worktree_entries);
|
||||
|
||||
self.visible_entries
|
||||
|
@ -2475,7 +2474,7 @@ impl ProjectPanel {
|
|||
if worktree_id == *id {
|
||||
entries
|
||||
.iter()
|
||||
.position(|entry| entry.0.id == project_entry_id)
|
||||
.position(|entry| entry.id == project_entry_id)
|
||||
} else {
|
||||
visited_worktrees_length += entries.len();
|
||||
None
|
||||
|
@ -2654,19 +2653,19 @@ impl ProjectPanel {
|
|||
return visible_worktree_entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, entry)| entry.0.id == entry_id)
|
||||
.find(|(_, entry)| entry.id == entry_id)
|
||||
.map(|(ix, _)| (worktree_ix, ix, total_ix + ix));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn entry_at_index(&self, index: usize) -> Option<(WorktreeId, Option<GitFileStatus>, &Entry)> {
|
||||
fn entry_at_index(&self, index: usize) -> Option<(WorktreeId, GitEntryRef)> {
|
||||
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, git_file_status)| (*worktree_id, *git_file_status, entry));
|
||||
.map(|entry| (*worktree_id, entry.to_ref()));
|
||||
}
|
||||
offset += visible_worktree_entries.len();
|
||||
}
|
||||
|
@ -2695,11 +2694,11 @@ impl ProjectPanel {
|
|||
let entries = entries_paths.get_or_init(|| {
|
||||
visible_worktree_entries
|
||||
.iter()
|
||||
.map(|e| (e.0.path.clone()))
|
||||
.map(|e| (e.path.clone()))
|
||||
.collect()
|
||||
});
|
||||
for entry in visible_worktree_entries[entry_range].iter() {
|
||||
callback(&entry.0, entries, cx);
|
||||
callback(&entry, entries, cx);
|
||||
}
|
||||
ix = end_ix;
|
||||
}
|
||||
|
@ -2744,11 +2743,11 @@ impl ProjectPanel {
|
|||
let entries = entries_paths.get_or_init(|| {
|
||||
visible_worktree_entries
|
||||
.iter()
|
||||
.map(|e| (e.0.path.clone()))
|
||||
.map(|e| (e.path.clone()))
|
||||
.collect()
|
||||
});
|
||||
for (entry, git_status) in visible_worktree_entries[entry_range].iter() {
|
||||
let status = git_status_setting.then_some(*git_status).flatten();
|
||||
for entry in visible_worktree_entries[entry_range].iter() {
|
||||
let status = git_status_setting.then_some(entry.git_status).flatten();
|
||||
let is_expanded = expanded_entry_ids.binary_search(&entry.id).is_ok();
|
||||
let icon = match entry.kind {
|
||||
EntryKind::File => {
|
||||
|
@ -2897,9 +2896,9 @@ impl ProjectPanel {
|
|||
worktree_id: WorktreeId,
|
||||
reverse_search: bool,
|
||||
only_visible_entries: bool,
|
||||
predicate: impl Fn(&Entry, Option<GitFileStatus>, WorktreeId) -> bool,
|
||||
predicate: impl Fn(GitEntryRef, WorktreeId) -> bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Entry> {
|
||||
) -> Option<GitEntry> {
|
||||
if only_visible_entries {
|
||||
let entries = self
|
||||
.visible_entries
|
||||
|
@ -2914,19 +2913,18 @@ impl ProjectPanel {
|
|||
.clone();
|
||||
|
||||
return utils::ReversibleIterable::new(entries.iter(), reverse_search)
|
||||
.find(|ele| predicate(&ele.0, ele.1, worktree_id))
|
||||
.map(|ele| ele.0);
|
||||
.find(|ele| predicate(ele.to_ref(), worktree_id))
|
||||
.cloned();
|
||||
}
|
||||
|
||||
let worktree = self.project.read(cx).worktree_for_id(worktree_id, cx)?;
|
||||
worktree.update(cx, |tree, _| {
|
||||
utils::ReversibleIterable::new(
|
||||
tree.entries(true, 0usize)
|
||||
.with_git_statuses(&tree.snapshot()),
|
||||
tree.entries(true, 0usize).with_git_statuses(),
|
||||
reverse_search,
|
||||
)
|
||||
.find_single_ended(|ele| predicate(&ele.0, ele.1, worktree_id))
|
||||
.map(|ele| ele.0)
|
||||
.find_single_ended(|ele| predicate(*ele, worktree_id))
|
||||
.map(|ele| ele.to_owned())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -2934,7 +2932,7 @@ impl ProjectPanel {
|
|||
&self,
|
||||
start: Option<&SelectedEntry>,
|
||||
reverse_search: bool,
|
||||
predicate: impl Fn(&Entry, Option<GitFileStatus>, WorktreeId) -> bool,
|
||||
predicate: impl Fn(GitEntryRef, WorktreeId) -> bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<SelectedEntry> {
|
||||
let mut worktree_ids: Vec<_> = self
|
||||
|
@ -2956,11 +2954,9 @@ impl ProjectPanel {
|
|||
let root_entry = tree.root_entry()?;
|
||||
let tree_id = tree.id();
|
||||
|
||||
// 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);
|
||||
.with_git_statuses();
|
||||
|
||||
if reverse_search {
|
||||
first_iter.next();
|
||||
|
@ -2968,25 +2964,25 @@ impl ProjectPanel {
|
|||
|
||||
let first = first_iter
|
||||
.enumerate()
|
||||
.take_until(|(count, ele)| ele.0 == root_entry && *count != 0usize)
|
||||
.map(|(_, ele)| ele)
|
||||
.find(|ele| predicate(ele.0, ele.1 tree_id))
|
||||
.cloned();
|
||||
.take_until(|(count, entry)| entry.entry == root_entry && *count != 0usize)
|
||||
.map(|(_, entry)| entry)
|
||||
.find(|ele| predicate(*ele, tree_id))
|
||||
.map(|ele| ele.to_owned());
|
||||
|
||||
let second_iter = tree.entries(true, 0usize);
|
||||
let second_iter = tree.entries(true, 0usize).with_git_statuses();
|
||||
|
||||
let second = if reverse_search {
|
||||
second_iter
|
||||
.take_until(|ele| ele.id == start.entry_id)
|
||||
.filter(|ele| predicate(ele, tree_id))
|
||||
.filter(|ele| predicate(*ele, tree_id))
|
||||
.last()
|
||||
.cloned()
|
||||
.map(|ele| ele.to_owned())
|
||||
} else {
|
||||
second_iter
|
||||
.take_while(|ele| ele.id != start.entry_id)
|
||||
.filter(|ele| predicate(ele, tree_id))
|
||||
.filter(|ele| predicate(*ele, tree_id))
|
||||
.last()
|
||||
.cloned()
|
||||
.map(|ele| ele.to_owned())
|
||||
};
|
||||
|
||||
if reverse_search {
|
||||
|
@ -3043,7 +3039,7 @@ impl ProjectPanel {
|
|||
&self,
|
||||
start: Option<&SelectedEntry>,
|
||||
reverse_search: bool,
|
||||
predicate: impl Fn(&Entry, WorktreeId) -> bool,
|
||||
predicate: impl Fn(GitEntryRef, WorktreeId) -> bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<SelectedEntry> {
|
||||
let mut worktree_ids: Vec<_> = self
|
||||
|
@ -3085,8 +3081,8 @@ impl ProjectPanel {
|
|||
)
|
||||
};
|
||||
|
||||
let first_search = first_iter.find(|ele| predicate(ele, start.worktree_id));
|
||||
let second_search = second_iter.find(|ele| predicate(ele, start.worktree_id));
|
||||
let first_search = first_iter.find(|ele| predicate(ele.to_ref(), start.worktree_id));
|
||||
let second_search = second_iter.find(|ele| predicate(ele.to_ref(), start.worktree_id));
|
||||
|
||||
if first_search.is_some() {
|
||||
return first_search.map(|entry| SelectedEntry {
|
||||
|
@ -4026,8 +4022,7 @@ impl Render for ProjectPanel {
|
|||
if cx.modifiers().secondary() {
|
||||
let ix = active_indent_guide.offset.y;
|
||||
let Some((target_entry, worktree)) = maybe!({
|
||||
let (worktree_id, _git_status, entry) =
|
||||
this.entry_at_index(ix)?;
|
||||
let (worktree_id, entry) = this.entry_at_index(ix)?;
|
||||
let worktree = this
|
||||
.project
|
||||
.read(cx)
|
||||
|
|
|
@ -193,7 +193,7 @@ pub struct RepositoryEntry {
|
|||
/// With this setup, this field would contain 2 entries, like so:
|
||||
/// - my_sub_folder_1/project_root/changed_file_1
|
||||
/// - my_sub_folder_2/changed_file_2
|
||||
pub(crate) git_entries_by_path: SumTree<GitEntry>,
|
||||
pub(crate) git_entries_by_path: SumTree<StatusEntry>,
|
||||
pub(crate) work_directory_id: ProjectEntryId,
|
||||
pub(crate) work_directory: WorkDirectory,
|
||||
pub(crate) branch: Option<Arc<str>>,
|
||||
|
@ -226,7 +226,7 @@ impl RepositoryEntry {
|
|||
self.into()
|
||||
}
|
||||
|
||||
pub fn status(&self) -> impl Iterator<Item = GitEntry> + '_ {
|
||||
pub fn status(&self) -> impl Iterator<Item = StatusEntry> + '_ {
|
||||
self.git_entries_by_path.iter().cloned()
|
||||
}
|
||||
}
|
||||
|
@ -2480,7 +2480,7 @@ impl Snapshot {
|
|||
}
|
||||
|
||||
#[cfg(any(feature = "test-support", test))]
|
||||
pub fn git_satus(&self, work_dir: &Path) -> Option<Vec<GitEntry>> {
|
||||
pub fn git_satus(&self, work_dir: &Path) -> Option<Vec<StatusEntry>> {
|
||||
self.repositories
|
||||
.get(&PathKey(work_dir.into()), &())
|
||||
.map(|repo| repo.status().collect())
|
||||
|
@ -2532,7 +2532,7 @@ impl Snapshot {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn propagate_git_statuses(&self, entries: &[Entry]) -> Vec<Option<GitFileStatus>> {
|
||||
pub fn propagate_git_statuses(&self, entries: &mut [GitEntry]) -> Vec<Option<GitFileStatus>> {
|
||||
let mut cursor = all_statuses_cursor(self);
|
||||
let mut entry_stack = Vec::<(usize, GitStatuses)>::new();
|
||||
|
||||
|
@ -3586,7 +3586,7 @@ pub type UpdatedEntriesSet = Arc<[(Arc<Path>, ProjectEntryId, PathChange)]>;
|
|||
pub type UpdatedGitRepositoriesSet = Arc<[(Arc<Path>, GitRepositoryChange)]>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct GitEntry {
|
||||
pub struct StatusEntry {
|
||||
pub path: RepoPath,
|
||||
pub git_status: GitFileStatus,
|
||||
}
|
||||
|
@ -3665,7 +3665,7 @@ impl sum_tree::Summary for GitStatuses {
|
|||
}
|
||||
}
|
||||
|
||||
impl sum_tree::Item for GitEntry {
|
||||
impl sum_tree::Item for StatusEntry {
|
||||
type Summary = PathSummary<GitStatuses>;
|
||||
|
||||
fn summary(&self, _: &<Self::Summary as Summary>::Context) -> Self::Summary {
|
||||
|
@ -3694,7 +3694,7 @@ impl sum_tree::Item for GitEntry {
|
|||
}
|
||||
}
|
||||
|
||||
impl sum_tree::KeyedItem for GitEntry {
|
||||
impl sum_tree::KeyedItem for StatusEntry {
|
||||
type Key = PathKey;
|
||||
|
||||
fn key(&self) -> Self::Key {
|
||||
|
@ -3779,7 +3779,7 @@ struct AllStatusesCursor<'a, I> {
|
|||
repos: I,
|
||||
current_location: Option<(
|
||||
&'a WorkDirectory,
|
||||
Cursor<'a, GitEntry, (TraversalProgress<'a>, GitStatuses)>,
|
||||
Cursor<'a, StatusEntry, (TraversalProgress<'a>, GitStatuses)>,
|
||||
)>,
|
||||
statuses_before_current_repo: GitStatuses,
|
||||
}
|
||||
|
@ -4809,7 +4809,7 @@ impl BackgroundScanner {
|
|||
let mut changed_path_statuses = Vec::new();
|
||||
for (repo_path, status) in &*status.entries {
|
||||
paths.remove_repo_path(repo_path);
|
||||
changed_path_statuses.push(Edit::Insert(GitEntry {
|
||||
changed_path_statuses.push(Edit::Insert(StatusEntry {
|
||||
path: repo_path.clone(),
|
||||
git_status: *status,
|
||||
}));
|
||||
|
@ -5226,7 +5226,7 @@ impl BackgroundScanner {
|
|||
};
|
||||
|
||||
new_entries_by_path.insert_or_replace(
|
||||
GitEntry {
|
||||
StatusEntry {
|
||||
path: path.clone(),
|
||||
git_status: *status,
|
||||
},
|
||||
|
@ -5639,12 +5639,13 @@ impl<'a> Default for TraversalProgress<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct EntryWithGitStatusRef<'a> {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GitEntryRef<'a> {
|
||||
pub entry: &'a Entry,
|
||||
pub git_status: Option<GitFileStatus>,
|
||||
}
|
||||
|
||||
impl<'a> EntryWithGitStatusRef<'a> {
|
||||
impl<'a> GitEntryRef<'a> {
|
||||
fn entry(entry: &'a Entry) -> Self {
|
||||
Self {
|
||||
entry,
|
||||
|
@ -5652,15 +5653,15 @@ impl<'a> EntryWithGitStatusRef<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn to_owned(&self) -> EntryWithGitStatus {
|
||||
EntryWithGitStatus {
|
||||
pub fn to_owned(&self) -> GitEntry {
|
||||
GitEntry {
|
||||
entry: self.entry.clone(),
|
||||
git_status: self.git_status.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for EntryWithGitStatusRef<'a> {
|
||||
impl<'a> Deref for GitEntryRef<'a> {
|
||||
type Target = Entry;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
@ -5668,12 +5669,28 @@ impl<'a> Deref for EntryWithGitStatusRef<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct EntryWithGitStatus {
|
||||
impl<'a> AsRef<Entry> for GitEntryRef<'a> {
|
||||
fn as_ref(&self) -> &Entry {
|
||||
self.entry
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct GitEntry {
|
||||
pub entry: Entry,
|
||||
pub git_status: Option<GitFileStatus>,
|
||||
}
|
||||
|
||||
impl Deref for EntryWithGitStatus {
|
||||
impl GitEntry {
|
||||
pub fn to_ref(&self) -> GitEntryRef {
|
||||
GitEntryRef {
|
||||
entry: &self.entry,
|
||||
git_status: self.git_status.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for GitEntry {
|
||||
type Target = Entry;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
@ -5681,11 +5698,17 @@ impl Deref for EntryWithGitStatus {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> AsRef<Entry> for GitEntry {
|
||||
fn as_ref(&self) -> &Entry {
|
||||
&self.entry
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GitTraversal<'a> {
|
||||
traversal: Traversal<'a>,
|
||||
reset: bool,
|
||||
repositories: Cursor<'a, RepositoryEntry, PathProgress<'a>>,
|
||||
statuses: Option<Cursor<'a, GitEntry, PathProgress<'a>>>,
|
||||
statuses: Option<Cursor<'a, StatusEntry, PathProgress<'a>>>,
|
||||
}
|
||||
|
||||
impl<'a> GitTraversal<'a> {
|
||||
|
@ -5718,7 +5741,7 @@ impl<'a> GitTraversal<'a> {
|
|||
self.traversal.end_offset()
|
||||
}
|
||||
|
||||
pub fn entry(&mut self) -> Option<EntryWithGitStatusRef<'a>> {
|
||||
pub fn entry(&mut self) -> Option<GitEntryRef<'a>> {
|
||||
let reset = mem::take(&mut self.reset);
|
||||
let entry = self.traversal.cursor.item()?;
|
||||
let current_repository_id = self
|
||||
|
@ -5735,7 +5758,7 @@ impl<'a> GitTraversal<'a> {
|
|||
}
|
||||
let Some(repository) = self.repositories.item() else {
|
||||
self.statuses = None;
|
||||
return Some(EntryWithGitStatusRef::entry(entry));
|
||||
return Some(GitEntryRef::entry(entry));
|
||||
};
|
||||
|
||||
if reset || Some(repository.work_directory_id) != current_repository_id {
|
||||
|
@ -5743,30 +5766,30 @@ impl<'a> GitTraversal<'a> {
|
|||
}
|
||||
|
||||
let Some(statuses) = self.statuses.as_mut() else {
|
||||
return Some(EntryWithGitStatusRef::entry(entry));
|
||||
return Some(GitEntryRef::entry(entry));
|
||||
};
|
||||
let Some(repo_path) = repository.relativize(&entry.path).ok() else {
|
||||
return Some(EntryWithGitStatusRef::entry(entry));
|
||||
return Some(GitEntryRef::entry(entry));
|
||||
};
|
||||
let found = statuses.seek_forward(&PathTarget::Path(&repo_path.0), Bias::Left, &());
|
||||
|
||||
if found {
|
||||
let Some(status) = statuses.item() else {
|
||||
return Some(EntryWithGitStatusRef::entry(entry));
|
||||
return Some(GitEntryRef::entry(entry));
|
||||
};
|
||||
|
||||
Some(EntryWithGitStatusRef {
|
||||
Some(GitEntryRef {
|
||||
entry,
|
||||
git_status: Some(status.git_status),
|
||||
})
|
||||
} else {
|
||||
Some(EntryWithGitStatusRef::entry(entry))
|
||||
Some(GitEntryRef::entry(entry))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for GitTraversal<'a> {
|
||||
type Item = EntryWithGitStatusRef<'a>;
|
||||
type Item = GitEntryRef<'a>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(item) = self.entry() {
|
||||
self.advance();
|
||||
|
@ -6009,6 +6032,20 @@ pub struct ChildEntriesIter<'a> {
|
|||
traversal: Traversal<'a>,
|
||||
}
|
||||
|
||||
impl<'a> ChildEntriesIter<'a> {
|
||||
pub fn with_git_statuses(self) -> ChildEntriesGitIter<'a> {
|
||||
ChildEntriesGitIter {
|
||||
parent_path: self.parent_path,
|
||||
traversal: self.traversal.with_git_statuses(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChildEntriesGitIter<'a> {
|
||||
parent_path: &'a Path,
|
||||
traversal: GitTraversal<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ChildEntriesIter<'a> {
|
||||
type Item = &'a Entry;
|
||||
|
||||
|
@ -6023,6 +6060,20 @@ impl<'a> Iterator for ChildEntriesIter<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ChildEntriesGitIter<'a> {
|
||||
type Item = GitEntryRef<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(item) = self.traversal.entry() {
|
||||
if item.path.starts_with(self.parent_path) {
|
||||
self.traversal.advance_to_sibling();
|
||||
return Some(item);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Entry> for proto::Entry {
|
||||
fn from(entry: &'a Entry) -> Self {
|
||||
Self {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
worktree_settings::WorktreeSettings, Entry, EntryKind, Event, PathChange, Snapshot, Worktree,
|
||||
WorktreeModelHandle,
|
||||
worktree_settings::WorktreeSettings, Entry, EntryKind, Event, GitEntry, PathChange, Snapshot,
|
||||
Worktree, WorktreeModelHandle,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use fs::{FakeFs, Fs, RealFs, RemoveOptions};
|
||||
|
@ -3183,7 +3183,10 @@ fn check_propagated_statuses(
|
|||
.iter()
|
||||
.map(|(path, _)| snapshot.entry_for_path(path).unwrap().clone())
|
||||
.collect::<Vec<_>>();
|
||||
let statuses = snapshot.propagate_git_statuses(&entries);
|
||||
// TODO: recreate this
|
||||
// let statuses = snapshot.propagate_git_statuses(&entries);
|
||||
let statuses: Vec<Option<GitFileStatus>> = Vec::new();
|
||||
panic!("Redo git status propogation");
|
||||
assert_eq!(
|
||||
entries
|
||||
.iter()
|
||||
|
|
Loading…
Reference in a new issue