Skip to main content

TuiState

Struct TuiState 

Source
pub struct TuiState {
Show 18 fields pub input: String, pub cursor: usize, pub transcript: Vec<TranscriptEntry>, pub streaming: Option<String>, pub modal: Option<Modal>, pub theme: Theme, pub keymap: Keymap, pub vim_enabled: bool, pub vim_mode: VimMode, pub history: PromptHistory, pub history_search: Option<HistorySearchState>, pub input_focus: InputFocus, pub status: StatusLine, pub scroll: usize, pub should_quit: bool, pub external_editor_requested: bool, pub pending_images: Vec<String>, pub last_submission: Option<String>, /* private fields */
}
Expand description

The whole TUI view-model — see the module doc comment. Constructed fresh per TUI session by the CLI render layer; every mutation goes through Self::apply.

Fields§

§input: String

The composer’s current text.

§cursor: usize

Byte offset into input — always on a char boundary.

§transcript: Vec<TranscriptEntry>

The scrollback transcript, oldest first.

§streaming: Option<String>

In-progress assistant text (streaming) — None when no turn is mid-flight.

§modal: Option<Modal>

The currently-showing modal, if any.

§theme: Theme

The active display theme.

§keymap: Keymap

The resolved (default + overrides) keybinding table.

§vim_enabled: bool

D8 “vim” — whether modal editing is active at all (crate::Config::tui_vim_mode). false (the default): every key is a plain insert/navigate, VimMode is never consulted.

§vim_mode: VimMode

The current vim sub-mode (only meaningful when vim_enabled).

§history: PromptHistory

The cross-session prompt history.

§history_search: Option<HistorySearchState>

Live Ctrl+R search state, if Self::input_focus is InputFocus::HistorySearch.

§input_focus: InputFocus

What the composer area is currently showing.

§status: StatusLine

The status line’s contents.

§scroll: usize

Current transcript scroll offset (pages back from the bottom).

§should_quit: bool

Set once the user has asked to quit — the render loop’s exit signal.

§external_editor_requested: bool

Set while the CLI layer’s $EDITOR invocation is in flight.

§pending_images: Vec<String>

Image references pasted into the composer, in submission order — drained by the CLI layer once it reads Action::Submit.

§last_submission: Option<String>

Set by apply(Action::Submit(text)) to Some(text) — the CLI event loop’s ONE polling point for “a turn needs to be sent”: call Self::take_submission after every Self::on_key (or manual apply) to both read and clear it in one step, so a submission is never double-sent.

Implementations§

Source§

impl TuiState

Source

pub fn new( theme: Theme, keymap: Keymap, vim_enabled: bool, history: PromptHistory, ) -> TuiState

A fresh, empty state — theme/vim_enabled/keymap typically come from the resolved crate::Config (tui_theme/tui_vim_mode/ tui_keymap), history from PromptHistory::load_from_file.

Source

pub fn new_default() -> TuiState

Convenience for a caller that doesn’t need Default::default()-style construction control — plain-mode, dark theme, default keymap, empty history. Handy for tests and the render layer’s smoke-test harness.

Source

pub fn handle_key(&self, key: KeyEvent) -> Vec<Action>

Translate one keypress into the Actions it produces — READS state (to be context-sensitive: a modal open, history-search active, vim normal-mode all change what a key means) but never mutates it. Call Self::apply on each returned action (in order) to actually realize the transition — the render layer’s on_key convenience does exactly that.

Source

pub fn apply(&mut self, action: Action)

Apply one Action — the only place TuiState mutates. Any key OTHER than the one that just armed Self::quit_armed disarms it (so “Ctrl+C, type something, Ctrl+C” does NOT quit — only two CONSECUTIVE Ctrl+C presses do), except ArmQuit/Quit themselves.

Source

pub fn on_key(&mut self, key: KeyEvent)

Run Self::handle_key, then Self::apply every resulting action in order — the render layer’s one-call-per-keypress convenience. Every externally-relevant outcome (a turn to send, an editor to launch, …) lands in a dedicated TuiState field (Self::last_submission/Self::external_editor_requested/ Self::should_quit) the caller polls afterward — Action itself is intentionally NOT Clone (it carries one-shot reply channels), so this doesn’t hand actions back; a caller that needs to react to the RAW action stream (e.g. a test) calls handle_key+apply directly instead, as most of this module’s own tests do.

Source

pub fn take_submission(&mut self) -> Option<String>

Take (and clear) the most recent submission, if any — see Self::last_submission’s doc comment.

Source

pub fn take_pending_images(&mut self) -> Vec<String>

F5 (Fable-5 adversarial review): the CLI layer’s paired polling point alongside Self::take_submission — call both together, same tick, right after take_submission returns Some: this drains (and clears) every image path staged via Action::PasteImage for THAT submission, for the caller to route into the turn’s multimodal content (e.g. Agent::send_with_images). Previously Action::Submit cleared pending_images eagerly, before the CLI layer could ever read it — this method is what makes draining it the CLI’s job instead, so a pasted image path actually reaches the model.

Source

pub fn fail_close_pending_modals(&mut self)

D-1 (Fable-5 delta review — MEDIUM, “error-path indefinite hang”): drop the active modal AND everything still queued behind it, without sending a reply. Each Modal variant that carries a reply channel (Approval/ChildApproval’s std::sync::mpsc::Sender, Elicitation’s tokio::sync::oneshot::Sender) has its sender dropped as part of this — the corresponding blocked caller (TuiApprovalHandler::ask/elicitation) already treats a closed channel as its documented fail-closed default (ApprovalOutcome::Deny / a declined ElicitationResponse; see crate::tui::handlers), so this never silently allows anything. OAuthDeviceCode carries no reply channel — dropping it is a plain dismissal.

The CLI’s render loop (run_turn_blocking_with_input) calls this when its own terminal I/O has failed while a modal is still unanswered: nothing is left alive to answer it (crossterm is broken), and the in-flight turn’s worker thread is parked in a blocking recv()/.await on that modal’s reply channel that std::thread::scope will join before the loop can return ANY value, including its own I/O error — so leaving the modal pending would hang the whole session forever instead of surfacing that error.

Trait Implementations§

Source§

impl Debug for TuiState

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more