rho-coding-agent 1.16.0

A lightweight agent harness inspired by Pi
Documentation
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};

use super::{
    questionnaire::{QuestionnaireComposer, QuestionnaireEnterAction},
    questionnaire_notice_text, App, ComposerMode, Entry, HerdrUserWait, QuestionAnswerRequest,
};

impl App {
    pub(super) fn handle_questionnaire_key(&mut self, key: KeyEvent) -> anyhow::Result<bool> {
        if !matches!(self.input_ui.composer(), ComposerMode::Questionnaire(_)) {
            return Ok(false);
        }

        match (key.modifiers, key.code) {
            (KeyModifiers::CONTROL, KeyCode::Char('c')) => {
                if self.ctrl_c_streak == 0 {
                    if let Some(questionnaire) = self.questionnaire_mut() {
                        questionnaire.clear_active_answer();
                    }
                    self.status = "answer cleared; press ctrl-c again to cancel".into();
                    self.ctrl_c_streak = 1;
                } else {
                    self.cancel_questionnaire_answer();
                }
            }
            (KeyModifiers::ALT, KeyCode::Up) | (_, KeyCode::BackTab) => {
                if let Some(questionnaire) = self.questionnaire_mut() {
                    questionnaire.move_to_previous_field();
                }
                self.ctrl_c_streak = 0;
            }
            (KeyModifiers::ALT, KeyCode::Down) | (_, KeyCode::Tab) => {
                if let Some(questionnaire) = self.questionnaire_mut() {
                    questionnaire.move_to_next_field();
                }
                self.ctrl_c_streak = 0;
            }
            (KeyModifiers::ALT, KeyCode::Backspace) => {
                if let Some(questionnaire) = self.questionnaire_mut() {
                    questionnaire.delete_previous_word();
                }
                self.ctrl_c_streak = 0;
            }
            (KeyModifiers::NONE, KeyCode::Enter) => {
                let action = match self.questionnaire_mut() {
                    Some(questionnaire) => questionnaire.confirm_active_question(),
                    None => unreachable!("questionnaire mode checked before key handling"),
                };
                match action {
                    QuestionnaireEnterAction::Advance => {}
                    QuestionnaireEnterAction::Submit => self.submit_questionnaire_answer()?,
                }
                self.ctrl_c_streak = 0;
            }
            (_, KeyCode::Esc) => {
                self.cancel_questionnaire_answer();
                self.ctrl_c_streak = 0;
            }
            (_, KeyCode::Up) => {
                if let Some(questionnaire) = self.questionnaire_mut() {
                    questionnaire.move_up();
                }
                self.ctrl_c_streak = 0;
            }
            (_, KeyCode::Down) => {
                if let Some(questionnaire) = self.questionnaire_mut() {
                    questionnaire.move_down();
                }
                self.ctrl_c_streak = 0;
            }
            (_, KeyCode::Backspace) => {
                if let Some(questionnaire) = self.questionnaire_mut() {
                    questionnaire.backspace();
                }
                self.ctrl_c_streak = 0;
            }
            (_, KeyCode::Delete) => {
                if let Some(questionnaire) = self.questionnaire_mut() {
                    questionnaire.delete();
                }
                self.ctrl_c_streak = 0;
            }
            (KeyModifiers::ALT, KeyCode::Left) => {
                if let Some(questionnaire) = self.questionnaire_mut() {
                    questionnaire.move_text_cursor_previous_word();
                }
                self.ctrl_c_streak = 0;
            }
            (KeyModifiers::ALT, KeyCode::Right) => {
                if let Some(questionnaire) = self.questionnaire_mut() {
                    questionnaire.move_text_cursor_next_word();
                }
                self.ctrl_c_streak = 0;
            }
            (_, KeyCode::Left) => {
                if let Some(questionnaire) = self.questionnaire_mut() {
                    questionnaire.move_cursor_left();
                }
                self.ctrl_c_streak = 0;
            }
            (_, KeyCode::Right) => {
                if let Some(questionnaire) = self.questionnaire_mut() {
                    questionnaire.move_cursor_right();
                }
                self.ctrl_c_streak = 0;
            }
            (_, KeyCode::Home) => {
                if let Some(questionnaire) = self.questionnaire_mut() {
                    questionnaire.move_cursor_home();
                }
                self.ctrl_c_streak = 0;
            }
            (_, KeyCode::End) => {
                if let Some(questionnaire) = self.questionnaire_mut() {
                    questionnaire.move_cursor_end();
                }
                self.ctrl_c_streak = 0;
            }
            (KeyModifiers::CONTROL, KeyCode::Char('j')) | (KeyModifiers::ALT, KeyCode::Enter) => {
                if let Some(questionnaire) = self.questionnaire_mut() {
                    questionnaire.insert_char('\n');
                }
                self.ctrl_c_streak = 0;
            }
            (modifiers, KeyCode::Enter) if modifiers.contains(KeyModifiers::SHIFT) => {
                if let Some(questionnaire) = self.questionnaire_mut() {
                    questionnaire.insert_char('\n');
                }
                self.ctrl_c_streak = 0;
            }
            (_, KeyCode::Char(' ')) => {
                if let Some(questionnaire) = self.questionnaire_mut() {
                    if questionnaire.active_text_entry_active() {
                        questionnaire.insert_char(' ');
                    } else {
                        questionnaire.toggle_active_choice();
                    }
                }
                self.ctrl_c_streak = 0;
            }
            (modifiers, KeyCode::Char(ch))
                if !modifiers.intersects(KeyModifiers::CONTROL | KeyModifiers::ALT) =>
            {
                if let Some(questionnaire) = self.questionnaire_mut() {
                    questionnaire.insert_char(ch);
                }
                self.ctrl_c_streak = 0;
            }
            _ => {
                self.ctrl_c_streak = 0;
            }
        }
        self.input_ui.clear_paste_burst();
        Ok(true)
    }

