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 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);