1use std::path::PathBuf;
2
3use anyhow::Result;
4
5use crate::app::Tab;
6use crate::modes::{CaseDependantRegex, Display, FileInfo, Go, IndexToIndex, To, ToPath, Tree};
7
8pub struct Search {
13 pub regex: CaseDependantRegex,
14 pub paths: Vec<PathBuf>,
15 pub index: usize,
16}
17
18impl std::fmt::Display for Search {
19 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
20 if self.is_empty() {
21 write!(f, "")
22 } else {
23 write!(
24 f,
25 " Searched: {regex} - {pos} / {len} ",
26 regex = self.regex,
27 pos = self.index + 1 - self.paths.is_empty() as usize,
28 len = self.paths.len()
29 )
30 }
31 }
32}
33
34impl Search {
35 pub fn empty() -> Self {
36 Self {
37 regex: CaseDependantRegex::new("").unwrap(),
38 paths: vec![],
39 index: 0,
40 }
41 }
42
43 pub fn new(searched: &str) -> Result<Self> {
44 Ok(Self {
45 regex: CaseDependantRegex::new(searched)?,
46 paths: vec![],
47 index: 0,
48 })
49 }
50
51 pub fn clone_with_regex(&self) -> Self {
52 Self {
53 regex: self.regex.clone(),
54 paths: vec![],
55 index: 0,
56 }
57 }
58
59 pub fn is_empty(&self) -> bool {
60 self.regex.is_empty()
61 }
62
63 pub fn reset_paths(&mut self) {
64 self.paths = vec![];
65 self.index = 0;
66 }
67
68 pub fn select_next(&mut self) -> Option<PathBuf> {
69 if !self.paths.is_empty() && !self.regex.to_string().is_empty() {
70 self.index = (self.index + 1) % self.paths.len();
71 return Some(self.paths[self.index].to_owned());
72 }
73 None
74 }
75
76 pub fn execute_search(&mut self, tab: &mut Tab) -> Result<()> {
77 match tab.display_mode {
78 Display::Tree => {
79 self.tree(&mut tab.tree);
80 }
81 Display::Directory => {
82 self.directory(tab);
83 }
84 _ => (),
85 };
86 Ok(())
87 }
88
89 #[inline]
94 fn directory(&mut self, tab: &mut Tab) {
95 let current_index = tab.directory.index;
96 let mut next_index = current_index;
97 let mut found = false;
98 for (index, file) in tab.directory.enumerate().skip(current_index) {
99 if self.regex.is_match(&file.filename) {
100 (next_index, found) = self.set_found(index, file, next_index, found);
101 }
102 }
103 for (index, file) in tab.directory.enumerate().take(current_index) {
104 if self.regex.is_match(&file.filename) {
105 (next_index, found) = self.set_found(index, file, next_index, found);
106 }
107 }
108 tab.go_to_index(next_index);
109 }
110
111 #[inline]
112 fn set_found(
113 &mut self,
114 index: usize,
115 file: &FileInfo,
116 mut next_index: usize,
117 mut found: bool,
118 ) -> (usize, bool) {
119 if !found {
120 next_index = index;
121 self.index = self.paths.len();
122 found = true;
123 }
124 self.paths.push(file.path.to_path_buf());
125
126 (next_index, found)
127 }
128
129 pub fn directory_search_next<'a>(
130 &mut self,
131 files: impl Iterator<Item = &'a FileInfo>,
132 ) -> Option<PathBuf> {
133 let (paths, Some(next_index), Some(next_path)) = self.directory_update_search(files) else {
134 return None;
135 };
136 self.set_index_paths(next_index, paths);
137 Some(next_path)
138 }
139
140 fn directory_update_search<'a>(
141 &self,
142 files: impl std::iter::Iterator<Item = &'a FileInfo>,
143 ) -> (Vec<PathBuf>, Option<usize>, Option<PathBuf>) {
144 let mut paths = vec![];
145 let mut next_index = None;
146 let mut next_path = None;
147
148 for file in files {
149 if self.regex.is_match(&file.filename) {
150 if next_index.is_none() {
151 (next_index, next_path) = self.found_first_match(file)
152 }
153 paths.push(file.path.to_path_buf());
154 }
155 }
156 (paths, next_index, next_path)
157 }
158
159 fn found_first_match(&self, file: &FileInfo) -> (Option<usize>, Option<PathBuf>) {
160 (Some(self.paths.len()), Some(file.path.to_path_buf()))
161 }
162
163 pub fn set_index_paths(&mut self, index: usize, paths: Vec<PathBuf>) {
164 self.index = index;
165 self.paths = paths;
166 }
167
168 pub fn tree(&mut self, tree: &mut Tree) {
169 if let Some(path) = &self.tree_find_next_path(tree) {
170 tree.go(To::Path(path));
171 }
172 }
173
174 fn tree_find_next_path(&mut self, tree: &mut Tree) -> Option<PathBuf> {
175 if let Some(path) = self.select_next() {
176 return Some(path);
177 }
178 self.tree_search_again(tree)
179 }
180
181 fn tree_search_again(&mut self, tree: &mut Tree) -> Option<PathBuf> {
182 let mut next_path = None;
183 for line in tree.index_to_index() {
184 let Some(filename) = line.path.file_name() else {
185 continue;
186 };
187 if self.regex.is_match(&filename.to_string_lossy()) {
188 let match_path = line.path.to_path_buf();
189 if next_path.is_none() {
190 self.index = self.paths.len();
191 next_path = Some(match_path.clone());
192 }
193 self.paths.push(match_path);
194 }
195 }
196 next_path
197 }
198
199 #[inline]
200 pub fn matches_from(&self, content: &[impl ToPath]) -> Vec<String> {
201 content
202 .iter()
203 .filter_map(|e| e.to_path().file_name())
204 .map(|s| s.to_string_lossy().to_string())
205 .filter(|p| self.regex.is_match(p))
206 .collect()
207 }
208
209 #[inline]
210 pub fn is_match(&self, filename: &str) -> bool {
211 !self.is_empty() && self.regex.is_match(filename)
212 }
213}