intelli_shell/process/
search.rs1use 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 if self.ai {
29 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 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 let res = service.suggest_commands(prompt, cancellation_token).await;
51
52 pb.finish_and_clear();
54
55 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 let (config, query) = merge_config(self, config);
66
67 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}