intelli_shell/service/
mod.rs1use std::{
2 env,
3 path::{Path, PathBuf},
4 sync::{Arc, Mutex},
5};
6
7use tokio::fs::File;
8use tracing::instrument;
9
10use crate::{
11 config::{AiConfig, SearchTuning},
12 errors::Result,
13 model::{CATEGORY_WORKSPACE, SOURCE_WORKSPACE},
14 service::import::parse_import_items,
15 storage::SqliteStorage,
16 utils::get_working_dir,
17};
18
19mod ai;
20mod command;
21mod completion;
22mod export;
23mod import;
24mod variable;
25mod version;
26
27#[cfg(feature = "tldr")]
28mod tldr;
29
30pub use ai::AiFixProgress;
31pub use completion::{FORBIDDEN_COMPLETION_ROOT_CMD_CHARS, FORBIDDEN_COMPLETION_VARIABLE_CHARS};
32#[cfg(feature = "tldr")]
33pub use tldr::{RepoStatus, TldrFetchProgress};
34
35#[derive(Clone)]
37pub struct IntelliShellService {
38 check_updates: bool,
39 storage: SqliteStorage,
40 tuning: SearchTuning,
41 ai: AiConfig,
42 #[cfg(feature = "tldr")]
43 tldr_repo_path: PathBuf,
44 version_check_state: Arc<Mutex<version::VersionCheckState>>,
45}
46
47impl IntelliShellService {
48 pub fn new(
50 storage: SqliteStorage,
51 tuning: SearchTuning,
52 ai: AiConfig,
53 data_dir: impl AsRef<Path>,
54 check_updates: bool,
55 ) -> Self {
56 Self {
57 check_updates,
58 storage,
59 tuning,
60 ai,
61 #[cfg(feature = "tldr")]
62 tldr_repo_path: data_dir.as_ref().join("tldr"),
63 version_check_state: Arc::new(Mutex::new(version::VersionCheckState::NotStarted)),
64 }
65 }
66
67 #[cfg(debug_assertions)]
68 pub async fn query(&self, sql: String) -> crate::errors::Result<String> {
69 self.storage.query(sql).await
70 }
71
72 #[instrument(skip_all)]
77 pub async fn load_workspace_items(&self) -> Result<bool> {
78 if env::var("INTELLI_SKIP_WORKSPACE")
79 .map(|v| v != "1" && v.to_lowercase() != "true")
80 .unwrap_or(true)
81 && let Some((workspace_file, folder_name)) = find_workspace_file()
82 {
83 tracing::debug!("Found workspace file at {}", workspace_file.display());
84
85 self.storage.setup_workspace_storage().await?;
87
88 let file = File::open(&workspace_file).await?;
90 let tag = format!("#{}", folder_name.as_deref().unwrap_or("workspace"));
91 let items_stream = parse_import_items(file, vec![tag], CATEGORY_WORKSPACE, SOURCE_WORKSPACE);
92
93 let stats = self.storage.import_items(items_stream, false, true).await?;
95
96 tracing::info!(
97 "Loaded {} commands and {} completions from workspace {}",
98 stats.commands_imported,
99 stats.completions_imported,
100 workspace_file.display()
101 );
102
103 Ok(true)
104 } else {
105 Ok(false)
106 }
107 }
108}
109
110fn find_workspace_file() -> Option<(PathBuf, Option<String>)> {
115 let working_dir = PathBuf::from(get_working_dir());
116 let mut current = Some(working_dir.as_path());
117 while let Some(parent) = current {
118 let candidate = parent.join(".intellishell");
119 if candidate.is_file() {
120 let folder_name = parent.file_name().and_then(|n| n.to_str()).map(String::from);
121 return Some((candidate, folder_name));
122 }
123
124 if parent.join(".git").is_dir() {
125 return None;
127 }
128
129 current = parent.parent();
130 }
131 None
132}