1use bitflags::bitflags;
2pub use buffer_search::BufferSearchBar;
3use editor::SearchSettings;
4use gpui::{actions, Action, AppContext, IntoElement};
5use project::search::SearchQuery;
6pub use project_search::ProjectSearchView;
7use settings::Settings;
8use ui::{prelude::*, Tooltip};
9use ui::{ButtonStyle, IconButton};
10use workspace::notifications::NotificationId;
11use workspace::{Toast, Workspace};
12
13pub mod buffer_search;
14pub mod project_search;
15pub(crate) mod search_bar;
16
17pub fn init(cx: &mut AppContext) {
18 SearchSettings::register(cx);
19 menu::init();
20 buffer_search::init(cx);
21 project_search::init(cx);
22}
23
24actions!(
25 search,
26 [
27 FocusSearch,
28 ToggleWholeWord,
29 ToggleCaseSensitive,
30 ToggleIncludeIgnored,
31 ToggleRegex,
32 ToggleReplace,
33 ToggleSelection,
34 SelectNextMatch,
35 SelectPrevMatch,
36 SelectAllMatches,
37 NextHistoryQuery,
38 PreviousHistoryQuery,
39 ReplaceAll,
40 ReplaceNext,
41 ]
42);
43
44bitflags! {
45 #[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
46 pub struct SearchOptions: u8 {
47 const NONE = 0b000;
48 const WHOLE_WORD = 0b001;
49 const CASE_SENSITIVE = 0b010;
50 const INCLUDE_IGNORED = 0b100;
51 const REGEX = 0b1000;
52 }
53}
54
55impl SearchOptions {
56 pub fn label(&self) -> &'static str {
57 match *self {
58 SearchOptions::WHOLE_WORD => "Match whole words",
59 SearchOptions::CASE_SENSITIVE => "Match case sensitively",
60 SearchOptions::INCLUDE_IGNORED => "Also search files ignored by configuration",
61 SearchOptions::REGEX => "Use regular expressions",
62 _ => panic!("{:?} is not a named SearchOption", self),
63 }
64 }
65
66 pub fn icon(&self) -> ui::IconName {
67 match *self {
68 SearchOptions::WHOLE_WORD => ui::IconName::WholeWord,
69 SearchOptions::CASE_SENSITIVE => ui::IconName::CaseSensitive,
70 SearchOptions::INCLUDE_IGNORED => ui::IconName::Sliders,
71 SearchOptions::REGEX => ui::IconName::Regex,
72 _ => panic!("{:?} is not a named SearchOption", self),
73 }
74 }
75
76 pub fn to_toggle_action(&self) -> Box<dyn Action + Sync + Send + 'static> {
77 match *self {
78 SearchOptions::WHOLE_WORD => Box::new(ToggleWholeWord),
79 SearchOptions::CASE_SENSITIVE => Box::new(ToggleCaseSensitive),
80 SearchOptions::INCLUDE_IGNORED => Box::new(ToggleIncludeIgnored),
81 SearchOptions::REGEX => Box::new(ToggleRegex),
82 _ => panic!("{:?} is not a named SearchOption", self),
83 }
84 }
85
86 pub fn none() -> SearchOptions {
87 SearchOptions::NONE
88 }
89
90 pub fn from_query(query: &SearchQuery) -> SearchOptions {
91 let mut options = SearchOptions::NONE;
92 options.set(SearchOptions::WHOLE_WORD, query.whole_word());
93 options.set(SearchOptions::CASE_SENSITIVE, query.case_sensitive());
94 options.set(SearchOptions::INCLUDE_IGNORED, query.include_ignored());
95 options.set(SearchOptions::REGEX, query.is_regex());
96 options
97 }
98
99 pub fn from_settings(settings: &SearchSettings) -> SearchOptions {
100 let mut options = SearchOptions::NONE;
101 options.set(SearchOptions::WHOLE_WORD, settings.whole_word);
102 options.set(SearchOptions::CASE_SENSITIVE, settings.case_sensitive);
103 options.set(SearchOptions::INCLUDE_IGNORED, settings.include_ignored);
104 options.set(SearchOptions::REGEX, settings.regex);
105 options
106 }
107
108 pub fn as_button(
109 &self,
110 active: bool,
111 action: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
112 ) -> impl IntoElement {
113 IconButton::new(self.label(), self.icon())
114 .on_click(action)
115 .style(ButtonStyle::Subtle)
116 .selected(active)
117 .tooltip({
118 let action = self.to_toggle_action();
119 let label = self.label();
120 move |cx| Tooltip::for_action(label, &*action, cx)
121 })
122 }
123}
124
125pub(crate) fn show_no_more_matches(cx: &mut WindowContext) {
126 cx.defer(|cx| {
127 struct NotifType();
128 let notification_id = NotificationId::unique::<NotifType>();
129 let Some(workspace) = cx.window_handle().downcast::<Workspace>() else {
130 return;
131 };
132 workspace
133 .update(cx, |workspace, cx| {
134 workspace.show_toast(
135 Toast::new(notification_id.clone(), "No more matches").autohide(),
136 cx,
137 );
138 })
139 .ok();
140 });
141}