From 31ecb2f7bc91c6fe529910e1a7d2aad67754eab8 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Tue, 6 Sep 2022 16:05:36 -0700 Subject: [PATCH] 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);