Skip to main content

vtcode_tui/
session_options.rs

1use std::path::PathBuf;
2use std::sync::Arc;
3
4use crate::config::KeyboardProtocolConfig;
5use crate::core_tui::session::config::AppearanceConfig;
6
7use crate::{
8    FocusChangeCallback, InlineEventCallback, InlineSession, InlineTheme, SlashCommandItem,
9};
10
11/// Standalone surface preference for selecting inline vs alternate rendering.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
13pub enum SessionSurface {
14    #[default]
15    Auto,
16    Alternate,
17    Inline,
18}
19
20impl From<SessionSurface> for crate::config::UiSurfacePreference {
21    fn from(value: SessionSurface) -> Self {
22        match value {
23            SessionSurface::Auto => Self::Auto,
24            SessionSurface::Alternate => Self::Alternate,
25            SessionSurface::Inline => Self::Inline,
26        }
27    }
28}
29
30impl From<crate::config::UiSurfacePreference> for SessionSurface {
31    fn from(value: crate::config::UiSurfacePreference) -> Self {
32        match value {
33            crate::config::UiSurfacePreference::Auto => Self::Auto,
34            crate::config::UiSurfacePreference::Alternate => Self::Alternate,
35            crate::config::UiSurfacePreference::Inline => Self::Inline,
36        }
37    }
38}
39
40/// Standalone keyboard protocol settings for terminal key event enhancements.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct KeyboardProtocolSettings {
43    pub enabled: bool,
44    pub mode: String,
45    pub disambiguate_escape_codes: bool,
46    pub report_event_types: bool,
47    pub report_alternate_keys: bool,
48    pub report_all_keys: bool,
49}
50
51impl Default for KeyboardProtocolSettings {
52    fn default() -> Self {
53        Self::from(KeyboardProtocolConfig::default())
54    }
55}
56
57impl From<KeyboardProtocolConfig> for KeyboardProtocolSettings {
58    fn from(value: KeyboardProtocolConfig) -> Self {
59        Self {
60            enabled: value.enabled,
61            mode: value.mode,
62            disambiguate_escape_codes: value.disambiguate_escape_codes,
63            report_event_types: value.report_event_types,
64            report_alternate_keys: value.report_alternate_keys,
65            report_all_keys: value.report_all_keys,
66        }
67    }
68}
69
70impl From<KeyboardProtocolSettings> for KeyboardProtocolConfig {
71    fn from(value: KeyboardProtocolSettings) -> Self {
72        Self {
73            enabled: value.enabled,
74            mode: value.mode,
75            disambiguate_escape_codes: value.disambiguate_escape_codes,
76            report_event_types: value.report_event_types,
77            report_alternate_keys: value.report_alternate_keys,
78            report_all_keys: value.report_all_keys,
79        }
80    }
81}
82
83/// Standalone session launch options for reusable integrations.
84#[derive(Clone)]
85pub struct SessionOptions {
86    pub placeholder: Option<String>,
87    pub surface_preference: SessionSurface,
88    pub inline_rows: u16,
89    pub event_callback: Option<InlineEventCallback>,
90    pub focus_callback: Option<FocusChangeCallback>,
91    pub active_pty_sessions: Option<Arc<std::sync::atomic::AtomicUsize>>,
92    pub input_activity_counter: Option<Arc<std::sync::atomic::AtomicU64>>,
93    pub keyboard_protocol: KeyboardProtocolSettings,
94    pub workspace_root: Option<PathBuf>,
95    pub slash_commands: Vec<SlashCommandItem>,
96    pub appearance: Option<AppearanceConfig>,
97    pub app_name: String,
98    pub non_interactive_hint: Option<String>,
99}
100
101impl Default for SessionOptions {
102    fn default() -> Self {
103        Self {
104            placeholder: None,
105            surface_preference: SessionSurface::Auto,
106            inline_rows: crate::config::constants::ui::DEFAULT_INLINE_VIEWPORT_ROWS,
107            event_callback: None,
108            focus_callback: None,
109            active_pty_sessions: None,
110            input_activity_counter: None,
111            keyboard_protocol: KeyboardProtocolSettings::default(),
112            workspace_root: None,
113            slash_commands: Vec::new(),
114            appearance: None,
115            app_name: "Agent TUI".to_string(),
116            non_interactive_hint: None,
117        }
118    }
119}
120
121impl SessionOptions {
122    /// Build options from a host adapter's defaults.
123    pub fn from_host(host: &impl crate::host::HostAdapter) -> Self {
124        let defaults = host.session_defaults();
125        Self {
126            surface_preference: defaults.surface_preference,
127            inline_rows: defaults.inline_rows,
128            keyboard_protocol: defaults.keyboard_protocol,
129            workspace_root: host.workspace_root(),
130            slash_commands: host.slash_commands(),
131            app_name: host.app_name(),
132            non_interactive_hint: host.non_interactive_hint(),
133            ..Self::default()
134        }
135    }
136}
137
138/// Spawn a session using standalone options and local config types.
139pub fn spawn_session_with_options(
140    theme: InlineTheme,
141    options: SessionOptions,
142) -> anyhow::Result<InlineSession> {
143    crate::core_tui::spawn_session_with_prompts_and_options(
144        theme,
145        options.placeholder,
146        options.surface_preference.into(),
147        options.inline_rows,
148        options.event_callback,
149        options.focus_callback,
150        options.active_pty_sessions,
151        options.input_activity_counter,
152        options.keyboard_protocol.into(),
153        options.workspace_root,
154        options.slash_commands,
155        options.appearance,
156        options.app_name,
157        options.non_interactive_hint,
158    )
159}
160
161/// Spawn a session using defaults from a host adapter.
162pub fn spawn_session_with_host(
163    theme: InlineTheme,
164    host: &impl crate::host::HostAdapter,
165) -> anyhow::Result<InlineSession> {
166    spawn_session_with_options(theme, SessionOptions::from_host(host))
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    struct DemoHost;
174
175    impl crate::host::WorkspaceInfoProvider for DemoHost {
176        fn workspace_name(&self) -> String {
177            "demo".to_string()
178        }
179
180        fn workspace_root(&self) -> Option<PathBuf> {
181            Some(PathBuf::from("/workspace/demo"))
182        }
183    }
184
185    impl crate::host::NotificationProvider for DemoHost {
186        fn set_terminal_focused(&self, _focused: bool) {}
187    }
188
189    impl crate::host::ThemeProvider for DemoHost {
190        fn available_themes(&self) -> Vec<String> {
191            vec!["default".to_string()]
192        }
193
194        fn active_theme_name(&self) -> Option<String> {
195            Some("default".to_string())
196        }
197    }
198
199    impl crate::host::HostAdapter for DemoHost {
200        fn session_defaults(&self) -> crate::host::HostSessionDefaults {
201            crate::host::HostSessionDefaults {
202                surface_preference: SessionSurface::Inline,
203                inline_rows: 24,
204                keyboard_protocol: KeyboardProtocolSettings::default(),
205            }
206        }
207    }
208
209    #[test]
210    fn session_surface_conversion_roundtrip() {
211        let variants = [
212            SessionSurface::Auto,
213            SessionSurface::Alternate,
214            SessionSurface::Inline,
215        ];
216
217        for variant in variants {
218            let converted: crate::config::UiSurfacePreference = variant.into();
219            let roundtrip = SessionSurface::from(converted);
220            assert_eq!(variant, roundtrip);
221        }
222    }
223
224    #[test]
225    fn keyboard_protocol_conversion_roundtrip() {
226        let settings = KeyboardProtocolSettings {
227            enabled: true,
228            mode: "custom".to_string(),
229            disambiguate_escape_codes: true,
230            report_event_types: false,
231            report_alternate_keys: true,
232            report_all_keys: false,
233        };
234
235        let config: KeyboardProtocolConfig = settings.clone().into();
236        let restored = KeyboardProtocolSettings::from(config);
237
238        assert_eq!(settings, restored);
239    }
240
241    #[test]
242    fn session_options_from_host_uses_defaults() {
243        let options = SessionOptions::from_host(&DemoHost);
244
245        assert_eq!(options.surface_preference, SessionSurface::Inline);
246        assert_eq!(options.inline_rows, 24);
247        assert_eq!(
248            options.workspace_root,
249            Some(PathBuf::from("/workspace/demo"))
250        );
251    }
252}