feat: add icon_index module for recursive SVG listing and fuzzy search

This commit is contained in:
2026-08-03 19:11:29 -04:00
parent 3296e495fe
commit 06d1204aac
2 changed files with 90 additions and 0 deletions
+89
View File
@@ -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
View File
@@ -1,5 +1,6 @@
mod color_scheme;
mod config;
mod icon_index;
use gtk::prelude::*;
use gtk::{glib, Application, ApplicationWindow};