rho-coding-agent 0.26.0

A lightweight agent harness inspired by Pi
Documentation
use super::{App, CommandChoice, CommandChoiceKind};
use crate::commands;

impl App {
    pub(super) fn command_matches(&self) -> Vec<CommandChoice> {
        let Some(prefix) = commands::command_prefix(&self.input) else {
            return Vec::new();
        };
        let prefix = prefix
            .strip_prefix('/')
            .unwrap_or(prefix)
            .to_ascii_lowercase();
        let mut matches = commands::matching_commands(&prefix)
            .into_iter()
            .map(|command| CommandChoice {
                name: command.name.to_string(),
                usage: command.usage.to_string(),
                description: command.description.to_string(),
                kind: CommandChoiceKind::Builtin(command),
            })
            .collect::<Vec<_>>();
        let mut template_matches = self
            .info
            .prompt_templates
            .iter()
            .filter(|(name, _)| crate::prompt_templates::matches_search(name, &prefix))
            .map(|(name, template)| {
                let command_name = format!("prompt:{name}");
                CommandChoice {
                    usage: format!("/{command_name} [text]"),
                    name: command_name,
                    description: crate::prompt_templates::description(template),
                    kind: CommandChoiceKind::PromptTemplate(template.clone()),
                }
            })
            .collect::<Vec<_>>();
        if let Some(index) = template_matches.iter().position(|choice| {
            choice
                .name
                .strip_prefix("prompt:")
                .is_some_and(|name| name.eq_ignore_ascii_case(&prefix))
        }) {
            let exact = template_matches.remove(index);
            matches.insert(0, exact);
        }
        matches.extend(template_matches);
        matches.extend(
            crate::skills::discover(&self.info.cwd)
                .into_iter()
                .filter(|skill| {
                    skill.name.starts_with(&prefix)
                        || format!("skill:{}", skill.name).starts_with(&prefix)
                })
                .map(|skill| {
                    let command_name = format!("skill:{}", skill.name);
                    CommandChoice {
                        usage: format!("/{command_name}"),
                        name: command_name,
                        description: skill.description,
                        kind: CommandChoiceKind::Skill,
                    }
                }),
        );
        matches
    }

    pub(super) fn selected_command(&self) -> Option<CommandChoice> {
        let matches = self.command_matches();
        matches
            .get(self.command_selection.min(matches.len().saturating_sub(1)))
            .cloned()
    }

    pub(super) fn complete_command_choice(&mut self, choice: &CommandChoice) {
        let (input, cursor) = match &choice.kind {
            CommandChoiceKind::Builtin(spec) => {
                self.input_submission_mode = super::InputSubmissionMode::ParseCommands;
                commands::complete_command(&self.input, self.input_cursor, spec)
            }
            CommandChoiceKind::PromptTemplate(template) => {
                let expanded_input = self.expanded_input();
                let mut input = crate::prompt_templates::expand(
                    template,
                    super::slash_command_args(&expanded_input),
                );
                input.push(' ');
                let cursor = input.chars().count();
                self.paste_segments.clear();
                self.input_submission_mode = super::InputSubmissionMode::Prompt;
                (input, cursor)
            }
            CommandChoiceKind::Skill => {
                self.input_submission_mode = super::InputSubmissionMode::ParseCommands;
                super::complete_slash_command(&self.input, self.input_cursor, &choice.name)
            }
        };
        self.input = input;
        self.input_cursor = cursor;
    }
}

#[cfg(test)]
#[path = "command_palette_tests.rs"]
mod tests;