vtcode 0.169.3

A Rust-based terminal coding agent with modular architecture supporting multiple LLM providers
use std::fmt::Write as _;

use anyhow::{Context, Result, bail};
use toml::Value as TomlValue;

#[derive(Debug, Clone)]
pub(super) enum PathToken {
    Key(String),
    Index(usize),
}

pub(crate) fn parent_view_path(path: &str) -> Option<String> {
    if path.is_empty() {
        return None;
    }

    if path == super::SETTINGS_ADVANCED_VIEW_PATH {
        return None;
    }

    if let Some(group_view) = path.strip_prefix(super::SETTINGS_GROUP_PREFIX) {
        let (group_id, nested_path) = group_view.split_once(':')?;
        return Some(
            parent_view_path(nested_path)
                .map(|parent| format!("{}{group_id}:{parent}", super::SETTINGS_GROUP_PREFIX))
                .unwrap_or_else(|| format!("{}{group_id}", super::SETTINGS_GROUP_PREFIX)),
        );
    }

    if let Some(nested_path) = path.strip_prefix(super::SETTINGS_ADVANCED_NESTED_PREFIX) {
        return Some(
            parent_view_path(nested_path)
                .map(|parent| format!("{}{parent}", super::SETTINGS_ADVANCED_NESTED_PREFIX))
                .unwrap_or_else(|| super::SETTINGS_ADVANCED_VIEW_PATH.to_string()),
        );
    }

    if path.starts_with("advanced.") {
        return Some(super::SETTINGS_ADVANCED_VIEW_PATH.to_string());
    }

    parent_setting_path(path)
}

pub(super) fn parse_path_tokens(path: &str) -> Result<Vec<PathToken>> {
    let mut tokens = Vec::new();

    let mut position = 0;
    while position < path.len() {
        if path.as_bytes()[position] == b'.' {
            position += 1;
            continue;
        }

        if path.as_bytes()[position] == b'[' {
            tokens.push(parse_bracket_token(path, &mut position)?);
            continue;
        }

        let start = position;
        while position < path.len() {
            let character = path[position..]
                .chars()
                .next()
                .expect("position should remain on a character boundary");
            if matches!(character, '.' | '[') {
                break;
            }
            position += character.len_utf8();
        }
        if position > start {
            tokens.push(PathToken::Key(path[start..position].to_string()));
        }
    }

    Ok(tokens)
}

fn parse_bracket_token(path: &str, position: &mut usize) -> Result<PathToken> {
    debug_assert_eq!(path.as_bytes()[*position], b'[');
    *position += 1;

    if path.as_bytes().get(*position) == Some(&b'"') {
        *position += 1;
        let mut key = String::new();
        while *position < path.len() {
            let character = path[*position..]
                .chars()
                .next()
                .expect("position should remain on a character boundary");
            *position += character.len_utf8();

            match character {
                '"' => {
                    if path.as_bytes().get(*position) != Some(&b']') {
                        bail!("Invalid quoted path key: missing closing bracket");
                    }
                    *position += 1;
                    return Ok(PathToken::Key(key));
                }
                '\\' => {
                    let escaped = path[*position..]
                        .chars()
                        .next()
                        .ok_or_else(|| anyhow::anyhow!("Invalid quoted path key: dangling escape"))?;
                    *position += escaped.len_utf8();
                    key.push(escaped);
                }
                _ => key.push(character),
            }
        }
        bail!("Invalid quoted path key: missing closing quote")
    }

    let start = *position;
    while *position < path.len() && path.as_bytes()[*position] != b']' {
        *position += 1;
    }
    let Some(index_text) = path.get(start..*position) else {
        bail!("Invalid array index in path");
    };
    if *position == path.len() {
        bail!("Invalid path: missing closing bracket");
    }
    *position += 1;

    let index = index_text
        .parse::<usize>()
        .with_context(|| format!("Invalid array index '{index_text}'"))?;
    Ok(PathToken::Index(index))
}

pub(super) fn path_with_key(parent: &str, key: &str) -> String {
    let mut path = parent.to_string();
    append_key_path(&mut path, key);
    path
}

fn append_key_path(path: &mut String, key: &str) {
    if is_bare_key(key) {
        if !path.is_empty() {
            path.push('.');
        }
        path.push_str(key);
        return;
    }

    path.push_str("[\"");
    for character in key.chars() {
        match character {
            '\\' | '"' => {
                path.push('\\');
                path.push(character);
            }
            _ => path.push(character),
        }
    }
    path.push_str("\"]");
}

fn is_bare_key(key: &str) -> bool {
    !key.is_empty()
        && key
            .chars()
            .all(|character| character.is_ascii_alphanumeric() || matches!(character, '_' | '-'))
}

fn parent_setting_path(path: &str) -> Option<String> {
    let tokens = parse_path_tokens(path).ok()?;
    (tokens.len() > 1).then(|| format_path_tokens(&tokens[..tokens.len() - 1]))
}

