intelli_shell/process/
completion_new.rs

1use std::time::Duration;
2
3use color_eyre::Result;
4use indicatif::{ProgressBar, ProgressStyle};
5use tokio_util::sync::CancellationToken;
6
7use super::{InteractiveProcess, Process, ProcessOutput};
8use crate::{
9    cli::CompletionNewProcess,
10    component::{
11        Component,
12        completion_edit::{EditCompletionComponent, EditCompletionComponentMode},
13    },
14    config::Config,
15    errors::{AppError, UserFacingError},
16    format_error, format_msg,
17    model::{SOURCE_USER, VariableCompletion},
18    service::IntelliShellService,
19    widgets::SPINNER_CHARS,
20};
21
22impl Process for CompletionNewProcess {
23    async fn execute(
24        self,
25        config: Config,
26        service: IntelliShellService,
27        cancellation_token: CancellationToken,
28    ) -> Result<ProcessOutput> {
29        let root_cmd = self.command.unwrap_or_default();
30        let suggestions_provider = self.provider.unwrap_or_default();
31        let Some(variable_name) = self.variable.filter(|v| !v.trim().is_empty()) else {
32            return Ok(ProcessOutput::fail().stderr(format_error!(
33                config.theme,
34                "{}",
35                UserFacingError::CompletionEmptyVariable
36            )));
37        };
38
39        let mut completion = VariableCompletion::new(SOURCE_USER, root_cmd, variable_name, suggestions_provider);
40
41        // If AI is enabled
42        if self.ai {
43            // Setup the progress bar
44            let pb = ProgressBar::new_spinner();
45            pb.set_style(
46                ProgressStyle::with_template("{spinner:.blue} {wide_msg}")
47                    .unwrap()
48                    .tick_strings(&SPINNER_CHARS),
49            );
50            pb.enable_steady_tick(Duration::from_millis(100));
51            pb.set_message("Thinking ...");
52
53            // Suggest provider using AI
54            let res = service
55                .suggest_completion(
56                    &completion.root_cmd,
57                    &completion.variable,
58                    &completion.suggestions_provider,
59                    cancellation_token,
60                )
61                .await;
62
63            // Clear the spinner
64            pb.finish_and_clear();
65
66            // Handle the result
67            match res {
68                Ok(p) if p.is_empty() => {
69                    return Ok(
70                        ProcessOutput::fail().stderr(format_error!(config.theme, "AI generated an empty response"))
71                    );
72                }
73                Ok(provider) => {
74                    completion.suggestions_provider = provider;
75                }
76                Err(AppError::UserFacing(err)) => {
77                    return Ok(ProcessOutput::fail().stderr(format_error!(config.theme, "{err}")));
78                }
79                Err(AppError::Unexpected(report)) => return Err(report),
80            }
81        }
82
83        match service.create_variable_completion(completion).await {
84            Ok(c) if c.is_global() => Ok(ProcessOutput::success().stderr(format_msg!(
85                config.theme,
86                "Completion for global {} variable stored: {}",
87                config.theme.secondary.apply(&c.variable),
88                config.theme.secondary.apply(&c.suggestions_provider)
89            ))),
90            Ok(c) => Ok(ProcessOutput::success().stderr(format_msg!(
91                config.theme,
92                "Completion for {} variable within {} commands stored: {}",
93                config.theme.secondary.apply(&c.variable),
94                config.theme.secondary.apply(&c.root_cmd),
95                config.theme.secondary.apply(&c.suggestions_provider)
96            ))),
97            Err(AppError::UserFacing(err)) => Ok(ProcessOutput::fail().stderr(format_error!(config.theme, "{err}"))),
98            Err(AppError::Unexpected(report)) => Err(report),
99        }
100    }
101}
102
103impl InteractiveProcess for CompletionNewProcess {
104    fn into_component(
105        self,
106        config: Config,
107        service: IntelliShellService,
108        inline: bool,
109        cancellation_token: CancellationToken,
110    ) -> Result<Box<dyn Component>> {
111        let root_cmd = self.command.unwrap_or_default();
112        let suggestions_provider = self.provider.unwrap_or_default();
113        let variable_name = self.variable.unwrap_or_default();
114
115        let completion = VariableCompletion::new(SOURCE_USER, root_cmd, variable_name, suggestions_provider);
116
117        let component = EditCompletionComponent::new(
118            service,
119            config.theme,
120            inline,
121            completion,
122            EditCompletionComponentMode::New { ai: self.ai },
123            cancellation_token,
124        );
125        Ok(Box::new(component))
126    }
127}