use std::error::Error;
use std::path::PathBuf;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use ratatui::Frame;
use crate::ui::terminal::views::{FileExplorer, PreviewView, QueueView};
use crate::ui::terminal::{AppMode, Event, KeyResult, Tui};
use crate::ui::{Theme, UiAction, UserInterface};
pub struct OperationQueue {
operations: Vec<FileOperation>,
selected_index: usize,
}
impl OperationQueue {
pub fn new() -> Self {
Self {
operations: Vec::new(),
selected_index: 0,
}
}
pub fn add(&mut self, operation: FileOperation) {
self.operations.push(operation);
}
pub fn is_empty(&self) -> bool {
self.operations.is_empty()
}
pub fn operations(&self) -> &[FileOperation] {
&self.operations
}
pub fn selected_index(&self) -> usize {
self.selected_index
}
pub fn select_next(&mut self) {
if !self.operations.is_empty() {
self.selected_index = (self.selected_index + 1) % self.operations.len();
}
}
pub fn select_prev(&mut self) {
if !self.operations.is_empty() {
self.selected_index = self
.selected_index
.checked_sub(1)
.unwrap_or(self.operations.len() - 1);
}
}
pub fn remove_selected(&mut self) {
if !self.operations.is_empty() {
self.operations.remove(self.selected_index);
if self.selected_index >= self.operations.len() && !self.operations.is_empty() {
self.selected_index = self.operations.len() - 1;
}
}
}
pub fn clear(&mut self) {
self.operations.clear();
self.selected_index = 0;
}
}
#[derive(Clone, Debug)]
pub struct FileOperation {
pub source: PathBuf,
pub destination: PathBuf,
pub operation_type: OperationType,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum OperationType {
Move,
Transform(TransformType),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TransformType {
Snake,
Kebab,
Title,
Camel,
Pascal,
Lower,
Upper,
Clean,
}
pub struct App {
tui: Tui,
mode: AppMode,
current_dir: PathBuf,
explorer: FileExplorer,
queue: OperationQueue,
queue_view: QueueView,
preview: PreviewView,
theme: Theme,
should_exit: bool,
status_message: String,
}
impl App {
pub fn new() -> anyhow::Result<Self> {
let tui = Tui::new()?;
Tui::init_panic_hook();
let current_dir = std::env::current_dir()?;
Ok(Self {
tui,
mode: AppMode::Normal,
current_dir: current_dir.clone(),
explorer: FileExplorer::new(current_dir.clone()),
queue: OperationQueue::new(),
queue_view: QueueView::new(),
preview: PreviewView::new(),
theme: Theme::default(),
should_exit: false,
status_message: String::from("Press ? for help"),
})
}
fn handle_key_event(&mut self, key: KeyEvent) -> anyhow::Result<()> {
match (key.code, key.modifiers) {
(KeyCode::Char('q'), KeyModifiers::CONTROL) => {
self.should_exit = true;
return Ok(());
}
(KeyCode::Char('?'), KeyModifiers::NONE) => {
self.status_message = String::from("Help mode - press ESC to exit");
return Ok(());
}
(KeyCode::Esc, KeyModifiers::NONE) => {
self.mode = AppMode::Normal;
self.status_message = String::from("Normal mode");
return Ok(());
}
_ => {}
}
match self.mode {
AppMode::Normal => self.handle_normal_mode_key(key)?,
AppMode::Visual => self.handle_visual_mode_key(key)?,
AppMode::Command => self.handle_command_mode_key(key)?,
AppMode::Insert => self.handle_insert_mode_key(key)?,
}
Ok(())
}
fn handle_normal_mode_key(&mut self, key: KeyEvent) -> anyhow::Result<()> {
match self.explorer.handle_key(key, &self.mode) {
KeyResult::Handled(action) => {
if let Some(action) = action {
self.handle_ui_action(action)?;
}
return Ok(());
}
KeyResult::NotHandled => {}
}
match self.queue_view.handle_key(key, &self.mode, &mut self.queue) {
KeyResult::Handled(action) => {
if let Some(action) = action {
self.handle_ui_action(action)?;
}
return Ok(());
}
KeyResult::NotHandled => {}
}
match (key.code, key.modifiers) {
(KeyCode::Char('v'), KeyModifiers::NONE) => {
self.mode = AppMode::Visual;
self.status_message = String::from("Visual mode");
}
(KeyCode::Char(':'), KeyModifiers::NONE) => {
self.mode = AppMode::Command;
self.status_message = String::from(":");
}
_ => {}
}
Ok(())
}
fn handle_visual_mode_key(&mut self, key: KeyEvent) -> anyhow::Result<()> {
match self.explorer.handle_key(key, &self.mode) {
KeyResult::Handled(action) => {
if let Some(action) = action {
self.handle_ui_action(action)?;
}
return Ok(());
}
KeyResult::NotHandled => {}
}
Ok(())
}
fn handle_command_mode_key(&mut self, key: KeyEvent) -> anyhow::Result<()> {
match key.code {
KeyCode::Enter => {
self.mode = AppMode::Normal;
self.status_message = String::from("Command executed");
}
_ => {}
}
Ok(())
}
fn handle_insert_mode_key(&mut self, key: KeyEvent) -> anyhow::Result<()> {
match key.code {
KeyCode::Enter => {
self.mode = AppMode::Normal;
}
_ => {}
}
Ok(())
}
fn handle_ui_action(&mut self, action: UiAction) -> anyhow::Result<()> {
match action {
UiAction::Exit => {
self.should_exit = true;
}
UiAction::ExecuteQueue => {
self.status_message = String::from("Executing queue (not implemented)");
}
UiAction::ShowHelp => {
self.status_message = String::from("Help view (not implemented)");
}
UiAction::Continue => {}
}
Ok(())
}
fn render(&mut self) -> anyhow::Result<()> {
self.tui.draw(|frame| {
Ok(())
})?;
Ok(())
}
fn render_app(&self, _frame: &mut Frame) -> anyhow::Result<()> {
Ok(())
}
}
impl UserInterface for App {
fn run(&mut self) -> Result<(), Box<dyn Error>> {
while !self.should_exit {
self.render()?;
match self.tui.next_event()? {
Event::Key(key) => self.handle_key_event(key)?,
Event::Resize(_, _) => {} Event::Tick => {} }
}
self.tui.exit()?;
Ok(())
}
fn open_directory(&mut self, path: PathBuf) -> Result<(), Box<dyn Error>> {
self.current_dir = path.clone();
self.explorer.change_directory(path)?;
Ok(())
}
}