fast-fs 0.2.1

High-speed async file system traversal library with batteries-included file browser component
Documentation
// <FILE>crates/fast-fs/src/nav/fnc_browser_actions.rs</FILE> - <DESC>Browser action helper functions</DESC>
// <VERS>VERSION: 0.1.0</VERS>
// <WCTX>Extracting from cls_browser.rs for OFPF compliance</WCTX>
// <CLOG>Initial extraction of action helper functions</CLOG>

//! Action helper functions for Browser
//!
//! These functions provide the logic for clipboard operations and
//! path collection, decoupled from Browser state management.

#![allow(dead_code)] // Public API functions for library consumers

use super::action_result::{ActionResult, ClipboardOp, ClipboardState, InputRequest, PendingOp};
use std::path::{Path, PathBuf};

/// Build clipboard state for cut operation
pub fn build_cut_clipboard(paths: Vec<PathBuf>, current_path: &Path) -> ActionResult {
    if paths.is_empty() {
        return ActionResult::Done;
    }
    ActionResult::Clipboard(ClipboardState::new(
        ClipboardOp::Cut,
        paths,
        current_path.to_path_buf(),
    ))
}

/// Build clipboard state for copy operation
pub fn build_copy_clipboard(paths: Vec<PathBuf>, current_path: &Path) -> ActionResult {
    if paths.is_empty() {
        return ActionResult::Done;
    }
    ActionResult::Clipboard(ClipboardState::new(
        ClipboardOp::Copy,
        paths,
        current_path.to_path_buf(),
    ))
}

/// Build rename input request
pub fn build_rename_request(current_name: String) -> (ActionResult, InputRequest) {
    let req = InputRequest::Rename {
        current_name: current_name.clone(),
    };
    (ActionResult::NeedsInput(req.clone()), req)
}

/// Build filter input request
pub fn build_filter_request(current_pattern: Option<String>) -> (ActionResult, InputRequest) {
    let req = InputRequest::Filter {
        current: current_pattern.clone(),
    };
    (ActionResult::NeedsInput(req.clone()), req)
}

/// Build path input request
pub fn build_path_request(current_path: &Path) -> (ActionResult, InputRequest) {
    let req = InputRequest::Path {
        current: current_path.to_path_buf(),
    };
    (ActionResult::NeedsInput(req.clone()), req)
}

/// Build delete confirmation request
pub fn build_delete_confirmation(paths: Vec<PathBuf>) -> (ActionResult, PendingOp) {
    let op = PendingOp::Delete {
        paths: paths.clone(),
    };
    (ActionResult::NeedsConfirmation(op.clone()), op)
}

/// Build new directory input request
pub fn build_new_dir_request() -> (ActionResult, InputRequest) {
    let req = InputRequest::NewDirectory;
    (ActionResult::NeedsInput(req.clone()), req)
}

/// Build new file input request
pub fn build_new_file_request() -> (ActionResult, InputRequest) {
    let req = InputRequest::NewFile;
    (ActionResult::NeedsInput(req.clone()), req)
}

/// Collect paths from selection or current entry
pub fn collect_target_paths<'a>(
    selection_paths: impl Iterator<Item = &'a Path>,
    current_entry_path: Option<&'a Path>,
    selection_is_empty: bool,
) -> Vec<PathBuf> {
    if selection_is_empty {
        current_entry_path
            .map(|p| vec![p.to_path_buf()])
            .unwrap_or_default()
    } else {
        selection_paths.map(|p| p.to_path_buf()).collect()
    }
}

/// Build breadcrumbs from a path
pub fn build_breadcrumbs(path: &Path) -> Vec<(String, PathBuf)> {
    let mut crumbs = Vec::new();
    let mut current = path.to_path_buf();

    loop {
        let name = current
            .file_name()
            .map(|n| n.to_string_lossy().into_owned())
            .unwrap_or_else(|| current.to_string_lossy().into_owned());
        crumbs.push((name, current.clone()));

        if let Some(parent) = current.parent() {
            if parent == current {
                break;
            }
            current = parent.to_path_buf();
        } else {
            break;
        }
    }

    crumbs.reverse();
    crumbs
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_build_breadcrumbs() {
        let crumbs = build_breadcrumbs(Path::new("/home/user/project"));
        assert_eq!(crumbs.len(), 4);
        assert_eq!(crumbs[0].0, "/");
        assert_eq!(crumbs[1].0, "home");
        assert_eq!(crumbs[2].0, "user");
        assert_eq!(crumbs[3].0, "project");
    }

    #[test]
    fn test_build_breadcrumbs_root() {
        let crumbs = build_breadcrumbs(Path::new("/"));
        assert_eq!(crumbs.len(), 1);
        assert_eq!(crumbs[0].0, "/");
    }

    #[test]
    fn test_collect_target_paths_with_selection() {
        let paths = vec![
            PathBuf::from("/a"),
            PathBuf::from("/b"),
            PathBuf::from("/c"),
        ];
        let selection: Vec<&Path> = paths.iter().map(|p| p.as_path()).collect();
        let result =
            collect_target_paths(selection.into_iter(), Some(Path::new("/current")), false);
        assert_eq!(result.len(), 3);
    }

    #[test]
    fn test_collect_target_paths_empty_selection() {
        let result = collect_target_paths(std::iter::empty(), Some(Path::new("/current")), true);
        assert_eq!(result.len(), 1);
        assert_eq!(result[0], PathBuf::from("/current"));
    }

    #[test]
    fn test_collect_target_paths_nothing() {
        let result: Vec<PathBuf> = collect_target_paths(std::iter::empty(), None, true);
        assert!(result.is_empty());
    }

    #[test]
    fn test_build_cut_clipboard_empty() {
        let result = build_cut_clipboard(vec![], Path::new("/test"));
        assert!(matches!(result, ActionResult::Done));
    }

    #[test]
    fn test_build_cut_clipboard() {
        let result = build_cut_clipboard(
            vec![PathBuf::from("/a"), PathBuf::from("/b")],
            Path::new("/test"),
        );
        if let ActionResult::Clipboard(state) = result {
            assert!(matches!(state.operation, ClipboardOp::Cut));
            assert_eq!(state.paths.len(), 2);
        } else {
            panic!("Expected Clipboard result");
        }
    }
}

// <FILE>crates/fast-fs/src/nav/fnc_browser_actions.rs</FILE>
// <VERS>END OF VERSION: 0.1.0</VERS>