diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index ccef198f04..da2d35d74c 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1,12 +1,10 @@ mod registrar; use crate::{ - FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions, - SelectAllMatches, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleRegex, - ToggleReplace, ToggleSelection, ToggleWholeWord, - search_bar::{ - input_base_styles, render_action_button, render_text_input, toggle_replace_button, - }, + FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOption, + SearchOptions, SelectAllMatches, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, + ToggleRegex, ToggleReplace, ToggleSelection, ToggleWholeWord, + search_bar::{input_base_styles, render_action_button, render_text_input}, }; use any_vec::AnyVec; use anyhow::Context as _; @@ -215,31 +213,22 @@ impl Render for BufferSearchBar { h_flex() .gap_1() .when(case, |div| { - div.child(SearchOptions::CASE_SENSITIVE.as_button( - self.search_options.contains(SearchOptions::CASE_SENSITIVE), - focus_handle.clone(), - cx.listener(|this, _, window, cx| { - this.toggle_case_sensitive(&ToggleCaseSensitive, window, cx) - }), - )) + div.child( + SearchOption::CaseSensitive + .as_button(self.search_options, focus_handle.clone()), + ) }) .when(word, |div| { - div.child(SearchOptions::WHOLE_WORD.as_button( - self.search_options.contains(SearchOptions::WHOLE_WORD), - focus_handle.clone(), - cx.listener(|this, _, window, cx| { - this.toggle_whole_word(&ToggleWholeWord, window, cx) - }), - )) + div.child( + SearchOption::WholeWord + .as_button(self.search_options, focus_handle.clone()), + ) }) .when(regex, |div| { - div.child(SearchOptions::REGEX.as_button( - self.search_options.contains(SearchOptions::REGEX), - focus_handle.clone(), - cx.listener(|this, _, window, cx| { - this.toggle_regex(&ToggleRegex, window, cx) - }), - )) + div.child( + SearchOption::Regex + .as_button(self.search_options, focus_handle.clone()), + ) }), ) }); @@ -248,13 +237,13 @@ impl Render for BufferSearchBar { .gap_1() .min_w_64() .when(replacement, |this| { - this.child(toggle_replace_button( - "buffer-search-bar-toggle-replace-button", - focus_handle.clone(), + this.child(render_action_button( + "buffer-search-bar-toggle", + IconName::Replace, self.replace_enabled, - cx.listener(|this, _: &ClickEvent, window, cx| { - this.toggle_replace(&ToggleReplace, window, cx); - }), + "Toggle Replace", + &ToggleReplace, + focus_handle.clone(), )) }) .when(selection, |this| { diff --git a/crates/search/src/mode.rs b/crates/search/src/mode.rs deleted file mode 100644 index 957eb707a5..0000000000 --- a/crates/search/src/mode.rs +++ /dev/null @@ -1,36 +0,0 @@ -use gpui::{Action, SharedString}; - -use crate::{ActivateRegexMode, ActivateTextMode}; - -// TODO: Update the default search mode to get from config -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub enum SearchMode { - #[default] - Text, - Regex, -} - -impl SearchMode { - pub(crate) fn label(&self) -> &'static str { - match self { - SearchMode::Text => "Text", - SearchMode::Regex => "Regex", - } - } - pub(crate) fn tooltip(&self) -> SharedString { - format!("Activate {} Mode", self.label()).into() - } - pub(crate) fn action(&self) -> Box { - match self { - SearchMode::Text => ActivateTextMode.boxed_clone(), - SearchMode::Regex => ActivateRegexMode.boxed_clone(), - } - } -} - -pub(crate) fn next_mode(mode: &SearchMode) -> SearchMode { - match mode { - SearchMode::Text => SearchMode::Regex, - SearchMode::Regex => SearchMode::Text, - } -} diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 9e8afa4392..6b9777906a 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1,11 +1,9 @@ use crate::{ BufferSearchBar, FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, - SearchOptions, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleIncludeIgnored, - ToggleRegex, ToggleReplace, ToggleWholeWord, + SearchOption, SearchOptions, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, + ToggleIncludeIgnored, ToggleRegex, ToggleReplace, ToggleWholeWord, buffer_search::Deploy, - search_bar::{ - input_base_styles, render_action_button, render_text_input, toggle_replace_button, - }, + search_bar::{input_base_styles, render_action_button, render_text_input}, }; use anyhow::Context as _; use collections::HashMap; @@ -1784,14 +1782,6 @@ impl ProjectSearchBar { } } - fn is_option_enabled(&self, option: SearchOptions, cx: &App) -> bool { - if let Some(search) = self.active_project_search.as_ref() { - search.read(cx).search_options.contains(option) - } else { - false - } - } - fn next_history_query( &mut self, _: &NextHistoryQuery, @@ -1972,27 +1962,17 @@ impl Render for ProjectSearchBar { .child( h_flex() .gap_1() - .child(SearchOptions::CASE_SENSITIVE.as_button( - self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx), - focus_handle.clone(), - cx.listener(|this, _, window, cx| { - this.toggle_search_option(SearchOptions::CASE_SENSITIVE, window, cx); - }), - )) - .child(SearchOptions::WHOLE_WORD.as_button( - self.is_option_enabled(SearchOptions::WHOLE_WORD, cx), - focus_handle.clone(), - cx.listener(|this, _, window, cx| { - this.toggle_search_option(SearchOptions::WHOLE_WORD, window, cx); - }), - )) - .child(SearchOptions::REGEX.as_button( - self.is_option_enabled(SearchOptions::REGEX, cx), - focus_handle.clone(), - cx.listener(|this, _, window, cx| { - this.toggle_search_option(SearchOptions::REGEX, window, cx); - }), - )), + .child( + SearchOption::CaseSensitive + .as_button(search.search_options, focus_handle.clone()), + ) + .child( + SearchOption::WholeWord + .as_button(search.search_options, focus_handle.clone()), + ) + .child( + SearchOption::Regex.as_button(search.search_options, focus_handle.clone()), + ), ); let mode_column = h_flex() @@ -2026,16 +2006,16 @@ impl Render for ProjectSearchBar { } }), ) - .child(toggle_replace_button( - "project-search-toggle-replace", - focus_handle.clone(), + .child(render_action_button( + "project-search", + IconName::Replace, self.active_project_search .as_ref() .map(|search| search.read(cx).replace_enabled) .unwrap_or_default(), - cx.listener(|this, _, window, cx| { - this.toggle_replace(&ToggleReplace, window, cx); - }), + "Toggle Replace", + &ToggleReplace, + focus_handle.clone(), )); let query_focus = search.query_editor.focus_handle(cx); @@ -2149,15 +2129,8 @@ impl Render for ProjectSearchBar { })), ) .child( - SearchOptions::INCLUDE_IGNORED.as_button( - search - .search_options - .contains(SearchOptions::INCLUDE_IGNORED), - focus_handle.clone(), - cx.listener(|this, _, window, cx| { - this.toggle_search_option(SearchOptions::INCLUDE_IGNORED, window, cx); - }), - ), + SearchOption::IncludeIgnored + .as_button(search.search_options, focus_handle.clone()), ); h_flex() .w_full() diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index 5f57bfb4b1..89064e0a27 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -9,6 +9,8 @@ use ui::{Tooltip, prelude::*}; use workspace::notifications::NotificationId; use workspace::{Toast, Workspace}; +pub use search_status_button::SEARCH_ICON; + pub mod buffer_search; pub mod project_search; pub(crate) mod search_bar; @@ -59,48 +61,87 @@ actions!( bitflags! { #[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] pub struct SearchOptions: u8 { - const NONE = 0b000; - const WHOLE_WORD = 0b001; - const CASE_SENSITIVE = 0b010; - const INCLUDE_IGNORED = 0b100; - const REGEX = 0b1000; - const ONE_MATCH_PER_LINE = 0b100000; + const NONE = 0; + const WHOLE_WORD = 1 << SearchOption::WholeWord as u8; + const CASE_SENSITIVE = 1 << SearchOption::CaseSensitive as u8; + const INCLUDE_IGNORED = 1 << SearchOption::IncludeIgnored as u8; + const REGEX = 1 << SearchOption::Regex as u8; + const ONE_MATCH_PER_LINE = 1 << SearchOption::OneMatchPerLine as u8; /// If set, reverse direction when finding the active match - const BACKWARDS = 0b10000; + const BACKWARDS = 1 << SearchOption::Backwards as u8; } } -impl SearchOptions { +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum SearchOption { + WholeWord = 0, + CaseSensitive, + IncludeIgnored, + Regex, + OneMatchPerLine, + Backwards, +} + +impl SearchOption { + pub fn as_options(self) -> SearchOptions { + SearchOptions::from_bits(1 << self as u8).unwrap() + } + pub fn label(&self) -> &'static str { - match *self { - SearchOptions::WHOLE_WORD => "Match Whole Words", - SearchOptions::CASE_SENSITIVE => "Match Case Sensitively", - SearchOptions::INCLUDE_IGNORED => "Also search files ignored by configuration", - SearchOptions::REGEX => "Use Regular Expressions", - _ => panic!("{:?} is not a named SearchOption", self), + match self { + SearchOption::WholeWord => "Match Whole Words", + SearchOption::CaseSensitive => "Match Case Sensitively", + SearchOption::IncludeIgnored => "Also search files ignored by configuration", + SearchOption::Regex => "Use Regular Expressions", + SearchOption::OneMatchPerLine => "One Match Per Line", + SearchOption::Backwards => "Search Backwards", } } pub fn icon(&self) -> ui::IconName { - match *self { - SearchOptions::WHOLE_WORD => ui::IconName::WholeWord, - SearchOptions::CASE_SENSITIVE => ui::IconName::CaseSensitive, - SearchOptions::INCLUDE_IGNORED => ui::IconName::Sliders, - SearchOptions::REGEX => ui::IconName::Regex, - _ => panic!("{:?} is not a named SearchOption", self), + match self { + SearchOption::WholeWord => ui::IconName::WholeWord, + SearchOption::CaseSensitive => ui::IconName::CaseSensitive, + SearchOption::IncludeIgnored => ui::IconName::Sliders, + SearchOption::Regex => ui::IconName::Regex, + _ => panic!("{self:?} is not a named SearchOption"), } } - pub fn to_toggle_action(&self) -> Box { + pub fn to_toggle_action(&self) -> &'static dyn Action { match *self { - SearchOptions::WHOLE_WORD => Box::new(ToggleWholeWord), - SearchOptions::CASE_SENSITIVE => Box::new(ToggleCaseSensitive), - SearchOptions::INCLUDE_IGNORED => Box::new(ToggleIncludeIgnored), - SearchOptions::REGEX => Box::new(ToggleRegex), - _ => panic!("{:?} is not a named SearchOption", self), + SearchOption::WholeWord => &ToggleWholeWord, + SearchOption::CaseSensitive => &ToggleCaseSensitive, + SearchOption::IncludeIgnored => &ToggleIncludeIgnored, + SearchOption::Regex => &ToggleRegex, + _ => panic!("{self:?} is not a toggle action"), } } + pub fn as_button(&self, active: SearchOptions, focus_handle: FocusHandle) -> impl IntoElement { + let action = self.to_toggle_action(); + let label = self.label(); + IconButton::new(label, self.icon()) + .on_click({ + let focus_handle = focus_handle.clone(); + move |_, window, cx| { + if !focus_handle.is_focused(&window) { + window.focus(&focus_handle); + } + window.dispatch_action(action.boxed_clone(), cx) + } + }) + .style(ButtonStyle::Subtle) + .shape(IconButtonShape::Square) + .toggle_state(active.contains(self.as_options())) + .tooltip({ + move |window, cx| Tooltip::for_action_in(label, action, &focus_handle, window, cx) + }) + } +} + +impl SearchOptions { pub fn none() -> SearchOptions { SearchOptions::NONE } @@ -122,24 +163,6 @@ impl SearchOptions { options.set(SearchOptions::REGEX, settings.regex); options } - - pub fn as_button( - &self, - active: bool, - focus_handle: FocusHandle, - action: Action, - ) -> impl IntoElement + use { - IconButton::new(self.label(), self.icon()) - .on_click(action) - .style(ButtonStyle::Subtle) - .shape(IconButtonShape::Square) - .toggle_state(active) - .tooltip({ - let action = self.to_toggle_action(); - let label = self.label(); - move |window, cx| Tooltip::for_action_in(label, &*action, &focus_handle, window, cx) - }) - } } pub(crate) fn show_no_more_matches(window: &mut Window, cx: &mut App) { diff --git a/crates/search/src/search_bar.rs b/crates/search/src/search_bar.rs index 2805b0c62d..094ce3638e 100644 --- a/crates/search/src/search_bar.rs +++ b/crates/search/src/search_bar.rs @@ -5,8 +5,6 @@ use theme::ThemeSettings; use ui::{IconButton, IconButtonShape}; use ui::{Tooltip, prelude::*}; -use crate::ToggleReplace; - pub(super) fn render_action_button( id_prefix: &'static str, icon: ui::IconName, @@ -46,25 +44,6 @@ pub(crate) fn input_base_styles(border_color: Hsla, map: impl FnOnce(Div) -> Div .rounded_lg() } -pub(crate) fn toggle_replace_button( - id: &'static str, - focus_handle: FocusHandle, - replace_enabled: bool, - on_click: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static, -) -> IconButton { - IconButton::new(id, IconName::Replace) - .shape(IconButtonShape::Square) - .style(ButtonStyle::Subtle) - .when(replace_enabled, |button| button.style(ButtonStyle::Filled)) - .on_click(on_click) - .toggle_state(replace_enabled) - .tooltip({ - move |window, cx| { - Tooltip::for_action_in("Toggle Replace", &ToggleReplace, &focus_handle, window, cx) - } - }) -} - pub(crate) fn render_text_input( editor: &Entity, color_override: Option, diff --git a/crates/search/src/search_status_button.rs b/crates/search/src/search_status_button.rs index ff2ee1641d..fcf36e86fa 100644 --- a/crates/search/src/search_status_button.rs +++ b/crates/search/src/search_status_button.rs @@ -3,6 +3,8 @@ use settings::Settings as _; use ui::{ButtonCommon, Clickable, Context, Render, Tooltip, Window, prelude::*}; use workspace::{ItemHandle, StatusItemView}; +pub const SEARCH_ICON: IconName = IconName::MagnifyingGlass; + pub struct SearchButton; impl SearchButton { @@ -20,7 +22,7 @@ impl Render for SearchButton { } button.child( - IconButton::new("project-search-indicator", IconName::MagnifyingGlass) + IconButton::new("project-search-indicator", SEARCH_ICON) .icon_size(IconSize::Small) .tooltip(|window, cx| { Tooltip::for_action( diff --git a/crates/zed/src/zed/quick_action_bar.rs b/crates/zed/src/zed/quick_action_bar.rs index e76bef59a3..2b7c38f997 100644 --- a/crates/zed/src/zed/quick_action_bar.rs +++ b/crates/zed/src/zed/quick_action_bar.rs @@ -140,7 +140,7 @@ impl Render for QuickActionBar { let search_button = editor.is_singleton(cx).then(|| { QuickActionBarButton::new( "toggle buffer search", - IconName::MagnifyingGlass, + search::SEARCH_ICON, !self.buffer_search_bar.read(cx).is_dismissed(), Box::new(buffer_search::Deploy::find()), focus_handle.clone(),