Cargo.lock 🔗
@@ -727,6 +727,7 @@ dependencies = [
"editor",
"gpui",
"language",
+ "search",
"theme",
"workspace",
]
Nathan Sobo , Antonio Scandurra , and Max Brunsfeld created
- 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>
Cargo.lock | 1
crates/breadcrumbs/Cargo.toml | 1
crates/breadcrumbs/src/breadcrumbs.rs | 33 ++++--
crates/search/src/buffer_search.rs | 138 +++++++++++++++-------------
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, 208 insertions(+), 126 deletions(-)
@@ -727,6 +727,7 @@ dependencies = [
"editor",
"gpui",
"language",
+ "search",
"theme",
"workspace",
]
@@ -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" }
@@ -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();
}
}
@@ -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()
- };
-
- 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")
- }
+ 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")
}
}
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
}
@@ -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();
}
}
@@ -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,
@@ -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>)
- 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>)
+ 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.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)
- });
+ })
}
}
@@ -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<
@@ -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);
})
});
})