From 942bedb8552ee135b4b5961d7a1c94e1570f381a Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Fri, 20 Dec 2024 19:38:21 -0500 Subject: [PATCH] Push entry changes out further Co-authored-by: Mikayla --- Cargo.lock | 1 + crates/editor/src/git/project_diff.rs | 31 +- crates/image_viewer/src/image_viewer.rs | 8 +- crates/outline_panel/Cargo.toml | 5 +- crates/outline_panel/src/outline_panel.rs | 1117 ++++++++++++--------- crates/project/src/project.rs | 20 +- crates/project/src/worktree_store.rs | 2 +- crates/project_panel/src/project_panel.rs | 95 +- crates/tab_switcher/src/tab_switcher.rs | 15 +- crates/worktree/src/worktree.rs | 55 + 10 files changed, 838 insertions(+), 511 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d92c2deab..afb272bb37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8430,6 +8430,7 @@ dependencies = [ "editor", "file_icons", "fuzzy", + "git", "gpui", "itertools 0.13.0", "language", diff --git a/crates/editor/src/git/project_diff.rs b/crates/editor/src/git/project_diff.rs index 7dc4e0d892..5ae26da03d 100644 --- a/crates/editor/src/git/project_diff.rs +++ b/crates/editor/src/git/project_diff.rs @@ -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::>(); Some( diff --git a/crates/image_viewer/src/image_viewer.rs b/crates/image_viewer/src/image_viewer.rs index 570948a822..b78f1bd085 100644 --- a/crates/image_viewer/src/image_viewer.rs +++ b/crates/image_viewer/src/image_viewer.rs @@ -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 { diff --git a/crates/outline_panel/Cargo.toml b/crates/outline_panel/Cargo.toml index 6dfe1ceccc..51e80d8fdf 100644 --- a/crates/outline_panel/Cargo.toml +++ b/crates/outline_panel/Cargo.toml @@ -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"] } diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 586ae89a5b..359970a142 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -22,6 +22,7 @@ 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, @@ -348,10 +349,18 @@ enum ExcerptOutlines { NotFetched, } +#[derive(Clone, Debug, PartialEq, Eq)] +struct FoldedDirsEntry { + worktree_id: WorktreeId, + entries: Vec, + git_file_statuses: Vec>, +} + +// TODO: collapse the inner enums into panel entry #[derive(Clone, Debug)] enum PanelEntry { Fs(FsEntry), - FoldedDirs(WorktreeId, Vec), + FoldedDirs(FoldedDirsEntry), Outline(OutlineEntry), Search(SearchEntry), } @@ -383,7 +392,18 @@ impl PartialEq for PanelEntry { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Fs(a), Self::Fs(b)) => a == b, - (Self::FoldedDirs(a1, a2), Self::FoldedDirs(b1, b2)) => a1 == b1 && a2 == b2, + ( + Self::FoldedDirs(FoldedDirsEntry { + worktree_id: worktree_id_a, + entries: entries_a, + .. + }), + Self::FoldedDirs(FoldedDirsEntry { + worktree_id: worktree_id_b, + entries: entries_b, + .. + }), + ) => worktree_id_a == worktree_id_b && entries_a == entries_b, (Self::Outline(a), Self::Outline(b)) => a == b, ( Self::Search(SearchEntry { @@ -505,54 +525,126 @@ impl SearchData { } } -#[derive(Clone, Debug, PartialEq, Eq)] -enum OutlineEntry { - Excerpt(BufferId, ExcerptId, ExcerptRange), - Outline(BufferId, ExcerptId, Outline), +#[derive(Clone, Debug, Eq)] +struct OutlineEntryExcerpt { + id: ExcerptId, + buffer_id: BufferId, + range: ExcerptRange, +} + +impl PartialEq for OutlineEntryExcerpt { + fn eq(&self, other: &Self) -> bool { + self.buffer_id == other.buffer_id && self.id == other.id + } +} + +impl Hash for OutlineEntryExcerpt { + fn hash(&self, state: &mut H) { + (self.buffer_id, self.id).hash(state) + } } #[derive(Clone, Debug, Eq)] -enum FsEntry { - ExternalFile(BufferId, Vec), - Directory(WorktreeId, Entry), - File(WorktreeId, Entry, BufferId, Vec), +struct OutlineEntryOutline { + buffer_id: BufferId, + excerpt_id: ExcerptId, + outline: Outline, } -impl PartialEq for FsEntry { +impl PartialEq for OutlineEntryOutline { fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::ExternalFile(id_a, _), Self::ExternalFile(id_b, _)) => id_a == id_b, - (Self::Directory(id_a, entry_a), Self::Directory(id_b, entry_b)) => { - id_a == id_b && entry_a.id == entry_b.id - } - ( - Self::File(worktree_a, entry_a, id_a, ..), - Self::File(worktree_b, entry_b, id_b, ..), - ) => worktree_a == worktree_b && entry_a.id == entry_b.id && id_a == id_b, - _ => false, + self.buffer_id == other.buffer_id && self.excerpt_id == other.excerpt_id + } +} + +impl Hash for OutlineEntryOutline { + fn hash(&self, state: &mut H) { + (self.buffer_id, self.excerpt_id).hash(state); + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum OutlineEntry { + Excerpt(OutlineEntryExcerpt), + Outline(OutlineEntryOutline), +} + +impl OutlineEntry { + fn ids(&self) -> (BufferId, ExcerptId) { + match self { + OutlineEntry::Excerpt(excerpt) => (excerpt.buffer_id, excerpt.id), + OutlineEntry::Outline(outline) => (outline.buffer_id, outline.excerpt_id), } } } -impl Hash for FsEntry { - fn hash(&self, state: &mut H) { - match self { - Self::ExternalFile(buffer_id, _) => { - buffer_id.hash(state); - } - Self::Directory(worktree_id, entry) => { - worktree_id.hash(state); - entry.id.hash(state); - } - Self::File(worktree_id, entry, buffer_id, _) => { - worktree_id.hash(state); - entry.id.hash(state); - buffer_id.hash(state); - } - } +#[derive(Debug, Clone, Eq)] +struct FsEntryFile { + worktree_id: WorktreeId, + entry: Entry, + buffer_id: BufferId, + excerpts: Vec, + git_status: Option, +} + +impl PartialEq for FsEntryFile { + fn eq(&self, other: &Self) -> bool { + self.worktree_id == other.worktree_id + && self.entry.id == other.entry.id + && self.buffer_id == other.buffer_id } } +impl Hash for FsEntryFile { + fn hash(&self, state: &mut H) { + (self.buffer_id, self.entry.id, self.worktree_id).hash(state); + } +} + +#[derive(Debug, Clone, Eq)] +struct FsEntryDirectory { + worktree_id: WorktreeId, + entry: Entry, + git_status: Option, +} + +impl PartialEq for FsEntryDirectory { + fn eq(&self, other: &Self) -> bool { + self.worktree_id == other.worktree_id && self.entry.id == other.entry.id + } +} + +impl Hash for FsEntryDirectory { + fn hash(&self, state: &mut H) { + (self.worktree_id, self.entry.id).hash(state); + } +} + +#[derive(Debug, Clone, Eq)] +struct FsEntryExternalFile { + buffer_id: BufferId, + excerpts: Vec, +} + +impl PartialEq for FsEntryExternalFile { + fn eq(&self, other: &Self) -> bool { + self.buffer_id == other.buffer_id + } +} + +impl Hash for FsEntryExternalFile { + fn hash(&self, state: &mut H) { + self.buffer_id.hash(state); + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum FsEntry { + ExternalFile(FsEntryExternalFile), + Directory(FsEntryDirectory), + File(FsEntryFile), +} + struct ActiveItem { item_handle: Box, active_editor: WeakView, @@ -775,7 +867,12 @@ impl OutlinePanel { } fn unfold_directory(&mut self, _: &UnfoldDirectory, cx: &mut ViewContext) { - if let Some(PanelEntry::FoldedDirs(worktree_id, entries)) = self.selected_entry().cloned() { + if let Some(PanelEntry::FoldedDirs(FoldedDirsEntry { + worktree_id, + entries, + .. + })) = self.selected_entry().cloned() + { self.unfolded_dirs .entry(worktree_id) .or_default() @@ -786,11 +883,11 @@ impl OutlinePanel { fn fold_directory(&mut self, _: &FoldDirectory, cx: &mut ViewContext) { let (worktree_id, entry) = match self.selected_entry().cloned() { - Some(PanelEntry::Fs(FsEntry::Directory(worktree_id, entry))) => { - (worktree_id, Some(entry)) + Some(PanelEntry::Fs(FsEntry::Directory(directory))) => { + (directory.worktree_id, Some(directory.entry)) } - Some(PanelEntry::FoldedDirs(worktree_id, entries)) => { - (worktree_id, entries.last().cloned()) + Some(PanelEntry::FoldedDirs(folded_dirs)) => { + (folded_dirs.worktree_id, folded_dirs.entries.last().cloned()) } _ => return, }; @@ -875,12 +972,12 @@ impl OutlinePanel { let mut scroll_to_buffer = None; let scroll_target = match entry { PanelEntry::FoldedDirs(..) | PanelEntry::Fs(FsEntry::Directory(..)) => None, - PanelEntry::Fs(FsEntry::ExternalFile(buffer_id, _)) => { + PanelEntry::Fs(FsEntry::ExternalFile(file)) => { change_selection = false; - scroll_to_buffer = Some(*buffer_id); + scroll_to_buffer = Some(file.buffer_id); multi_buffer_snapshot.excerpts().find_map( |(excerpt_id, buffer_snapshot, excerpt_range)| { - if &buffer_snapshot.remote_id() == buffer_id { + if buffer_snapshot.remote_id() == file.buffer_id { multi_buffer_snapshot .anchor_in_excerpt(excerpt_id, excerpt_range.context.start) } else { @@ -889,13 +986,14 @@ impl OutlinePanel { }, ) } - PanelEntry::Fs(FsEntry::File(_, file_entry, buffer_id, _)) => { + + PanelEntry::Fs(FsEntry::File(file)) => { change_selection = false; - scroll_to_buffer = Some(*buffer_id); + scroll_to_buffer = Some(file.buffer_id); self.project .update(cx, |project, cx| { project - .path_for_entry(file_entry.id, cx) + .path_for_entry(file.entry.id, cx) .and_then(|path| project.get_open_buffer(&path, cx)) }) .map(|buffer| { @@ -909,18 +1007,17 @@ impl OutlinePanel { .anchor_in_excerpt(*excerpt_id, excerpt_range.context.start) }) } - PanelEntry::Outline(OutlineEntry::Outline(_, excerpt_id, outline)) => { - multi_buffer_snapshot - .anchor_in_excerpt(*excerpt_id, outline.range.start) - .or_else(|| { - multi_buffer_snapshot.anchor_in_excerpt(*excerpt_id, outline.range.end) - }) - } - PanelEntry::Outline(OutlineEntry::Excerpt(_, excerpt_id, excerpt_range)) => { + PanelEntry::Outline(OutlineEntry::Outline(outline)) => multi_buffer_snapshot + .anchor_in_excerpt(outline.excerpt_id, outline.outline.range.start) + .or_else(|| { + multi_buffer_snapshot + .anchor_in_excerpt(outline.excerpt_id, outline.outline.range.end) + }), + PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => { change_selection = false; - multi_buffer_snapshot.anchor_in_excerpt(*excerpt_id, excerpt_range.context.start) + multi_buffer_snapshot.anchor_in_excerpt(excerpt.id, excerpt.range.context.start) } - PanelEntry::Search(SearchEntry { match_range, .. }) => Some(match_range.start), + PanelEntry::Search(search_entry) => Some(search_entry.match_range.start), }; if let Some(anchor) = scroll_target { @@ -960,8 +1057,10 @@ impl OutlinePanel { .iter() .rev() .filter_map(|entry| match entry { - FsEntry::File(_, _, buffer_id, _) - | FsEntry::ExternalFile(buffer_id, _) => Some(*buffer_id), + FsEntry::File(file) => Some(file.buffer_id), + FsEntry::ExternalFile(external_file) => { + Some(external_file.buffer_id) + } FsEntry::Directory(..) => None, }) .skip_while(|id| *id != buffer_id) @@ -1044,69 +1143,68 @@ impl OutlinePanel { match &selected_entry { PanelEntry::Fs(fs_entry) => match fs_entry { FsEntry::ExternalFile(..) => None, - FsEntry::File(worktree_id, entry, ..) - | FsEntry::Directory(worktree_id, entry) => { - entry.path.parent().and_then(|parent_path| { - previous_entries.find(|entry| match entry { - PanelEntry::Fs(FsEntry::Directory(dir_worktree_id, dir_entry)) => { - dir_worktree_id == worktree_id - && dir_entry.path.as_ref() == parent_path - } - PanelEntry::FoldedDirs(dirs_worktree_id, dirs) => { - dirs_worktree_id == worktree_id - && dirs - .last() - .map_or(false, |dir| dir.path.as_ref() == parent_path) - } - _ => false, - }) + FsEntry::File(FsEntryFile { + worktree_id, entry, .. + }) + | FsEntry::Directory(FsEntryDirectory { + worktree_id, entry, .. + }) => entry.path.parent().and_then(|parent_path| { + previous_entries.find(|entry| match entry { + PanelEntry::Fs(FsEntry::Directory(directory)) => { + directory.worktree_id == *worktree_id + && directory.entry.path.as_ref() == parent_path + } + PanelEntry::FoldedDirs(FoldedDirsEntry { + worktree_id: dirs_worktree_id, + entries: dirs, + .. + }) => { + dirs_worktree_id == worktree_id + && dirs + .last() + .map_or(false, |dir| dir.path.as_ref() == parent_path) + } + _ => false, }) - } + }), }, - PanelEntry::FoldedDirs(worktree_id, entries) => entries + PanelEntry::FoldedDirs(folded_dirs) => folded_dirs + .entries .first() .and_then(|entry| entry.path.parent()) .and_then(|parent_path| { previous_entries.find(|entry| { - if let PanelEntry::Fs(FsEntry::Directory(dir_worktree_id, dir_entry)) = - entry - { - dir_worktree_id == worktree_id - && dir_entry.path.as_ref() == parent_path + if let PanelEntry::Fs(FsEntry::Directory(directory)) = entry { + directory.worktree_id == folded_dirs.worktree_id + && directory.entry.path.as_ref() == parent_path } else { false } }) }), - PanelEntry::Outline(OutlineEntry::Excerpt(excerpt_buffer_id, excerpt_id, _)) => { + PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => { previous_entries.find(|entry| match entry { - PanelEntry::Fs(FsEntry::File(_, _, file_buffer_id, file_excerpts)) => { - file_buffer_id == excerpt_buffer_id - && file_excerpts.contains(excerpt_id) + PanelEntry::Fs(FsEntry::File(file)) => { + file.buffer_id == excerpt.buffer_id + && file.excerpts.contains(&excerpt.id) } - PanelEntry::Fs(FsEntry::ExternalFile(file_buffer_id, file_excerpts)) => { - file_buffer_id == excerpt_buffer_id - && file_excerpts.contains(excerpt_id) + PanelEntry::Fs(FsEntry::ExternalFile(external_file)) => { + external_file.buffer_id == excerpt.buffer_id + && external_file.excerpts.contains(&excerpt.id) } _ => false, }) } - PanelEntry::Outline(OutlineEntry::Outline( - outline_buffer_id, - outline_excerpt_id, - _, - )) => previous_entries.find(|entry| { - if let PanelEntry::Outline(OutlineEntry::Excerpt( - excerpt_buffer_id, - excerpt_id, - _, - )) = entry - { - outline_buffer_id == excerpt_buffer_id && outline_excerpt_id == excerpt_id - } else { - false - } - }), + PanelEntry::Outline(OutlineEntry::Outline(outline)) => { + previous_entries.find(|entry| { + if let PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) = entry { + outline.buffer_id == excerpt.buffer_id + && outline.excerpt_id == excerpt.id + } else { + false + } + }) + } PanelEntry::Search(_) => { previous_entries.find(|entry| !matches!(entry, PanelEntry::Search(_))) } @@ -1164,8 +1262,12 @@ impl OutlinePanel { ) { self.select_entry(entry.clone(), true, cx); let is_root = match &entry { - PanelEntry::Fs(FsEntry::File(worktree_id, entry, ..)) - | PanelEntry::Fs(FsEntry::Directory(worktree_id, entry)) => self + PanelEntry::Fs(FsEntry::File(FsEntryFile { + worktree_id, entry, .. + })) + | PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory { + worktree_id, entry, .. + })) => self .project .read(cx) .worktree_for_id(*worktree_id, cx) @@ -1173,7 +1275,11 @@ impl OutlinePanel { worktree.read(cx).root_entry().map(|entry| entry.id) == Some(entry.id) }) .unwrap_or(false), - PanelEntry::FoldedDirs(worktree_id, entries) => entries + PanelEntry::FoldedDirs(FoldedDirsEntry { + worktree_id, + entries, + .. + }) => entries .first() .and_then(|entry| { self.project @@ -1232,9 +1338,11 @@ impl OutlinePanel { fn is_foldable(&self, entry: &PanelEntry) -> bool { let (directory_worktree, directory_entry) = match entry { - PanelEntry::Fs(FsEntry::Directory(directory_worktree, directory_entry)) => { - (*directory_worktree, Some(directory_entry)) - } + PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory { + worktree_id, + entry: directory_entry, + .. + })) => (*worktree_id, Some(directory_entry)), _ => return false, }; let Some(directory_entry) = directory_entry else { @@ -1270,24 +1378,34 @@ impl OutlinePanel { }; let mut buffers_to_unfold = HashSet::default(); let entry_to_expand = match &selected_entry { - PanelEntry::FoldedDirs(worktree_id, dir_entries) => dir_entries.last().map(|entry| { + PanelEntry::FoldedDirs(FoldedDirsEntry { + entries: dir_entries, + worktree_id, + .. + }) => dir_entries.last().map(|entry| { buffers_to_unfold.extend(self.buffers_inside_directory(*worktree_id, entry)); CollapsedEntry::Dir(*worktree_id, entry.id) }), - PanelEntry::Fs(FsEntry::Directory(worktree_id, dir_entry)) => { - buffers_to_unfold.extend(self.buffers_inside_directory(*worktree_id, dir_entry)); - Some(CollapsedEntry::Dir(*worktree_id, dir_entry.id)) + PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory { + worktree_id, entry, .. + })) => { + buffers_to_unfold.extend(self.buffers_inside_directory(*worktree_id, entry)); + Some(CollapsedEntry::Dir(*worktree_id, entry.id)) } - PanelEntry::Fs(FsEntry::File(worktree_id, _, buffer_id, _)) => { + PanelEntry::Fs(FsEntry::File(FsEntryFile { + worktree_id, + buffer_id, + .. + })) => { buffers_to_unfold.insert(*buffer_id); Some(CollapsedEntry::File(*worktree_id, *buffer_id)) } - PanelEntry::Fs(FsEntry::ExternalFile(buffer_id, _)) => { - buffers_to_unfold.insert(*buffer_id); - Some(CollapsedEntry::ExternalFile(*buffer_id)) + PanelEntry::Fs(FsEntry::ExternalFile(external_file)) => { + buffers_to_unfold.insert(external_file.buffer_id); + Some(CollapsedEntry::ExternalFile(external_file.buffer_id)) } - PanelEntry::Outline(OutlineEntry::Excerpt(buffer_id, excerpt_id, _)) => { - Some(CollapsedEntry::Excerpt(*buffer_id, *excerpt_id)) + PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => { + Some(CollapsedEntry::Excerpt(excerpt.buffer_id, excerpt.id)) } PanelEntry::Search(_) | PanelEntry::Outline(..) => return, }; @@ -1330,19 +1448,24 @@ impl OutlinePanel { let mut buffers_to_fold = HashSet::default(); let collapsed = match &selected_entry { - PanelEntry::Fs(FsEntry::Directory(worktree_id, selected_dir_entry)) => { + PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory { + worktree_id, entry, .. + })) => { if self .collapsed_entries - .insert(CollapsedEntry::Dir(*worktree_id, selected_dir_entry.id)) + .insert(CollapsedEntry::Dir(*worktree_id, entry.id)) { - buffers_to_fold - .extend(self.buffers_inside_directory(*worktree_id, selected_dir_entry)); + buffers_to_fold.extend(self.buffers_inside_directory(*worktree_id, entry)); true } else { false } } - PanelEntry::Fs(FsEntry::File(worktree_id, _, buffer_id, _)) => { + PanelEntry::Fs(FsEntry::File(FsEntryFile { + worktree_id, + buffer_id, + .. + })) => { if self .collapsed_entries .insert(CollapsedEntry::File(*worktree_id, *buffer_id)) @@ -1353,34 +1476,35 @@ impl OutlinePanel { false } } - PanelEntry::Fs(FsEntry::ExternalFile(buffer_id, _)) => { + PanelEntry::Fs(FsEntry::ExternalFile(external_file)) => { if self .collapsed_entries - .insert(CollapsedEntry::ExternalFile(*buffer_id)) + .insert(CollapsedEntry::ExternalFile(external_file.buffer_id)) { - buffers_to_fold.insert(*buffer_id); + buffers_to_fold.insert(external_file.buffer_id); true } else { false } } - PanelEntry::FoldedDirs(worktree_id, dir_entries) => { + PanelEntry::FoldedDirs(folded_dirs) => { let mut folded = false; - if let Some(dir_entry) = dir_entries.last() { + if let Some(dir_entry) = folded_dirs.entries.last() { if self .collapsed_entries - .insert(CollapsedEntry::Dir(*worktree_id, dir_entry.id)) + .insert(CollapsedEntry::Dir(folded_dirs.worktree_id, dir_entry.id)) { folded = true; - buffers_to_fold - .extend(self.buffers_inside_directory(*worktree_id, dir_entry)); + buffers_to_fold.extend( + self.buffers_inside_directory(folded_dirs.worktree_id, dir_entry), + ); } } folded } - PanelEntry::Outline(OutlineEntry::Excerpt(buffer_id, excerpt_id, _)) => self + PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => self .collapsed_entries - .insert(CollapsedEntry::Excerpt(*buffer_id, *excerpt_id)), + .insert(CollapsedEntry::Excerpt(excerpt.buffer_id, excerpt.id)), PanelEntry::Search(_) | PanelEntry::Outline(..) => false, }; @@ -1409,30 +1533,41 @@ impl OutlinePanel { .iter() .fold(HashSet::default(), |mut entries, fs_entry| { match fs_entry { - FsEntry::ExternalFile(buffer_id, _) => { - buffers_to_unfold.insert(*buffer_id); - entries.insert(CollapsedEntry::ExternalFile(*buffer_id)); - entries.extend(self.excerpts.get(buffer_id).into_iter().flat_map( - |excerpts| { - excerpts.iter().map(|(excerpt_id, _)| { - CollapsedEntry::Excerpt(*buffer_id, *excerpt_id) - }) - }, + FsEntry::ExternalFile(external_file) => { + buffers_to_unfold.insert(external_file.buffer_id); + entries.insert(CollapsedEntry::ExternalFile(external_file.buffer_id)); + entries.extend( + self.excerpts + .get(&external_file.buffer_id) + .into_iter() + .flat_map(|excerpts| { + excerpts.iter().map(|(excerpt_id, _)| { + CollapsedEntry::Excerpt( + external_file.buffer_id, + *excerpt_id, + ) + }) + }), + ); + } + FsEntry::Directory(directory) => { + entries.insert(CollapsedEntry::Dir( + directory.worktree_id, + directory.entry.id, )); } - FsEntry::Directory(worktree_id, entry) => { - entries.insert(CollapsedEntry::Dir(*worktree_id, entry.id)); - } - FsEntry::File(worktree_id, _, buffer_id, _) => { - buffers_to_unfold.insert(*buffer_id); - entries.insert(CollapsedEntry::File(*worktree_id, *buffer_id)); - entries.extend(self.excerpts.get(buffer_id).into_iter().flat_map( - |excerpts| { - excerpts.iter().map(|(excerpt_id, _)| { - CollapsedEntry::Excerpt(*buffer_id, *excerpt_id) - }) - }, - )); + FsEntry::File(file) => { + buffers_to_unfold.insert(file.buffer_id); + entries.insert(CollapsedEntry::File(file.worktree_id, file.buffer_id)); + entries.extend( + self.excerpts.get(&file.buffer_id).into_iter().flat_map( + |excerpts| { + excerpts.iter().map(|(excerpt_id, _)| { + CollapsedEntry::Excerpt(file.buffer_id, *excerpt_id) + }) + }, + ), + ); } }; entries @@ -1459,22 +1594,28 @@ impl OutlinePanel { .cached_entries .iter() .flat_map(|cached_entry| match &cached_entry.entry { - PanelEntry::Fs(FsEntry::Directory(worktree_id, entry)) => { - Some(CollapsedEntry::Dir(*worktree_id, entry.id)) - } - PanelEntry::Fs(FsEntry::File(worktree_id, _, buffer_id, _)) => { + PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory { + worktree_id, entry, .. + })) => Some(CollapsedEntry::Dir(*worktree_id, entry.id)), + PanelEntry::Fs(FsEntry::File(FsEntryFile { + worktree_id, + buffer_id, + .. + })) => { buffers_to_fold.insert(*buffer_id); Some(CollapsedEntry::File(*worktree_id, *buffer_id)) } - PanelEntry::Fs(FsEntry::ExternalFile(buffer_id, _)) => { - buffers_to_fold.insert(*buffer_id); - Some(CollapsedEntry::ExternalFile(*buffer_id)) + PanelEntry::Fs(FsEntry::ExternalFile(external_file)) => { + buffers_to_fold.insert(external_file.buffer_id); + Some(CollapsedEntry::ExternalFile(external_file.buffer_id)) } - PanelEntry::FoldedDirs(worktree_id, entries) => { - Some(CollapsedEntry::Dir(*worktree_id, entries.last()?.id)) - } - PanelEntry::Outline(OutlineEntry::Excerpt(buffer_id, excerpt_id, _)) => { - Some(CollapsedEntry::Excerpt(*buffer_id, *excerpt_id)) + PanelEntry::FoldedDirs(FoldedDirsEntry { + worktree_id, + entries, + .. + }) => Some(CollapsedEntry::Dir(*worktree_id, entries.last()?.id)), + PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => { + Some(CollapsedEntry::Excerpt(excerpt.buffer_id, excerpt.id)) } PanelEntry::Search(_) | PanelEntry::Outline(..) => None, }) @@ -1498,7 +1639,11 @@ impl OutlinePanel { let mut fold = false; let mut buffers_to_toggle = HashSet::default(); match entry { - PanelEntry::Fs(FsEntry::Directory(worktree_id, dir_entry)) => { + PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory { + worktree_id, + entry: dir_entry, + .. + })) => { let entry_id = dir_entry.id; let collapsed_entry = CollapsedEntry::Dir(*worktree_id, entry_id); buffers_to_toggle.extend(self.buffers_inside_directory(*worktree_id, dir_entry)); @@ -1514,7 +1659,11 @@ impl OutlinePanel { fold = true; } } - PanelEntry::Fs(FsEntry::File(worktree_id, _, buffer_id, _)) => { + PanelEntry::Fs(FsEntry::File(FsEntryFile { + worktree_id, + buffer_id, + .. + })) => { let collapsed_entry = CollapsedEntry::File(*worktree_id, *buffer_id); buffers_to_toggle.insert(*buffer_id); if !self.collapsed_entries.remove(&collapsed_entry) { @@ -1522,15 +1671,19 @@ impl OutlinePanel { fold = true; } } - PanelEntry::Fs(FsEntry::ExternalFile(buffer_id, _)) => { - let collapsed_entry = CollapsedEntry::ExternalFile(*buffer_id); - buffers_to_toggle.insert(*buffer_id); + PanelEntry::Fs(FsEntry::ExternalFile(external_file)) => { + let collapsed_entry = CollapsedEntry::ExternalFile(external_file.buffer_id); + buffers_to_toggle.insert(external_file.buffer_id); if !self.collapsed_entries.remove(&collapsed_entry) { self.collapsed_entries.insert(collapsed_entry); fold = true; } } - PanelEntry::FoldedDirs(worktree_id, dir_entries) => { + PanelEntry::FoldedDirs(FoldedDirsEntry { + worktree_id, + entries: dir_entries, + .. + }) => { if let Some(dir_entry) = dir_entries.first() { let entry_id = dir_entry.id; let collapsed_entry = CollapsedEntry::Dir(*worktree_id, entry_id); @@ -1549,8 +1702,8 @@ impl OutlinePanel { } } } - PanelEntry::Outline(OutlineEntry::Excerpt(buffer_id, excerpt_id, _)) => { - let collapsed_entry = CollapsedEntry::Excerpt(*buffer_id, *excerpt_id); + PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => { + let collapsed_entry = CollapsedEntry::Excerpt(excerpt.buffer_id, excerpt.id); if !self.collapsed_entries.remove(&collapsed_entry) { self.collapsed_entries.insert(collapsed_entry); } @@ -1625,7 +1778,9 @@ impl OutlinePanel { .selected_entry() .and_then(|entry| match entry { PanelEntry::Fs(entry) => self.relative_path(entry, cx), - PanelEntry::FoldedDirs(_, dirs) => dirs.last().map(|entry| entry.path.clone()), + PanelEntry::FoldedDirs(folded_dirs) => { + folded_dirs.entries.last().map(|entry| entry.path.clone()) + } PanelEntry::Search(_) | PanelEntry::Outline(..) => None, }) .map(|p| p.to_string_lossy().to_string()) @@ -1679,23 +1834,24 @@ impl OutlinePanel { return Ok(()); }; let related_buffer_entry = match &entry_with_selection { - PanelEntry::Fs(FsEntry::File(worktree_id, _, buffer_id, _)) => { - project.update(&mut cx, |project, cx| { - let entry_id = project - .buffer_for_id(*buffer_id, cx) - .and_then(|buffer| buffer.read(cx).entry_id(cx)); - project - .worktree_for_id(*worktree_id, cx) - .zip(entry_id) - .and_then(|(worktree, entry_id)| { - let entry = worktree.read(cx).entry_for_id(entry_id)?.clone(); - Some((worktree, entry)) - }) - })? - } + PanelEntry::Fs(FsEntry::File(FsEntryFile { + worktree_id, + buffer_id, + .. + })) => project.update(&mut cx, |project, cx| { + let entry_id = project + .buffer_for_id(*buffer_id, cx) + .and_then(|buffer| buffer.read(cx).entry_id(cx)); + project + .worktree_for_id(*worktree_id, cx) + .zip(entry_id) + .and_then(|(worktree, entry_id)| { + let entry = worktree.read(cx).entry_for_id(entry_id)?.clone(); + Some((worktree, entry)) + }) + })?, PanelEntry::Outline(outline_entry) => { - let &(OutlineEntry::Outline(buffer_id, excerpt_id, _) - | OutlineEntry::Excerpt(buffer_id, excerpt_id, _)) = outline_entry; + let (buffer_id, excerpt_id) = outline_entry.ids(); outline_panel.update(&mut cx, |outline_panel, cx| { outline_panel .collapsed_entries @@ -1808,25 +1964,21 @@ impl OutlinePanel { fn render_excerpt( &self, - buffer_id: BufferId, - excerpt_id: ExcerptId, - range: &ExcerptRange, + excerpt: &OutlineEntryExcerpt, depth: usize, cx: &mut ViewContext, ) -> Option> { - let item_id = ElementId::from(excerpt_id.to_proto() as usize); + let item_id = ElementId::from(excerpt.id.to_proto() as usize); let is_active = match self.selected_entry() { - Some(PanelEntry::Outline(OutlineEntry::Excerpt( - selected_buffer_id, - selected_excerpt_id, - _, - ))) => selected_buffer_id == &buffer_id && selected_excerpt_id == &excerpt_id, + Some(PanelEntry::Outline(OutlineEntry::Excerpt(selected_excerpt))) => { + selected_excerpt.buffer_id == excerpt.buffer_id && selected_excerpt.id == excerpt.id + } _ => false, }; let has_outlines = self .excerpts - .get(&buffer_id) - .and_then(|excerpts| match &excerpts.get(&excerpt_id)?.outlines { + .get(&excerpt.buffer_id) + .and_then(|excerpts| match &excerpts.get(&excerpt.id)?.outlines { ExcerptOutlines::Outlines(outlines) => Some(outlines), ExcerptOutlines::Invalidated(outlines) => Some(outlines), ExcerptOutlines::NotFetched => None, @@ -1834,7 +1986,7 @@ impl OutlinePanel { .map_or(false, |outlines| !outlines.is_empty()); let is_expanded = !self .collapsed_entries - .contains(&CollapsedEntry::Excerpt(buffer_id, excerpt_id)); + .contains(&CollapsedEntry::Excerpt(excerpt.buffer_id, excerpt.id)); let color = entry_git_aware_label_color(None, false, is_active); let icon = if has_outlines { FileIcons::get_chevron_icon(is_expanded, cx) @@ -1844,14 +1996,14 @@ impl OutlinePanel { } .unwrap_or_else(empty_icon); - let label = self.excerpt_label(buffer_id, range, cx)?; + let label = self.excerpt_label(excerpt.buffer_id, &excerpt.range, cx)?; let label_element = Label::new(label) .single_line() .color(color) .into_any_element(); Some(self.entry_element( - PanelEntry::Outline(OutlineEntry::Excerpt(buffer_id, excerpt_id, range.clone())), + PanelEntry::Outline(OutlineEntry::Excerpt(excerpt.clone())), item_id, depth, Some(icon), @@ -1878,50 +2030,40 @@ impl OutlinePanel { fn render_outline( &self, - buffer_id: BufferId, - excerpt_id: ExcerptId, - rendered_outline: &Outline, + outline: &OutlineEntryOutline, depth: usize, string_match: Option<&StringMatch>, cx: &mut ViewContext, ) -> Stateful
{ - let (item_id, label_element) = ( - ElementId::from(SharedString::from(format!( - "{buffer_id:?}|{excerpt_id:?}{:?}|{:?}", - rendered_outline.range, &rendered_outline.text, - ))), - outline::render_item( - rendered_outline, - string_match - .map(|string_match| string_match.ranges().collect::>()) - .unwrap_or_default(), - cx, - ) - .into_any_element(), - ); + let item_id = ElementId::from(SharedString::from(format!( + "{:?}|{:?}{:?}|{:?}", + outline.buffer_id, outline.excerpt_id, outline.outline.range, &outline.outline.text, + ))); + + let label_element = outline::render_item( + &outline.outline, + string_match + .map(|string_match| string_match.ranges().collect::>()) + .unwrap_or_default(), + cx, + ) + .into_any_element(); + let is_active = match self.selected_entry() { - Some(PanelEntry::Outline(OutlineEntry::Outline( - selected_buffer_id, - selected_excerpt_id, - selected_entry, - ))) => { - selected_buffer_id == &buffer_id - && selected_excerpt_id == &excerpt_id - && selected_entry == rendered_outline + Some(PanelEntry::Outline(OutlineEntry::Outline(selected))) => { + outline == selected && outline.outline == selected.outline } _ => false, }; + let icon = if self.is_singleton_active(cx) { None } else { Some(empty_icon()) }; + self.entry_element( - PanelEntry::Outline(OutlineEntry::Outline( - buffer_id, - excerpt_id, - rendered_outline.clone(), - )), + PanelEntry::Outline(OutlineEntry::Outline(outline.clone())), item_id, depth, icon, @@ -1944,10 +2086,14 @@ impl OutlinePanel { _ => false, }; let (item_id, label_element, icon) = match rendered_entry { - FsEntry::File(worktree_id, entry, ..) => { + FsEntry::File(FsEntryFile { + worktree_id, + entry, + git_status, + .. + }) => { let name = self.entry_name(worktree_id, entry, cx); - let color = - entry_git_aware_label_color(entry.git_status, entry.is_ignored, is_active); + let color = entry_git_aware_label_color(*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()) @@ -1967,14 +2113,18 @@ impl OutlinePanel { icon.unwrap_or_else(empty_icon), ) } - FsEntry::Directory(worktree_id, entry) => { + FsEntry::Directory(FsEntryDirectory { + worktree_id, + + 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(entry.git_status, entry.is_ignored, is_active); + let color = entry_git_aware_label_color(*git_status, entry.is_ignored, is_active); let icon = if settings.folder_icons { FileIcons::get_folder_icon(is_expanded, cx) } else { @@ -1995,9 +2145,9 @@ impl OutlinePanel { icon.unwrap_or_else(empty_icon), ) } - FsEntry::ExternalFile(buffer_id, _) => { + FsEntry::ExternalFile(external_file) => { let color = entry_label_color(is_active); - let (icon, name) = match self.buffer_snapshot_for_id(*buffer_id, cx) { + let (icon, name) = match self.buffer_snapshot_for_id(external_file.buffer_id, cx) { Some(buffer_snapshot) => match buffer_snapshot.file() { Some(file) => { let path = file.path(); @@ -2015,7 +2165,7 @@ impl OutlinePanel { None => (None, "Unknown buffer".to_string()), }; ( - ElementId::from(buffer_id.to_proto() as usize), + ElementId::from(external_file.buffer_id.to_proto() as usize), HighlightedLabel::new( name, string_match @@ -2042,29 +2192,29 @@ impl OutlinePanel { fn render_folded_dirs( &self, - worktree_id: WorktreeId, - dir_entries: &[Entry], + folded_dir: &FoldedDirsEntry, depth: usize, string_match: Option<&StringMatch>, cx: &mut ViewContext, ) -> Stateful
{ let settings = OutlinePanelSettings::get_global(cx); let is_active = match self.selected_entry() { - Some(PanelEntry::FoldedDirs(selected_worktree_id, selected_entries)) => { - selected_worktree_id == &worktree_id && selected_entries == dir_entries + Some(PanelEntry::FoldedDirs(selected_dirs)) => { + selected_dirs.worktree_id == folded_dir.worktree_id + && selected_dirs.entries == folded_dir.entries } _ => false, }; let (item_id, label_element, icon) = { - let name = self.dir_names_string(dir_entries, worktree_id, cx); + let name = self.dir_names_string(&folded_dir.entries, folded_dir.worktree_id, cx); - let is_expanded = dir_entries.iter().all(|dir| { + let is_expanded = folded_dir.entries.iter().all(|dir| { !self .collapsed_entries - .contains(&CollapsedEntry::Dir(worktree_id, dir.id)) + .contains(&CollapsedEntry::Dir(folded_dir.worktree_id, dir.id)) }); - let is_ignored = dir_entries.iter().any(|entry| entry.is_ignored); - let git_status = dir_entries.first().and_then(|entry| entry.git_status); + let is_ignored = folded_dir.entries.iter().any(|entry| entry.is_ignored); + let git_status = folded_dir.git_file_statuses.first().cloned().flatten(); 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) @@ -2075,10 +2225,12 @@ impl OutlinePanel { .map(|icon| icon.color(color).into_any_element()); ( ElementId::from( - dir_entries + folded_dir + .entries .last() .map(|entry| entry.id.to_proto()) - .unwrap_or_else(|| worktree_id.to_proto()) as usize, + .unwrap_or_else(|| folded_dir.worktree_id.to_proto()) + as usize, ), HighlightedLabel::new( name, @@ -2093,7 +2245,7 @@ impl OutlinePanel { }; self.entry_element( - PanelEntry::FoldedDirs(worktree_id, dir_entries.to_vec()), + PanelEntry::FoldedDirs(folded_dir.clone()), item_id, depth, Some(icon), @@ -2484,14 +2636,15 @@ impl OutlinePanel { let mut entries = entries.into_values().collect::>(); // 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())); - worktree_snapshot.propagate_git_statuses(&mut entries); + let statuses = worktree_snapshot.propagate_git_statuses(&entries); + let entries = entries.into_iter().zip(statuses).collect::>(); (worktree_id, entries) }) .flat_map(|(worktree_id, entries)| { { entries .into_iter() - .filter_map(|entry| { + .filter_map(|(entry, git_status)| { if auto_fold_dirs { if let Some(parent) = entry.path.parent() { let children = new_children_count @@ -2508,19 +2661,24 @@ impl OutlinePanel { } if entry.is_dir() { - Some(FsEntry::Directory(worktree_id, entry)) + Some(FsEntry::Directory(FsEntryDirectory { + worktree_id, + entry, + git_status, + })) } else { let (buffer_id, excerpts) = worktree_excerpts .get_mut(&worktree_id) .and_then(|worktree_excerpts| { worktree_excerpts.remove(&entry.id) })?; - Some(FsEntry::File( + Some(FsEntry::File(FsEntryFile { worktree_id, - entry, buffer_id, + entry, excerpts, - )) + git_status, + })) } }) .collect::>() @@ -2533,25 +2691,29 @@ impl OutlinePanel { let new_visible_entries = external_excerpts .into_iter() .sorted_by_key(|(id, _)| *id) - .map(|(buffer_id, excerpts)| FsEntry::ExternalFile(buffer_id, excerpts)) + .map(|(buffer_id, excerpts)| { + FsEntry::ExternalFile(FsEntryExternalFile { + buffer_id, + excerpts, + }) + }) .chain(worktree_entries) .filter(|visible_item| { match visible_item { - FsEntry::Directory(worktree_id, dir_entry) => { + FsEntry::Directory(directory) => { let parent_id = back_to_common_visited_parent( &mut visited_dirs, - worktree_id, - dir_entry, + &directory.worktree_id, + &directory.entry, ); - let depth = if root_entries.contains(&dir_entry.id) { - 0 - } else { + let mut depth = 0; + if !root_entries.contains(&directory.entry.id) { if auto_fold_dirs { let children = new_children_count - .get(worktree_id) + .get(&directory.worktree_id) .and_then(|children_count| { - children_count.get(&dir_entry.path) + children_count.get(&directory.entry.path) }) .copied() .unwrap_or_default(); @@ -2562,7 +2724,7 @@ impl OutlinePanel { .last() .map(|(parent_dir_id, _)| { new_unfolded_dirs - .get(worktree_id) + .get(&directory.worktree_id) .map_or(true, |unfolded_dirs| { unfolded_dirs .contains(parent_dir_id) @@ -2571,23 +2733,29 @@ impl OutlinePanel { .unwrap_or(true)) { new_unfolded_dirs - .entry(*worktree_id) + .entry(directory.worktree_id) .or_default() - .insert(dir_entry.id); + .insert(directory.entry.id); } } - parent_id + depth = parent_id .and_then(|(worktree_id, id)| { new_depth_map.get(&(worktree_id, id)).copied() }) .unwrap_or(0) - + 1 + + 1; }; - visited_dirs.push((dir_entry.id, dir_entry.path.clone())); - new_depth_map.insert((*worktree_id, dir_entry.id), depth); + visited_dirs + .push((directory.entry.id, directory.entry.path.clone())); + new_depth_map + .insert((directory.worktree_id, directory.entry.id), depth); } - FsEntry::File(worktree_id, file_entry, ..) => { + FsEntry::File(FsEntryFile { + worktree_id, + entry: file_entry, + .. + }) => { let parent_id = back_to_common_visited_parent( &mut visited_dirs, worktree_id, @@ -2718,8 +2886,10 @@ impl OutlinePanel { .iter() .find(|fs_entry| match fs_entry { FsEntry::Directory(..) => false, - FsEntry::File(_, _, file_buffer_id, _) - | FsEntry::ExternalFile(file_buffer_id, _) => *file_buffer_id == buffer_id, + FsEntry::File(FsEntryFile { buffer_id, .. }) + | FsEntry::ExternalFile(FsEntryExternalFile { buffer_id, .. }) => { + buffer_id == buffer_id + } }) .cloned() .map(PanelEntry::Fs); @@ -2869,26 +3039,31 @@ impl OutlinePanel { .cloned(); let closest_container = match outline_item { - Some(outline) => { - PanelEntry::Outline(OutlineEntry::Outline(buffer_id, excerpt_id, outline)) - } + Some(outline) => PanelEntry::Outline(OutlineEntry::Outline(OutlineEntryOutline { + buffer_id, + excerpt_id, + outline, + })), None => { self.cached_entries.iter().rev().find_map(|cached_entry| { match &cached_entry.entry { - PanelEntry::Outline(OutlineEntry::Excerpt( - entry_buffer_id, - entry_excerpt_id, - _, - )) => { - if entry_buffer_id == &buffer_id && entry_excerpt_id == &excerpt_id { + PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => { + if excerpt.buffer_id == buffer_id && excerpt.id == excerpt_id { Some(cached_entry.entry.clone()) } else { None } } PanelEntry::Fs( - FsEntry::ExternalFile(file_buffer_id, file_excerpts) - | FsEntry::File(_, _, file_buffer_id, file_excerpts), + FsEntry::ExternalFile(FsEntryExternalFile { + buffer_id: file_buffer_id, + excerpts: file_excerpts, + }) + | FsEntry::File(FsEntryFile { + buffer_id: file_buffer_id, + excerpts: file_excerpts, + .. + }), ) => { if file_buffer_id == &buffer_id && file_excerpts.contains(&excerpt_id) { Some(cached_entry.entry.clone()) @@ -2987,8 +3162,15 @@ impl OutlinePanel { .iter() .fold(HashMap::default(), |mut excerpts_to_fetch, fs_entry| { match fs_entry { - FsEntry::File(_, _, buffer_id, file_excerpts) - | FsEntry::ExternalFile(buffer_id, file_excerpts) => { + FsEntry::File(FsEntryFile { + buffer_id, + excerpts: file_excerpts, + .. + }) + | FsEntry::ExternalFile(FsEntryExternalFile { + buffer_id, + excerpts: file_excerpts, + }) => { let excerpts = self.excerpts.get(buffer_id); for &file_excerpt in file_excerpts { if let Some(excerpt) = excerpts @@ -3038,21 +3220,28 @@ impl OutlinePanel { fn abs_path(&self, entry: &PanelEntry, cx: &AppContext) -> Option { match entry { PanelEntry::Fs( - FsEntry::File(_, _, buffer_id, _) | FsEntry::ExternalFile(buffer_id, _), + FsEntry::File(FsEntryFile { buffer_id, .. }) + | FsEntry::ExternalFile(FsEntryExternalFile { buffer_id, .. }), ) => self .buffer_snapshot_for_id(*buffer_id, cx) .and_then(|buffer_snapshot| { let file = File::from_dyn(buffer_snapshot.file())?; file.worktree.read(cx).absolutize(&file.path).ok() }), - PanelEntry::Fs(FsEntry::Directory(worktree_id, entry)) => self + PanelEntry::Fs(FsEntry::Directory(FsEntryDirectory { + worktree_id, entry, .. + })) => self .project .read(cx) .worktree_for_id(*worktree_id, cx)? .read(cx) .absolutize(&entry.path) .ok(), - PanelEntry::FoldedDirs(worktree_id, dirs) => dirs.last().and_then(|entry| { + PanelEntry::FoldedDirs(FoldedDirsEntry { + worktree_id, + entries: dirs, + .. + }) => dirs.last().and_then(|entry| { self.project .read(cx) .worktree_for_id(*worktree_id, cx) @@ -3064,12 +3253,12 @@ impl OutlinePanel { fn relative_path(&self, entry: &FsEntry, cx: &AppContext) -> Option> { match entry { - FsEntry::ExternalFile(buffer_id, _) => { + FsEntry::ExternalFile(FsEntryExternalFile { buffer_id, .. }) => { let buffer_snapshot = self.buffer_snapshot_for_id(*buffer_id, cx)?; Some(buffer_snapshot.file()?.path().clone()) } - FsEntry::Directory(_, entry) => Some(entry.path.clone()), - FsEntry::File(_, entry, ..) => Some(entry.path.clone()), + FsEntry::Directory(FsEntryDirectory { entry, .. }) => Some(entry.path.clone()), + FsEntry::File(FsEntryFile { entry, .. }) => Some(entry.path.clone()), } } @@ -3135,7 +3324,7 @@ impl OutlinePanel { let Ok(()) = outline_panel.update(&mut cx, |outline_panel, cx| { let auto_fold_dirs = OutlinePanelSettings::get_global(cx).auto_fold_dirs; - let mut folded_dirs_entry = None::<(usize, WorktreeId, Vec)>; + let mut folded_dirs_entry = None::<(usize, FoldedDirsEntry)>; let track_matches = query.is_some(); #[derive(Debug)] @@ -3149,29 +3338,29 @@ impl OutlinePanel { for entry in outline_panel.fs_entries.clone() { let is_expanded = outline_panel.is_expanded(&entry); let (depth, should_add) = match &entry { - FsEntry::Directory(worktree_id, dir_entry) => { + FsEntry::Directory(directory_entry) => { let mut should_add = true; let is_root = project .read(cx) - .worktree_for_id(*worktree_id, cx) + .worktree_for_id(directory_entry.worktree_id, cx) .map_or(false, |worktree| { - worktree.read(cx).root_entry() == Some(dir_entry) + worktree.read(cx).root_entry() == Some(&directory_entry.entry) }); let folded = auto_fold_dirs && !is_root && outline_panel .unfolded_dirs - .get(worktree_id) + .get(&directory_entry.worktree_id) .map_or(true, |unfolded_dirs| { - !unfolded_dirs.contains(&dir_entry.id) + !unfolded_dirs.contains(&directory_entry.entry.id) }); let fs_depth = outline_panel .fs_entries_depth - .get(&(*worktree_id, dir_entry.id)) + .get(&(directory_entry.worktree_id, directory_entry.entry.id)) .copied() .unwrap_or(0); while let Some(parent) = parent_dirs.last() { - if dir_entry.path.starts_with(&parent.path) { + if directory_entry.entry.path.starts_with(&parent.path) { break; } parent_dirs.pop(); @@ -3179,11 +3368,14 @@ impl OutlinePanel { let auto_fold = match parent_dirs.last() { Some(parent) => { parent.folded - && Some(parent.path.as_ref()) == dir_entry.path.parent() + && Some(parent.path.as_ref()) + == directory_entry.entry.path.parent() && outline_panel .fs_children_count - .get(worktree_id) - .and_then(|entries| entries.get(&dir_entry.path)) + .get(&directory_entry.worktree_id) + .and_then(|entries| { + entries.get(&directory_entry.entry.path) + }) .copied() .unwrap_or_default() .may_be_fold_part() @@ -3201,7 +3393,7 @@ impl OutlinePanel { parent.depth + 1 }; parent_dirs.push(ParentStats { - path: dir_entry.path.clone(), + path: directory_entry.entry.path.clone(), folded, expanded: parent_expanded && is_expanded, depth: new_depth, @@ -3210,7 +3402,7 @@ impl OutlinePanel { } None => { parent_dirs.push(ParentStats { - path: dir_entry.path.clone(), + path: directory_entry.entry.path.clone(), folded, expanded: is_expanded, depth: fs_depth, @@ -3219,37 +3411,38 @@ impl OutlinePanel { } }; - if let Some((folded_depth, folded_worktree_id, mut folded_dirs)) = - folded_dirs_entry.take() + if let Some((folded_depth, mut folded_dirs)) = folded_dirs_entry.take() { if folded - && worktree_id == &folded_worktree_id - && dir_entry.path.parent() - == folded_dirs.last().map(|entry| entry.path.as_ref()) + && directory_entry.worktree_id == folded_dirs.worktree_id + && directory_entry.entry.path.parent() + == folded_dirs + .entries + .last() + .map(|entry| entry.path.as_ref()) { - folded_dirs.push(dir_entry.clone()); - folded_dirs_entry = - Some((folded_depth, folded_worktree_id, folded_dirs)) + folded_dirs.entries.push(directory_entry.entry.clone()); + folded_dirs_entry = Some((folded_depth, folded_dirs)) } else { if !is_singleton { let start_of_collapsed_dir_sequence = !parent_expanded && parent_dirs .iter() .rev() - .nth(folded_dirs.len() + 1) + .nth(folded_dirs.entries.len() + 1) .map_or(true, |parent| parent.expanded); if start_of_collapsed_dir_sequence || parent_expanded || query.is_some() { if parent_folded { - folded_dirs.push(dir_entry.clone()); + folded_dirs + .entries + .push(directory_entry.entry.clone()); should_add = false; } - let new_folded_dirs = PanelEntry::FoldedDirs( - folded_worktree_id, - folded_dirs, - ); + let new_folded_dirs = + PanelEntry::FoldedDirs(folded_dirs.clone()); outline_panel.push_entry( &mut generation_state, track_matches, @@ -3263,12 +3456,25 @@ impl OutlinePanel { folded_dirs_entry = if parent_folded { None } else { - Some((depth, *worktree_id, vec![dir_entry.clone()])) + Some(( + depth, + FoldedDirsEntry { + worktree_id: directory_entry.worktree_id, + entries: vec![directory_entry.entry.clone()], + git_file_statuses: vec![directory_entry.git_status], + }, + )) }; } } else if folded { - folded_dirs_entry = - Some((depth, *worktree_id, vec![dir_entry.clone()])); + folded_dirs_entry = Some(( + depth, + FoldedDirsEntry { + worktree_id: directory_entry.worktree_id, + entries: vec![directory_entry.entry.clone()], + git_file_statuses: vec![directory_entry.git_status], + }, + )); } let should_add = @@ -3276,21 +3482,22 @@ impl OutlinePanel { (depth, should_add) } FsEntry::ExternalFile(..) => { - if let Some((folded_depth, worktree_id, folded_dirs)) = - folded_dirs_entry.take() - { + if let Some((folded_depth, folded_dir)) = folded_dirs_entry.take() { let parent_expanded = parent_dirs .iter() .rev() .find(|parent| { - folded_dirs.iter().all(|entry| entry.path != parent.path) + folded_dir + .entries + .iter() + .all(|entry| entry.path != parent.path) }) .map_or(true, |parent| parent.expanded); if !is_singleton && (parent_expanded || query.is_some()) { outline_panel.push_entry( &mut generation_state, track_matches, - PanelEntry::FoldedDirs(worktree_id, folded_dirs), + PanelEntry::FoldedDirs(folded_dir), folded_depth, cx, ); @@ -3299,22 +3506,23 @@ impl OutlinePanel { parent_dirs.clear(); (0, true) } - FsEntry::File(worktree_id, file_entry, ..) => { - if let Some((folded_depth, worktree_id, folded_dirs)) = - folded_dirs_entry.take() - { + FsEntry::File(file) => { + if let Some((folded_depth, folded_dirs)) = folded_dirs_entry.take() { let parent_expanded = parent_dirs .iter() .rev() .find(|parent| { - folded_dirs.iter().all(|entry| entry.path != parent.path) + folded_dirs + .entries + .iter() + .all(|entry| entry.path != parent.path) }) .map_or(true, |parent| parent.expanded); if !is_singleton && (parent_expanded || query.is_some()) { outline_panel.push_entry( &mut generation_state, track_matches, - PanelEntry::FoldedDirs(worktree_id, folded_dirs), + PanelEntry::FoldedDirs(folded_dirs), folded_depth, cx, ); @@ -3323,23 +3531,22 @@ impl OutlinePanel { let fs_depth = outline_panel .fs_entries_depth - .get(&(*worktree_id, file_entry.id)) + .get(&(file.worktree_id, file.entry.id)) .copied() .unwrap_or(0); while let Some(parent) = parent_dirs.last() { - if file_entry.path.starts_with(&parent.path) { + if file.entry.path.starts_with(&parent.path) { break; } parent_dirs.pop(); } - let (depth, should_add) = match parent_dirs.last() { + match parent_dirs.last() { Some(parent) => { let new_depth = parent.depth + 1; (new_depth, parent.expanded) } None => (fs_depth, true), - }; - (depth, should_add) + } } }; @@ -3373,12 +3580,16 @@ impl OutlinePanel { let excerpts_to_consider = if is_singleton || query.is_some() || (should_add && is_expanded) { match &entry { - FsEntry::File(_, _, buffer_id, entry_excerpts) => { - Some((*buffer_id, entry_excerpts)) - } - FsEntry::ExternalFile(buffer_id, entry_excerpts) => { - Some((*buffer_id, entry_excerpts)) - } + FsEntry::File(FsEntryFile { + buffer_id, + excerpts, + .. + }) + | FsEntry::ExternalFile(FsEntryExternalFile { + buffer_id, + excerpts, + .. + }) => Some((*buffer_id, excerpts)), _ => None, } } else { @@ -3417,17 +3628,22 @@ impl OutlinePanel { } } - if let Some((folded_depth, worktree_id, folded_dirs)) = folded_dirs_entry.take() { + if let Some((folded_depth, folded_dirs)) = folded_dirs_entry.take() { let parent_expanded = parent_dirs .iter() .rev() - .find(|parent| folded_dirs.iter().all(|entry| entry.path != parent.path)) + .find(|parent| { + folded_dirs + .entries + .iter() + .all(|entry| entry.path != parent.path) + }) .map_or(true, |parent| parent.expanded); if parent_expanded || query.is_some() { outline_panel.push_entry( &mut generation_state, track_matches, - PanelEntry::FoldedDirs(worktree_id, folded_dirs), + PanelEntry::FoldedDirs(folded_dirs), folded_depth, cx, ); @@ -3490,13 +3706,17 @@ impl OutlinePanel { depth: usize, cx: &mut WindowContext, ) { - let entry = if let PanelEntry::FoldedDirs(worktree_id, entries) = &entry { - match entries.len() { + let entry = if let PanelEntry::FoldedDirs(folded_dirs_entry) = &entry { + match folded_dirs_entry.entries.len() { 0 => { debug_panic!("Empty folded dirs receiver"); return; } - 1 => PanelEntry::Fs(FsEntry::Directory(*worktree_id, entries[0].clone())), + 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, } } else { @@ -3515,22 +3735,22 @@ impl OutlinePanel { .push(StringMatchCandidate::new(id, &file_name)); } } - PanelEntry::FoldedDirs(worktree_id, entries) => { - let dir_names = self.dir_names_string(entries, *worktree_id, cx); + PanelEntry::FoldedDirs(folded_dir_entry) => { + let dir_names = self.dir_names_string( + &folded_dir_entry.entries, + folded_dir_entry.worktree_id, + cx, + ); { state .match_candidates .push(StringMatchCandidate::new(id, &dir_names)); } } - PanelEntry::Outline(outline_entry) => match outline_entry { - OutlineEntry::Outline(_, _, outline) => { - state - .match_candidates - .push(StringMatchCandidate::new(id, &outline.text)); - } - OutlineEntry::Excerpt(..) => {} - }, + PanelEntry::Outline(OutlineEntry::Outline(outline_entry)) => state + .match_candidates + .push(StringMatchCandidate::new(id, &outline_entry.outline.text)), + PanelEntry::Outline(OutlineEntry::Excerpt(_)) => {} PanelEntry::Search(new_search_entry) => { if let Some(search_data) = new_search_entry.render_data.get() { state @@ -3580,11 +3800,17 @@ impl OutlinePanel { fn is_expanded(&self, entry: &FsEntry) -> bool { let entry_to_check = match entry { - FsEntry::ExternalFile(buffer_id, _) => CollapsedEntry::ExternalFile(*buffer_id), - FsEntry::File(worktree_id, _, buffer_id, _) => { - CollapsedEntry::File(*worktree_id, *buffer_id) + FsEntry::ExternalFile(FsEntryExternalFile { buffer_id, .. }) => { + CollapsedEntry::ExternalFile(*buffer_id) } - FsEntry::Directory(worktree_id, entry) => CollapsedEntry::Dir(*worktree_id, entry.id), + FsEntry::File(FsEntryFile { + worktree_id, + buffer_id, + .. + }) => CollapsedEntry::File(*worktree_id, *buffer_id), + FsEntry::Directory(FsEntryDirectory { + worktree_id, entry, .. + }) => CollapsedEntry::Dir(*worktree_id, entry.id), }; !self.collapsed_entries.contains(&entry_to_check) } @@ -3708,11 +3934,11 @@ impl OutlinePanel { self.push_entry( state, track_matches, - PanelEntry::Outline(OutlineEntry::Excerpt( + PanelEntry::Outline(OutlineEntry::Excerpt(OutlineEntryExcerpt { buffer_id, - excerpt_id, - excerpt.range.clone(), - )), + id: excerpt_id, + range: excerpt.range.clone(), + })), excerpt_depth, cx, ); @@ -3733,11 +3959,11 @@ impl OutlinePanel { self.push_entry( state, track_matches, - PanelEntry::Outline(OutlineEntry::Outline( + PanelEntry::Outline(OutlineEntry::Outline(OutlineEntryOutline { buffer_id, excerpt_id, - outline.clone(), - )), + outline: outline.clone(), + })), outline_base_depth + outline.depth, cx, ); @@ -3763,9 +3989,9 @@ impl OutlinePanel { let kind = search_state.kind; let related_excerpts = match &parent_entry { - FsEntry::Directory(_, _) => return, - FsEntry::ExternalFile(_, excerpts) => excerpts, - FsEntry::File(_, _, _, excerpts) => excerpts, + FsEntry::Directory(_) => return, + FsEntry::ExternalFile(external) => &external.excerpts, + FsEntry::File(file) => &file.excerpts, } .iter() .copied() @@ -4031,24 +4257,28 @@ impl OutlinePanel { fn width_estimate(&self, depth: usize, entry: &PanelEntry, cx: &AppContext) -> u64 { let item_text_chars = match entry { - PanelEntry::Fs(FsEntry::ExternalFile(buffer_id, _)) => self - .buffer_snapshot_for_id(*buffer_id, cx) + PanelEntry::Fs(FsEntry::ExternalFile(external)) => self + .buffer_snapshot_for_id(external.buffer_id, cx) .and_then(|snapshot| { Some(snapshot.file()?.path().file_name()?.to_string_lossy().len()) }) .unwrap_or_default(), - PanelEntry::Fs(FsEntry::Directory(_, directory)) => directory + PanelEntry::Fs(FsEntry::Directory(directory)) => directory + .entry .path .file_name() .map(|name| name.to_string_lossy().len()) .unwrap_or_default(), - PanelEntry::Fs(FsEntry::File(_, file, _, _)) => file + PanelEntry::Fs(FsEntry::File(file)) => file + .entry .path .file_name() .map(|name| name.to_string_lossy().len()) .unwrap_or_default(), - PanelEntry::FoldedDirs(_, dirs) => { - dirs.iter() + PanelEntry::FoldedDirs(folded_dirs) => { + folded_dirs + .entries + .iter() .map(|dir| { dir.path .file_name() @@ -4056,13 +4286,13 @@ impl OutlinePanel { .unwrap_or_default() }) .sum::() - + dirs.len().saturating_sub(1) * MAIN_SEPARATOR_STR.len() + + folded_dirs.entries.len().saturating_sub(1) * MAIN_SEPARATOR_STR.len() } - PanelEntry::Outline(OutlineEntry::Excerpt(buffer_id, _, range)) => self - .excerpt_label(*buffer_id, range, cx) + PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => self + .excerpt_label(excerpt.buffer_id, &excerpt.range, cx) .map(|label| label.len()) .unwrap_or_default(), - PanelEntry::Outline(OutlineEntry::Outline(_, _, outline)) => outline.text.len(), + PanelEntry::Outline(OutlineEntry::Outline(entry)) => entry.outline.text.len(), PanelEntry::Search(search) => search .render_data .get() @@ -4136,38 +4366,25 @@ impl OutlinePanel { cached_entry.string_match.as_ref(), cx, )), - PanelEntry::FoldedDirs(worktree_id, entries) => { + PanelEntry::FoldedDirs(folded_dirs_entry) => { Some(outline_panel.render_folded_dirs( - worktree_id, - &entries, + &folded_dirs_entry, + cached_entry.depth, + cached_entry.string_match.as_ref(), + cx, + )) + } + PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => { + outline_panel.render_excerpt(&excerpt, cached_entry.depth, cx) + } + PanelEntry::Outline(OutlineEntry::Outline(entry)) => { + Some(outline_panel.render_outline( + &entry, cached_entry.depth, cached_entry.string_match.as_ref(), cx, )) } - PanelEntry::Outline(OutlineEntry::Excerpt( - buffer_id, - excerpt_id, - excerpt, - )) => outline_panel.render_excerpt( - buffer_id, - excerpt_id, - &excerpt, - cached_entry.depth, - cx, - ), - PanelEntry::Outline(OutlineEntry::Outline( - buffer_id, - excerpt_id, - outline, - )) => Some(outline_panel.render_outline( - buffer_id, - excerpt_id, - &outline, - cached_entry.depth, - cached_entry.string_match.as_ref(), - cx, - )), PanelEntry::Search(SearchEntry { match_range, render_data, @@ -4314,23 +4531,24 @@ impl OutlinePanel { self.fs_entries .iter() .skip_while(|fs_entry| match fs_entry { - FsEntry::Directory(worktree_id, entry) => { - *worktree_id != dir_worktree || entry != dir_entry + FsEntry::Directory(directory) => { + directory.worktree_id != dir_worktree || &directory.entry != dir_entry } _ => true, }) .skip(1) .take_while(|fs_entry| match fs_entry { FsEntry::ExternalFile(..) => false, - FsEntry::Directory(worktree_id, entry) => { - *worktree_id == dir_worktree && entry.path.starts_with(&dir_entry.path) + FsEntry::Directory(directory) => { + directory.worktree_id == dir_worktree + && directory.entry.path.starts_with(&dir_entry.path) } - FsEntry::File(worktree_id, entry, ..) => { - *worktree_id == dir_worktree && entry.path.starts_with(&dir_entry.path) + FsEntry::File(file) => { + file.worktree_id == dir_worktree && file.entry.path.starts_with(&dir_entry.path) } }) .filter_map(|fs_entry| match fs_entry { - FsEntry::File(_, _, buffer_id, _) => Some(*buffer_id), + FsEntry::File(file) => Some(file.buffer_id), _ => None, }) .collect() @@ -4674,14 +4892,14 @@ fn subscribe_for_editor_events( .fs_entries .iter() .find_map(|fs_entry| match fs_entry { - FsEntry::ExternalFile(buffer_id, _) => { - if *buffer_id == toggled_buffer_id { + FsEntry::ExternalFile(external) => { + if external.buffer_id == toggled_buffer_id { Some(fs_entry.clone()) } else { None } } - FsEntry::File(_, _, buffer_id, _) => { + FsEntry::File(FsEntryFile { buffer_id, .. }) => { if *buffer_id == toggled_buffer_id { Some(fs_entry.clone()) } else { @@ -5541,41 +5759,46 @@ mod tests { } display_string += &match &entry.entry { PanelEntry::Fs(entry) => match entry { - FsEntry::ExternalFile(_, _) => { + FsEntry::ExternalFile(_) => { panic!("Did not cover external files with tests") } - FsEntry::Directory(_, dir_entry) => format!( + FsEntry::Directory(directory) => format!( "{}/", - dir_entry + directory + .entry .path .file_name() .map(|name| name.to_string_lossy().to_string()) .unwrap_or_default() ), - FsEntry::File(_, file_entry, ..) => file_entry + FsEntry::File(file) => file + .entry .path .file_name() .map(|name| name.to_string_lossy().to_string()) .unwrap_or_default(), }, - PanelEntry::FoldedDirs(_, dirs) => dirs + PanelEntry::FoldedDirs(folded_dirs) => folded_dirs + .entries .iter() .filter_map(|dir| dir.path.file_name()) .map(|name| name.to_string_lossy().to_string() + "/") .collect(), PanelEntry::Outline(outline_entry) => match outline_entry { - OutlineEntry::Excerpt(_, _, _) => continue, - OutlineEntry::Outline(_, _, outline) => format!("outline: {}", outline.text), + OutlineEntry::Excerpt(_) => continue, + OutlineEntry::Outline(outline_entry) => { + format!("outline: {}", outline_entry.outline.text) + } }, - PanelEntry::Search(SearchEntry { - render_data, - match_range, - .. - }) => { + PanelEntry::Search(search_entry) => { format!( "search: {}", - render_data - .get_or_init(|| SearchData::new(match_range, &multi_buffer_snapshot)) + search_entry + .render_data + .get_or_init(|| SearchData::new( + &search_entry.match_range, + &multi_buffer_snapshot + )) .context_text ) } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 3def4506e8..0083bd5561 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -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 { + 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 { 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)]) { 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()), ) }); } diff --git a/crates/project/src/worktree_store.rs b/crates/project/src/worktree_store.rs index 9fce4d90c9..64f31ee7bd 100644 --- a/crates/project/src/worktree_store.rs +++ b/crates/project/src/worktree_store.rs @@ -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); } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 2053fbe6d4..20bab76334 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -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>, - visible_entries: Vec<(WorktreeId, Vec, OnceCell>>)>, + visible_entries: Vec<( + WorktreeId, + Vec<(Entry, Option)>, + OnceCell>>, + )>, /// 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 = 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::>(); 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, &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, WorktreeId) -> bool, cx: &mut ViewContext, ) -> Option { 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, WorktreeId) -> bool, cx: &mut ViewContext, ) -> Option { 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) diff --git a/crates/tab_switcher/src/tab_switcher.rs b/crates/tab_switcher/src/tab_switcher.rs index 5d02884472..c4223d09c6 100644 --- a/crates/tab_switcher/src/tab_switcher.rs +++ b/crates/tab_switcher/src/tab_switcher.rs @@ -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(); diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index 3623b97aa8..a76192a817 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -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 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, +} + +impl Deref for EntryWithGitStatus { + type Target = Entry; + + fn deref(&self) -> &Self::Target { + &self.entry + } +} + +impl< + 'a, + // I: Iterator + FusedIterator, + > Iterator for GitTraversal<'a> +{ + type Item = (Entry, Option); + fn next(&mut self) -> Option { + todo!() + } +} + +impl< + 'a, + // I: Iterator + FusedIterator, + > DoubleEndedIterator for GitTraversal<'a> +{ + fn next_back(&mut self) -> Option { + 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) }