1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
//! Async follow-ups that pointer events hand to the event loops.
//!
//! Pointer handling is sync and backend-generic, so a click that must submit,
//! confirm, or run a command records a [`PointerAction`] instead. The idle and
//! running loops take it right after the mouse event and run it here with the
//! terminal and runtime, through the same paths the equivalent keys use.
use ratatui::DefaultTerminal;
use super::{
app_state::PointerAction, command_actions::CommandSubmission, commands, App, ComposerMode,
InteractiveRuntime, TurnPrompt,
};
impl App {
/// Runs a pointer action from the idle loop.
pub(super) async fn run_idle_pointer_action(
&mut self,
action: PointerAction,
terminal: &mut DefaultTerminal,
agent: &mut InteractiveRuntime,
) -> anyhow::Result<()> {
match action {
PointerAction::ConfirmInlineChoice => {
if let ComposerMode::InlineChoice(modal) = self.input_ui.composer() {
let value = modal.choice.selected_value().to_owned();
self.resolve_inline_choice(value, terminal, agent).await?;
}
}
PointerAction::SubmitPicker => {
if matches!(self.input_ui.composer(), ComposerMode::Picker(_)) {
self.submit_picker_selection(terminal, agent).await?;
}
}
PointerAction::RunCommand(text) => {
if let Some(invocation) = self.pointer_command(&text) {
// The click carries its own command text and no media or
// pasted segments, so the composer draft stays untouched.
let submission = CommandSubmission::new(
invocation,
TurnPrompt::command(text.clone(), text),
Vec::new(),
Vec::new(),
);
self.execute_command(submission, terminal, agent).await?;
}
}
}
Ok(())
}
/// Runs a pointer action from the running-turn loop. Inline choices only
/// resolve while idle, so that request is dropped here; commands take the
/// during-turn dispatch, which reports the ones a turn blocks.
pub(super) async fn run_running_pointer_action(
&mut self,
action: PointerAction,
terminal: &mut DefaultTerminal,
) -> anyhow::Result<()> {
match action {
PointerAction::ConfirmInlineChoice => {}
PointerAction::SubmitPicker => {
if matches!(self.input_ui.composer(), ComposerMode::Picker(_)) {
self.submit_picker_selection_during_turn().await?;
}
}
PointerAction::RunCommand(text) => {
if let Some(invocation) = self.pointer_command(&text) {
self.execute_command_during_turn(invocation, terminal)
.await?;
}
}
}
Ok(())
}
/// Parses a pointer-issued command. Only an idle `Input` composer runs
/// one, so a click never discards a modal the user is in the middle of.
fn pointer_command(&mut self, text: &str) -> Option<commands::CommandInvocation> {
if !matches!(self.input_ui.composer(), ComposerMode::Input) {
return None;
}
match commands::parse_command(text) {
Ok(invocation) => invocation,
Err(error) => {
self.set_status(format!("could not run {text}: {error}"));
None
}
}
}
}