fs.rs

   1use anyhow::{anyhow, Result};
   2use fsevent::EventStream;
   3use futures::{future::BoxFuture, Stream, StreamExt};
   4use language::git::libgit::{Repository, RepositoryOpenFlags};
   5use language::LineEnding;
   6use smol::io::{AsyncReadExt, AsyncWriteExt};
   7use std::{
   8    ffi::OsStr,
   9    io,
  10    os::unix::fs::MetadataExt,
  11    path::{Component, Path, PathBuf},
  12    pin::Pin,
  13    time::{Duration, SystemTime},
  14};
  15
  16use text::Rope;
  17
  18#[cfg(any(test, feature = "test-support"))]
  19use collections::{btree_map, BTreeMap};
  20#[cfg(any(test, feature = "test-support"))]
  21use futures::lock::Mutex;
  22#[cfg(any(test, feature = "test-support"))]
  23use std::sync::{Arc, Weak};
  24
  25use crate::git_repository::{FakeGitRepository, GitRepository, RealGitRepository};
  26
  27#[async_trait::async_trait]
  28pub trait Fs: Send + Sync {
  29    async fn create_dir(&self, path: &Path) -> Result<()>;
  30    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()>;
  31    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()>;
  32    async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()>;
  33    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()>;
  34    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>;
  35    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>>;
  36    async fn load(&self, path: &Path) -> Result<String>;
  37    async fn load_head_text(&self, path: &Path) -> Option<String>;
  38    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()>;
  39    async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
  40    async fn is_file(&self, path: &Path) -> bool;
  41    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>>;
  42    async fn read_dir(
  43        &self,
  44        path: &Path,
  45    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>>;
  46    async fn watch(
  47        &self,
  48        path: &Path,
  49        latency: Duration,
  50    ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>;
  51    fn open_git_repository(
  52        &self,
  53        abs_dotgit_path: &Path,
  54        content_path: &Arc<Path>,
  55    ) -> Option<Box<dyn GitRepository>>;
  56    fn is_fake(&self) -> bool;
  57    #[cfg(any(test, feature = "test-support"))]
  58    fn as_fake(&self) -> &FakeFs;
  59}
  60
  61#[derive(Copy, Clone, Default)]
  62pub struct CreateOptions {
  63    pub overwrite: bool,
  64    pub ignore_if_exists: bool,
  65}
  66
  67#[derive(Copy, Clone, Default)]
  68pub struct CopyOptions {
  69    pub overwrite: bool,
  70    pub ignore_if_exists: bool,
  71}
  72
  73#[derive(Copy, Clone, Default)]
  74pub struct RenameOptions {
  75    pub overwrite: bool,
  76    pub ignore_if_exists: bool,
  77}
  78
  79#[derive(Copy, Clone, Default)]
  80pub struct RemoveOptions {
  81    pub recursive: bool,
  82    pub ignore_if_not_exists: bool,
  83}
  84
  85#[derive(Clone, Debug)]
  86pub struct Metadata {
  87    pub inode: u64,
  88    pub mtime: SystemTime,
  89    pub is_symlink: bool,
  90    pub is_dir: bool,
  91}
  92
  93pub struct RealFs;
  94
  95#[async_trait::async_trait]
  96impl Fs for RealFs {
  97    async fn create_dir(&self, path: &Path) -> Result<()> {
  98        Ok(smol::fs::create_dir_all(path).await?)
  99    }
 100
 101    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
 102        let mut open_options = smol::fs::OpenOptions::new();
 103        open_options.write(true).create(true);
 104        if options.overwrite {
 105            open_options.truncate(true);
 106        } else if !options.ignore_if_exists {
 107            open_options.create_new(true);
 108        }
 109        open_options.open(path).await?;
 110        Ok(())
 111    }
 112
 113    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
 114        if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
 115            if options.ignore_if_exists {
 116                return Ok(());
 117            } else {
 118                return Err(anyhow!("{target:?} already exists"));
 119            }
 120        }
 121
 122        smol::fs::copy(source, target).await?;
 123        Ok(())
 124    }
 125
 126    async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> {
 127        if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
 128            if options.ignore_if_exists {
 129                return Ok(());
 130            } else {
 131                return Err(anyhow!("{target:?} already exists"));
 132            }
 133        }
 134
 135        smol::fs::rename(source, target).await?;
 136        Ok(())
 137    }
 138
 139    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 140        let result = if options.recursive {
 141            smol::fs::remove_dir_all(path).await
 142        } else {
 143            smol::fs::remove_dir(path).await
 144        };
 145        match result {
 146            Ok(()) => Ok(()),
 147            Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
 148                Ok(())
 149            }
 150            Err(err) => Err(err)?,
 151        }
 152    }
 153
 154    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 155        match smol::fs::remove_file(path).await {
 156            Ok(()) => Ok(()),
 157            Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
 158                Ok(())
 159            }
 160            Err(err) => Err(err)?,
 161        }
 162    }
 163
 164    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>> {
 165        Ok(Box::new(std::fs::File::open(path)?))
 166    }
 167
 168    async fn load(&self, path: &Path) -> Result<String> {
 169        let mut file = smol::fs::File::open(path).await?;
 170        let mut text = String::new();
 171        file.read_to_string(&mut text).await?;
 172        Ok(text)
 173    }
 174
 175    async fn load_head_text(&self, path: &Path) -> Option<String> {
 176        fn logic(path: &Path) -> Result<Option<String>> {
 177            let repo = Repository::open_ext(path, RepositoryOpenFlags::empty(), &[OsStr::new("")])?;
 178            assert!(repo.path().ends_with(".git"));
 179            let repo_root_path = match repo.path().parent() {
 180                Some(root) => root,
 181                None => return Ok(None),
 182            };
 183
 184            let relative_path = path.strip_prefix(repo_root_path)?;
 185            let object = repo
 186                .head()?
 187                .peel_to_tree()?
 188                .get_path(relative_path)?
 189                .to_object(&repo)?;
 190
 191            let content = match object.as_blob() {
 192                Some(blob) => blob.content().to_owned(),
 193                None => return Ok(None),
 194            };
 195
 196            let head_text = String::from_utf8(content.to_owned())?;
 197            Ok(Some(head_text))
 198        }
 199
 200        match logic(path) {
 201            Ok(value) => return value,
 202            Err(err) => log::error!("Error loading head text: {:?}", err),
 203        }
 204        None
 205    }
 206
 207    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
 208        let buffer_size = text.summary().len.min(10 * 1024);
 209        let file = smol::fs::File::create(path).await?;
 210        let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
 211        for chunk in chunks(text, line_ending) {
 212            writer.write_all(chunk.as_bytes()).await?;
 213        }
 214        writer.flush().await?;
 215        Ok(())
 216    }
 217
 218    async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
 219        Ok(smol::fs::canonicalize(path).await?)
 220    }
 221
 222    async fn is_file(&self, path: &Path) -> bool {
 223        smol::fs::metadata(path)
 224            .await
 225            .map_or(false, |metadata| metadata.is_file())
 226    }
 227
 228    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
 229        let symlink_metadata = match smol::fs::symlink_metadata(path).await {
 230            Ok(metadata) => metadata,
 231            Err(err) => {
 232                return match (err.kind(), err.raw_os_error()) {
 233                    (io::ErrorKind::NotFound, _) => Ok(None),
 234                    (io::ErrorKind::Other, Some(libc::ENOTDIR)) => Ok(None),
 235                    _ => Err(anyhow::Error::new(err)),
 236                }
 237            }
 238        };
 239
 240        let is_symlink = symlink_metadata.file_type().is_symlink();
 241        let metadata = if is_symlink {
 242            smol::fs::metadata(path).await?
 243        } else {
 244            symlink_metadata
 245        };
 246        Ok(Some(Metadata {
 247            inode: metadata.ino(),
 248            mtime: metadata.modified().unwrap(),
 249            is_symlink,
 250            is_dir: metadata.file_type().is_dir(),
 251        }))
 252    }
 253
 254    async fn read_dir(
 255        &self,
 256        path: &Path,
 257    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
 258        let result = smol::fs::read_dir(path).await?.map(|entry| match entry {
 259            Ok(entry) => Ok(entry.path()),
 260            Err(error) => Err(anyhow!("failed to read dir entry {:?}", error)),
 261        });
 262        Ok(Box::pin(result))
 263    }
 264
 265    async fn watch(
 266        &self,
 267        path: &Path,
 268        latency: Duration,
 269    ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>> {
 270        let (tx, rx) = smol::channel::unbounded();
 271        let (stream, handle) = EventStream::new(&[path], latency);
 272        std::thread::spawn(move || {
 273            stream.run(move |events| smol::block_on(tx.send(events)).is_ok());
 274        });
 275        Box::pin(rx.chain(futures::stream::once(async move {
 276            drop(handle);
 277            vec![]
 278        })))
 279    }
 280
 281    fn open_git_repository(
 282        &self,
 283        abs_dotgit_path: &Path,
 284        content_path: &Arc<Path>,
 285    ) -> Option<Box<dyn GitRepository>> {
 286        RealGitRepository::open(abs_dotgit_path, content_path)
 287    }
 288
 289    fn is_fake(&self) -> bool {
 290        false
 291    }
 292    #[cfg(any(test, feature = "test-support"))]
 293    fn as_fake(&self) -> &FakeFs {
 294        panic!("called `RealFs::as_fake`")
 295    }
 296}
 297
 298#[cfg(any(test, feature = "test-support"))]
 299pub struct FakeFs {
 300    // Use an unfair lock to ensure tests are deterministic.
 301    state: Mutex<FakeFsState>,
 302    executor: Weak<gpui::executor::Background>,
 303}
 304
 305#[cfg(any(test, feature = "test-support"))]
 306struct FakeFsState {
 307    root: Arc<Mutex<FakeFsEntry>>,
 308    next_inode: u64,
 309    event_txs: Vec<smol::channel::Sender<Vec<fsevent::Event>>>,
 310}
 311
 312#[cfg(any(test, feature = "test-support"))]
 313#[derive(Debug)]
 314enum FakeFsEntry {
 315    File {
 316        inode: u64,
 317        mtime: SystemTime,
 318        content: String,
 319    },
 320    Dir {
 321        inode: u64,
 322        mtime: SystemTime,
 323        entries: BTreeMap<String, Arc<Mutex<FakeFsEntry>>>,
 324    },
 325    Symlink {
 326        target: PathBuf,
 327    },
 328}
 329
 330#[cfg(any(test, feature = "test-support"))]
 331impl FakeFsState {
 332    async fn read_path<'a>(&'a self, target: &Path) -> Result<Arc<Mutex<FakeFsEntry>>> {
 333        Ok(self
 334            .try_read_path(target)
 335            .await
 336            .ok_or_else(|| anyhow!("path does not exist: {}", target.display()))?
 337            .0)
 338    }
 339
 340    async fn try_read_path<'a>(
 341        &'a self,
 342        target: &Path,
 343    ) -> Option<(Arc<Mutex<FakeFsEntry>>, PathBuf)> {
 344        let mut path = target.to_path_buf();
 345        let mut real_path = PathBuf::new();
 346        let mut entry_stack = Vec::new();
 347        'outer: loop {
 348            let mut path_components = path.components().collect::<collections::VecDeque<_>>();
 349            while let Some(component) = path_components.pop_front() {
 350                match component {
 351                    Component::Prefix(_) => panic!("prefix paths aren't supported"),
 352                    Component::RootDir => {
 353                        entry_stack.clear();
 354                        entry_stack.push(self.root.clone());
 355                        real_path.clear();
 356                        real_path.push("/");
 357                    }
 358                    Component::CurDir => {}
 359                    Component::ParentDir => {
 360                        entry_stack.pop()?;
 361                        real_path.pop();
 362                    }
 363                    Component::Normal(name) => {
 364                        let current_entry = entry_stack.last().cloned()?;
 365                        let current_entry = current_entry.lock().await;
 366                        if let FakeFsEntry::Dir { entries, .. } = &*current_entry {
 367                            let entry = entries.get(name.to_str().unwrap()).cloned()?;
 368                            let _entry = entry.lock().await;
 369                            if let FakeFsEntry::Symlink { target, .. } = &*_entry {
 370                                let mut target = target.clone();
 371                                target.extend(path_components);
 372                                path = target;
 373                                continue 'outer;
 374                            } else {
 375                                entry_stack.push(entry.clone());
 376                                real_path.push(name);
 377                            }
 378                        } else {
 379                            return None;
 380                        }
 381                    }
 382                }
 383            }
 384            break;
 385        }
 386        entry_stack.pop().map(|entry| (entry, real_path))
 387    }
 388
 389    async fn write_path<Fn, T>(&self, path: &Path, callback: Fn) -> Result<T>
 390    where
 391        Fn: FnOnce(btree_map::Entry<String, Arc<Mutex<FakeFsEntry>>>) -> Result<T>,
 392    {
 393        let path = normalize_path(path);
 394        let filename = path
 395            .file_name()
 396            .ok_or_else(|| anyhow!("cannot overwrite the root"))?;
 397        let parent_path = path.parent().unwrap();
 398
 399        let parent = self.read_path(parent_path).await?;
 400        let mut parent = parent.lock().await;
 401        let new_entry = parent
 402            .dir_entries(parent_path)?
 403            .entry(filename.to_str().unwrap().into());
 404        callback(new_entry)
 405    }
 406
 407    fn emit_event<I, T>(&mut self, paths: I)
 408    where
 409        I: IntoIterator<Item = T>,
 410        T: Into<PathBuf>,
 411    {
 412        let events = paths
 413            .into_iter()
 414            .map(|path| fsevent::Event {
 415                event_id: 0,
 416                flags: fsevent::StreamFlags::empty(),
 417                path: path.into(),
 418            })
 419            .collect::<Vec<_>>();
 420
 421        self.event_txs.retain(|tx| {
 422            let _ = tx.try_send(events.clone());
 423            !tx.is_closed()
 424        });
 425    }
 426}
 427
 428#[cfg(any(test, feature = "test-support"))]
 429impl FakeFs {
 430    pub fn new(executor: Arc<gpui::executor::Background>) -> Arc<Self> {
 431        Arc::new(Self {
 432            executor: Arc::downgrade(&executor),
 433            state: Mutex::new(FakeFsState {
 434                root: Arc::new(Mutex::new(FakeFsEntry::Dir {
 435                    inode: 0,
 436                    mtime: SystemTime::now(),
 437                    entries: Default::default(),
 438                })),
 439                next_inode: 1,
 440                event_txs: Default::default(),
 441            }),
 442        })
 443    }
 444
 445    pub async fn insert_file(&self, path: impl AsRef<Path>, content: String) {
 446        let mut state = self.state.lock().await;
 447        let path = path.as_ref();
 448        let inode = state.next_inode;
 449        state.next_inode += 1;
 450        let file = Arc::new(Mutex::new(FakeFsEntry::File {
 451            inode,
 452            mtime: SystemTime::now(),
 453            content,
 454        }));
 455        state
 456            .write_path(path, move |entry| {
 457                match entry {
 458                    btree_map::Entry::Vacant(e) => {
 459                        e.insert(file);
 460                    }
 461                    btree_map::Entry::Occupied(mut e) => {
 462                        *e.get_mut() = file;
 463                    }
 464                }
 465                Ok(())
 466            })
 467            .await
 468            .unwrap();
 469        state.emit_event(&[path]);
 470    }
 471
 472    pub async fn insert_symlink(&self, path: impl AsRef<Path>, target: PathBuf) {
 473        let mut state = self.state.lock().await;
 474        let path = path.as_ref();
 475        let file = Arc::new(Mutex::new(FakeFsEntry::Symlink { target }));
 476        state
 477            .write_path(path.as_ref(), move |e| match e {
 478                btree_map::Entry::Vacant(e) => {
 479                    e.insert(file);
 480                    Ok(())
 481                }
 482                btree_map::Entry::Occupied(mut e) => {
 483                    *e.get_mut() = file;
 484                    Ok(())
 485                }
 486            })
 487            .await
 488            .unwrap();
 489        state.emit_event(&[path]);
 490    }
 491
 492    #[must_use]
 493    pub fn insert_tree<'a>(
 494        &'a self,
 495        path: impl 'a + AsRef<Path> + Send,
 496        tree: serde_json::Value,
 497    ) -> futures::future::BoxFuture<'a, ()> {
 498        use futures::FutureExt as _;
 499        use serde_json::Value::*;
 500
 501        async move {
 502            let path = path.as_ref();
 503
 504            match tree {
 505                Object(map) => {
 506                    self.create_dir(path).await.unwrap();
 507                    for (name, contents) in map {
 508                        let mut path = PathBuf::from(path);
 509                        path.push(name);
 510                        self.insert_tree(&path, contents).await;
 511                    }
 512                }
 513                Null => {
 514                    self.create_dir(path).await.unwrap();
 515                }
 516                String(contents) => {
 517                    self.insert_file(&path, contents).await;
 518                }
 519                _ => {
 520                    panic!("JSON object must contain only objects, strings, or null");
 521                }
 522            }
 523        }
 524        .boxed()
 525    }
 526
 527    pub async fn files(&self) -> Vec<PathBuf> {
 528        let mut result = Vec::new();
 529        let mut queue = collections::VecDeque::new();
 530        queue.push_back((PathBuf::from("/"), self.state.lock().await.root.clone()));
 531        while let Some((path, entry)) = queue.pop_front() {
 532            let e = entry.lock().await;
 533            match &*e {
 534                FakeFsEntry::File { .. } => result.push(path),
 535                FakeFsEntry::Dir { entries, .. } => {
 536                    for (name, entry) in entries {
 537                        queue.push_back((path.join(name), entry.clone()));
 538                    }
 539                }
 540                FakeFsEntry::Symlink { .. } => {}
 541            }
 542        }
 543        result
 544    }
 545
 546    async fn simulate_random_delay(&self) {
 547        self.executor
 548            .upgrade()
 549            .expect("executor has been dropped")
 550            .simulate_random_delay()
 551            .await;
 552    }
 553}
 554
 555#[cfg(any(test, feature = "test-support"))]
 556impl FakeFsEntry {
 557    fn is_file(&self) -> bool {
 558        matches!(self, Self::File { .. })
 559    }
 560
 561    fn file_content(&self, path: &Path) -> Result<&String> {
 562        if let Self::File { content, .. } = self {
 563            Ok(content)
 564        } else {
 565            Err(anyhow!("not a file: {}", path.display()))
 566        }
 567    }
 568
 569    fn set_file_content(&mut self, path: &Path, new_content: String) -> Result<()> {
 570        if let Self::File { content, mtime, .. } = self {
 571            *mtime = SystemTime::now();
 572            *content = new_content;
 573            Ok(())
 574        } else {
 575            Err(anyhow!("not a file: {}", path.display()))
 576        }
 577    }
 578
 579    fn dir_entries(
 580        &mut self,
 581        path: &Path,
 582    ) -> Result<&mut BTreeMap<String, Arc<Mutex<FakeFsEntry>>>> {
 583        if let Self::Dir { entries, .. } = self {
 584            Ok(entries)
 585        } else {
 586            Err(anyhow!("not a directory: {}", path.display()))
 587        }
 588    }
 589}
 590
 591#[cfg(any(test, feature = "test-support"))]
 592#[async_trait::async_trait]
 593impl Fs for FakeFs {
 594    async fn create_dir(&self, path: &Path) -> Result<()> {
 595        self.simulate_random_delay().await;
 596        let mut state = self.state.lock().await;
 597
 598        let mut created_dirs = Vec::new();
 599        let mut cur_path = PathBuf::new();
 600        for component in path.components() {
 601            cur_path.push(component);
 602            if cur_path == Path::new("/") {
 603                continue;
 604            }
 605
 606            let inode = state.next_inode;
 607            state.next_inode += 1;
 608            state
 609                .write_path(&cur_path, |entry| {
 610                    entry.or_insert_with(|| {
 611                        created_dirs.push(cur_path.clone());
 612                        Arc::new(Mutex::new(FakeFsEntry::Dir {
 613                            inode,
 614                            mtime: SystemTime::now(),
 615                            entries: Default::default(),
 616                        }))
 617                    });
 618                    Ok(())
 619                })
 620                .await?;
 621        }
 622
 623        state.emit_event(&created_dirs);
 624        Ok(())
 625    }
 626
 627    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
 628        self.simulate_random_delay().await;
 629        let mut state = self.state.lock().await;
 630        let inode = state.next_inode;
 631        state.next_inode += 1;
 632        let file = Arc::new(Mutex::new(FakeFsEntry::File {
 633            inode,
 634            mtime: SystemTime::now(),
 635            content: String::new(),
 636        }));
 637        state
 638            .write_path(path, |entry| {
 639                match entry {
 640                    btree_map::Entry::Occupied(mut e) => {
 641                        if options.overwrite {
 642                            *e.get_mut() = file;
 643                        } else if !options.ignore_if_exists {
 644                            return Err(anyhow!("path already exists: {}", path.display()));
 645                        }
 646                    }
 647                    btree_map::Entry::Vacant(e) => {
 648                        e.insert(file);
 649                    }
 650                }
 651                Ok(())
 652            })
 653            .await?;
 654        state.emit_event(&[path]);
 655        Ok(())
 656    }
 657
 658    async fn rename(&self, old_path: &Path, new_path: &Path, options: RenameOptions) -> Result<()> {
 659        let old_path = normalize_path(old_path);
 660        let new_path = normalize_path(new_path);
 661        let mut state = self.state.lock().await;
 662        let moved_entry = state
 663            .write_path(&old_path, |e| {
 664                if let btree_map::Entry::Occupied(e) = e {
 665                    Ok(e.remove())
 666                } else {
 667                    Err(anyhow!("path does not exist: {}", &old_path.display()))
 668                }
 669            })
 670            .await?;
 671        state
 672            .write_path(&new_path, |e| {
 673                match e {
 674                    btree_map::Entry::Occupied(mut e) => {
 675                        if options.overwrite {
 676                            *e.get_mut() = moved_entry;
 677                        } else if !options.ignore_if_exists {
 678                            return Err(anyhow!("path already exists: {}", new_path.display()));
 679                        }
 680                    }
 681                    btree_map::Entry::Vacant(e) => {
 682                        e.insert(moved_entry);
 683                    }
 684                }
 685                Ok(())
 686            })
 687            .await?;
 688        state.emit_event(&[old_path, new_path]);
 689        Ok(())
 690    }
 691
 692    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
 693        let source = normalize_path(source);
 694        let target = normalize_path(target);
 695        let mut state = self.state.lock().await;
 696        let source_entry = state.read_path(&source).await?;
 697        let content = source_entry.lock().await.file_content(&source)?.clone();
 698        let entry = state
 699            .write_path(&target, |e| match e {
 700                btree_map::Entry::Occupied(e) => {
 701                    if options.overwrite {
 702                        Ok(Some(e.get().clone()))
 703                    } else if !options.ignore_if_exists {
 704                        return Err(anyhow!("{target:?} already exists"));
 705                    } else {
 706                        Ok(None)
 707                    }
 708                }
 709                btree_map::Entry::Vacant(e) => Ok(Some(
 710                    e.insert(Arc::new(Mutex::new(FakeFsEntry::File {
 711                        inode: 0,
 712                        mtime: SystemTime::now(),
 713                        content: String::new(),
 714                    })))
 715                    .clone(),
 716                )),
 717            })
 718            .await?;
 719        if let Some(entry) = entry {
 720            entry.lock().await.set_file_content(&target, content)?;
 721        }
 722        state.emit_event(&[target]);
 723        Ok(())
 724    }
 725
 726    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 727        let path = normalize_path(path);
 728        let parent_path = path
 729            .parent()
 730            .ok_or_else(|| anyhow!("cannot remove the root"))?;
 731        let base_name = path.file_name().unwrap();
 732
 733        let state = self.state.lock().await;
 734        let parent_entry = state.read_path(parent_path).await?;
 735        let mut parent_entry = parent_entry.lock().await;
 736        let entry = parent_entry
 737            .dir_entries(parent_path)?
 738            .entry(base_name.to_str().unwrap().into());
 739
 740        match entry {
 741            btree_map::Entry::Vacant(_) => {
 742                if !options.ignore_if_not_exists {
 743                    return Err(anyhow!("{path:?} does not exist"));
 744                }
 745            }
 746            btree_map::Entry::Occupied(e) => {
 747                {
 748                    let mut entry = e.get().lock().await;
 749                    let children = entry.dir_entries(&path)?;
 750                    if !options.recursive && !children.is_empty() {
 751                        return Err(anyhow!("{path:?} is not empty"));
 752                    }
 753                }
 754                e.remove();
 755            }
 756        }
 757
 758        Ok(())
 759    }
 760
 761    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 762        let path = normalize_path(path);
 763        let parent_path = path
 764            .parent()
 765            .ok_or_else(|| anyhow!("cannot remove the root"))?;
 766        let base_name = path.file_name().unwrap();
 767        let mut state = self.state.lock().await;
 768        let parent_entry = state.read_path(parent_path).await?;
 769        let mut parent_entry = parent_entry.lock().await;
 770        let entry = parent_entry
 771            .dir_entries(parent_path)?
 772            .entry(base_name.to_str().unwrap().into());
 773        match entry {
 774            btree_map::Entry::Vacant(_) => {
 775                if !options.ignore_if_not_exists {
 776                    return Err(anyhow!("{path:?} does not exist"));
 777                }
 778            }
 779            btree_map::Entry::Occupied(e) => {
 780                e.get().lock().await.file_content(&path)?;
 781                e.remove();
 782            }
 783        }
 784        state.emit_event(&[path]);
 785        Ok(())
 786    }
 787
 788    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>> {
 789        let text = self.load(path).await?;
 790        Ok(Box::new(io::Cursor::new(text)))
 791    }
 792
 793    async fn load(&self, path: &Path) -> Result<String> {
 794        let path = normalize_path(path);
 795        self.simulate_random_delay().await;
 796        let state = self.state.lock().await;
 797        let entry = state.read_path(&path).await?;
 798        let entry = entry.lock().await;
 799        entry.file_content(&path).cloned()
 800    }
 801
 802    async fn load_head_text(&self, _: &Path) -> Option<String> {
 803        None
 804    }
 805
 806    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
 807        self.simulate_random_delay().await;
 808        let path = normalize_path(path);
 809        let content = chunks(text, line_ending).collect();
 810        self.insert_file(path, content).await;
 811        Ok(())
 812    }
 813
 814    async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
 815        let path = normalize_path(path);
 816        self.simulate_random_delay().await;
 817        let state = self.state.lock().await;
 818        if let Some((_, real_path)) = state.try_read_path(&path).await {
 819            Ok(real_path)
 820        } else {
 821            Err(anyhow!("path does not exist: {}", path.display()))
 822        }
 823    }
 824
 825    async fn is_file(&self, path: &Path) -> bool {
 826        let path = normalize_path(path);
 827        self.simulate_random_delay().await;
 828        let state = self.state.lock().await;
 829        if let Some((entry, _)) = state.try_read_path(&path).await {
 830            entry.lock().await.is_file()
 831        } else {
 832            false
 833        }
 834    }
 835
 836    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
 837        self.simulate_random_delay().await;
 838        let path = normalize_path(path);
 839        let state = self.state.lock().await;
 840        if let Some((entry, real_path)) = state.try_read_path(&path).await {
 841            let entry = entry.lock().await;
 842            let is_symlink = real_path != path;
 843
 844            Ok(Some(match &*entry {
 845                FakeFsEntry::File { inode, mtime, .. } => Metadata {
 846                    inode: *inode,
 847                    mtime: *mtime,
 848                    is_dir: false,
 849                    is_symlink,
 850                },
 851                FakeFsEntry::Dir { inode, mtime, .. } => Metadata {
 852                    inode: *inode,
 853                    mtime: *mtime,
 854                    is_dir: true,
 855                    is_symlink,
 856                },
 857                FakeFsEntry::Symlink { .. } => unreachable!(),
 858            }))
 859        } else {
 860            Ok(None)
 861        }
 862    }
 863
 864    async fn read_dir(
 865        &self,
 866        path: &Path,
 867    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
 868        self.simulate_random_delay().await;
 869        let path = normalize_path(path);
 870        let state = self.state.lock().await;
 871        let entry = state.read_path(&path).await?;
 872        let mut entry = entry.lock().await;
 873        let children = entry.dir_entries(&path)?;
 874        let paths = children
 875            .keys()
 876            .map(|file_name| Ok(path.join(file_name)))
 877            .collect::<Vec<_>>();
 878        Ok(Box::pin(futures::stream::iter(paths)))
 879    }
 880
 881    async fn watch(
 882        &self,
 883        path: &Path,
 884        _: Duration,
 885    ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>> {
 886        let mut state = self.state.lock().await;
 887        self.simulate_random_delay().await;
 888        let (tx, rx) = smol::channel::unbounded();
 889        state.event_txs.push(tx);
 890        let path = path.to_path_buf();
 891        let executor = self.executor.clone();
 892        Box::pin(futures::StreamExt::filter(rx, move |events| {
 893            let result = events.iter().any(|event| event.path.starts_with(&path));
 894            let executor = executor.clone();
 895            async move {
 896                if let Some(executor) = executor.clone().upgrade() {
 897                    executor.simulate_random_delay().await;
 898                }
 899                result
 900            }
 901        }))
 902    }
 903
 904    fn open_git_repository(
 905        &self,
 906        abs_dotgit_path: &Path,
 907        content_path: &Arc<Path>,
 908    ) -> Option<Box<dyn GitRepository>> {
 909        Some(Box::new(FakeGitRepository::new(
 910            abs_dotgit_path,
 911            content_path,
 912        )))
 913    }
 914
 915    fn is_fake(&self) -> bool {
 916        true
 917    }
 918
 919    #[cfg(any(test, feature = "test-support"))]
 920    fn as_fake(&self) -> &FakeFs {
 921        self
 922    }
 923}
 924
 925fn chunks(rope: &Rope, line_ending: LineEnding) -> impl Iterator<Item = &str> {
 926    rope.chunks().flat_map(move |chunk| {
 927        let mut newline = false;
 928        chunk.split('\n').flat_map(move |line| {
 929            let ending = if newline {
 930                Some(line_ending.as_str())
 931            } else {
 932                None
 933            };
 934            newline = true;
 935            ending.into_iter().chain([line])
 936        })
 937    })
 938}
 939
 940pub fn normalize_path(path: &Path) -> PathBuf {
 941    let mut components = path.components().peekable();
 942    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
 943        components.next();
 944        PathBuf::from(c.as_os_str())
 945    } else {
 946        PathBuf::new()
 947    };
 948
 949    for component in components {
 950        match component {
 951            Component::Prefix(..) => unreachable!(),
 952            Component::RootDir => {
 953                ret.push(component.as_os_str());
 954            }
 955            Component::CurDir => {}
 956            Component::ParentDir => {
 957                ret.pop();
 958            }
 959            Component::Normal(c) => {
 960                ret.push(c);
 961            }
 962        }
 963    }
 964    ret
 965}
 966
 967pub fn copy_recursive<'a>(
 968    fs: &'a dyn Fs,
 969    source: &'a Path,
 970    target: &'a Path,
 971    options: CopyOptions,
 972) -> BoxFuture<'a, Result<()>> {
 973    use futures::future::FutureExt;
 974
 975    async move {
 976        let metadata = fs
 977            .metadata(source)
 978            .await?
 979            .ok_or_else(|| anyhow!("path does not exist: {}", source.display()))?;
 980        if metadata.is_dir {
 981            if !options.overwrite && fs.metadata(target).await.is_ok() {
 982                if options.ignore_if_exists {
 983                    return Ok(());
 984                } else {
 985                    return Err(anyhow!("{target:?} already exists"));
 986                }
 987            }
 988
 989            let _ = fs
 990                .remove_dir(
 991                    target,
 992                    RemoveOptions {
 993                        recursive: true,
 994                        ignore_if_not_exists: true,
 995                    },
 996                )
 997                .await;
 998            fs.create_dir(target).await?;
 999            let mut children = fs.read_dir(source).await?;
1000            while let Some(child_path) = children.next().await {
1001                if let Ok(child_path) = child_path {
1002                    if let Some(file_name) = child_path.file_name() {
1003                        let child_target_path = target.join(file_name);
1004                        copy_recursive(fs, &child_path, &child_target_path, options).await?;
1005                    }
1006                }
1007            }
1008
1009            Ok(())
1010        } else {
1011            fs.copy_file(source, target, options).await
1012        }
1013    }
1014    .boxed()
1015}
1016
1017#[cfg(test)]
1018mod tests {
1019    use super::*;
1020    use gpui::TestAppContext;
1021    use serde_json::json;
1022
1023    #[gpui::test]
1024    async fn test_fake_fs(cx: &mut TestAppContext) {
1025        let fs = FakeFs::new(cx.background());
1026
1027        fs.insert_tree(
1028            "/root",
1029            json!({
1030                "dir1": {
1031                    "a": "A",
1032                    "b": "B"
1033                },
1034                "dir2": {
1035                    "c": "C",
1036                    "dir3": {
1037                        "d": "D"
1038                    }
1039                }
1040            }),
1041        )
1042        .await;
1043
1044        assert_eq!(
1045            fs.files().await,
1046            vec![
1047                PathBuf::from("/root/dir1/a"),
1048                PathBuf::from("/root/dir1/b"),
1049                PathBuf::from("/root/dir2/c"),
1050                PathBuf::from("/root/dir2/dir3/d"),
1051            ]
1052        );
1053
1054        fs.insert_symlink("/root/dir2/link-to-dir3", "./dir3".into())
1055            .await;
1056
1057        assert_eq!(
1058            fs.canonicalize("/root/dir2/link-to-dir3".as_ref())
1059                .await
1060                .unwrap(),
1061            PathBuf::from("/root/dir2/dir3"),
1062        );
1063        assert_eq!(
1064            fs.canonicalize("/root/dir2/link-to-dir3/d".as_ref())
1065                .await
1066                .unwrap(),
1067            PathBuf::from("/root/dir2/dir3/d"),
1068        );
1069        assert_eq!(
1070            fs.load("/root/dir2/link-to-dir3/d".as_ref()).await.unwrap(),
1071            "D",
1072        );
1073    }
1074}