chore: move each view used in BottomPane into its own file (#928)
`BottomPane` was getting a bit unwieldy because it maintained a `PaneState` enum with three variants and many of its methods had `match` statements to handle each variant. To replace the enum, this PR: * Introduces a `trait BottomPaneView` that has two implementations: `StatusIndicatorView` and `ApprovalModalView`. * Migrates `PaneState::TextInput` into its own struct, `ChatComposer`, that does **not** implement `BottomPaneView`. * Updates `BottomPane` so it has `composer: ChatComposer` and `active_view: Option<Box<dyn BottomPaneView<'a> + 'a>>`. The idea is that `active_view` takes priority and is displayed when it is `Some`; otherwise, `ChatComposer` is displayed. * While methods of `BottomPane` often have to check whether `active_view` is present to decide which component to delegate to, the code is more straightforward than before and introducing new implementations of `BottomPaneView` should be less painful. Because we want to retain the `TextArea` owned by `ChatComposer` even when another view is displayed, to keep the ownership logic simple, it seemed best to keep `ChatComposer` distinct from `BottomPaneView`.
This commit is contained in:
73
codex-rs/tui/src/bottom_pane/approval_modal_view.rs
Normal file
73
codex-rs/tui/src/bottom_pane/approval_modal_view.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use std::sync::mpsc::SendError;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use crossterm::event::KeyEvent;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::widgets::WidgetRef;
|
||||
|
||||
use crate::app_event::AppEvent;
|
||||
use crate::user_approval_widget::ApprovalRequest;
|
||||
use crate::user_approval_widget::UserApprovalWidget;
|
||||
|
||||
use super::BottomPane;
|
||||
use super::BottomPaneView;
|
||||
|
||||
/// Modal overlay asking the user to approve/deny a sequence of requests.
|
||||
pub(crate) struct ApprovalModalView<'a> {
|
||||
current: UserApprovalWidget<'a>,
|
||||
queue: Vec<ApprovalRequest>,
|
||||
app_event_tx: Sender<AppEvent>,
|
||||
}
|
||||
|
||||
impl ApprovalModalView<'_> {
|
||||
pub fn new(request: ApprovalRequest, app_event_tx: Sender<AppEvent>) -> Self {
|
||||
Self {
|
||||
current: UserApprovalWidget::new(request, app_event_tx.clone()),
|
||||
queue: Vec::new(),
|
||||
app_event_tx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enqueue_request(&mut self, req: ApprovalRequest) {
|
||||
self.queue.push(req);
|
||||
}
|
||||
|
||||
/// Advance to next request if the current one is finished.
|
||||
fn maybe_advance(&mut self) {
|
||||
if self.current.is_complete() {
|
||||
if let Some(req) = self.queue.pop() {
|
||||
self.current = UserApprovalWidget::new(req, self.app_event_tx.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BottomPaneView<'a> for ApprovalModalView<'a> {
|
||||
fn handle_key_event(
|
||||
&mut self,
|
||||
_pane: &mut BottomPane<'a>,
|
||||
key_event: KeyEvent,
|
||||
) -> Result<(), SendError<AppEvent>> {
|
||||
self.current.handle_key_event(key_event)?;
|
||||
self.maybe_advance();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_complete(&self) -> bool {
|
||||
self.current.is_complete() && self.queue.is_empty()
|
||||
}
|
||||
|
||||
fn calculate_required_height(&self, area: &Rect) -> u16 {
|
||||
self.current.get_height(area)
|
||||
}
|
||||
|
||||
fn render(&self, area: Rect, buf: &mut Buffer) {
|
||||
(&self.current).render_ref(area, buf);
|
||||
}
|
||||
|
||||
fn try_consume_approval_request(&mut self, req: ApprovalRequest) -> Option<ApprovalRequest> {
|
||||
self.enqueue_request(req);
|
||||
None
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user