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