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 gpui::{
action, actions, div, red, Action, AppContext, Component, Div, EventEmitter,
ParentComponent as _, Render, Styled, Subscription, Task, View, ViewContext,
VisualContext as _, WindowContext,
InteractiveComponent, ParentComponent as _, Render, Styled, Subscription, Task, View,
ViewContext, VisualContext as _, WindowContext,
};
use project::search::SearchQuery;
use std::{any::Any, sync::Arc};
@ -38,7 +38,6 @@ pub enum Event {
}
pub fn init(cx: &mut AppContext) {
dbg!("Registered");
cx.observe_new_views(|workspace: &mut Workspace, _| BufferSearchBar::register(workspace))
.detach();
}
@ -78,45 +77,51 @@ impl Render for BufferSearchBar {
.map(|active_searchable_item| active_searchable_item.supported_options())
.unwrap_or_default();
// let previous_query_keystrokes =
// cx.binding_for_action(&PreviousHistoryQuery {})
// .map(|binding| {
// binding
// .keystrokes()
// .iter()
// .map(|k| k.to_string())
// .collect::<Vec<_>>()
// });
// let next_query_keystrokes = cx.binding_for_action(&NextHistoryQuery {}).map(|binding| {
// binding
// .keystrokes()
// .iter()
// .map(|k| k.to_string())
// .collect::<Vec<_>>()
// });
// let new_placeholder_text = match (previous_query_keystrokes, next_query_keystrokes) {
// (Some(previous_query_keystrokes), Some(next_query_keystrokes)) => {
// format!(
// "Search ({}/{} for previous/next query)",
// previous_query_keystrokes.join(" "),
// next_query_keystrokes.join(" ")
// )
// }
// (None, Some(next_query_keystrokes)) => {
// format!(
// "Search ({} for next query)",
// next_query_keystrokes.join(" ")
// )
// }
// (Some(previous_query_keystrokes), None) => {
// format!(
// "Search ({} for previous query)",
// previous_query_keystrokes.join(" ")
// )
// }
// (None, None) => String::new(),
// };
let new_placeholder_text = Arc::from("Search for..");
let previous_query_keystrokes = cx
.bindings_for_action(&PreviousHistoryQuery {})
.into_iter()
.next()
.map(|binding| {
binding
.keystrokes()
.iter()
.map(|k| k.to_string())
.collect::<Vec<_>>()
});
let next_query_keystrokes = cx
.bindings_for_action(&NextHistoryQuery {})
.into_iter()
.next()
.map(|binding| {
binding
.keystrokes()
.iter()
.map(|k| k.to_string())
.collect::<Vec<_>>()
});
let new_placeholder_text = match (previous_query_keystrokes, next_query_keystrokes) {
(Some(previous_query_keystrokes), Some(next_query_keystrokes)) => {
format!(
"Search ({}/{} for previous/next query)",
previous_query_keystrokes.join(" "),
next_query_keystrokes.join(" ")
)
}
(None, Some(next_query_keystrokes)) => {
format!(
"Search ({} for next query)",
next_query_keystrokes.join(" ")
)
}
(Some(previous_query_keystrokes), None) => {
format!(
"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| {
editor.set_placeholder_text(new_placeholder_text, cx);
});
@ -159,9 +164,9 @@ impl Render for BufferSearchBar {
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(
label,
icon,
direction,
self.active_match_index.is_some(),
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 replace_all = should_show_replace_input.then(|| {
super::replace_action::<Self>(ReplaceAll, "Replace all", ui::Icon::ReplaceAll)
});
let replace_all = should_show_replace_input
.then(|| super::render_replace_button::<Self>(ReplaceAll, ui::Icon::ReplaceAll));
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()
.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()
.p_1()
.child(
@ -226,9 +251,18 @@ impl Render for BufferSearchBar {
h_stack()
.gap_0p5()
.flex_none()
.child(self.render_action_button(cx))
.children(match_count)
.child(nav_button_for_direction("<", Direction::Prev, cx))
.child(nav_button_for_direction(">", Direction::Next, cx)),
.child(nav_button_for_direction(
ui::Icon::ChevronLeft,
Direction::Prev,
cx,
))
.child(nav_button_for_direction(
ui::Icon::ChevronRight,
Direction::Next,
cx,
)),
)
// let query_column = Flex::row()
@ -343,7 +377,7 @@ impl ToolbarItemView for BufferSearchBar {
cx.notify();
self.active_searchable_item_subscription.take();
self.active_searchable_item.take();
dbg!("Take?");
self.pending_search.take();
if let Some(searchable_item_handle) =
@ -382,7 +416,8 @@ impl BufferSearchBar {
workspace.register_action(|workspace, a: &Deploy, cx| {
workspace.active_pane().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;
}
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 {
dbg!("New");
let query_editor = cx.build_view(|cx| Editor::single_line(cx));
cx.subscribe(&query_editor, Self::on_query_editor_event)
.detach();
@ -463,7 +460,7 @@ impl BufferSearchBar {
pub fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
self.dismissed = true;
dbg!("Dismissed :(");
for searchable_item in self.searchable_items_with_matches.keys() {
if let Some(searchable_item) =
WeakSearchableItemHandle::upgrade(searchable_item.as_ref(), cx)
@ -495,10 +492,9 @@ impl BufferSearchBar {
pub fn show(&mut self, cx: &mut ViewContext<Self>) -> bool {
if self.active_searchable_item.is_none() {
dbg!("Hey");
return false;
}
dbg!("not dismissed");
self.dismissed = false;
cx.notify();
cx.emit(Event::UpdateLocation);
@ -590,13 +586,7 @@ impl BufferSearchBar {
self.update_matches(cx)
}
fn render_action_button(
&self,
icon: &'static str,
cx: &mut ViewContext<Self>,
) -> impl Component<Self> {
let tooltip = "Select All Matches";
let theme = cx.theme();
fn render_action_button(&self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
// let tooltip_style = theme.tooltip.clone();
// let style = theme.search.action_button.clone();
@ -695,8 +685,13 @@ impl BufferSearchBar {
.searchable_items_with_matches
.get(&searchable_item.downgrade())
{
let new_match_index = searchable_item
.match_index_for_direction(matches, index, direction, count, cx);
let new_match_index = searchable_item.match_index_for_direction(
matches,
index,
direction,
dbg!(count),
cx,
);
searchable_item.update_matches(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>) {
dbg!(&event);
match event {
SearchEvent::MatchesInvalidated => {
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>) {
let mut active_item_matches = None;
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 query = self.query(cx);
self.pending_search.take();
dbg!("update_matches");
if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
if query.is_empty() {
self.active_match_index.take();
@ -841,26 +843,23 @@ impl BufferSearchBar {
.into();
self.active_search = Some(query.clone());
let query_text = query.as_str().to_string();
dbg!(&query_text);
let matches = active_searchable_item.find_matches(query, cx);
let active_searchable_item = active_searchable_item.downgrade();
self.pending_search = Some(cx.spawn(|this, mut cx| async move {
let matches = matches.await;
//dbg!(&matches);
this.update(&mut cx, |this, cx| {
dbg!("Updating!!");
if let Some(active_searchable_item) =
WeakSearchableItemHandle::upgrade(active_searchable_item.as_ref(), cx)
{
dbg!("in if!!");
this.searchable_items_with_matches
.insert(active_searchable_item.downgrade(), matches);
this.update_match_index(cx);
this.search_history.add(query_text);
if !this.dismissed {
dbg!("Not dismissed");
let matches = this
.searchable_items_with_matches
.get(&active_searchable_item.downgrade())