fs.rs

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