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