use anyhow::Result;
use std::sync::Arc;
use tokio::sync::Notify;
use vtcode_core::config::loader::VTCodeConfig;
use vtcode_core::config::types::AgentConfig as CoreAgentConfig;
use vtcode_core::llm::provider::{self as uni};
use vtcode_core::utils::ansi::AnsiRenderer;
use vtcode_tui::app::{
InlineEvent, InlineHandle, InlineHeaderContext, TransientEvent, TransientHotkeyAction,
TransientSelectionChange, TransientSubmission,
};
use crate::agent::runloop::model_picker::ModelPickerState;
use crate::agent::runloop::unified::palettes::ActivePalette;
use crate::agent::runloop::welcome::SessionBootstrap;
use super::action::InlineLoopAction;
use super::control::InlineControlProcessor;
use super::input::InlineInputProcessor;
use super::interrupts::InlineInterruptCoordinator;
use super::modal::InlineModalProcessor;
use super::queue::InlineQueueState;
use super::state::InlineEventState;
pub(crate) struct InlineEventContext<'a> {
state: InlineEventState<'a>,
modal: InlineModalProcessor<'a>,
}
impl<'a> InlineEventContext<'a> {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
renderer: &'a mut AnsiRenderer,
handle: &'a InlineHandle,
interrupts: InlineInterruptCoordinator<'a>,
ctrl_c_notice_displayed: &'a mut bool,
header_context: &'a mut InlineHeaderContext,
model_picker_state: &'a mut Option<ModelPickerState>,
palette_state: &'a mut Option<ActivePalette>,
config: &'a mut CoreAgentConfig,
vt_cfg: &'a mut Option<VTCodeConfig>,
provider_client: &'a mut Box<dyn uni::LLMProvider>,
ctrl_c_state: &'a Arc<crate::agent::runloop::unified::state::CtrlCState>,
ctrl_c_notify: &'a Arc<Notify>,
session_bootstrap: &'a SessionBootstrap,
full_auto: bool,
conversation_history_len: usize,
) -> Self {
let state = InlineEventState::new(renderer, interrupts, ctrl_c_notice_displayed);
let modal = InlineModalProcessor::new(
handle,
header_context,
model_picker_state,
palette_state,
config,
vt_cfg,
provider_client,
ctrl_c_state,
ctrl_c_notify,
session_bootstrap,
full_auto,
conversation_history_len,
);
Self { state, modal }
}
pub(crate) async fn process_event(
&mut self,
event: InlineEvent,
queue: &mut InlineQueueState<'_>,
) -> Result<InlineLoopAction> {
let action = match event {
InlineEvent::Submit(text) => self.input_processor().submit(text),
InlineEvent::QueueSubmit(text) => self.input_processor().queue_submit(text, queue),
InlineEvent::ProcessLatestQueued => {
self.state.reset_interrupt_state();
queue.prefer_latest_next();
InlineLoopAction::Continue
}
InlineEvent::Steer(_) | InlineEvent::Pause | InlineEvent::Resume => {
self.state.reset_interrupt_state();
self.input_processor().passive()
}
InlineEvent::EditQueue => {
self.state.reset_interrupt_state();
queue.edit_latest();
InlineLoopAction::Continue
}
InlineEvent::Transient(overlay_event) => match overlay_event {
TransientEvent::SelectionChanged(TransientSelectionChange::List(selection)) => self
.modal
.handle_preview(self.state.renderer(), selection)?,
TransientEvent::SelectionChanged(TransientSelectionChange::DiffTrustMode {
..
}) => {
self.state.reset_interrupt_state();
self.input_processor().passive()
}
TransientEvent::Submitted(TransientSubmission::Selection(selection)) => {
self.state.reset_interrupt_state();
self.modal
.handle_submit(self.state.renderer(), selection)
.await?
}
TransientEvent::Submitted(TransientSubmission::Wizard(_)) => {
self.state.reset_interrupt_state();
self.input_processor().passive()
}
TransientEvent::Submitted(TransientSubmission::DiffApply) => {
self.state.reset_interrupt_state();
InlineLoopAction::DiffApproved
}
TransientEvent::Submitted(TransientSubmission::DiffReject) => {
self.state.reset_interrupt_state();
InlineLoopAction::DiffRejected
}
TransientEvent::Submitted(
TransientSubmission::DiffProceed
| TransientSubmission::DiffReload
| TransientSubmission::DiffAbort,
) => {
self.state.reset_interrupt_state();
self.input_processor().passive()
}
TransientEvent::Submitted(TransientSubmission::Hotkey(action)) => {
self.state.reset_interrupt_state();
match action {
TransientHotkeyAction::LaunchEditor => {
self.input_processor().submit("/edit".to_string())
}
TransientHotkeyAction::ReloadSubagentInspector
| TransientHotkeyAction::GracefulStopSubagent
| TransientHotkeyAction::ForceCancelSubagent
| TransientHotkeyAction::OpenSourceThread
| TransientHotkeyAction::FocusJobOutput
| TransientHotkeyAction::InterruptJob
| TransientHotkeyAction::PreviewJobSnapshot => {
self.input_processor().passive()
}
}
}
TransientEvent::Cancelled => {
self.state.reset_interrupt_state();
self.modal.handle_cancel(self.state.renderer())?
}
},
InlineEvent::Cancel => self.control_processor().cancel()?,
InlineEvent::ForceCancelPtySession => {
self.control_processor().force_cancel_pty_session()?
}
InlineEvent::Exit => self.control_processor().exit()?,
InlineEvent::Interrupt => self.handle_interrupt(),
InlineEvent::BackgroundOperation => self
.input_processor()
.submit("/subprocesses toggle".to_string()),
InlineEvent::LaunchEditor => {
self.input_processor().submit("/edit".to_string())
}
InlineEvent::RequestInlinePromptSuggestion(draft) => {
self.state.reset_interrupt_state();
InlineLoopAction::RequestInlinePromptSuggestion(draft)
}
InlineEvent::OpenTranscriptReviewInEditor(text) => {
self.state.reset_interrupt_state();
InlineLoopAction::OpenTranscriptReviewInEditor(text)
}
InlineEvent::OpenTranscriptReviewScrollback(text) => {
self.state.reset_interrupt_state();
InlineLoopAction::OpenTranscriptReviewScrollback(text)
}
InlineEvent::OpenFileInEditor(path) => {
self.input_processor().submit(format!("/edit {}", path))
}
InlineEvent::OpenUrl(url) => {
self.state.reset_interrupt_state();
self.modal.request_url_guard(self.state.renderer(), url)?
}
InlineEvent::ScrollLineUp
| InlineEvent::ScrollLineDown
| InlineEvent::ScrollPageUp
| InlineEvent::ScrollPageDown
| InlineEvent::FileSelected(_)
| InlineEvent::HistoryPrevious
| InlineEvent::HistoryNext => self.input_processor().passive(),
InlineEvent::ToggleMode => {
self.input_processor().submit("/mode".to_string())
}
};
Ok(action)
}
fn handle_interrupt(&mut self) -> InlineLoopAction {
let _ = self.modal.handle_cancel(self.state.renderer());
self.state.interrupts().action_for_interrupt()
}
fn input_processor(&mut self) -> InlineInputProcessor<'_, 'a> {
InlineInputProcessor::new(&mut self.state)
}
fn control_processor(&mut self) -> InlineControlProcessor<'_, 'a> {
InlineControlProcessor::new(&mut self.state)
}
}