mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 04:06:45 +01:00
188 lines
5.9 KiB
Rust
188 lines
5.9 KiB
Rust
use crate::*;
|
|
use KeyCode::{Up, Down, Right, Left, Enter, Esc, Char, Backspace};
|
|
use FileBrowserCommand::*;
|
|
use super::phrase_list::PhraseListMode::{Import, Export};
|
|
|
|
/// 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<Tui>
|
|
}
|
|
|
|
render!(|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(())
|
|
})
|
|
});
|
|
|
|
impl FileBrowser {
|
|
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
|
|
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(|_|"<unreadable>".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> {
|
|
Self::new(Some(self.path()))
|
|
}
|
|
}
|
|
|
|
/// Commands supported by [FileBrowser]
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum FileBrowserCommand {
|
|
Begin,
|
|
Cancel,
|
|
Confirm,
|
|
Select(usize),
|
|
Chdir(PathBuf),
|
|
Filter(String),
|
|
}
|
|
|
|
command!(|self: FileBrowserCommand, state: PhraseListModel|{
|
|
let mode = state.phrases_mode_mut();
|
|
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(PhraseListMode::Export(index, ref mut browser)) => match self {
|
|
Cancel => {
|
|
*mode = None;
|
|
},
|
|
Chdir(cwd) => {
|
|
*mode = Some(PhraseListMode::Export(*index, FileBrowser::new(Some(cwd))?));
|
|
},
|
|
Select(index) => {
|
|
browser.index = index;
|
|
},
|
|
_ => unreachable!()
|
|
},
|
|
_ => unreachable!(),
|
|
};
|
|
None
|
|
});
|
|
|
|
input_to_command!(FileBrowserCommand:<Tui>|state:PhraseListModel,from|{
|
|
if let Some(PhraseListMode::Import(_index, browser)) = state.phrases_mode() {
|
|
match from.event() {
|
|
key_pat!(Up) => Select(
|
|
browser.index.overflowing_sub(1).0.min(browser.len().saturating_sub(1))
|
|
),
|
|
key_pat!(Down) => Select(
|
|
browser.index.saturating_add(1) % browser.len()
|
|
),
|
|
key_pat!(Right) => Chdir(browser.cwd.clone()),
|
|
key_pat!(Left) => Chdir(browser.cwd.clone()),
|
|
key_pat!(Enter) => Confirm,
|
|
key_pat!(Char(_)) => { todo!() },
|
|
key_pat!(Backspace) => { todo!() },
|
|
key_pat!(Esc) => Self::Cancel,
|
|
_ => return None
|
|
}
|
|
} else if let Some(PhraseListMode::Export(_index, browser)) = state.phrases_mode() {
|
|
match from.event() {
|
|
key_pat!(Up) => Select(browser.index.overflowing_sub(1).0.min(browser.len())),
|
|
key_pat!(Down) => Select(browser.index.saturating_add(1) % browser.len()),
|
|
key_pat!(Right) => Chdir(browser.cwd.clone()),
|
|
key_pat!(Left) => Chdir(browser.cwd.clone()),
|
|
key_pat!(Enter) => Confirm,
|
|
key_pat!(Char(_)) => { todo!() },
|
|
key_pat!(Backspace) => { todo!() },
|
|
key_pat!(Esc) => Self::Cancel,
|
|
_ => return None
|
|
}
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
});
|
|
|
|
input_to_command!(PhraseLengthCommand:<Tui>|state:PhraseListModel,from|{
|
|
if let Some(PhraseListMode::Length(_, length, _)) = state.phrases_mode() {
|
|
match from.event() {
|
|
key_pat!(Up) => Self::Inc,
|
|
key_pat!(Down) => Self::Dec,
|
|
key_pat!(Right) => Self::Next,
|
|
key_pat!(Left) => Self::Prev,
|
|
key_pat!(Enter) => Self::Set(*length),
|
|
key_pat!(Esc) => Self::Cancel,
|
|
_ => return None
|
|
}
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
});
|