From ab81093ef5e3af43b1284f9e30fafe5d2bf640d6 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Tue, 6 Sep 2022 14:39:58 -0700 Subject: [PATCH 1/2] WIP pull breadcrumb rendering out into item trait --- Cargo.lock | 1 + crates/breadcrumbs/Cargo.toml | 1 + crates/breadcrumbs/src/breadcrumbs.rs | 115 ++++++++-------- crates/diagnostics/src/diagnostics.rs | 8 +- crates/editor/src/items.rs | 22 ++- crates/search/src/buffer_search.rs | 21 +-- crates/search/src/project_search.rs | 17 +-- .../terminal/src/terminal_container_view.rs | 18 +-- crates/workspace/src/searchable.rs | 4 +- crates/workspace/src/workspace.rs | 128 ++++++++++-------- 10 files changed, 176 insertions(+), 159 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9cbdce801..634ef452a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -613,6 +613,7 @@ dependencies = [ "collections", "editor", "gpui", + "itertools", "language", "project", "search", diff --git a/crates/breadcrumbs/Cargo.toml b/crates/breadcrumbs/Cargo.toml index 88fd614a89..e5cae74e8f 100644 --- a/crates/breadcrumbs/Cargo.toml +++ b/crates/breadcrumbs/Cargo.toml @@ -17,6 +17,7 @@ search = { path = "../search" } settings = { path = "../settings" } theme = { path = "../theme" } workspace = { path = "../workspace" } +itertools = "0.10" [dev-dependencies] editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/breadcrumbs/src/breadcrumbs.rs b/crates/breadcrumbs/src/breadcrumbs.rs index 2d8089c120..5698b7f990 100644 --- a/crates/breadcrumbs/src/breadcrumbs.rs +++ b/crates/breadcrumbs/src/breadcrumbs.rs @@ -1,13 +1,12 @@ -use editor::{Anchor, Editor}; +use editor::Editor; use gpui::{ elements::*, AppContext, Entity, ModelHandle, RenderContext, Subscription, View, ViewContext, ViewHandle, }; -use language::{Buffer, OutlineItem}; +use itertools::Itertools; use project::Project; use search::ProjectSearchView; use settings::Settings; -use theme::SyntaxTheme; use workspace::{ItemHandle, ToolbarItemLocation, ToolbarItemView}; pub enum Event { @@ -16,7 +15,7 @@ pub enum Event { pub struct Breadcrumbs { project: ModelHandle, - editor: Option>, + active_item: Option>, project_search: Option>, subscriptions: Vec, } @@ -25,24 +24,23 @@ impl Breadcrumbs { pub fn new(project: ModelHandle) -> Self { Self { project, - editor: Default::default(), + active_item: Default::default(), subscriptions: Default::default(), project_search: Default::default(), } } - - fn active_symbols( - &self, - theme: &SyntaxTheme, - cx: &AppContext, - ) -> Option<(ModelHandle, Vec>)> { - let editor = self.editor.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)) - } + // fn active_symbols( + // &self, + // theme: &SyntaxTheme, + // cx: &AppContext, + // ) -> Option<(ModelHandle, Vec>)> { + // 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 { @@ -55,41 +53,50 @@ impl View for Breadcrumbs { } fn render(&mut self, cx: &mut RenderContext) -> ElementBox { - let theme = cx.global::().theme.clone(); - 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 (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() + // }; - Flex::row() - .with_child(Label::new(filename, theme.breadcrumbs.text.clone()).boxed()) - .with_children(symbols.into_iter().flat_map(|symbol| { - [ - Label::new(" 〉 ".to_string(), theme.breadcrumbs.text.clone()).boxed(), - Text::new(symbol.text, theme.breadcrumbs.text.clone()) - .with_highlights(symbol.highlight_ranges) - .boxed(), - ] - })) - .contained() - .with_style(theme.breadcrumbs.container) - .aligned() - .left() - .boxed() + let theme = cx.global::().theme.clone(); + if let Some(breadcrumbs) = self + .active_item + .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() + .left() + .boxed() + } else { + Empty::new().boxed() + } } } @@ -101,7 +108,7 @@ impl ToolbarItemView for Breadcrumbs { ) -> ToolbarItemLocation { cx.notify(); self.subscriptions.clear(); - self.editor = None; + self.active_item = None; self.project_search = None; if let Some(item) = active_pane_item { if let Some(editor) = item.act_as::(cx) { @@ -114,7 +121,7 @@ impl ToolbarItemView for Breadcrumbs { editor::Event::SelectionsChanged { local } if *local => cx.notify(), _ => {} })); - self.editor = Some(editor); + self.active_item = Some(editor); if let Some(project_search) = item.downcast::() { self.subscriptions .push(cx.subscribe(&project_search, |_, _, _, cx| { diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index e14a44b058..271843dc69 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -566,12 +566,8 @@ impl workspace::Item for ProjectDiagnosticsEditor { unreachable!() } - fn should_update_tab_on_event(event: &Event) -> bool { - Editor::should_update_tab_on_event(event) - } - - fn is_edit_event(event: &Self::Event) -> bool { - Editor::is_edit_event(event) + fn to_item_events(event: &Self::Event) -> Vec { + Editor::to_item_events(event) } fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext) { diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 3d412c423e..15262472e4 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -26,7 +26,7 @@ use text::{Point, Selection}; use util::TryFutureExt; use workspace::{ searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle}, - FollowableItem, Item, ItemHandle, ItemNavHistory, ProjectItem, StatusItemView, + FollowableItem, Item, ItemEvent, ItemHandle, ItemNavHistory, ProjectItem, StatusItemView, }; pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); @@ -475,19 +475,13 @@ impl Item for Editor { }) } - fn should_close_item_on_event(event: &Event) -> bool { - matches!(event, Event::Closed) - } - - fn should_update_tab_on_event(event: &Event) -> bool { - matches!( - event, - Event::Saved | Event::DirtyChanged | Event::TitleChanged - ) - } - - fn is_edit_event(event: &Self::Event) -> bool { - matches!(event, Event::BufferEdited) + fn to_item_events(event: &Self::Event) -> Vec { + match event { + Event::Closed => vec![ItemEvent::CloseItem], + Event::Saved | Event::DirtyChanged | Event::TitleChanged => vec![ItemEvent::UpdateTab], + Event::BufferEdited => vec![ItemEvent::Edit], + _ => Vec::new(), + } } fn as_searchable(&self, handle: &ViewHandle) -> Option> { diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 22574b9b71..c38f31ad91 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -191,16 +191,17 @@ impl ToolbarItemView for BufferSearchBar { if let Some(searchable_item_handle) = item.and_then(|item| item.as_searchable(cx)) { let handle = cx.weak_handle(); - self.active_searchable_item_subscription = Some(searchable_item_handle.subscribe( - cx, - Box::new(move |search_event, cx| { - if let Some(this) = handle.upgrade(cx) { - this.update(cx, |this, cx| { - this.on_active_searchable_item_event(search_event, cx) - }); - } - }), - )); + self.active_searchable_item_subscription = + Some(searchable_item_handle.subscribe_to_search_events( + cx, + Box::new(move |search_event, cx| { + if let Some(this) = handle.upgrade(cx) { + this.update(cx, |this, cx| { + this.on_active_searchable_item_event(search_event, cx) + }); + } + }), + )); self.active_searchable_item = Some(searchable_item_handle); self.update_matches(false, cx); diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 8caa7bf71d..b664675877 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -24,7 +24,8 @@ use std::{ use util::ResultExt as _; use workspace::{ searchable::{Direction, SearchableItem, SearchableItemHandle}, - Item, ItemHandle, ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace, + Item, ItemEvent, ItemHandle, ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, + Workspace, }; actions!(project_search, [SearchInNew, ToggleFocus]); @@ -326,15 +327,11 @@ impl Item for ProjectSearchView { .update(cx, |editor, cx| editor.navigate(data, cx)) } - fn should_update_tab_on_event(event: &ViewEvent) -> bool { - matches!(event, ViewEvent::UpdateTab) - } - - fn is_edit_event(event: &Self::Event) -> bool { - if let ViewEvent::EditorEvent(editor_event) = event { - Editor::is_edit_event(editor_event) - } else { - false + fn to_item_events(event: &Self::Event) -> Vec { + match event { + ViewEvent::UpdateTab => vec![ItemEvent::UpdateTab], + ViewEvent::EditorEvent(editor_event) => Editor::to_item_events(editor_event), + _ => Vec::new(), } } } diff --git a/crates/terminal/src/terminal_container_view.rs b/crates/terminal/src/terminal_container_view.rs index baba487980..ad2cb9ffdd 100644 --- a/crates/terminal/src/terminal_container_view.rs +++ b/crates/terminal/src/terminal_container_view.rs @@ -9,7 +9,7 @@ use gpui::{ }; use util::truncate_and_trailoff; use workspace::searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle}; -use workspace::{Item, Workspace}; +use workspace::{Item, ItemEvent, Workspace}; use crate::TerminalSize; use project::{LocalWorktree, Project, ProjectPath}; @@ -359,17 +359,17 @@ impl Item for TerminalContainer { false } - fn should_update_tab_on_event(event: &Self::Event) -> bool { - matches!(event, &Event::TitleChanged | &Event::Wakeup) - } - - fn should_close_item_on_event(event: &Self::Event) -> bool { - matches!(event, &Event::CloseTerminal) - } - fn as_searchable(&self, handle: &ViewHandle) -> Option> { Some(Box::new(handle.clone())) } + + fn to_item_events(event: &Self::Event) -> Vec { + match event { + Event::TitleChanged | Event::Wakeup => vec![ItemEvent::UpdateTab], + Event::CloseTerminal => vec![ItemEvent::CloseItem], + _ => vec![], + } + } } impl SearchableItem for TerminalContainer { diff --git a/crates/workspace/src/searchable.rs b/crates/workspace/src/searchable.rs index f566d1136e..cbe7364536 100644 --- a/crates/workspace/src/searchable.rs +++ b/crates/workspace/src/searchable.rs @@ -88,7 +88,7 @@ pub trait SearchableItemHandle: ItemHandle { fn downgrade(&self) -> Box; fn boxed_clone(&self) -> Box; fn supported_options(&self) -> SearchOptions; - fn subscribe( + fn subscribe_to_search_events( &self, cx: &mut MutableAppContext, handler: Box, @@ -134,7 +134,7 @@ impl SearchableItemHandle for ViewHandle { T::supported_options() } - fn subscribe( + fn subscribe_to_search_events( &self, cx: &mut MutableAppContext, handler: Box, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index c7a122e9db..e17aded2b3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -267,6 +267,14 @@ pub struct AppState { pub initialize_workspace: fn(&mut Workspace, &Arc, &mut ViewContext), } +#[derive(Eq, PartialEq, Hash)] +pub enum ItemEvent { + CloseItem, + UpdateTab, + UpdateBreadcrumbs, + Edit, +} + pub trait Item: View { fn deactivated(&mut self, _: &mut ViewContext) {} fn workspace_deactivated(&mut self, _: &mut ViewContext) {} @@ -311,15 +319,7 @@ pub trait Item: View { project: ModelHandle, cx: &mut ViewContext, ) -> Task>; - fn should_close_item_on_event(_: &Self::Event) -> bool { - false - } - fn should_update_tab_on_event(_: &Self::Event) -> bool { - false - } - fn is_edit_event(_: &Self::Event) -> bool { - false - } + fn to_item_events(event: &Self::Event) -> Vec; fn act_as_type( &self, type_id: TypeId, @@ -335,6 +335,13 @@ pub trait Item: View { fn as_searchable(&self, _: &ViewHandle) -> Option> { None } + + fn breadcrumb_location(&self) -> ToolbarItemLocation { + ToolbarItemLocation::Hidden + } + fn breadcrumbs(&self, _theme: &Theme) -> Option> { + None + } } pub trait ProjectItem: Item { @@ -470,6 +477,9 @@ pub trait ItemHandle: 'static + fmt::Debug { callback: Box, ) -> gpui::Subscription; fn as_searchable(&self, cx: &AppContext) -> Option>; + + fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation; + fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option>; } pub trait WeakItemHandle { @@ -605,47 +615,53 @@ impl ItemHandle for ViewHandle { } } - if T::should_close_item_on_event(event) { - Pane::close_item(workspace, pane, item.id(), cx).detach_and_log_err(cx); - return; - } + for item_event in T::to_item_events(event).into_iter() { + match item_event { + ItemEvent::CloseItem => { + Pane::close_item(workspace, pane, item.id(), cx) + .detach_and_log_err(cx); + return; + } + ItemEvent::UpdateTab => { + pane.update(cx, |_, cx| { + cx.emit(pane::Event::ChangeItemTitle); + cx.notify(); + }); + } + ItemEvent::Edit => { + if let Autosave::AfterDelay { milliseconds } = + cx.global::().autosave + { + let prev_autosave = pending_autosave + .take() + .unwrap_or_else(|| Task::ready(Some(()))); + let (cancel_tx, mut cancel_rx) = oneshot::channel::<()>(); + let prev_cancel_tx = + mem::replace(&mut cancel_pending_autosave, cancel_tx); + let project = workspace.project.downgrade(); + let _ = prev_cancel_tx.send(()); + let item = item.clone(); + pending_autosave = + Some(cx.spawn_weak(|_, mut cx| async move { + let mut timer = cx + .background() + .timer(Duration::from_millis(milliseconds)) + .fuse(); + prev_autosave.await; + futures::select_biased! { + _ = cancel_rx => return None, + _ = timer => {} + } - if T::should_update_tab_on_event(event) { - pane.update(cx, |_, cx| { - cx.emit(pane::Event::ChangeItemTitle); - cx.notify(); - }); - } - - if T::is_edit_event(event) { - if let Autosave::AfterDelay { milliseconds } = - cx.global::().autosave - { - let prev_autosave = pending_autosave - .take() - .unwrap_or_else(|| Task::ready(Some(()))); - let (cancel_tx, mut cancel_rx) = oneshot::channel::<()>(); - let prev_cancel_tx = - mem::replace(&mut cancel_pending_autosave, cancel_tx); - let project = workspace.project.downgrade(); - let _ = prev_cancel_tx.send(()); - pending_autosave = Some(cx.spawn_weak(|_, mut cx| async move { - let mut timer = cx - .background() - .timer(Duration::from_millis(milliseconds)) - .fuse(); - prev_autosave.await; - futures::select_biased! { - _ = cancel_rx => return None, - _ = timer => {} + let project = project.upgrade(&cx)?; + cx.update(|cx| Pane::autosave_item(&item, project, cx)) + .await + .log_err(); + None + })); } - - let project = project.upgrade(&cx)?; - cx.update(|cx| Pane::autosave_item(&item, project, cx)) - .await - .log_err(); - None - })); + } + _ => {} } } })); @@ -749,6 +765,14 @@ impl ItemHandle for ViewHandle { fn as_searchable(&self, cx: &AppContext) -> Option> { self.read(cx).as_searchable(self) } + + fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation { + self.read(cx).breadcrumb_location() + } + + fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option> { + self.read(cx).breadcrumbs(theme) + } } impl From> for AnyViewHandle { @@ -3590,12 +3614,8 @@ mod tests { Task::ready(Ok(())) } - fn should_update_tab_on_event(_: &Self::Event) -> bool { - true - } - - fn is_edit_event(event: &Self::Event) -> bool { - matches!(event, TestItemEvent::Edit) + fn to_item_events(_: &Self::Event) -> Vec { + vec![ItemEvent::UpdateTab, ItemEvent::Edit] } } } From 31ecb2f7bc91c6fe529910e1a7d2aad67754eab8 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Tue, 6 Sep 2022 16:05:36 -0700 Subject: [PATCH 2/2] 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 --- crates/breadcrumbs/src/breadcrumbs.rs | 107 ++++-------------- crates/editor/src/items.rs | 63 ++++++++++- crates/search/src/buffer_search.rs | 4 +- crates/search/src/project_search.rs | 14 ++- crates/terminal/src/terminal.rs | 3 + .../terminal/src/terminal_container_view.rs | 28 ++++- crates/workspace/src/workspace.rs | 26 ++++- crates/zed/src/zed.rs | 3 +- 8 files changed, 149 insertions(+), 99 deletions(-) diff --git a/crates/breadcrumbs/src/breadcrumbs.rs b/crates/breadcrumbs/src/breadcrumbs.rs index 5698b7f990..85f0509caf 100644 --- a/crates/breadcrumbs/src/breadcrumbs.rs +++ b/crates/breadcrumbs/src/breadcrumbs.rs @@ -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, active_item: Option>, project_search: Option>, - subscriptions: Vec, + subscription: Option, } impl Breadcrumbs { - pub fn new(project: ModelHandle) -> 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, Vec>)> { - // 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) -> 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::().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, ) -> 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::(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::() { - 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 } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 15262472e4..fb6f12a16f 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -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 { + 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) -> Option> { Some(Box::new(handle.clone())) } + + fn breadcrumb_location(&self) -> ToolbarItemLocation { + ToolbarItemLocation::PrimaryLeft { flex: None } + } + + fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option> { + 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 { diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index c38f31ad91..c042e29e78 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -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( diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index b664675877..ca073bb10a 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -329,11 +329,23 @@ impl Item for ProjectSearchView { fn to_item_events(event: &Self::Event) -> Vec { 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> { + self.results_editor.breadcrumbs(theme, cx) + } } impl ProjectSearchView { diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 85a6fef7b6..d6c22ee6bc 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -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())) diff --git a/crates/terminal/src/terminal_container_view.rs b/crates/terminal/src/terminal_container_view.rs index ad2cb9ffdd..1aebd1f5e7 100644 --- a/crates/terminal/src/terminal_container_view.rs +++ b/crates/terminal/src/terminal_container_view.rs @@ -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 { + fn to_item_events(event: &Self::Event) -> Vec { 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> { + 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 { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e17aded2b3..9f6c7f1612 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -339,7 +339,7 @@ pub trait Item: View { fn breadcrumb_location(&self) -> ToolbarItemLocation { ToolbarItemLocation::Hidden } - fn breadcrumbs(&self, _theme: &Theme) -> Option> { + fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option> { None } } @@ -437,6 +437,11 @@ impl FollowableItemHandle for ViewHandle { } pub trait ItemHandle: 'static + fmt::Debug { + fn subscribe_to_item_events( + &self, + cx: &mut MutableAppContext, + handler: Box, + ) -> gpui::Subscription; fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option>; fn tab_content(&self, detail: Option, style: &theme::Tab, cx: &AppContext) -> ElementBox; @@ -476,8 +481,7 @@ pub trait ItemHandle: 'static + fmt::Debug { cx: &mut MutableAppContext, callback: Box, ) -> gpui::Subscription; - fn as_searchable(&self, cx: &AppContext) -> Option>; - + fn to_searchable_item_handle(&self, cx: &AppContext) -> Option>; fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation; fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option>; } @@ -500,6 +504,18 @@ impl dyn ItemHandle { } impl ItemHandle for ViewHandle { + fn subscribe_to_item_events( + &self, + cx: &mut MutableAppContext, + handler: Box, + ) -> 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> { self.read(cx).tab_description(detail, cx) } @@ -762,7 +778,7 @@ impl ItemHandle for ViewHandle { cx.observe_release(self, move |_, cx| callback(cx)) } - fn as_searchable(&self, cx: &AppContext) -> Option> { + fn to_searchable_item_handle(&self, cx: &AppContext) -> Option> { self.read(cx).as_searchable(self) } @@ -771,7 +787,7 @@ impl ItemHandle for ViewHandle { } fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option> { - self.read(cx).breadcrumbs(theme) + self.read(cx).breadcrumbs(theme, cx) } } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index e924812718..f64da9c1c8 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -225,12 +225,11 @@ pub fn initialize_workspace( cx: &mut ViewContext, ) { 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);