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