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, PlanConfirmationResult, PlanContent, PlanPhase,
24    PlanStep, 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        None,
71        active_pty_sessions,
72        keyboard_protocol,
73        workspace_root,
74        slash_commands,
75        None,
76        "Agent TUI".to_string(),
77        None,
78    )
79}
80
81/// Spawn session with host-injected UI options.
82#[allow(clippy::too_many_arguments)]
83pub fn spawn_session_with_prompts_and_options(
84    theme: InlineTheme,
85    placeholder: Option<String>,
86    surface_preference: UiSurfacePreference,
87    inline_rows: u16,
88    event_callback: Option<InlineEventCallback>,
89    focus_callback: Option<FocusChangeCallback>,
90    active_pty_sessions: Option<Arc<std::sync::atomic::AtomicUsize>>,
91    keyboard_protocol: crate::config::KeyboardProtocolConfig,
92    workspace_root: Option<std::path::PathBuf>,
93    slash_commands: Vec<SlashCommandItem>,
94    appearance: Option<session::config::AppearanceConfig>,
95    app_name: String,
96    non_interactive_hint: Option<String>,
97) -> Result<InlineSession> {
98    use crossterm::tty::IsTty;
99
100    // Check stdin is a terminal BEFORE spawning the task
101    if !std::io::stdin().is_tty() {
102        return Err(anyhow::anyhow!(
103            "cannot run interactive TUI: stdin is not a terminal (must be run in an interactive terminal)"
104        ));
105    }
106
107    let (command_tx, command_rx) = mpsc::unbounded_channel();
108    let (event_tx, event_rx) = mpsc::unbounded_channel();
109
110    tokio::spawn(async move {
111        if let Err(error) = run_tui(
112            command_rx,
113            event_tx,
114            TuiOptions {
115                theme,
116                placeholder,
117                surface_preference,
118                inline_rows,
119                show_logs: log::is_tui_log_capture_enabled(),
120                log_theme: None,
121                event_callback,
122                focus_callback,
123                active_pty_sessions,
124                keyboard_protocol,
125                workspace_root,
126                slash_commands,
127                appearance,
128                app_name: app_name.clone(),
129            },
130        )
131        .await
132        {
133            let error_msg = error.to_string();
134            if error_msg.contains("stdin is not a terminal") {
135                eprintln!("Error: Interactive TUI requires a proper terminal.");
136                if let Some(hint) = non_interactive_hint.as_deref() {
137                    eprintln!("{}", hint);
138                } else {
139                    eprintln!("Use a non-interactive mode in your host app for piped input.");
140                }
141            } else {
142                eprintln!("Error: TUI startup failed: {:#}", error);
143            }
144            tracing::error!(%error, "inline session terminated unexpectedly");
145        }
146    });
147
148    Ok(InlineSession {
149        handle: InlineHandle { sender: command_tx },
150        events: event_rx,
151    })
152}