1#[allow(unused_imports)]
4use super::{CommandRequest, WorkspaceError, WorkspaceVersionConflict};
5use super::{
6 LocalWorkspaceBackend, ManifestWorkspaceBackend, VirtualPathResolver, WorkspaceCapabilities,
7 WorkspaceChunkCatalog, WorkspaceCommandRunner, WorkspaceFileSystem, WorkspaceFileSystemExt,
8 WorkspaceGit, WorkspaceGitStashProvider, WorkspaceGitWorktreeProvider, WorkspacePath,
9 WorkspacePathResolver, WorkspacePersistentIndex, WorkspaceRef, WorkspaceResult,
10 WorkspaceRetrievalRuntime, WorkspaceSearch, WorkspaceTextReader, WorkspaceWriteOutcome,
11};
12use crate::code_intelligence::{LocalCodeIntelligence, WorkspaceCodeIntelligence};
13use anyhow::{anyhow, Result};
14use std::path::{Path, PathBuf};
15use std::sync::{Arc, OnceLock};
16
17type LazyLexicalHandles = (
19 Arc<WorkspaceChunkCatalog>,
20 Option<Arc<WorkspacePersistentIndex>>,
21);
22
23pub struct WorkspaceServices {
25 workspace_ref: WorkspaceRef,
26 capabilities: WorkspaceCapabilities,
27 path_resolver: Arc<dyn WorkspacePathResolver>,
28 file_system: Arc<dyn WorkspaceFileSystem>,
29 file_system_ext: Option<Arc<dyn WorkspaceFileSystemExt>>,
30 text_reader: Option<Arc<dyn WorkspaceTextReader>>,
31 command_runner: Option<Arc<dyn WorkspaceCommandRunner>>,
32 search: Option<Arc<dyn WorkspaceSearch>>,
33 code_intelligence: Option<Arc<dyn WorkspaceCodeIntelligence>>,
34 chunk_catalog: Option<Arc<WorkspaceChunkCatalog>>,
35 persistent_index: Option<Arc<WorkspacePersistentIndex>>,
36 lazy_lexical_backend: Option<Arc<ManifestWorkspaceBackend>>,
40 lazy_lexical: Arc<OnceLock<LazyLexicalHandles>>,
41 workspace_retrieval: Option<Arc<WorkspaceRetrievalRuntime>>,
42 git: Option<Arc<dyn WorkspaceGit>>,
43 git_stash: Option<Arc<dyn WorkspaceGitStashProvider>>,
44 git_worktree: Option<Arc<dyn WorkspaceGitWorktreeProvider>>,
45 operation_timeout: Option<std::time::Duration>,
49 local_root: Option<PathBuf>,
50}
51
52impl std::fmt::Debug for WorkspaceServices {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 f.debug_struct("WorkspaceServices")
55 .field("workspace_ref", &self.workspace_ref)
56 .field("capabilities", &self.capabilities)
57 .field("file_system_ext", &self.file_system_ext.is_some())
58 .field("text_reader", &self.text_reader.is_some())
59 .field("command_runner", &self.command_runner.is_some())
60 .field("search", &self.search.is_some())
61 .field("code_intelligence", &self.code_intelligence.is_some())
62 .field("chunk_catalog", &self.chunk_catalog.is_some())
63 .field("persistent_index", &self.persistent_index.is_some())
64 .field("workspace_retrieval", &self.workspace_retrieval.is_some())
65 .field("git", &self.git.is_some())
66 .field("git_stash", &self.git_stash.is_some())
67 .field("git_worktree", &self.git_worktree.is_some())
68 .field("local_root", &self.local_root)
69 .finish()
70 }
71}
72
73impl WorkspaceServices {
74 pub(crate) fn new_with_git(
75 workspace_ref: WorkspaceRef,
76 mut capabilities: WorkspaceCapabilities,
77 path_resolver: Arc<dyn WorkspacePathResolver>,
78 file_system: Arc<dyn WorkspaceFileSystem>,
79 command_runner: Option<Arc<dyn WorkspaceCommandRunner>>,
80 search: Option<Arc<dyn WorkspaceSearch>>,
81 git: Option<Arc<dyn WorkspaceGit>>,
82 ) -> Self {
83 if command_runner.is_none() {
84 capabilities.exec = false;
85 }
86 if search.is_none() {
87 capabilities.search = false;
88 }
89 if git.is_none() {
90 capabilities.git = false;
91 }
92 capabilities.code_intelligence = false;
93 Self {
94 workspace_ref,
95 capabilities,
96 path_resolver,
97 file_system,
98 file_system_ext: None,
99 text_reader: None,
100 command_runner,
101 search,
102 code_intelligence: None,
103 chunk_catalog: None,
104 persistent_index: None,
105 lazy_lexical_backend: None,
106 lazy_lexical: Arc::new(OnceLock::new()),
107 workspace_retrieval: None,
108 git,
109 git_stash: None,
110 git_worktree: None,
111 operation_timeout: None,
112 local_root: None,
113 }
114 }
115
116 pub fn builder(
117 workspace_ref: WorkspaceRef,
118 file_system: Arc<dyn WorkspaceFileSystem>,
119 ) -> WorkspaceServicesBuilder {
120 WorkspaceServicesBuilder::new(workspace_ref, file_system)
121 }
122
123 pub fn local(root: impl Into<PathBuf>) -> Arc<Self> {
124 let backend = Arc::new(LocalWorkspaceBackend::new(root.into()));
125 let workspace_ref = WorkspaceRef::new(
126 backend.root.display().to_string(),
127 backend.root.display().to_string(),
128 );
129 let path_resolver: Arc<dyn WorkspacePathResolver> = backend.clone();
130 let file_system: Arc<dyn WorkspaceFileSystem> = backend.clone();
131 let text_reader: Arc<dyn WorkspaceTextReader> = backend.clone();
132 let command_runner: Arc<dyn WorkspaceCommandRunner> = backend.clone();
133 let search: Arc<dyn WorkspaceSearch> = backend.clone();
134 let git: Arc<dyn WorkspaceGit> = backend.clone();
135 let git_stash: Arc<dyn WorkspaceGitStashProvider> = backend.clone();
136 let git_worktree: Arc<dyn WorkspaceGitWorktreeProvider> = backend.clone();
137 Arc::new(Self {
138 workspace_ref,
139 capabilities: WorkspaceCapabilities::local_default(),
140 path_resolver,
141 file_system,
142 file_system_ext: None,
143 text_reader: Some(text_reader),
144 command_runner: Some(command_runner),
145 search: Some(search),
146 code_intelligence: None,
147 chunk_catalog: None,
148 persistent_index: None,
149 lazy_lexical_backend: None,
150 lazy_lexical: Arc::new(OnceLock::new()),
151 workspace_retrieval: None,
152 git: Some(git),
153 git_stash: Some(git_stash),
154 git_worktree: Some(git_worktree),
155 operation_timeout: None,
156 local_root: Some(backend.root.clone()),
157 })
158 }
159
160 pub fn local_with_manifest(root: impl Into<PathBuf>) -> Arc<Self> {
165 let backend = ManifestWorkspaceBackend::new(root);
166 Self::local_with_manifest_backend(backend)
167 }
168
169 pub fn local_with_retrieval(root: impl Into<PathBuf>) -> Arc<Self> {
172 let backend = ManifestWorkspaceBackend::new(root);
173 Self::local_with_retrieval_backend(backend)
174 }
175
176 pub async fn local_with_code_intelligence(
181 root: impl Into<PathBuf>,
182 isolation_scope: impl Into<String>,
183 ) -> Result<Arc<Self>> {
184 let backend = ManifestWorkspaceBackend::new(root);
185 Self::local_with_code_intelligence_backend(backend, isolation_scope).await
186 }
187
188 pub async fn local_with_retrieval_and_code_intelligence(
191 root: impl Into<PathBuf>,
192 isolation_scope: impl Into<String>,
193 ) -> Result<Arc<Self>> {
194 let backend = ManifestWorkspaceBackend::new(root);
195 Self::local_with_retrieval_and_code_intelligence_backend(backend, isolation_scope).await
196 }
197
198 pub async fn local_with_code_intelligence_backend(
200 backend: Arc<ManifestWorkspaceBackend>,
201 isolation_scope: impl Into<String>,
202 ) -> Result<Arc<Self>> {
203 let manifest = backend.manifest();
204 let file_system: Arc<dyn WorkspaceFileSystem> = backend.clone();
205 let services = Self::local_with_manifest_backend(backend);
206 let provider = LocalCodeIntelligence::start(isolation_scope, manifest, file_system)
207 .await
208 .map_err(|error| anyhow!("failed to start Code Intelligence: {error}"))?;
209 Ok(services.with_code_intelligence(provider))
210 }
211
212 pub async fn local_with_retrieval_and_code_intelligence_backend(
214 backend: Arc<ManifestWorkspaceBackend>,
215 isolation_scope: impl Into<String>,
216 ) -> Result<Arc<Self>> {
217 let manifest = backend.manifest();
218 let file_system: Arc<dyn WorkspaceFileSystem> = backend.clone();
219 let services = Self::local_with_retrieval_backend(backend);
220 let provider = LocalCodeIntelligence::start(isolation_scope, manifest, file_system)
221 .await
222 .map_err(|error| anyhow!("failed to start Code Intelligence: {error}"))?;
223 Ok(services.with_code_intelligence(provider))
224 }
225
226 pub fn local_with_manifest_backend(backend: Arc<ManifestWorkspaceBackend>) -> Arc<Self> {
229 Self::local_with_manifest_backend_and_catalog(backend, None, None, None)
230 }
231
232 pub fn local_with_retrieval_backend(backend: Arc<ManifestWorkspaceBackend>) -> Arc<Self> {
240 if backend.catalog_is_configured() {
241 let catalog = backend.chunk_catalog();
242 let persistent = backend.persistent_index();
243 Self::local_with_manifest_backend_and_catalog(
244 backend.clone(),
245 Some(catalog),
246 persistent,
247 Some(backend),
248 )
249 } else {
250 Self::local_with_manifest_backend_and_catalog(
251 backend.clone(),
252 None,
253 None,
254 Some(backend),
255 )
256 }
257 }
258
259 pub fn local_with_indexed_retrieval(root: impl Into<PathBuf>) -> Result<Arc<Self>> {
263 let backend = ManifestWorkspaceBackend::new(root);
264 let index_root = backend.local_root().join(".a3s-code").join("index");
265 let persistent = backend
266 .configure_persistent_index(index_root)
267 .map_err(|error| anyhow!("failed to configure persistent workspace index: {error}"))?;
268 let catalog = backend.chunk_catalog();
269 Ok(Self::local_with_manifest_backend_and_catalog(
270 backend,
271 Some(catalog),
272 Some(persistent),
273 None,
274 ))
275 }
276
277 fn local_with_manifest_backend_and_catalog(
278 backend: Arc<ManifestWorkspaceBackend>,
279 chunk_catalog: Option<Arc<WorkspaceChunkCatalog>>,
280 persistent_index: Option<Arc<WorkspacePersistentIndex>>,
281 lazy_lexical_backend: Option<Arc<ManifestWorkspaceBackend>>,
282 ) -> Arc<Self> {
283 let workspace_ref = WorkspaceRef::new(
284 backend.local_root().display().to_string(),
285 backend.local_root().display().to_string(),
286 );
287 let path_resolver: Arc<dyn WorkspacePathResolver> = backend.clone();
288 let file_system: Arc<dyn WorkspaceFileSystem> = backend.clone();
289 let text_reader: Arc<dyn WorkspaceTextReader> = backend.clone();
290 let command_runner: Arc<dyn WorkspaceCommandRunner> = backend.clone();
291 let search: Arc<dyn WorkspaceSearch> = backend.clone();
292 let git: Arc<dyn WorkspaceGit> = backend.clone();
293 let git_stash: Arc<dyn WorkspaceGitStashProvider> = backend.clone();
294 let git_worktree: Arc<dyn WorkspaceGitWorktreeProvider> = backend.clone();
295 Arc::new(Self {
296 workspace_ref,
297 capabilities: WorkspaceCapabilities::local_default(),
298 path_resolver,
299 file_system,
300 file_system_ext: None,
301 text_reader: Some(text_reader),
302 command_runner: Some(command_runner),
303 search: Some(search),
304 code_intelligence: None,
305 chunk_catalog,
306 persistent_index,
307 lazy_lexical_backend,
308 lazy_lexical: Arc::new(OnceLock::new()),
309 workspace_retrieval: None,
310 git: Some(git),
311 git_stash: Some(git_stash),
312 git_worktree: Some(git_worktree),
313 operation_timeout: None,
314 local_root: Some(backend.local_root().to_path_buf()),
315 })
316 }
317
318 pub fn workspace_ref(&self) -> &WorkspaceRef {
319 &self.workspace_ref
320 }
321
322 pub fn capabilities(&self) -> WorkspaceCapabilities {
323 self.capabilities
324 }
325
326 pub fn normalize_path(&self, input: &str) -> Result<WorkspacePath> {
327 self.path_resolver.normalize(input)
328 }
329
330 pub fn fs(&self) -> Arc<dyn WorkspaceFileSystem> {
331 Arc::clone(&self.file_system)
332 }
333
334 pub fn fs_ext(&self) -> Option<Arc<dyn WorkspaceFileSystemExt>> {
341 self.file_system_ext.clone()
342 }
343
344 pub fn text_reader(&self) -> Option<Arc<dyn WorkspaceTextReader>> {
345 self.text_reader.clone()
346 }
347
348 pub fn command_runner(&self) -> Option<Arc<dyn WorkspaceCommandRunner>> {
349 self.command_runner.clone()
350 }
351
352 pub fn search(&self) -> Option<Arc<dyn WorkspaceSearch>> {
353 self.search.clone()
354 }
355
356 pub fn code_intelligence(&self) -> Option<Arc<dyn WorkspaceCodeIntelligence>> {
358 self.code_intelligence.clone()
359 }
360
361 pub fn chunk_catalog(&self) -> Option<Arc<WorkspaceChunkCatalog>> {
363 if let Some(catalog) = &self.chunk_catalog {
364 return Some(Arc::clone(catalog));
365 }
366 self.ensure_lazy_lexical()
367 .map(|(catalog, _)| Arc::clone(catalog))
368 }
369
370 pub fn persistent_index(&self) -> Option<Arc<WorkspacePersistentIndex>> {
371 if let Some(index) = &self.persistent_index {
372 return Some(Arc::clone(index));
373 }
374 if let Some(index) = self
380 .lazy_lexical_backend
381 .as_ref()
382 .and_then(|backend| backend.ensure_persistent_index())
383 {
384 let _ = self.ensure_lazy_lexical();
385 return Some(index);
386 }
387 if let Some(handles) = self.ensure_lazy_lexical() {
388 if let Some(index) = &handles.1 {
389 return Some(Arc::clone(index));
390 }
391 }
392 None
393 }
394
395 fn ensure_lazy_lexical(&self) -> Option<&LazyLexicalHandles> {
396 let backend = self.lazy_lexical_backend.as_ref()?;
397 Some(self.lazy_lexical.get_or_init(|| {
398 let catalog = backend.chunk_catalog();
399 let persistent = backend.persistent_index();
401 (catalog, persistent)
402 }))
403 }
404
405 pub fn workspace_retrieval(&self) -> Option<Arc<WorkspaceRetrievalRuntime>> {
407 self.workspace_retrieval.clone()
408 }
409
410 pub async fn semantic_search(
413 &self,
414 mut request: super::WorkspaceSemanticSearchRequest,
415 cancellation: tokio_util::sync::CancellationToken,
416 ) -> super::WorkspaceRetrievalResult<super::WorkspaceSemanticSearchResult> {
417 if !self.capabilities.read {
418 return Err(super::WorkspaceRetrievalError::Unavailable);
419 }
420 if let Some(path) = request.path.take() {
421 request.path = Some(
422 self.path_resolver
423 .normalize(&path)
424 .map_err(|_| {
425 super::WorkspaceRetrievalError::InvalidQuery(
426 "path was rejected by the workspace resolver".to_owned(),
427 )
428 })?
429 .as_str()
430 .to_owned(),
431 );
432 }
433 let runtime = self
434 .workspace_retrieval
435 .as_ref()
436 .ok_or(super::WorkspaceRetrievalError::Unavailable)?;
437 runtime
438 .search(
439 request,
440 Arc::clone(&self.file_system),
441 self.operation_timeout,
442 cancellation,
443 )
444 .await
445 }
446
447 pub async fn hybrid_search(
450 &self,
451 mut request: super::WorkspaceHybridSearchRequest,
452 cancellation: tokio_util::sync::CancellationToken,
453 ) -> super::WorkspaceRetrievalResult<super::WorkspaceHybridSearchResult> {
454 if !self.capabilities.read {
455 return Err(super::WorkspaceRetrievalError::Unavailable);
456 }
457 if let Some(path) = request.path.take() {
458 request.path = Some(
459 self.path_resolver
460 .normalize(&path)
461 .map_err(|_| {
462 super::WorkspaceRetrievalError::InvalidQuery(
463 "path was rejected by the workspace resolver".to_owned(),
464 )
465 })?
466 .as_str()
467 .to_owned(),
468 );
469 }
470 let runtime = self
471 .workspace_retrieval
472 .as_ref()
473 .ok_or(super::WorkspaceRetrievalError::Unavailable)?;
474 runtime
475 .hybrid_search(
476 request,
477 Arc::clone(&self.file_system),
478 self.code_intelligence.clone(),
479 self.operation_timeout,
480 cancellation,
481 )
482 .await
483 }
484
485 pub(crate) fn with_workspace_retrieval(
486 &self,
487 runtime: Arc<WorkspaceRetrievalRuntime>,
488 ) -> Option<Arc<Self>> {
489 if self.workspace_retrieval.is_some() {
490 return None;
491 }
492 Some(Arc::new(Self {
493 workspace_ref: self.workspace_ref.clone(),
494 capabilities: self.capabilities,
495 path_resolver: Arc::clone(&self.path_resolver),
496 file_system: Arc::clone(&self.file_system),
497 file_system_ext: self.file_system_ext.clone(),
498 text_reader: self.text_reader.clone(),
499 command_runner: self.command_runner.clone(),
500 search: self.search.clone(),
501 code_intelligence: self.code_intelligence.clone(),
502 chunk_catalog: self.chunk_catalog.clone(),
503 persistent_index: self.persistent_index.clone(),
504 lazy_lexical_backend: self.lazy_lexical_backend.clone(),
505 lazy_lexical: Arc::clone(&self.lazy_lexical),
506 workspace_retrieval: Some(runtime),
507 git: self.git.clone(),
508 git_stash: self.git_stash.clone(),
509 git_worktree: self.git_worktree.clone(),
510 operation_timeout: self.operation_timeout,
511 local_root: self.local_root.clone(),
512 }))
513 }
514
515 pub fn with_code_intelligence(
518 &self,
519 provider: Arc<dyn WorkspaceCodeIntelligence>,
520 ) -> Arc<Self> {
521 let mut capabilities = self.capabilities;
522 capabilities.code_intelligence = true;
523 Arc::new(Self {
524 workspace_ref: self.workspace_ref.clone(),
525 capabilities,
526 path_resolver: Arc::clone(&self.path_resolver),
527 file_system: Arc::clone(&self.file_system),
528 file_system_ext: self.file_system_ext.clone(),
529 text_reader: self.text_reader.clone(),
530 command_runner: self.command_runner.clone(),
531 search: self.search.clone(),
532 code_intelligence: Some(provider),
533 chunk_catalog: self.chunk_catalog.clone(),
534 persistent_index: self.persistent_index.clone(),
535 lazy_lexical_backend: self.lazy_lexical_backend.clone(),
536 lazy_lexical: Arc::clone(&self.lazy_lexical),
537 workspace_retrieval: self.workspace_retrieval.clone(),
538 git: self.git.clone(),
539 git_stash: self.git_stash.clone(),
540 git_worktree: self.git_worktree.clone(),
541 operation_timeout: self.operation_timeout,
542 local_root: self.local_root.clone(),
543 })
544 }
545
546 pub fn git(&self) -> Option<Arc<dyn WorkspaceGit>> {
547 self.git.clone()
548 }
549
550 pub fn git_stash(&self) -> Option<Arc<dyn WorkspaceGitStashProvider>> {
551 self.git_stash.clone()
552 }
553
554 pub fn git_worktree(&self) -> Option<Arc<dyn WorkspaceGitWorktreeProvider>> {
555 self.git_worktree.clone()
556 }
557
558 pub(crate) fn with_git_provider(
575 &self,
576 git: Arc<dyn WorkspaceGit>,
577 git_stash: Option<Arc<dyn WorkspaceGitStashProvider>>,
578 ) -> Arc<Self> {
579 let mut capabilities = self.capabilities;
580 capabilities.git = true;
581 Arc::new(Self {
582 workspace_ref: self.workspace_ref.clone(),
583 capabilities,
584 path_resolver: Arc::clone(&self.path_resolver),
585 file_system: Arc::clone(&self.file_system),
586 file_system_ext: self.file_system_ext.clone(),
587 text_reader: self.text_reader.clone(),
588 command_runner: self.command_runner.clone(),
589 search: self.search.clone(),
590 code_intelligence: self.code_intelligence.clone(),
591 chunk_catalog: self.chunk_catalog.clone(),
592 persistent_index: self.persistent_index.clone(),
593 lazy_lexical_backend: self.lazy_lexical_backend.clone(),
594 lazy_lexical: Arc::clone(&self.lazy_lexical),
595 workspace_retrieval: self.workspace_retrieval.clone(),
596 git: Some(git),
597 git_stash,
598 git_worktree: None,
599 operation_timeout: self.operation_timeout,
600 local_root: self.local_root.clone(),
601 })
602 }
603
604 pub fn operation_timeout(&self) -> Option<std::time::Duration> {
610 self.operation_timeout
611 }
612
613 pub async fn run_with_timeout<F, T, E>(
627 &self,
628 op: &'static str,
629 fut: F,
630 ) -> std::result::Result<T, E>
631 where
632 F: std::future::Future<Output = std::result::Result<T, E>>,
633 E: From<anyhow::Error>,
634 {
635 match self.operation_timeout {
636 Some(d) => tokio::time::timeout(d, fut).await.map_err(|_| {
637 E::from(anyhow!(
638 "workspace operation '{}' timed out after {:?}",
639 op,
640 d
641 ))
642 })?,
643 None => fut.await,
644 }
645 }
646
647 pub async fn read_for_edit(
654 &self,
655 path: &WorkspacePath,
656 ) -> WorkspaceResult<(String, Option<String>)> {
657 if let Some(ext) = self.fs_ext() {
658 let path = path.clone();
659 return self
660 .run_with_timeout("read_text_with_version", async move {
661 let (content, version) = ext.read_text_with_version(&path).await?;
662 Ok((content, Some(version)))
663 })
664 .await;
665 }
666 let fs = self.fs();
667 let path_owned = path.clone();
668 let content = self
669 .run_with_timeout("read_text", async move { fs.read_text(&path_owned).await })
670 .await?;
671 Ok((content, None))
672 }
673
674 pub async fn write_for_edit(
682 &self,
683 path: &WorkspacePath,
684 content: &str,
685 expected_version: Option<&str>,
686 ) -> WorkspaceResult<WorkspaceWriteOutcome> {
687 if let (Some(ext), Some(version)) = (self.fs_ext(), expected_version) {
688 let path = path.clone();
689 let content = content.to_string();
690 let expected = version.to_string();
691 return self
692 .run_with_timeout("write_text_if_version", async move {
693 ext.write_text_if_version(&path, &content, &expected).await
694 })
695 .await;
696 }
697 let fs = self.fs();
698 let path = path.clone();
699 let content = content.to_string();
700 self.run_with_timeout(
701 "write_text",
702 async move { fs.write_text(&path, &content).await },
703 )
704 .await
705 }
706
707 pub fn local_root(&self) -> Option<&Path> {
708 self.local_root.as_deref()
709 }
710
711 pub fn display_path(&self, path: &WorkspacePath) -> String {
712 if path.is_root() {
713 return self.workspace_ref.display_root.clone();
714 }
715
716 let root = self.workspace_ref.display_root.trim_end_matches('/');
717 if root.is_empty() {
718 path.as_str().to_string()
719 } else {
720 format!("{root}/{}", path.as_str())
721 }
722 }
723}
724
725pub struct WorkspaceServicesBuilder {
727 workspace_ref: WorkspaceRef,
728 capabilities: WorkspaceCapabilities,
729 path_resolver: Arc<dyn WorkspacePathResolver>,
730 file_system: Arc<dyn WorkspaceFileSystem>,
731 file_system_ext: Option<Arc<dyn WorkspaceFileSystemExt>>,
732 text_reader: Option<Arc<dyn WorkspaceTextReader>>,
733 command_runner: Option<Arc<dyn WorkspaceCommandRunner>>,
734 search: Option<Arc<dyn WorkspaceSearch>>,
735 code_intelligence: Option<Arc<dyn WorkspaceCodeIntelligence>>,
736 chunk_catalog: Option<Arc<WorkspaceChunkCatalog>>,
737 persistent_index: Option<Arc<WorkspacePersistentIndex>>,
738 git: Option<Arc<dyn WorkspaceGit>>,
739 git_stash: Option<Arc<dyn WorkspaceGitStashProvider>>,
740 git_worktree: Option<Arc<dyn WorkspaceGitWorktreeProvider>>,
741 operation_timeout: Option<std::time::Duration>,
742}
743
744impl WorkspaceServicesBuilder {
745 pub fn new(workspace_ref: WorkspaceRef, file_system: Arc<dyn WorkspaceFileSystem>) -> Self {
746 Self {
747 workspace_ref,
748 capabilities: WorkspaceCapabilities::read_write(),
749 path_resolver: Arc::new(VirtualPathResolver),
750 file_system,
751 file_system_ext: None,
752 text_reader: None,
753 command_runner: None,
754 search: None,
755 code_intelligence: None,
756 chunk_catalog: None,
757 persistent_index: None,
758 git: None,
759 git_stash: None,
760 git_worktree: None,
761 operation_timeout: None,
762 }
763 }
764
765 pub fn capabilities(mut self, capabilities: WorkspaceCapabilities) -> Self {
766 self.capabilities = capabilities;
767 self
768 }
769
770 pub fn command_runner(mut self, command_runner: Arc<dyn WorkspaceCommandRunner>) -> Self {
771 self.capabilities.exec = true;
772 self.command_runner = Some(command_runner);
773 self
774 }
775
776 pub fn search(mut self, search: Arc<dyn WorkspaceSearch>) -> Self {
777 self.capabilities.search = true;
778 self.search = Some(search);
779 self
780 }
781
782 pub fn code_intelligence(mut self, provider: Arc<dyn WorkspaceCodeIntelligence>) -> Self {
783 self.capabilities.code_intelligence = true;
784 self.code_intelligence = Some(provider);
785 self
786 }
787
788 pub fn chunk_catalog(mut self, catalog: Arc<WorkspaceChunkCatalog>) -> Self {
790 self.chunk_catalog = Some(catalog);
791 self
792 }
793
794 pub fn persistent_index(mut self, index: Arc<WorkspacePersistentIndex>) -> Self {
795 self.persistent_index = Some(index);
796 self
797 }
798
799 pub fn git(mut self, git: Arc<dyn WorkspaceGit>) -> Self {
800 self.capabilities.git = true;
801 self.git = Some(git);
802 self
803 }
804
805 pub fn git_stash(mut self, git_stash: Arc<dyn WorkspaceGitStashProvider>) -> Self {
806 self.git_stash = Some(git_stash);
807 self
808 }
809
810 pub fn git_worktree(mut self, git_worktree: Arc<dyn WorkspaceGitWorktreeProvider>) -> Self {
811 self.git_worktree = Some(git_worktree);
812 self
813 }
814
815 pub fn file_system_ext(mut self, ext: Arc<dyn WorkspaceFileSystemExt>) -> Self {
820 self.file_system_ext = Some(ext);
821 self
822 }
823
824 pub fn text_reader(mut self, reader: Arc<dyn WorkspaceTextReader>) -> Self {
825 self.text_reader = Some(reader);
826 self
827 }
828
829 pub fn operation_timeout(mut self, timeout: std::time::Duration) -> Self {
833 self.operation_timeout = Some(timeout);
834 self
835 }
836
837 pub fn build(self) -> Arc<WorkspaceServices> {
838 let mut services = WorkspaceServices::new_with_git(
839 self.workspace_ref,
840 self.capabilities,
841 self.path_resolver,
842 self.file_system,
843 self.command_runner,
844 self.search,
845 self.git,
846 );
847 services.file_system_ext = self.file_system_ext;
848 services.text_reader = self.text_reader;
849 services.capabilities.code_intelligence = self.code_intelligence.is_some();
850 services.code_intelligence = self.code_intelligence;
851 services.chunk_catalog = self.chunk_catalog;
852 services.persistent_index = self.persistent_index;
853 services.workspace_retrieval = None;
854 services.git_stash = self.git_stash;
855 services.git_worktree = self.git_worktree;
856 services.operation_timeout = self.operation_timeout;
857 Arc::new(services)
858 }
859}