use super::super::*;
use super::startup::{draw_initial_frame, initial_prompt_text_and_cursor};
use super::{
DrainLoopBudgetState, MissionControlApp, PostDrainContext, PostDrainFlow, PostDrainProcessing,
actions::{self, AppControlFlow},
classify_prompt_key_burst_event, display_reducer, draw_frame, tui_perf_enabled,
};
struct RunLoopState {
last_area: ratatui::layout::Rect,
autocomplete_candidates: Vec<state::AutocompleteCandidate>,
redraw_intent: RedrawIntent,
pending_input_events: VecDeque<TerminalInputEvent>,
last_prompt_cursor_blink: Instant,
last_footer_git_branch_refresh: Instant,
last_fast_mode_rainbow: Option<Instant>,
clipboard: input::clipboard::Osc52Clipboard,
drain_budget_state: DrainLoopBudgetState,
input_priority_turn: bool,
diff_worker: crate::tui::diff::worker::DiffWorker,
}
enum TimerTick {
SkipIteration,
Proceed { animation_only: bool },
}
enum InputWait {
Received(TerminalInputEvent, Instant),
Retry,
Exit,
}
impl MissionControlApp {
pub(crate) fn run(&mut self, receiver: Receiver<TuiEvent>) -> Result<()> {
let image_cell_pixels = terminal::graphics::detect_cell_pixels();
let input_events = TerminalInputBridge::spawn();
let _terminal_guard = terminal::TerminalGuard::enter()?;
let mut terminal =
ratatui::Terminal::new(ratatui::backend::CrosstermBackend::new(std::io::stdout()))?;
let mut state = self.initial_ui_state(image_cell_pixels);
let mut run_loop = self.start_run_loop(&mut terminal, &mut state)?;
self.run_event_loop(
&mut terminal,
&mut state,
&mut run_loop,
&receiver,
&input_events,
)?;
self.startup_cancel.store(true, Ordering::SeqCst);
self.side.request_shutdown();
let _ = self.request_cancel_active_worker(&mut state);
Ok(())
}
pub(super) fn initial_ui_state(
&self,
image_cell_pixels: Option<(u16, u16)>,
) -> state::MissionControlState {
let provider = self.state.auth_state.provider().to_string();
let model = self.state.model.clone();
let thinking_levels = vec![self.config.thinking_level];
let (initial_prompt, initial_prompt_cursor) =
initial_prompt_text_and_cursor(self.pending_initial_prompt.as_deref());
let mut state = state::MissionControlState {
provider,
model,
provider_ready: self.state.auth_state.is_ready(),
color_enabled: self.appearance.policy.color_enabled,
reduced_motion: self.appearance.reduced_motion,
theme: crate::tui::theme::MissionControlTheme::from_runtime_appearance(
&self.appearance,
0,
),
fast_mode_enabled: self.settings.fast.enabled,
compaction_settings: self.settings.compaction.clone(),
fast_mode_capable: false,
fast_mode_effective: false,
thinking_level: self.config.thinking_level,
thinking_levels: thinking_levels.clone(),
prompt_editor: state::PromptEditor::from_canonical(
&initial_prompt,
initial_prompt_cursor,
),
focus_pane: state::TuiFocusPane::Prompt,
startup_prompt_queued: self.pending_initial_prompt.is_some(),
status: self.startup_initial_status(),
footer_session: self
.state
.current_session
.as_ref()
.map(|session| session.id().to_string())
.unwrap_or_else(|| "<disabled>".to_string()),
footer_session_id: self
.state
.current_session
.as_ref()
.map(|session| session.id().to_string()),
footer_cwd: self.state.cwd.display().to_string(),
images: std::cell::RefCell::new(
image_cell_pixels
.map(crate::tui::images::TranscriptImages::start)
.unwrap_or_default(),
),
..Default::default()
};
super::panel_layout_persistence::restore_panel_layout(
&mut state,
&self.settings.tui.panel_layout,
);
state.images.borrow_mut().sync_policy(
&self.state.cwd,
self.settings.tools.view_image.absolute_paths,
self.settings.tools.view_image.max_image_bytes,
self.state.active_session_id(),
);
#[cfg(any(test, debug_assertions))]
{
state.perf = crate::tui::perf::TuiPerfCounters::with_enabled(self.tui_perf_enabled);
}
state.refresh_thinking_levels(self.config.thinking_level, thinking_levels);
state.update_context_window(crate::context::ContextBudget::default().max_tokens);
state
}
fn start_run_loop<B>(
&mut self,
terminal: &mut ratatui::Terminal<B>,
state: &mut state::MissionControlState,
) -> Result<RunLoopState>
where
B: ratatui::backend::Backend,
B::Error: Send + Sync + 'static,
{
let autocomplete_candidates = tui_autocomplete_candidates(&self.commands);
state.update_restart_welcome = self.update_restart_launch;
self.release_notes = crate::tui::release_notes::WelcomeReleaseNotes::start(
self.config.paths.clone(),
self.release_notes_eligible,
);
let last_area = draw_initial_frame(terminal, state)?;
self.initialize_startup_prompt_queue(true);
let startup_request_id = self.start_startup_loader();
state.pending_startup_critical_request_id = Some(startup_request_id);
state.pending_startup_decorative_request_id = Some(startup_request_id);
Ok(RunLoopState {
last_area,
autocomplete_candidates,
redraw_intent: RedrawIntent::None,
pending_input_events: VecDeque::new(),
last_prompt_cursor_blink: Instant::now(),
last_footer_git_branch_refresh: Instant::now(),
last_fast_mode_rainbow: if state.fast_mode_rainbow_active()
|| state.welcome_animation_active()
{
Some(Instant::now())
} else {
None
},
clipboard: input::clipboard::Osc52Clipboard,
drain_budget_state: DrainLoopBudgetState::default(),
input_priority_turn: false,
diff_worker: crate::tui::diff::worker::DiffWorker::default(),
})
}
fn run_event_loop<B>(
&mut self,
terminal: &mut ratatui::Terminal<B>,
state: &mut state::MissionControlState,
run_loop: &mut RunLoopState,
receiver: &Receiver<TuiEvent>,
input_events: &TerminalInputBridge,
) -> Result<()>
where
B: ratatui::backend::Backend,
B::Error: Send + Sync + 'static,
{
loop {
self.poll_background_sources(state, &mut run_loop.redraw_intent);
if run_loop.drain_budget_state.input_paint_barrier_pending() {
self.paint_input_barrier_frame(terminal, state, run_loop, receiver)?;
continue;
}
let post_drain = self.drain_control_events(state, run_loop, receiver, input_events)?;
if post_drain.flow == PostDrainFlow::Exit {
return Ok(());
}
let TimerTick::Proceed { animation_only } =
self.poll_workers_and_timers(state, run_loop, post_drain.skip_loop)
else {
continue;
};
if self.draw_due_frame(
terminal,
state,
run_loop,
receiver,
input_events,
animation_only,
)? {
continue;
}
let (input_event, input_received_at) =
match self.next_input_event(state, run_loop, receiver, input_events)? {
InputWait::Received(event, received_at) => (event, received_at),
InputWait::Retry => continue,
InputWait::Exit => return Ok(()),
};
run_loop.input_priority_turn = false;
let flow = self.dispatch_input_event(
input_event,
input_received_at,
state,
run_loop,
receiver,
input_events,
);
if matches!(flow, AppControlFlow::Exit) {
return Ok(());
}
}
}
fn poll_background_sources(
&mut self,
state: &mut state::MissionControlState,
redraw_intent: &mut RedrawIntent,
) {
if self.release_notes.poll(state) {
redraw_intent.request_immediate();
}
if let Some(version) = self.update_checks.poll(Instant::now()) {
state.available_update_version = Some(version.to_string());
redraw_intent.request_immediate();
}
let image_policy_changed = state.images.borrow_mut().sync_policy(
&self.state.cwd,
self.settings.tools.view_image.absolute_paths,
self.settings.tools.view_image.max_image_bytes,
self.state.active_session_id(),
);
if image_policy_changed && state.images.borrow().enabled() {
terminal::graphics::cleanup();
redraw_intent.request_immediate();
}
if state.images.borrow_mut().poll() {
redraw_intent.request_immediate();
}
self.sync_startup_prompt_signal(state);
}
fn paint_input_barrier_frame<B>(
&mut self,
terminal: &mut ratatui::Terminal<B>,
state: &mut state::MissionControlState,
run_loop: &mut RunLoopState,
receiver: &Receiver<TuiEvent>,
) -> Result<()>
where
B: ratatui::backend::Backend,
B::Error: Send + Sync + 'static,
{
terminal::synchronized_output::draw_synchronized(&mut std::io::stdout().lock(), |_| {
draw_frame(
terminal,
state,
&mut run_loop.last_area,
receiver,
&mut run_loop.redraw_intent,
&mut run_loop.drain_budget_state,
)
})?;
self.mark_pending_startup_prompt_painted();
Ok(())
}
fn drain_control_events(
&mut self,
state: &mut state::MissionControlState,
run_loop: &mut RunLoopState,
receiver: &Receiver<TuiEvent>,
input_events: &TerminalInputBridge,
) -> Result<PostDrainProcessing> {
let input_queued_for_priority =
!run_loop.pending_input_events.is_empty() || !input_events.receiver.is_empty();
let skip_tui_drain_for_input = input_queued_for_priority && run_loop.input_priority_turn;
let mut drain_outcome = if run_loop.drain_budget_state.should_drain_at_loop_top()
&& !skip_tui_drain_for_input
{
#[cfg(any(test, debug_assertions))]
{
if state.perf.enabled() {
state.perf.set_backlog_before_drain(receiver.len());
let started = Instant::now();
let outcome = drain_tui_events_with_pending(
receiver,
&mut self.pending_critical_events,
state,
UI_DRAIN_BUDGET,
);
state
.perf
.record_drain_us(Instant::now().duration_since(started).as_micros() as u64);
state.perf.set_backlog_after_drain(receiver.len());
outcome
} else {
drain_tui_events_with_pending(
receiver,
&mut self.pending_critical_events,
state,
UI_DRAIN_BUDGET,
)
}
}
#[cfg(not(any(test, debug_assertions)))]
{
drain_tui_events_with_pending(
receiver,
&mut self.pending_critical_events,
state,
UI_DRAIN_BUDGET,
)
}
} else {
DrainOutcome::default()
};
if !skip_tui_drain_for_input {
run_loop.input_priority_turn = true;
}
let post_drain = self.process_post_drain(
state,
&mut drain_outcome,
&mut run_loop.autocomplete_candidates,
PostDrainContext {
receiver,
input_events,
terminal_area: run_loop.last_area,
pending_input_events: &mut run_loop.pending_input_events,
drain_budget_state: &mut run_loop.drain_budget_state,
finish_completed_worker: true,
},
)?;
if post_drain.redraw_requested {
run_loop.redraw_intent.request_immediate();
}
Ok(post_drain)
}
fn poll_workers_and_timers(
&mut self,
state: &mut state::MissionControlState,
run_loop: &mut RunLoopState,
skip_loop: bool,
) -> TimerTick {
if self.reap_preference_save(state) {
run_loop.redraw_intent.request_immediate();
}
if run_loop.diff_worker.poll(
&mut state.diff,
&self.state.cwd,
state.right_column_tab == state::RightColumnTab::Diff,
) {
run_loop.redraw_intent.request_immediate();
}
if self.poll_side(state) {
run_loop.redraw_intent.request_immediate();
}
let now = Instant::now();
if self.poll_codex_quota(state, now) {
run_loop.redraw_intent.request_immediate();
}
if self.poll_claude_quota(state, now) {
run_loop.redraw_intent.request_immediate();
}
let rainbow_advanced =
tick_fast_mode_rainbow(state, &mut run_loop.last_fast_mode_rainbow, now);
let activity_advanced = state.tick_activity_motion(now);
let elapsed_seconds = state.session_active_time.elapsed(now).as_secs();
let session_time_advanced = elapsed_seconds != state.displayed_session_active_seconds;
if session_time_advanced {
state.displayed_session_active_seconds = elapsed_seconds;
run_loop.redraw_intent.request_immediate();
}
if skip_loop && !rainbow_advanced && !activity_advanced && !session_time_advanced {
return TimerTick::SkipIteration;
}
if now.duration_since(run_loop.last_footer_git_branch_refresh)
>= FOOTER_GIT_BRANCH_REFRESH_INTERVAL
{
self.request_footer_git_branch_refresh();
run_loop.last_footer_git_branch_refresh = now;
}
if state.expire_toast(now) {
run_loop.redraw_intent.request_immediate();
}
if prompt_cursor_blink_due(state, run_loop.last_prompt_cursor_blink) {
state.toggle_prompt_cursor();
run_loop.last_prompt_cursor_blink = Instant::now();
run_loop.redraw_intent.request_immediate();
}
let animation_advanced = rainbow_advanced || activity_advanced;
let animation_only = animation_advanced && run_loop.redraw_intent == RedrawIntent::None;
if animation_advanced {
run_loop.redraw_intent.request_immediate();
}
TimerTick::Proceed { animation_only }
}
fn draw_due_frame<B>(
&mut self,
terminal: &mut ratatui::Terminal<B>,
state: &mut state::MissionControlState,
run_loop: &mut RunLoopState,
receiver: &Receiver<TuiEvent>,
input_events: &TerminalInputBridge,
animation_only: bool,
) -> Result<bool>
where
B: ratatui::backend::Backend,
B::Error: Send + Sync + 'static,
{
let defer_animation_draw = run_loop
.drain_budget_state
.defer_animation_draw_for_queued_input(
animation_only,
!run_loop.pending_input_events.is_empty() || !input_events.receiver.is_empty(),
self.pending_startup_prompt
.as_ref()
.is_some_and(|pending| !pending.painted),
);
if !should_draw_frame(run_loop.redraw_intent) || defer_animation_draw {
return Ok(false);
}
let continue_after_pending_draw = terminal::synchronized_output::draw_synchronized(
&mut std::io::stdout().lock(),
|_| {
draw_frame(
terminal,
state,
&mut run_loop.last_area,
receiver,
&mut run_loop.redraw_intent,
&mut run_loop.drain_budget_state,
)
},
)?;
if self.release_notes.poll(state) {
run_loop.redraw_intent.request_immediate();
}
self.mark_pending_startup_prompt_painted();
Ok(continue_after_pending_draw)
}
fn next_input_event(
&mut self,
state: &mut state::MissionControlState,
run_loop: &mut RunLoopState,
receiver: &Receiver<TuiEvent>,
input_events: &TerminalInputBridge,
) -> Result<InputWait> {
if let Some(event) = run_loop.pending_input_events.pop_front() {
self.consume_startup_input_boundary_event();
return Ok(InputWait::Received(event, Instant::now()));
}
if let Ok(event) = input_events.receiver.try_recv() {
self.consume_startup_input_boundary_event();
return Ok(InputWait::Received(event, Instant::now()));
}
if !self.pending_critical_events.is_empty() {
return Ok(InputWait::Retry);
}
let timeout = self.input_wait_timeout(state, run_loop);
crossbeam_channel::select_biased! {
recv(receiver) -> event => {
match event {
Ok(event) => self.drain_control_events_after_wake(
event,
state,
run_loop,
receiver,
input_events,
),
Err(_) => Ok(InputWait::Exit),
}
}
recv(input_events.receiver) -> event => {
match event {
Ok(event) => {
self.consume_startup_input_boundary_event();
Ok(InputWait::Received(event, Instant::now()))
}
Err(_) => Err(self.input_reader_disconnected_error()),
}
}
default(timeout.unwrap_or(Duration::MAX)) => Ok(InputWait::Retry),
}
}
fn input_wait_timeout(
&self,
state: &state::MissionControlState,
run_loop: &RunLoopState,
) -> Option<Duration> {
let now = Instant::now();
let timeout = next_ui_timer_timeout(
state,
run_loop.last_prompt_cursor_blink,
run_loop.last_footer_git_branch_refresh,
run_loop.last_fast_mode_rainbow,
now,
);
let timeout = if self.settings_persistence.is_pending()
|| self.panel_layout_persistence.is_pending()
|| self.session_maintenance.is_pending()
|| self.system_prompt.is_pending()
|| self.footer_git_branch_worker.is_some()
|| self.preference_save_worker.is_some()
|| self.side.is_pending()
|| state.images.borrow().is_pending()
{
Some(
timeout
.unwrap_or(Duration::MAX)
.min(Duration::from_millis(20)),
)
} else {
timeout
};
if self.state.current_session.is_some()
|| state.right_column_tab == state::RightColumnTab::Diff
|| state.diff.saving
|| run_loop.diff_worker.is_pending()
{
Some(
timeout
.unwrap_or(Duration::MAX)
.min(Duration::from_millis(100)),
)
} else {
timeout
}
}
fn drain_control_events_after_wake(
&mut self,
event: TuiEvent,
state: &mut state::MissionControlState,
run_loop: &mut RunLoopState,
receiver: &Receiver<TuiEvent>,
input_events: &TerminalInputBridge,
) -> Result<InputWait> {
#[cfg(any(test, debug_assertions))]
if state.perf.enabled() {
state
.perf
.set_backlog_before_drain(receiver.len().saturating_add(1));
}
let budget_started = Instant::now();
let mut drain_outcome = DrainOutcome::default();
apply_tui_event_to_state(state, event, &mut drain_outcome.result);
let tail_outcome = drain_tui_events_budgeted_from(
receiver,
state,
UI_DRAIN_BUDGET,
1,
budget_started,
Instant::now,
);
drain_outcome.processed = tail_outcome.processed;
drain_outcome.exhausted = tail_outcome.exhausted;
drain_outcome.result.merge(tail_outcome.result);
#[cfg(any(test, debug_assertions))]
if state.perf.enabled() {
state
.perf
.record_drain_us(Instant::now().duration_since(budget_started).as_micros() as u64);
state.perf.set_backlog_after_drain(receiver.len());
}
let post_drain = self.process_post_drain(
state,
&mut drain_outcome,
&mut run_loop.autocomplete_candidates,
PostDrainContext {
receiver,
input_events,
terminal_area: run_loop.last_area,
pending_input_events: &mut run_loop.pending_input_events,
drain_budget_state: &mut run_loop.drain_budget_state,
finish_completed_worker: false,
},
)?;
if post_drain.redraw_requested {
run_loop.redraw_intent.request_immediate();
}
if post_drain.flow == PostDrainFlow::Exit {
return Ok(InputWait::Exit);
}
Ok(InputWait::Retry)
}
fn dispatch_input_event(
&mut self,
input_event: TerminalInputEvent,
input_received_at: Instant,
state: &mut state::MissionControlState,
run_loop: &mut RunLoopState,
receiver: &Receiver<TuiEvent>,
input_events: &TerminalInputBridge,
) -> AppControlFlow {
if state
.conversation_modal
.as_ref()
.is_some_and(|modal| modal.visible)
&& !matches!(
input_event,
TerminalInputEvent::Event(crossterm::event::Event::Resize(..))
)
{
self.handle_side_input(
input_event,
state,
run_loop.last_area,
&mut run_loop.clipboard,
&run_loop.autocomplete_candidates,
);
run_loop.drain_budget_state.request_input_paint(
&mut run_loop.redraw_intent,
tui_perf_enabled(state),
input_received_at,
);
return AppControlFlow::Continue;
}
match input_event {
burst @ (TerminalInputEvent::KeyBurst(_) | TerminalInputEvent::KeyBurstTooLarge) => {
self.handle_key_burst_input(burst, input_received_at, state, run_loop, receiver)
}
TerminalInputEvent::Event(crossterm::event::Event::Key(key)) => {
self.handle_key_input(key, input_received_at, state, run_loop, receiver)
}
TerminalInputEvent::Event(crossterm::event::Event::Paste(text)) => {
self.handle_paste_input(&text, input_received_at, state, run_loop, receiver)
}
TerminalInputEvent::Event(crossterm::event::Event::Mouse(mouse)) => {
self.handle_mouse_input(mouse, state, run_loop, receiver, input_events)
}
TerminalInputEvent::Event(crossterm::event::Event::Resize(width, height)) => {
run_loop.last_area = ratatui::layout::Rect::new(0, 0, width, height);
run_loop.redraw_intent.request_immediate();
AppControlFlow::Continue
}
TerminalInputEvent::Event(_) => AppControlFlow::Continue,
}
}
fn handle_key_burst_input(
&mut self,
burst: TerminalInputEvent,
input_received_at: Instant,
state: &mut state::MissionControlState,
run_loop: &mut RunLoopState,
receiver: &Receiver<TuiEvent>,
) -> AppControlFlow {
let paints_prompt_input = matches!(burst, TerminalInputEvent::KeyBurst(_));
let prompt_revision_before = state.prompt_content_revision();
let Some(command) = classify_prompt_key_burst_event(
burst,
state,
run_loop.last_area,
&run_loop.autocomplete_candidates,
) else {
return AppControlFlow::Continue;
};
let (flow, _) = self.reduce_loop_input_command(
command,
state,
run_loop,
receiver,
prompt_revision_before,
);
if matches!(flow, AppControlFlow::Exit) {
return flow;
}
if paints_prompt_input {
request_prompt_input_paint(state, run_loop, prompt_revision_before, input_received_at);
} else {
run_loop.redraw_intent.request_immediate();
}
AppControlFlow::Continue
}
fn handle_key_input(
&mut self,
key: crossterm::event::KeyEvent,
input_received_at: Instant,
state: &mut state::MissionControlState,
run_loop: &mut RunLoopState,
receiver: &Receiver<TuiEvent>,
) -> AppControlFlow {
if self.release_notes.dismiss_key(key, state) {
run_loop.redraw_intent.request_immediate();
return AppControlFlow::Continue;
}
if crate::tui::release_notes::scroll_key(key, state) {
run_loop.redraw_intent.request_immediate();
return AppControlFlow::Continue;
}
let prompt_revision_before = state.prompt_content_revision();
let command = input::classify_key_with_all_viewports(
key,
state,
viewports::key_viewports(run_loop.last_area, state),
&run_loop.autocomplete_candidates,
);
let (flow, _) = self.reduce_loop_input_command(
command,
state,
run_loop,
receiver,
prompt_revision_before,
);
if matches!(flow, AppControlFlow::Exit) {
return flow;
}
if state.session_picker_visible() {
self.refresh_selected_session_preview(state);
}
request_prompt_input_paint(state, run_loop, prompt_revision_before, input_received_at);
AppControlFlow::Continue
}
fn handle_paste_input(
&mut self,
text: &str,
input_received_at: Instant,
state: &mut state::MissionControlState,
run_loop: &mut RunLoopState,
receiver: &Receiver<TuiEvent>,
) -> AppControlFlow {
let prompt_revision_before = state.prompt_content_revision();
let command = input::classify_paste_with_all_viewports(
text,
state,
viewports::paste_viewports(run_loop.last_area, state),
&run_loop.autocomplete_candidates,
);
let (flow, _) = self.reduce_loop_input_command(
command,
state,
run_loop,
receiver,
prompt_revision_before,
);
if matches!(flow, AppControlFlow::Exit) {
return flow;
}
request_prompt_input_paint(state, run_loop, prompt_revision_before, input_received_at);
AppControlFlow::Continue
}
fn handle_mouse_input(
&mut self,
mouse: crossterm::event::MouseEvent,
state: &mut state::MissionControlState,
run_loop: &mut RunLoopState,
receiver: &Receiver<TuiEvent>,
input_events: &TerminalInputBridge,
) -> AppControlFlow {
let (mouse, scroll_rows, coalesced_events) = coalesce_mouse_scroll_event(
mouse,
run_loop.last_area,
state,
&input_events.receiver,
&mut run_loop.pending_input_events,
);
self.consume_startup_input_boundary_events(coalesced_events);
let scroll_only = mouse_scroll_batch(mouse, run_loop.last_area, state).is_some();
let command =
input::mouse::classify_mouse_input(mouse, run_loop.last_area, state, scroll_rows);
let prompt_revision_before = state.prompt_content_revision();
#[cfg(any(test, debug_assertions))]
let scroll_measurement = (scroll_only && state.perf.enabled()).then(|| {
(
Instant::now(),
input_events
.receiver
.len()
.saturating_add(run_loop.pending_input_events.len()),
)
});
let (flow, result) = self.reduce_loop_input_command(
command,
state,
run_loop,
receiver,
prompt_revision_before,
);
if matches!(flow, AppControlFlow::Exit) {
return flow;
}
if result.changed {
#[cfg(any(test, debug_assertions))]
if let Some((started, backlog)) = scroll_measurement {
state.perf.record_scroll_applied(started, backlog);
}
if scroll_only {
run_loop.redraw_intent.request_scroll();
} else {
run_loop.redraw_intent.request_immediate();
}
}
if state.session_picker_visible() && self.refresh_selected_session_preview(state) {
run_loop.redraw_intent.request_immediate();
}
AppControlFlow::Continue
}
fn reduce_loop_input_command(
&mut self,
command: input::InputCommand,
state: &mut state::MissionControlState,
run_loop: &mut RunLoopState,
receiver: &Receiver<TuiEvent>,
prompt_revision_before: u64,
) -> (AppControlFlow, display_reducer::DisplayApplyResult) {
let mut action_context = actions::ActionContext {
receiver,
autocomplete_candidates: &mut run_loop.autocomplete_candidates,
clipboard: &mut run_loop.clipboard,
last_area: run_loop.last_area,
};
self.reduce_input_command(command, state, &mut action_context, prompt_revision_before)
}
}
fn request_prompt_input_paint(
state: &state::MissionControlState,
run_loop: &mut RunLoopState,
prompt_revision_before: u64,
input_received_at: Instant,
) {
let input_changed = state.prompt_content_revision() != prompt_revision_before;
run_loop.redraw_intent.request_immediate();
if input_changed {
run_loop.drain_budget_state.request_input_paint(
&mut run_loop.redraw_intent,
tui_perf_enabled(state),
input_received_at,
);
}
}