mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
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:
parent
0453dd1101
commit
8bfac63e0d
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -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()
|
||||
};
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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>)
|
||||
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)
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
})
|
||||
});
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user