1pub use buffer_search::BufferSearchBar;
2use editor::{display_map::ToDisplayPoint, Anchor, Bias, Editor, MultiBufferSnapshot};
3use gpui::{actions, Action, MutableAppContext, ViewHandle};
4pub use project_search::{ProjectSearchBar, ProjectSearchView};
5use std::{
6 cmp::{self, Ordering},
7 ops::Range,
8};
9
10pub mod buffer_search;
11pub mod project_search;
12
13pub fn init(cx: &mut MutableAppContext) {
14 buffer_search::init(cx);
15 project_search::init(cx);
16}
17
18actions!(
19 search,
20 [
21 ToggleWholeWord,
22 ToggleCaseSensitive,
23 ToggleRegex,
24 SelectNextMatch,
25 SelectPrevMatch
26 ]
27);
28
29#[derive(Clone, Copy, PartialEq)]
30pub enum SearchOption {
31 WholeWord,
32 CaseSensitive,
33 Regex,
34}
35
36impl SearchOption {
37 pub fn label(&self) -> &'static str {
38 match self {
39 SearchOption::WholeWord => "Match Whole Word",
40 SearchOption::CaseSensitive => "Match Case",
41 SearchOption::Regex => "Use Regular Expression",
42 }
43 }
44
45 pub fn to_toggle_action(&self) -> Box<dyn Action> {
46 match self {
47 SearchOption::WholeWord => Box::new(ToggleWholeWord),
48 SearchOption::CaseSensitive => Box::new(ToggleCaseSensitive),
49 SearchOption::Regex => Box::new(ToggleRegex),
50 }
51 }
52}
53
54#[derive(Clone, Copy, PartialEq, Eq)]
55pub enum Direction {
56 Prev,
57 Next,
58}
59
60pub(crate) fn active_match_index(
61 ranges: &[Range<Anchor>],
62 cursor: &Anchor,
63 buffer: &MultiBufferSnapshot,
64) -> Option<usize> {
65 if ranges.is_empty() {
66 None
67 } else {
68 match ranges.binary_search_by(|probe| {
69 if probe.end.cmp(&cursor, &*buffer).is_lt() {
70 Ordering::Less
71 } else if probe.start.cmp(&cursor, &*buffer).is_gt() {
72 Ordering::Greater
73 } else {
74 Ordering::Equal
75 }
76 }) {
77 Ok(i) | Err(i) => Some(cmp::min(i, ranges.len() - 1)),
78 }
79 }
80}
81
82pub(crate) fn match_index_for_direction(
83 ranges: &[Range<Anchor>],
84 cursor: &Anchor,
85 mut index: usize,
86 direction: Direction,
87 buffer: &MultiBufferSnapshot,
88) -> usize {
89 if ranges[index].start.cmp(&cursor, &buffer).is_gt() {
90 if direction == Direction::Prev {
91 if index == 0 {
92 index = ranges.len() - 1;
93 } else {
94 index -= 1;
95 }
96 }
97 } else if ranges[index].end.cmp(&cursor, &buffer).is_lt() {
98 if direction == Direction::Next {
99 index = 0;
100 }
101 } else if direction == Direction::Prev {
102 if index == 0 {
103 index = ranges.len() - 1;
104 } else {
105 index -= 1;
106 }
107 } else if direction == Direction::Next {
108 if index == ranges.len() - 1 {
109 index = 0
110 } else {
111 index += 1;
112 }
113 };
114 index
115}
116
117pub(crate) fn query_suggestion_for_editor(
118 editor: &ViewHandle<Editor>,
119 cx: &mut MutableAppContext,
120) -> String {
121 let display_map = editor
122 .update(cx, |editor, cx| editor.snapshot(cx))
123 .display_snapshot;
124 let selection = editor.read(cx).selections.newest::<usize>(cx);
125 if selection.start == selection.end {
126 let point = selection.start.to_display_point(&display_map);
127 let range = editor::movement::surrounding_word(&display_map, point);
128 let range = range.start.to_offset(&display_map, Bias::Left)
129 ..range.end.to_offset(&display_map, Bias::Right);
130 let text: String = display_map.buffer_snapshot.text_for_range(range).collect();
131 if text.trim().is_empty() {
132 String::new()
133 } else {
134 text
135 }
136 } else {
137 display_map
138 .buffer_snapshot
139 .text_for_range(selection.start..selection.end)
140 .collect()
141 }
142}