1use anyhow::{anyhow, Result};
2use fsevent::EventStream;
3use futures::{Stream, StreamExt};
4use smol::io::{AsyncReadExt, AsyncWriteExt};
5use std::{
6 io,
7 os::unix::fs::MetadataExt,
8 path::{Component, Path, PathBuf},
9 pin::Pin,
10 time::{Duration, SystemTime},
11};
12use text::Rope;
13
14#[async_trait::async_trait]
15pub trait Fs: Send + Sync {
16 async fn create_dir(&self, path: &Path) -> Result<()>;
17 async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()>;
18 async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()>;
19 async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()>;
20 async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>;
21 async fn load(&self, path: &Path) -> Result<String>;
22 async fn save(&self, path: &Path, text: &Rope) -> Result<()>;
23 async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
24 async fn is_file(&self, path: &Path) -> bool;
25 async fn metadata(&self, path: &Path) -> Result<Option<Metadata>>;
26 async fn read_dir(
27 &self,
28 path: &Path,
29 ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>>;
30 async fn watch(
31 &self,
32 path: &Path,
33 latency: Duration,
34 ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>;
35 fn is_fake(&self) -> bool;
36 #[cfg(any(test, feature = "test-support"))]
37 fn as_fake(&self) -> &FakeFs;
38}
39
40#[derive(Copy, Clone, Default)]
41pub struct CreateOptions {
42 pub overwrite: bool,
43 pub ignore_if_exists: bool,
44}
45
46#[derive(Copy, Clone, Default)]
47pub struct RenameOptions {
48 pub overwrite: bool,
49 pub ignore_if_exists: bool,
50}
51
52#[derive(Copy, Clone, Default)]
53pub struct RemoveOptions {
54 pub recursive: bool,
55 pub ignore_if_not_exists: bool,
56}
57
58#[derive(Clone, Debug)]
59pub struct Metadata {
60 pub inode: u64,
61 pub mtime: SystemTime,
62 pub is_symlink: bool,
63 pub is_dir: bool,
64}
65
66pub struct RealFs;
67
68#[async_trait::async_trait]
69impl Fs for RealFs {
70 async fn create_dir(&self, path: &Path) -> Result<()> {
71 Ok(smol::fs::create_dir_all(path).await?)
72 }
73
74 async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
75 let mut open_options = smol::fs::OpenOptions::new();
76 open_options.write(true).create(true);
77 if options.overwrite {
78 open_options.truncate(true);
79 } else if !options.ignore_if_exists {
80 open_options.create_new(true);
81 }
82 open_options.open(path).await?;
83 Ok(())
84 }
85
86 async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> {
87 if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
88 if options.ignore_if_exists {
89 return Ok(());
90 } else {
91 return Err(anyhow!("{target:?} already exists"));
92 }
93 }
94
95 smol::fs::rename(source, target).await?;
96 Ok(())
97 }
98
99 async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
100 let result = if options.recursive {
101 smol::fs::remove_dir_all(path).await
102 } else {
103 smol::fs::remove_dir(path).await
104 };
105 match result {
106 Ok(()) => Ok(()),
107 Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
108 Ok(())
109 }
110 Err(err) => Err(err)?,
111 }
112 }
113
114 async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
115 match smol::fs::remove_file(path).await {
116 Ok(()) => Ok(()),
117 Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
118 Ok(())
119 }
120 Err(err) => Err(err)?,
121 }
122 }
123
124 async fn load(&self, path: &Path) -> Result<String> {
125 let mut file = smol::fs::File::open(path).await?;
126 let mut text = String::new();
127 file.read_to_string(&mut text).await?;
128 Ok(text)
129 }
130
131 async fn save(&self, path: &Path, text: &Rope) -> Result<()> {
132 let buffer_size = text.summary().bytes.min(10 * 1024);
133 let file = smol::fs::File::create(path).await?;
134 let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
135 for chunk in text.chunks() {
136 writer.write_all(chunk.as_bytes()).await?;
137 }
138 writer.flush().await?;
139 Ok(())
140 }
141
142 async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
143 Ok(smol::fs::canonicalize(path).await?)
144 }
145
146 async fn is_file(&self, path: &Path) -> bool {
147 smol::fs::metadata(path)
148 .await
149 .map_or(false, |metadata| metadata.is_file())
150 }
151
152 async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
153 let symlink_metadata = match smol::fs::symlink_metadata(path).await {
154 Ok(metadata) => metadata,
155 Err(err) => {
156 return match (err.kind(), err.raw_os_error()) {
157 (io::ErrorKind::NotFound, _) => Ok(None),
158 (io::ErrorKind::Other, Some(libc::ENOTDIR)) => Ok(None),
159 _ => Err(anyhow::Error::new(err)),
160 }
161 }
162 };
163
164 let is_symlink = symlink_metadata.file_type().is_symlink();
165 let metadata = if is_symlink {
166 smol::fs::metadata(path).await?
167 } else {
168 symlink_metadata
169 };
170 Ok(Some(Metadata {
171 inode: metadata.ino(),
172 mtime: metadata.modified().unwrap(),
173 is_symlink,
174 is_dir: metadata.file_type().is_dir(),
175 }))
176 }
177
178 async fn read_dir(
179 &self,
180 path: &Path,
181 ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
182 let result = smol::fs::read_dir(path).await?.map(|entry| match entry {
183 Ok(entry) => Ok(entry.path()),
184 Err(error) => Err(anyhow!("failed to read dir entry {:?}", error)),
185 });
186 Ok(Box::pin(result))
187 }
188
189 async fn watch(
190 &self,
191 path: &Path,
192 latency: Duration,
193 ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>> {
194 let (tx, rx) = smol::channel::unbounded();
195 let (stream, handle) = EventStream::new(&[path], latency);
196 std::mem::forget(handle);
197 std::thread::spawn(move || {
198 stream.run(move |events| smol::block_on(tx.send(events)).is_ok());
199 });
200 Box::pin(rx)
201 }
202
203 fn is_fake(&self) -> bool {
204 false
205 }
206
207 #[cfg(any(test, feature = "test-support"))]
208 fn as_fake(&self) -> &FakeFs {
209 panic!("called `RealFs::as_fake`")
210 }
211}
212
213#[cfg(any(test, feature = "test-support"))]
214#[derive(Clone, Debug)]
215struct FakeFsEntry {
216 metadata: Metadata,
217 content: Option<String>,
218}
219
220#[cfg(any(test, feature = "test-support"))]
221struct FakeFsState {
222 entries: std::collections::BTreeMap<PathBuf, FakeFsEntry>,
223 next_inode: u64,
224 events_tx: postage::broadcast::Sender<Vec<fsevent::Event>>,
225}
226
227#[cfg(any(test, feature = "test-support"))]
228impl FakeFsState {
229 fn validate_path(&self, path: &Path) -> Result<()> {
230 if path.is_absolute()
231 && path
232 .parent()
233 .and_then(|path| self.entries.get(path))
234 .map_or(false, |e| e.metadata.is_dir)
235 {
236 Ok(())
237 } else {
238 Err(anyhow!("invalid path {:?}", path))
239 }
240 }
241
242 async fn emit_event<I, T>(&mut self, paths: I)
243 where
244 I: IntoIterator<Item = T>,
245 T: Into<PathBuf>,
246 {
247 use postage::prelude::Sink as _;
248
249 let events = paths
250 .into_iter()
251 .map(|path| fsevent::Event {
252 event_id: 0,
253 flags: fsevent::StreamFlags::empty(),
254 path: path.into(),
255 })
256 .collect();
257
258 let _ = self.events_tx.send(events).await;
259 }
260}
261
262#[cfg(any(test, feature = "test-support"))]
263pub struct FakeFs {
264 // Use an unfair lock to ensure tests are deterministic.
265 state: futures::lock::Mutex<FakeFsState>,
266 executor: std::sync::Arc<gpui::executor::Background>,
267}
268
269#[cfg(any(test, feature = "test-support"))]
270impl FakeFs {
271 pub fn new(executor: std::sync::Arc<gpui::executor::Background>) -> std::sync::Arc<Self> {
272 let (events_tx, _) = postage::broadcast::channel(2048);
273 let mut entries = std::collections::BTreeMap::new();
274 entries.insert(
275 Path::new("/").to_path_buf(),
276 FakeFsEntry {
277 metadata: Metadata {
278 inode: 0,
279 mtime: SystemTime::now(),
280 is_dir: true,
281 is_symlink: false,
282 },
283 content: None,
284 },
285 );
286 std::sync::Arc::new(Self {
287 executor,
288 state: futures::lock::Mutex::new(FakeFsState {
289 entries,
290 next_inode: 1,
291 events_tx,
292 }),
293 })
294 }
295
296 pub async fn insert_dir(&self, path: impl AsRef<Path>) {
297 let mut state = self.state.lock().await;
298 let path = path.as_ref();
299 state.validate_path(path).unwrap();
300
301 let inode = state.next_inode;
302 state.next_inode += 1;
303 state.entries.insert(
304 path.to_path_buf(),
305 FakeFsEntry {
306 metadata: Metadata {
307 inode,
308 mtime: SystemTime::now(),
309 is_dir: true,
310 is_symlink: false,
311 },
312 content: None,
313 },
314 );
315 state.emit_event(&[path]).await;
316 }
317
318 pub async fn insert_file(&self, path: impl AsRef<Path>, content: String) {
319 let mut state = self.state.lock().await;
320 let path = path.as_ref();
321 state.validate_path(path).unwrap();
322
323 let inode = state.next_inode;
324 state.next_inode += 1;
325 state.entries.insert(
326 path.to_path_buf(),
327 FakeFsEntry {
328 metadata: Metadata {
329 inode,
330 mtime: SystemTime::now(),
331 is_dir: false,
332 is_symlink: false,
333 },
334 content: Some(content),
335 },
336 );
337 state.emit_event(&[path]).await;
338 }
339
340 #[must_use]
341 pub fn insert_tree<'a>(
342 &'a self,
343 path: impl 'a + AsRef<Path> + Send,
344 tree: serde_json::Value,
345 ) -> futures::future::BoxFuture<'a, ()> {
346 use futures::FutureExt as _;
347 use serde_json::Value::*;
348
349 async move {
350 let path = path.as_ref();
351
352 match tree {
353 Object(map) => {
354 self.insert_dir(path).await;
355 for (name, contents) in map {
356 let mut path = PathBuf::from(path);
357 path.push(name);
358 self.insert_tree(&path, contents).await;
359 }
360 }
361 Null => {
362 self.insert_dir(&path).await;
363 }
364 String(contents) => {
365 self.insert_file(&path, contents).await;
366 }
367 _ => {
368 panic!("JSON object must contain only objects, strings, or null");
369 }
370 }
371 }
372 .boxed()
373 }
374}
375
376#[cfg(any(test, feature = "test-support"))]
377#[async_trait::async_trait]
378impl Fs for FakeFs {
379 async fn create_dir(&self, path: &Path) -> Result<()> {
380 self.executor.simulate_random_delay().await;
381 let state = &mut *self.state.lock().await;
382 let path = normalize_path(path);
383 let mut ancestor_path = PathBuf::new();
384 let mut created_dir_paths = Vec::new();
385 for component in path.components() {
386 ancestor_path.push(component);
387 let entry = state
388 .entries
389 .entry(ancestor_path.clone())
390 .or_insert_with(|| {
391 let inode = state.next_inode;
392 state.next_inode += 1;
393 created_dir_paths.push(ancestor_path.clone());
394 FakeFsEntry {
395 metadata: Metadata {
396 inode,
397 mtime: SystemTime::now(),
398 is_dir: true,
399 is_symlink: false,
400 },
401 content: None,
402 }
403 });
404 if !entry.metadata.is_dir {
405 return Err(anyhow!(
406 "cannot create directory because {:?} is a file",
407 ancestor_path
408 ));
409 }
410 }
411 state.emit_event(&created_dir_paths).await;
412
413 Ok(())
414 }
415
416 async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
417 self.executor.simulate_random_delay().await;
418 let mut state = self.state.lock().await;
419 let path = normalize_path(path);
420 state.validate_path(&path)?;
421 if let Some(entry) = state.entries.get_mut(&path) {
422 if entry.metadata.is_dir || entry.metadata.is_symlink {
423 return Err(anyhow!(
424 "cannot create file because {:?} is a dir or a symlink",
425 path
426 ));
427 }
428
429 if options.overwrite {
430 entry.metadata.mtime = SystemTime::now();
431 entry.content = Some(Default::default());
432 } else if !options.ignore_if_exists {
433 return Err(anyhow!(
434 "cannot create file because {:?} already exists",
435 &path
436 ));
437 }
438 } else {
439 let inode = state.next_inode;
440 state.next_inode += 1;
441 let entry = FakeFsEntry {
442 metadata: Metadata {
443 inode,
444 mtime: SystemTime::now(),
445 is_dir: false,
446 is_symlink: false,
447 },
448 content: Some(Default::default()),
449 };
450 state.entries.insert(path.to_path_buf(), entry);
451 }
452 state.emit_event(&[path]).await;
453
454 Ok(())
455 }
456
457 async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> {
458 let source = normalize_path(source);
459 let target = normalize_path(target);
460
461 let mut state = self.state.lock().await;
462 state.validate_path(&source)?;
463 state.validate_path(&target)?;
464
465 if !options.overwrite && state.entries.contains_key(&target) {
466 if options.ignore_if_exists {
467 return Ok(());
468 } else {
469 return Err(anyhow!("{target:?} already exists"));
470 }
471 }
472
473 let mut removed = Vec::new();
474 state.entries.retain(|path, entry| {
475 if let Ok(relative_path) = path.strip_prefix(&source) {
476 removed.push((relative_path.to_path_buf(), entry.clone()));
477 false
478 } else {
479 true
480 }
481 });
482
483 for (relative_path, entry) in removed {
484 let new_path = target.join(relative_path);
485 state.entries.insert(new_path, entry);
486 }
487
488 state.emit_event(&[source, target]).await;
489 Ok(())
490 }
491
492 async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
493 let path = normalize_path(path);
494 let mut state = self.state.lock().await;
495 state.validate_path(&path)?;
496 if let Some(entry) = state.entries.get(&path) {
497 if !entry.metadata.is_dir {
498 return Err(anyhow!("cannot remove {path:?} because it is not a dir"));
499 }
500
501 if !options.recursive {
502 let descendants = state
503 .entries
504 .keys()
505 .filter(|path| path.starts_with(path))
506 .count();
507 if descendants > 1 {
508 return Err(anyhow!("{path:?} is not empty"));
509 }
510 }
511
512 state.entries.retain(|path, _| !path.starts_with(path));
513 state.emit_event(&[path]).await;
514 } else if !options.ignore_if_not_exists {
515 return Err(anyhow!("{path:?} does not exist"));
516 }
517
518 Ok(())
519 }
520
521 async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
522 let path = normalize_path(path);
523 let mut state = self.state.lock().await;
524 state.validate_path(&path)?;
525 if let Some(entry) = state.entries.get(&path) {
526 if entry.metadata.is_dir {
527 return Err(anyhow!("cannot remove {path:?} because it is not a file"));
528 }
529
530 state.entries.remove(&path);
531 state.emit_event(&[path]).await;
532 } else if !options.ignore_if_not_exists {
533 return Err(anyhow!("{path:?} does not exist"));
534 }
535 Ok(())
536 }
537
538 async fn load(&self, path: &Path) -> Result<String> {
539 let path = normalize_path(path);
540 self.executor.simulate_random_delay().await;
541 let state = self.state.lock().await;
542 let text = state
543 .entries
544 .get(&path)
545 .and_then(|e| e.content.as_ref())
546 .ok_or_else(|| anyhow!("file {:?} does not exist", path))?;
547 Ok(text.clone())
548 }
549
550 async fn save(&self, path: &Path, text: &Rope) -> Result<()> {
551 self.executor.simulate_random_delay().await;
552 let mut state = self.state.lock().await;
553 let path = normalize_path(path);
554 state.validate_path(&path)?;
555 if let Some(entry) = state.entries.get_mut(&path) {
556 if entry.metadata.is_dir {
557 Err(anyhow!("cannot overwrite a directory with a file"))
558 } else {
559 entry.content = Some(text.chunks().collect());
560 entry.metadata.mtime = SystemTime::now();
561 state.emit_event(&[path]).await;
562 Ok(())
563 }
564 } else {
565 let inode = state.next_inode;
566 state.next_inode += 1;
567 let entry = FakeFsEntry {
568 metadata: Metadata {
569 inode,
570 mtime: SystemTime::now(),
571 is_dir: false,
572 is_symlink: false,
573 },
574 content: Some(text.chunks().collect()),
575 };
576 state.entries.insert(path.to_path_buf(), entry);
577 state.emit_event(&[path]).await;
578 Ok(())
579 }
580 }
581
582 async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
583 self.executor.simulate_random_delay().await;
584 Ok(normalize_path(path))
585 }
586
587 async fn is_file(&self, path: &Path) -> bool {
588 let path = normalize_path(path);
589 self.executor.simulate_random_delay().await;
590 let state = self.state.lock().await;
591 state
592 .entries
593 .get(&path)
594 .map_or(false, |entry| !entry.metadata.is_dir)
595 }
596
597 async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
598 self.executor.simulate_random_delay().await;
599 let state = self.state.lock().await;
600 let path = normalize_path(path);
601 Ok(state.entries.get(&path).map(|entry| entry.metadata.clone()))
602 }
603
604 async fn read_dir(
605 &self,
606 abs_path: &Path,
607 ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
608 use futures::{future, stream};
609 self.executor.simulate_random_delay().await;
610 let state = self.state.lock().await;
611 let abs_path = normalize_path(abs_path);
612 Ok(Box::pin(stream::iter(state.entries.clone()).filter_map(
613 move |(child_path, _)| {
614 future::ready(if child_path.parent() == Some(&abs_path) {
615 Some(Ok(child_path))
616 } else {
617 None
618 })
619 },
620 )))
621 }
622
623 async fn watch(
624 &self,
625 path: &Path,
626 _: Duration,
627 ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>> {
628 let state = self.state.lock().await;
629 self.executor.simulate_random_delay().await;
630 let rx = state.events_tx.subscribe();
631 let path = path.to_path_buf();
632 Box::pin(futures::StreamExt::filter(rx, move |events| {
633 let result = events.iter().any(|event| event.path.starts_with(&path));
634 async move { result }
635 }))
636 }
637
638 fn is_fake(&self) -> bool {
639 true
640 }
641
642 #[cfg(any(test, feature = "test-support"))]
643 fn as_fake(&self) -> &FakeFs {
644 self
645 }
646}
647
648pub fn normalize_path(path: &Path) -> PathBuf {
649 let mut components = path.components().peekable();
650 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
651 components.next();
652 PathBuf::from(c.as_os_str())
653 } else {
654 PathBuf::new()
655 };
656
657 for component in components {
658 match component {
659 Component::Prefix(..) => unreachable!(),
660 Component::RootDir => {
661 ret.push(component.as_os_str());
662 }
663 Component::CurDir => {}
664 Component::ParentDir => {
665 ret.pop();
666 }
667 Component::Normal(c) => {
668 ret.push(c);
669 }
670 }
671 }
672 ret
673}