vtcode 0.99.1

A Rust-based terminal coding agent with modular architecture supporting multiple LLM providers
use toml::Value as TomlValue;
use vtcode_tui::app::{InlineListItem, InlineListSelection};

use crate::agent::runloop::unified::config_section_headings::{
    heading_for_path, humanize_identifier,
};

use super::docs::FieldDoc;
pub(super) fn display_title(label: &str, path: &str, value: &TomlValue) -> String {
    if label.starts_with('[') {
        return format!("Item {}", label);
    }

    match value {
        TomlValue::Table(_) => heading_for_path(path).title.into_owned(),
        _ => humanize_identifier(label),
    }
}

pub(super) fn section_subtitle(path: &str, value: &TomlValue) -> String {
    let heading = heading_for_path(path);
    let count = count_leaf_entries(value);
    let mut parts = Vec::new();
    if !heading.summary.is_empty() {
        parts.push(heading.summary.into_owned());
    }
    parts.push(format!(
        "{} setting{}",
        count,
        if count == 1 { "" } else { "s" }
    ));
    parts.join("")
}

pub(super) fn setting_subtitle(summary: &str, description: &str, adjustable: bool) -> String {
    let value_display = if adjustable {
        format!("<- {} ->", summary)
    } else {
        summary.to_string()
    };
    let mut parts = vec![value_display];
    if !description.is_empty() {
        parts.push(description.to_string());
    }
    parts.join("")
}

pub(super) fn collection_subtitle(summary: String, description: &str) -> String {
    let mut parts = vec![summary];
    if !description.is_empty() {
        parts.push(description.to_string());
    }
    parts.join("")
}

pub(super) fn search_value_for_missing_doc(
    path: &str,
    label: &str,
    doc: Option<&FieldDoc>,
) -> String {
    let mut parts = vec![path.to_string(), label.to_string(), "unset".to_string()];
    if let Some(doc) = doc {
        if !doc.description.is_empty() {
            parts.push(doc.description.clone());
        }
        if !doc.options.is_empty() {
            parts.push(doc.options.join(" "));
        }
    }
    parts.join(" ").to_ascii_lowercase()
}

pub(super) fn section_item(label: &str) -> InlineListItem {
    InlineListItem {
        title: label.to_string(),
        subtitle: None,
        badge: None,
        indent: 0,
        selection: None,
        search_value: None,
    }
}

pub(super) fn action_item(
    title: &str,
    subtitle: &str,
    badge: Option<&str>,
    action: &str,
) -> InlineListItem {
    InlineListItem {
        title: title.to_string(),
        subtitle: Some(subtitle.to_string()),
        badge: badge.map(str::to_string),
        indent: 0,
        selection: Some(InlineListSelection::ConfigAction(action.to_string())),
        search_value: Some(format!("{} {}", title, subtitle)),
    }
}

pub(super) fn search_value_with_content(
    path: &str,
    label: &str,
    value: &TomlValue,
    doc: Option<&FieldDoc>,
) -> String {
    let mut parts = vec![
        path.to_string(),
        label.to_string(),
        display_title(label, path, value),
    ];

    if matches!(value, TomlValue::Table(_)) {
        let heading = heading_for_path(path);
        if !heading.summary.is_empty() {
            parts.push(heading.summary.into_owned());
        }
    }

    collect_search_terms(path, value, &mut parts);

    if let Some(doc) = doc {
        if !doc.description.is_empty() {
            parts.push(doc.description.clone());
        }
        if !doc.options.is_empty() {
            parts.push(doc.options.join(" "));
        }
    }
    parts.join(" ").to_ascii_lowercase()
}

fn collect_search_terms(path: &str, value: &TomlValue, parts: &mut Vec<String>) {
    match value {
        TomlValue::String(value) => parts.push(value.clone()),
        TomlValue::Integer(value) => parts.push(value.to_string()),
        TomlValue::Float(value) => parts.push(value.to_string()),
        TomlValue::Boolean(value) => {
            parts.push(value.to_string());
            parts.push(if *value { "on" } else { "off" }.to_string());
        }
        TomlValue::Array(values) => {
            for (index, child) in values.iter().enumerate() {
                let child_path = format!("{}[{}]", path, index);
                parts.push(child_path.clone());
                collect_search_terms(&child_path, child, parts);
            }
        }
        TomlValue::Table(table) => {
            for key in sorted_table_keys(table) {
                let Some(child) = table.get(key) else {
                    continue;
                };
                let child_path = format!("{}.{}", path, key);
                parts.push(child_path.clone());
                parts.push(humanize_identifier(key));
                collect_search_terms(&child_path, child, parts);
            }
        }
        _ => {}
    }
}

pub(super) fn sorted_table_keys(table: &toml::map::Map<String, TomlValue>) -> Vec<&String> {
    let mut keys: Vec<&String> = table.keys().collect();
    keys.sort();
    keys
}

pub(super) fn count_leaf_entries(value: &TomlValue) -> usize {
    match value {
        TomlValue::Table(table) => table.values().map(count_leaf_entries).sum(),
        TomlValue::Array(values) => values.len().max(1),
        _ => 1,
    }
}

pub(super) fn summarize_value(value: &TomlValue) -> String {
    match value {
        TomlValue::String(text) => format!("\"{}\"", truncate_middle(text, 48)),
        TomlValue::Integer(number) => number.to_string(),
        TomlValue::Float(number) => number.to_string(),
        TomlValue::Boolean(value) => {
            if *value {
                "On".to_string()
            } else {
                "Off".to_string()
            }
        }
        TomlValue::Array(values) => format!(
            "{} item{}",
            values.len(),
            if values.len() == 1 { "" } else { "s" }
        ),
        TomlValue::Table(_) => {
            let count = count_leaf_entries(value);
            format!("{} setting{}", count, if count == 1 { "" } else { "s" })
        }
        _ => "<unsupported>".to_string(),
    }
}

fn truncate_middle(value: &str, max_len: usize) -> String {
    let total_chars = value.chars().count();
    if total_chars <= max_len {
        return value.to_string();
    }
    if max_len <= 3 {
        return "...".to_string();
    }

    let prefix_len = max_len / 2;
    let suffix_len = max_len.saturating_sub(prefix_len + 3);
    let prefix: String = value.chars().take(prefix_len).collect();
    let suffix: String = value
        .chars()
        .skip(total_chars.saturating_sub(suffix_len))
        .collect();
    format!("{prefix}...{suffix}")
}