Add query history and replace buttons

This commit is contained in:
Piotr Osiewicz 2023-11-15 15:05:48 +01:00
parent b11bfa8821
commit c37faf0ab3
3 changed files with 113 additions and 124 deletions

View file

@ -11,8 +11,8 @@ use editor::Editor;
use futures::channel::oneshot; use futures::channel::oneshot;
use gpui::{ use gpui::{
action, actions, div, red, Action, AppContext, Component, Div, EventEmitter, action, actions, div, red, Action, AppContext, Component, Div, EventEmitter,
ParentComponent as _, Render, Styled, Subscription, Task, View, ViewContext, InteractiveComponent, ParentComponent as _, Render, Styled, Subscription, Task, View,
VisualContext as _, WindowContext, ViewContext, VisualContext as _, WindowContext,
}; };
use project::search::SearchQuery; use project::search::SearchQuery;
use std::{any::Any, sync::Arc}; use std::{any::Any, sync::Arc};
@ -38,7 +38,6 @@ pub enum Event {
} }
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
dbg!("Registered");
cx.observe_new_views(|workspace: &mut Workspace, _| BufferSearchBar::register(workspace)) cx.observe_new_views(|workspace: &mut Workspace, _| BufferSearchBar::register(workspace))
.detach(); .detach();
} }
@ -78,45 +77,51 @@ impl Render for BufferSearchBar {
.map(|active_searchable_item| active_searchable_item.supported_options()) .map(|active_searchable_item| active_searchable_item.supported_options())
.unwrap_or_default(); .unwrap_or_default();
// let previous_query_keystrokes = let previous_query_keystrokes = cx
// cx.binding_for_action(&PreviousHistoryQuery {}) .bindings_for_action(&PreviousHistoryQuery {})
// .map(|binding| { .into_iter()
// binding .next()
// .keystrokes() .map(|binding| {
// .iter() binding
// .map(|k| k.to_string()) .keystrokes()
// .collect::<Vec<_>>() .iter()
// }); .map(|k| k.to_string())
// let next_query_keystrokes = cx.binding_for_action(&NextHistoryQuery {}).map(|binding| { .collect::<Vec<_>>()
// binding });
// .keystrokes() let next_query_keystrokes = cx
// .iter() .bindings_for_action(&NextHistoryQuery {})
// .map(|k| k.to_string()) .into_iter()
// .collect::<Vec<_>>() .next()
// }); .map(|binding| {
// let new_placeholder_text = match (previous_query_keystrokes, next_query_keystrokes) { binding
// (Some(previous_query_keystrokes), Some(next_query_keystrokes)) => { .keystrokes()
// format!( .iter()
// "Search ({}/{} for previous/next query)", .map(|k| k.to_string())
// previous_query_keystrokes.join(" "), .collect::<Vec<_>>()
// next_query_keystrokes.join(" ") });
// ) let new_placeholder_text = match (previous_query_keystrokes, next_query_keystrokes) {
// } (Some(previous_query_keystrokes), Some(next_query_keystrokes)) => {
// (None, Some(next_query_keystrokes)) => { format!(
// format!( "Search ({}/{} for previous/next query)",
// "Search ({} for next query)", previous_query_keystrokes.join(" "),
// next_query_keystrokes.join(" ") next_query_keystrokes.join(" ")
// ) )
// } }
// (Some(previous_query_keystrokes), None) => { (None, Some(next_query_keystrokes)) => {
// format!( format!(
// "Search ({} for previous query)", "Search ({} for next query)",
// previous_query_keystrokes.join(" ") next_query_keystrokes.join(" ")
// ) )
// } }
// (None, None) => String::new(), (Some(previous_query_keystrokes), None) => {
// }; format!(
let new_placeholder_text = Arc::from("Search for.."); "Search ({} for previous query)",
previous_query_keystrokes.join(" ")
)
}
(None, None) => String::new(),
};
let new_placeholder_text = Arc::from(new_placeholder_text);
self.query_editor.update(cx, |editor, cx| { self.query_editor.update(cx, |editor, cx| {
editor.set_placeholder_text(new_placeholder_text, cx); editor.set_placeholder_text(new_placeholder_text, cx);
}); });
@ -159,9 +164,9 @@ impl Render for BufferSearchBar {
Some(ui::Label::new(message)) Some(ui::Label::new(message))
}); });
let nav_button_for_direction = |label, direction, cx: &mut ViewContext<Self>| { let nav_button_for_direction = |icon, direction, cx: &mut ViewContext<Self>| {
render_nav_button( render_nav_button(
label, icon,
direction, direction,
self.active_match_index.is_some(), self.active_match_index.is_some(),
move |this, cx| match direction { move |this, cx| match direction {
@ -172,12 +177,32 @@ impl Render for BufferSearchBar {
) )
}; };
let should_show_replace_input = self.replace_enabled && supported_options.replacement; let should_show_replace_input = self.replace_enabled && supported_options.replacement;
let replace_all = should_show_replace_input.then(|| { let replace_all = should_show_replace_input
super::replace_action::<Self>(ReplaceAll, "Replace all", ui::Icon::ReplaceAll) .then(|| super::render_replace_button::<Self>(ReplaceAll, ui::Icon::ReplaceAll));
});
let replace_next = should_show_replace_input let replace_next = should_show_replace_input
.then(|| super::replace_action::<Self>(ReplaceNext, "Replace next", ui::Icon::Replace)); .then(|| super::render_replace_button::<Self>(ReplaceNext, ui::Icon::Replace));
let in_replace = self.replacement_editor.focus_handle(cx).is_focused(cx);
h_stack() h_stack()
.key_context("BufferSearchBar")
.when(in_replace, |this| {
this.key_context("in_replace")
.on_action(Self::replace_next)
.on_action(Self::replace_all)
})
.on_action(Self::previous_history_query)
.on_action(Self::next_history_query)
.when(supported_options.case, |this| {
this.on_action(Self::toggle_case_sensitive)
})
.when(supported_options.word, |this| {
this.on_action(Self::toggle_whole_word)
})
.when(supported_options.replacement, |this| {
this.on_action(Self::toggle_replace)
})
.on_action(Self::select_next_match)
.on_action(Self::select_prev_match)
.w_full() .w_full()
.p_1() .p_1()
.child( .child(
@ -226,9 +251,18 @@ impl Render for BufferSearchBar {
h_stack() h_stack()
.gap_0p5() .gap_0p5()
.flex_none() .flex_none()
.child(self.render_action_button(cx))
.children(match_count) .children(match_count)
.child(nav_button_for_direction("<", Direction::Prev, cx)) .child(nav_button_for_direction(
.child(nav_button_for_direction(">", Direction::Next, cx)), ui::Icon::ChevronLeft,
Direction::Prev,
cx,
))
.child(nav_button_for_direction(
ui::Icon::ChevronRight,
Direction::Next,
cx,
)),
) )
// let query_column = Flex::row() // let query_column = Flex::row()
@ -343,7 +377,7 @@ impl ToolbarItemView for BufferSearchBar {
cx.notify(); cx.notify();
self.active_searchable_item_subscription.take(); self.active_searchable_item_subscription.take();
self.active_searchable_item.take(); self.active_searchable_item.take();
dbg!("Take?");
self.pending_search.take(); self.pending_search.take();
if let Some(searchable_item_handle) = if let Some(searchable_item_handle) =
@ -382,7 +416,8 @@ impl BufferSearchBar {
workspace.register_action(|workspace, a: &Deploy, cx| { workspace.register_action(|workspace, a: &Deploy, cx| {
workspace.active_pane().update(cx, |this, cx| { workspace.active_pane().update(cx, |this, cx| {
this.toolbar().update(cx, |this, cx| { this.toolbar().update(cx, |this, cx| {
if this.item_of_type::<BufferSearchBar>().is_some() { if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
search_bar.update(cx, |this, cx| this.dismiss(&Dismiss, cx));
return; return;
} }
let view = cx.build_view(|cx| BufferSearchBar::new(cx)); let view = cx.build_view(|cx| BufferSearchBar::new(cx));
@ -392,46 +427,8 @@ impl BufferSearchBar {
}) })
}); });
}); });
fn register_action<A: Action>(
workspace: &mut Workspace,
update: fn(&mut BufferSearchBar, &mut ViewContext<'_, BufferSearchBar>),
) {
workspace.register_action(move |workspace, _: &A, cx| {
workspace.active_pane().update(cx, move |this, cx| {
this.toolbar().update(cx, move |toolbar, cx| {
let Some(search_bar) = toolbar.item_of_type::<BufferSearchBar>() else {
return;
};
search_bar.update(cx, |this, cx| update(this, cx))
})
});
});
}
register_action::<ToggleCaseSensitive>(workspace, |this, cx| {
this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx)
});
register_action::<ToggleWholeWord>(workspace, |this: &mut BufferSearchBar, cx| {
this.toggle_search_option(SearchOptions::WHOLE_WORD, cx)
});
register_action::<ToggleReplace>(workspace, |this: &mut BufferSearchBar, cx| {
dbg!("Toggling");
this.toggle_replace(&ToggleReplace, cx)
});
// workspace.register_action(|workspace, _: &ToggleCaseSensitive, cx| {
// workspace.active_pane().update(cx, |this, cx| {
// this.toolbar().update(cx, |toolbar, cx| {
// let Some(search_bar) = toolbar.item_of_type::<BufferSearchBar>() else {
// return;
// };
// search_bar.update(cx, |this, cx| {
// this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
// })
// })
// });
// });
} }
pub fn new(cx: &mut ViewContext<Self>) -> Self { pub fn new(cx: &mut ViewContext<Self>) -> Self {
dbg!("New");
let query_editor = cx.build_view(|cx| Editor::single_line(cx)); let query_editor = cx.build_view(|cx| Editor::single_line(cx));
cx.subscribe(&query_editor, Self::on_query_editor_event) cx.subscribe(&query_editor, Self::on_query_editor_event)
.detach(); .detach();
@ -463,7 +460,7 @@ impl BufferSearchBar {
pub fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) { pub fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
self.dismissed = true; self.dismissed = true;
dbg!("Dismissed :(");
for searchable_item in self.searchable_items_with_matches.keys() { for searchable_item in self.searchable_items_with_matches.keys() {
if let Some(searchable_item) = if let Some(searchable_item) =
WeakSearchableItemHandle::upgrade(searchable_item.as_ref(), cx) WeakSearchableItemHandle::upgrade(searchable_item.as_ref(), cx)
@ -495,10 +492,9 @@ impl BufferSearchBar {
pub fn show(&mut self, cx: &mut ViewContext<Self>) -> bool { pub fn show(&mut self, cx: &mut ViewContext<Self>) -> bool {
if self.active_searchable_item.is_none() { if self.active_searchable_item.is_none() {
dbg!("Hey");
return false; return false;
} }
dbg!("not dismissed");
self.dismissed = false; self.dismissed = false;
cx.notify(); cx.notify();
cx.emit(Event::UpdateLocation); cx.emit(Event::UpdateLocation);
@ -590,13 +586,7 @@ impl BufferSearchBar {
self.update_matches(cx) self.update_matches(cx)
} }
fn render_action_button( fn render_action_button(&self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
&self,
icon: &'static str,
cx: &mut ViewContext<Self>,
) -> impl Component<Self> {
let tooltip = "Select All Matches";
let theme = cx.theme();
// let tooltip_style = theme.tooltip.clone(); // let tooltip_style = theme.tooltip.clone();
// let style = theme.search.action_button.clone(); // let style = theme.search.action_button.clone();
@ -695,8 +685,13 @@ impl BufferSearchBar {
.searchable_items_with_matches .searchable_items_with_matches
.get(&searchable_item.downgrade()) .get(&searchable_item.downgrade())
{ {
let new_match_index = searchable_item let new_match_index = searchable_item.match_index_for_direction(
.match_index_for_direction(matches, index, direction, count, cx); matches,
index,
direction,
dbg!(count),
cx,
);
searchable_item.update_matches(matches, cx); searchable_item.update_matches(matches, cx);
searchable_item.activate_match(new_match_index, matches, cx); searchable_item.activate_match(new_match_index, matches, cx);
} }
@ -769,6 +764,7 @@ impl BufferSearchBar {
} }
fn on_active_searchable_item_event(&mut self, event: &SearchEvent, cx: &mut ViewContext<Self>) { fn on_active_searchable_item_event(&mut self, event: &SearchEvent, cx: &mut ViewContext<Self>) {
dbg!(&event);
match event { match event {
SearchEvent::MatchesInvalidated => { SearchEvent::MatchesInvalidated => {
let _ = self.update_matches(cx); let _ = self.update_matches(cx);
@ -777,6 +773,12 @@ impl BufferSearchBar {
} }
} }
fn toggle_case_sensitive(&mut self, _: &ToggleCaseSensitive, cx: &mut ViewContext<Self>) {
self.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx)
}
fn toggle_whole_word(&mut self, _: &ToggleWholeWord, cx: &mut ViewContext<Self>) {
self.toggle_search_option(SearchOptions::WHOLE_WORD, cx)
}
fn clear_matches(&mut self, cx: &mut ViewContext<Self>) { fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
let mut active_item_matches = None; let mut active_item_matches = None;
for (searchable_item, matches) in self.searchable_items_with_matches.drain() { for (searchable_item, matches) in self.searchable_items_with_matches.drain() {
@ -799,7 +801,7 @@ impl BufferSearchBar {
let (done_tx, done_rx) = oneshot::channel(); let (done_tx, done_rx) = oneshot::channel();
let query = self.query(cx); let query = self.query(cx);
self.pending_search.take(); self.pending_search.take();
dbg!("update_matches");
if let Some(active_searchable_item) = self.active_searchable_item.as_ref() { if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
if query.is_empty() { if query.is_empty() {
self.active_match_index.take(); self.active_match_index.take();
@ -841,26 +843,23 @@ impl BufferSearchBar {
.into(); .into();
self.active_search = Some(query.clone()); self.active_search = Some(query.clone());
let query_text = query.as_str().to_string(); let query_text = query.as_str().to_string();
dbg!(&query_text);
let matches = active_searchable_item.find_matches(query, cx); let matches = active_searchable_item.find_matches(query, cx);
let active_searchable_item = active_searchable_item.downgrade(); let active_searchable_item = active_searchable_item.downgrade();
self.pending_search = Some(cx.spawn(|this, mut cx| async move { self.pending_search = Some(cx.spawn(|this, mut cx| async move {
let matches = matches.await; let matches = matches.await;
//dbg!(&matches);
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
dbg!("Updating!!");
if let Some(active_searchable_item) = if let Some(active_searchable_item) =
WeakSearchableItemHandle::upgrade(active_searchable_item.as_ref(), cx) WeakSearchableItemHandle::upgrade(active_searchable_item.as_ref(), cx)
{ {
dbg!("in if!!");
this.searchable_items_with_matches this.searchable_items_with_matches
.insert(active_searchable_item.downgrade(), matches); .insert(active_searchable_item.downgrade(), matches);
this.update_match_index(cx); this.update_match_index(cx);
this.search_history.add(query_text); this.search_history.add(query_text);
if !this.dismissed { if !this.dismissed {
dbg!("Not dismissed");
let matches = this let matches = this
.searchable_items_with_matches .searchable_items_with_matches
.get(&active_searchable_item.downgrade()) .get(&active_searchable_item.downgrade())

View file

@ -1,6 +1,6 @@
use bitflags::bitflags; use bitflags::bitflags;
pub use buffer_search::BufferSearchBar; pub use buffer_search::BufferSearchBar;
use gpui::{actions, Action, AnyElement, AppContext, Component, Element, Svg, View}; use gpui::{actions, Action, AppContext, Component};
pub use mode::SearchMode; pub use mode::SearchMode;
use project::search::SearchQuery; use project::search::SearchQuery;
use ui::ButtonVariant; use ui::ButtonVariant;
@ -106,9 +106,8 @@ fn toggle_replace_button<V: 'static>(active: bool) -> impl Component<V> {
.when(active, |button| button.variant(ButtonVariant::Filled)) .when(active, |button| button.variant(ButtonVariant::Filled))
} }
fn replace_action<V: 'static>( fn render_replace_button<V: 'static>(
action: impl Action + 'static + Send + Sync, action: impl Action + 'static + Send + Sync,
name: &'static str,
icon: ui::Icon, icon: ui::Icon,
) -> impl Component<V> { ) -> impl Component<V> {
// todo: add tooltip // todo: add tooltip

View file

@ -1,28 +1,19 @@
use std::{borrow::Cow, sync::Arc}; use std::{borrow::Cow, sync::Arc};
use gpui::{div, Action, Component, ViewContext}; use gpui::{div, Action, Component, ViewContext};
use ui::{Button, ButtonVariant}; use ui::{Button, ButtonVariant, IconButton};
use workspace::searchable::Direction; use workspace::searchable::Direction;
use crate::mode::{SearchMode, Side}; use crate::mode::{SearchMode, Side};
pub(super) fn render_nav_button<V: 'static>( pub(super) fn render_nav_button<V: 'static>(
icon: &'static str, icon: ui::Icon,
direction: Direction, direction: Direction,
active: bool, active: bool,
on_click: impl Fn(&mut V, &mut ViewContext<V>) + 'static + Send + Sync, on_click: impl Fn(&mut V, &mut ViewContext<V>) + 'static + Send + Sync,
cx: &mut ViewContext<V>, cx: &mut ViewContext<V>,
) -> impl Component<V> { ) -> impl Component<V> {
let tooltip;
match direction {
Direction::Prev => {
tooltip = "Select Previous Match";
}
Direction::Next => {
tooltip = "Select Next Match";
}
};
// let tooltip_style = cx.theme().tooltip.clone(); // let tooltip_style = cx.theme().tooltip.clone();
// let cursor_style = if active { // let cursor_style = if active {
// CursorStyle::PointingHand // CursorStyle::PointingHand
@ -30,7 +21,7 @@ pub(super) fn render_nav_button<V: 'static>(
// CursorStyle::default() // CursorStyle::default()
// }; // };
// enum NavButton {} // enum NavButton {}
Button::new(icon).on_click(Arc::new(on_click)) IconButton::new("search-nav-button", icon).on_click(on_click)
// MouseEventHandler::new::<NavButton, _>(direction as usize, cx, |state, cx| { // MouseEventHandler::new::<NavButton, _>(direction as usize, cx, |state, cx| {
// let theme = cx.theme(); // let theme = cx.theme();
// let style = theme // let style = theme