feat: add icon_index module for recursive SVG listing and fuzzy search
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub fn list_svg_icons(root: &Path) -> Vec<PathBuf> {
|
||||
WalkDir::new(root)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.file_type().is_file())
|
||||
.filter(|e| e.path().extension().and_then(|s| s.to_str()) == Some("svg"))
|
||||
.map(|e| e.path().to_path_buf())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn fuzzy_match(query: &str, candidate: &str) -> bool {
|
||||
if query.is_empty() {
|
||||
return true;
|
||||
}
|
||||
let candidate_lower = candidate.to_lowercase();
|
||||
let mut chars = candidate_lower.chars();
|
||||
for q in query.to_lowercase().chars() {
|
||||
if !chars.any(|c| c == q) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn filter_icons<'a>(icons: &'a [PathBuf], query: &str, limit: usize) -> Vec<&'a PathBuf> {
|
||||
icons
|
||||
.iter()
|
||||
.filter(|p| fuzzy_match(query, &p.to_string_lossy()))
|
||||
.take(limit)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fuzzy_match_empty_query_matches_everything() {
|
||||
assert!(fuzzy_match("", "anything"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzzy_match_is_case_insensitive_subsequence() {
|
||||
assert!(fuzzy_match("cls", "actions/16/edit-clear-symbolic.svg"));
|
||||
assert!(fuzzy_match("CLS", "actions/16/edit-clear-symbolic.svg"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzzy_match_rejects_out_of_order_or_missing_chars() {
|
||||
assert!(!fuzzy_match("zzz", "actions/16/edit-clear-symbolic.svg"));
|
||||
assert!(!fuzzy_match("scl", "edit-clear-symbolic.svg")); // 's' comes after "cl" here, wrong order
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_icons_applies_query_and_limit() {
|
||||
let icons = vec![
|
||||
PathBuf::from("a/edit-clear.svg"),
|
||||
PathBuf::from("b/edit-copy.svg"),
|
||||
PathBuf::from("c/folder.svg"),
|
||||
];
|
||||
|
||||
let matches = filter_icons(&icons, "edit", 10);
|
||||
assert_eq!(matches.len(), 2);
|
||||
|
||||
let limited = filter_icons(&icons, "", 1);
|
||||
assert_eq!(limited.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_svg_icons_finds_only_svg_files_recursively() {
|
||||
let dir = std::env::temp_dir().join(format!(
|
||||
"icon-color-tool-test-iconindex-{}",
|
||||
std::process::id()
|
||||
));
|
||||
let sub = dir.join("actions/16");
|
||||
std::fs::create_dir_all(&sub).unwrap();
|
||||
std::fs::write(sub.join("edit-clear.svg"), "<svg></svg>").unwrap();
|
||||
std::fs::write(sub.join("readme.txt"), "not an icon").unwrap();
|
||||
|
||||
let found = list_svg_icons(&dir);
|
||||
assert_eq!(found.len(), 1);
|
||||
assert!(found[0].ends_with("edit-clear.svg"));
|
||||
|
||||
std::fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
mod color_scheme;
|
||||
mod config;
|
||||
mod icon_index;
|
||||
|
||||
use gtk::prelude::*;
|
||||
use gtk::{glib, Application, ApplicationWindow};
|
||||
|
||||
Reference in New Issue
Block a user