WIP pull breadcrumb rendering out into item trait

This commit is contained in:
K Simmons 2022-09-06 14:39:58 -07:00
parent 1014d65e8e
commit ab81093ef5
10 changed files with 176 additions and 159 deletions

1
Cargo.lock generated
View file

@ -613,6 +613,7 @@ dependencies = [
"collections", "collections",
"editor", "editor",
"gpui", "gpui",
"itertools",
"language", "language",
"project", "project",
"search", "search",

View file

@ -17,6 +17,7 @@ search = { path = "../search" }
settings = { path = "../settings" } settings = { path = "../settings" }
theme = { path = "../theme" } theme = { path = "../theme" }
workspace = { path = "../workspace" } workspace = { path = "../workspace" }
itertools = "0.10"
[dev-dependencies] [dev-dependencies]
editor = { path = "../editor", features = ["test-support"] } editor = { path = "../editor", features = ["test-support"] }

View file

@ -1,13 +1,12 @@
use editor::{Anchor, Editor}; use editor::Editor;
use gpui::{ use gpui::{
elements::*, AppContext, Entity, ModelHandle, RenderContext, Subscription, View, ViewContext, elements::*, AppContext, Entity, ModelHandle, RenderContext, Subscription, View, ViewContext,
ViewHandle, ViewHandle,
}; };
use language::{Buffer, OutlineItem}; use itertools::Itertools;
use project::Project; use project::Project;
use search::ProjectSearchView; use search::ProjectSearchView;
use settings::Settings; use settings::Settings;
use theme::SyntaxTheme;
use workspace::{ItemHandle, ToolbarItemLocation, ToolbarItemView}; use workspace::{ItemHandle, ToolbarItemLocation, ToolbarItemView};
pub enum Event { pub enum Event {
@ -16,7 +15,7 @@ pub enum Event {
pub struct Breadcrumbs { pub struct Breadcrumbs {
project: ModelHandle<Project>, project: ModelHandle<Project>,
editor: Option<ViewHandle<Editor>>, active_item: Option<Box<dyn ItemHandle>>,
project_search: Option<ViewHandle<ProjectSearchView>>, project_search: Option<ViewHandle<ProjectSearchView>>,
subscriptions: Vec<Subscription>, subscriptions: Vec<Subscription>,
} }
@ -25,24 +24,23 @@ impl Breadcrumbs {
pub fn new(project: ModelHandle<Project>) -> Self { pub fn new(project: ModelHandle<Project>) -> Self {
Self { Self {
project, project,
editor: Default::default(), active_item: Default::default(),
subscriptions: Default::default(), subscriptions: Default::default(),
project_search: Default::default(), project_search: Default::default(),
} }
} }
// fn active_symbols(
fn active_symbols( // &self,
&self, // theme: &SyntaxTheme,
theme: &SyntaxTheme, // cx: &AppContext,
cx: &AppContext, // ) -> Option<(ModelHandle<Buffer>, Vec<OutlineItem<Anchor>>)> {
) -> Option<(ModelHandle<Buffer>, Vec<OutlineItem<Anchor>>)> { // let editor = self.active_item.as_ref()?.read(cx);
let editor = self.editor.as_ref()?.read(cx); // let cursor = editor.selections.newest_anchor().head();
let cursor = editor.selections.newest_anchor().head(); // let multibuffer = &editor.buffer().read(cx);
let multibuffer = &editor.buffer().read(cx); // let (buffer_id, symbols) = multibuffer.symbols_containing(cursor, Some(theme), cx)?;
let (buffer_id, symbols) = multibuffer.symbols_containing(cursor, Some(theme), cx)?; // let buffer = multibuffer.buffer(buffer_id)?;
let buffer = multibuffer.buffer(buffer_id)?; // Some((buffer, symbols))
Some((buffer, symbols)) // }
}
} }
impl Entity for Breadcrumbs { impl Entity for Breadcrumbs {
@ -55,41 +53,50 @@ impl View for Breadcrumbs {
} }
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox { fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
let theme = cx.global::<Settings>().theme.clone(); // let (buffer, symbols) =
let (buffer, symbols) = // if let Some((buffer, symbols)) = self.active_symbols(&theme.editor.syntax, cx) {
if let Some((buffer, symbols)) = self.active_symbols(&theme.editor.syntax, cx) { // (buffer, symbols)
(buffer, symbols) // } else {
} else { // return Empty::new().boxed();
return Empty::new().boxed(); // };
}; // let buffer = buffer.read(cx);
let buffer = buffer.read(cx); // let filename = if let Some(file) = buffer.file() {
let filename = if let Some(file) = buffer.file() { // if file.path().file_name().is_none()
if file.path().file_name().is_none() // || self.project.read(cx).visible_worktrees(cx).count() > 1
|| self.project.read(cx).visible_worktrees(cx).count() > 1 // {
{ // file.full_path(cx).to_string_lossy().to_string()
file.full_path(cx).to_string_lossy().to_string() // } else {
} else { // file.path().to_string_lossy().to_string()
file.path().to_string_lossy().to_string() // }
} // } else {
} else { // "untitled".to_string()
"untitled".to_string() // };
};
Flex::row() let theme = cx.global::<Settings>().theme.clone();
.with_child(Label::new(filename, theme.breadcrumbs.text.clone()).boxed()) if let Some(breadcrumbs) = self
.with_children(symbols.into_iter().flat_map(|symbol| { .active_item
[ .and_then(|item| item.breadcrumbs(&theme, cx))
Label::new("".to_string(), theme.breadcrumbs.text.clone()).boxed(), {
Text::new(symbol.text, theme.breadcrumbs.text.clone()) Flex::row()
.with_highlights(symbol.highlight_ranges) .with_children(Itertools::intersperse_with(breadcrumbs.into_iter(), || {
.boxed(), Label::new("".to_string(), theme.breadcrumbs.text.clone()).boxed()
] }))
})) // .with_child(Label::new(filename, theme.breadcrumbs.text.clone()).boxed())
.contained() // .with_children(symbols.into_iter().flat_map(|symbol| {
.with_style(theme.breadcrumbs.container) // [
.aligned() // Text::new(symbol.text, theme.breadcrumbs.text.clone())
.left() // .with_highlights(symbol.highlight_ranges)
.boxed() // .boxed(),
// ]
// }))
.contained()
.with_style(theme.breadcrumbs.container)
.aligned()
.left()
.boxed()
} else {
Empty::new().boxed()
}
} }
} }
@ -101,7 +108,7 @@ impl ToolbarItemView for Breadcrumbs {
) -> ToolbarItemLocation { ) -> ToolbarItemLocation {
cx.notify(); cx.notify();
self.subscriptions.clear(); self.subscriptions.clear();
self.editor = None; self.active_item = None;
self.project_search = None; self.project_search = None;
if let Some(item) = active_pane_item { if let Some(item) = active_pane_item {
if let Some(editor) = item.act_as::<Editor>(cx) { if let Some(editor) = item.act_as::<Editor>(cx) {
@ -114,7 +121,7 @@ impl ToolbarItemView for Breadcrumbs {
editor::Event::SelectionsChanged { local } if *local => cx.notify(), editor::Event::SelectionsChanged { local } if *local => cx.notify(),
_ => {} _ => {}
})); }));
self.editor = Some(editor); self.active_item = Some(editor);
if let Some(project_search) = item.downcast::<ProjectSearchView>() { if let Some(project_search) = item.downcast::<ProjectSearchView>() {
self.subscriptions self.subscriptions
.push(cx.subscribe(&project_search, |_, _, _, cx| { .push(cx.subscribe(&project_search, |_, _, _, cx| {

View file

@ -566,12 +566,8 @@ impl workspace::Item for ProjectDiagnosticsEditor {
unreachable!() unreachable!()
} }
fn should_update_tab_on_event(event: &Event) -> bool { fn to_item_events(event: &Self::Event) -> Vec<workspace::ItemEvent> {
Editor::should_update_tab_on_event(event) Editor::to_item_events(event)
}
fn is_edit_event(event: &Self::Event) -> bool {
Editor::is_edit_event(event)
} }
fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) { fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {

View file

@ -26,7 +26,7 @@ use text::{Point, Selection};
use util::TryFutureExt; use util::TryFutureExt;
use workspace::{ use workspace::{
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle}, 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); 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 { fn to_item_events(event: &Self::Event) -> Vec<workspace::ItemEvent> {
matches!(event, Event::Closed) match event {
} Event::Closed => vec![ItemEvent::CloseItem],
Event::Saved | Event::DirtyChanged | Event::TitleChanged => vec![ItemEvent::UpdateTab],
fn should_update_tab_on_event(event: &Event) -> bool { Event::BufferEdited => vec![ItemEvent::Edit],
matches!( _ => Vec::new(),
event, }
Event::Saved | Event::DirtyChanged | Event::TitleChanged
)
}
fn is_edit_event(event: &Self::Event) -> bool {
matches!(event, Event::BufferEdited)
} }
fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> { fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {

View file

@ -191,16 +191,17 @@ impl ToolbarItemView for BufferSearchBar {
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.as_searchable(cx)) {
let handle = cx.weak_handle(); let handle = cx.weak_handle();
self.active_searchable_item_subscription = Some(searchable_item_handle.subscribe( self.active_searchable_item_subscription =
cx, Some(searchable_item_handle.subscribe_to_search_events(
Box::new(move |search_event, cx| { cx,
if let Some(this) = handle.upgrade(cx) { Box::new(move |search_event, cx| {
this.update(cx, |this, cx| { if let Some(this) = handle.upgrade(cx) {
this.on_active_searchable_item_event(search_event, cx) this.update(cx, |this, cx| {
}); this.on_active_searchable_item_event(search_event, cx)
} });
}), }
)); }),
));
self.active_searchable_item = Some(searchable_item_handle); self.active_searchable_item = Some(searchable_item_handle);
self.update_matches(false, cx); self.update_matches(false, cx);

View file

@ -24,7 +24,8 @@ use std::{
use util::ResultExt as _; use util::ResultExt as _;
use workspace::{ use workspace::{
searchable::{Direction, SearchableItem, SearchableItemHandle}, searchable::{Direction, SearchableItem, SearchableItemHandle},
Item, ItemHandle, ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace, Item, ItemEvent, ItemHandle, ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView,
Workspace,
}; };
actions!(project_search, [SearchInNew, ToggleFocus]); actions!(project_search, [SearchInNew, ToggleFocus]);
@ -326,15 +327,11 @@ impl Item for ProjectSearchView {
.update(cx, |editor, cx| editor.navigate(data, cx)) .update(cx, |editor, cx| editor.navigate(data, cx))
} }
fn should_update_tab_on_event(event: &ViewEvent) -> bool { fn to_item_events(event: &Self::Event) -> Vec<ItemEvent> {
matches!(event, ViewEvent::UpdateTab) match event {
} ViewEvent::UpdateTab => vec![ItemEvent::UpdateTab],
ViewEvent::EditorEvent(editor_event) => Editor::to_item_events(editor_event),
fn is_edit_event(event: &Self::Event) -> bool { _ => Vec::new(),
if let ViewEvent::EditorEvent(editor_event) = event {
Editor::is_edit_event(editor_event)
} else {
false
} }
} }
} }

View file

@ -9,7 +9,7 @@ use gpui::{
}; };
use util::truncate_and_trailoff; use util::truncate_and_trailoff;
use workspace::searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle}; use workspace::searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle};
use workspace::{Item, Workspace}; use workspace::{Item, ItemEvent, Workspace};
use crate::TerminalSize; use crate::TerminalSize;
use project::{LocalWorktree, Project, ProjectPath}; use project::{LocalWorktree, Project, ProjectPath};
@ -359,17 +359,17 @@ impl Item for TerminalContainer {
false 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<Self>) -> Option<Box<dyn SearchableItemHandle>> { fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(handle.clone())) Some(Box::new(handle.clone()))
} }
fn to_item_events(event: &Self::Event) -> Vec<workspace::ItemEvent> {
match event {
Event::TitleChanged | Event::Wakeup => vec![ItemEvent::UpdateTab],
Event::CloseTerminal => vec![ItemEvent::CloseItem],
_ => vec![],
}
}
} }
impl SearchableItem for TerminalContainer { impl SearchableItem for TerminalContainer {

View file

@ -88,7 +88,7 @@ pub trait SearchableItemHandle: ItemHandle {
fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>; fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>; fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
fn supported_options(&self) -> SearchOptions; fn supported_options(&self) -> SearchOptions;
fn subscribe( fn subscribe_to_search_events(
&self, &self,
cx: &mut MutableAppContext, cx: &mut MutableAppContext,
handler: Box<dyn Fn(SearchEvent, &mut MutableAppContext)>, handler: Box<dyn Fn(SearchEvent, &mut MutableAppContext)>,
@ -134,7 +134,7 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
T::supported_options() T::supported_options()
} }
fn subscribe( fn subscribe_to_search_events(
&self, &self,
cx: &mut MutableAppContext, cx: &mut MutableAppContext,
handler: Box<dyn Fn(SearchEvent, &mut MutableAppContext)>, handler: Box<dyn Fn(SearchEvent, &mut MutableAppContext)>,

View file

@ -267,6 +267,14 @@ pub struct AppState {
pub initialize_workspace: fn(&mut Workspace, &Arc<AppState>, &mut ViewContext<Workspace>), pub initialize_workspace: fn(&mut Workspace, &Arc<AppState>, &mut ViewContext<Workspace>),
} }
#[derive(Eq, PartialEq, Hash)]
pub enum ItemEvent {
CloseItem,
UpdateTab,
UpdateBreadcrumbs,
Edit,
}
pub trait Item: View { pub trait Item: View {
fn deactivated(&mut self, _: &mut ViewContext<Self>) {} fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {} fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
@ -311,15 +319,7 @@ pub trait Item: View {
project: ModelHandle<Project>, project: ModelHandle<Project>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Task<Result<()>>; ) -> Task<Result<()>>;
fn should_close_item_on_event(_: &Self::Event) -> bool { fn to_item_events(event: &Self::Event) -> Vec<ItemEvent>;
false
}
fn should_update_tab_on_event(_: &Self::Event) -> bool {
false
}
fn is_edit_event(_: &Self::Event) -> bool {
false
}
fn act_as_type( fn act_as_type(
&self, &self,
type_id: TypeId, type_id: TypeId,
@ -335,6 +335,13 @@ pub trait Item: View {
fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> { fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
None None
} }
fn breadcrumb_location(&self) -> ToolbarItemLocation {
ToolbarItemLocation::Hidden
}
fn breadcrumbs(&self, _theme: &Theme) -> Option<Vec<ElementBox>> {
None
}
} }
pub trait ProjectItem: Item { pub trait ProjectItem: Item {
@ -470,6 +477,9 @@ pub trait ItemHandle: 'static + fmt::Debug {
callback: Box<dyn FnOnce(&mut MutableAppContext)>, callback: Box<dyn FnOnce(&mut MutableAppContext)>,
) -> gpui::Subscription; ) -> gpui::Subscription;
fn as_searchable(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>; fn as_searchable(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>>;
} }
pub trait WeakItemHandle { pub trait WeakItemHandle {
@ -605,47 +615,53 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
} }
} }
if T::should_close_item_on_event(event) { for item_event in T::to_item_events(event).into_iter() {
Pane::close_item(workspace, pane, item.id(), cx).detach_and_log_err(cx); match item_event {
return; 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::<Settings>().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) { let project = project.upgrade(&cx)?;
pane.update(cx, |_, cx| { cx.update(|cx| Pane::autosave_item(&item, project, cx))
cx.emit(pane::Event::ChangeItemTitle); .await
cx.notify(); .log_err();
}); None
} }));
if T::is_edit_event(event) {
if let Autosave::AfterDelay { milliseconds } =
cx.global::<Settings>().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
}));
} }
} }
})); }));
@ -749,6 +765,14 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
fn as_searchable(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> { fn as_searchable(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
self.read(cx).as_searchable(self) 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<Vec<ElementBox>> {
self.read(cx).breadcrumbs(theme)
}
} }
impl From<Box<dyn ItemHandle>> for AnyViewHandle { impl From<Box<dyn ItemHandle>> for AnyViewHandle {
@ -3590,12 +3614,8 @@ mod tests {
Task::ready(Ok(())) Task::ready(Ok(()))
} }
fn should_update_tab_on_event(_: &Self::Event) -> bool { fn to_item_events(_: &Self::Event) -> Vec<ItemEvent> {
true vec![ItemEvent::UpdateTab, ItemEvent::Edit]
}
fn is_edit_event(event: &Self::Event) -> bool {
matches!(event, TestItemEvent::Edit)
} }
} }
} }