From 8bfac63e0d4a7bd44d3799870cbb41bcf80f2859 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 31 Mar 2022 10:36:39 -0600 Subject: [PATCH] Render the search UI on a separate row from the breadcrumbs - In project search, render it above the breadcrumbs - In buffer search, render it below Co-Authored-By: Antonio Scandurra Co-Authored-By: Max Brunsfeld --- Cargo.lock | 1 + crates/breadcrumbs/Cargo.toml | 1 + crates/breadcrumbs/src/breadcrumbs.rs | 33 +++++-- crates/search/src/buffer_search.rs | 136 ++++++++++++++------------ crates/search/src/project_search.rs | 15 ++- crates/search/src/search.rs | 2 +- crates/workspace/src/toolbar.rs | 135 +++++++++++++++++-------- crates/workspace/src/workspace.rs | 2 +- crates/zed/src/zed.rs | 7 +- 9 files changed, 207 insertions(+), 125 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d119b987e5..3db82b3cb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -727,6 +727,7 @@ dependencies = [ "editor", "gpui", "language", + "search", "theme", "workspace", ] diff --git a/crates/breadcrumbs/Cargo.toml b/crates/breadcrumbs/Cargo.toml index 2e6e07ca19..2e74fd2090 100644 --- a/crates/breadcrumbs/Cargo.toml +++ b/crates/breadcrumbs/Cargo.toml @@ -12,6 +12,7 @@ collections = { path = "../collections" } editor = { path = "../editor" } gpui = { path = "../gpui" } language = { path = "../language" } +search = { path = "../search" } theme = { path = "../theme" } workspace = { path = "../workspace" } diff --git a/crates/breadcrumbs/src/breadcrumbs.rs b/crates/breadcrumbs/src/breadcrumbs.rs index 19042b3e40..5bbba7b973 100644 --- a/crates/breadcrumbs/src/breadcrumbs.rs +++ b/crates/breadcrumbs/src/breadcrumbs.rs @@ -3,9 +3,10 @@ use gpui::{ elements::*, AppContext, Entity, RenderContext, Subscription, View, ViewContext, ViewHandle, }; use language::{BufferSnapshot, OutlineItem}; +use search::ProjectSearchView; use std::borrow::Cow; use theme::SyntaxTheme; -use workspace::{ItemHandle, Settings, ToolbarItemView}; +use workspace::{ItemHandle, Settings, ToolbarItemLocation, ToolbarItemView}; pub struct Breadcrumbs { editor: Option>, @@ -83,17 +84,29 @@ impl ToolbarItemView for Breadcrumbs { &mut self, active_pane_item: Option<&dyn ItemHandle>, cx: &mut ViewContext, - ) { + ) -> ToolbarItemLocation { + cx.notify(); self.editor_subscription = None; self.editor = None; - if let Some(editor) = active_pane_item.and_then(|i| i.act_as::(cx)) { - self.editor_subscription = Some(cx.subscribe(&editor, |_, _, event, cx| match event { - editor::Event::BufferEdited => cx.notify(), - editor::Event::SelectionsChanged { local } if *local => cx.notify(), - _ => {} - })); - self.editor = Some(editor); + if let Some(item) = active_pane_item { + if let Some(editor) = item.act_as::(cx) { + self.editor_subscription = + Some(cx.subscribe(&editor, |_, _, event, cx| match event { + editor::Event::BufferEdited => cx.notify(), + editor::Event::SelectionsChanged { local } if *local => cx.notify(), + _ => {} + })); + self.editor = Some(editor); + if item.downcast::().is_some() { + ToolbarItemLocation::Secondary + } else { + ToolbarItemLocation::PrimaryLeft + } + } else { + ToolbarItemLocation::Hidden + } + } else { + ToolbarItemLocation::Hidden } - cx.notify(); } } diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 90d2876092..d7b5eec672 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -8,13 +8,17 @@ use gpui::{ use language::OffsetRangeExt; use project::search::SearchQuery; use std::ops::Range; -use workspace::{ItemHandle, Pane, Settings, ToolbarItemView}; +use workspace::{ItemHandle, Pane, Settings, ToolbarItemLocation, ToolbarItemView}; action!(Deploy, bool); action!(Dismiss); action!(FocusEditor); action!(ToggleSearchOption, SearchOption); +pub enum Event { + UpdateLocation, +} + pub fn init(cx: &mut MutableAppContext) { cx.add_bindings([ Binding::new("cmd-f", Deploy(true), Some("Editor && mode == full")), @@ -57,7 +61,7 @@ pub struct BufferSearchBar { } impl Entity for BufferSearchBar { - type Event = (); + type Event = Event; } impl View for BufferSearchBar { @@ -70,70 +74,66 @@ impl View for BufferSearchBar { } fn render(&mut self, cx: &mut RenderContext) -> ElementBox { - if self.dismissed || self.active_editor.is_none() { - Empty::new().boxed() + let theme = cx.global::().theme.clone(); + let editor_container = if self.query_contains_error { + theme.search.invalid_editor } else { - let theme = cx.global::().theme.clone(); - let editor_container = if self.query_contains_error { - theme.search.invalid_editor - } else { - theme.search.editor.input.container - }; - Flex::row() - .with_child( - Flex::row() - .with_child(ChildView::new(&self.query_editor).flex(1., true).boxed()) - .with_children(self.active_editor.as_ref().and_then(|editor| { - let matches = self.editors_with_matches.get(&editor.downgrade())?; - let message = if let Some(match_ix) = self.active_match_index { - format!("{}/{}", match_ix + 1, matches.len()) - } else { - "No matches".to_string() - }; + theme.search.editor.input.container + }; + Flex::row() + .with_child( + Flex::row() + .with_child(ChildView::new(&self.query_editor).flex(1., true).boxed()) + .with_children(self.active_editor.as_ref().and_then(|editor| { + let matches = self.editors_with_matches.get(&editor.downgrade())?; + let message = if let Some(match_ix) = self.active_match_index { + format!("{}/{}", match_ix + 1, matches.len()) + } else { + "No matches".to_string() + }; - Some( - Label::new(message, theme.search.match_index.text.clone()) - .contained() - .with_style(theme.search.match_index.container) - .aligned() - .boxed(), - ) - })) - .contained() - .with_style(editor_container) - .aligned() - .constrained() - .with_max_width(theme.search.editor.max_width) - .boxed(), - ) - .with_child( - Flex::row() - .with_child(self.render_nav_button("<", Direction::Prev, cx)) - .with_child(self.render_nav_button(">", Direction::Next, cx)) - .aligned() - .boxed(), - ) - .with_child( - Flex::row() - .with_child(self.render_search_option( - "Case", - SearchOption::CaseSensitive, - cx, - )) - .with_child(self.render_search_option("Word", SearchOption::WholeWord, cx)) - .with_child(self.render_search_option("Regex", SearchOption::Regex, cx)) - .contained() - .with_style(theme.search.option_button_group) - .aligned() - .boxed(), - ) - .named("search bar") - } + Some( + Label::new(message, theme.search.match_index.text.clone()) + .contained() + .with_style(theme.search.match_index.container) + .aligned() + .boxed(), + ) + })) + .contained() + .with_style(editor_container) + .aligned() + .constrained() + .with_max_width(theme.search.editor.max_width) + .boxed(), + ) + .with_child( + Flex::row() + .with_child(self.render_nav_button("<", Direction::Prev, cx)) + .with_child(self.render_nav_button(">", Direction::Next, cx)) + .aligned() + .boxed(), + ) + .with_child( + Flex::row() + .with_child(self.render_search_option("Case", SearchOption::CaseSensitive, cx)) + .with_child(self.render_search_option("Word", SearchOption::WholeWord, cx)) + .with_child(self.render_search_option("Regex", SearchOption::Regex, cx)) + .contained() + .with_style(theme.search.option_button_group) + .aligned() + .boxed(), + ) + .named("search bar") } } impl ToolbarItemView for BufferSearchBar { - fn set_active_pane_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext) { + fn set_active_pane_item( + &mut self, + item: Option<&dyn ItemHandle>, + cx: &mut ViewContext, + ) -> ToolbarItemLocation { cx.notify(); self.active_editor_subscription.take(); self.active_editor.take(); @@ -145,9 +145,21 @@ impl ToolbarItemView for BufferSearchBar { Some(cx.subscribe(&editor, Self::on_active_editor_event)); self.active_editor = Some(editor); self.update_matches(false, cx); - return; + if !self.dismissed { + return ToolbarItemLocation::Secondary; + } } } + + ToolbarItemLocation::Hidden + } + + fn location_for_event(&self, _: &Self::Event, _: ToolbarItemLocation) -> ToolbarItemLocation { + if self.active_editor.is_some() && !self.dismissed { + ToolbarItemLocation::Secondary + } else { + ToolbarItemLocation::Hidden + } } } @@ -186,6 +198,7 @@ impl BufferSearchBar { if let Some(active_editor) = self.active_editor.as_ref() { cx.focus(active_editor); } + cx.emit(Event::UpdateLocation); cx.notify(); } @@ -234,6 +247,7 @@ impl BufferSearchBar { self.dismissed = false; cx.notify(); + cx.emit(Event::UpdateLocation); true } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 009c67ba93..7026ac3473 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -16,7 +16,9 @@ use std::{ path::PathBuf, }; use util::ResultExt as _; -use workspace::{Item, ItemNavHistory, Pane, Settings, ToolbarItemView, Workspace}; +use workspace::{ + Item, ItemNavHistory, Pane, Settings, ToolbarItemLocation, ToolbarItemView, Workspace, +}; action!(Deploy); action!(Search); @@ -56,7 +58,7 @@ struct ProjectSearch { active_query: Option, } -struct ProjectSearchView { +pub struct ProjectSearchView { model: ModelHandle, query_editor: ViewHandle, results_editor: ViewHandle, @@ -136,7 +138,7 @@ impl ProjectSearch { } } -enum ViewEvent { +pub enum ViewEvent { UpdateTab, } @@ -748,14 +750,17 @@ impl ToolbarItemView for ProjectSearchBar { &mut self, active_pane_item: Option<&dyn workspace::ItemHandle>, cx: &mut ViewContext, - ) { + ) -> ToolbarItemLocation { + cx.notify(); self.subscription = None; self.active_project_search = None; if let Some(search) = active_pane_item.and_then(|i| i.downcast::()) { self.subscription = Some(cx.observe(&search, |_, _, cx| cx.notify())); self.active_project_search = Some(search); + ToolbarItemLocation::PrimaryLeft + } else { + ToolbarItemLocation::Hidden } - cx.notify(); } } diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index e1ef1357ce..38d3a5fce8 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -1,7 +1,7 @@ pub use buffer_search::BufferSearchBar; use editor::{Anchor, MultiBufferSnapshot}; use gpui::{action, MutableAppContext}; -pub use project_search::ProjectSearchBar; +pub use project_search::{ProjectSearchBar, ProjectSearchView}; use std::{ cmp::{self, Ordering}, ops::Range, diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index ed3370c315..6580818e0f 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -9,22 +9,38 @@ pub trait ToolbarItemView: View { &mut self, active_pane_item: Option<&dyn crate::ItemHandle>, cx: &mut ViewContext, - ); + ) -> ToolbarItemLocation; + + fn location_for_event( + &self, + _event: &Self::Event, + current_location: ToolbarItemLocation, + ) -> ToolbarItemLocation { + current_location + } } trait ToolbarItemViewHandle { + fn id(&self) -> usize; fn to_any(&self) -> AnyViewHandle; fn set_active_pane_item( &self, active_pane_item: Option<&dyn ItemHandle>, cx: &mut MutableAppContext, - ); + ) -> ToolbarItemLocation; +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ToolbarItemLocation { + Hidden, + PrimaryLeft, + PrimaryRight, + Secondary, } pub struct Toolbar { active_pane_item: Option>, - left_items: Vec>, - right_items: Vec>, + items: Vec<(Box, ToolbarItemLocation)>, } impl Entity for Toolbar { @@ -38,26 +54,50 @@ impl View for Toolbar { fn render(&mut self, cx: &mut RenderContext) -> ElementBox { let theme = &cx.global::().theme.workspace.toolbar; - Flex::row() - .with_children(self.left_items.iter().map(|i| { - ChildView::new(i.as_ref()) - .aligned() - .contained() - .with_margin_right(theme.item_spacing) - .boxed() - })) - .with_children(self.right_items.iter().map(|i| { - ChildView::new(i.as_ref()) - .aligned() - .contained() - .with_margin_left(theme.item_spacing) - .flex_float() + + let mut primary_left_items = Vec::new(); + let mut primary_right_items = Vec::new(); + let mut secondary_item = None; + + for (item, position) in &self.items { + match position { + ToolbarItemLocation::Hidden => {} + ToolbarItemLocation::PrimaryLeft => primary_left_items.push(item), + ToolbarItemLocation::PrimaryRight => primary_right_items.push(item), + ToolbarItemLocation::Secondary => secondary_item = Some(item), + } + } + + Flex::column() + .with_child( + Flex::row() + .with_children(primary_left_items.iter().map(|i| { + ChildView::new(i.as_ref()) + .aligned() + .contained() + .with_margin_right(theme.item_spacing) + .boxed() + })) + .with_children(primary_right_items.iter().map(|i| { + ChildView::new(i.as_ref()) + .aligned() + .contained() + .with_margin_left(theme.item_spacing) + .flex_float() + .boxed() + })) + .constrained() + .with_height(theme.height) + .boxed(), + ) + .with_children(secondary_item.map(|item| { + ChildView::new(item.as_ref()) + .constrained() + .with_height(theme.height) .boxed() })) .contained() .with_style(theme.container) - .constrained() - .with_height(theme.height) .boxed() } } @@ -66,49 +106,58 @@ impl Toolbar { pub fn new() -> Self { Self { active_pane_item: None, - left_items: Default::default(), - right_items: Default::default(), + items: Default::default(), } } - pub fn add_left_item(&mut self, item: ViewHandle, cx: &mut ViewContext) + pub fn add_item(&mut self, item: ViewHandle, cx: &mut ViewContext) where T: 'static + ToolbarItemView, { - item.set_active_pane_item(self.active_pane_item.as_deref(), cx); - self.left_items.push(Box::new(item)); - cx.notify(); - } - - pub fn add_right_item(&mut self, item: ViewHandle, cx: &mut ViewContext) - where - T: 'static + ToolbarItemView, - { - item.set_active_pane_item(self.active_pane_item.as_deref(), cx); - self.right_items.push(Box::new(item)); + let location = item.set_active_pane_item(self.active_pane_item.as_deref(), cx); + cx.subscribe(&item, |this, item, event, cx| { + if let Some((_, current_location)) = + this.items.iter_mut().find(|(i, _)| i.id() == item.id()) + { + let new_location = item.read(cx).location_for_event(event, *current_location); + if new_location != *current_location { + *current_location = new_location; + cx.notify(); + } + } + }) + .detach(); + self.items.push((Box::new(item), dbg!(location))); cx.notify(); } pub fn set_active_pane_item( &mut self, - item: Option<&dyn ItemHandle>, + pane_item: Option<&dyn ItemHandle>, cx: &mut ViewContext, ) { - self.active_pane_item = item.map(|item| item.boxed_clone()); - for tool in self.left_items.iter().chain(&self.right_items) { - tool.set_active_pane_item(item, cx); + self.active_pane_item = pane_item.map(|item| item.boxed_clone()); + for (toolbar_item, current_location) in self.items.iter_mut() { + let new_location = toolbar_item.set_active_pane_item(pane_item, cx); + if new_location != *current_location { + *current_location = new_location; + cx.notify(); + } } } pub fn item_of_type(&self) -> Option> { - self.left_items + self.items .iter() - .chain(&self.right_items) - .find_map(|tool| tool.to_any().downcast()) + .find_map(|(item, _)| item.to_any().downcast()) } } impl ToolbarItemViewHandle for ViewHandle { + fn id(&self) -> usize { + self.id() + } + fn to_any(&self) -> AnyViewHandle { self.into() } @@ -117,10 +166,10 @@ impl ToolbarItemViewHandle for ViewHandle { &self, active_pane_item: Option<&dyn ItemHandle>, cx: &mut MutableAppContext, - ) { + ) -> ToolbarItemLocation { self.update(cx, |this, cx| { this.set_active_pane_item(active_pane_item, cx) - }); + }) } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 2a6a3c6355..9929cd9a51 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -48,7 +48,7 @@ use std::{ }, }; use theme::{Theme, ThemeRegistry}; -pub use toolbar::ToolbarItemView; +pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; use util::ResultExt; type ProjectItemBuilders = HashMap< diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 84cf163b76..c94f8a0e81 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -111,12 +111,11 @@ pub fn build_workspace( pane.update(cx, |pane, cx| { pane.toolbar().update(cx, |toolbar, cx| { let breadcrumbs = cx.add_view(|_| Breadcrumbs::new()); - toolbar.add_left_item(breadcrumbs, cx); - + toolbar.add_item(breadcrumbs, cx); let buffer_search_bar = cx.add_view(|cx| BufferSearchBar::new(cx)); - toolbar.add_right_item(buffer_search_bar, cx); + toolbar.add_item(buffer_search_bar, cx); let project_search_bar = cx.add_view(|_| ProjectSearchBar::new()); - toolbar.add_right_item(project_search_bar, cx); + toolbar.add_item(project_search_bar, cx); }) }); })