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