Skip to main content

vtcode_ui/tui/core_tui/
mod.rs

1use anyhow::Result;
2use std::sync::Arc;
3use tokio::sync::mpsc;
4
5use crate::tui::config::types::UiSurfacePreference;
6
7pub mod alternate_screen;
8pub mod app;
9pub(crate) mod blocked_status;
10pub(crate) mod language_badge;
11pub mod log;
12pub mod panic_hook;
13pub(crate) mod program_status;
14pub mod runner;
15pub mod session;
16pub mod style;
17pub mod theme_parser;
18pub mod types;
19pub mod widgets;
20
21pub use style::{convert_style, theme_from_styles};
22pub use theme_parser::ThemeConfigParser;
23pub use types::{
24    ContentPart, FocusChangeCallback, InlineCommand, InlineEvent, InlineEventCallback, InlineHandle, InlineHeaderBadge,
25    InlineHeaderContext, InlineHeaderHighlight, InlineHeaderStatusBadge, InlineHeaderStatusTone, InlineLinkRange,
26    InlineLinkTarget, InlineListItem, InlineListSearchConfig, InlineListSelection, InlineMessageKind, InlineSegment,
27    InlineSession, InlineTextStyle, InlineTheme, ListOverlayRequest, ModalOverlayRequest, OverlayEvent, OverlayHotkey,
28    OverlayHotkeyAction, OverlayHotkeyKey, OverlayRequest, OverlaySelectionChange, OverlaySubmission, PreviewCallback,
29    RewindAction, SecurePromptConfig, SubmittedInput, WizardModalMode, WizardOverlayRequest, WizardStep,
30};
31
32use runner::{TuiOptions, run_tui};
33
34pub fn spawn_session(
35    theme: InlineTheme,
36    placeholder: Option<String>,
37    surface_preference: UiSurfacePreference,
38    inline_rows: u16,
39    event_callback: Option<InlineEventCallback>,
40    active_pty_sessions: Option<Arc<std::sync::atomic::AtomicUsize>>,
41    workspace_root: Option<std::path::PathBuf>,
42) -> Result<InlineSession> {
43    spawn_session_with_prompts(
44        theme,
45        placeholder,
46        surface_preference,
47        inline_rows,
48        event_callback,
49        active_pty_sessions,
50        None,
51        crate::tui::config::KeyboardProtocolConfig::default(),
52        crate::tui::options::FullscreenInteractionSettings::default(),
53        workspace_root,
54        None,
55    )
56}
57
58/// Spawn session with optional custom prompts pre-loaded
59#[expect(
60    clippy::too_many_arguments,
61    reason = "Intentional compatibility, platform, test, or API-shape suppression."
62)]
63pub fn spawn_session_with_prompts(
64    theme: InlineTheme,
65    placeholder: Option<String>,
66    surface_preference: UiSurfacePreference,
67    inline_rows: u16,
68    event_callback: Option<InlineEventCallback>,
69    active_pty_sessions: Option<Arc<std::sync::atomic::AtomicUsize>>,
70    input_activity_counter: Option<Arc<std::sync::atomic::AtomicU64>>,
71    keyboard_protocol: crate::tui::config::KeyboardProtocolConfig,
72    fullscreen: crate::tui::options::FullscreenInteractionSettings,
73    workspace_root: Option<std::path::PathBuf>,
74    preview_callback: Option<PreviewCallback>,
75) -> Result<InlineSession> {
76    spawn_session_with_prompts_and_options(
77        theme,
78        placeholder,
79        surface_preference,
80        inline_rows,
81        event_callback,
82        None,
83        active_pty_sessions,
84        input_activity_counter,
85        keyboard_protocol,
86        fullscreen,
87        workspace_root,
88        None,
89        "Agent TUI".to_string(),
90        None,
91        preview_callback,
92    )
93}
94
95/// Spawn session with host-injected UI options.
96#[expect(
97    clippy::too_many_arguments,
98    reason = "Intentional compatibility, platform, test, or API-shape suppression."
99)]
100pub fn spawn_session_with_prompts_and_options(
101    theme: InlineTheme,
102    placeholder: Option<String>,
103    surface_preference: UiSurfacePreference,
104    inline_rows: u16,
105    event_callback: Option<InlineEventCallback>,
106    focus_callback: Option<FocusChangeCallback>,
107    active_pty_sessions: Option<Arc<std::sync::atomic::AtomicUsize>>,
108    input_activity_counter: Option<Arc<std::sync::atomic::AtomicU64>>,
109    keyboard_protocol: crate::tui::config::KeyboardProtocolConfig,
110    fullscreen: crate::tui::options::FullscreenInteractionSettings,
111    workspace_root: Option<std::path::PathBuf>,
112    appearance: Option<session::config::AppearanceConfig>,
113    app_name: String,
114    non_interactive_hint: Option<String>,
115    preview_callback: Option<PreviewCallback>,
116) -> Result<InlineSession> {
117    use crossterm::tty::IsTty;
118
119    // Check stdin is a terminal BEFORE spawning the task
120    if !std::io::stdin().is_tty() {
121        return Err(anyhow::anyhow!(
122            "cannot run interactive TUI: stdin is not a terminal (must be run in an interactive terminal)"
123        ));
124    }
125
126    let (command_tx, command_rx) = mpsc::unbounded_channel();
127    let (event_tx, event_rx) = mpsc::unbounded_channel();
128    let show_logs = log::is_tui_log_capture_enabled();
129
130    tokio::spawn(async move {
131        if let Err(error) = run_tui(
132            command_rx,
133            event_tx,
134            TuiOptions {
135                surface_preference,
136                inline_rows,
137                show_logs,
138                log_theme: None,
139                event_callback,
140                focus_callback,
141                active_pty_sessions,
142                input_activity_counter,
143                keyboard_protocol,
144                fullscreen,
145                workspace_root,
146                preview_callback,
147            },
148            move |rows| session::Session::new_with_logs(theme, placeholder, rows, show_logs, appearance, app_name),
149        )
150        .await
151        {
152            let error_msg = error.to_string();
153            if error_msg.contains("stdin is not a terminal") {
154                eprintln!("Error: Interactive TUI requires a proper terminal.");
155                if let Some(hint) = non_interactive_hint.as_deref() {
156                    eprintln!("{hint}");
157                } else {
158                    eprintln!("Use a non-interactive mode in your host app for piped input.");
159                }
160            } else {
161                eprintln!("Error: TUI startup failed: {error:#}");
162            }
163            tracing::error!(%error, "inline session terminated unexpectedly");
164        }
165    });
166
167    Ok(InlineSession {
168        handle: InlineHandle { sender: command_tx },
169        events: event_rx,
170    })
171}