use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClipboardOp {
Cut,
Copy,
}
#[derive(Debug, Clone)]
pub struct ClipboardState {
pub operation: ClipboardOp,
pub paths: Vec<PathBuf>,
pub source_dir: PathBuf,
}
impl ClipboardState {
pub fn new(operation: ClipboardOp, paths: Vec<PathBuf>, source_dir: PathBuf) -> Self {
Self {
operation,
paths,
source_dir,
}
}
pub fn message(&self) -> String {
let op = match self.operation {
ClipboardOp::Cut => "move",
ClipboardOp::Copy => "copy",
};
if self.paths.len() == 1 {
format!(
"{} '{}'",
op,
self.paths[0]
.file_name()
.map(|n| n.to_string_lossy())
.unwrap_or_default()
)
} else {
format!("{} {} items", op, self.paths.len())
}
}
pub fn would_conflict(&self, dest: &Path) -> Vec<PathBuf> {
self.paths
.iter()
.filter_map(|p| {
let name = p.file_name()?;
let dest_path = dest.join(name);
if dest_path.exists() {
Some(dest_path)
} else {
None
}
})
.collect()
}
pub fn is_same_directory(&self, dest: &Path) -> bool {
self.source_dir == dest
}
pub fn len(&self) -> usize {
self.paths.len()
}
pub fn is_empty(&self) -> bool {
self.paths.is_empty()
}
}
#[derive(Debug, Clone)]
pub enum ActionResult {
Done,
DirectoryChanged,
NeedsConfirmation(PendingOp),
FileSelected(PathBuf),
NeedsInput(InputRequest),
Clipboard(ClipboardState),
Unhandled,
}
impl ActionResult {
pub fn is_directory_changed(&self) -> bool {
matches!(self, ActionResult::DirectoryChanged)
}
pub fn needs_interaction(&self) -> bool {
matches!(
self,
ActionResult::NeedsConfirmation(_) | ActionResult::NeedsInput(_)
)
}
}
#[derive(Debug, Clone)]
pub enum InputRequest {
Filter {
current: Option<String>,
},
Path {
current: PathBuf,
},
Rename {
current_name: String,
},
NewDirectory,
NewFile,
}
impl InputRequest {
pub fn prompt(&self) -> &'static str {
match self {
InputRequest::Filter { .. } => "Filter:",
InputRequest::Path { .. } => "Go to:",
InputRequest::Rename { .. } => "New name:",
InputRequest::NewDirectory => "Directory name:",
InputRequest::NewFile => "File name:",
}
}
pub fn current(&self) -> Option<String> {
match self {
InputRequest::Filter { current } => current.clone(),
InputRequest::Path { current } => Some(current.to_string_lossy().into_owned()),
InputRequest::Rename { current_name } => Some(current_name.clone()),
InputRequest::NewDirectory | InputRequest::NewFile => None,
}
}
pub fn current_path(&self) -> Option<&Path> {
match self {
InputRequest::Path { current } => Some(current.as_path()),
_ => None,
}
}
pub fn is_create(&self) -> bool {
matches!(self, InputRequest::NewDirectory | InputRequest::NewFile)
}
}
#[derive(Debug, Clone)]
pub enum PendingOp {
Delete {
paths: Vec<PathBuf>,
},
Rename {
from: PathBuf,
to: PathBuf,
},
Overwrite {
path: PathBuf,
},
}
impl PendingOp {
pub fn message(&self) -> String {
match self {
PendingOp::Delete { paths } => {
if paths.len() == 1 {
format!("Delete '{}'?", paths[0].display())
} else {
format!("Delete {} items?", paths.len())
}
}
PendingOp::Rename { from, to } => {
format!(
"Rename '{}' to '{}'?",
from.file_name()
.map(|n| n.to_string_lossy())
.unwrap_or_default(),
to.file_name()
.map(|n| n.to_string_lossy())
.unwrap_or_default()
)
}
PendingOp::Overwrite { path } => {
format!("'{}' already exists. Overwrite?", path.display())
}
}
}
pub fn paths(&self) -> impl Iterator<Item = &Path> {
let paths: Vec<&Path> = match self {
PendingOp::Delete { paths } => paths.iter().map(|p| p.as_path()).collect(),
PendingOp::Rename { from, to } => vec![from.as_path(), to.as_path()],
PendingOp::Overwrite { path } => vec![path.as_path()],
};
paths.into_iter()
}
pub fn operation_type(&self) -> &'static str {
match self {
PendingOp::Delete { .. } => "delete",
PendingOp::Rename { .. } => "rename",
PendingOp::Overwrite { .. } => "overwrite",
}
}
}