mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-24 17:28:40 +00:00
Introduced ItemEvent and to_item_events function to Item trait which converts the Item's events into a standard ItemEvent similar to how SearchableItems work.
Add breadcrumb_location and breadcrumbs functions to item trait which handles rendering of the breadcrumb elements Change breadcrumb toolbar to use these new functions rather than having hard coded breadcrumb logic Add breadcrumb support to the terminal tabs Co-Authored-By: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
parent
ab81093ef5
commit
31ecb2f7bc
8 changed files with 149 additions and 99 deletions
|
@ -1,46 +1,29 @@
|
|||
use editor::Editor;
|
||||
use gpui::{
|
||||
elements::*, AppContext, Entity, ModelHandle, RenderContext, Subscription, View, ViewContext,
|
||||
ViewHandle,
|
||||
elements::*, AppContext, Entity, RenderContext, Subscription, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use project::Project;
|
||||
use search::ProjectSearchView;
|
||||
use settings::Settings;
|
||||
use workspace::{ItemHandle, ToolbarItemLocation, ToolbarItemView};
|
||||
use workspace::{ItemEvent, ItemHandle, ToolbarItemLocation, ToolbarItemView};
|
||||
|
||||
pub enum Event {
|
||||
UpdateLocation,
|
||||
}
|
||||
|
||||
pub struct Breadcrumbs {
|
||||
project: ModelHandle<Project>,
|
||||
active_item: Option<Box<dyn ItemHandle>>,
|
||||
project_search: Option<ViewHandle<ProjectSearchView>>,
|
||||
subscriptions: Vec<Subscription>,
|
||||
subscription: Option<Subscription>,
|
||||
}
|
||||
|
||||
impl Breadcrumbs {
|
||||
pub fn new(project: ModelHandle<Project>) -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
project,
|
||||
active_item: Default::default(),
|
||||
subscriptions: Default::default(),
|
||||
subscription: Default::default(),
|
||||
project_search: Default::default(),
|
||||
}
|
||||
}
|
||||
// fn active_symbols(
|
||||
// &self,
|
||||
// theme: &SyntaxTheme,
|
||||
// cx: &AppContext,
|
||||
// ) -> Option<(ModelHandle<Buffer>, Vec<OutlineItem<Anchor>>)> {
|
||||
// let editor = self.active_item.as_ref()?.read(cx);
|
||||
// let cursor = editor.selections.newest_anchor().head();
|
||||
// let multibuffer = &editor.buffer().read(cx);
|
||||
// let (buffer_id, symbols) = multibuffer.symbols_containing(cursor, Some(theme), cx)?;
|
||||
// let buffer = multibuffer.buffer(buffer_id)?;
|
||||
// Some((buffer, symbols))
|
||||
// }
|
||||
}
|
||||
|
||||
impl Entity for Breadcrumbs {
|
||||
|
@ -53,42 +36,16 @@ impl View for Breadcrumbs {
|
|||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
// let (buffer, symbols) =
|
||||
// if let Some((buffer, symbols)) = self.active_symbols(&theme.editor.syntax, cx) {
|
||||
// (buffer, symbols)
|
||||
// } else {
|
||||
// return Empty::new().boxed();
|
||||
// };
|
||||
// let buffer = buffer.read(cx);
|
||||
// let filename = if let Some(file) = buffer.file() {
|
||||
// if file.path().file_name().is_none()
|
||||
// || self.project.read(cx).visible_worktrees(cx).count() > 1
|
||||
// {
|
||||
// file.full_path(cx).to_string_lossy().to_string()
|
||||
// } else {
|
||||
// file.path().to_string_lossy().to_string()
|
||||
// }
|
||||
// } else {
|
||||
// "untitled".to_string()
|
||||
// };
|
||||
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
if let Some(breadcrumbs) = self
|
||||
.active_item
|
||||
.as_ref()
|
||||
.and_then(|item| item.breadcrumbs(&theme, cx))
|
||||
{
|
||||
Flex::row()
|
||||
.with_children(Itertools::intersperse_with(breadcrumbs.into_iter(), || {
|
||||
Label::new(" 〉 ".to_string(), theme.breadcrumbs.text.clone()).boxed()
|
||||
}))
|
||||
// .with_child(Label::new(filename, theme.breadcrumbs.text.clone()).boxed())
|
||||
// .with_children(symbols.into_iter().flat_map(|symbol| {
|
||||
// [
|
||||
// Text::new(symbol.text, theme.breadcrumbs.text.clone())
|
||||
// .with_highlights(symbol.highlight_ranges)
|
||||
// .boxed(),
|
||||
// ]
|
||||
// }))
|
||||
.contained()
|
||||
.with_style(theme.breadcrumbs.container)
|
||||
.aligned()
|
||||
|
@ -107,39 +64,25 @@ impl ToolbarItemView for Breadcrumbs {
|
|||
cx: &mut ViewContext<Self>,
|
||||
) -> ToolbarItemLocation {
|
||||
cx.notify();
|
||||
self.subscriptions.clear();
|
||||
self.active_item = None;
|
||||
self.project_search = None;
|
||||
if let Some(item) = active_pane_item {
|
||||
if let Some(editor) = item.act_as::<Editor>(cx) {
|
||||
self.subscriptions
|
||||
.push(cx.subscribe(&editor, |_, _, event, cx| match event {
|
||||
editor::Event::BufferEdited
|
||||
| editor::Event::TitleChanged
|
||||
| editor::Event::Saved
|
||||
| editor::Event::Reparsed => cx.notify(),
|
||||
editor::Event::SelectionsChanged { local } if *local => cx.notify(),
|
||||
_ => {}
|
||||
}));
|
||||
self.active_item = Some(editor);
|
||||
if let Some(project_search) = item.downcast::<ProjectSearchView>() {
|
||||
self.subscriptions
|
||||
.push(cx.subscribe(&project_search, |_, _, _, cx| {
|
||||
cx.emit(Event::UpdateLocation);
|
||||
}));
|
||||
self.project_search = Some(project_search.clone());
|
||||
|
||||
if project_search.read(cx).has_matches() {
|
||||
ToolbarItemLocation::Secondary
|
||||
} else {
|
||||
ToolbarItemLocation::Hidden
|
||||
let this = cx.weak_handle();
|
||||
self.subscription = Some(item.subscribe_to_item_events(
|
||||
cx,
|
||||
Box::new(move |event, cx| {
|
||||
if let Some(this) = this.upgrade(cx) {
|
||||
if let ItemEvent::UpdateBreadcrumbs = event {
|
||||
this.update(cx, |_, cx| {
|
||||
cx.emit(Event::UpdateLocation);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ToolbarItemLocation::PrimaryLeft { flex: None }
|
||||
}
|
||||
} else {
|
||||
ToolbarItemLocation::Hidden
|
||||
}
|
||||
}),
|
||||
));
|
||||
self.active_item = Some(item.boxed_clone());
|
||||
item.breadcrumb_location(cx)
|
||||
} else {
|
||||
ToolbarItemLocation::Hidden
|
||||
}
|
||||
|
@ -151,12 +94,8 @@ impl ToolbarItemView for Breadcrumbs {
|
|||
current_location: ToolbarItemLocation,
|
||||
cx: &AppContext,
|
||||
) -> ToolbarItemLocation {
|
||||
if let Some(project_search) = self.project_search.as_ref() {
|
||||
if project_search.read(cx).has_matches() {
|
||||
ToolbarItemLocation::Secondary
|
||||
} else {
|
||||
ToolbarItemLocation::Hidden
|
||||
}
|
||||
if let Some(active_item) = self.active_item.as_ref() {
|
||||
active_item.breadcrumb_location(cx)
|
||||
} else {
|
||||
current_location
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ use util::TryFutureExt;
|
|||
use workspace::{
|
||||
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
|
||||
FollowableItem, Item, ItemEvent, ItemHandle, ItemNavHistory, ProjectItem, StatusItemView,
|
||||
ToolbarItemLocation,
|
||||
};
|
||||
|
||||
pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
|
||||
|
@ -476,17 +477,71 @@ impl Item for Editor {
|
|||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event) -> Vec<workspace::ItemEvent> {
|
||||
let mut result = Vec::new();
|
||||
match event {
|
||||
Event::Closed => vec![ItemEvent::CloseItem],
|
||||
Event::Saved | Event::DirtyChanged | Event::TitleChanged => vec![ItemEvent::UpdateTab],
|
||||
Event::BufferEdited => vec![ItemEvent::Edit],
|
||||
_ => Vec::new(),
|
||||
Event::Closed => result.push(ItemEvent::CloseItem),
|
||||
Event::Saved | Event::TitleChanged => {
|
||||
result.push(ItemEvent::UpdateTab);
|
||||
result.push(ItemEvent::UpdateBreadcrumbs);
|
||||
}
|
||||
Event::Reparsed => {
|
||||
result.push(ItemEvent::UpdateBreadcrumbs);
|
||||
}
|
||||
Event::SelectionsChanged { local } if *local => {
|
||||
result.push(ItemEvent::UpdateBreadcrumbs);
|
||||
}
|
||||
Event::DirtyChanged => {
|
||||
result.push(ItemEvent::UpdateTab);
|
||||
}
|
||||
Event::BufferEdited => {
|
||||
result.push(ItemEvent::Edit);
|
||||
result.push(ItemEvent::UpdateBreadcrumbs);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
Some(Box::new(handle.clone()))
|
||||
}
|
||||
|
||||
fn breadcrumb_location(&self) -> ToolbarItemLocation {
|
||||
ToolbarItemLocation::PrimaryLeft { flex: None }
|
||||
}
|
||||
|
||||
fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
|
||||
let cursor = self.selections.newest_anchor().head();
|
||||
let multibuffer = &self.buffer().read(cx);
|
||||
let (buffer_id, symbols) =
|
||||
multibuffer.symbols_containing(cursor, Some(&theme.editor.syntax), cx)?;
|
||||
let buffer = multibuffer.buffer(buffer_id)?;
|
||||
|
||||
let buffer = buffer.read(cx);
|
||||
let filename = if let Some(file) = buffer.file() {
|
||||
if file.path().file_name().is_none()
|
||||
|| self
|
||||
.project
|
||||
.as_ref()
|
||||
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
file.full_path(cx).to_string_lossy().to_string()
|
||||
} else {
|
||||
file.path().to_string_lossy().to_string()
|
||||
}
|
||||
} else {
|
||||
"untitled".to_string()
|
||||
};
|
||||
|
||||
let mut breadcrumbs = vec![Label::new(filename, theme.breadcrumbs.text.clone()).boxed()];
|
||||
breadcrumbs.extend(symbols.into_iter().map(|symbol| {
|
||||
Text::new(symbol.text, theme.breadcrumbs.text.clone())
|
||||
.with_highlights(symbol.highlight_ranges)
|
||||
.boxed()
|
||||
}));
|
||||
Some(breadcrumbs)
|
||||
}
|
||||
}
|
||||
|
||||
impl ProjectItem for Editor {
|
||||
|
|
|
@ -189,7 +189,9 @@ impl ToolbarItemView for BufferSearchBar {
|
|||
self.active_searchable_item.take();
|
||||
self.pending_search.take();
|
||||
|
||||
if let Some(searchable_item_handle) = item.and_then(|item| item.as_searchable(cx)) {
|
||||
if let Some(searchable_item_handle) =
|
||||
item.and_then(|item| item.to_searchable_item_handle(cx))
|
||||
{
|
||||
let handle = cx.weak_handle();
|
||||
self.active_searchable_item_subscription =
|
||||
Some(searchable_item_handle.subscribe_to_search_events(
|
||||
|
|
|
@ -329,11 +329,23 @@ impl Item for ProjectSearchView {
|
|||
|
||||
fn to_item_events(event: &Self::Event) -> Vec<ItemEvent> {
|
||||
match event {
|
||||
ViewEvent::UpdateTab => vec![ItemEvent::UpdateTab],
|
||||
ViewEvent::UpdateTab => vec![ItemEvent::UpdateBreadcrumbs, ItemEvent::UpdateTab],
|
||||
ViewEvent::EditorEvent(editor_event) => Editor::to_item_events(editor_event),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn breadcrumb_location(&self) -> ToolbarItemLocation {
|
||||
if self.has_matches() {
|
||||
ToolbarItemLocation::Secondary
|
||||
} else {
|
||||
ToolbarItemLocation::Hidden
|
||||
}
|
||||
}
|
||||
|
||||
fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
|
||||
self.results_editor.breadcrumbs(theme, cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl ProjectSearchView {
|
||||
|
|
|
@ -83,6 +83,7 @@ const DEBUG_LINE_HEIGHT: f32 = 5.;
|
|||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Event {
|
||||
TitleChanged,
|
||||
BreadcrumbsChanged,
|
||||
CloseTerminal,
|
||||
Bell,
|
||||
Wakeup,
|
||||
|
@ -494,9 +495,11 @@ impl Terminal {
|
|||
match event {
|
||||
AlacTermEvent::Title(title) => {
|
||||
self.breadcrumb_text = title.to_string();
|
||||
cx.emit(Event::BreadcrumbsChanged);
|
||||
}
|
||||
AlacTermEvent::ResetTitle => {
|
||||
self.breadcrumb_text = String::new();
|
||||
cx.emit(Event::BreadcrumbsChanged);
|
||||
}
|
||||
AlacTermEvent::ClipboardStore(_, data) => {
|
||||
cx.write_to_clipboard(ClipboardItem::new(data.to_string()))
|
||||
|
|
|
@ -9,7 +9,7 @@ use gpui::{
|
|||
};
|
||||
use util::truncate_and_trailoff;
|
||||
use workspace::searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle};
|
||||
use workspace::{Item, ItemEvent, Workspace};
|
||||
use workspace::{Item, ItemEvent, ToolbarItemLocation, Workspace};
|
||||
|
||||
use crate::TerminalSize;
|
||||
use project::{LocalWorktree, Project, ProjectPath};
|
||||
|
@ -363,13 +363,37 @@ impl Item for TerminalContainer {
|
|||
Some(Box::new(handle.clone()))
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event) -> Vec<workspace::ItemEvent> {
|
||||
fn to_item_events(event: &Self::Event) -> Vec<ItemEvent> {
|
||||
match event {
|
||||
Event::BreadcrumbsChanged => vec![ItemEvent::UpdateBreadcrumbs],
|
||||
Event::TitleChanged | Event::Wakeup => vec![ItemEvent::UpdateTab],
|
||||
Event::CloseTerminal => vec![ItemEvent::CloseItem],
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn breadcrumb_location(&self) -> ToolbarItemLocation {
|
||||
if self.connected().is_some() {
|
||||
ToolbarItemLocation::PrimaryLeft { flex: None }
|
||||
} else {
|
||||
ToolbarItemLocation::Hidden
|
||||
}
|
||||
}
|
||||
|
||||
fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
|
||||
let connected = self.connected()?;
|
||||
|
||||
Some(vec![Text::new(
|
||||
connected
|
||||
.read(cx)
|
||||
.terminal()
|
||||
.read(cx)
|
||||
.breadcrumb_text
|
||||
.to_string(),
|
||||
theme.breadcrumbs.text.clone(),
|
||||
)
|
||||
.boxed()])
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchableItem for TerminalContainer {
|
||||
|
|
|
@ -339,7 +339,7 @@ pub trait Item: View {
|
|||
fn breadcrumb_location(&self) -> ToolbarItemLocation {
|
||||
ToolbarItemLocation::Hidden
|
||||
}
|
||||
fn breadcrumbs(&self, _theme: &Theme) -> Option<Vec<ElementBox>> {
|
||||
fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<ElementBox>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -437,6 +437,11 @@ impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
|
|||
}
|
||||
|
||||
pub trait ItemHandle: 'static + fmt::Debug {
|
||||
fn subscribe_to_item_events(
|
||||
&self,
|
||||
cx: &mut MutableAppContext,
|
||||
handler: Box<dyn Fn(ItemEvent, &mut MutableAppContext)>,
|
||||
) -> gpui::Subscription;
|
||||
fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
|
||||
fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
|
||||
-> ElementBox;
|
||||
|
@ -476,8 +481,7 @@ pub trait ItemHandle: 'static + fmt::Debug {
|
|||
cx: &mut MutableAppContext,
|
||||
callback: Box<dyn FnOnce(&mut MutableAppContext)>,
|
||||
) -> gpui::Subscription;
|
||||
fn as_searchable(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
|
||||
|
||||
fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
|
||||
fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
|
||||
fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>>;
|
||||
}
|
||||
|
@ -500,6 +504,18 @@ impl dyn ItemHandle {
|
|||
}
|
||||
|
||||
impl<T: Item> ItemHandle for ViewHandle<T> {
|
||||
fn subscribe_to_item_events(
|
||||
&self,
|
||||
cx: &mut MutableAppContext,
|
||||
handler: Box<dyn Fn(ItemEvent, &mut MutableAppContext)>,
|
||||
) -> gpui::Subscription {
|
||||
cx.subscribe(self, move |_, event, cx| {
|
||||
for item_event in T::to_item_events(event) {
|
||||
handler(item_event, cx)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>> {
|
||||
self.read(cx).tab_description(detail, cx)
|
||||
}
|
||||
|
@ -762,7 +778,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
|||
cx.observe_release(self, move |_, cx| callback(cx))
|
||||
}
|
||||
|
||||
fn as_searchable(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
self.read(cx).as_searchable(self)
|
||||
}
|
||||
|
||||
|
@ -771,7 +787,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
|||
}
|
||||
|
||||
fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
|
||||
self.read(cx).breadcrumbs(theme)
|
||||
self.read(cx).breadcrumbs(theme, cx)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -225,12 +225,11 @@ pub fn initialize_workspace(
|
|||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
cx.subscribe(&cx.handle(), {
|
||||
let project = workspace.project().clone();
|
||||
move |_, _, event, cx| {
|
||||
if let workspace::Event::PaneAdded(pane) = event {
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.toolbar().update(cx, |toolbar, cx| {
|
||||
let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(project.clone()));
|
||||
let breadcrumbs = cx.add_view(|_| Breadcrumbs::new());
|
||||
toolbar.add_item(breadcrumbs, cx);
|
||||
let buffer_search_bar = cx.add_view(BufferSearchBar::new);
|
||||
toolbar.add_item(buffer_search_bar, cx);
|
||||
|
|
Loading…
Reference in a new issue