diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index aae355a697..4122162ae9 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -19,12 +19,12 @@ use editor::{ use file_icons::FileIcons; use futures::{stream::FuturesUnordered, StreamExt}; use gpui::{ - actions, anchored, deferred, div, px, uniform_list, Action, AppContext, AssetSource, - AsyncWindowContext, ClipboardItem, DismissEvent, Div, ElementId, EntityId, EventEmitter, - FocusHandle, FocusableView, InteractiveElement, IntoElement, KeyContext, Model, MouseButton, - MouseDownEvent, ParentElement, Pixels, Point, Render, SharedString, Stateful, Styled, - Subscription, Task, UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, - WindowContext, + actions, anchored, deferred, div, px, uniform_list, Action, AnyElement, AppContext, + AssetSource, AsyncWindowContext, ClipboardItem, DismissEvent, Div, ElementId, EntityId, + EventEmitter, FocusHandle, FocusableView, InteractiveElement, IntoElement, KeyContext, Model, + MouseButton, MouseDownEvent, ParentElement, Pixels, Point, Render, SharedString, Stateful, + Styled, Subscription, Task, UniformListScrollHandle, View, ViewContext, VisualContext, + WeakView, WindowContext, }; use itertools::Itertools; use language::{BufferId, BufferSnapshot, OffsetRangeExt, OutlineItem}; @@ -97,6 +97,7 @@ enum CollapsedEntry { Excerpt(BufferId, ExcerptId), } +#[derive(Debug)] struct Excerpt { range: ExcerptRange, outlines: ExcerptOutlines, @@ -126,6 +127,7 @@ impl Excerpt { } } +#[derive(Debug)] enum ExcerptOutlines { Outlines(Vec), Invalidated(Vec), @@ -1247,12 +1249,11 @@ impl OutlinePanel { let color = entry_git_aware_label_color(None, false, is_active); let icon = if has_outlines { FileIcons::get_chevron_icon(is_expanded, cx) + .map(|icon_path| Icon::from_path(icon_path).color(color).into_any_element()) } else { None } - .map(Icon::from_path) - .map(|icon| icon.color(color)); - let depth = if icon.is_some() { depth + 1 } else { depth }; + .unwrap_or_else(empty_icon); let buffer_snapshot = self .project @@ -1274,7 +1275,7 @@ impl OutlinePanel { EntryRef::Excerpt(buffer_id, excerpt_id, range), item_id, depth, - icon, + Some(icon), is_active, label_element, cx, @@ -1304,12 +1305,16 @@ impl OutlinePanel { } _ => false, }; - + let icon = if self.is_singleton_active(cx) { + None + } else { + Some(empty_icon()) + }; self.entry_element( EntryRef::Outline(buffer_id, excerpt_id, rendered_outline), item_id, depth, - None, + icon, is_active, label_element, cx, @@ -1334,18 +1339,17 @@ impl OutlinePanel { 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()) } else { None - } - .map(Icon::from_path) - .map(|icon| icon.color(color)); + }; ( ElementId::from(entry.id.to_proto() as usize), Label::new(name) .single_line() .color(color) .into_any_element(), - icon, + icon.unwrap_or_else(empty_icon), ) } FsEntry::Directory(worktree_id, entry) => { @@ -1362,14 +1366,14 @@ impl OutlinePanel { FileIcons::get_chevron_icon(is_expanded, cx) } .map(Icon::from_path) - .map(|icon| icon.color(color)); + .map(|icon| icon.color(color).into_any_element()); ( ElementId::from(entry.id.to_proto() as usize), Label::new(name) .single_line() .color(color) .into_any_element(), - icon, + icon.unwrap_or_else(empty_icon), ) } FsEntry::ExternalFile(buffer_id, ..) => { @@ -1384,7 +1388,7 @@ impl OutlinePanel { None } .map(Icon::from_path) - .map(|icon| icon.color(color)); + .map(|icon| icon.color(color).into_any_element()); (icon, file_name(path.as_ref())) } None => (None, "Untitled".to_string()), @@ -1397,7 +1401,7 @@ impl OutlinePanel { .single_line() .color(color) .into_any_element(), - icon, + icon.unwrap_or_else(empty_icon), ) } }; @@ -1406,7 +1410,7 @@ impl OutlinePanel { EntryRef::Entry(rendered_entry), item_id, depth, - icon, + Some(icon), is_active, label_element, cx, @@ -1450,7 +1454,7 @@ impl OutlinePanel { FileIcons::get_chevron_icon(is_expanded, cx) } .map(Icon::from_path) - .map(|icon| icon.color(color)); + .map(|icon| icon.color(color).into_any_element()); ( ElementId::from( dir_entries @@ -1462,7 +1466,7 @@ impl OutlinePanel { .single_line() .color(color) .into_any_element(), - icon, + icon.unwrap_or_else(empty_icon), ) }; @@ -1470,7 +1474,7 @@ impl OutlinePanel { EntryRef::FoldedDirs(worktree_id, dir_entries), item_id, depth, - icon, + Some(icon), is_active, label_element, cx, @@ -1483,7 +1487,7 @@ impl OutlinePanel { rendered_entry: EntryRef<'_>, item_id: ElementId, depth: usize, - icon: Option, + icon_element: Option, is_active: bool, label_element: gpui::AnyElement, cx: &mut ViewContext, @@ -1498,13 +1502,8 @@ impl OutlinePanel { .indent_level(depth) .indent_step_size(px(settings.indent_size)) .selected(is_active) - .child(if let Some(icon) = icon { - h_flex().child(icon) - } else { - h_flex() - .size(IconSize::default().rems()) - .invisible() - .flex_none() + .when_some(icon_element, |list_item, icon_element| { + list_item.child(h_flex().child(icon_element)) }) .child(h_flex().h_6().child(label_element).ml_1()) .on_click({ @@ -2172,32 +2171,15 @@ impl OutlinePanel { } fn entries_with_depths(&mut self, cx: &AppContext) -> &[(usize, EntryOwned)] { + let is_singleton = self.is_singleton_active(cx); self.cached_entries_with_depth.get_or_insert_with(|| { let auto_fold_dirs = OutlinePanelSettings::get_global(cx).auto_fold_dirs; let mut folded_dirs_entry = None::<(usize, WorktreeId, Vec)>; let mut entries = Vec::new(); - let is_singleton = self - .active_item - .as_ref() - .and_then(|active_item| { - Some( - active_item - .active_editor - .upgrade()? - .read(cx) - .buffer() - .read(cx) - .is_singleton(), - ) - }) - .unwrap_or(false); for entry in &self.fs_entries { let depth = match entry { FsEntry::Directory(worktree_id, dir_entry) => { - if is_singleton { - continue; - } let depth = self .fs_entries_depth .get(&(*worktree_id, dir_entry.id)) @@ -2240,16 +2222,11 @@ impl OutlinePanel { depth } FsEntry::ExternalFile(..) => 0, - FsEntry::File(worktree_id, file_entry, ..) => { - if is_singleton { - 0 - } else { - self.fs_entries_depth - .get(&(*worktree_id, file_entry.id)) - .map(|&(_, depth)| depth) - .unwrap_or(0) - } - } + FsEntry::File(worktree_id, file_entry, ..) => self + .fs_entries_depth + .get(&(*worktree_id, file_entry.id)) + .map(|&(_, depth)| depth) + .unwrap_or(0), }; if let Some((folded_depth, worktree_id, folded_dirs)) = folded_dirs_entry.take() { entries.push(( @@ -2267,7 +2244,7 @@ impl OutlinePanel { let Some(excerpt) = excerpts.get(&entry_excerpt) else { continue; }; - let excerpt_depth = depth; + let excerpt_depth = depth + 1; entries.push(( excerpt_depth, EntryOwned::Excerpt( @@ -2277,30 +2254,25 @@ impl OutlinePanel { ), )); - if !self + let mut outline_base_depth = excerpt_depth + 1; + if is_singleton { + outline_base_depth = 0; + entries.clear(); + } else if self .collapsed_entries .contains(&CollapsedEntry::Excerpt(*buffer_id, entry_excerpt)) { - let mut outline_data_depth = None::; - let mut outline_depth = excerpt_depth + 1; - for outline in excerpt.iter_outlines() { - if let Some(outline_data_depth) = outline_data_depth { - match outline_data_depth.cmp(&outline.depth) { - cmp::Ordering::Less => outline_depth += 1, - cmp::Ordering::Equal => {} - cmp::Ordering::Greater => outline_depth -= 1, - }; - } - outline_data_depth = Some(outline.depth); - entries.push(( - outline_depth, - EntryOwned::Outline( - *buffer_id, - entry_excerpt, - outline.clone(), - ), - )); - } + continue; + } + + for outline in excerpt.iter_outlines() { + entries.push(( + outline_base_depth + outline.depth, + EntryOwned::Outline(*buffer_id, entry_excerpt, outline.clone()), + )); + } + if is_singleton && entries.is_empty() { + entries.push((0, EntryOwned::Entry(entry.clone()))); } } } @@ -2316,6 +2288,23 @@ impl OutlinePanel { }) } + fn is_singleton_active(&self, cx: &AppContext) -> bool { + self.active_item + .as_ref() + .and_then(|active_item| { + Some( + active_item + .active_editor + .upgrade()? + .read(cx) + .buffer() + .read(cx) + .is_singleton(), + ) + }) + .unwrap_or(false) + } + fn invalidate_outlines(&mut self, ids: &[ExcerptId]) { self.outline_fetch_tasks.clear(); let mut ids = ids.into_iter().collect::>(); @@ -2668,3 +2657,11 @@ fn range_contains( range.start.cmp(&anchor, buffer_snapshot).is_le() && range.end.cmp(&anchor, buffer_snapshot).is_ge() } + +fn empty_icon() -> AnyElement { + h_flex() + .size(IconSize::default().rems()) + .invisible() + .flex_none() + .into_any_element() +}