use std::collections::HashMap;
use super::schema;
const SOURCE: &str = include_str!("../../Documentation/CONFIGURATION.md");
include!(concat!(env!("OUT_DIR"), "/config_help.rs"));
pub struct HelpIndex {
curated: HashMap<String, String>,
doc_comments: HashMap<String, String>,
}
impl HelpIndex {
pub fn build() -> Self {
let mut curated: HashMap<String, String> = HashMap::new();
for raw_line in SOURCE.lines() {
let line = raw_line.trim_start();
if !line.starts_with('|') {
continue;
}
if line.contains("---") {
continue;
}
let cells: Vec<&str> =
line.split('|').map(str::trim).collect();
if cells.len() < 4 {
continue;
}
let key_cell = cells[1];
let Some(path) = extract_path(key_cell) else {
continue;
};
let body = format!(
"**`{path}`**\n\n{}",
cells[3..].join(" · "),
);
curated.insert(path.to_string(), body);
}
let mut doc_comments: HashMap<String, String> = HashMap::new();
for (path, doc) in FIELD_DOCS {
doc_comments.insert((*path).to_string(), (*doc).to_string());
}
Self {
curated,
doc_comments,
}
}
pub fn lookup(&self, path: &str) -> Option<&str> {
if let Some(body) = self.curated.get(path) {
return Some(body.as_str());
}
if let Some(body) = self.doc_comments.get(path) {
return Some(body.as_str());
}
if let Some(canonical) = canonicalise_map_path(path) {
if let Some(body) = self.doc_comments.get(&canonical) {
return Some(body.as_str());
}
if let Some(body) = self.curated.get(&canonical) {
return Some(body.as_str());
}
}
let mut remainder = path;
while let Some(idx) = remainder.find('.') {
remainder = &remainder[idx + 1..];
if let Some(body) = self.curated.get(remainder) {
return Some(body.as_str());
}
if let Some(body) = self.doc_comments.get(remainder) {
return Some(body.as_str());
}
}
if let Some(last) = path.rsplit('.').next() {
if let Some(body) = self.curated.get(last) {
return Some(body.as_str());
}
if let Some(body) = self.doc_comments.get(last) {
return Some(body.as_str());
}
}
None
}
#[allow(dead_code)]
pub fn entry_count(&self) -> usize {
self.curated.len() + self.doc_comments.len()
}
}
fn canonicalise_map_path(path: &str) -> Option<String> {
let parts: Vec<&str> = path.split('.').collect();
if parts.len() < 2 {
return None;
}
for end in (1..parts.len()).rev() {
let prefix = parts[..end].join(".");
if schema::is_known_map_path(&prefix) {
let mut out_parts: Vec<String> =
parts[..end].iter().map(|s| s.to_string()).collect();
out_parts.push("<entry>".to_string());
for p in &parts[end + 1..] {
out_parts.push(p.to_string());
}
return Some(out_parts.join("."));
}
}
None
}
fn extract_path(cell: &str) -> Option<&str> {
let start = cell.find('`')?;
let rest = &cell[start + 1..];
let end = rest.find('`')?;
Some(&rest[..end])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_indexes_known_fields() {
let idx = HelpIndex::build();
assert!(idx.entry_count() > 30, "got {}", idx.entry_count());
let probes = [
"editor.autosave_seconds",
"autosave_seconds",
"language",
"pov_chip_enabled",
];
let hit_count = probes.iter().filter(|p| idx.lookup(p).is_some()).count();
assert!(hit_count >= 1, "no doc rows matched any probe");
}
#[test]
fn lookup_walks_up_dotted_path() {
let mut curated: HashMap<String, String> = HashMap::new();
curated.insert("wrap".to_string(), "wrap doc".into());
let idx = HelpIndex {
curated,
doc_comments: HashMap::new(),
};
assert_eq!(idx.lookup("editor.wrap"), Some("wrap doc"));
}
#[test]
fn extract_path_handles_simple_row() {
assert_eq!(
extract_path("`editor.autosave_seconds`"),
Some("editor.autosave_seconds")
);
}
#[test]
fn doc_comment_extraction_covers_undocumented_field() {
let idx = HelpIndex::build();
let hit = idx.lookup("ai.diff_review_on_apply");
assert!(hit.is_some(), "expected build-time doc-comment fallback for ai.diff_review_on_apply");
let body = hit.unwrap();
assert!(body.contains("AI rewrite") || body.contains("diff"));
}
#[test]
fn canonicalise_map_path_substitutes_entry_name() {
let canon = canonicalise_map_path("llm.providers.gemini.model");
assert_eq!(canon, Some("llm.providers.<entry>.model".to_string()));
}
#[test]
fn canonicalise_returns_none_outside_map_paths() {
assert!(canonicalise_map_path("editor.autosave_seconds").is_none());
assert!(canonicalise_map_path("language").is_none());
}
}