Improve outline panel entries' revealing and grouping (#13127)
Release Notes: - N/A
This commit is contained in:
parent
2b46a4a0e9
commit
0afb3abfd2
4 changed files with 346 additions and 242 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -7162,7 +7162,6 @@ dependencies = [
|
||||||
"db",
|
"db",
|
||||||
"editor",
|
"editor",
|
||||||
"file_icons",
|
"file_icons",
|
||||||
"futures 0.3.28",
|
|
||||||
"gpui",
|
"gpui",
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
"language",
|
"language",
|
||||||
|
|
|
@ -1021,6 +1021,22 @@ impl Debug for DisplayPoint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Add for DisplayPoint {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, other: Self) -> Self::Output {
|
||||||
|
DisplayPoint(BlockPoint(self.0 .0 + other.0 .0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub for DisplayPoint {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn sub(self, other: Self) -> Self::Output {
|
||||||
|
DisplayPoint(BlockPoint(self.0 .0 - other.0 .0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
|
#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct DisplayRow(pub u32);
|
pub struct DisplayRow(pub u32);
|
||||||
|
|
|
@ -18,7 +18,6 @@ collections.workspace = true
|
||||||
db.workspace = true
|
db.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
file_icons.workspace = true
|
file_icons.workspace = true
|
||||||
futures.workspace = true
|
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
|
|
|
@ -12,12 +12,12 @@ use anyhow::Context;
|
||||||
use collections::{hash_map, BTreeSet, HashMap, HashSet};
|
use collections::{hash_map, BTreeSet, HashMap, HashSet};
|
||||||
use db::kvp::KEY_VALUE_STORE;
|
use db::kvp::KEY_VALUE_STORE;
|
||||||
use editor::{
|
use editor::{
|
||||||
|
display_map::ToDisplayPoint,
|
||||||
items::{entry_git_aware_label_color, entry_label_color},
|
items::{entry_git_aware_label_color, entry_label_color},
|
||||||
scroll::ScrollAnchor,
|
scroll::ScrollAnchor,
|
||||||
Editor, EditorEvent, ExcerptId, ExcerptRange,
|
DisplayPoint, Editor, EditorEvent, ExcerptId, ExcerptRange,
|
||||||
};
|
};
|
||||||
use file_icons::FileIcons;
|
use file_icons::FileIcons;
|
||||||
use futures::{stream::FuturesUnordered, StreamExt};
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, anchored, deferred, div, px, uniform_list, Action, AnyElement, AppContext,
|
actions, anchored, deferred, div, px, uniform_list, Action, AnyElement, AppContext,
|
||||||
AssetSource, AsyncWindowContext, ClipboardItem, DismissEvent, Div, ElementId, EntityId,
|
AssetSource, AsyncWindowContext, ClipboardItem, DismissEvent, Div, ElementId, EntityId,
|
||||||
|
@ -31,10 +31,10 @@ use language::{BufferId, BufferSnapshot, OffsetRangeExt, OutlineItem};
|
||||||
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
|
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
|
||||||
|
|
||||||
use outline_panel_settings::{OutlinePanelDockPosition, OutlinePanelSettings};
|
use outline_panel_settings::{OutlinePanelDockPosition, OutlinePanelSettings};
|
||||||
use project::{File, Fs, Project};
|
use project::{File, Fs, Item, Project};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
use util::{ResultExt, TryFutureExt};
|
use util::{RangeExt, ResultExt, TryFutureExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
dock::{DockPosition, Panel, PanelEvent},
|
dock::{DockPosition, Panel, PanelEvent},
|
||||||
item::ItemHandle,
|
item::ItemHandle,
|
||||||
|
@ -77,7 +77,7 @@ pub struct OutlinePanel {
|
||||||
context_menu: Option<(View<ContextMenu>, Point<Pixels>, Subscription)>,
|
context_menu: Option<(View<ContextMenu>, Point<Pixels>, Subscription)>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
pending_serialization: Task<Option<()>>,
|
pending_serialization: Task<Option<()>>,
|
||||||
fs_entries_depth: HashMap<(WorktreeId, ProjectEntryId), (bool, usize)>,
|
fs_entries_depth: HashMap<(WorktreeId, ProjectEntryId), usize>,
|
||||||
fs_entries: Vec<FsEntry>,
|
fs_entries: Vec<FsEntry>,
|
||||||
collapsed_entries: HashSet<CollapsedEntry>,
|
collapsed_entries: HashSet<CollapsedEntry>,
|
||||||
unfolded_dirs: HashMap<WorktreeId, BTreeSet<ProjectEntryId>>,
|
unfolded_dirs: HashMap<WorktreeId, BTreeSet<ProjectEntryId>>,
|
||||||
|
@ -86,7 +86,7 @@ pub struct OutlinePanel {
|
||||||
active_item: Option<ActiveItem>,
|
active_item: Option<ActiveItem>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
update_task: Task<()>,
|
update_task: Task<()>,
|
||||||
outline_fetch_tasks: Vec<Task<()>>,
|
outline_fetch_tasks: HashMap<(BufferId, ExcerptId), Task<()>>,
|
||||||
excerpts: HashMap<BufferId, HashMap<ExcerptId, Excerpt>>,
|
excerpts: HashMap<BufferId, HashMap<ExcerptId, Excerpt>>,
|
||||||
cached_entries_with_depth: Option<Vec<(usize, EntryOwned)>>,
|
cached_entries_with_depth: Option<Vec<(usize, EntryOwned)>>,
|
||||||
}
|
}
|
||||||
|
@ -382,7 +382,7 @@ impl OutlinePanel {
|
||||||
active_item: None,
|
active_item: None,
|
||||||
pending_serialization: Task::ready(None),
|
pending_serialization: Task::ready(None),
|
||||||
update_task: Task::ready(()),
|
update_task: Task::ready(()),
|
||||||
outline_fetch_tasks: Vec::new(),
|
outline_fetch_tasks: HashMap::default(),
|
||||||
excerpts: HashMap::default(),
|
excerpts: HashMap::default(),
|
||||||
last_visible_range: 0..0,
|
last_visible_range: 0..0,
|
||||||
cached_entries_with_depth: None,
|
cached_entries_with_depth: None,
|
||||||
|
@ -1009,12 +1009,23 @@ impl OutlinePanel {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.collapsed_entries.extend(
|
let new_entries = self
|
||||||
self.fs_entries_depth
|
.entries_with_depths(cx)
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, &(is_dir, depth))| is_dir && depth == 0)
|
.flat_map(|(_, entry)| match entry {
|
||||||
.map(|(&(worktree_id, entry_id), _)| CollapsedEntry::Dir(worktree_id, entry_id)),
|
EntryOwned::Entry(FsEntry::Directory(worktree_id, entry)) => {
|
||||||
);
|
Some(CollapsedEntry::Dir(*worktree_id, entry.id))
|
||||||
|
}
|
||||||
|
EntryOwned::FoldedDirs(worktree_id, entries) => {
|
||||||
|
Some(CollapsedEntry::Dir(*worktree_id, entries.last()?.id))
|
||||||
|
}
|
||||||
|
EntryOwned::Excerpt(buffer_id, excerpt_id, _) => {
|
||||||
|
Some(CollapsedEntry::Excerpt(*buffer_id, *excerpt_id))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
self.collapsed_entries.extend(new_entries);
|
||||||
self.update_fs_entries(&editor, HashSet::default(), None, None, false, cx);
|
self.update_fs_entries(&editor, HashSet::default(), None, None, false, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1138,55 +1149,80 @@ impl OutlinePanel {
|
||||||
if !OutlinePanelSettings::get_global(cx).auto_reveal_entries {
|
if !OutlinePanelSettings::get_global(cx).auto_reveal_entries {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let Some((buffer_id, excerpt_id, outline)) = self.location_for_editor_selection(editor, cx)
|
let Some(entry_with_selection) = self.location_for_editor_selection(editor, cx) else {
|
||||||
else {
|
self.selected_entry = None;
|
||||||
|
cx.notify();
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some((file_entry_with_selection, entry_with_selection)) =
|
let related_buffer_entry = match entry_with_selection {
|
||||||
self.entry_for_selection(buffer_id, excerpt_id, outline)
|
EntryOwned::Entry(FsEntry::File(worktree_id, _, buffer_id, _)) => {
|
||||||
else {
|
let project = self.project.read(cx);
|
||||||
return;
|
let entry_id = project
|
||||||
|
.buffer_for_id(buffer_id)
|
||||||
|
.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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
EntryOwned::Outline(buffer_id, excerpt_id, _)
|
||||||
|
| EntryOwned::Excerpt(buffer_id, excerpt_id, _) => {
|
||||||
|
self.collapsed_entries
|
||||||
|
.remove(&CollapsedEntry::Excerpt(buffer_id, excerpt_id));
|
||||||
|
let project = self.project.read(cx);
|
||||||
|
let entry_id = project
|
||||||
|
.buffer_for_id(buffer_id)
|
||||||
|
.and_then(|buffer| buffer.read(cx).entry_id(cx));
|
||||||
|
entry_id.and_then(|entry_id| {
|
||||||
|
let worktree = project.worktree_for_entry(entry_id, cx)?;
|
||||||
|
let entry = worktree.read(cx).entry_for_id(entry_id)?.clone();
|
||||||
|
Some((worktree, entry))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
EntryOwned::Entry(FsEntry::ExternalFile(..)) => None,
|
||||||
|
_ => return,
|
||||||
};
|
};
|
||||||
if self.selected_entry.as_ref() == Some(&entry_with_selection) {
|
if let Some((worktree, buffer_entry)) = related_buffer_entry {
|
||||||
return;
|
let worktree_id = worktree.read(cx).id();
|
||||||
}
|
let mut dirs_to_expand = Vec::new();
|
||||||
|
{
|
||||||
|
let mut traversal = worktree.read(cx).traverse_from_path(
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
buffer_entry.path.as_ref(),
|
||||||
|
);
|
||||||
|
let mut current_entry = buffer_entry;
|
||||||
|
loop {
|
||||||
|
if current_entry.is_dir() {
|
||||||
|
if self
|
||||||
|
.collapsed_entries
|
||||||
|
.remove(&CollapsedEntry::Dir(worktree_id, current_entry.id))
|
||||||
|
{
|
||||||
|
dirs_to_expand.push(current_entry.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let FsEntry::File(file_worktree_id, file_entry, ..) = file_entry_with_selection {
|
|
||||||
if let Some(worktree) = self.project.read(cx).worktree_for_id(file_worktree_id, cx) {
|
|
||||||
let parent_entry = {
|
|
||||||
let mut traversal = worktree.read(cx).traverse_from_path(
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
file_entry.path.as_ref(),
|
|
||||||
);
|
|
||||||
if traversal.back_to_parent() {
|
if traversal.back_to_parent() {
|
||||||
traversal.entry()
|
if let Some(parent_entry) = traversal.entry() {
|
||||||
} else {
|
current_entry = parent_entry.clone();
|
||||||
None
|
continue;
|
||||||
}
|
}
|
||||||
.cloned()
|
|
||||||
};
|
|
||||||
if let Some(directory_entry) = parent_entry {
|
|
||||||
let worktree_id = worktree.read(cx).id();
|
|
||||||
let entry_id = directory_entry.id;
|
|
||||||
if self
|
|
||||||
.collapsed_entries
|
|
||||||
.remove(&CollapsedEntry::Dir(worktree_id, entry_id))
|
|
||||||
{
|
|
||||||
self.project
|
|
||||||
.update(cx, |project, cx| {
|
|
||||||
project.expand_entry(worktree_id, entry_id, cx)
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| Task::ready(Ok(())))
|
|
||||||
.detach_and_log_err(cx)
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
for dir_to_expand in dirs_to_expand {
|
||||||
if let EntryOwned::Outline(buffer_id, excerpt_id, _) = &entry_with_selection {
|
self.project
|
||||||
self.collapsed_entries
|
.update(cx, |project, cx| {
|
||||||
.remove(&CollapsedEntry::Excerpt(*buffer_id, *excerpt_id));
|
project.expand_entry(worktree_id, dir_to_expand, cx)
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| Task::ready(Ok(())))
|
||||||
|
.detach_and_log_err(cx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_fs_entries(
|
self.update_fs_entries(
|
||||||
|
@ -1199,26 +1235,6 @@ impl OutlinePanel {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entry_for_selection(
|
|
||||||
&mut self,
|
|
||||||
buffer_id: BufferId,
|
|
||||||
excerpt_id: ExcerptId,
|
|
||||||
outline: Option<OutlineItem<language::Anchor>>,
|
|
||||||
) -> Option<(FsEntry, EntryOwned)> {
|
|
||||||
let fs_entry_with_selection = self.fs_entries.iter().find(|entry| match entry {
|
|
||||||
FsEntry::File(_, _, file_buffer_id, excerpts)
|
|
||||||
| FsEntry::ExternalFile(file_buffer_id, excerpts) => {
|
|
||||||
file_buffer_id == &buffer_id && excerpts.contains(&excerpt_id)
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
});
|
|
||||||
let entry_with_selection = outline
|
|
||||||
.map(|outline| EntryOwned::Outline(buffer_id, excerpt_id, outline))
|
|
||||||
.or_else(|| Some(EntryOwned::Entry(fs_entry_with_selection.cloned()?)));
|
|
||||||
|
|
||||||
fs_entry_with_selection.cloned().zip(entry_with_selection)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_excerpt(
|
fn render_excerpt(
|
||||||
&self,
|
&self,
|
||||||
buffer_id: BufferId,
|
buffer_id: BufferId,
|
||||||
|
@ -1828,7 +1844,7 @@ impl OutlinePanel {
|
||||||
),
|
),
|
||||||
new_depth_map
|
new_depth_map
|
||||||
.get(&(worktree_id, id))
|
.get(&(worktree_id, id))
|
||||||
.map(|&(_, depth)| depth)
|
.copied()
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -1875,15 +1891,12 @@ impl OutlinePanel {
|
||||||
} else {
|
} else {
|
||||||
parent_id
|
parent_id
|
||||||
.and_then(|(worktree_id, id)| {
|
.and_then(|(worktree_id, id)| {
|
||||||
new_depth_map
|
new_depth_map.get(&(worktree_id, id)).copied()
|
||||||
.get(&(worktree_id, id))
|
|
||||||
.map(|&(_, depth)| depth)
|
|
||||||
})
|
})
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
+ 1
|
+ 1
|
||||||
};
|
};
|
||||||
new_depth_map
|
new_depth_map.insert((*worktree_id, dir_entry.id), depth);
|
||||||
.insert((*worktree_id, dir_entry.id), (true, depth));
|
|
||||||
}
|
}
|
||||||
FsEntry::File(worktree_id, file_entry, ..) => {
|
FsEntry::File(worktree_id, file_entry, ..) => {
|
||||||
let parent_id = back_to_common_visited_parent(
|
let parent_id = back_to_common_visited_parent(
|
||||||
|
@ -1896,15 +1909,12 @@ impl OutlinePanel {
|
||||||
} else {
|
} else {
|
||||||
parent_id
|
parent_id
|
||||||
.and_then(|(worktree_id, id)| {
|
.and_then(|(worktree_id, id)| {
|
||||||
new_depth_map
|
new_depth_map.get(&(worktree_id, id)).copied()
|
||||||
.get(&(worktree_id, id))
|
|
||||||
.map(|&(_, depth)| depth)
|
|
||||||
})
|
})
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
+ 1
|
+ 1
|
||||||
};
|
};
|
||||||
new_depth_map
|
new_depth_map.insert((*worktree_id, file_entry.id), depth);
|
||||||
.insert((*worktree_id, file_entry.id), (false, depth));
|
|
||||||
}
|
}
|
||||||
FsEntry::ExternalFile(..) => {
|
FsEntry::ExternalFile(..) => {
|
||||||
visited_dirs.clear();
|
visited_dirs.clear();
|
||||||
|
@ -1960,18 +1970,13 @@ impl OutlinePanel {
|
||||||
new_active_editor: View<Editor>,
|
new_active_editor: View<Editor>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
|
let new_selected_entry = self.location_for_editor_selection(&new_active_editor, cx);
|
||||||
self.clear_previous();
|
self.clear_previous();
|
||||||
self.active_item = Some(ActiveItem {
|
self.active_item = Some(ActiveItem {
|
||||||
item_id: new_active_editor.item_id(),
|
item_id: new_active_editor.item_id(),
|
||||||
_editor_subscrpiption: subscribe_for_editor_events(&new_active_editor, cx),
|
_editor_subscrpiption: subscribe_for_editor_events(&new_active_editor, cx),
|
||||||
active_editor: new_active_editor.downgrade(),
|
active_editor: new_active_editor.downgrade(),
|
||||||
});
|
});
|
||||||
let new_selected_entry = self
|
|
||||||
.location_for_editor_selection(&new_active_editor, cx)
|
|
||||||
.and_then(|(buffer_id, excerpt_id, outline)| {
|
|
||||||
let (_, entry) = self.entry_for_selection(buffer_id, excerpt_id, outline)?;
|
|
||||||
Some(entry)
|
|
||||||
});
|
|
||||||
let new_entries =
|
let new_entries =
|
||||||
HashSet::from_iter(new_active_editor.read(cx).buffer().read(cx).excerpt_ids());
|
HashSet::from_iter(new_active_editor.read(cx).buffer().read(cx).excerpt_ids());
|
||||||
self.update_fs_entries(
|
self.update_fs_entries(
|
||||||
|
@ -2002,172 +2007,192 @@ impl OutlinePanel {
|
||||||
&self,
|
&self,
|
||||||
editor: &View<Editor>,
|
editor: &View<Editor>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Option<(BufferId, ExcerptId, Option<Outline>)> {
|
) -> Option<EntryOwned> {
|
||||||
let selection = editor
|
let selection = editor
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.selections
|
.selections
|
||||||
.newest::<language::Point>(cx)
|
.newest::<language::Point>(cx)
|
||||||
.head();
|
.head();
|
||||||
|
let editor_snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx));
|
||||||
let multi_buffer = editor.read(cx).buffer();
|
let multi_buffer = editor.read(cx).buffer();
|
||||||
let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
|
let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
|
||||||
let selection = multi_buffer_snapshot.anchor_before(selection);
|
let (excerpt_id, buffer, _) = editor
|
||||||
let excerpt_id = selection.excerpt_id;
|
.read(cx)
|
||||||
let buffer_snapshot = multi_buffer_snapshot.buffer_for_excerpt(selection.excerpt_id)?;
|
.buffer()
|
||||||
let buffer_id = buffer_snapshot.remote_id();
|
.read(cx)
|
||||||
|
.excerpt_containing(selection, cx)?;
|
||||||
|
let buffer_id = buffer.read(cx).remote_id();
|
||||||
|
let selection_display_point = selection.to_display_point(&editor_snapshot);
|
||||||
|
|
||||||
let outline_item = self
|
let excerpt_outlines = self
|
||||||
.excerpts
|
.excerpts
|
||||||
.get(&buffer_id)
|
.get(&buffer_id)
|
||||||
.and_then(|excerpts| excerpts.get(&excerpt_id))
|
.and_then(|excerpts| excerpts.get(&excerpt_id))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|excerpt| excerpt.iter_outlines())
|
.flat_map(|excerpt| excerpt.iter_outlines())
|
||||||
.filter(|outline_item| {
|
.flat_map(|outline| {
|
||||||
range_contains(&outline_item.range, selection.text_anchor, buffer_snapshot)
|
let start = multi_buffer_snapshot
|
||||||
|
.anchor_in_excerpt(excerpt_id, outline.range.start)?
|
||||||
|
.to_display_point(&editor_snapshot);
|
||||||
|
let end = multi_buffer_snapshot
|
||||||
|
.anchor_in_excerpt(excerpt_id, outline.range.end)?
|
||||||
|
.to_display_point(&editor_snapshot);
|
||||||
|
Some((start..end, outline))
|
||||||
})
|
})
|
||||||
.min_by_key(|outline| {
|
.collect::<Vec<_>>();
|
||||||
let range = outline.range.start.offset..outline.range.end.offset;
|
|
||||||
let cursor_offset = selection.text_anchor.offset as isize;
|
let mut matching_outline_indices = Vec::new();
|
||||||
let distance_to_closest_endpoint = cmp::min(
|
let mut children = HashMap::default();
|
||||||
(range.start as isize - cursor_offset).abs(),
|
let mut parents_stack = Vec::<(&Range<DisplayPoint>, &&Outline, usize)>::new();
|
||||||
(range.end as isize - cursor_offset).abs(),
|
|
||||||
);
|
for (i, (outline_range, outline)) in excerpt_outlines.iter().enumerate() {
|
||||||
distance_to_closest_endpoint
|
if outline_range
|
||||||
|
.to_inclusive()
|
||||||
|
.contains(&selection_display_point)
|
||||||
|
{
|
||||||
|
matching_outline_indices.push(i);
|
||||||
|
} else if (outline_range.start.row()..outline_range.end.row())
|
||||||
|
.to_inclusive()
|
||||||
|
.contains(&selection_display_point.row())
|
||||||
|
{
|
||||||
|
matching_outline_indices.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some((parent_range, parent_outline, _)) = parents_stack.last() {
|
||||||
|
if parent_outline.depth >= outline.depth
|
||||||
|
|| !parent_range.contains(&outline_range.start)
|
||||||
|
{
|
||||||
|
parents_stack.pop();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some((_, _, parent_index)) = parents_stack.last_mut() {
|
||||||
|
children
|
||||||
|
.entry(*parent_index)
|
||||||
|
.or_insert_with(Vec::new)
|
||||||
|
.push(i);
|
||||||
|
}
|
||||||
|
parents_stack.push((outline_range, outline, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
let outline_item = matching_outline_indices
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|i| Some((i, excerpt_outlines.get(i)?)))
|
||||||
|
.filter(|(i, _)| {
|
||||||
|
children
|
||||||
|
.get(i)
|
||||||
|
.map(|children| {
|
||||||
|
children.iter().all(|child_index| {
|
||||||
|
excerpt_outlines
|
||||||
|
.get(*child_index)
|
||||||
|
.map(|(child_range, _)| child_range.start > selection_display_point)
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or(true)
|
||||||
})
|
})
|
||||||
|
.min_by_key(|(_, (outline_range, outline))| {
|
||||||
|
let distance_from_start = if outline_range.start > selection_display_point {
|
||||||
|
outline_range.start - selection_display_point
|
||||||
|
} else {
|
||||||
|
selection_display_point - outline_range.start
|
||||||
|
};
|
||||||
|
let distance_from_end = if outline_range.end > selection_display_point {
|
||||||
|
outline_range.end - selection_display_point
|
||||||
|
} else {
|
||||||
|
selection_display_point - outline_range.end
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
cmp::Reverse(outline.depth),
|
||||||
|
distance_from_start + distance_from_end,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map(|(_, (_, outline))| *outline)
|
||||||
.cloned();
|
.cloned();
|
||||||
|
|
||||||
Some((buffer_id, excerpt_id, outline_item))
|
let closest_container = match outline_item {
|
||||||
|
Some(outline) => EntryOwned::Outline(buffer_id, excerpt_id, outline),
|
||||||
|
None => self
|
||||||
|
.cached_entries_with_depth
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.rev()
|
||||||
|
.find_map(|(_, entry)| match entry {
|
||||||
|
EntryOwned::Excerpt(entry_buffer_id, entry_excerpt_id, _) => {
|
||||||
|
if entry_buffer_id == &buffer_id && entry_excerpt_id == &excerpt_id {
|
||||||
|
Some(entry.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EntryOwned::Entry(
|
||||||
|
FsEntry::ExternalFile(file_buffer_id, file_excerpts)
|
||||||
|
| FsEntry::File(_, _, file_buffer_id, file_excerpts),
|
||||||
|
) => {
|
||||||
|
if file_buffer_id == &buffer_id && file_excerpts.contains(&excerpt_id) {
|
||||||
|
Some(entry.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})?,
|
||||||
|
};
|
||||||
|
Some(closest_container)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_outlines(&mut self, range: &Range<usize>, cx: &mut ViewContext<Self>) {
|
fn fetch_outlines(&mut self, range: &Range<usize>, cx: &mut ViewContext<Self>) {
|
||||||
let project = self.project.clone();
|
|
||||||
let range_len = range.len();
|
let range_len = range.len();
|
||||||
let half_range = range_len / 2;
|
let half_range = range_len / 2;
|
||||||
let entries = self.entries_with_depths(cx);
|
let entries = self.entries_with_depths(cx);
|
||||||
let expanded_range =
|
let expanded_range =
|
||||||
range.start.saturating_sub(half_range)..(range.end + half_range).min(entries.len());
|
range.start.saturating_sub(half_range)..(range.end + half_range).min(entries.len());
|
||||||
|
|
||||||
let entries = entries
|
let excerpt_fetch_ranges = self.excerpt_fetch_ranges(expanded_range, cx);
|
||||||
.get(expanded_range)
|
|
||||||
.map(|slice| slice.to_vec())
|
|
||||||
.unwrap_or_default();
|
|
||||||
let excerpt_fetch_ranges = entries.into_iter().fold(
|
|
||||||
HashMap::<
|
|
||||||
BufferId,
|
|
||||||
(
|
|
||||||
BufferSnapshot,
|
|
||||||
HashMap<ExcerptId, ExcerptRange<language::Anchor>>,
|
|
||||||
),
|
|
||||||
>::default(),
|
|
||||||
|mut excerpts_to_fetch, (_, entry)| {
|
|
||||||
match entry {
|
|
||||||
EntryOwned::Entry(FsEntry::File(_, _, buffer_id, file_excerpts))
|
|
||||||
| EntryOwned::Entry(FsEntry::ExternalFile(buffer_id, file_excerpts)) => {
|
|
||||||
let excerpts = self.excerpts.get(&buffer_id);
|
|
||||||
for file_excerpt in file_excerpts {
|
|
||||||
if let Some(excerpt) = excerpts
|
|
||||||
.and_then(|excerpts| excerpts.get(&file_excerpt))
|
|
||||||
.filter(|excerpt| excerpt.should_fetch_outlines())
|
|
||||||
{
|
|
||||||
match excerpts_to_fetch.entry(buffer_id) {
|
|
||||||
hash_map::Entry::Occupied(mut o) => {
|
|
||||||
o.get_mut().1.insert(file_excerpt, excerpt.range.clone());
|
|
||||||
}
|
|
||||||
hash_map::Entry::Vacant(v) => {
|
|
||||||
if let Some(buffer_snapshot) = project
|
|
||||||
.read(cx)
|
|
||||||
.buffer_for_id(buffer_id)
|
|
||||||
.map(|buffer| buffer.read(cx).snapshot())
|
|
||||||
{
|
|
||||||
v.insert((buffer_snapshot, HashMap::default()))
|
|
||||||
.1
|
|
||||||
.insert(file_excerpt, excerpt.range.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EntryOwned::Excerpt(buffer_id, excerpt_id, _) => {
|
|
||||||
if let Some(excerpt) = self
|
|
||||||
.excerpts
|
|
||||||
.get(&buffer_id)
|
|
||||||
.and_then(|excerpts| excerpts.get(&excerpt_id))
|
|
||||||
.filter(|excerpt| excerpt.should_fetch_outlines())
|
|
||||||
{
|
|
||||||
match excerpts_to_fetch.entry(buffer_id) {
|
|
||||||
hash_map::Entry::Occupied(mut o) => {
|
|
||||||
o.get_mut().1.insert(excerpt_id, excerpt.range.clone());
|
|
||||||
}
|
|
||||||
hash_map::Entry::Vacant(v) => {
|
|
||||||
if let Some(buffer_snapshot) = project
|
|
||||||
.read(cx)
|
|
||||||
.buffer_for_id(buffer_id)
|
|
||||||
.map(|buffer| buffer.read(cx).snapshot())
|
|
||||||
{
|
|
||||||
v.insert((buffer_snapshot, HashMap::default()))
|
|
||||||
.1
|
|
||||||
.insert(excerpt_id, excerpt.range.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
excerpts_to_fetch
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if excerpt_fetch_ranges.is_empty() {
|
if excerpt_fetch_ranges.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let syntax_theme = cx.theme().syntax().clone();
|
let syntax_theme = cx.theme().syntax().clone();
|
||||||
self.outline_fetch_tasks
|
for (buffer_id, (buffer_snapshot, excerpt_ranges)) in excerpt_fetch_ranges {
|
||||||
.push(cx.spawn(|outline_panel, mut cx| async move {
|
for (excerpt_id, excerpt_range) in excerpt_ranges {
|
||||||
let mut fetch_tasks = excerpt_fetch_ranges
|
let syntax_theme = syntax_theme.clone();
|
||||||
.into_iter()
|
let buffer_snapshot = buffer_snapshot.clone();
|
||||||
.map(|(buffer_id, (buffer_snapshot, excerpt_ranges))| {
|
self.outline_fetch_tasks.insert(
|
||||||
let syntax_theme = syntax_theme.clone();
|
(buffer_id, excerpt_id),
|
||||||
cx.background_executor().spawn(async move {
|
cx.spawn(|outline_panel, mut cx| async move {
|
||||||
let new_outlines = excerpt_ranges
|
let fetched_outlines = cx
|
||||||
.into_iter()
|
.background_executor()
|
||||||
.map(|(excerpt_id, excerpt_range)| {
|
.spawn(async move {
|
||||||
let outlines = buffer_snapshot
|
buffer_snapshot
|
||||||
.outline_items_containing(
|
.outline_items_containing(
|
||||||
excerpt_range.context,
|
excerpt_range.context,
|
||||||
false,
|
false,
|
||||||
Some(&syntax_theme),
|
Some(&syntax_theme),
|
||||||
)
|
)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default()
|
||||||
(excerpt_id, outlines)
|
})
|
||||||
})
|
.await;
|
||||||
.collect::<HashMap<_, _>>();
|
outline_panel
|
||||||
(buffer_id, new_outlines)
|
.update(&mut cx, |outline_panel, cx| {
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<FuturesUnordered<_>>();
|
|
||||||
|
|
||||||
while let Some((buffer_id, fetched_outlines)) = fetch_tasks.next().await {
|
|
||||||
outline_panel
|
|
||||||
.update(&mut cx, |outline_panel, cx| {
|
|
||||||
for (excerpt_id, fetched_outlines) in fetched_outlines {
|
|
||||||
if let Some(excerpt) = outline_panel
|
if let Some(excerpt) = outline_panel
|
||||||
.excerpts
|
.excerpts
|
||||||
.entry(buffer_id)
|
.entry(buffer_id)
|
||||||
.or_default()
|
.or_default()
|
||||||
.get_mut(&excerpt_id)
|
.get_mut(&excerpt_id)
|
||||||
.filter(|excerpt| excerpt.should_fetch_outlines())
|
|
||||||
{
|
{
|
||||||
excerpt.outlines = ExcerptOutlines::Outlines(fetched_outlines);
|
excerpt.outlines = ExcerptOutlines::Outlines(fetched_outlines);
|
||||||
}
|
}
|
||||||
}
|
outline_panel.cached_entries_with_depth = None;
|
||||||
outline_panel.cached_entries_with_depth = None;
|
cx.notify();
|
||||||
cx.notify();
|
})
|
||||||
})
|
.ok();
|
||||||
.ok();
|
}),
|
||||||
}
|
);
|
||||||
}));
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entries_with_depths(&mut self, cx: &AppContext) -> &[(usize, EntryOwned)] {
|
fn entries_with_depths(&mut self, cx: &AppContext) -> &[(usize, EntryOwned)] {
|
||||||
|
@ -2183,7 +2208,7 @@ impl OutlinePanel {
|
||||||
let depth = self
|
let depth = self
|
||||||
.fs_entries_depth
|
.fs_entries_depth
|
||||||
.get(&(*worktree_id, dir_entry.id))
|
.get(&(*worktree_id, dir_entry.id))
|
||||||
.map(|&(_, depth)| depth)
|
.copied()
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
if auto_fold_dirs {
|
if auto_fold_dirs {
|
||||||
let folded = self
|
let folded = self
|
||||||
|
@ -2225,7 +2250,7 @@ impl OutlinePanel {
|
||||||
FsEntry::File(worktree_id, file_entry, ..) => self
|
FsEntry::File(worktree_id, file_entry, ..) => self
|
||||||
.fs_entries_depth
|
.fs_entries_depth
|
||||||
.get(&(*worktree_id, file_entry.id))
|
.get(&(*worktree_id, file_entry.id))
|
||||||
.map(|&(_, depth)| depth)
|
.copied()
|
||||||
.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() {
|
||||||
|
@ -2322,6 +2347,87 @@ impl OutlinePanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn excerpt_fetch_ranges(
|
||||||
|
&self,
|
||||||
|
entry_range: Range<usize>,
|
||||||
|
cx: &AppContext,
|
||||||
|
) -> HashMap<
|
||||||
|
BufferId,
|
||||||
|
(
|
||||||
|
BufferSnapshot,
|
||||||
|
HashMap<ExcerptId, ExcerptRange<language::Anchor>>,
|
||||||
|
),
|
||||||
|
> {
|
||||||
|
match self.cached_entries_with_depth.as_ref() {
|
||||||
|
Some(entries) => entries.get(entry_range).into_iter().flatten().fold(
|
||||||
|
HashMap::default(),
|
||||||
|
|mut excerpts_to_fetch, (_, entry)| {
|
||||||
|
match entry {
|
||||||
|
EntryOwned::Entry(FsEntry::File(_, _, buffer_id, file_excerpts))
|
||||||
|
| EntryOwned::Entry(FsEntry::ExternalFile(buffer_id, file_excerpts)) => {
|
||||||
|
let excerpts = self.excerpts.get(&buffer_id);
|
||||||
|
for &file_excerpt in file_excerpts {
|
||||||
|
if let Some(excerpt) = excerpts
|
||||||
|
.and_then(|excerpts| excerpts.get(&file_excerpt))
|
||||||
|
.filter(|excerpt| excerpt.should_fetch_outlines())
|
||||||
|
{
|
||||||
|
match excerpts_to_fetch.entry(*buffer_id) {
|
||||||
|
hash_map::Entry::Occupied(mut o) => {
|
||||||
|
o.get_mut()
|
||||||
|
.1
|
||||||
|
.insert(file_excerpt, excerpt.range.clone());
|
||||||
|
}
|
||||||
|
hash_map::Entry::Vacant(v) => {
|
||||||
|
if let Some(buffer_snapshot) = self
|
||||||
|
.project
|
||||||
|
.read(cx)
|
||||||
|
.buffer_for_id(*buffer_id)
|
||||||
|
.map(|buffer| buffer.read(cx).snapshot())
|
||||||
|
{
|
||||||
|
v.insert((buffer_snapshot, HashMap::default()))
|
||||||
|
.1
|
||||||
|
.insert(file_excerpt, excerpt.range.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EntryOwned::Excerpt(buffer_id, excerpt_id, _) => {
|
||||||
|
if let Some(excerpt) = self
|
||||||
|
.excerpts
|
||||||
|
.get(&buffer_id)
|
||||||
|
.and_then(|excerpts| excerpts.get(&excerpt_id))
|
||||||
|
.filter(|excerpt| excerpt.should_fetch_outlines())
|
||||||
|
{
|
||||||
|
match excerpts_to_fetch.entry(*buffer_id) {
|
||||||
|
hash_map::Entry::Occupied(mut o) => {
|
||||||
|
o.get_mut().1.insert(*excerpt_id, excerpt.range.clone());
|
||||||
|
}
|
||||||
|
hash_map::Entry::Vacant(v) => {
|
||||||
|
if let Some(buffer_snapshot) = self
|
||||||
|
.project
|
||||||
|
.read(cx)
|
||||||
|
.buffer_for_id(*buffer_id)
|
||||||
|
.map(|buffer| buffer.read(cx).snapshot())
|
||||||
|
{
|
||||||
|
v.insert((buffer_snapshot, HashMap::default()))
|
||||||
|
.1
|
||||||
|
.insert(*excerpt_id, excerpt.range.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
excerpts_to_fetch
|
||||||
|
},
|
||||||
|
),
|
||||||
|
None => HashMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn back_to_common_visited_parent(
|
fn back_to_common_visited_parent(
|
||||||
|
@ -2429,13 +2535,7 @@ impl Panel for OutlinePanel {
|
||||||
if self.active_item.as_ref().map(|item| item.item_id)
|
if self.active_item.as_ref().map(|item| item.item_id)
|
||||||
== Some(active_editor.item_id())
|
== Some(active_editor.item_id())
|
||||||
{
|
{
|
||||||
let new_selected_entry = self
|
let new_selected_entry = self.location_for_editor_selection(&active_editor, cx);
|
||||||
.location_for_editor_selection(&active_editor, cx)
|
|
||||||
.and_then(|(buffer_id, excerpt_id, outline)| {
|
|
||||||
let (_, entry) =
|
|
||||||
self.entry_for_selection(buffer_id, excerpt_id, outline)?;
|
|
||||||
Some(entry)
|
|
||||||
});
|
|
||||||
self.update_fs_entries(
|
self.update_fs_entries(
|
||||||
&active_editor,
|
&active_editor,
|
||||||
HashSet::default(),
|
HashSet::default(),
|
||||||
|
@ -2520,12 +2620,11 @@ impl Render for OutlinePanel {
|
||||||
move |outline_panel, range, cx| {
|
move |outline_panel, range, cx| {
|
||||||
outline_panel.last_visible_range = range.clone();
|
outline_panel.last_visible_range = range.clone();
|
||||||
outline_panel.fetch_outlines(&range, cx);
|
outline_panel.fetch_outlines(&range, cx);
|
||||||
outline_panel
|
let entries = outline_panel.entries_with_depths(cx).get(range);
|
||||||
.entries_with_depths(cx)
|
entries
|
||||||
.get(range)
|
|
||||||
.map(|entries| entries.to_vec())
|
.map(|entries| entries.to_vec())
|
||||||
|
.unwrap_or_default()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
|
||||||
.filter_map(|(depth, entry)| match entry {
|
.filter_map(|(depth, entry)| match entry {
|
||||||
EntryOwned::Entry(entry) => {
|
EntryOwned::Entry(entry) => {
|
||||||
Some(outline_panel.render_entry(&entry, depth, cx))
|
Some(outline_panel.render_entry(&entry, depth, cx))
|
||||||
|
@ -2649,15 +2748,6 @@ fn subscribe_for_editor_events(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn range_contains(
|
|
||||||
range: &Range<language::Anchor>,
|
|
||||||
anchor: language::Anchor,
|
|
||||||
buffer_snapshot: &language::BufferSnapshot,
|
|
||||||
) -> bool {
|
|
||||||
range.start.cmp(&anchor, buffer_snapshot).is_le()
|
|
||||||
&& range.end.cmp(&anchor, buffer_snapshot).is_ge()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn empty_icon() -> AnyElement {
|
fn empty_icon() -> AnyElement {
|
||||||
h_flex()
|
h_flex()
|
||||||
.size(IconSize::default().rems())
|
.size(IconSize::default().rems())
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue