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;
16
17pub struct WorkspaceServices {
19 workspace_ref: WorkspaceRef,
20 capabilities: WorkspaceCapabilities,
21 path_resolver: Arc<dyn WorkspacePathResolver>,
22 file_system: Arc<dyn WorkspaceFileSystem>,
23 file_system_ext: Option<Arc<dyn WorkspaceFileSystemExt>>,
24 text_reader: Option<Arc<dyn WorkspaceTextReader>>,
25 command_runner: Option<Arc<dyn WorkspaceCommandRunner>>,
26 search: Option<Arc<dyn WorkspaceSearch>>,
27 code_intelligence: Option<Arc<dyn WorkspaceCodeIntelligence>>,
28 chunk_catalog: Option<Arc<WorkspaceChunkCatalog>>,
29 persistent_index: Option<Arc<WorkspacePersistentIndex>>,
30 workspace_retrieval: Option<Arc<WorkspaceRetrievalRuntime>>,
31 git: Option<Arc<dyn WorkspaceGit>>,
32 git_stash: Option<Arc<dyn WorkspaceGitStashProvider>>,
33 git_worktree: Option<Arc<dyn WorkspaceGitWorktreeProvider>>,
34 operation_timeout: Option<std::time::Duration>,
38 local_root: Option<PathBuf>,
39}
40
41impl std::fmt::Debug for WorkspaceServices {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 f.debug_struct("WorkspaceServices")
44 .field("workspace_ref", &self.workspace_ref)
45 .field("capabilities", &self.capabilities)
46 .field("file_system_ext", &self.file_system_ext.is_some())
47 .field("text_reader", &self.text_reader.is_some())
48 .field("command_runner", &self.command_runner.is_some())
49 .field("search", &self.search.is_some())
50 .field("code_intelligence", &self.code_intelligence.is_some())
51 .field("chunk_catalog", &self.chunk_catalog.is_some())
52 .field("persistent_index", &self.persistent_index.is_some())
53 .field("workspace_retrieval", &self.workspace_retrieval.is_some())
54 .field("git", &self.git.is_some())
55 .field("git_stash", &self.git_stash.is_some())
56 .field("git_worktree", &self.git_worktree.is_some())
57 .field("local_root", &self.local_root)
58 .finish()
59 }
60}
61
62impl WorkspaceServices {
63 pub(crate) fn new_with_git(
64 workspace_ref: WorkspaceRef,
65 mut capabilities: WorkspaceCapabilities,
66 path_resolver: Arc<dyn WorkspacePathResolver>,
67 file_system: Arc<dyn WorkspaceFileSystem>,
68 command_runner: Option<Arc<dyn WorkspaceCommandRunner>>,
69 search: Option<Arc<dyn WorkspaceSearch>>,
70 git: Option<Arc<dyn WorkspaceGit>>,
71 ) -> Self {
72 if command_runner.is_none() {
73 capabilities.exec = false;
74 }
75 if search.is_none() {
76 capabilities.search = false;
77 }
78 if git.is_none() {
79 capabilities.git = false;
80 }
81 capabilities.code_intelligence = false;
82 Self {
83 workspace_ref,
84 capabilities,
85 path_resolver,
86 file_system,
87 file_system_ext: None,
88 text_reader: None,
89 command_runner,
90 search,
91 code_intelligence: None,
92 chunk_catalog: None,
93 persistent_index: None,
94 workspace_retrieval: None,
95 git,
96 git_stash: None,
97 git_worktree: None,
98 operation_timeout: None,
99 local_root: None,
100 }
101 }
102
103 pub fn builder(
104 workspace_ref: WorkspaceRef,
105 file_system: Arc<dyn WorkspaceFileSystem>,
106 ) -> WorkspaceServicesBuilder {
107 WorkspaceServicesBuilder::new(workspace_ref, file_system)
108 }
109
110 pub fn local(root: impl Into<PathBuf>) -> Arc<Self> {
111 let backend = Arc::new(LocalWorkspaceBackend::new(root.into()));
112 let workspace_ref = WorkspaceRef::new(
113 backend.root.display().to_string(),
114 backend.root.display().to_string(),
115 );
116 let path_resolver: Arc<dyn WorkspacePathResolver> = backend.clone();
117 let file_system: Arc<dyn WorkspaceFileSystem> = backend.clone();
118 let text_reader: Arc<dyn WorkspaceTextReader> = backend.clone();
119 let command_runner: Arc<dyn WorkspaceCommandRunner> = backend.clone();
120 let search: Arc<dyn WorkspaceSearch> = backend.clone();
121 let git: Arc<dyn WorkspaceGit> = backend.clone();
122 let git_stash: Arc<dyn WorkspaceGitStashProvider> = backend.clone();
123 let git_worktree: Arc<dyn WorkspaceGitWorktreeProvider> = backend.clone();
124 Arc::new(Self {
125 workspace_ref,
126 capabilities: WorkspaceCapabilities::local_default(),
127 path_resolver,
128 file_system,
129 file_system_ext: None,
130 text_reader: Some(text_reader),
131 command_runner: Some(command_runner),
132 search: Some(search),
133 code_intelligence: None,
134 chunk_catalog: None,
135 persistent_index: None,
136 workspace_retrieval: None,
137 git: Some(git),
138 git_stash: Some(git_stash),
139 git_worktree: Some(git_worktree),
140 operation_timeout: None,
141 local_root: Some(backend.root.clone()),
142 })
143 }
144
145 pub fn local_with_manifest(root: impl Into<PathBuf>) -> Arc<Self> {
150 let backend = ManifestWorkspaceBackend::new(root);
151 Self::local_with_manifest_backend(backend)
152 }
153
154 pub fn local_with_retrieval(root: impl Into<PathBuf>) -> Arc<Self> {
157 let backend = ManifestWorkspaceBackend::new(root);
158 Self::local_with_retrieval_backend(backend)
159 }
160
161 pub async fn local_with_code_intelligence(
166 root: impl Into<PathBuf>,
167 isolation_scope: impl Into<String>,
168 ) -> Result<Arc<Self>> {
169 let backend = ManifestWorkspaceBackend::new(root);
170 Self::local_with_code_intelligence_backend(backend, isolation_scope).await
171 }
172
173 pub async fn local_with_retrieval_and_code_intelligence(
176 root: impl Into<PathBuf>,
177 isolation_scope: impl Into<String>,
178 ) -> Result<Arc<Self>> {
179 let backend = ManifestWorkspaceBackend::new(root);
180 Self::local_with_retrieval_and_code_intelligence_backend(backend, isolation_scope).await
181 }
182
183 pub async fn local_with_code_intelligence_backend(
185 backend: Arc<ManifestWorkspaceBackend>,
186 isolation_scope: impl Into<String>,
187 ) -> Result<Arc<Self>> {
188 let manifest = backend.manifest();
189 let file_system: Arc<dyn WorkspaceFileSystem> = backend.clone();
190 let services = Self::local_with_manifest_backend(backend);
191 let provider = LocalCodeIntelligence::start(isolation_scope, manifest, file_system)
192 .await
193 .map_err(|error| anyhow!("failed to start Code Intelligence: {error}"))?;
194 Ok(services.with_code_intelligence(provider))
195 }
196
197 pub async fn local_with_retrieval_and_code_intelligence_backend(
199 backend: Arc<ManifestWorkspaceBackend>,
200 isolation_scope: impl Into<String>,
201 ) -> Result<Arc<Self>> {
202 let manifest = backend.manifest();
203 let file_system: Arc<dyn WorkspaceFileSystem> = backend.clone();
204 let services = Self::local_with_retrieval_backend(backend);
205 let provider = LocalCodeIntelligence::start(isolation_scope, manifest, file_system)
206 .await
207 .map_err(|error| anyhow!("failed to start Code Intelligence: {error}"))?;
208 Ok(services.with_code_intelligence(provider))
209 }
210
211 pub fn local_with_manifest_backend(backend: Arc<ManifestWorkspaceBackend>) -> Arc<Self> {
214 Self::local_with_manifest_backend_and_catalog(backend, None, None)
215 }
216
217 pub fn local_with_retrieval_backend(backend: Arc<ManifestWorkspaceBackend>) -> Arc<Self> {
219 let catalog = backend.chunk_catalog();
220 let persistent = backend.persistent_index();
221 Self::local_with_manifest_backend_and_catalog(backend, Some(catalog), persistent)
222 }
223
224 pub fn local_with_indexed_retrieval(root: impl Into<PathBuf>) -> Result<Arc<Self>> {
228 let backend = ManifestWorkspaceBackend::new(root);
229 let index_root = backend.local_root().join(".a3s-code").join("index");
230 let persistent = backend
231 .configure_persistent_index(index_root)
232 .map_err(|error| anyhow!("failed to configure persistent workspace index: {error}"))?;
233 let catalog = backend.chunk_catalog();
234 Ok(Self::local_with_manifest_backend_and_catalog(
235 backend,
236 Some(catalog),
237 Some(persistent),
238 ))
239 }
240
241 fn local_with_manifest_backend_and_catalog(
242 backend: Arc<ManifestWorkspaceBackend>,
243 chunk_catalog: Option<Arc<WorkspaceChunkCatalog>>,
244 persistent_index: Option<Arc<WorkspacePersistentIndex>>,
245 ) -> Arc<Self> {
246 let workspace_ref = WorkspaceRef::new(
247 backend.local_root().display().to_string(),
248 backend.local_root().display().to_string(),
249 );
250 let path_resolver: Arc<dyn WorkspacePathResolver> = backend.clone();
251 let file_system: Arc<dyn WorkspaceFileSystem> = backend.clone();
252 let text_reader: Arc<dyn WorkspaceTextReader> = backend.clone();
253 let command_runner: Arc<dyn WorkspaceCommandRunner> = backend.clone();
254 let search: Arc<dyn WorkspaceSearch> = backend.clone();
255 let git: Arc<dyn WorkspaceGit> = backend.clone();
256 let git_stash: Arc<dyn WorkspaceGitStashProvider> = backend.clone();
257 let git_worktree: Arc<dyn WorkspaceGitWorktreeProvider> = backend.clone();
258 Arc::new(Self {
259 workspace_ref,
260 capabilities: WorkspaceCapabilities::local_default(),
261 path_resolver,
262 file_system,
263 file_system_ext: None,
264 text_reader: Some(text_reader),
265 command_runner: Some(command_runner),
266 search: Some(search),
267 code_intelligence: None,
268 chunk_catalog,
269 persistent_index,
270 workspace_retrieval: None,
271 git: Some(git),
272 git_stash: Some(git_stash),
273 git_worktree: Some(git_worktree),
274 operation_timeout: None,
275 local_root: Some(backend.local_root().to_path_buf()),
276 })
277 }
278
279 pub fn workspace_ref(&self) -> &WorkspaceRef {
280 &self.workspace_ref
281 }
282
283 pub fn capabilities(&self) -> WorkspaceCapabilities {
284 self.capabilities
285 }
286
287 pub fn normalize_path(&self, input: &str) -> Result<WorkspacePath> {
288 self.path_resolver.normalize(input)
289 }
290
291 pub fn fs(&self) -> Arc<dyn WorkspaceFileSystem> {
292 Arc::clone(&self.file_system)
293 }
294
295 pub fn fs_ext(&self) -> Option<Arc<dyn WorkspaceFileSystemExt>> {
302 self.file_system_ext.clone()
303 }
304
305 pub fn text_reader(&self) -> Option<Arc<dyn WorkspaceTextReader>> {
306 self.text_reader.clone()
307 }
308
309 pub fn command_runner(&self) -> Option<Arc<dyn WorkspaceCommandRunner>> {
310 self.command_runner.clone()
311 }
312
313 pub fn search(&self) -> Option<Arc<dyn WorkspaceSearch>> {
314 self.search.clone()
315 }
316
317 pub fn code_intelligence(&self) -> Option<Arc<dyn WorkspaceCodeIntelligence>> {
319 self.code_intelligence.clone()
320 }
321
322 pub fn chunk_catalog(&self) -> Option<Arc<WorkspaceChunkCatalog>> {
324 self.chunk_catalog.clone()
325 }
326
327 pub fn persistent_index(&self) -> Option<Arc<WorkspacePersistentIndex>> {
328 self.persistent_index.clone()
329 }
330
331 pub fn workspace_retrieval(&self) -> Option<Arc<WorkspaceRetrievalRuntime>> {
333 self.workspace_retrieval.clone()
334 }
335
336 pub async fn semantic_search(
339 &self,
340 mut request: super::WorkspaceSemanticSearchRequest,
341 cancellation: tokio_util::sync::CancellationToken,
342 ) -> super::WorkspaceRetrievalResult<super::WorkspaceSemanticSearchResult> {
343 if !self.capabilities.read {
344 return Err(super::WorkspaceRetrievalError::Unavailable);
345 }
346 if let Some(path) = request.path.take() {
347 request.path = Some(
348 self.path_resolver
349 .normalize(&path)
350 .map_err(|_| {
351 super::WorkspaceRetrievalError::InvalidQuery(
352 "path was rejected by the workspace resolver".to_owned(),
353 )
354 })?
355 .as_str()
356 .to_owned(),
357 );
358 }
359 let runtime = self
360 .workspace_retrieval
361 .as_ref()
362 .ok_or(super::WorkspaceRetrievalError::Unavailable)?;
363 runtime
364 .search(
365 request,
366 Arc::clone(&self.file_system),
367 self.operation_timeout,
368 cancellation,
369 )
370 .await
371 }
372
373 pub async fn hybrid_search(
376 &self,
377 mut request: super::WorkspaceHybridSearchRequest,
378 cancellation: tokio_util::sync::CancellationToken,
379 ) -> super::WorkspaceRetrievalResult<super::WorkspaceHybridSearchResult> {
380 if !self.capabilities.read {
381 return Err(super::WorkspaceRetrievalError::Unavailable);
382 }
383 if let Some(path) = request.path.take() {
384 request.path = Some(
385 self.path_resolver
386 .normalize(&path)
387 .map_err(|_| {
388 super::WorkspaceRetrievalError::InvalidQuery(
389 "path was rejected by the workspace resolver".to_owned(),
390 )
391 })?
392 .as_str()
393 .to_owned(),
394 );
395 }
396 let runtime = self
397 .workspace_retrieval
398 .as_ref()
399 .ok_or(super::WorkspaceRetrievalError::Unavailable)?;
400 runtime
401 .hybrid_search(
402 request,
403 Arc::clone(&self.file_system),
404 self.code_intelligence.clone(),
405 self.operation_timeout,
406 cancellation,
407 )
408 .await
409 }
410
411 pub(crate) fn with_workspace_retrieval(
412 &self,
413 runtime: Arc<WorkspaceRetrievalRuntime>,
414 ) -> Option<Arc<Self>> {
415 if self.workspace_retrieval.is_some() {
416 return None;
417 }
418 Some(Arc::new(Self {
419 workspace_ref: self.workspace_ref.clone(),
420 capabilities: self.capabilities,
421 path_resolver: Arc::clone(&self.path_resolver),
422 file_system: Arc::clone(&self.file_system),
423 file_system_ext: self.file_system_ext.clone(),
424 text_reader: self.text_reader.clone(),
425 command_runner: self.command_runner.clone(),
426 search: self.search.clone(),
427 code_intelligence: self.code_intelligence.clone(),
428 chunk_catalog: self.chunk_catalog.clone(),
429 persistent_index: self.persistent_index.clone(),
430 workspace_retrieval: Some(runtime),
431 git: self.git.clone(),
432 git_stash: self.git_stash.clone(),
433 git_worktree: self.git_worktree.clone(),
434 operation_timeout: self.operation_timeout,
435 local_root: self.local_root.clone(),
436 }))
437 }
438
439 pub fn with_code_intelligence(
442 &self,
443 provider: Arc<dyn WorkspaceCodeIntelligence>,
444 ) -> Arc<Self> {
445 let mut capabilities = self.capabilities;
446 capabilities.code_intelligence = true;
447 Arc::new(Self {
448 workspace_ref: self.workspace_ref.clone(),
449 capabilities,
450 path_resolver: Arc::clone(&self.path_resolver),
451 file_system: Arc::clone(&self.file_system),
452 file_system_ext: self.file_system_ext.clone(),
453 text_reader: self.text_reader.clone(),
454 command_runner: self.command_runner.clone(),
455 search: self.search.clone(),
456 code_intelligence: Some(provider),
457 chunk_catalog: self.chunk_catalog.clone(),
458 persistent_index: self.persistent_index.clone(),
459 workspace_retrieval: self.workspace_retrieval.clone(),
460 git: self.git.clone(),
461 git_stash: self.git_stash.clone(),
462 git_worktree: self.git_worktree.clone(),
463 operation_timeout: self.operation_timeout,
464 local_root: self.local_root.clone(),
465 })
466 }
467
468 pub fn git(&self) -> Option<Arc<dyn WorkspaceGit>> {
469 self.git.clone()
470 }
471
472 pub fn git_stash(&self) -> Option<Arc<dyn WorkspaceGitStashProvider>> {
473 self.git_stash.clone()
474 }
475
476 pub fn git_worktree(&self) -> Option<Arc<dyn WorkspaceGitWorktreeProvider>> {
477 self.git_worktree.clone()
478 }
479
480 pub(crate) fn with_git_provider(
497 &self,
498 git: Arc<dyn WorkspaceGit>,
499 git_stash: Option<Arc<dyn WorkspaceGitStashProvider>>,
500 ) -> Arc<Self> {
501 let mut capabilities = self.capabilities;
502 capabilities.git = true;
503 Arc::new(Self {
504 workspace_ref: self.workspace_ref.clone(),
505 capabilities,
506 path_resolver: Arc::clone(&self.path_resolver),
507 file_system: Arc::clone(&self.file_system),
508 file_system_ext: self.file_system_ext.clone(),
509 text_reader: self.text_reader.clone(),
510 command_runner: self.command_runner.clone(),
511 search: self.search.clone(),
512 code_intelligence: self.code_intelligence.clone(),
513 chunk_catalog: self.chunk_catalog.clone(),
514 persistent_index: self.persistent_index.clone(),
515 workspace_retrieval: self.workspace_retrieval.clone(),
516 git: Some(git),
517 git_stash,
518 git_worktree: None,
519 operation_timeout: self.operation_timeout,
520 local_root: self.local_root.clone(),
521 })
522 }
523
524 pub fn operation_timeout(&self) -> Option<std::time::Duration> {
530 self.operation_timeout
531 }
532
533 pub async fn run_with_timeout<F, T, E>(
547 &self,
548 op: &'static str,
549 fut: F,
550 ) -> std::result::Result<T, E>
551 where
552 F: std::future::Future<Output = std::result::Result<T, E>>,
553 E: From<anyhow::Error>,
554 {
555 match self.operation_timeout {
556 Some(d) => tokio::time::timeout(d, fut).await.map_err(|_| {
557 E::from(anyhow!(
558 "workspace operation '{}' timed out after {:?}",
559 op,
560 d
561 ))
562 })?,
563 None => fut.await,
564 }
565 }
566
567 pub async fn read_for_edit(
574 &self,
575 path: &WorkspacePath,
576 ) -> WorkspaceResult<(String, Option<String>)> {
577 if let Some(ext) = self.fs_ext() {
578 let path = path.clone();
579 return self
580 .run_with_timeout("read_text_with_version", async move {
581 let (content, version) = ext.read_text_with_version(&path).await?;
582 Ok((content, Some(version)))
583 })
584 .await;
585 }
586 let fs = self.fs();
587 let path_owned = path.clone();
588 let content = self
589 .run_with_timeout("read_text", async move { fs.read_text(&path_owned).await })
590 .await?;
591 Ok((content, None))
592 }
593
594 pub async fn write_for_edit(
602 &self,
603 path: &WorkspacePath,
604 content: &str,
605 expected_version: Option<&str>,
606 ) -> WorkspaceResult<WorkspaceWriteOutcome> {
607 if let (Some(ext), Some(version)) = (self.fs_ext(), expected_version) {
608 let path = path.clone();
609 let content = content.to_string();
610 let expected = version.to_string();
611 return self
612 .run_with_timeout("write_text_if_version", async move {
613 ext.write_text_if_version(&path, &content, &expected).await
614 })
615 .await;
616 }
617 let fs = self.fs();
618 let path = path.clone();
619 let content = content.to_string();
620 self.run_with_timeout(
621 "write_text",
622 async move { fs.write_text(&path, &content).await },
623 )
624 .await
625 }
626
627 pub fn local_root(&self) -> Option<&Path> {
628 self.local_root.as_deref()
629 }
630
631 pub fn display_path(&self, path: &WorkspacePath) -> String {
632 if path.is_root() {
633 return self.workspace_ref.display_root.clone();
634 }
635
636 let root = self.workspace_ref.display_root.trim_end_matches('/');
637 if root.is_empty() {
638 path.as_str().to_string()
639 } else {
640 format!("{root}/{}", path.as_str())
641 }
642 }
643}
644
645pub struct WorkspaceServicesBuilder {
647 workspace_ref: WorkspaceRef,
648 capabilities: WorkspaceCapabilities,
649 path_resolver: Arc<dyn WorkspacePathResolver>,
650 file_system: Arc<dyn WorkspaceFileSystem>,
651 file_system_ext: Option<Arc<dyn WorkspaceFileSystemExt>>,
652 text_reader: Option<Arc<dyn WorkspaceTextReader>>,
653 command_runner: Option<Arc<dyn WorkspaceCommandRunner>>,
654 search: Option<Arc<dyn WorkspaceSearch>>,
655 code_intelligence: Option<Arc<dyn WorkspaceCodeIntelligence>>,
656 chunk_catalog: Option<Arc<WorkspaceChunkCatalog>>,
657 persistent_index: Option<Arc<WorkspacePersistentIndex>>,
658 git: Option<Arc<dyn WorkspaceGit>>,
659 git_stash: Option<Arc<dyn WorkspaceGitStashProvider>>,
660 git_worktree: Option<Arc<dyn WorkspaceGitWorktreeProvider>>,
661 operation_timeout: Option<std::time::Duration>,
662}
663
664impl WorkspaceServicesBuilder {
665 pub fn new(workspace_ref: WorkspaceRef, file_system: Arc<dyn WorkspaceFileSystem>) -> Self {
666 Self {
667 workspace_ref,
668 capabilities: WorkspaceCapabilities::read_write(),
669 path_resolver: Arc::new(VirtualPathResolver),
670 file_system,
671 file_system_ext: None,
672 text_reader: None,
673 command_runner: None,
674 search: None,
675 code_intelligence: None,
676 chunk_catalog: None,
677 persistent_index: None,
678 git: None,
679 git_stash: None,
680 git_worktree: None,
681 operation_timeout: None,
682 }
683 }
684
685 pub fn capabilities(mut self, capabilities: WorkspaceCapabilities) -> Self {
686 self.capabilities = capabilities;
687 self
688 }
689
690 pub fn command_runner(mut self, command_runner: Arc<dyn WorkspaceCommandRunner>) -> Self {
691 self.capabilities.exec = true;
692 self.command_runner = Some(command_runner);
693 self
694 }
695
696 pub fn search(mut self, search: Arc<dyn WorkspaceSearch>) -> Self {
697 self.capabilities.search = true;
698 self.search = Some(search);
699 self
700 }
701
702 pub fn code_intelligence(mut self, provider: Arc<dyn WorkspaceCodeIntelligence>) -> Self {
703 self.capabilities.code_intelligence = true;
704 self.code_intelligence = Some(provider);
705 self
706 }
707
708 pub fn chunk_catalog(mut self, catalog: Arc<WorkspaceChunkCatalog>) -> Self {
710 self.chunk_catalog = Some(catalog);
711 self
712 }
713
714 pub fn persistent_index(mut self, index: Arc<WorkspacePersistentIndex>) -> Self {
715 self.persistent_index = Some(index);
716 self
717 }
718
719 pub fn git(mut self, git: Arc<dyn WorkspaceGit>) -> Self {
720 self.capabilities.git = true;
721 self.git = Some(git);
722 self
723 }
724
725 pub fn git_stash(mut self, git_stash: Arc<dyn WorkspaceGitStashProvider>) -> Self {
726 self.git_stash = Some(git_stash);
727 self
728 }
729
730 pub fn git_worktree(mut self, git_worktree: Arc<dyn WorkspaceGitWorktreeProvider>) -> Self {
731 self.git_worktree = Some(git_worktree);
732 self
733 }
734
735 pub fn file_system_ext(mut self, ext: Arc<dyn WorkspaceFileSystemExt>) -> Self {
740 self.file_system_ext = Some(ext);
741 self
742 }
743
744 pub fn text_reader(mut self, reader: Arc<dyn WorkspaceTextReader>) -> Self {
745 self.text_reader = Some(reader);
746 self
747 }
748
749 pub fn operation_timeout(mut self, timeout: std::time::Duration) -> Self {
753 self.operation_timeout = Some(timeout);
754 self
755 }
756
757 pub fn build(self) -> Arc<WorkspaceServices> {
758 let mut services = WorkspaceServices::new_with_git(
759 self.workspace_ref,
760 self.capabilities,
761 self.path_resolver,
762 self.file_system,
763 self.command_runner,
764 self.search,
765 self.git,
766 );
767 services.file_system_ext = self.file_system_ext;
768 services.text_reader = self.text_reader;
769 services.capabilities.code_intelligence = self.code_intelligence.is_some();
770 services.code_intelligence = self.code_intelligence;
771 services.chunk_catalog = self.chunk_catalog;
772 services.persistent_index = self.persistent_index;
773 services.workspace_retrieval = None;
774 services.git_stash = self.git_stash;
775 services.git_worktree = self.git_worktree;
776 services.operation_timeout = self.operation_timeout;
777 Arc::new(services)
778 }
779}