    fn questionnaire_mut(&mut self) -> Option<&mut QuestionnaireComposer> {
        match self.input_ui.composer_mut() {
            ComposerMode::Questionnaire(questionnaire) => Some(questionnaire),
            _ => None,
        }
    }

    fn submit_questionnaire_answer(&mut self) -> anyhow::Result<()> {
        if let Some(display) = self.prepare_questionnaire_answer()? {
            self.insert_entry(&Entry::User(display));
        }
        Ok(())
    }

    fn prepare_questionnaire_answer(&mut self) -> anyhow::Result<Option<String>> {
        let ComposerMode::Questionnaire(mut questionnaire) = self.input_ui.take_composer() else {
            return Ok(None);
        };
        match questionnaire.submit() {
            Ok(submitted) => {
                let display = submitted.display;
                self.clear_submitted_input();
                self.status = "answers submitted".into();
                Ok(Some(display))
            }
            Err(error) => {
                self.input_ui
                    .set_composer(ComposerMode::Questionnaire(questionnaire));
                self.status = error;
                Ok(None)
            }
        }
    }

    fn cancel_questionnaire_answer(&mut self) {
        let ComposerMode::Questionnaire(mut questionnaire) = self.input_ui.take_composer() else {
            return;
        };
        questionnaire.cancel_by_user();
        self.ctrl_c_streak = 0;
        self.clear_submitted_input();
        self.status = "answer cancelled".into();
    }

    pub(super) async fn open_questionnaire(
        &mut self,
        request: QuestionAnswerRequest,
    ) -> std::io::Result<()> {
        self.finish_streams();
        self.clear_submitted_input();
        let notice = request
            .notice
            .unwrap_or_else(|| questionnaire_notice_text(&request.request));
        self.insert_entry(&Entry::Notice(notice));
        self.input_ui
            .set_composer(ComposerMode::Questionnaire(QuestionnaireComposer::new(
                request.request,
                request.response,
            )));
        self.status = HerdrUserWait::Questionnaire.message().into();
        self.report_herdr_waiting_for_user(HerdrUserWait::Questionnaire)
            .await;
        Ok(())
    }
}