From ab81093ef5e3af43b1284f9e30fafe5d2bf640d6 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Tue, 6 Sep 2022 14:39:58 -0700 Subject: [PATCH] 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] } } }