1use bitflags::bitflags;
2pub use buffer_search::BufferSearchBar;
3use gpui::{actions, Action, AppContext, IntoElement};
4pub use mode::SearchMode;
5use project::search::SearchQuery;
6use ui::{prelude::*, Tooltip};
7use ui::{ButtonStyle, Icon, IconButton};
8//pub use project_search::{ProjectSearchBar, ProjectSearchView};
9// use theme::components::{
10// action_button::Button, svg::Svg, ComponentExt, IconButtonStyle, ToggleIconButtonStyle,
11// };
12
13pub mod buffer_search;
14mod history;
15mod mode;
16pub mod project_search;
17pub(crate) mod search_bar;
18
19pub fn init(cx: &mut AppContext) {
20 menu::init();
21 buffer_search::init(cx);
22 project_search::init(cx);
23}
24
25actions!(
26 search,
27 [
28 CycleMode,
29 ToggleWholeWord,
30 ToggleCaseSensitive,
31 ToggleIncludeIgnored,
32 ToggleReplace,
33 SelectNextMatch,
34 SelectPrevMatch,
35 SelectAllMatches,
36 NextHistoryQuery,
37 PreviousHistoryQuery,
38 ActivateTextMode,
39 ActivateSemanticMode,
40 ActivateRegexMode,
41 ReplaceAll,
42 ReplaceNext,
43 ]
44);
45
46bitflags! {
47 #[derive(Default)]
48 pub struct SearchOptions: u8 {
49 const NONE = 0b000;
50 const WHOLE_WORD = 0b001;
51 const CASE_SENSITIVE = 0b010;
52 const INCLUDE_IGNORED = 0b100;
53 }
54}
55
56impl SearchOptions {
57 pub fn label(&self) -> &'static str {
58 match *self {
59 SearchOptions::WHOLE_WORD => "Match Whole Word",
60 SearchOptions::CASE_SENSITIVE => "Match Case",
61 SearchOptions::INCLUDE_IGNORED => "Include ignored",
62 _ => panic!("{:?} is not a named SearchOption", self),
63 }
64 }
65
66 pub fn icon(&self) -> ui::Icon {
67 match *self {
68 SearchOptions::WHOLE_WORD => ui::Icon::WholeWord,
69 SearchOptions::CASE_SENSITIVE => ui::Icon::CaseSensitive,
70 SearchOptions::INCLUDE_IGNORED => ui::Icon::FileGit,
71 _ => panic!("{:?} is not a named SearchOption", self),
72 }
73 }
74
75 pub fn to_toggle_action(&self) -> Box<dyn Action + Sync + Send + 'static> {
76 match *self {
77 SearchOptions::WHOLE_WORD => Box::new(ToggleWholeWord),
78 SearchOptions::CASE_SENSITIVE => Box::new(ToggleCaseSensitive),
79 SearchOptions::INCLUDE_IGNORED => Box::new(ToggleIncludeIgnored),
80 _ => panic!("{:?} is not a named SearchOption", self),
81 }
82 }
83
84 pub fn none() -> SearchOptions {
85 SearchOptions::NONE
86 }
87
88 pub fn from_query(query: &SearchQuery) -> SearchOptions {
89 let mut options = SearchOptions::NONE;
90 options.set(SearchOptions::WHOLE_WORD, query.whole_word());
91 options.set(SearchOptions::CASE_SENSITIVE, query.case_sensitive());
92 options.set(SearchOptions::INCLUDE_IGNORED, query.include_ignored());
93 options
94 }
95
96 pub fn as_button(
97 &self,
98 active: bool,
99 action: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
100 ) -> impl IntoElement {
101 IconButton::new(self.label(), self.icon())
102 .on_click(action)
103 .style(ButtonStyle::Subtle)
104 .when(active, |button| button.style(ButtonStyle::Filled))
105 .tooltip({
106 let action = self.to_toggle_action();
107 let label: SharedString = format!("Toggle {}", self.label()).into();
108 move |cx| Tooltip::for_action(label.clone(), &*action, cx)
109 })
110 }
111}
112
113fn toggle_replace_button(
114 active: bool,
115 action: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
116) -> impl IntoElement {
117 // todo: add toggle_replace button
118 IconButton::new("buffer-search-bar-toggle-replace-button", Icon::Replace)
119 .on_click(action)
120 .style(ButtonStyle::Subtle)
121 .when(active, |button| button.style(ButtonStyle::Filled))
122 .tooltip(|cx| Tooltip::for_action("Toggle replace", &ToggleReplace, cx))
123}
124
125fn render_replace_button(
126 action: impl Action + 'static + Send + Sync,
127 icon: Icon,
128 tooltip: &'static str,
129 on_click: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
130) -> impl IntoElement {
131 let id: SharedString = format!("search-replace-{}", action.name()).into();
132 IconButton::new(id, icon)
133 .tooltip({
134 let action = action.boxed_clone();
135 move |cx| Tooltip::for_action(tooltip, &*action, cx)
136 })
137 .on_click(on_click)
138}