agent-file-tools 0.26.0

Agent File Tools — tree-sitter powered code analysis for AI agents
Documentation
use std::path::{Path, PathBuf};

use crate::protocol::Response;
use crate::search_index::resolve_search_scope;

pub(crate) enum SearchPathResolution {
    Single(PathBuf),
    Multi(Vec<PathBuf>),
}

pub(crate) fn resolve_path_or_multi<F>(
    raw: &str,
    project_root: &Path,
    validate: F,
) -> Result<SearchPathResolution, Response>
where
    F: Fn(&Path) -> Result<PathBuf, Response>,
{
    let validated = validate(Path::new(raw))?;
    let single_root = search_root(project_root, &validated);
    if single_root.exists() || !raw.chars().any(char::is_whitespace) {
        return Ok(SearchPathResolution::Single(single_root));
    }

    let fragments = raw.split_whitespace().collect::<Vec<_>>();
    if fragments.len() < 2 {
        return Ok(SearchPathResolution::Single(single_root));
    }

    let mut roots = Vec::with_capacity(fragments.len());
    for fragment in fragments {
        let validated = validate(Path::new(fragment))?;
        let root = search_root(project_root, &validated);
        if !root.exists() {
            return Ok(SearchPathResolution::Single(single_root));
        }
        roots.push(root);
    }

    let roots = dedupe_nested_paths(roots);
    if roots.len() == 1 {
        Ok(SearchPathResolution::Single(
            roots.into_iter().next().expect("one root"),
        ))
    } else {
        Ok(SearchPathResolution::Multi(roots))
    }
}

pub(crate) fn dedupe_nested_paths(paths: Vec<PathBuf>) -> Vec<PathBuf> {
    let mut keyed = Vec::new();
    for path in paths {
        let key = canonical_key(&path);
        if keyed.iter().any(|(_, existing_key)| existing_key == &key) {
            continue;
        }
        keyed.push((path, key));
    }

    let mut deduped = Vec::new();
    'outer: for (index, (path, key)) in keyed.iter().enumerate() {
        for (other_index, (_, other_key)) in keyed.iter().enumerate() {
            if index != other_index && key.starts_with(other_key) {
                continue 'outer;
            }
        }
        deduped.push(path.clone());
    }
    deduped
}

pub(crate) fn canonical_key(path: &Path) -> PathBuf {
    std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
}

fn search_root(project_root: &Path, validated: &Path) -> PathBuf {
    let path = validated.to_string_lossy();
    resolve_search_scope(project_root, Some(path.as_ref())).root
}