summaryrefslogtreecommitdiffstats
path: root/src/modes/edit/search.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/modes/edit/search.rs')
-rw-r--r--src/modes/edit/search.rs238
1 files changed, 238 insertions, 0 deletions
diff --git a/src/modes/edit/search.rs b/src/modes/edit/search.rs
new file mode 100644
index 0000000..0b7e993
--- /dev/null
+++ b/src/modes/edit/search.rs
@@ -0,0 +1,238 @@
+use anyhow::Result;
+
+use crate::{
+ app::{Status, Tab},
+ modes::{Display, Flagged, Go, To, ToPath, Tree},
+};
+
+#[derive(Clone)]
+pub struct Search {
+ pub regex: regex::Regex,
+ pub paths: Vec<std::path::PathBuf>,
+ pub index: usize,
+}
+
+impl std::fmt::Display for Search {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ if self.is_empty() {
+ write!(f, "")
+ } else {
+ write!(
+ f,
+ "Searched: {regex} - {pos} / {len}",
+ regex = self.regex,
+ pos = self.index + if self.paths.is_empty() { 0 } else { 1 },
+ len = self.paths.len()
+ )
+ }
+ }
+}
+
+impl Search {
+ pub fn empty() -> Self {
+ Self {
+ regex: regex::Regex::new("").unwrap(),
+ paths: vec![],
+ index: 0,
+ }
+ }
+
+ pub fn new(searched: &str) -> Result<Self> {
+ Ok(Self {
+ regex: regex::Regex::new(searched)?,
+ paths: vec![],
+ index: 0,
+ })
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.regex.to_string().is_empty()
+ }
+
+ pub fn reset_paths(&mut self) {
+ self.paths = vec![];
+ self.index = 0;
+ }
+
+ pub fn select_next(&mut self) -> Option<std::path::PathBuf> {
+ if !self.paths.is_empty() && !self.regex.to_string().is_empty() {
+ self.index = (self.index + 1) % self.paths.len();
+ return Some(self.paths[self.index].to_owned());
+ }
+ None
+ }
+
+ pub fn execute_search(&mut self, status: &mut Status) -> Result<()> {
+ match status.current_tab().display_mode {
+ Display::Tree => {
+ self.tree(&mut status.current_tab_mut().tree);
+ }
+ Display::Directory => {
+ self.directory(status.current_tab_mut());
+ }
+ Display::Flagged => self.flagged(&mut status.menu.flagged),
+ _ => (),
+ };
+ status.update_second_pane_for_preview()
+ }
+
+ /// Search in current directory for an file whose name contains `searched_name`,
+ /// from a starting position `next_index`.
+ /// We search forward from that position and start again from top if nothing is found.
+ /// We move the selection to the first matching file.
+ fn directory(&mut self, tab: &mut Tab) {
+ let current_index = tab.directory.index;
+ let mut next_index = current_index;
+ let mut found = false;
+ for (index, file) in tab.directory.enumerate().skip(current_index) {
+ if self.regex.is_match(&file.filename) {
+ if !found {
+ next_index = index;
+ self.index = self.paths.len();
+ found = true;
+ }
+ self.paths.push(file.path.to_path_buf());
+ }
+ }
+ for (index, file) in tab.directory.enumerate().take(current_index) {
+ if self.regex.is_match(&file.filename) {
+ if !found {
+ next_index = index;
+ self.index = self.paths.len();
+ found = true;
+ }
+ self.paths.push(file.path.to_path_buf());
+ }
+ }
+
+ tab.go_to_index(next_index);
+ }
+
+ pub fn directory_search_next(
+ &self,
+ tab: &Tab,
+ ) -> (
+ Vec<std::path::PathBuf>,
+ Option<usize>,
+ Option<std::path::PathBuf>,
+ ) {
+ let current_index = tab.directory.index;
+ let mut paths = vec![];
+ let mut found = false;
+ let mut index = None;
+ let mut found_path = None;
+ for file in tab
+ .directory
+ .iter()
+ .skip(current_index)
+ .chain(tab.directory.iter().take(current_index))
+ {
+ if self.regex.is_match(&file.filename) {
+ if !found {
+ index = Some(self.paths.len());
+ found = true;
+ found_path = Some(file.path.to_path_buf());
+ }
+ paths.push(file.path.to_path_buf());
+ }
+ }
+ (paths, index, found_path)
+ }
+
+ pub fn set_index_paths(&mut self, index: usize, paths: Vec<std::path::PathBuf>) {
+ self.paths = paths;
+ self.index = index;
+ }
+
+ pub fn tree(&mut self, tree: &mut Tree) {
+ if let Some(path) = self.tree_find_next_path(tree).to_owned() {
+ tree.go(To::Path(&path));
+ }
+ }
+
+ fn tree_find_next_path(&mut self, tree: &mut Tree) -> Option<std::path::PathBuf> {
+ if let Some(path) = self.select_next() {
+ return Some(path);
+ } else {
+ self.reset_paths()
+ }
+ let mut found_path = None;
+ let mut found = false;
+ for line in tree
+ .displayable()
+ .lines()
+ .iter()
+ .skip(tree.displayable().index() + 1)
+ .chain(
+ tree.displayable()
+ .lines()
+ .iter()
+ .take(tree.displayable().index()),
+ )
+ {
+ let Some(filename) = line.path.file_name() else {
+ continue;
+ };
+ if self.regex.is_match(&filename.to_string_lossy()) {
+ self.paths.push(line.path.to_path_buf());
+ if !found {
+ self.index = self.paths.len();
+ found_path = Some(line.path.to_path_buf());
+ found = true;
+ }
+ }
+ }
+ found_path
+ }
+
+ pub fn flagged(&mut self, flagged: &mut Flagged) {
+ if let Some(path) = self.select_next() {
+ flagged.select_path(&path);
+ return;
+ } else {
+ self.reset_paths();
+ }
+ if let Some(path) = self.find_in_flagged(flagged) {
+ flagged.select_path(&path);
+ }
+ }
+
+ fn find_in_flagged(&mut self, flagged: &Flagged) -> Option<std::path::PathBuf> {
+ let mut found = false;
+ let mut found_path = None;
+
+ for path in flagged
+ .content
+ .iter()
+ .skip(flagged.index + 1)
+ .chain(flagged.content.iter().take(flagged.index + 1))
+ {
+ if self
+ .regex
+ .is_match(&path.file_name().unwrap().to_string_lossy())
+ {
+ if !found {
+ found = true;
+ found_path = Some(path.to_path_buf());
+ self.index = self.paths.len();
+ }
+ self.paths.push(path.to_path_buf());
+ }
+ }
+ found_path
+ }
+
+ #[inline]
+ pub fn complete(&self, content: &[impl ToPath]) -> Vec<String> {
+ content
+ .iter()
+ .map(|elt| elt.to_path())
+ .filter(|p| {
+ self.regex
+ .is_match(p.file_name().unwrap_or_default().to_string_lossy().as_ref())
+ })
+ .filter_map(|p| p.file_name())
+ .map(|s| s.to_string_lossy().to_string())
+ .collect()
+ }
+}