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 | 113 ++++++++------- 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 | 130 ++++++++++-------- 10 files changed, 176 insertions(+), 159 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9cbdce80147ce61ea141278bbfe6c64f0d56206..634ef452a369b142c983302bcb471c00395e1a19 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 88fd614a893e0badaa5800a9c9eef6d75b4da374..e5cae74e8f61fc8177d1cc84b9a7463df5c61ddb 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 2d8089c1205709e63e2adcc42715ba4a48be4d61..5698b7f9904585f9b8aa87bfe6c0db3afb8dd9cd 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 (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(); - 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() - } + 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 { - "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() + 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 e14a44b05825f39656ca3ccd4f8b121ba8eb8753..271843dc69a5f572264ee02ab40c921d856759d8 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 3d412c423ec9ec38ddda520026cf75cc2c1b5c57..15262472e49cfa7a887babb2f2faedf67fd69a0f 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 22574b9b718f6cec0619aaaf14faabcb8db749f0..c38f31ad91d201ba0c216f1c8f78e93392bde96b 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 8caa7bf71de3a36b67c51b7e761e653236fb1ff2..b664675877e528934730f2505cacc8c9497db35a 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 baba4879804d46f968726d84bf32dabb601cdca0..ad2cb9ffdda6debfa06ae12cd8a2415e16bb17c2 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 f566d1136e58e2e658d176bd15919ffa5bee3189..cbe7364536281856da7947a1e2162e5024347683 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 c7a122e9db14f79322ba0ebc2842a7da21e10e3e..e17aded2b38590094d6e13085c4cd0598e1019ac 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; - } - - 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 => {} + 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 => {} + } + + 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 5698b7f9904585f9b8aa87bfe6c0db3afb8dd9cd..85f0509caf6f7f73e84312454a321d94d139edcd 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 15262472e49cfa7a887babb2f2faedf67fd69a0f..fb6f12a16f7335dcdc62d0809158faa14d656881 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 c38f31ad91d201ba0c216f1c8f78e93392bde96b..c042e29e78d478ff013f78bb3d4e0345dc3e98de 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 b664675877e528934730f2505cacc8c9497db35a..ca073bb10a0e5e40517ca781936082ede2574f25 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 85a6fef7b6239b3c7583f5aed398533806cc3fdc..d6c22ee6bce68013123028d17fe1e176f9229c66 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 ad2cb9ffdda6debfa06ae12cd8a2415e16bb17c2..1aebd1f5e7a06200594df31bd4bc2dd6fc42b356 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 e17aded2b38590094d6e13085c4cd0598e1019ac..9f6c7f16122a1b8dbd4121720dbceb010ad600f3 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 e9248127189f2fcae44fb322253a86a52739c218..f64da9c1c89d340147ffa4f6d2196447e74dfe3f 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);