use crate::*; /// Browses for phrase to import/export #[derive(Debug, Clone)] pub struct FileBrowser { pub cwd: PathBuf, pub dirs: Vec<(OsString, String)>, pub files: Vec<(OsString, String)>, pub filter: String, pub index: usize, pub scroll: usize, pub size: Measure } /// Commands supported by [FileBrowser] #[derive(Debug, Clone, PartialEq)] pub enum FileBrowserCommand { Begin, Cancel, Confirm, Select(usize), Chdir(PathBuf), Filter(String), } render!(Tui: (self: FileBrowser) => /*Stack::down(|add|{ let mut i = 0; for (_, name) in self.dirs.iter() { if i >= self.scroll { add(&Tui::bold(i == self.index, name.as_str()))?; } i += 1; } for (_, name) in self.files.iter() { if i >= self.scroll { add(&Tui::bold(i == self.index, name.as_str()))?; } i += 1; } add(&format!("{}/{i}", self.index))?; Ok(()) })*/"todo"); impl FileBrowser { pub fn new (cwd: Option) -> Usually { let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? }; let mut dirs = vec![]; let mut files = vec![]; for entry in std::fs::read_dir(&cwd)? { let entry = entry?; let name = entry.file_name(); let decoded = name.clone().into_string().unwrap_or_else(|_|"".to_string()); let meta = entry.metadata()?; if meta.is_dir() { dirs.push((name, format!("📁 {decoded}"))); } else if meta.is_file() { files.push((name, format!("📄 {decoded}"))); } } Ok(Self { cwd, dirs, files, filter: "".to_string(), index: 0, scroll: 0, size: Measure::new(), }) } pub fn len (&self) -> usize { self.dirs.len() + self.files.len() } pub fn is_dir (&self) -> bool { self.index < self.dirs.len() } pub fn is_file (&self) -> bool { self.index >= self.dirs.len() } pub fn path (&self) -> PathBuf { self.cwd.join(if self.is_dir() { &self.dirs[self.index].0 } else if self.is_file() { &self.files[self.index - self.dirs.len()].0 } else { unreachable!() }) } pub fn chdir (&self) -> Usually { Self::new(Some(self.path())) } } command!(|self: FileBrowserCommand, state: PoolModel|{ use PoolMode::*; use FileBrowserCommand::*; let mode = &mut state.mode; match mode { Some(Import(index, ref mut browser)) => match self { Cancel => { *mode = None; }, Chdir(cwd) => { *mode = Some(Import(*index, FileBrowser::new(Some(cwd))?)); }, Select(index) => { browser.index = index; }, Confirm => if browser.is_file() { let index = *index; let path = browser.path(); *mode = None; PhrasePoolCommand::Import(index, path).execute(state)?; } else if browser.is_dir() { *mode = Some(Import(*index, browser.chdir()?)); }, _ => todo!(), }, Some(Export(index, ref mut browser)) => match self { Cancel => { *mode = None; }, Chdir(cwd) => { *mode = Some(Export(*index, FileBrowser::new(Some(cwd))?)); }, Select(index) => { browser.index = index; }, _ => unreachable!() }, _ => unreachable!(), }; None }); input_to_command!(FileBrowserCommand: |state: PoolModel, input: Event|{ use FileBrowserCommand::*; use KeyCode::{Up, Down, Left, Right, Enter, Esc, Backspace, Char}; if let Some(PoolMode::Import(_index, browser)) = &state.mode { match input { kpat!(Up) => Select(browser.index.overflowing_sub(1).0 .min(browser.len().saturating_sub(1))), kpat!(Down) => Select(browser.index.saturating_add(1) % browser.len()), kpat!(Right) => Chdir(browser.cwd.clone()), kpat!(Left) => Chdir(browser.cwd.clone()), kpat!(Enter) => Confirm, kpat!(Char(_)) => { todo!() }, kpat!(Backspace) => { todo!() }, kpat!(Esc) => Cancel, _ => return None } } else if let Some(PoolMode::Export(_index, browser)) = &state.mode { match input { kpat!(Up) => Select(browser.index.overflowing_sub(1).0 .min(browser.len())), kpat!(Down) => Select(browser.index.saturating_add(1) % browser.len()), kpat!(Right) => Chdir(browser.cwd.clone()), kpat!(Left) => Chdir(browser.cwd.clone()), kpat!(Enter) => Confirm, kpat!(Char(_)) => { todo!() }, kpat!(Backspace) => { todo!() }, kpat!(Esc) => Cancel, _ => return None } } else { unreachable!() } });