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 <me@as-cii.com>
Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
Nathan Sobo 2022-03-31 10:36:39 -06:00
parent 0453dd1101
commit 8bfac63e0d
9 changed files with 207 additions and 125 deletions

1
Cargo.lock generated
View File

@ -727,6 +727,7 @@ dependencies = [
"editor", "editor",
"gpui", "gpui",
"language", "language",
"search",
"theme", "theme",
"workspace", "workspace",
] ]

View File

@ -12,6 +12,7 @@ collections = { path = "../collections" }
editor = { path = "../editor" } editor = { path = "../editor" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
language = { path = "../language" } language = { path = "../language" }
search = { path = "../search" }
theme = { path = "../theme" } theme = { path = "../theme" }
workspace = { path = "../workspace" } workspace = { path = "../workspace" }

View File

@ -3,9 +3,10 @@ use gpui::{
elements::*, AppContext, Entity, RenderContext, Subscription, View, ViewContext, ViewHandle, elements::*, AppContext, Entity, RenderContext, Subscription, View, ViewContext, ViewHandle,
}; };
use language::{BufferSnapshot, OutlineItem}; use language::{BufferSnapshot, OutlineItem};
use search::ProjectSearchView;
use std::borrow::Cow; use std::borrow::Cow;
use theme::SyntaxTheme; use theme::SyntaxTheme;
use workspace::{ItemHandle, Settings, ToolbarItemView}; use workspace::{ItemHandle, Settings, ToolbarItemLocation, ToolbarItemView};
pub struct Breadcrumbs { pub struct Breadcrumbs {
editor: Option<ViewHandle<Editor>>, editor: Option<ViewHandle<Editor>>,
@ -83,17 +84,29 @@ impl ToolbarItemView for Breadcrumbs {
&mut self, &mut self,
active_pane_item: Option<&dyn ItemHandle>, active_pane_item: Option<&dyn ItemHandle>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) -> ToolbarItemLocation {
cx.notify();
self.editor_subscription = None; self.editor_subscription = None;
self.editor = None; self.editor = None;
if let Some(editor) = active_pane_item.and_then(|i| i.act_as::<Editor>(cx)) { if let Some(item) = active_pane_item {
self.editor_subscription = Some(cx.subscribe(&editor, |_, _, event, cx| match event { if let Some(editor) = item.act_as::<Editor>(cx) {
self.editor_subscription =
Some(cx.subscribe(&editor, |_, _, event, cx| match event {
editor::Event::BufferEdited => cx.notify(), editor::Event::BufferEdited => cx.notify(),
editor::Event::SelectionsChanged { local } if *local => cx.notify(), editor::Event::SelectionsChanged { local } if *local => cx.notify(),
_ => {} _ => {}
})); }));
self.editor = Some(editor); self.editor = Some(editor);
if item.downcast::<ProjectSearchView>().is_some() {
ToolbarItemLocation::Secondary
} else {
ToolbarItemLocation::PrimaryLeft
}
} else {
ToolbarItemLocation::Hidden
}
} else {
ToolbarItemLocation::Hidden
} }
cx.notify();
} }
} }

View File

