mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-24 06:12:25 +03:00
Slightly improve project panel ergonomics (#13704)
* properly fetch outlines from channel notes and other project-less external files * show better messages when for no contents * make file entries collapsible (hiding all excerpts and outlines beneath), keep the initial panel state unfolded up to file level Release Notes: - Slightly improved project panel ergonomics
This commit is contained in:
parent
25ad3185e0
commit
0e60730742
@ -40,7 +40,7 @@ use workspace::{
|
||||
item::ItemHandle,
|
||||
ui::{
|
||||
h_flex, v_flex, ActiveTheme, Color, ContextMenu, FluentBuilder, Icon, IconName, IconSize,
|
||||
Label, LabelCommon, ListItem, Selectable, StyledTypography,
|
||||
Label, LabelCommon, ListItem, Selectable, Spacing, StyledTypography,
|
||||
},
|
||||
OpenInTerminal, Workspace,
|
||||
};
|
||||
@ -85,6 +85,7 @@ pub struct OutlinePanel {
|
||||
selected_entry: Option<EntryOwned>,
|
||||
active_item: Option<ActiveItem>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
loading_outlines: bool,
|
||||
update_task: Task<()>,
|
||||
outline_fetch_tasks: HashMap<(BufferId, ExcerptId), Task<()>>,
|
||||
excerpts: HashMap<BufferId, HashMap<ExcerptId, Excerpt>>,
|
||||
@ -94,6 +95,8 @@ pub struct OutlinePanel {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
enum CollapsedEntry {
|
||||
Dir(WorktreeId, ProjectEntryId),
|
||||
File(WorktreeId, BufferId),
|
||||
ExternalFile(BufferId),
|
||||
Excerpt(BufferId, ExcerptId),
|
||||
}
|
||||
|
||||
@ -155,19 +158,6 @@ impl EntryOwned {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn abs_path(&self, project: &Model<Project>, cx: &AppContext) -> Option<PathBuf> {
|
||||
match self {
|
||||
Self::Entry(entry) => entry.abs_path(project, cx),
|
||||
Self::FoldedDirs(worktree_id, dirs) => dirs.last().and_then(|entry| {
|
||||
project
|
||||
.read(cx)
|
||||
.worktree_for_id(*worktree_id, cx)
|
||||
.and_then(|worktree| worktree.read(cx).absolutize(&entry.path).ok())
|
||||
}),
|
||||
Self::Excerpt(..) | Self::Outline(..) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@ -218,46 +208,6 @@ impl PartialEq for FsEntry {
|
||||
}
|
||||
}
|
||||
|
||||
impl FsEntry {
|
||||
fn abs_path(&self, project: &Model<Project>, cx: &AppContext) -> Option<PathBuf> {
|
||||
match self {
|
||||
Self::ExternalFile(buffer_id, _) => project
|
||||
.read(cx)
|
||||
.buffer_for_id(*buffer_id)
|
||||
.and_then(|buffer| File::from_dyn(buffer.read(cx).file()))
|
||||
.and_then(|file| file.worktree.read(cx).absolutize(&file.path).ok()),
|
||||
Self::Directory(worktree_id, entry) => project
|
||||
.read(cx)
|
||||
.worktree_for_id(*worktree_id, cx)?
|
||||
.read(cx)
|
||||
.absolutize(&entry.path)
|
||||
.ok(),
|
||||
Self::File(worktree_id, entry, _, _) => project
|
||||
.read(cx)
|
||||
.worktree_for_id(*worktree_id, cx)?
|
||||
.read(cx)
|
||||
.absolutize(&entry.path)
|
||||
.ok(),
|
||||
}
|
||||
}
|
||||
|
||||
fn relative_path<'a>(
|
||||
&'a self,
|
||||
project: &Model<Project>,
|
||||
cx: &'a AppContext,
|
||||
) -> Option<&'a Path> {
|
||||
match self {
|
||||
Self::ExternalFile(buffer_id, _) => project
|
||||
.read(cx)
|
||||
.buffer_for_id(*buffer_id)
|
||||
.and_then(|buffer| buffer.read(cx).file())
|
||||
.map(|file| file.path().as_ref()),
|
||||
Self::Directory(_, entry) => Some(entry.path.as_ref()),
|
||||
Self::File(_, entry, ..) => Some(entry.path.as_ref()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ActiveItem {
|
||||
item_id: EntityId,
|
||||
active_editor: WeakView<Editor>,
|
||||
@ -383,6 +333,7 @@ impl OutlinePanel {
|
||||
width: None,
|
||||
active_item: None,
|
||||
pending_serialization: Task::ready(None),
|
||||
loading_outlines: false,
|
||||
update_task: Task::ready(()),
|
||||
outline_fetch_tasks: HashMap::default(),
|
||||
excerpts: HashMap::default(),
|
||||
@ -529,7 +480,9 @@ impl OutlinePanel {
|
||||
Point::new(0.0, -(active_editor.read(cx).file_header_size() as f32))
|
||||
};
|
||||
|
||||
self.toggle_expanded(entry, cx);
|
||||
match entry {
|
||||
EntryOwned::FoldedDirs(..) | EntryOwned::Entry(FsEntry::Directory(..)) => {}
|
||||
EntryOwned::Entry(FsEntry::ExternalFile(buffer_id, _)) => {
|
||||
let scroll_target = multi_buffer_snapshot.excerpts().find_map(
|
||||
|(excerpt_id, buffer_snapshot, excerpt_range)| {
|
||||
@ -554,12 +507,6 @@ impl OutlinePanel {
|
||||
})
|
||||
}
|
||||
}
|
||||
entry @ EntryOwned::Entry(FsEntry::Directory(..)) => {
|
||||
self.toggle_expanded(entry, cx);
|
||||
}
|
||||
entry @ EntryOwned::FoldedDirs(..) => {
|
||||
self.toggle_expanded(entry, cx);
|
||||
}
|
||||
EntryOwned::Entry(FsEntry::File(_, file_entry, ..)) => {
|
||||
let scroll_target = self
|
||||
.project
|
||||
@ -610,8 +557,7 @@ impl OutlinePanel {
|
||||
})
|
||||
}
|
||||
}
|
||||
excerpt_entry @ EntryOwned::Excerpt(_, excerpt_id, excerpt_range) => {
|
||||
self.toggle_expanded(excerpt_entry, cx);
|
||||
EntryOwned::Excerpt(_, excerpt_id, excerpt_range) => {
|
||||
let scroll_target = multi_buffer_snapshot
|
||||
.anchor_in_excerpt(*excerpt_id, excerpt_range.context.start);
|
||||
if let Some(anchor) = scroll_target {
|
||||
@ -923,10 +869,16 @@ impl OutlinePanel {
|
||||
Some(EntryOwned::Entry(FsEntry::Directory(worktree_id, dir_entry))) => {
|
||||
Some(CollapsedEntry::Dir(*worktree_id, dir_entry.id))
|
||||
}
|
||||
Some(EntryOwned::Entry(FsEntry::File(worktree_id, _, buffer_id, _))) => {
|
||||
Some(CollapsedEntry::File(*worktree_id, *buffer_id))
|
||||
}
|
||||
Some(EntryOwned::Entry(FsEntry::ExternalFile(buffer_id, _))) => {
|
||||
Some(CollapsedEntry::ExternalFile(*buffer_id))
|
||||
}
|
||||
Some(EntryOwned::Excerpt(buffer_id, excerpt_id, _)) => {
|
||||
Some(CollapsedEntry::Excerpt(*buffer_id, *excerpt_id))
|
||||
}
|
||||
_ => None,
|
||||
None | Some(EntryOwned::Outline(..)) => None,
|
||||
};
|
||||
let Some(collapsed_entry) = entry_to_expand else {
|
||||
return;
|
||||
@ -967,6 +919,30 @@ impl OutlinePanel {
|
||||
cx,
|
||||
);
|
||||
}
|
||||
Some(file_entry @ EntryOwned::Entry(FsEntry::File(worktree_id, _, buffer_id, _))) => {
|
||||
self.collapsed_entries
|
||||
.insert(CollapsedEntry::File(*worktree_id, *buffer_id));
|
||||
self.update_fs_entries(
|
||||
&editor,
|
||||
HashSet::default(),
|
||||
Some(file_entry.clone()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
Some(file_entry @ EntryOwned::Entry(FsEntry::ExternalFile(buffer_id, _))) => {
|
||||
self.collapsed_entries
|
||||
.insert(CollapsedEntry::ExternalFile(*buffer_id));
|
||||
self.update_fs_entries(
|
||||
&editor,
|
||||
HashSet::default(),
|
||||
Some(file_entry.clone()),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
Some(dirs_entry @ EntryOwned::FoldedDirs(worktree_id, dir_entries)) => {
|
||||
if let Some(dir_entry) = dir_entries.last() {
|
||||
if self
|
||||
@ -999,7 +975,7 @@ impl OutlinePanel {
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
None | Some(EntryOwned::Outline(..)) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1019,13 +995,19 @@ impl OutlinePanel {
|
||||
EntryOwned::Entry(FsEntry::Directory(worktree_id, entry)) => {
|
||||
Some(CollapsedEntry::Dir(*worktree_id, entry.id))
|
||||
}
|
||||
EntryOwned::Entry(FsEntry::File(worktree_id, _, buffer_id, _)) => {
|
||||
Some(CollapsedEntry::File(*worktree_id, *buffer_id))
|
||||
}
|
||||
EntryOwned::Entry(FsEntry::ExternalFile(buffer_id, _)) => {
|
||||
Some(CollapsedEntry::ExternalFile(*buffer_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,
|
||||
EntryOwned::Outline(..) => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
self.collapsed_entries.extend(new_entries);
|
||||
@ -1056,6 +1038,18 @@ impl OutlinePanel {
|
||||
self.collapsed_entries.insert(collapsed_entry);
|
||||
}
|
||||
}
|
||||
EntryOwned::Entry(FsEntry::File(worktree_id, _, buffer_id, _)) => {
|
||||
let collapsed_entry = CollapsedEntry::File(*worktree_id, *buffer_id);
|
||||
if !self.collapsed_entries.remove(&collapsed_entry) {
|
||||
self.collapsed_entries.insert(collapsed_entry);
|
||||
}
|
||||
}
|
||||
EntryOwned::Entry(FsEntry::ExternalFile(buffer_id, _)) => {
|
||||
let collapsed_entry = CollapsedEntry::ExternalFile(*buffer_id);
|
||||
if !self.collapsed_entries.remove(&collapsed_entry) {
|
||||
self.collapsed_entries.insert(collapsed_entry);
|
||||
}
|
||||
}
|
||||
EntryOwned::FoldedDirs(worktree_id, dir_entries) => {
|
||||
if let Some(entry_id) = dir_entries.first().map(|entry| entry.id) {
|
||||
let collapsed_entry = CollapsedEntry::Dir(*worktree_id, entry_id);
|
||||
@ -1077,7 +1071,7 @@ impl OutlinePanel {
|
||||
self.collapsed_entries.insert(collapsed_entry);
|
||||
}
|
||||
}
|
||||
_ => return,
|
||||
EntryOwned::Outline(..) => return,
|
||||
}
|
||||
|
||||
self.update_fs_entries(
|
||||
@ -1094,7 +1088,7 @@ impl OutlinePanel {
|
||||
if let Some(clipboard_text) = self
|
||||
.selected_entry
|
||||
.as_ref()
|
||||
.and_then(|entry| entry.abs_path(&self.project, cx))
|
||||
.and_then(|entry| self.abs_path(&entry, cx))
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
{
|
||||
cx.write_to_clipboard(ClipboardItem::new(clipboard_text));
|
||||
@ -1106,8 +1100,10 @@ impl OutlinePanel {
|
||||
.selected_entry
|
||||
.as_ref()
|
||||
.and_then(|entry| match entry {
|
||||
EntryOwned::Entry(entry) => entry.relative_path(&self.project, cx),
|
||||
EntryOwned::FoldedDirs(_, dirs) => dirs.last().map(|entry| entry.path.as_ref()),
|
||||
EntryOwned::Entry(entry) => self.relative_path(&entry, cx),
|
||||
EntryOwned::FoldedDirs(_, dirs) => {
|
||||
dirs.last().map(|entry| entry.path.to_path_buf())
|
||||
}
|
||||
EntryOwned::Excerpt(..) | EntryOwned::Outline(..) => None,
|
||||
})
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
@ -1120,7 +1116,7 @@ impl OutlinePanel {
|
||||
if let Some(abs_path) = self
|
||||
.selected_entry
|
||||
.as_ref()
|
||||
.and_then(|entry| entry.abs_path(&self.project, cx))
|
||||
.and_then(|entry| self.abs_path(&entry, cx))
|
||||
{
|
||||
cx.reveal_path(&abs_path);
|
||||
}
|
||||
@ -1128,7 +1124,7 @@ impl OutlinePanel {
|
||||
|
||||
fn open_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
|
||||
let selected_entry = self.selected_entry.as_ref();
|
||||
let abs_path = selected_entry.and_then(|entry| entry.abs_path(&self.project, cx));
|
||||
let abs_path = selected_entry.and_then(|entry| self.abs_path(&entry, cx));
|
||||
let working_directory = if let (
|
||||
Some(abs_path),
|
||||
Some(EntryOwned::Entry(FsEntry::File(..) | FsEntry::ExternalFile(..))),
|
||||
@ -1173,17 +1169,26 @@ impl OutlinePanel {
|
||||
}
|
||||
EntryOwned::Outline(buffer_id, excerpt_id, _)
|
||||
| EntryOwned::Excerpt(buffer_id, excerpt_id, _) => {
|
||||
self.collapsed_entries
|
||||
.remove(&CollapsedEntry::ExternalFile(buffer_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)?;
|
||||
project
|
||||
.worktree_for_entry(entry_id, cx)
|
||||
.and_then(|worktree| {
|
||||
let worktree_id = worktree.read(cx).id();
|
||||
self.collapsed_entries
|
||||
.remove(&CollapsedEntry::File(worktree_id, buffer_id));
|
||||
let entry = worktree.read(cx).entry_for_id(entry_id)?.clone();
|
||||
Some((worktree, entry))
|
||||
})
|
||||
})
|
||||
}
|
||||
EntryOwned::Entry(FsEntry::ExternalFile(..)) => None,
|
||||
_ => return,
|
||||
@ -1274,12 +1279,7 @@ impl OutlinePanel {
|
||||
}
|
||||
.unwrap_or_else(empty_icon);
|
||||
|
||||
let buffer_snapshot = self
|
||||
.project
|
||||
.read(cx)
|
||||
.buffer_for_id(buffer_id)?
|
||||
.read(cx)
|
||||
.snapshot();
|
||||
let buffer_snapshot = self.buffer_snapshot_for_id(buffer_id, cx)?;
|
||||
let excerpt_range = range.context.to_point(&buffer_snapshot);
|
||||
let label_element = Label::new(format!(
|
||||
"Lines {}-{}",
|
||||
@ -1397,8 +1397,8 @@ impl OutlinePanel {
|
||||
}
|
||||
FsEntry::ExternalFile(buffer_id, ..) => {
|
||||
let color = entry_label_color(is_active);
|
||||
let (icon, name) = match self.project.read(cx).buffer_for_id(*buffer_id) {
|
||||
Some(buffer) => match buffer.read(cx).file() {
|
||||
let (icon, name) = match self.buffer_snapshot_for_id(*buffer_id, cx) {
|
||||
Some(buffer_snapshot) => match buffer_snapshot.file() {
|
||||
Some(file) => {
|
||||
let path = file.path();
|
||||
let icon = if settings.file_icons {
|
||||
@ -1621,10 +1621,12 @@ impl OutlinePanel {
|
||||
let file = File::from_dyn(buffer_snapshot.file());
|
||||
let entry_id = file.and_then(|file| file.project_entry_id(cx));
|
||||
let worktree = file.map(|file| file.worktree.read(cx).snapshot());
|
||||
let is_new =
|
||||
new_entries.contains(&excerpt_id) || !self.excerpts.contains_key(&buffer_id);
|
||||
buffer_excerpts
|
||||
.entry(buffer_id)
|
||||
.or_insert_with(|| (Vec::new(), entry_id, worktree))
|
||||
.0
|
||||
.or_insert_with(|| (is_new, Vec::new(), entry_id, worktree))
|
||||
.1
|
||||
.push(excerpt_id);
|
||||
|
||||
let outlines = match self
|
||||
@ -1656,6 +1658,7 @@ impl OutlinePanel {
|
||||
},
|
||||
);
|
||||
|
||||
self.loading_outlines = true;
|
||||
self.update_task = cx.spawn(|outline_panel, mut cx| async move {
|
||||
if let Some(debounce) = debounce {
|
||||
cx.background_executor().timer(debounce).await;
|
||||
@ -1672,7 +1675,25 @@ impl OutlinePanel {
|
||||
>::default();
|
||||
let mut external_excerpts = HashMap::default();
|
||||
|
||||
for (buffer_id, (excerpts, entry_id, worktree)) in buffer_excerpts {
|
||||
for (buffer_id, (is_new, excerpts, entry_id, worktree)) in buffer_excerpts {
|
||||
if is_new {
|
||||
match &worktree {
|
||||
Some(worktree) => {
|
||||
new_collapsed_entries
|
||||
.insert(CollapsedEntry::File(worktree.id(), buffer_id));
|
||||
}
|
||||
None => {
|
||||
new_collapsed_entries
|
||||
.insert(CollapsedEntry::ExternalFile(buffer_id));
|
||||
}
|
||||
}
|
||||
|
||||
for excerpt_id in &excerpts {
|
||||
new_collapsed_entries
|
||||
.insert(CollapsedEntry::Excerpt(buffer_id, *excerpt_id));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(worktree) = worktree {
|
||||
let worktree_id = worktree.id();
|
||||
let unfolded_dirs =
|
||||
@ -1688,9 +1709,6 @@ impl OutlinePanel {
|
||||
);
|
||||
|
||||
let mut entries_to_add = HashSet::default();
|
||||
let is_new = excerpts
|
||||
.iter()
|
||||
.any(|excerpt_id| new_entries.contains(excerpt_id));
|
||||
worktree_excerpts
|
||||
.entry(worktree_id)
|
||||
.or_default()
|
||||
@ -1945,6 +1963,7 @@ impl OutlinePanel {
|
||||
|
||||
outline_panel
|
||||
.update(&mut cx, |outline_panel, cx| {
|
||||
outline_panel.loading_outlines = false;
|
||||
outline_panel.excerpts = new_excerpts;
|
||||
outline_panel.collapsed_entries = new_collapsed_entries;
|
||||
outline_panel.unfolded_dirs = new_unfolded_dirs;
|
||||
@ -2266,10 +2285,34 @@ impl OutlinePanel {
|
||||
}
|
||||
|
||||
entries.push((depth, EntryOwned::Entry(entry.clone())));
|
||||
if let FsEntry::File(_, _, buffer_id, entry_excerpts)
|
||||
| FsEntry::ExternalFile(buffer_id, entry_excerpts) = entry
|
||||
|
||||
let excerpts_to_consider = match entry {
|
||||
FsEntry::File(worktree_id, _, buffer_id, entry_excerpts) => {
|
||||
if is_singleton
|
||||
|| !self
|
||||
.collapsed_entries
|
||||
.contains(&CollapsedEntry::File(*worktree_id, *buffer_id))
|
||||
{
|
||||
if let Some(excerpts) = self.excerpts.get(buffer_id) {
|
||||
Some((*buffer_id, entry_excerpts))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
FsEntry::ExternalFile(buffer_id, entry_excerpts) => {
|
||||
if is_singleton
|
||||
|| !self
|
||||
.collapsed_entries
|
||||
.contains(&CollapsedEntry::ExternalFile(*buffer_id))
|
||||
{
|
||||
Some((*buffer_id, entry_excerpts))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some((buffer_id, entry_excerpts)) = excerpts_to_consider {
|
||||
if let Some(excerpts) = self.excerpts.get(&buffer_id) {
|
||||
for &entry_excerpt in entry_excerpts {
|
||||
let Some(excerpt) = excerpts.get(&entry_excerpt) else {
|
||||
continue;
|
||||
@ -2278,7 +2321,7 @@ impl OutlinePanel {
|
||||
entries.push((
|
||||
excerpt_depth,
|
||||
EntryOwned::Excerpt(
|
||||
*buffer_id,
|
||||
buffer_id,
|
||||
entry_excerpt,
|
||||
excerpt.range.clone(),
|
||||
),
|
||||
@ -2290,7 +2333,7 @@ impl OutlinePanel {
|
||||
entries.clear();
|
||||
} else if self
|
||||
.collapsed_entries
|
||||
.contains(&CollapsedEntry::Excerpt(*buffer_id, entry_excerpt))
|
||||
.contains(&CollapsedEntry::Excerpt(buffer_id, entry_excerpt))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -2298,7 +2341,7 @@ impl OutlinePanel {
|
||||
for outline in excerpt.iter_outlines() {
|
||||
entries.push((
|
||||
outline_base_depth + outline.depth,
|
||||
EntryOwned::Outline(*buffer_id, entry_excerpt, outline.clone()),
|
||||
EntryOwned::Outline(buffer_id, entry_excerpt, outline.clone()),
|
||||
));
|
||||
}
|
||||
if is_singleton && entries.is_empty() {
|
||||
@ -2306,7 +2349,7 @@ impl OutlinePanel {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
if let Some((folded_depth, worktree_id, folded_dirs)) = folded_dirs_entry.take() {
|
||||
entries.push((
|
||||
@ -2384,11 +2427,8 @@ impl OutlinePanel {
|
||||
.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())
|
||||
if let Some(buffer_snapshot) =
|
||||
self.buffer_snapshot_for_id(*buffer_id, cx)
|
||||
{
|
||||
v.insert((buffer_snapshot, HashMap::default()))
|
||||
.1
|
||||
@ -2411,11 +2451,8 @@ impl OutlinePanel {
|
||||
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())
|
||||
if let Some(buffer_snapshot) =
|
||||
self.buffer_snapshot_for_id(*buffer_id, cx)
|
||||
{
|
||||
v.insert((buffer_snapshot, HashMap::default()))
|
||||
.1
|
||||
@ -2433,6 +2470,60 @@ impl OutlinePanel {
|
||||
None => HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn buffer_snapshot_for_id(
|
||||
&self,
|
||||
buffer_id: BufferId,
|
||||
cx: &AppContext,
|
||||
) -> Option<BufferSnapshot> {
|
||||
let editor = self.active_item.as_ref()?.active_editor.upgrade()?;
|
||||
Some(
|
||||
editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.buffer(buffer_id)?
|
||||
.read(cx)
|
||||
.snapshot(),
|
||||
)
|
||||
}
|
||||
|
||||
fn abs_path(&self, entry: &EntryOwned, cx: &AppContext) -> Option<PathBuf> {
|
||||
match entry {
|
||||
EntryOwned::Entry(
|
||||
FsEntry::File(_, _, buffer_id, _) | FsEntry::ExternalFile(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()
|
||||
}),
|
||||
EntryOwned::Entry(FsEntry::Directory(worktree_id, entry)) => self
|
||||
.project
|
||||
.read(cx)
|
||||
.worktree_for_id(*worktree_id, cx)?
|
||||
.read(cx)
|
||||
.absolutize(&entry.path)
|
||||
.ok(),
|
||||
EntryOwned::FoldedDirs(worktree_id, dirs) => dirs.last().and_then(|entry| {
|
||||
self.project
|
||||
.read(cx)
|
||||
.worktree_for_id(*worktree_id, cx)
|
||||
.and_then(|worktree| worktree.read(cx).absolutize(&entry.path).ok())
|
||||
}),
|
||||
EntryOwned::Excerpt(..) | EntryOwned::Outline(..) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn relative_path(&self, entry: &FsEntry, cx: &AppContext) -> Option<PathBuf> {
|
||||
match entry {
|
||||
FsEntry::ExternalFile(buffer_id, _) => self
|
||||
.buffer_snapshot_for_id(*buffer_id, cx)
|
||||
.and_then(|buffer_snapshot| Some(buffer_snapshot.file()?.path().to_path_buf())),
|
||||
FsEntry::Directory(_, entry) => Some(entry.path.to_path_buf()),
|
||||
FsEntry::File(_, entry, ..) => Some(entry.path.to_path_buf()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn back_to_common_visited_parent(
|
||||
@ -2572,12 +2663,37 @@ impl Render for OutlinePanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let project = self.project.read(cx);
|
||||
if self.fs_entries.is_empty() {
|
||||
let header = if self.loading_outlines {
|
||||
"Loading outlines"
|
||||
} else {
|
||||
"No outlines available"
|
||||
};
|
||||
v_flex()
|
||||
.id("empty-outline_panel")
|
||||
.justify_center()
|
||||
.size_full()
|
||||
.p_4()
|
||||
.track_focus(&self.focus_handle)
|
||||
.child(Label::new("No editor outlines available"))
|
||||
.child(h_flex().justify_center().child(Label::new(header)))
|
||||
.child(
|
||||
h_flex()
|
||||
.pt(Spacing::Small.rems(cx))
|
||||
.justify_center()
|
||||
.child({
|
||||
let keystroke = match self.position(cx) {
|
||||
DockPosition::Left => {
|
||||
cx.keystroke_text_for(&workspace::ToggleLeftDock)
|
||||
}
|
||||
DockPosition::Bottom => {
|
||||
cx.keystroke_text_for(&workspace::ToggleBottomDock)
|
||||
}
|
||||
DockPosition::Right => {
|
||||
cx.keystroke_text_for(&workspace::ToggleRightDock)
|
||||
}
|
||||
};
|
||||
Label::new(format!("Toggle this panel with {keystroke}",))
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
h_flex()
|
||||
.id("outline-panel")
|
||||
|
Loading…
Reference in New Issue
Block a user