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",
"gpui",
"language",
"search",
"theme",
"workspace",
]

View File

@ -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" }

View File

@ -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<ViewHandle<Editor>>,
@ -83,17 +84,29 @@ impl ToolbarItemView for Breadcrumbs {
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
cx: &mut ViewContext<Self>,
) {
) -> ToolbarItemLocation {
cx.notify();
self.editor_subscription = None;
self.editor = None;
if let Some(editor) = active_pane_item.and_then(|i| i.act_as::<Editor>(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::<Editor>(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::<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 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<Self>) -> ElementBox {
if self.dismissed || self.active_editor.is_none() {
Empty::new().boxed()
let theme = cx.global::<Settings>().theme.clone();
let editor_container = if self.query_contains_error {
theme.search.invalid_editor
} else {
let theme = cx.global::<Settings>().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<Self>) {
fn set_active_pane_item(
&mut self,
item: Option<&dyn ItemHandle>,
cx: &mut ViewContext<Self>,
) -> 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
}

View File

@ -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<SearchQuery>,
}
struct ProjectSearchView {
pub struct ProjectSearchView {
model: ModelHandle<ProjectSearch>,
query_editor: ViewHandle<Editor>,
results_editor: ViewHandle<Editor>,
@ -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<Self>,
) {
) -> ToolbarItemLocation {
cx.notify();
self.subscription = None;
self.active_project_search = None;
if let Some(search) = active_pane_item.and_then(|i| i.downcast::<ProjectSearchView>()) {
self.subscription = Some(cx.observe(&search, |_, _, cx| cx.notify()));
self.active_project_search = Some(search);
ToolbarItemLocation::PrimaryLeft
} else {
ToolbarItemLocation::Hidden
}
cx.notify();
}
}

View File

@ -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,

View File

@ -9,22 +9,38 @@ pub trait ToolbarItemView: View {
&mut self,
active_pane_item: Option<&dyn crate::ItemHandle>,
cx: &mut ViewContext<Self>,
);
) -> 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<Box<dyn ItemHandle>>,
left_items: Vec<Box<dyn ToolbarItemViewHandle>>,
right_items: Vec<Box<dyn ToolbarItemViewHandle>>,
items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
}
impl Entity for Toolbar {
@ -38,26 +54,50 @@ impl View for Toolbar {
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
let theme = &cx.global::<Settings>().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<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
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<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
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>,
) {
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<T: ToolbarItemView>(&self) -> Option<ViewHandle<T>> {
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<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
fn id(&self) -> usize {
self.id()
}
fn to_any(&self) -> AnyViewHandle {
self.into()
}
@ -117,10 +166,10 @@ impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
&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)
});
})
}
}

View File

@ -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<

View File

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