fn format_path_tokens(tokens: &[PathToken]) -> String {
    let mut path = String::new();
    for token in tokens {
        match token {
            PathToken::Key(key) => append_key_path(&mut path, key),
            PathToken::Index(index) => {
                let _ = write!(path, "[{index}]");
            }
        }
    }
    path
}

pub(super) fn get_node<'a>(root: &'a TomlValue, path: &str) -> Option<&'a TomlValue> {
    let tokens = parse_path_tokens(path).ok()?;
    let mut current = root;

    for token in tokens {
        match token {
            PathToken::Key(key) => {
                let TomlValue::Table(table) = current else {
                    return None;
                };
                current = table.get(&key)?;
            }
            PathToken::Index(index) => {
                let TomlValue::Array(entries) = current else {
                    return None;
                };
                current = entries.get(index)?;
            }
        }
    }

    Some(current)
}

pub(super) fn get_node_mut<'a>(root: &'a mut TomlValue, path: &str) -> Option<&'a mut TomlValue> {
    let tokens = parse_path_tokens(path).ok()?;
    let mut current = root;

    for token in tokens {
        match token {
            PathToken::Key(key) => {
                let TomlValue::Table(table) = current else {
                    return None;
                };
                current = table.get_mut(&key)?;
            }
            PathToken::Index(index) => {
                let TomlValue::Array(entries) = current else {
                    return None;
                };
                current = entries.get_mut(index)?;
            }
        }
    }

    Some(current)
}

pub(super) fn set_node(root: &mut TomlValue, path: &str, value: TomlValue) -> Result<()> {
    let tokens = parse_path_tokens(path)?;
    if tokens.is_empty() {
        bail!("Settings path '{path}' was not found");
    }

    set_node_tokens(root, &tokens, value, path)
}

fn set_node_tokens(current: &mut TomlValue, tokens: &[PathToken], value: TomlValue, path: &str) -> Result<()> {
    let Some(token) = tokens.first() else {
        *current = value;
        return Ok(());
    };

    match token {
        PathToken::Key(key) => {
            let TomlValue::Table(table) = current else {
                bail!("Settings path '{path}' traverses a non-table value");
            };

            if tokens.len() == 1 {
                table.insert(key.clone(), value);
                return Ok(());
            }

            let child = table.entry(key.clone()).or_insert_with(|| match tokens[1] {
                PathToken::Key(_) => TomlValue::Table(toml::map::Map::new()),
                PathToken::Index(_) => TomlValue::Array(Vec::new()),
            });
            set_node_tokens(child, &tokens[1..], value, path)
        }
        PathToken::Index(index) => {
            let TomlValue::Array(entries) = current else {
                bail!("Settings path '{path}' traverses a non-array value");
            };
            let entry = entries
                .get_mut(*index)
                .ok_or_else(|| anyhow::anyhow!("Settings path '{path}' references a missing array item"))?;
            set_node_tokens(entry, &tokens[1..], value, path)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn set_node_updates_nested_array_value() {
        let mut root: TomlValue = toml::from_str(
            r#"
            [[hooks.lifecycle.pre_tool_use]]
            hooks = ["before"]
            "#,
        )
        .expect("valid TOML");

        set_node(&mut root, "hooks.lifecycle.pre_tool_use[0].hooks[0]", TomlValue::String("after".to_string()))
            .expect("nested array value should be updated");

        assert_eq!(
            get_node(&root, "hooks.lifecycle.pre_tool_use[0].hooks[0]").and_then(TomlValue::as_str),
            Some("after")
        );
    }

    #[test]
    fn set_node_creates_missing_tables() {
        let mut root = TomlValue::Table(toml::Table::new());

        set_node(&mut root, "agent.small_model.model", TomlValue::String("small-model".to_string()))
            .expect("missing tables should be created");

        assert_eq!(get_node(&root, "agent.small_model.model").and_then(TomlValue::as_str), Some("small-model"));
    }

    #[test]
    fn quoted_map_keys_round_trip_through_path_operations() {
        let mut root: TomlValue = toml::from_str(
            r#"
            [profiles."gpt-5.4"]
            temperature = 0.2
            "#,
        )
        .expect("valid TOML");

        let path = r#"profiles["gpt-5.4"].temperature"#;
        assert_eq!(get_node(&root, path).and_then(TomlValue::as_float), Some(0.2));
        assert_eq!(
            parent_setting_path(r#"profiles["gpt-5.4"].temperature"#),
            Some(r#"profiles["gpt-5.4"]"#.to_string())
        );

        set_node(&mut root, path, TomlValue::Float(0.7)).expect("quoted map key should be writable");
        assert_eq!(get_node(&root, path).and_then(TomlValue::as_float), Some(0.7));

        let unicode_tokens = parse_path_tokens("profiles.模型").expect("unicode path should parse");
        assert!(matches!(
            unicode_tokens.as_slice(),
            [PathToken::Key(root), PathToken::Key(key)] if root == "profiles" && key == "模型"
        ));
    }
}