@ -8,13 +8,17 @@ use gpui::{
use language::OffsetRangeExt; use language::OffsetRangeExt;
use project::search::SearchQuery; use project::search::SearchQuery;
use std::ops::Range; use std::ops::Range;
use workspace::{ItemHandle, Pane, Settings, ToolbarItemView}; use workspace::{ItemHandle, Pane, Settings, ToolbarItemLocation, ToolbarItemView};
action!(Deploy, bool); action!(Deploy, bool);
action!(Dismiss); action!(Dismiss);
action!(FocusEditor); action!(FocusEditor);
action!(ToggleSearchOption, SearchOption); action!(ToggleSearchOption, SearchOption);
pub enum Event {
UpdateLocation,
}
pub fn init(cx: &mut MutableAppContext) { pub fn init(cx: &mut MutableAppContext) {
cx.add_bindings([ cx.add_bindings([
Binding::new("cmd-f", Deploy(true), Some("Editor && mode == full")), Binding::new("cmd-f", Deploy(true), Some("Editor && mode == full")),
@ -57,7 +61,7 @@ pub struct BufferSearchBar {
} }
impl Entity for BufferSearchBar { impl Entity for BufferSearchBar {
type Event = (); type Event = Event;
} }
impl View for BufferSearchBar { impl View for BufferSearchBar {
@ -70,9 +74,6 @@ impl View for BufferSearchBar {
} }
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox { fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
if self.dismissed || self.active_editor.is_none() {
Empty::new().boxed()
} else {
let theme = cx.global::<Settings>().theme.clone(); let theme = cx.global::<Settings>().theme.clone();
let editor_container = if self.query_contains_error { let editor_container = if self.query_contains_error {
theme.search.invalid_editor theme.search.invalid_editor
@ -115,11 +116,7 @@ impl View for BufferSearchBar {
) )
.with_child( .with_child(
Flex::row() Flex::row()
.with_child(self.render_search_option( .with_child(self.render_search_option("Case", SearchOption::CaseSensitive, cx))
"Case",
SearchOption::CaseSensitive,
cx,
))
.with_child(self.render_search_option("Word", SearchOption::WholeWord, cx)) .with_child(self.render_search_option("Word", SearchOption::WholeWord, cx))
.with_child(self.render_search_option("Regex", SearchOption::Regex, cx)) .with_child(self.render_search_option("Regex", SearchOption::Regex, cx))
.contained() .contained()
@ -130,10 +127,13 @@ impl View for BufferSearchBar {
.named("search bar") .named("search bar")
} }
} }
}
impl ToolbarItemView for BufferSearchBar { impl ToolbarItemView for BufferSearchBar {
fn set_active_pane_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) { fn set_active_pane_item(
&mut self,
item: Option<&dyn ItemHandle>,
cx: &mut ViewContext<Self>,
) -> ToolbarItemLocation {
cx.notify(); cx.notify();
self.active_editor_subscription.take(); self.active_editor_subscription.take();
self.active_editor.take(); self.active_editor.take();
@ -145,10 +145,22 @@ impl ToolbarItemView for BufferSearchBar {
Some(cx.subscribe(&editor, Self::on_active_editor_event)); Some(cx.subscribe(&editor, Self::on_active_editor_event));
self.active_editor = Some(editor); self.active_editor = Some(editor);
self.update_matches(false, cx); 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
}
}
} }
impl BufferSearchBar { impl BufferSearchBar {
@ -186,6 +198,7 @@ impl BufferSearchBar {
if let Some(active_editor) = self.active_editor.as_ref() { if let Some(active_editor) = self.active_editor.as_ref() {
cx.focus(active_editor); cx.focus(active_editor);
} }
cx.emit(Event::UpdateLocation);
cx.notify(); cx.notify();
} }
@ -234,6 +247,7 @@ impl BufferSearchBar {
self.dismissed = false; self.dismissed = false;
cx.notify(); cx.notify();
cx.emit(Event::UpdateLocation);
true true
} }

View File

@ -16,7 +16,9 @@ use std::{
path::PathBuf, path::PathBuf,
}; };
use util::ResultExt as _; use util::ResultExt as _;
use workspace::{Item, ItemNavHistory, Pane, Settings, ToolbarItemView, Workspace}; use workspace::{
Item, ItemNavHistory, Pane, Settings, ToolbarItemLocation, ToolbarItemView, Workspace,
};
action!(Deploy); action!(Deploy);
action!(Search); action!(Search);
@ -56,7 +58,7 @@ struct ProjectSearch {
active_query: Option<SearchQuery>, active_query: Option<SearchQuery>,
} }
struct ProjectSearchView { pub struct ProjectSearchView {
model: ModelHandle<ProjectSearch>, model: ModelHandle<ProjectSearch>,
query_editor: ViewHandle<Editor>, query_editor: ViewHandle<Editor>,
results_editor: ViewHandle<Editor>, results_editor: ViewHandle<Editor>,
@ -136,7 +138,7 @@ impl ProjectSearch {
} }
} }
enum ViewEvent { pub enum ViewEvent {
UpdateTab, UpdateTab,
} }
@ -748,14 +750,17 @@ impl ToolbarItemView for ProjectSearchBar {
&mut self, &mut self,
active_pane_item: Option<&dyn workspace::ItemHandle>, active_pane_item: Option<&dyn workspace::ItemHandle>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) -> ToolbarItemLocation {
cx.notify();
self.subscription = None; self.subscription = None;
self.active_project_search = None; self.active_project_search = None;
if let Some(search) = active_pane_item.and_then(|i| i.downcast::<ProjectSearchView>()) { if let Some(search) = active_pane_item.and_then(|i| i.downcast::<ProjectSearchView>()) {
self.subscription = Some(cx.observe(&search, |_, _, cx| cx.notify())); self.subscription = Some(cx.observe(&search, |_, _, cx| cx.notify()));
self.active_project_search = Some(search); self.active_project_search = Some(search);
ToolbarItemLocation::PrimaryLeft
} else {
ToolbarItemLocation::Hidden
} }
cx.notify();
} }
} }

View File

@ -1,7 +1,7 @@
pub use buffer_search::BufferSearchBar; pub use buffer_search::BufferSearchBar;
use editor::{Anchor, MultiBufferSnapshot}; use editor::{Anchor, MultiBufferSnapshot};
use gpui::{action, MutableAppContext}; use gpui::{action, MutableAppContext};
pub use project_search::ProjectSearchBar; pub use project_search::{ProjectSearchBar, ProjectSearchView};
use std::{ use std::{
cmp::{self, Ordering}, cmp::{self, Ordering},
ops::Range, ops::Range,

View File

@ -9,22 +9,38 @@ pub trait ToolbarItemView: View {
&mut self, &mut self,
active_pane_item: Option<&dyn crate::ItemHandle>, active_pane_item: Option<&dyn crate::ItemHandle>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
); ) -> ToolbarItemLocation;
fn location_for_event(
&self,
_event: &Self::Event,
current_location: ToolbarItemLocation,
) -> ToolbarItemLocation {
current_location
}
} }
trait ToolbarItemViewHandle { trait ToolbarItemViewHandle {
fn id(&self) -> usize;
fn to_any(&self) -> AnyViewHandle; fn to_any(&self) -> AnyViewHandle;
fn set_active_pane_item( fn set_active_pane_item(
&self, &self,
active_pane_item: Option<&dyn ItemHandle>, active_pane_item: Option<&dyn ItemHandle>,
cx: &mut MutableAppContext, cx: &mut MutableAppContext,
); ) -> ToolbarItemLocation;
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ToolbarItemLocation {
Hidden,
PrimaryLeft,
PrimaryRight,
Secondary,
} }
pub struct Toolbar { pub struct Toolbar {
active_pane_item: Option<Box<dyn ItemHandle>>, active_pane_item: Option<Box<dyn ItemHandle>>,
left_items: Vec<Box<dyn ToolbarItemViewHandle>>, items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
right_items: Vec<Box<dyn ToolbarItemViewHandle>>,
} }
impl Entity for Toolbar { impl Entity for Toolbar {
@ -38,15 +54,31 @@ impl View for Toolbar {
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox { fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
let theme = &cx.global::<Settings>().theme.workspace.toolbar; let theme = &cx.global::<Settings>().theme.workspace.toolbar;
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() Flex::row()
.with_children(self.left_items.iter().map(|i| { .with_children(primary_left_items.iter().map(|i| {
ChildView::new(i.as_ref()) ChildView::new(i.as_ref())
.aligned() .aligned()
.contained() .contained()
.with_margin_right(theme.item_spacing) .with_margin_right(theme.item_spacing)
.boxed() .boxed()
})) }))
.with_children(self.right_items.iter().map(|i| { .with_children(primary_right_items.iter().map(|i| {
ChildView::new(i.as_ref()) ChildView::new(i.as_ref())
.aligned() .aligned()
.contained() .contained()
@ -54,10 +86,18 @@ impl View for Toolbar {
.flex_float() .flex_float()
.boxed() .boxed()
})) }))
.contained()
.with_style(theme.container)
.constrained() .constrained()
.with_height(theme.height) .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)
.boxed() .boxed()
} }
} }
@ -66,49 +106,58 @@ impl Toolbar {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
active_pane_item: None, active_pane_item: None,
left_items: Default::default(), items: Default::default(),
right_items: Default::default(),
} }
} }
pub fn add_left_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>) pub fn add_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
where where
T: 'static + ToolbarItemView, T: 'static + ToolbarItemView,
{ {
item.set_active_pane_item(self.active_pane_item.as_deref(), cx); let location = item.set_active_pane_item(self.active_pane_item.as_deref(), cx);
self.left_items.push(Box::new(item)); 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(); cx.notify();
} }
}
pub fn add_right_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>) })
where .detach();
T: 'static + ToolbarItemView, self.items.push((Box::new(item), dbg!(location)));
{
item.set_active_pane_item(self.active_pane_item.as_deref(), cx);
self.right_items.push(Box::new(item));
cx.notify(); cx.notify();
} }
pub fn set_active_pane_item( pub fn set_active_pane_item(
&mut self, &mut self,
item: Option<&dyn ItemHandle>, pane_item: Option<&dyn ItemHandle>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
self.active_pane_item = item.map(|item| item.boxed_clone()); self.active_pane_item = pane_item.map(|item| item.boxed_clone());
for tool in self.left_items.iter().chain(&self.right_items) { for (toolbar_item, current_location) in self.items.iter_mut() {
tool.set_active_pane_item(item, cx); 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<T: ToolbarItemView>(&self) -> Option<ViewHandle<T>> { pub fn item_of_type<T: ToolbarItemView>(&self) -> Option<ViewHandle<T>> {
self.left_items self.items
.iter() .iter()
.chain(&self.right_items) .find_map(|(item, _)| item.to_any().downcast())
.find_map(|tool| tool.to_any().downcast())
} }
} }
impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> { impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
fn id(&self) -> usize {
self.id()
}
fn to_any(&self) -> AnyViewHandle { fn to_any(&self) -> AnyViewHandle {
self.into() self.into()
} }
@ -117,10 +166,10 @@ impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
&self, &self,
active_pane_item: Option<&dyn ItemHandle>, active_pane_item: Option<&dyn ItemHandle>,
cx: &mut MutableAppContext, cx: &mut MutableAppContext,
) { ) -> ToolbarItemLocation {
self.update(cx, |this, cx| { self.update(cx, |this, cx| {
this.set_active_pane_item(active_pane_item, cx) this.set_active_pane_item(active_pane_item, cx)
}); })
} }
} }

View File

@ -48,7 +48,7 @@ use std::{
}, },
}; };
use theme::{Theme, ThemeRegistry}; use theme::{Theme, ThemeRegistry};
pub use toolbar::ToolbarItemView; pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
use util::ResultExt; use util::ResultExt;
type ProjectItemBuilders = HashMap< type ProjectItemBuilders = HashMap<

View File

@ -111,12 +111,11 @@ pub fn build_workspace(
pane.update(cx, |pane, cx| { pane.update(cx, |pane, cx| {
pane.toolbar().update(cx, |toolbar, cx| { pane.toolbar().update(cx, |toolbar, cx| {
let breadcrumbs = cx.add_view(|_| Breadcrumbs::new()); 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)); 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()); let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
toolbar.add_right_item(project_search_bar, cx); toolbar.add_item(project_search_bar, cx);
}) })
}); });
}) })