intelli_shell/process/
search.rs

1use std::time::Duration;
2
3use indicatif::{ProgressBar, ProgressStyle};
4use itertools::Itertools;
5use tokio_util::sync::CancellationToken;
6use tracing::instrument;
7
8use super::{InteractiveProcess, Process, ProcessOutput};
9use crate::{
10    cli::SearchCommandsProcess,
11    component::{Component, search::SearchCommandsComponent},
12    config::Config,
13    errors::{AppError, UserFacingError},
14    format_error,
15    service::IntelliShellService,
16    widgets::SPINNER_CHARS,
17};
18
19impl Process for SearchCommandsProcess {
20    #[instrument(skip_all)]
21    async fn execute(
22        self,
23        config: Config,
24        service: IntelliShellService,
25        cancellation_token: CancellationToken,
26    ) -> color_eyre::Result<ProcessOutput> {
27        // Different behaviors based on ai flag
28        if self.ai {
29            // Validate we've a query
30            let prompt = self.query.as_deref().unwrap_or_default();
31            if prompt.trim().is_empty() {
32                return Ok(ProcessOutput::fail().stderr(format_error!(
33                    config.theme,
34                    "{}",
35                    UserFacingError::AiEmptyCommand
36                )));
37            }
38
39            // Setup the progress bar
40            let pb = ProgressBar::new_spinner();
41            pb.set_style(
42                ProgressStyle::with_template("{spinner:.blue} {wide_msg}")
43                    .unwrap()
44                    .tick_strings(&SPINNER_CHARS),
45            );
46            pb.enable_steady_tick(Duration::from_millis(100));
47            pb.set_message("Thinking ...");
48
49            // Suggest commands using AI
50            let res = service.suggest_commands(prompt, cancellation_token).await;
51
52            // Clear the spinner
53            pb.finish_and_clear();
54
55            // Handle the result
56            match res {
57                Ok(commands) => Ok(ProcessOutput::success().stdout(commands.into_iter().map(|c| c.cmd).join("\n"))),
58                Err(AppError::UserFacing(err)) => {
59                    Ok(ProcessOutput::fail().stderr(format_error!(config.theme, "{err}")))
60                }
61                Err(AppError::Unexpected(report)) => Err(report),
62            }
63        } else {
64            // Merge config with args
65            let (config, query) = merge_config(self, config);
66
67            // Search for commands and handle result
68            match service
69                .search_commands(config.search.mode, config.search.user_only, &query)
70                .await
71            {
72                Ok((commands, _)) => {
73                    Ok(ProcessOutput::success().stdout(commands.into_iter().map(|c| c.cmd).join("\n")))
74                }
75                Err(AppError::UserFacing(err)) => {
76                    Ok(ProcessOutput::fail().stderr(format_error!(config.theme, "{err}")))
77                }
78                Err(AppError::Unexpected(report)) => Err(report),
79            }
80        }
81    }
82}
83
84impl InteractiveProcess for SearchCommandsProcess {
85    #[instrument(skip_all)]
86    fn into_component(
87        self,
88        config: Config,
89        service: IntelliShellService,
90        inline: bool,
91        cancellation_token: CancellationToken,
92    ) -> color_eyre::Result<Box<dyn Component>> {
93        let ai = self.ai;
94        let (config, query) = merge_config(self, config);
95        Ok(Box::new(SearchCommandsComponent::new(
96            service,
97            config,
98            inline,
99            query,
100            ai,
101            cancellation_token,
102        )))
103    }
104}
105
106fn merge_config(p: SearchCommandsProcess, mut config: Config) -> (Config, String) {
107    let SearchCommandsProcess {
108        query,
109        mode,
110        user_only,
111        ai: _,
112    } = p;
113    config.search.mode = mode.unwrap_or(config.search.mode);
114    config.search.user_only = user_only || config.search.user_only;
115    (config, query.unwrap_or_default())
116}