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 keyboard_protocol: KeyboardProtocolSettings,
93    pub workspace_root: Option<PathBuf>,
94    pub slash_commands: Vec<SlashCommandItem>,
95    pub appearance: Option<AppearanceConfig>,
96    pub app_name: String,
97    pub non_interactive_hint: Option<String>,
98}
99
100impl Default for SessionOptions {
101    fn default() -> Self {
102        Self {
103            placeholder: None,
104            surface_preference: SessionSurface::Auto,
105            inline_rows: crate::config::constants::ui::DEFAULT_INLINE_VIEWPORT_ROWS,
106            event_callback: None,
107            focus_callback: None,
108            active_pty_sessions: None,
109            keyboard_protocol: KeyboardProtocolSettings::default(),
110            workspace_root: None,
111            slash_commands: Vec::new(),
112            appearance: None,
113            app_name: "Agent TUI".to_string(),
114            non_interactive_hint: None,
115        }
116    }
117}
118
119impl SessionOptions {
120    /// Build options from a host adapter's defaults.
121    pub fn from_host(host: &impl crate::host::HostAdapter) -> Self {
122        let defaults = host.session_defaults();
123        Self {
124            surface_preference: defaults.surface_preference,
125            inline_rows: defaults.inline_rows,
126            keyboard_protocol: defaults.keyboard_protocol,
127            workspace_root: host.workspace_root(),
128            slash_commands: host.slash_commands(),
129            app_name: host.app_name(),
130            non_interactive_hint: host.non_interactive_hint(),
131            ..Self::default()
132        }
133    }
134}
135
136/// Spawn a session using standalone options and local config types.
137pub fn spawn_session_with_options(
138    theme: InlineTheme,
139    options: SessionOptions,
140) -> anyhow::Result<InlineSession> {
141    crate::core_tui::spawn_session_with_prompts_and_options(
142        theme,
143        options.placeholder,
144        options.surface_preference.into(),
145        options.inline_rows,
146        options.event_callback,
147        options.focus_callback,
148        options.active_pty_sessions,
149        options.keyboard_protocol.into(),
150        options.workspace_root,
151        options.slash_commands,
152        options.appearance,
153        options.app_name,
154        options.non_interactive_hint,
155    )
156}
157
158/// Spawn a session using defaults from a host adapter.
159pub fn spawn_session_with_host(
160    theme: InlineTheme,
161    host: &impl crate::host::HostAdapter,
162) -> anyhow::Result<InlineSession> {
163    spawn_session_with_options(theme, SessionOptions::from_host(host))
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    struct DemoHost;
171
172    impl crate::host::WorkspaceInfoProvider for DemoHost {
173        fn workspace_name(&self) -> String {
174            "demo".to_string()
175        }
176
177        fn workspace_root(&self) -> Option<PathBuf> {
178            Some(PathBuf::from("/workspace/demo"))
179        }
180    }
181
182    impl crate::host::NotificationProvider for DemoHost {
183        fn set_terminal_focused(&self, _focused: bool) {}
184    }
185
186    impl crate::host::ThemeProvider for DemoHost {
187        fn available_themes(&self) -> Vec<String> {
188            vec!["default".to_string()]
189        }
190
191        fn active_theme_name(&self) -> Option<String> {
192            Some("default".to_string())
193        }
194    }
195
196    impl crate::host::HostAdapter for DemoHost {
197        fn session_defaults(&self) -> crate::host::HostSessionDefaults {
198            crate::host::HostSessionDefaults {
199                surface_preference: SessionSurface::Inline,
200                inline_rows: 24,
201                keyboard_protocol: KeyboardProtocolSettings::default(),
202            }
203        }
204    }
205
206    #[test]
207    fn session_surface_conversion_roundtrip() {
208        let variants = [
209            SessionSurface::Auto,
210            SessionSurface::Alternate,
211            SessionSurface::Inline,
212        ];
213
214        for variant in variants {
215            let converted: crate::config::UiSurfacePreference = variant.into();
216            let roundtrip = SessionSurface::from(converted);
217            assert_eq!(variant, roundtrip);
218        }
219    }
220
221    #[test]
222    fn keyboard_protocol_conversion_roundtrip() {
223        let settings = KeyboardProtocolSettings {
224            enabled: true,
225            mode: "custom".to_string(),
226            disambiguate_escape_codes: true,
227            report_event_types: false,
228            report_alternate_keys: true,
229            report_all_keys: false,
230        };
231
232        let config: KeyboardProtocolConfig = settings.clone().into();
233        let restored = KeyboardProtocolSettings::from(config);
234
235        assert_eq!(settings, restored);
236    }
237
238    #[test]
239    fn session_options_from_host_uses_defaults() {
240        let options = SessionOptions::from_host(&DemoHost);
241
242        assert_eq!(options.surface_preference, SessionSurface::Inline);
243        assert_eq!(options.inline_rows, 24);
244        assert_eq!(
245            options.workspace_root,
246            Some(PathBuf::from("/workspace/demo"))
247        );
248    }
249}