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
379 .lazy_lexical_backend
380 .as_ref()
381 .and_then(|backend| backend.ensure_persistent_index())
382 {
383 let _ = self.ensure_lazy_lexical();
384 return Some(index);
385 }
386 if let Some(handles) = self.ensure_lazy_lexical() {
387 if let Some(index) = &handles.1 {
388 return Some(Arc::clone(index));
389 }
390 }
391 None
392 }
393
394 fn ensure_lazy_lexical(&self) -> Option<&LazyLexicalHandles> {
395 let backend = self.lazy_lexical_backend.as_ref()?;
396 Some(self.lazy_lexical.get_or_init(|| {
397 let catalog = backend.chunk_catalog();
398 let persistent = backend.persistent_index();
400 (catalog, persistent)
401 }))
402 }
403
404 pub fn workspace_retrieval(&self) -> Option<Arc<WorkspaceRetrievalRuntime>> {
406 self.workspace_retrieval.clone()
407 }
408
409 pub async fn semantic_search(
412 &self,
413 mut request: super::WorkspaceSemanticSearchRequest,
414 cancellation: tokio_util::sync::CancellationToken,
415 ) -> super::WorkspaceRetrievalResult<super::WorkspaceSemanticSearchResult> {
416 if !self.capabilities.read {
417 return Err(super::WorkspaceRetrievalError::Unavailable);
418 }
419 if let Some(path) = request.path.take() {
420 request.path = Some(
421 self.path_resolver
422 .normalize(&path)
423 .map_err(|_| {
424 super::WorkspaceRetrievalError::InvalidQuery(
425 "path was rejected by the workspace resolver".to_owned(),
426 )
427 })?
428 .as_str()
429 .to_owned(),
430 );
431 }
432 let runtime = self
433 .workspace_retrieval
434 .as_ref()
435 .ok_or(super::WorkspaceRetrievalError::Unavailable)?;
436 runtime
437 .search(
438 request,
439 Arc::clone(&self.file_system),
440 self.operation_timeout,
441 cancellation,
442 )
443 .await
444 }
445
446 pub async fn hybrid_search(
449 &self,
450 mut request: super::WorkspaceHybridSearchRequest,
451 cancellation: tokio_util::sync::CancellationToken,
452 ) -> super::WorkspaceRetrievalResult<super::WorkspaceHybridSearchResult> {
453 if !self.capabilities.read {
454 return Err(super::WorkspaceRetrievalError::Unavailable);
455 }
456 if let Some(path) = request.path.take() {
457 request.path = Some(
458 self.path_resolver
459 .normalize(&path)
460 .map_err(|_| {
461 super::WorkspaceRetrievalError::InvalidQuery(
462 "path was rejected by the workspace resolver".to_owned(),
463 )
464 })?
465 .as_str()
466 .to_owned(),
467 );
468 }
469 let runtime = self
470 .workspace_retrieval
471 .as_ref()
472 .ok_or(super::WorkspaceRetrievalError::Unavailable)?;
473 runtime
474 .hybrid_search(
475 request,
476 Arc::clone(&self.file_system),
477 self.code_intelligence.clone(),
478 self.operation_timeout,
479 cancellation,
480 )
481 .await
482 }
483
484 pub(crate) fn with_workspace_retrieval(
485 &self,
486 runtime: Arc<WorkspaceRetrievalRuntime>,
487 ) -> Option<Arc<Self>> {
488 if self.workspace_retrieval.is_some() {
489 return None;
490 }
491 Some(Arc::new(Self {
492 workspace_ref: self.workspace_ref.clone(),
493 capabilities: self.capabilities,
494 path_resolver: Arc::clone(&self.path_resolver),
495 file_system: Arc::clone(&self.file_system),
496 file_system_ext: self.file_system_ext.clone(),
497 text_reader: self.text_reader.clone(),
498 command_runner: self.command_runner.clone(),
499 search: self.search.clone(),
500 code_intelligence: self.code_intelligence.clone(),
501 chunk_catalog: self.chunk_catalog.clone(),
502 persistent_index: self.persistent_index.clone(),
503 lazy_lexical_backend: self.lazy_lexical_backend.clone(),
504 lazy_lexical: Arc::clone(&self.lazy_lexical),
505 workspace_retrieval: Some(runtime),
506 git: self.git.clone(),
507 git_stash: self.git_stash.clone(),
508 git_worktree: self.git_worktree.clone(),
509 operation_timeout: self.operation_timeout,
510 local_root: self.local_root.clone(),
511 }))
512 }
513
514 pub fn with_code_intelligence(
517 &self,
518 provider: Arc<dyn WorkspaceCodeIntelligence>,
519 ) -> Arc<Self> {
520 let mut capabilities = self.capabilities;
521 capabilities.code_intelligence = true;
522 Arc::new(Self {
523 workspace_ref: self.workspace_ref.clone(),
524 capabilities,
525 path_resolver: Arc::clone(&self.path_resolver),
526 file_system: Arc::clone(&self.file_system),
527 file_system_ext: self.file_system_ext.clone(),
528 text_reader: self.text_reader.clone(),
529 command_runner: self.command_runner.clone(),
530 search: self.search.clone(),
531 code_intelligence: Some(provider),
532 chunk_catalog: self.chunk_catalog.clone(),
533 persistent_index: self.persistent_index.clone(),
534 lazy_lexical_backend: self.lazy_lexical_backend.clone(),
535 lazy_lexical: Arc::clone(&self.lazy_lexical),
536 workspace_retrieval: self.workspace_retrieval.clone(),
537 git: self.git.clone(),
538 git_stash: self.git_stash.clone(),
539 git_worktree: self.git_worktree.clone(),
540 operation_timeout: self.operation_timeout,
541 local_root: self.local_root.clone(),
542 })
543 }
544
545 pub fn git(&self) -> Option<Arc<dyn WorkspaceGit>> {
546 self.git.clone()
547 }
548
549 pub fn git_stash(&self) -> Option<Arc<dyn WorkspaceGitStashProvider>> {
550 self.git_stash.clone()
551 }
552
553 pub fn git_worktree(&self) -> Option<Arc<dyn WorkspaceGitWorktreeProvider>> {
554 self.git_worktree.clone()
555 }
556
557 pub(crate) fn with_git_provider(
574 &self,
575 git: Arc<dyn WorkspaceGit>,
576 git_stash: Option<Arc<dyn WorkspaceGitStashProvider>>,
577 ) -> Arc<Self> {
578 let mut capabilities = self.capabilities;
579 capabilities.git = true;
580 Arc::new(Self {
581 workspace_ref: self.workspace_ref.clone(),
582 capabilities,
583 path_resolver: Arc::clone(&self.path_resolver),
584 file_system: Arc::clone(&self.file_system),
585 file_system_ext: self.file_system_ext.clone(),
586 text_reader: self.text_reader.clone(),
587 command_runner: self.command_runner.clone(),
588 search: self.search.clone(),
589 code_intelligence: self.code_intelligence.clone(),
590 chunk_catalog: self.chunk_catalog.clone(),
591 persistent_index: self.persistent_index.clone(),
592 lazy_lexical_backend: self.lazy_lexical_backend.clone(),
593 lazy_lexical: Arc::clone(&self.lazy_lexical),
594 workspace_retrieval: self.workspace_retrieval.clone(),
595 git: Some(git),
596 git_stash,
597 git_worktree: None,
598 operation_timeout: self.operation_timeout,
599 local_root: self.local_root.clone(),
600 })
601 }
602
603 pub fn operation_timeout(&self) -> Option<std::time::Duration> {
609 self.operation_timeout
610 }
611
612 pub async fn run_with_timeout<F, T, E>(
626 &self,
627 op: &'static str,
628 fut: F,
629 ) -> std::result::Result<T, E>
630 where
631 F: std::future::Future<Output = std::result::Result<T, E>>,
632 E: From<anyhow::Error>,
633 {
634 match self.operation_timeout {
635 Some(d) => tokio::time::timeout(d, fut).await.map_err(|_| {
636 E::from(anyhow!(
637 "workspace operation '{}' timed out after {:?}",
638 op,
639 d
640 ))
641 })?,
642 None => fut.await,
643 }
644 }
645
646 pub async fn read_for_edit(
653 &self,
654 path: &WorkspacePath,
655 ) -> WorkspaceResult<(String, Option<String>)> {
656 if let Some(ext) = self.fs_ext() {
657 let path = path.clone();
658 return self
659 .run_with_timeout("read_text_with_version", async move {
660 let (content, version) = ext.read_text_with_version(&path).await?;
661 Ok((content, Some(version)))
662 })
663 .await;
664 }
665 let fs = self.fs();
666 let path_owned = path.clone();
667 let content = self
668 .run_with_timeout("read_text", async move { fs.read_text(&path_owned).await })
669 .await?;
670 Ok((content, None))
671 }
672
673 pub async fn write_for_edit(
681 &self,
682 path: &WorkspacePath,
683 content: &str,
684 expected_version: Option<&str>,
685 ) -> WorkspaceResult<WorkspaceWriteOutcome> {
686 if let (Some(ext), Some(version)) = (self.fs_ext(), expected_version) {
687 let path = path.clone();
688 let content = content.to_string();
689 let expected = version.to_string();
690 return self
691 .run_with_timeout("write_text_if_version", async move {
692 ext.write_text_if_version(&path, &content, &expected).await
693 })
694 .await;
695 }
696 let fs = self.fs();
697 let path = path.clone();
698 let content = content.to_string();
699 self.run_with_timeout(
700 "write_text",
701 async move { fs.write_text(&path, &content).await },
702 )
703 .await
704 }
705
706 pub fn local_root(&self) -> Option<&Path> {
707 self.local_root.as_deref()
708 }
709
710 pub fn display_path(&self, path: &WorkspacePath) -> String {
711 if path.is_root() {
712 return self.workspace_ref.display_root.clone();
713 }
714
715 let root = self.workspace_ref.display_root.trim_end_matches('/');
716 if root.is_empty() {
717 path.as_str().to_string()
718 } else {
719 format!("{root}/{}", path.as_str())
720 }
721 }
722}
723
724pub struct WorkspaceServicesBuilder {
726 workspace_ref: WorkspaceRef,
727 capabilities: WorkspaceCapabilities,
728 path_resolver: Arc<dyn WorkspacePathResolver>,
729 file_system: Arc<dyn WorkspaceFileSystem>,
730 file_system_ext: Option<Arc<dyn WorkspaceFileSystemExt>>,
731 text_reader: Option<Arc<dyn WorkspaceTextReader>>,
732 command_runner: Option<Arc<dyn WorkspaceCommandRunner>>,
733 search: Option<Arc<dyn WorkspaceSearch>>,
734 code_intelligence: Option<Arc<dyn WorkspaceCodeIntelligence>>,
735 chunk_catalog: Option<Arc<WorkspaceChunkCatalog>>,
736 persistent_index: Option<Arc<WorkspacePersistentIndex>>,
737 git: Option<Arc<dyn WorkspaceGit>>,
738 git_stash: Option<Arc<dyn WorkspaceGitStashProvider>>,
739 git_worktree: Option<Arc<dyn WorkspaceGitWorktreeProvider>>,
740 operation_timeout: Option<std::time::Duration>,
741}
742
743impl WorkspaceServicesBuilder {
744 pub fn new(workspace_ref: WorkspaceRef, file_system: Arc<dyn WorkspaceFileSystem>) -> Self {
745 Self {
746 workspace_ref,
747 capabilities: WorkspaceCapabilities::read_write(),
748 path_resolver: Arc::new(VirtualPathResolver),
749 file_system,
750 file_system_ext: None,
751 text_reader: None,
752 command_runner: None,
753 search: None,
754 code_intelligence: None,
755 chunk_catalog: None,
756 persistent_index: None,
757 git: None,
758 git_stash: None,
759 git_worktree: None,
760 operation_timeout: None,
761 }
762 }
763
764 pub fn capabilities(mut self, capabilities: WorkspaceCapabilities) -> Self {
765 self.capabilities = capabilities;
766 self
767 }
768
769 pub fn command_runner(mut self, command_runner: Arc<dyn WorkspaceCommandRunner>) -> Self {
770 self.capabilities.exec = true;
771 self.command_runner = Some(command_runner);
772 self
773 }
774
775 pub fn search(mut self, search: Arc<dyn WorkspaceSearch>) -> Self {
776 self.capabilities.search = true;
777 self.search = Some(search);
778 self
779 }
780
781 pub fn code_intelligence(mut self, provider: Arc<dyn WorkspaceCodeIntelligence>) -> Self {
782 self.capabilities.code_intelligence = true;
783 self.code_intelligence = Some(provider);
784 self
785 }
786
787 pub fn chunk_catalog(mut self, catalog: Arc<WorkspaceChunkCatalog>) -> Self {
789 self.chunk_catalog = Some(catalog);
790 self
791 }
792
793 pub fn persistent_index(mut self, index: Arc<WorkspacePersistentIndex>) -> Self {
794 self.persistent_index = Some(index);
795 self
796 }
797
798 pub fn git(mut self, git: Arc<dyn WorkspaceGit>) -> Self {
799 self.capabilities.git = true;
800 self.git = Some(git);
801 self
802 }
803
804 pub fn git_stash(mut self, git_stash: Arc<dyn WorkspaceGitStashProvider>) -> Self {
805 self.git_stash = Some(git_stash);
806 self
807 }
808
809 pub fn git_worktree(mut self, git_worktree: Arc<dyn WorkspaceGitWorktreeProvider>) -> Self {
810 self.git_worktree = Some(git_worktree);
811 self
812 }
813
814 pub fn file_system_ext(mut self, ext: Arc<dyn WorkspaceFileSystemExt>) -> Self {
819 self.file_system_ext = Some(ext);
820 self
821 }
822
823 pub fn text_reader(mut self, reader: Arc<dyn WorkspaceTextReader>) -> Self {
824 self.text_reader = Some(reader);
825 self
826 }
827
828 pub fn operation_timeout(mut self, timeout: std::time::Duration) -> Self {
832 self.operation_timeout = Some(timeout);
833 self
834 }
835
836 pub fn build(self) -> Arc<WorkspaceServices> {
837 let mut services = WorkspaceServices::new_with_git(
838 self.workspace_ref,
839 self.capabilities,
840 self.path_resolver,
841 self.file_system,
842 self.command_runner,
843 self.search,
844 self.git,
845 );
846 services.file_system_ext = self.file_system_ext;
847 services.text_reader = self.text_reader;
848 services.capabilities.code_intelligence = self.code_intelligence.is_some();
849 services.code_intelligence = self.code_intelligence;
850 services.chunk_catalog = self.chunk_catalog;
851 services.persistent_index = self.persistent_index;
852 services.workspace_retrieval = None;
853 services.git_stash = self.git_stash;
854 services.git_worktree = self.git_worktree;
855 services.operation_timeout = self.operation_timeout;
856 Arc::new(services)
857 }
858}