use std::path::PathBuf;
use crossterm::event::{KeyCode, KeyEvent};
use crate::{ExplorerCommand, ExplorerOutcome, FileExplorer, Keymap, SortMode};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DualPaneActive {
#[default]
Left,
Right,
}
impl DualPaneActive {
#[must_use]
pub fn other(self) -> Self {
match self {
Self::Left => Self::Right,
Self::Right => Self::Left,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DualPaneOutcome {
Selected(PathBuf),
Dismissed,
Pending,
Unhandled,
MkdirCreated(PathBuf),
TouchCreated(PathBuf),
RenameCompleted {
from: PathBuf,
to: PathBuf,
},
}
impl From<ExplorerOutcome> for DualPaneOutcome {
fn from(outcome: ExplorerOutcome) -> Self {
match outcome {
ExplorerOutcome::Selected(p) => Self::Selected(p),
ExplorerOutcome::Dismissed => Self::Dismissed,
ExplorerOutcome::Pending => Self::Pending,
ExplorerOutcome::Unhandled => Self::Unhandled,
ExplorerOutcome::MkdirCreated(p) => Self::MkdirCreated(p),
ExplorerOutcome::TouchCreated(p) => Self::TouchCreated(p),
ExplorerOutcome::RenameCompleted { from, to } => Self::RenameCompleted { from, to },
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DualPaneCommand {
Explorer(ExplorerCommand),
NextPane,
PreviousPane,
FocusLeft,
FocusRight,
ToggleSinglePane,
}
#[derive(Debug)]
pub struct DualPane {
pub left: FileExplorer,
pub right: FileExplorer,
pub active_side: DualPaneActive,
pub single_pane: bool,
}
impl DualPane {
#[must_use]
pub fn builder(left_dir: PathBuf) -> DualPaneBuilder {
DualPaneBuilder::new(left_dir)
}
#[must_use]
pub fn active(&self) -> &FileExplorer {
match self.active_side {
DualPaneActive::Left => &self.left,
DualPaneActive::Right => &self.right,
}
}
pub fn active_mut(&mut self) -> &mut FileExplorer {
match self.active_side {
DualPaneActive::Left => &mut self.left,
DualPaneActive::Right => &mut self.right,
}
}
#[must_use]
pub fn inactive(&self) -> &FileExplorer {
match self.active_side {
DualPaneActive::Left => &self.right,
DualPaneActive::Right => &self.left,
}
}
pub fn handle_command(&mut self, command: DualPaneCommand) -> DualPaneOutcome {
match command {
DualPaneCommand::Explorer(command) => self.active_mut().handle_command(command).into(),
DualPaneCommand::NextPane | DualPaneCommand::PreviousPane => {
self.active_side = self.active_side.other();
DualPaneOutcome::Pending
}
DualPaneCommand::FocusLeft => {
self.focus_left();
DualPaneOutcome::Pending
}
DualPaneCommand::FocusRight => {
self.focus_right();
DualPaneOutcome::Pending
}
DualPaneCommand::ToggleSinglePane => {
self.toggle_single_pane();
DualPaneOutcome::Pending
}
}
}
pub fn handle_key(&mut self, key: KeyEvent) -> DualPaneOutcome {
press_only!(key, DualPaneOutcome::Pending);
match key.code {
KeyCode::Tab if key.modifiers.is_empty() => {
self.active_side = self.active_side.other();
return DualPaneOutcome::Pending;
}
KeyCode::Char('w') if key.modifiers.is_empty() => {
self.single_pane = !self.single_pane;
return DualPaneOutcome::Pending;
}
_ => {}
}
self.active_mut().handle_key(key).into()
}
pub fn focus_left(&mut self) {
self.active_side = DualPaneActive::Left;
}
pub fn focus_right(&mut self) {
self.active_side = DualPaneActive::Right;
}
pub fn toggle_single_pane(&mut self) {
self.single_pane = !self.single_pane;
}
pub fn reload_both(&mut self) {
self.left.reload();
self.right.reload();
}
}
#[derive(Debug, Clone)]
pub struct DualPaneBuilder {
left_dir: PathBuf,
right_dir: Option<PathBuf>,
extensions: Vec<String>,
show_hidden: bool,
show_sizes: bool,
show_dir_sizes: bool,
keymap: Keymap,
sort_mode: SortMode,
selection_mode: crate::SelectionMode,
filesystem: crate::SharedFileSystem,
single_pane: bool,
page_size: usize,
}
impl DualPaneBuilder {
#[must_use]
pub fn new(left_dir: PathBuf) -> Self {
Self {
left_dir,
right_dir: None,
extensions: Vec::new(),
show_hidden: false,
show_sizes: true,
show_dir_sizes: true,
keymap: Keymap::default(),
sort_mode: SortMode::default(),
selection_mode: crate::SelectionMode::default(),
filesystem: crate::std_filesystem(),
single_pane: false,
page_size: 10,
}
}
#[must_use]
pub fn right_dir(mut self, dir: PathBuf) -> Self {
self.right_dir = Some(dir);
self
}
#[must_use]
pub fn extension_filter(mut self, filter: Vec<String>) -> Self {
self.extensions = filter;
self
}
#[must_use]
pub fn allow_extension(mut self, ext: impl Into<String>) -> Self {
self.extensions.push(ext.into());
self
}
#[must_use]
pub fn show_hidden(mut self, show: bool) -> Self {
self.show_hidden = show;
self
}
#[must_use]
pub fn show_sizes(mut self, show: bool) -> Self {
self.show_sizes = show;
self
}
#[must_use]
pub fn keymap(mut self, keymap: Keymap) -> Self {
self.keymap = keymap;
self
}
#[must_use]
pub fn show_dir_sizes(mut self, show: bool) -> Self {
self.show_dir_sizes = show;
self
}
#[must_use]
pub fn selection_mode(mut self, mode: crate::SelectionMode) -> Self {
self.selection_mode = mode;
self
}
#[must_use]
pub fn filesystem(mut self, filesystem: crate::SharedFileSystem) -> Self {
self.filesystem = filesystem;
self
}
#[must_use]
pub fn sort_mode(mut self, mode: SortMode) -> Self {
self.sort_mode = mode;
self
}
#[must_use]
pub fn single_pane(mut self, enabled: bool) -> Self {
self.single_pane = enabled;
self
}
#[must_use]
pub fn page_size(mut self, size: usize) -> Self {
self.page_size = size;
self
}
#[must_use]
pub fn build(self) -> DualPane {
let right_dir = self.right_dir.unwrap_or_else(|| self.left_dir.clone());
let left = FileExplorer::builder(self.left_dir)
.filesystem(self.filesystem.clone())
.extension_filter(self.extensions.clone())
.show_hidden(self.show_hidden)
.show_sizes(self.show_sizes)
.show_dir_sizes(self.show_dir_sizes)
.keymap(self.keymap.clone())
.selection_mode(self.selection_mode)
.sort_mode(self.sort_mode)
.page_size(self.page_size)
.build();
let right = FileExplorer::builder(right_dir)
.filesystem(self.filesystem)
.extension_filter(self.extensions)
.show_hidden(self.show_hidden)
.show_sizes(self.show_sizes)
.show_dir_sizes(self.show_dir_sizes)
.keymap(self.keymap)
.selection_mode(self.selection_mode)
.sort_mode(self.sort_mode)
.page_size(self.page_size)
.build();
DualPane {
left,
right,
active_side: DualPaneActive::Left,
single_pane: self.single_pane,
}
}
}
#[cfg(test)]
mod tests;