ricecoder_cli/commands/
tui.rs

1//! TUI command - Launch the terminal user interface
2
3use crate::commands::Command;
4use crate::error::{CliError, CliResult};
5use std::path::PathBuf;
6
7/// TUI command configuration
8#[derive(Debug, Clone)]
9pub struct TuiConfig {
10    /// Theme to use (dark, light, monokai, dracula, nord)
11    pub theme: Option<String>,
12    /// Enable vim keybindings
13    pub vim_mode: bool,
14    /// Custom config file path
15    pub config_file: Option<PathBuf>,
16    /// AI provider to use
17    pub provider: Option<String>,
18    /// Model to use
19    pub model: Option<String>,
20}
21
22/// TUI command handler
23pub struct TuiCommand {
24    theme: Option<String>,
25    vim_mode: bool,
26    config_file: Option<PathBuf>,
27    provider: Option<String>,
28    model: Option<String>,
29}
30
31impl TuiCommand {
32    /// Create a new TUI command
33    pub fn new(
34        theme: Option<String>,
35        vim_mode: bool,
36        config_file: Option<PathBuf>,
37        provider: Option<String>,
38        model: Option<String>,
39    ) -> Self {
40        Self {
41            theme,
42            vim_mode,
43            config_file,
44            provider,
45            model,
46        }
47    }
48
49    /// Get the TUI configuration
50    pub fn get_config(&self) -> TuiConfig {
51        TuiConfig {
52            theme: self.theme.clone(),
53            vim_mode: self.vim_mode,
54            config_file: self.config_file.clone(),
55            provider: self.provider.clone(),
56            model: self.model.clone(),
57        }
58    }
59}
60
61impl Command for TuiCommand {
62    fn execute(&self) -> CliResult<()> {
63        // Build TUI configuration
64        let config = self.get_config();
65
66        // Launch the TUI application
67        launch_tui(config)
68    }
69}
70
71/// Launch the TUI application
72fn launch_tui(config: TuiConfig) -> CliResult<()> {
73    // Create a runtime for async operations
74    let rt = tokio::runtime::Runtime::new()
75        .map_err(|e| CliError::Internal(format!("Failed to create runtime: {}", e)))?;
76
77    rt.block_on(async {
78        // Import the TUI app
79        use ricecoder_tui::App;
80
81        // Create TUI configuration from CLI config
82        let mut tui_config = ricecoder_tui::TuiConfig::default();
83
84        // Apply theme if specified
85        if let Some(theme) = config.theme {
86            tui_config.theme = theme;
87        }
88
89        // Apply vim mode if enabled
90        if config.vim_mode {
91            tui_config.vim_mode = true;
92        }
93
94        // Apply provider and model if specified
95        if let Some(provider) = config.provider {
96            tui_config.provider = Some(provider);
97        }
98        if let Some(model) = config.model {
99            tui_config.model = Some(model);
100        }
101
102        // Validate provider configuration if specified
103        if tui_config.provider.is_some() {
104            validate_provider_config(&tui_config)?;
105        }
106
107        // Create and run the application
108        let mut app = App::with_config(tui_config)
109            .map_err(|e| CliError::Internal(format!("Failed to initialize TUI: {}", e)))?;
110
111        app.run()
112            .await
113            .map_err(|e| CliError::Internal(format!("TUI error: {}", e)))
114    })
115}
116
117/// Validate provider configuration
118fn validate_provider_config(config: &ricecoder_tui::TuiConfig) -> CliResult<()> {
119    let supported_providers = ["openai", "anthropic", "ollama", "google", "zen"];
120
121    if let Some(ref provider) = config.provider {
122        if !supported_providers.contains(&provider.as_str()) {
123            return Err(CliError::Internal(format!(
124                "Unsupported provider: {}. Supported providers: {}",
125                provider,
126                supported_providers.join(", ")
127            )));
128        }
129    }
130
131    Ok(())
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn test_tui_command_creation() {
140        let cmd = TuiCommand::new(
141            Some("dark".to_string()),
142            true,
143            None,
144            Some("openai".to_string()),
145            Some("gpt-4".to_string()),
146        );
147
148        let config = cmd.get_config();
149        assert_eq!(config.theme, Some("dark".to_string()));
150        assert!(config.vim_mode);
151        assert_eq!(config.provider, Some("openai".to_string()));
152        assert_eq!(config.model, Some("gpt-4".to_string()));
153    }
154
155    #[test]
156    fn test_tui_command_defaults() {
157        let cmd = TuiCommand::new(None, false, None, None, None);
158        let config = cmd.get_config();
159
160        assert_eq!(config.theme, None);
161        assert!(!config.vim_mode);
162        assert_eq!(config.provider, None);
163        assert_eq!(config.model, None);
164    }
165
166    #[test]
167    fn test_tui_config_with_provider() {
168        let cmd = TuiCommand::new(
169            None,
170            false,
171            None,
172            Some("anthropic".to_string()),
173            Some("claude-3-opus".to_string()),
174        );
175
176        let config = cmd.get_config();
177        assert_eq!(config.provider, Some("anthropic".to_string()));
178        assert_eq!(config.model, Some("claude-3-opus".to_string()));
179    }
180
181    #[test]
182    fn test_tui_config_with_theme() {
183        let cmd = TuiCommand::new(Some("monokai".to_string()), false, None, None, None);
184
185        let config = cmd.get_config();
186        assert_eq!(config.theme, Some("monokai".to_string()));
187    }
188
189    #[test]
190    fn test_tui_config_with_vim_mode() {
191        let cmd = TuiCommand::new(None, true, None, None, None);
192
193        let config = cmd.get_config();
194        assert!(config.vim_mode);
195    }
196}