use std::collections::HashSet;
use super::matcher::{Haystack, Query};
use crate::sidebar::SettingsTab;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ControlEntry {
pub label: String,
pub extra: Vec<String>,
}
impl ControlEntry {
fn haystack(&self) -> Haystack {
Haystack::of(
std::iter::once(self.label.as_str()).chain(self.extra.iter().map(String::as_str)),
)
}
}
#[derive(Debug, Clone, Default)]
pub struct SectionEntry {
pub tab: SettingsTab,
pub page: usize,
pub id: String,
pub title: String,
pub keywords: Vec<String>,
pub default_open: bool,
pub parent: Option<usize>,
pub controls: Vec<ControlEntry>,
}
impl SectionEntry {
fn haystack(&self) -> Haystack {
Haystack::of(
std::iter::once(self.title.as_str()).chain(self.keywords.iter().map(String::as_str)),
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Hit {
pub section: usize,
pub control: Option<usize>,
}
#[derive(Debug, Clone, Default)]
pub struct Registry {
pub sections: Vec<SectionEntry>,
}
impl Registry {
pub fn search(&self, query: &Query) -> Vec<Hit> {
if query.is_empty() {
return Vec::new();
}
let mut hits = Vec::new();
for (si, section) in self.sections.iter().enumerate() {
let meta = section.haystack();
let tab = Haystack::new(section.tab.display_name());
if query.matches_hays(&[&meta, &tab]) && query.any_token_in(&meta) {
hits.push(Hit {
section: si,
control: None,
});
}
for (ci, control) in section.controls.iter().enumerate() {
let own = control.haystack();
if query.matches_hays(&[&own, &meta, &tab]) && query.any_token_in(&own) {
hits.push(Hit {
section: si,
control: Some(ci),
});
}
}
}
hits
}
pub fn path(&self, section: usize) -> Vec<usize> {
let mut path = vec![section];
let mut at = section;
while let Some(parent) = self.sections.get(at).and_then(|s| s.parent) {
path.push(parent);
at = parent;
}
path
}
pub fn visible_sections(&self, query: &Query, hits: &[Hit]) -> HashSet<String> {
let mut visible = HashSet::new();
for hit in hits {
for at in self.path(hit.section) {
visible.insert(self.sections[at].id.clone());
}
if hit.control.is_none() {
for (i, _) in self.sections.iter().enumerate() {
if self.path(i).contains(&hit.section) {
visible.insert(self.sections[i].id.clone());
}
}
}
}
for section in &self.sections {
if tab_name_matches(section.tab, query) {
visible.insert(section.id.clone());
}
}
visible
}
pub fn known_sections(&self) -> HashSet<String> {
self.sections.iter().map(|s| s.id.clone()).collect()
}
pub fn control(&self, hit: Hit) -> Option<&ControlEntry> {
hit.control
.and_then(|c| self.sections.get(hit.section)?.controls.get(c))
}
pub fn tab_matches(&self, tab: SettingsTab, query: &Query, hits: &[Hit]) -> bool {
tab_name_matches(tab, query) || hits.iter().any(|h| self.sections[h.section].tab == tab)
}
}
pub fn tab_name_matches(tab: SettingsTab, query: &Query) -> bool {
!query.is_empty() && query.matches(tab.display_name())
}