Skip to main content

vtcode_tui/core_tui/
mod.rs

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