@@ -422,89 +422,115 @@ impl ToolbarItemView for BufferSearchBar {
}
}
+/// Registrar inverts the dependency between search and it's downstream user, allowing said downstream user to register search action without knowing exactly what those actions are.
+pub trait SearchActionsRegistrar {
+ fn register_handler<A: Action>(
+ &mut self,
+ callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
+ );
+}
+
impl BufferSearchBar {
- fn register(workspace: &mut Workspace) {
- workspace.register_action(move |workspace, deploy: &Deploy, cx| {
- let pane = workspace.active_pane();
-
- pane.update(cx, |this, cx| {
- this.toolbar().update(cx, |this, cx| {
- if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
- search_bar.update(cx, |this, cx| {
- this.deploy(deploy, cx);
- });
- return;
- }
- let view = cx.new_view(|cx| BufferSearchBar::new(cx));
- this.add_item(view.clone(), cx);
- view.update(cx, |this, cx| this.deploy(deploy, cx));
- cx.notify();
- })
+ pub fn register_inner(
+ registrar: &mut impl SearchActionsRegistrar,
+ supported_options: &workspace::searchable::SearchOptions,
+ ) {
+ // supported_options controls whether the action is registered in the first place,
+ // but we still perform dynamic checks as e.g. if a view (like Workspace) uses SearchableItemHandle, they cannot know in advance
+ // whether a given option is supported.
+ if supported_options.case {
+ registrar.register_handler(|this, action: &ToggleCaseSensitive, cx| {
+ if this.supported_options().case {
+ this.toggle_case_sensitive(action, cx);
+ }
});
- });
- fn register_action<A: Action>(
- workspace: &mut Workspace,
- update: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
- ) {
- workspace.register_action(move |workspace, action: &A, cx| {
- let pane = workspace.active_pane();
- pane.update(cx, move |this, cx| {
- this.toolbar().update(cx, move |this, cx| {
- if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
- search_bar.update(cx, move |this, cx| update(this, action, cx));
- cx.notify();
- }
- })
- });
+ }
+ if supported_options.word {
+ registrar.register_handler(|this, action: &ToggleWholeWord, cx| {
+ if this.supported_options().word {
+ this.toggle_whole_word(action, cx);
+ }
});
}
- register_action(workspace, |this, action: &ToggleCaseSensitive, cx| {
- if this.supported_options().case {
- this.toggle_case_sensitive(action, cx);
- }
- });
- register_action(workspace, |this, action: &ToggleWholeWord, cx| {
- if this.supported_options().word {
- this.toggle_whole_word(action, cx);
- }
- });
- register_action(workspace, |this, action: &ToggleReplace, cx| {
- if this.supported_options().replacement {
- this.toggle_replace(action, cx);
- }
- });
- register_action(workspace, |this, _: &ActivateRegexMode, cx| {
- if this.supported_options().regex {
- this.activate_search_mode(SearchMode::Regex, cx);
- }
- });
- register_action(workspace, |this, _: &ActivateTextMode, cx| {
+ if supported_options.replacement {
+ registrar.register_handler(|this, action: &ToggleReplace, cx| {
+ if this.supported_options().replacement {
+ this.toggle_replace(action, cx);
+ }
+ });
+ }
+
+ if supported_options.regex {
+ registrar.register_handler(|this, _: &ActivateRegexMode, cx| {
+ if this.supported_options().regex {
+ this.activate_search_mode(SearchMode::Regex, cx);
+ }
+ });
+ }
+
+ registrar.register_handler(|this, _: &ActivateTextMode, cx| {
this.activate_search_mode(SearchMode::Text, cx);
});
- register_action(workspace, |this, action: &CycleMode, cx| {
- if this.supported_options().regex {
- // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting
- // cycling.
- this.cycle_mode(action, cx)
- }
- });
- register_action(workspace, |this, action: &SelectNextMatch, cx| {
+
+ if supported_options.regex {
+ registrar.register_handler(|this, action: &CycleMode, cx| {
+ if this.supported_options().regex {
+ // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting
+ // cycling.
+ this.cycle_mode(action, cx)
+ }
+ });
+ }
+
+ registrar.register_handler(|this, action: &SelectNextMatch, cx| {
this.select_next_match(action, cx);
});
- register_action(workspace, |this, action: &SelectPrevMatch, cx| {
+ registrar.register_handler(|this, action: &SelectPrevMatch, cx| {
this.select_prev_match(action, cx);
});
- register_action(workspace, |this, action: &SelectAllMatches, cx| {
+ registrar.register_handler(|this, action: &SelectAllMatches, cx| {
this.select_all_matches(action, cx);
});
- register_action(workspace, |this, _: &editor::Cancel, cx| {
+ registrar.register_handler(|this, _: &editor::Cancel, cx| {
if !this.dismissed {
this.dismiss(&Dismiss, cx);
return;
}
cx.propagate();
});
+ registrar.register_handler(|this, deploy, cx| {
+ this.deploy(deploy, cx);
+ })
+ }
+ fn register(workspace: &mut Workspace) {
+ impl SearchActionsRegistrar for Workspace {
+ fn register_handler<A: Action>(
+ &mut self,
+ callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
+ ) {
+ self.register_action(move |workspace, action: &A, cx| {
+ let pane = workspace.active_pane();
+ pane.update(cx, move |this, cx| {
+ this.toolbar().update(cx, move |this, cx| {
+ if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
+ search_bar.update(cx, move |this, cx| callback(this, action, cx));
+ cx.notify();
+ }
+ })
+ });
+ });
+ }
+ }
+ Self::register_inner(
+ workspace,
+ &workspace::searchable::SearchOptions {
+ case: true,
+ word: true,
+ regex: true,
+ replacement: true,
+ },
+ );
}
pub fn new(cx: &mut ViewContext<Self>) -> Self {
let query_editor = cx.new_view(|cx| Editor::single_line(cx));
@@ -3,11 +3,13 @@ use std::{path::PathBuf, sync::Arc};
use crate::TerminalView;
use db::kvp::KEY_VALUE_STORE;
use gpui::{
- actions, div, serde_json, AppContext, AsyncWindowContext, Entity, EventEmitter, ExternalPaths,
- FocusHandle, FocusableView, IntoElement, ParentElement, Pixels, Render, Styled, Subscription,
- Task, View, ViewContext, VisualContext, WeakView, WindowContext,
+ actions, div, serde_json, AppContext, AsyncWindowContext, Div, Entity, EventEmitter,
+ ExternalPaths, FocusHandle, FocusableView, InteractiveElement, IntoElement, ParentElement,
+ Pixels, Render, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
+ WindowContext,
};
use project::Fs;
+use search::{buffer_search::SearchActionsRegistrar, BufferSearchBar};
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
use terminal::terminal_settings::{TerminalDockPosition, TerminalSettings};
@@ -17,6 +19,7 @@ use workspace::{
dock::{DockPosition, Panel, PanelEvent},
item::Item,
pane,
+ searchable::SearchableItem,
ui::Icon,
Pane, Workspace,
};
@@ -328,9 +331,36 @@ impl TerminalPanel {
impl EventEmitter<PanelEvent> for TerminalPanel {}
+struct ActionsRegistrar<'a, 'b>
+where
+ 'b: 'a,
+{
+ div: Option<Div>,
+ cx: &'a mut ViewContext<'b, TerminalPanel>,
+}
+impl SearchActionsRegistrar for ActionsRegistrar<'_, '_> {
+ fn register_handler<A: gpui::Action>(
+ &mut self,
+ callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
+ ) {
+ self.div = self.div.take().map(|div| {
+ div.on_action(self.cx.listener(move |this, action, cx| {
+ this.pane
+ .read(cx)
+ .toolbar()
+ .read(cx)
+ .item_of_type::<BufferSearchBar>()
+ .map(|search_bar| search_bar.update(cx, |this, cx| callback(this, action, cx)));
+ }))
+ });
+ }
+}
impl Render for TerminalPanel {
- fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
- div().size_full().child(self.pane.clone())
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+ let div = div();
+ let mut registrar = ActionsRegistrar { div: Some(div), cx };
+ BufferSearchBar::register_inner(&mut registrar, &TerminalView::supported_options());
+ registrar.div.unwrap().size_full().child(self.pane.clone())
}
}