Properly align excerpt and outline items (#13070)
This commit is contained in:
parent
9bc3c6810b
commit
ff8486e67f
1 changed files with 79 additions and 82 deletions
|
@ -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
|
|
||||||
} else {
|
|
||||||
self.fs_entries_depth
|
|
||||||
.get(&(*worktree_id, file_entry.id))
|
.get(&(*worktree_id, file_entry.id))
|
||||||
.map(|&(_, depth)| depth)
|
.map(|&(_, depth)| depth)
|
||||||
.unwrap_or(0)
|
.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 {
|
|
||||||
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);
|
|
||||||
|
for outline in excerpt.iter_outlines() {
|
||||||
entries.push((
|
entries.push((
|
||||||
outline_depth,
|
outline_base_depth + outline.depth,
|
||||||
EntryOwned::Outline(
|
EntryOwned::Outline(*buffer_id, entry_excerpt, outline.clone()),
|
||||||
*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]) {
|
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()
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue