Introduce an outline panel (#12637)

Adds a new panel: `OutlinePanel` which looks very close to project
panel:

<img width="256" alt="Screenshot 2024-06-10 at 23 19 05"
src="https://github.com/zed-industries/zed/assets/2690773/c66e6e78-44ec-4de8-8d60-43238bb09ae9">

has similar settings and keymap (actions work in the `OutlinePanel`
context and are under `outline_panel::` namespace), with two notable
differences:
* no "edit" actions such as cut/copy/paste/delete/etc.
* directory auto folding is enabled by default

Empty view: 
<img width="841" alt="Screenshot 2024-06-10 at 23 19 11"
src="https://github.com/zed-industries/zed/assets/2690773/dc8bf37c-5a70-4fd5-9b57-76271eb7a40c">


When editor gets active, the panel displays all related files in a tree
(similar to what the project panel does) and all related excerpts'
outlines under each file.
Same as in the project panel, directories can be expanded or collapsed,
unfolded or folded; clicking file entries or outlines scrolls the buffer
to the corresponding excerpt; changing editor's selection reveals the
corresponding outline in the panel.

The panel is applicable to any singleton buffer:
<img width="1215" alt="Screenshot 2024-06-10 at 23 19 35"
src="https://github.com/zed-industries/zed/assets/2690773/a087631f-5c2d-4d4d-ae25-30ab9731d528">

<img width="1728" alt="image"
src="https://github.com/zed-industries/zed/assets/2690773/e4f8082c-d12d-4473-8500-e8fd1051285b">

or any multi buffer:

(search multi buffer)

<img width="1728" alt="Screenshot 2024-06-10 at 23 19 41"
src="https://github.com/zed-industries/zed/assets/2690773/60f768a3-6716-4520-9b13-42da8fd15f50">

(diagnostics multi buffer)
<img width="1728" alt="image"
src="https://github.com/zed-industries/zed/assets/2690773/64e285bd-9530-4bf2-8f1f-10ee5596067c">

Release Notes:
- Added an outline panel to show a "map" of the active editor
This commit is contained in:
Kirill Bulatov 2024-06-12 23:22:52 +03:00 committed by GitHub
parent 7f56f4e78e
commit 8451dba6a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 2860 additions and 57 deletions

27
Cargo.lock generated
View File

@ -7132,7 +7132,6 @@ dependencies = [
"project",
"rope",
"serde_json",
"settings",
"smol",
"theme",
"tree-sitter-rust",
@ -7142,6 +7141,31 @@ dependencies = [
"workspace",
]
[[package]]
name = "outline_panel"
version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"db",
"editor",
"file_icons",
"git",
"gpui",
"language",
"log",
"menu",
"project",
"schemars",
"serde",
"serde_json",
"settings",
"unicase",
"util",
"workspace",
"worktree",
]
[[package]]
name = "outref"
version = "0.5.1"
@ -13255,6 +13279,7 @@ dependencies = [
"node_runtime",
"notifications",
"outline",
"outline_panel",
"parking_lot",
"profiling",
"project",

View File

@ -64,6 +64,7 @@ members = [
"crates/ollama",
"crates/open_ai",
"crates/outline",
"crates/outline_panel",
"crates/picker",
"crates/prettier",
"crates/project",
@ -212,6 +213,7 @@ notifications = { path = "crates/notifications" }
ollama = { path = "crates/ollama" }
open_ai = { path = "crates/open_ai" }
outline = { path = "crates/outline" }
outline_panel = { path = "crates/outline_panel" }
picker = { path = "crates/picker" }
plugin = { path = "crates/plugin" }
plugin_macros = { path = "crates/plugin_macros" }

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list-tree"><path d="M21 12h-8"/><path d="M21 6H8"/><path d="M21 18h-8"/><path d="M3 6v4c0 1.1.9 2 2 2h3"/><path d="M3 10v6c0 1.1.9 2 2 2h3"/></svg>

After

Width:  |  Height:  |  Size: 349 B

View File

@ -439,6 +439,7 @@
"ctrl-shift-p": "command_palette::Toggle",
"ctrl-shift-m": "diagnostics::Deploy",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-b": "outline_panel::ToggleFocus",
"ctrl-?": "assistant::ToggleFocus",
"ctrl-alt-s": "workspace::SaveAll",
"ctrl-k m": "language_selector::Toggle",
@ -562,6 +563,18 @@
"ctrl-enter": "project_search::SearchInNew"
}
},
{
"context": "OutlinePanel",
"bindings": {
"left": "project_panel::CollapseSelectedEntry",
"right": "project_panel::ExpandSelectedEntry",
"ctrl-alt-c": "project_panel::CopyPath",
"alt-ctrl-shift-c": "project_panel::CopyRelativePath",
"alt-ctrl-r": "project_panel::RevealInFinder",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev"
}
},
{
"context": "ProjectPanel",
"bindings": {
@ -583,7 +596,10 @@
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-ctrl-r": "project_panel::RevealInFinder",
"alt-shift-f": "project_panel::NewSearchInDirectory"
"alt-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev",
"escape": "menu::Cancel"
}
},
{

View File

@ -475,6 +475,7 @@
"cmd-shift-p": "command_palette::Toggle",
"cmd-shift-m": "diagnostics::Deploy",
"cmd-shift-e": "project_panel::ToggleFocus",
"cmd-shift-b": "outline_panel::ToggleFocus",
"cmd-?": "assistant::ToggleFocus",
"cmd-alt-s": "workspace::SaveAll",
"cmd-k m": "language_selector::Toggle",
@ -584,6 +585,18 @@
"cmd-enter": "project_search::SearchInNew"
}
},
{
"context": "OutlinePanel",
"bindings": {
"left": "outline_panel::CollapseSelectedEntry",
"right": "outline_panel::ExpandSelectedEntry",
"cmd-alt-c": "outline_panel::CopyPath",
"alt-cmd-shift-c": "outline_panel::CopyRelativePath",
"alt-cmd-r": "outline_panel::RevealInFinder",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev"
}
},
{
"context": "ProjectPanel",
"bindings": {

View File

@ -302,6 +302,29 @@
/// when a directory has only one directory inside.
"auto_fold_dirs": false
},
"outline_panel": {
// Whether to show the outline panel button in the status bar
"button": true,
// Default width of the outline panel.
"default_width": 240,
// Where to dock the outline panel. Can be 'left' or 'right'.
"dock": "left",
// Whether to show file icons in the outline panel.
"file_icons": true,
// Whether to show folder icons or chevrons for directories in the outline panel.
"folder_icons": true,
// Whether to show the git status in the outline panel.
"git_status": true,
// Amount of indentation for nested items.
"indent_size": 20,
// Whether to reveal it in the outline panel automatically,
// when a corresponding outline entry becomes active.
// Gitignored entries are never auto revealed.
"auto_reveal_entries": true,
/// Whether to fold directories automatically
/// when a directory has only one directory inside.
"auto_fold_dirs": true
},
"collaboration_panel": {
// Whether to show the collaboration panel button in the status bar.
"button": true,

View File

@ -149,6 +149,9 @@ use workspace::{OpenInTerminal, OpenTerminal, TabBarSettings, Toast};
use crate::hover_links::find_url;
pub const FILE_HEADER_HEIGHT: u8 = 1;
pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u8 = 1;
pub const MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT: u8 = 1;
pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
const MAX_LINE_LEN: usize = 1024;
@ -529,6 +532,7 @@ pub struct Editor {
tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
tasks_update_task: Option<Task<()>>,
previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
file_header_size: u8,
}
#[derive(Clone)]
@ -1651,9 +1655,8 @@ impl Editor {
}),
merge_adjacent: true,
};
let file_header_size = if show_excerpt_controls { 3 } else { 2 };
let display_map = cx.new_model(|cx| {
let file_header_size = if show_excerpt_controls { 3 } else { 2 };
DisplayMap::new(
buffer.clone(),
style.font(),
@ -1661,8 +1664,8 @@ impl Editor {
None,
show_excerpt_controls,
file_header_size,
1,
1,
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT,
fold_placeholder,
cx,
)
@ -1812,6 +1815,7 @@ impl Editor {
git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
blame: None,
blame_subscription: None,
file_header_size,
tasks: Default::default(),
_subscriptions: vec![
cx.observe(&buffer, Self::on_buffer_changed),
@ -10829,6 +10833,12 @@ impl Editor {
self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
}
multi_buffer::Event::ExcerptsEdited { ids } => {
cx.emit(EditorEvent::ExcerptsEdited { ids: ids.clone() })
}
multi_buffer::Event::ExcerptsExpanded { ids } => {
cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
}
multi_buffer::Event::Reparsed => {
self.tasks_update_task = Some(self.refresh_runnables(cx));
@ -11299,6 +11309,10 @@ impl Editor {
}));
self
}
pub fn file_header_size(&self) -> u8 {
self.file_header_size
}
}
fn hunks_for_selections(
@ -11743,6 +11757,12 @@ pub enum EditorEvent {
ExcerptsRemoved {
ids: Vec<ExcerptId>,
},
ExcerptsEdited {
ids: Vec<ExcerptId>,
},
ExcerptsExpanded {
ids: Vec<ExcerptId>,
},
BufferEdited,
Edited,
Reparsed,

View File

@ -1,6 +1,6 @@
use std::iter::FromIterator;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct CharBag(u64);
impl CharBag {

View File

@ -316,7 +316,7 @@ fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum GitFileStatus {
Added,
Modified,

View File

@ -1,6 +1,9 @@
use anyhow::{bail, Context};
use serde::de::{self, Deserialize, Deserializer, Visitor};
use std::fmt;
use std::{
fmt,
hash::{Hash, Hasher},
};
/// Convert an RGB hex color code number to a color type
pub fn rgb(hex: u32) -> Rgba {
@ -267,6 +270,15 @@ impl Ord for Hsla {
impl Eq for Hsla {}
impl Hash for Hsla {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u32(u32::from_be_bytes(self.h.to_be_bytes()));
state.write_u32(u32::from_be_bytes(self.s.to_be_bytes()));
state.write_u32(u32::from_be_bytes(self.l.to_be_bytes()));
state.write_u32(u32::from_be_bytes(self.a.to_be_bytes()));
}
}
/// Construct an [`Hsla`] object from plain values
pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
Hsla {

View File

@ -1,4 +1,8 @@
use std::{iter, mem, ops::Range};
use std::{
hash::{Hash, Hasher},
iter, mem,
ops::Range,
};
use crate::{
black, phi, point, quad, rems, AbsoluteLength, Bounds, ContentMask, Corners, CornersRefinement,
@ -319,6 +323,20 @@ pub struct HighlightStyle {
impl Eq for HighlightStyle {}
impl Hash for HighlightStyle {
fn hash<H: Hasher>(&self, state: &mut H) {
self.color.hash(state);
self.font_weight.hash(state);
self.font_style.hash(state);
self.background_color.hash(state);
self.underline.hash(state);
self.strikethrough.hash(state);
state.write_u32(u32::from_be_bytes(
self.fade_out.map(|f| f.to_be_bytes()).unwrap_or_default(),
));
}
}
impl Style {
/// Returns true if the style is visible and the background is opaque.
pub fn has_opaque_background(&self) -> bool {
@ -549,7 +567,7 @@ impl Default for Style {
}
/// The properties that can be applied to an underline.
#[derive(Refineable, Copy, Clone, Default, Debug, PartialEq, Eq)]
#[derive(Refineable, Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
#[refineable(Debug)]
pub struct UnderlineStyle {
/// The thickness of the underline.
@ -563,7 +581,7 @@ pub struct UnderlineStyle {
}
/// The properties that can be applied to a strikethrough.
#[derive(Refineable, Copy, Clone, Default, Debug, PartialEq, Eq)]
#[derive(Refineable, Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
#[refineable(Debug)]
pub struct StrikethroughStyle {
/// The thickness of the strikethrough.

View File

@ -2738,12 +2738,13 @@ impl BufferSnapshot {
Some(items)
}
fn outline_items_containing(
pub fn outline_items_containing<T: ToOffset>(
&self,
range: Range<usize>,
range: Range<T>,
include_extra_context: bool,
theme: Option<&SyntaxTheme>,
) -> Option<Vec<OutlineItem<Anchor>>> {
let range = range.to_offset(self);
let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| {
grammar.outline_config.as_ref().map(|c| &c.query)
});

View File

@ -70,7 +70,7 @@ pub use language_registry::{
PendingLanguageServer, QUERY_FILENAME_PREFIXES,
};
pub use lsp::LanguageServerId;
pub use outline::{Outline, OutlineItem};
pub use outline::{render_item, Outline, OutlineItem};
pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer};
pub use text::{AnchorRangeExt, LineEnding};
pub use tree_sitter::{Node, Parser, Tree, TreeCursor};

View File

@ -1,6 +1,11 @@
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{BackgroundExecutor, HighlightStyle};
use gpui::{
relative, AppContext, BackgroundExecutor, FontStyle, FontWeight, HighlightStyle, StyledText,
TextStyle, WhiteSpace,
};
use settings::Settings;
use std::ops::Range;
use theme::{ActiveTheme, ThemeSettings};
/// An outline of all the symbols contained in a buffer.
#[derive(Debug)]
@ -11,7 +16,7 @@ pub struct Outline<T> {
path_candidate_prefixes: Vec<usize>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct OutlineItem<T> {
pub depth: usize,
pub range: Range<T>,
@ -138,3 +143,34 @@ impl<T> Outline<T> {
tree_matches
}
}
pub fn render_item<T>(
outline_item: &OutlineItem<T>,
custom_highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
cx: &AppContext,
) -> StyledText {
let settings = ThemeSettings::get_global(cx);
// TODO: We probably shouldn't need to build a whole new text style here
// but I'm not sure how to get the current one and modify it.
// Before this change TextStyle::default() was used here, which was giving us the wrong font and text color.
let text_style = TextStyle {
color: cx.theme().colors().text,
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(),
font_size: settings.buffer_font_size(cx).into(),
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,
line_height: relative(1.),
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
};
let highlights = gpui::combine_highlights(
custom_highlights,
outline_item.highlight_ranges.iter().cloned(),
);
StyledText::new(outline_item.text.clone()).with_highlights(&text_style, highlights)
}

View File

@ -77,6 +77,9 @@ pub enum Event {
ExcerptsRemoved {
ids: Vec<ExcerptId>,
},
ExcerptsExpanded {
ids: Vec<ExcerptId>,
},
ExcerptsEdited {
ids: Vec<ExcerptId>,
},
@ -1666,8 +1669,9 @@ impl MultiBuffer {
}
self.sync(cx);
let ids = ids.into_iter().collect::<Vec<_>>();
let snapshot = self.snapshot(cx);
let locators = snapshot.excerpt_locators_for_ids(ids);
let locators = snapshot.excerpt_locators_for_ids(ids.iter().copied());
let mut new_excerpts = SumTree::new();
let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>();
let mut edits = Vec::<Edit<usize>>::new();
@ -1746,6 +1750,7 @@ impl MultiBuffer {
cx.emit(Event::Edited {
singleton_buffer_edited: false,
});
cx.emit(Event::ExcerptsExpanded { ids });
cx.notify();
}

View File

@ -19,7 +19,6 @@ gpui.workspace = true
language.workspace = true
ordered-float.workspace = true
picker.workspace = true
settings.workspace = true
smol.workspace = true
theme.workspace = true
ui.workspace = true

View File

@ -2,19 +2,18 @@ use editor::{scroll::Autoscroll, Anchor, AnchorRangeExt, Editor, EditorMode};
use fuzzy::StringMatch;
use gpui::{
actions, div, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView,
FontStyle, FontWeight, HighlightStyle, ParentElement, Point, Render, Styled, StyledText, Task,
TextStyle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext,
HighlightStyle, ParentElement, Point, Render, Styled, Task, View, ViewContext, VisualContext,
WeakView, WindowContext,
};
use language::Outline;
use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate};
use settings::Settings;
use std::{
cmp::{self, Reverse},
sync::Arc,
};
use theme::{color_alpha, ActiveTheme, ThemeSettings};
use theme::{color_alpha, ActiveTheme};
use ui::{prelude::*, ListItem, ListItemSpacing};
use util::ResultExt;
use workspace::{DismissDecision, ModalView};
@ -268,38 +267,12 @@ impl PickerDelegate for OutlineViewDelegate {
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let settings = ThemeSettings::get_global(cx);
// TODO: We probably shouldn't need to build a whole new text style here
// but I'm not sure how to get the current one and modify it.
// Before this change TextStyle::default() was used here, which was giving us the wrong font and text color.
let text_style = TextStyle {
color: cx.theme().colors().text,
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(),
font_size: settings.buffer_font_size(cx).into(),
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,
line_height: relative(1.),
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
};
let mat = self.matches.get(ix)?;
let outline_item = self.outline.items.get(mat.candidate_id)?;
let mut highlight_style = HighlightStyle::default();
highlight_style.background_color = Some(color_alpha(cx.theme().colors().text_accent, 0.3));
let mat = &self.matches[ix];
let outline_item = &self.outline.items[mat.candidate_id];
let highlights = gpui::combine_highlights(
mat.ranges().map(|range| (range, highlight_style)),
outline_item.highlight_ranges.iter().cloned(),
);
let styled_text =
StyledText::new(outline_item.text.clone()).with_highlights(&text_style, highlights);
let custom_highlights = mat.ranges().map(|range| (range, highlight_style));
Some(
ListItem::new(ix)
@ -310,7 +283,7 @@ impl PickerDelegate for OutlineViewDelegate {
div()
.text_ui(cx)
.pl(rems(outline_item.depth as f32))
.child(styled_text),
.child(language::render_item(outline_item, custom_highlights, cx)),
),
)
}

View File

@ -0,0 +1,37 @@
[package]
name = "outline_panel"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/outline_panel.rs"
doctest = false
[dependencies]
anyhow.workspace = true
collections.workspace = true
db.workspace = true
editor.workspace = true
file_icons.workspace = true
git.workspace = true
gpui.workspace = true
language.workspace = true
log.workspace = true
menu.workspace = true
project.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
unicase.workspace = true
util.workspace = true
worktree.workspace = true
workspace.workspace = true
[package.metadata.cargo-machete]
ignored = ["log"]

View File

@ -0,0 +1 @@
../../LICENSE-GPL

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,81 @@
use anyhow;
use gpui::Pixels;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum OutlinePanelDockPosition {
Left,
Right,
}
#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
pub struct OutlinePanelSettings {
pub button: bool,
pub default_width: Pixels,
pub dock: OutlinePanelDockPosition,
pub file_icons: bool,
pub folder_icons: bool,
pub git_status: bool,
pub indent_size: f32,
pub auto_reveal_entries: bool,
pub auto_fold_dirs: bool,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
pub struct OutlinePanelSettingsContent {
/// Whether to show the outline panel button in the status bar.
///
/// Default: true
pub button: Option<bool>,
/// Customise default width (in pixels) taken by outline panel
///
/// Default: 240
pub default_width: Option<f32>,
/// The position of outline panel
///
/// Default: left
pub dock: Option<OutlinePanelDockPosition>,
/// Whether to show file icons in the outline panel.
///
/// Default: true
pub file_icons: Option<bool>,
/// Whether to show folder icons or chevrons for directories in the outline panel.
///
/// Default: true
pub folder_icons: Option<bool>,
/// Whether to show the git status in the outline panel.
///
/// Default: true
pub git_status: Option<bool>,
/// Amount of indentation (in pixels) for nested items.
///
/// Default: 20
pub indent_size: Option<f32>,
/// Whether to reveal it in the outline panel automatically,
/// when a corresponding project entry becomes active.
/// Gitignored entries are never auto revealed.
///
/// Default: true
pub auto_reveal_entries: Option<bool>,
/// Whether to fold directories automatically
/// when directory has only one directory inside.
///
/// Default: true
pub auto_fold_dirs: Option<bool>,
}
impl Settings for OutlinePanelSettings {
const KEY: Option<&'static str> = Some("outline_panel");
type FileContent = OutlinePanelSettingsContent;
fn load(
sources: SettingsSources<Self::FileContent>,
_: &mut gpui::AppContext,
) -> anyhow::Result<Self> {
sources.json_merge()
}
}

View File

@ -144,6 +144,7 @@ pub enum IconName {
InlayHint,
Library,
Link,
ListTree,
MagicWand,
MagnifyingGlass,
MailOpen,
@ -274,6 +275,7 @@ impl IconName {
IconName::InlayHint => "icons/inlay_hint.svg",
IconName::Library => "icons/library.svg",
IconName::Link => "icons/link.svg",
IconName::ListTree => "icons/list_tree.svg",
IconName::MagicWand => "icons/magic_wand.svg",
IconName::MagnifyingGlass => "icons/magnifying_glass.svg",
IconName::MailOpen => "icons/mail_open.svg",

View File

@ -2000,7 +2000,7 @@ impl Snapshot {
}
}
fn traverse_from_path(
pub fn traverse_from_path(
&self,
include_files: bool,
include_dirs: bool,
@ -2991,7 +2991,7 @@ impl File {
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Entry {
pub id: ProjectEntryId,
pub kind: EntryKind,
@ -3020,7 +3020,7 @@ pub struct Entry {
pub is_private: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum EntryKind {
UnloadedDir,
PendingDir,
@ -4818,6 +4818,14 @@ impl<'a> Traversal<'a> {
false
}
pub fn back_to_parent(&mut self) -> bool {
let Some(parent_path) = self.cursor.item().and_then(|entry| entry.path.parent()) else {
return false;
};
self.cursor
.seek(&TraversalTarget::Path(parent_path), Bias::Left, &())
}
pub fn entry(&self) -> Option<&'a Entry> {
self.cursor.item()
}

View File

@ -68,6 +68,7 @@ nix = {workspace = true, features = ["pthread", "signal"] }
node_runtime.workspace = true
notifications.workspace = true
outline.workspace = true
outline_panel.workspace = true
parking_lot.workspace = true
profiling.workspace = true
project.workspace = true

View File

@ -185,6 +185,7 @@ fn init_ui(app_state: Arc<AppState>, cx: &mut AppContext) -> Result<()> {
outline::init(cx);
project_symbols::init(cx);
project_panel::init(Assets, cx);
outline_panel::init(Assets, cx);
tasks_ui::init(cx);
channel::init(&app_state.client.clone(), app_state.user_store.clone(), cx);
search::init(cx);

View File

@ -18,6 +18,7 @@ pub use open_listener::*;
use anyhow::Context as _;
use assets::Assets;
use futures::{channel::mpsc, select_biased, StreamExt};
use outline_panel::OutlinePanel;
use project::TaskSourceKind;
use project_panel::ProjectPanel;
use quick_action_bar::QuickActionBar;
@ -190,6 +191,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
let assistant_panel =
assistant::AssistantPanel::load(workspace_handle.clone(), cx.clone());
let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone());
let outline_panel = OutlinePanel::load(workspace_handle.clone(), cx.clone());
let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone());
let channels_panel =
collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone());
@ -202,6 +204,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
let (
project_panel,
outline_panel,
terminal_panel,
assistant_panel,
channels_panel,
@ -209,6 +212,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
notification_panel,
) = futures::try_join!(
project_panel,
outline_panel,
terminal_panel,
assistant_panel,
channels_panel,
@ -219,6 +223,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
workspace_handle.update(&mut cx, |workspace, cx| {
workspace.add_panel(assistant_panel, cx);
workspace.add_panel(project_panel, cx);
workspace.add_panel(outline_panel, cx);
workspace.add_panel(terminal_panel, cx);
workspace.add_panel(channels_panel, cx);
workspace.add_panel(chat_panel, cx);
@ -377,6 +382,13 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
workspace.toggle_panel_focus::<ProjectPanel>(cx);
},
)
.register_action(
|workspace: &mut Workspace,
_: &outline_panel::ToggleFocus,
cx: &mut ViewContext<Workspace>| {
workspace.toggle_panel_focus::<OutlinePanel>(cx);
},
)
.register_action(
|workspace: &mut Workspace,
_: &collab_ui::collab_panel::ToggleFocus,
@ -3093,9 +3105,9 @@ mod tests {
command_palette::init(cx);
language::init(cx);
editor::init(cx);
project_panel::init_settings(cx);
collab_ui::init(&app_state, cx);
project_panel::init((), cx);
outline_panel::init((), cx);
terminal_view::init(cx);
assistant::init(app_state.client.clone(), cx);
tasks_ui::init(cx);

View File

@ -123,6 +123,7 @@ pub fn app_menus() -> Vec<Menu<'static>> {
}),
MenuItem::separator(),
MenuItem::action("Project Panel", project_panel::ToggleFocus),
MenuItem::action("Outline Panel", outline_panel::ToggleFocus),
MenuItem::action("Collab Panel", collab_panel::ToggleFocus),
MenuItem::action("Terminal Panel", terminal_panel::ToggleFocus),
MenuItem::separator(),