pinel 0.8.0

A lightweight editor built with rust
use std::{cmp::Reverse, path::PathBuf};

use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};

#[derive(Debug, Clone)]
pub struct SearchMatch {
    pub line_number: usize,
    pub line_content: String,
}

#[derive(Debug, Clone)]
pub struct SearchResult {
    pub path: PathBuf,
    pub file_name: String,
    pub matches: Vec<SearchMatch>,
}

pub fn search_workspace(root: &PathBuf, query: &str) -> Vec<SearchResult> {
    use std::fs;

    use ignore::WalkBuilder;

    let query_lower = query.to_lowercase();
    let mut results = Vec::new();

    let walker = WalkBuilder::new(root).hidden(true).git_ignore(true).git_global(true).build();

    for entry in walker.flatten() {
        let path = entry.path();

        if !path.is_file() {
            continue;
        }

        let Ok(content) = fs::read_to_string(path) else {
            continue;
        };

        let mut matches = Vec::new();
        for (line_idx, line) in content.lines().enumerate() {
            if line.to_lowercase().contains(&query_lower) {
                matches.push(SearchMatch {
                    line_number: line_idx + 1,
                    line_content: line.to_string(),
                });
            }
        }

        if !matches.is_empty() {
            results.push(SearchResult {
                path: path.to_path_buf(),
                file_name: path.file_name().unwrap_or_default().to_string_lossy().to_string(),
                matches,
            });
        }
    }
    results
}

pub fn collect_all_files(root: &PathBuf) -> Vec<(String, PathBuf)> {
    use ignore::WalkBuilder;

    let mut files = Vec::new();

    let walker = WalkBuilder::new(root).hidden(true).git_ignore(true).git_global(true).build();

    for entry in walker.flatten() {
        let path = entry.path();
        if !path.is_file() {
            continue;
        }

        let display = path.strip_prefix(root).unwrap_or(path).to_string_lossy().to_string();

        files.push((display, path.to_path_buf()));
    }

    files
}

pub fn fuzzy_find_files(
    query: &str,
    files: &[(String, PathBuf)],
    max_results: usize,
) -> Vec<(i64, String, PathBuf)> {
    let matcher = SkimMatcherV2::default();

    let mut scored: Vec<(i64, String, PathBuf)> = files
        .iter()
        .filter_map(|(display, abs_path)| {
            matcher
                .fuzzy_match(display, query)
                .map(|score| (score, display.clone(), abs_path.clone()))
        })
        .collect();

    scored.sort_by_key(|b| Reverse(b.0));
    scored.truncate(max_results);
    scored
}