1212 lines · 44.4 KB · Rust
RawDownload
use std::{    io::{BufRead, BufReader},    path::{Path, PathBuf},    pin::pin,    sync::{Arc, atomic::AtomicUsize},}; use anyhow::{Context as _, Result, anyhow, bail};use collections::{HashMap, HashSet};use fs::{Fs, copy_recursive};use futures::{FutureExt, SinkExt, future::Shared};use gpui::{    App, AppContext as _, AsyncApp, Context, Entity, EntityId, EventEmitter, Task, WeakEntity,};use postage::oneshot;use rpc::{    AnyProtoClient, ErrorExt, TypedEnvelope,    proto::{self, REMOTE_SERVER_PROJECT_ID},};use smol::{    channel::{Receiver, Sender},    stream::StreamExt,};use text::ReplicaId;use util::{    ResultExt,    paths::{PathStyle, RemotePathBuf, SanitizedPath},    rel_path::RelPath,};use worktree::{    CreatedEntry, Entry, ProjectEntryId, UpdatedEntriesSet, UpdatedGitRepositoriesSet, Worktree,    WorktreeId, WorktreeSettings,}; use crate::{ProjectPath, search::SearchQuery}; struct MatchingEntry {    worktree_root: Arc<Path>,    path: ProjectPath,    respond: oneshot::Sender<ProjectPath>,} enum WorktreeStoreState {    Local {        fs: Arc<dyn Fs>,    },    Remote {        upstream_client: AnyProtoClient,        upstream_project_id: u64,        path_style: PathStyle,    },} pub struct WorktreeStore {    next_entry_id: Arc<AtomicUsize>,    downstream_client: Option<(AnyProtoClient, u64)>,    retain_worktrees: bool,    worktrees: Vec<WorktreeHandle>,    worktrees_reordered: bool,    scanning_enabled: bool,    #[allow(clippy::type_complexity)]    loading_worktrees:        HashMap<Arc<SanitizedPath>, Shared<Task<Result<Entity<Worktree>, Arc<anyhow::Error>>>>>,    state: WorktreeStoreState,} #[derive(Debug)]pub enum WorktreeStoreEvent {    WorktreeAdded(Entity<Worktree>),    WorktreeRemoved(EntityId, WorktreeId),    WorktreeReleased(EntityId, WorktreeId),    WorktreeOrderChanged,    WorktreeUpdateSent(Entity<Worktree>),    WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet),    WorktreeUpdatedGitRepositories(WorktreeId, UpdatedGitRepositoriesSet),    WorktreeDeletedEntry(WorktreeId, ProjectEntryId),} impl EventEmitter<WorktreeStoreEvent> for WorktreeStore {} impl WorktreeStore {    pub fn init(client: &AnyProtoClient) {        client.add_entity_request_handler(Self::handle_create_project_entry);        client.add_entity_request_handler(Self::handle_copy_project_entry);        client.add_entity_request_handler(Self::handle_delete_project_entry);        client.add_entity_request_handler(Self::handle_expand_project_entry);        client.add_entity_request_handler(Self::handle_expand_all_for_project_entry);    }     pub fn local(retain_worktrees: bool, fs: Arc<dyn Fs>) -> Self {        Self {            next_entry_id: Default::default(),            loading_worktrees: Default::default(),            downstream_client: None,            worktrees: Vec::new(),            worktrees_reordered: false,            scanning_enabled: true,            retain_worktrees,            state: WorktreeStoreState::Local { fs },        }    }     pub fn remote(        retain_worktrees: bool,        upstream_client: AnyProtoClient,        upstream_project_id: u64,        path_style: PathStyle,    ) -> Self {        Self {            next_entry_id: Default::default(),            loading_worktrees: Default::default(),            downstream_client: None,            worktrees: Vec::new(),            worktrees_reordered: false,            scanning_enabled: true,            retain_worktrees,            state: WorktreeStoreState::Remote {                upstream_client,                upstream_project_id,                path_style,            },        }    }     pub fn disable_scanner(&mut self) {        self.scanning_enabled = false;    }     /// Iterates through all worktrees, including ones that don't appear in the project panel    pub fn worktrees(&self) -> impl '_ + DoubleEndedIterator<Item = Entity<Worktree>> {        self.worktrees            .iter()            .filter_map(move |worktree| worktree.upgrade())    }     /// Iterates through all user-visible worktrees, the ones that appear in the project panel.    pub fn visible_worktrees<'a>(        &'a self,        cx: &'a App,    ) -> impl 'a + DoubleEndedIterator<Item = Entity<Worktree>> {        self.worktrees()            .filter(|worktree| worktree.read(cx).is_visible())    }     /// Iterates through all user-visible worktrees (directories and files that appear in the project panel) and other, invisible single files that could appear e.g. due to drag and drop.    pub fn visible_worktrees_and_single_files<'a>(        &'a self,        cx: &'a App,    ) -> impl 'a + DoubleEndedIterator<Item = Entity<Worktree>> {        self.worktrees()            .filter(|worktree| worktree.read(cx).is_visible() || worktree.read(cx).is_single_file())    }     pub fn worktree_for_id(&self, id: WorktreeId, cx: &App) -> Option<Entity<Worktree>> {        self.worktrees()            .find(|worktree| worktree.read(cx).id() == id)    }     pub fn worktree_for_entry(        &self,        entry_id: ProjectEntryId,        cx: &App,    ) -> Option<Entity<Worktree>> {        self.worktrees()            .find(|worktree| worktree.read(cx).contains_entry(entry_id))    }     pub fn find_worktree(        &self,        abs_path: impl AsRef<Path>,        cx: &App,    ) -> Option<(Entity<Worktree>, Arc<RelPath>)> {        let abs_path = SanitizedPath::new(abs_path.as_ref());        for tree in self.worktrees() {            let path_style = tree.read(cx).path_style();            if let Ok(relative_path) = abs_path.as_ref().strip_prefix(tree.read(cx).abs_path())                && let Ok(relative_path) = RelPath::new(relative_path, path_style)            {                return Some((tree.clone(), relative_path.into_arc()));            }        }        None    }     pub fn absolutize(&self, project_path: &ProjectPath, cx: &App) -> Option<PathBuf> {        let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;        Some(worktree.read(cx).absolutize(&project_path.path))    }     pub fn path_style(&self) -> PathStyle {        match &self.state {            WorktreeStoreState::Local { .. } => PathStyle::local(),            WorktreeStoreState::Remote { path_style, .. } => *path_style,        }    }     pub fn find_or_create_worktree(        &mut self,        abs_path: impl AsRef<Path>,        visible: bool,        cx: &mut Context<Self>,    ) -> Task<Result<(Entity<Worktree>, Arc<RelPath>)>> {        let abs_path = abs_path.as_ref();        if let Some((tree, relative_path)) = self.find_worktree(abs_path, cx) {            Task::ready(Ok((tree, relative_path)))        } else {            let worktree = self.create_worktree(abs_path, visible, cx);            cx.background_spawn(async move { Ok((worktree.await?, RelPath::empty().into())) })        }    }     pub fn entry_for_id<'a>(&'a self, entry_id: ProjectEntryId, cx: &'a App) -> Option<&'a Entry> {        self.worktrees()            .find_map(|worktree| worktree.read(cx).entry_for_id(entry_id))    }     pub fn worktree_and_entry_for_id<'a>(        &'a self,        entry_id: ProjectEntryId,        cx: &'a App,    ) -> Option<(Entity<Worktree>, &'a Entry)> {        self.worktrees().find_map(|worktree| {            worktree                .read(cx)                .entry_for_id(entry_id)                .map(|e| (worktree.clone(), e))        })    }     pub fn entry_for_path<'a>(&'a self, path: &ProjectPath, cx: &'a App) -> Option<&'a Entry> {        self.worktree_for_id(path.worktree_id, cx)?            .read(cx)            .entry_for_path(&path.path)    }     pub fn copy_entry(        &mut self,        entry_id: ProjectEntryId,        new_project_path: ProjectPath,        cx: &mut Context<Self>,    ) -> Task<Result<Option<Entry>>> {        let Some(old_worktree) = self.worktree_for_entry(entry_id, cx) else {            return Task::ready(Err(anyhow!("no such worktree")));        };        let Some(old_entry) = old_worktree.read(cx).entry_for_id(entry_id) else {            return Task::ready(Err(anyhow!("no such entry")));        };        let Some(new_worktree) = self.worktree_for_id(new_project_path.worktree_id, cx) else {            return Task::ready(Err(anyhow!("no such worktree")));        };         match &self.state {            WorktreeStoreState::Local { fs } => {                let old_abs_path = old_worktree.read(cx).absolutize(&old_entry.path);                let new_abs_path = new_worktree.read(cx).absolutize(&new_project_path.path);                let fs = fs.clone();                let copy = cx.background_spawn(async move {                    copy_recursive(                        fs.as_ref(),                        &old_abs_path,                        &new_abs_path,                        Default::default(),                    )                    .await                });                 cx.spawn(async move |_, cx| {                    copy.await?;                    new_worktree                        .update(cx, |this, cx| {                            this.as_local_mut().unwrap().refresh_entry(                                new_project_path.path,                                None,                                cx,                            )                        })?                        .await                })            }            WorktreeStoreState::Remote {                upstream_client,                upstream_project_id,                ..            } => {                let response = upstream_client.request(proto::CopyProjectEntry {                    project_id: *upstream_project_id,                    entry_id: entry_id.to_proto(),                    new_path: new_project_path.path.to_proto(),                    new_worktree_id: new_project_path.worktree_id.to_proto(),                });                cx.spawn(async move |_, cx| {                    let response = response.await?;                    match response.entry {                        Some(entry) => new_worktree                            .update(cx, |worktree, cx| {                                worktree.as_remote_mut().unwrap().insert_entry(                                    entry,                                    response.worktree_scan_id as usize,                                    cx,                                )                            })?                            .await                            .map(Some),                        None => Ok(None),                    }                })            }        }    }     pub fn rename_entry(        &mut self,        entry_id: ProjectEntryId,        new_project_path: ProjectPath,        cx: &mut Context<Self>,    ) -> Task<Result<CreatedEntry>> {        let Some(old_worktree) = self.worktree_for_entry(entry_id, cx) else {            return Task::ready(Err(anyhow!("no such worktree")));        };        let Some(old_entry) = old_worktree.read(cx).entry_for_id(entry_id).cloned() else {            return Task::ready(Err(anyhow!("no such entry")));        };        let Some(new_worktree) = self.worktree_for_id(new_project_path.worktree_id, cx) else {            return Task::ready(Err(anyhow!("no such worktree")));        };         match &self.state {            WorktreeStoreState::Local { fs } => {                let abs_old_path = old_worktree.read(cx).absolutize(&old_entry.path);                let new_worktree_ref = new_worktree.read(cx);                let is_root_entry = new_worktree_ref                    .root_entry()                    .is_some_and(|e| e.id == entry_id);                let abs_new_path = if is_root_entry {                    let abs_path = new_worktree_ref.abs_path();                    let Some(root_parent_path) = abs_path.parent() else {                        return Task::ready(Err(anyhow!("no parent for path {:?}", abs_path)));                    };                    root_parent_path.join(new_project_path.path.as_std_path())                } else {                    new_worktree_ref.absolutize(&new_project_path.path)                };                 let fs = fs.clone();                let case_sensitive = new_worktree                    .read(cx)                    .as_local()                    .unwrap()                    .fs_is_case_sensitive();                 let do_rename =                    async move |fs: &dyn Fs, old_path: &Path, new_path: &Path, overwrite| {                        fs.rename(                            &old_path,                            &new_path,                            fs::RenameOptions {                                overwrite,                                ..fs::RenameOptions::default()                            },                        )                        .await                        .with_context(|| format!("renaming {old_path:?} into {new_path:?}"))                    };                 let rename = cx.background_spawn({                    let abs_new_path = abs_new_path.clone();                    async move {                        // If we're on a case-insensitive FS and we're doing a case-only rename (i.e. `foobar` to `FOOBAR`)                        // we want to overwrite, because otherwise we run into a file-already-exists error.                        let overwrite = !case_sensitive                            && abs_old_path != abs_new_path                            && abs_old_path.to_str().map(|p| p.to_lowercase())                                == abs_new_path.to_str().map(|p| p.to_lowercase());                         // The directory we're renaming into might not exist yet                        if let Err(e) =                            do_rename(fs.as_ref(), &abs_old_path, &abs_new_path, overwrite).await                        {                            if let Some(err) = e.downcast_ref::<std::io::Error>()                                && err.kind() == std::io::ErrorKind::NotFound                            {                                if let Some(parent) = abs_new_path.parent() {                                    fs.create_dir(parent).await.with_context(|| {                                        format!("creating parent directory {parent:?}")                                    })?;                                    return do_rename(                                        fs.as_ref(),                                        &abs_old_path,                                        &abs_new_path,                                        overwrite,                                    )                                    .await;                                }                            }                            return Err(e);                        }                        Ok(())                    }                });                 cx.spawn(async move |_, cx| {                    rename.await?;                    Ok(new_worktree                        .update(cx, |this, cx| {                            let local = this.as_local_mut().unwrap();                            if is_root_entry {                                // We eagerly update `abs_path` and refresh this worktree.                                // Otherwise, the FS watcher would do it on the `RootUpdated` event,                                // but with a noticeable delay, so we handle it proactively.                                local.update_abs_path_and_refresh(                                    SanitizedPath::new_arc(&abs_new_path),                                    cx,                                );                                Task::ready(Ok(this.root_entry().cloned()))                            } else {                                // First refresh the parent directory (in case it was newly created)                                if let Some(parent) = new_project_path.path.parent() {                                    let _ = local.refresh_entries_for_paths(vec![parent.into()]);                                }                                // Then refresh the new path                                local.refresh_entry(                                    new_project_path.path.clone(),                                    Some(old_entry.path),                                    cx,                                )                            }                        })?                        .await?                        .map(CreatedEntry::Included)                        .unwrap_or_else(|| CreatedEntry::Excluded {                            abs_path: abs_new_path,                        }))                })            }            WorktreeStoreState::Remote {                upstream_client,                upstream_project_id,                ..            } => {                let response = upstream_client.request(proto::RenameProjectEntry {                    project_id: *upstream_project_id,                    entry_id: entry_id.to_proto(),                    new_path: new_project_path.path.to_proto(),                    new_worktree_id: new_project_path.worktree_id.to_proto(),                });                cx.spawn(async move |_, cx| {                    let response = response.await?;                    match response.entry {                        Some(entry) => new_worktree                            .update(cx, |worktree, cx| {                                worktree.as_remote_mut().unwrap().insert_entry(                                    entry,                                    response.worktree_scan_id as usize,                                    cx,                                )                            })?                            .await                            .map(CreatedEntry::Included),                        None => {                            let abs_path = new_worktree.read_with(cx, |worktree, _| {                                worktree.absolutize(&new_project_path.path)                            })?;                            Ok(CreatedEntry::Excluded { abs_path })                        }                    }                })            }        }    }    pub fn create_worktree(        &mut self,        abs_path: impl AsRef<Path>,        visible: bool,        cx: &mut Context<Self>,    ) -> Task<Result<Entity<Worktree>>> {        let abs_path: Arc<SanitizedPath> = SanitizedPath::new_arc(&abs_path);        if !self.loading_worktrees.contains_key(&abs_path) {            let task = match &self.state {                WorktreeStoreState::Remote {                    upstream_client,                    path_style,                    ..                } => {                    if upstream_client.is_via_collab() {                        Task::ready(Err(Arc::new(anyhow!("cannot create worktrees via collab"))))                    } else {                        let abs_path = RemotePathBuf::new(abs_path.to_string(), *path_style);                        self.create_remote_worktree(upstream_client.clone(), abs_path, visible, cx)                    }                }                WorktreeStoreState::Local { fs } => {                    self.create_local_worktree(fs.clone(), abs_path.clone(), visible, cx)                }            };             self.loading_worktrees                .insert(abs_path.clone(), task.shared());        }        let task = self.loading_worktrees.get(&abs_path).unwrap().clone();        cx.spawn(async move |this, cx| {

Lines 1–500 of 1212.