Properly align excerpt and outline items (#13070)

This commit is contained in:
Kirill Bulatov 2024-06-14 23:26:07 +03:00 committed by GitHub
parent 9bc3c6810b
commit ff8486e67f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

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