use crate::{commands::CommandRegistry, skills::SkillDiscovery, tui::state};
use std::path::{Component, Path};
pub(crate) fn tui_autocomplete_candidates(
commands: &CommandRegistry,
) -> Vec<state::AutocompleteCandidate> {
let mut candidates = commands
.list()
.into_iter()
.map(|command| {
state::AutocompleteCandidate::slash_command(
command.name.clone(),
command.description.clone(),
)
})
.collect::<Vec<_>>();
candidates.extend(tui_context_injection_autocomplete_candidates());
candidates
}
fn tui_context_injection_autocomplete_candidates() -> Vec<state::AutocompleteCandidate> {
crate::agent::prompt_injections::definitions()
.iter()
.map(|definition| {
state::AutocompleteCandidate::context_injection(definition.name, definition.description)
})
.collect()
}
pub(crate) fn tui_skill_autocomplete_candidates(
skills: &SkillDiscovery,
) -> Vec<state::AutocompleteCandidate> {
skills
.skills
.iter()
.map(|(name, skill)| {
state::AutocompleteCandidate::skill_tag_with_argument_hint(
name.clone(),
skill.frontmatter.get("argument-hint").map(String::as_str),
)
})
.collect()
}
pub(crate) fn refresh_skill_autocomplete_candidates(
candidates: &mut Vec<state::AutocompleteCandidate>,
skills: &SkillDiscovery,
) {
candidates.retain(|candidate| candidate.kind != state::AutocompleteKind::SkillTag);
candidates.extend(tui_skill_autocomplete_candidates(skills));
}
pub(crate) fn refresh_file_autocomplete_candidates(
candidates: &mut Vec<state::AutocompleteCandidate>,
cwd: &Path,
respect_gitignore: bool,
) {
candidates.retain(|candidate| candidate.kind != state::AutocompleteKind::FileTag);
candidates.extend(tui_file_autocomplete_candidates(cwd, respect_gitignore));
}
const MAX_FILE_AUTOCOMPLETE_CANDIDATES: usize = 2000;
const MAX_FILE_AUTOCOMPLETE_VISITED_ENTRIES: usize = 10_000;
pub(crate) fn tui_file_autocomplete_candidates(
cwd: &Path,
respect_gitignore: bool,
) -> Vec<state::AutocompleteCandidate> {
tui_file_autocomplete_candidates_with_limits(
cwd,
respect_gitignore,
MAX_FILE_AUTOCOMPLETE_CANDIDATES,
MAX_FILE_AUTOCOMPLETE_VISITED_ENTRIES,
)
}
fn tui_file_autocomplete_candidates_with_limits(
cwd: &Path,
respect_gitignore: bool,
max_candidates: usize,
max_visited_entries: usize,
) -> Vec<state::AutocompleteCandidate> {
if max_candidates == 0 || max_visited_entries == 0 {
return Vec::new();
}
let Ok(root) = cwd.canonicalize() else {
return Vec::new();
};
let mut paths = crate::tools::autocomplete_file_paths(
&root,
respect_gitignore,
max_candidates,
max_visited_entries,
)
.unwrap_or_default()
.iter()
.filter_map(|path| path.strip_prefix(&root).ok())
.map(slash_normalized_relative_path)
.collect::<Vec<_>>();
paths.sort();
paths.dedup();
paths
.into_iter()
.map(state::AutocompleteCandidate::file_tag)
.collect()
}
fn slash_normalized_relative_path(path: &Path) -> String {
path.components()
.filter_map(|component| match component {
Component::Normal(value) => Some(value.to_string_lossy().into_owned()),
_ => None,
})
.collect::<Vec<_>>()
.join("/")
}