rho-coding-agent 1.19.2

A lightweight agent harness inspired by Pi
Documentation
use crate::keybindings::Keybindings;

use super::{
    picker_overlay::OverlayChrome, App, ComposerMode, PickerAction, PickerBadge, PickerBadgeTone,
    PickerItem, PickerLayout, UiPicker,
};

pub(super) fn help_picker(keybindings: &Keybindings) -> UiPicker {
    let items = help_items(keybindings);
    UiPicker::new(
        "Keyboard shortcuts",
        "type regex filter, enter or esc closes",
        items,
        PickerAction::Dismiss,
    )
    .with_layout(PickerLayout::Overlay)
    .with_overlay_chrome(OverlayChrome {
        nav_label: " KEYS".into(),
        detail_label: Some(" DETAILS".into()),
        nav_keys_hint: "↑↓ keys".into(),
    })
    .with_confirm_verb("close")
}

fn help_items(keybindings: &Keybindings) -> Vec<PickerItem> {
    let reset = keybindings.reset_conversation.to_string();
    let editor = keybindings.open_editor.to_string();
    let jump = keybindings.jump_to_bottom.to_string();
    let toggle_tools = keybindings.toggle_tool_output.to_string();
    let newline = keybindings.insert_newline.to_string();
    let paste_image = keybindings.paste_image.to_string();
    let edit_pending = keybindings.edit_pending_input.to_string();
    let manage_pending = keybindings.manage_pending_input.to_string();

    vec![
        entry(
            "/",
            "Show commands",
            "Type / at the start of the composer to open the command palette. Keep typing to filter, tab to complete, and enter to run.",
        ),
        entry(
            "@",
            "Reference file",
            "Type @ to open workspace file path autocomplete. Keep typing to fuzzy-search, then tab or enter to insert an @path reference.",
        ),
        entry(
            "!",
            "Run shell",
            "Prefix a command with ! to run it in the configured inline shell. Output is shown in the transcript and included in model context.",
        ),
        entry(
            "!!",
            "Run local shell",
            "Prefix a command with !! to run it locally without adding the output to model context. The composer uses a distinct label for this mode.",
        ),
        entry(
            "enter",
            "Send, run, steer",
            "Send the composer contents. While a model turn is running, enter queues a steering message for after the current assistant turn. In pickers, enter confirms the selection.",
        ),
        entry(
            "esc",
            "Cancel or close",
            "Abort the current model response and restore queued work, cancel a running inline shell command, leave shell mode, or close an open palette or picker.",
        ),
        entry(
            "shift+tab",
            "Cycle reasoning",
            "Move through the configured reasoning levels for the active model and save the selection.",
        ),
        entry(
            "ctrl+c",
            "Clear composer",
            "Clear the message box on the first press. While idle, press again to quit Rho.",
        ),
        entry(
            newline,
            "New line",
            "Insert a newline in the composer without sending. shift+enter also inserts a newline. While idle, alt+enter inserts a newline too.",
        ),
        entry(
            "shift+enter",
            "New line",
            "Insert a newline in the composer without sending.",
        ),
        entry(
            "alt+enter",
            "Queue/newline",
            "While idle, insert a newline. While a model turn is running, queue the current composer contents to run after the turn finishes.",
        ),
        entry(
            paste_image,
            "Paste image",
            "Paste a clipboard image as an attachment when a supported host helper is available. alt+v is also accepted as a fallback.",
        ),
        entry(
            reset,
            "Reset chat",
            "Clear conversation history so the next message starts a new session. Unavailable while a model turn is running.",
        ),
        entry(
            editor,
            "External editor",
            "Open the composer contents in VISUAL, falling back to EDITOR when VISUAL is unset or empty. Warns with EDITOR is not set when neither is configured. Rho restores its screen when the editor exits.",
        ),
        entry(
            jump,
            "Jump to bottom",
            "Jump the transcript viewport back to the live bottom and resume following new output. Also available from the on-screen jump control when scrolled up.",
        ),
        entry(
            toggle_tools,
            "Toggle tools",
            "Expand or collapse the latest tool output block when output is truncated.",
        ),
        entry(
            edit_pending,
            "Edit queued",
            "Pull the most recent queued or steering prompt back into the composer for editing.",
        ),
        entry(
            manage_pending,
            "Manage queue",
            "Open the pending-input panel to inspect, reorder, or remove queued prompts.",
        ),
        entry(
            "up / down",
            "History or nav",
            "In the composer, re-enter previous prompts. In pickers and palettes, move the selection.",
        ),
        entry(
            "pgup / pgdn",
            "Scroll view",
            "Scroll the transcript viewport. In overlay pickers, page through the focused nav or detail pane.",
        ),
        entry(
            "mouse wheel",
            "Scroll view",
            "Scroll the transcript viewport with the mouse wheel.",
        ),
        entry(
            "click + drag",
            "Copy text",
            "Left-click and drag across transcript text to select it. Releasing copies the selection to the clipboard.",
        ),
    ]
}

fn entry(keys: impl Into<String>, summary: &str, detail: &str) -> PickerItem {
    let keys = keys.into();
    PickerItem {
        section: None,
        label: keys.clone(),
        detail: Some(detail.into()),
        preview: None,
        badge: Some(PickerBadge {
            text: summary.into(),
            tone: PickerBadgeTone::Selected,
        }),
        value: keys,
    }
}

impl App {
    pub(super) fn execute_help_command(&mut self) -> anyhow::Result<()> {
        self.input_ui.set_composer(ComposerMode::Picker(help_picker(
            &self.info.runtime.keybindings,
        )));
        self.status = "keyboard shortcuts".into();
        Ok(())
    }
}

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