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    InlineLinkRange, InlineLinkTarget, InlineListItem, InlineListSearchConfig, InlineListSelection,
25    InlineMessageKind, InlineSegment, InlineSession, InlineTextStyle, InlineTheme,
26    ListOverlayRequest, ModalOverlayRequest, OverlayEvent, OverlayHotkey, OverlayHotkeyAction,
27    OverlayHotkeyKey, OverlayRequest, OverlaySelectionChange, OverlaySubmission, PlanContent,
28    PlanPhase, PlanStep, RewindAction, SecurePromptConfig, SlashCommandItem, TrustMode,
29    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::config::KeyboardProtocolConfig::default(),
52        workspace_root,
53        Vec::new(),
54    )
55}
56
57/// Spawn session with optional custom prompts pre-loaded
58#[allow(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    workspace_root: Option<std::path::PathBuf>,
69    slash_commands: Vec<SlashCommandItem>,
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        workspace_root,
82        slash_commands,
83        None,
84        "Agent TUI".to_string(),
85        None,
86    )
87}
88
89/// Spawn session with host-injected UI options.
90#[allow(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    workspace_root: Option<std::path::PathBuf>,
102    slash_commands: Vec<SlashCommandItem>,
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
119    tokio::spawn(async move {
120        if let Err(error) = run_tui(
121            command_rx,
122            event_tx,
123            TuiOptions {
124                theme,
125                placeholder,
126                surface_preference,
127                inline_rows,
128                show_logs: log::is_tui_log_capture_enabled(),
129                log_theme: None,
130                event_callback,
131                focus_callback,
132                active_pty_sessions,
133                input_activity_counter,
134                keyboard_protocol,
135                workspace_root,
136                slash_commands,
137                appearance,
138                app_name: app_name.clone(),
139            },
140        )
141        .await
142        {
143            let error_msg = error.to_string();
144            if error_msg.contains("stdin is not a terminal") {
145                eprintln!("Error: Interactive TUI requires a proper terminal.");
146                if let Some(hint) = non_interactive_hint.as_deref() {
147                    eprintln!("{}", hint);
148                } else {
149                    eprintln!("Use a non-interactive mode in your host app for piped input.");
150                }
151            } else {
152                eprintln!("Error: TUI startup failed: {:#}", error);
153            }
154            tracing::error!(%error, "inline session terminated unexpectedly");
155        }
156    });
157
158    Ok(InlineSession {
159        handle: InlineHandle { sender: command_tx },
160        events: event_rx,
161    })
162}