mirror of
https://github.com/zed-industries/zed.git
synced 2024-10-07 11:28:37 +03:00
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:
parent
7f56f4e78e
commit
8451dba6a7
27
Cargo.lock
generated
27
Cargo.lock
generated
@ -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",
|
||||
|
@ -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" }
|
||||
|
1
assets/icons/list_tree.svg
Normal file
1
assets/icons/list_tree.svg
Normal 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 |
@ -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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -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": {
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
});
|
||||
|
@ -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};
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
37
crates/outline_panel/Cargo.toml
Normal file
37
crates/outline_panel/Cargo.toml
Normal 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"]
|
1
crates/outline_panel/LICENSE-GPL
Symbolic link
1
crates/outline_panel/LICENSE-GPL
Symbolic link
@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
2515
crates/outline_panel/src/outline_panel.rs
Normal file
2515
crates/outline_panel/src/outline_panel.rs
Normal file
File diff suppressed because it is too large
Load Diff
81
crates/outline_panel/src/outline_panel_settings.rs
Normal file
81
crates/outline_panel/src/outline_panel_settings.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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(),
|
||||
|
Loading…
Reference in New Issue
Block a user