diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 67aa4955bc..3521b1e849 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -422,89 +422,115 @@ impl ToolbarItemView for BufferSearchBar { } } -impl BufferSearchBar { - fn register(workspace: &mut Workspace) { - workspace.register_action(move |workspace, deploy: &Deploy, cx| { - let pane = workspace.active_pane(); +/// 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( + &mut self, + callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), + ); +} - pane.update(cx, |this, cx| { - this.toolbar().update(cx, |this, cx| { - if let Some(search_bar) = this.item_of_type::() { - 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(); - }) +impl BufferSearchBar { + 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( - workspace: &mut Workspace, - update: fn(&mut BufferSearchBar, &A, &mut ViewContext), - ) { - 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::() { - 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( + &mut self, + callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), + ) { + 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::() { + 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 { let query_editor = cx.new_view(|cx| Editor::single_line(cx)); diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 7118d6f767..f363c5228b 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -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 for TerminalPanel {} +struct ActionsRegistrar<'a, 'b> +where + 'b: 'a, +{ + div: Option
, + cx: &'a mut ViewContext<'b, TerminalPanel>, +} +impl SearchActionsRegistrar for ActionsRegistrar<'_, '_> { + fn register_handler( + &mut self, + callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), + ) { + 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::() + .map(|search_bar| search_bar.update(cx, |this, cx| callback(this, action, cx))); + })) + }); + } +} impl Render for TerminalPanel { - fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - div().size_full().child(self.pane.clone()) + fn render(&mut self, cx: &mut ViewContext) -> 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()) } }