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