2558 lines · 91.6 KB · Rust
RawDownload
use crate::{    lsp_store::OpenLspBufferHandle,    search::SearchQuery,    worktree_store::{WorktreeStore, WorktreeStoreEvent},    ProjectItem as _, ProjectPath,};use ::git::{parse_git_remote_url, BuildPermalinkParams, GitHostingProviderRegistry};use anyhow::{anyhow, bail, Context as _, Result};use client::Client;use collections::{hash_map, HashMap, HashSet};use fs::Fs;use futures::{channel::oneshot, future::Shared, Future, FutureExt as _, StreamExt};use git::{blame::Blame, diff::BufferDiff, repository::RepoPath};use gpui::{    App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Subscription, Task, WeakEntity,};use http_client::Url;use language::{    proto::{        deserialize_line_ending, deserialize_version, serialize_line_ending, serialize_version,        split_operations,    },    Buffer, BufferEvent, Capability, DiskState, File as _, Language, LanguageRegistry, Operation,};use rpc::{proto, AnyProtoClient, ErrorExt as _, TypedEnvelope};use serde::Deserialize;use smol::channel::Receiver;use std::{    io,    ops::Range,    path::{Path, PathBuf},    pin::pin,    str::FromStr as _,    sync::Arc,    time::Instant,};use text::{BufferId, LineEnding, Rope};use util::{debug_panic, maybe, ResultExt as _, TryFutureExt};use worktree::{File, PathChange, ProjectEntryId, UpdatedGitRepositoriesSet, Worktree, WorktreeId}; /// A set of open buffers.pub struct BufferStore {    state: BufferStoreState,    #[allow(clippy::type_complexity)]    loading_buffers: HashMap<ProjectPath, Shared<Task<Result<Entity<Buffer>, Arc<anyhow::Error>>>>>,    #[allow(clippy::type_complexity)]    loading_change_sets:        HashMap<BufferId, Shared<Task<Result<Entity<BufferChangeSet>, Arc<anyhow::Error>>>>>,    worktree_store: Entity<WorktreeStore>,    opened_buffers: HashMap<BufferId, OpenBuffer>,    downstream_client: Option<(AnyProtoClient, u64)>,    shared_buffers: HashMap<proto::PeerId, HashMap<BufferId, SharedBuffer>>,} #[derive(Hash, Eq, PartialEq, Clone)]struct SharedBuffer {    buffer: Entity<Buffer>,    unstaged_changes: Option<Entity<BufferChangeSet>>,    lsp_handle: Option<OpenLspBufferHandle>,} pub struct BufferChangeSet {    pub buffer_id: BufferId,    pub base_text: Option<language::BufferSnapshot>,    pub language: Option<Arc<Language>>,    pub diff_to_buffer: git::diff::BufferDiff,    pub recalculate_diff_task: Option<Task<Result<()>>>,    pub diff_updated_futures: Vec<oneshot::Sender<()>>,    pub language_registry: Option<Arc<LanguageRegistry>>,} enum BufferStoreState {    Local(LocalBufferStore),    Remote(RemoteBufferStore),} struct RemoteBufferStore {    shared_with_me: HashSet<Entity<Buffer>>,    upstream_client: AnyProtoClient,    project_id: u64,    loading_remote_buffers_by_id: HashMap<BufferId, Entity<Buffer>>,    remote_buffer_listeners:        HashMap<BufferId, Vec<oneshot::Sender<Result<Entity<Buffer>, anyhow::Error>>>>,    worktree_store: Entity<WorktreeStore>,} struct LocalBufferStore {    local_buffer_ids_by_path: HashMap<ProjectPath, BufferId>,    local_buffer_ids_by_entry_id: HashMap<ProjectEntryId, BufferId>,    worktree_store: Entity<WorktreeStore>,    _subscription: Subscription,} enum OpenBuffer {    Complete {        buffer: WeakEntity<Buffer>,        unstaged_changes: Option<WeakEntity<BufferChangeSet>>,    },    Operations(Vec<Operation>),} pub enum BufferStoreEvent {    BufferAdded(Entity<Buffer>),    BufferDropped(BufferId),    BufferChangedFilePath {        buffer: Entity<Buffer>,        old_file: Option<Arc<dyn language::File>>,    },} #[derive(Default, Debug)]pub struct ProjectTransaction(pub HashMap<Entity<Buffer>, language::Transaction>); impl EventEmitter<BufferStoreEvent> for BufferStore {} impl RemoteBufferStore {    fn load_staged_text(&self, buffer_id: BufferId, cx: &App) -> Task<Result<Option<String>>> {        let project_id = self.project_id;        let client = self.upstream_client.clone();        cx.background_executor().spawn(async move {            Ok(client                .request(proto::GetStagedText {                    project_id,                    buffer_id: buffer_id.to_proto(),                })                .await?                .staged_text)        })    }    pub fn wait_for_remote_buffer(        &mut self,        id: BufferId,        cx: &mut Context<BufferStore>,    ) -> Task<Result<Entity<Buffer>>> {        let (tx, rx) = oneshot::channel();        self.remote_buffer_listeners.entry(id).or_default().push(tx);         cx.spawn(|this, cx| async move {            if let Some(buffer) = this                .read_with(&cx, |buffer_store, _| buffer_store.get(id))                .ok()                .flatten()            {                return Ok(buffer);            }             cx.background_executor()                .spawn(async move { rx.await? })                .await        })    }     fn save_remote_buffer(        &self,        buffer_handle: Entity<Buffer>,        new_path: Option<proto::ProjectPath>,        cx: &Context<BufferStore>,    ) -> Task<Result<()>> {        let buffer = buffer_handle.read(cx);        let buffer_id = buffer.remote_id().into();        let version = buffer.version();        let rpc = self.upstream_client.clone();        let project_id = self.project_id;        cx.spawn(move |_, mut cx| async move {            let response = rpc                .request(proto::SaveBuffer {                    project_id,                    buffer_id,                    new_path,                    version: serialize_version(&version),                })                .await?;            let version = deserialize_version(&response.version);            let mtime = response.mtime.map(|mtime| mtime.into());             buffer_handle.update(&mut cx, |buffer, cx| {                buffer.did_save(version.clone(), mtime, cx);            })?;             Ok(())        })    }     pub fn handle_create_buffer_for_peer(        &mut self,        envelope: TypedEnvelope<proto::CreateBufferForPeer>,        replica_id: u16,        capability: Capability,        cx: &mut Context<BufferStore>,    ) -> Result<Option<Entity<Buffer>>> {        match envelope            .payload            .variant            .ok_or_else(|| anyhow!("missing variant"))?        {            proto::create_buffer_for_peer::Variant::State(mut state) => {                let buffer_id = BufferId::new(state.id)?;                 let buffer_result = maybe!({                    let mut buffer_file = None;                    if let Some(file) = state.file.take() {                        let worktree_id = worktree::WorktreeId::from_proto(file.worktree_id);                        let worktree = self                            .worktree_store                            .read(cx)                            .worktree_for_id(worktree_id, cx)                            .ok_or_else(|| {                                anyhow!("no worktree found for id {}", file.worktree_id)                            })?;                        buffer_file = Some(Arc::new(File::from_proto(file, worktree.clone(), cx)?)                            as Arc<dyn language::File>);                    }                    Buffer::from_proto(replica_id, capability, state, buffer_file)                });                 match buffer_result {                    Ok(buffer) => {                        let buffer = cx.new(|_| buffer);                        self.loading_remote_buffers_by_id.insert(buffer_id, buffer);                    }                    Err(error) => {                        if let Some(listeners) = self.remote_buffer_listeners.remove(&buffer_id) {                            for listener in listeners {                                listener.send(Err(anyhow!(error.cloned()))).ok();                            }                        }                    }                }            }            proto::create_buffer_for_peer::Variant::Chunk(chunk) => {                let buffer_id = BufferId::new(chunk.buffer_id)?;                let buffer = self                    .loading_remote_buffers_by_id                    .get(&buffer_id)                    .cloned()                    .ok_or_else(|| {                        anyhow!(                            "received chunk for buffer {} without initial state",                            chunk.buffer_id                        )                    })?;                 let result = maybe!({                    let operations = chunk                        .operations                        .into_iter()                        .map(language::proto::deserialize_operation)                        .collect::<Result<Vec<_>>>()?;                    buffer.update(cx, |buffer, cx| buffer.apply_ops(operations, cx));                    anyhow::Ok(())                });                 if let Err(error) = result {                    self.loading_remote_buffers_by_id.remove(&buffer_id);                    if let Some(listeners) = self.remote_buffer_listeners.remove(&buffer_id) {                        for listener in listeners {                            listener.send(Err(error.cloned())).ok();                        }                    }                } else if chunk.is_last {                    self.loading_remote_buffers_by_id.remove(&buffer_id);                    if self.upstream_client.is_via_collab() {                        // retain buffers sent by peers to avoid races.                        self.shared_with_me.insert(buffer.clone());                    }                     if let Some(senders) = self.remote_buffer_listeners.remove(&buffer_id) {                        for sender in senders {                            sender.send(Ok(buffer.clone())).ok();                        }                    }                    return Ok(Some(buffer));                }            }        }        return Ok(None);    }     pub fn incomplete_buffer_ids(&self) -> Vec<BufferId> {        self.loading_remote_buffers_by_id            .keys()            .copied()            .collect::<Vec<_>>()    }     pub fn deserialize_project_transaction(        &self,        message: proto::ProjectTransaction,        push_to_history: bool,        cx: &mut Context<BufferStore>,    ) -> Task<Result<ProjectTransaction>> {        cx.spawn(|this, mut cx| async move {            let mut project_transaction = ProjectTransaction::default();            for (buffer_id, transaction) in message.buffer_ids.into_iter().zip(message.transactions)            {                let buffer_id = BufferId::new(buffer_id)?;                let buffer = this                    .update(&mut cx, |this, cx| {                        this.wait_for_remote_buffer(buffer_id, cx)                    })?                    .await?;                let transaction = language::proto::deserialize_transaction(transaction)?;                project_transaction.0.insert(buffer, transaction);            }             for (buffer, transaction) in &project_transaction.0 {                buffer                    .update(&mut cx, |buffer, _| {                        buffer.wait_for_edits(transaction.edit_ids.iter().copied())                    })?                    .await?;                 if push_to_history {                    buffer.update(&mut cx, |buffer, _| {                        buffer.push_transaction(transaction.clone(), Instant::now());                    })?;                }            }             Ok(project_transaction)        })    }     fn open_buffer(        &self,        path: Arc<Path>,        worktree: Entity<Worktree>,        cx: &mut Context<BufferStore>,    ) -> Task<Result<Entity<Buffer>>> {        let worktree_id = worktree.read(cx).id().to_proto();        let project_id = self.project_id;        let client = self.upstream_client.clone();        let path_string = path.clone().to_string_lossy().to_string();        cx.spawn(move |this, mut cx| async move {            let response = client                .request(proto::OpenBufferByPath {                    project_id,                    worktree_id,                    path: path_string,                })                .await?;            let buffer_id = BufferId::new(response.buffer_id)?;             let buffer = this                .update(&mut cx, {                    |this, cx| this.wait_for_remote_buffer(buffer_id, cx)                })?                .await?;             Ok(buffer)        })    }     fn create_buffer(&self, cx: &mut Context<BufferStore>) -> Task<Result<Entity<Buffer>>> {        let create = self.upstream_client.request(proto::OpenNewBuffer {            project_id: self.project_id,        });        cx.spawn(|this, mut cx| async move {            let response = create.await?;            let buffer_id = BufferId::new(response.buffer_id)?;             this.update(&mut cx, |this, cx| {                this.wait_for_remote_buffer(buffer_id, cx)            })?            .await        })    }     fn reload_buffers(        &self,        buffers: HashSet<Entity<Buffer>>,        push_to_history: bool,        cx: &mut Context<BufferStore>,    ) -> Task<Result<ProjectTransaction>> {        let request = self.upstream_client.request(proto::ReloadBuffers {            project_id: self.project_id,            buffer_ids: buffers                .iter()                .map(|buffer| buffer.read(cx).remote_id().to_proto())                .collect(),        });         cx.spawn(|this, mut cx| async move {            let response = request                .await?                .transaction                .ok_or_else(|| anyhow!("missing transaction"))?;            this.update(&mut cx, |this, cx| {                this.deserialize_project_transaction(response, push_to_history, cx)            })?            .await        })    }} impl LocalBufferStore {    fn load_staged_text(&self, buffer: &Entity<Buffer>, cx: &App) -> Task<Result<Option<String>>> {        let Some(file) = buffer.read(cx).file() else {            return Task::ready(Ok(None));        };        let worktree_id = file.worktree_id(cx);        let path = file.path().clone();        let Some(worktree) = self            .worktree_store            .read(cx)            .worktree_for_id(worktree_id, cx)        else {            return Task::ready(Err(anyhow!("no such worktree")));        };         worktree.read(cx).load_staged_file(path.as_ref(), cx)    }     fn save_local_buffer(        &self,        buffer_handle: Entity<Buffer>,        worktree: Entity<Worktree>,        path: Arc<Path>,        mut has_changed_file: bool,        cx: &mut Context<BufferStore>,    ) -> Task<Result<()>> {        let buffer = buffer_handle.read(cx);         let text = buffer.as_rope().clone();        let line_ending = buffer.line_ending();        let version = buffer.version();        let buffer_id = buffer.remote_id();        if buffer            .file()            .is_some_and(|file| file.disk_state() == DiskState::New)        {            has_changed_file = true;        }         let save = worktree.update(cx, |worktree, cx| {            worktree.write_file(path.as_ref(), text, line_ending, cx)        });         cx.spawn(move |this, mut cx| async move {            let new_file = save.await?;            let mtime = new_file.disk_state().mtime();            this.update(&mut cx, |this, cx| {                if let Some((downstream_client, project_id)) = this.downstream_client.clone() {                    if has_changed_file {                        downstream_client                            .send(proto::UpdateBufferFile {                                project_id,                                buffer_id: buffer_id.to_proto(),                                file: Some(language::File::to_proto(&*new_file, cx)),                            })                            .log_err();                    }                    downstream_client                        .send(proto::BufferSaved {                            project_id,                            buffer_id: buffer_id.to_proto(),                            version: serialize_version(&version),                            mtime: mtime.map(|time| time.into()),                        })                        .log_err();                }            })?;            buffer_handle.update(&mut cx, |buffer, cx| {                if has_changed_file {                    buffer.file_updated(new_file, cx);                }                buffer.did_save(version.clone(), mtime, cx);            })        })    }     fn subscribe_to_worktree(        &mut self,        worktree: &Entity<Worktree>,        cx: &mut Context<BufferStore>,    ) {        cx.subscribe(worktree, |this, worktree, event, cx| {            if worktree.read(cx).is_local() {                match event {                    worktree::Event::UpdatedEntries(changes) => {                        Self::local_worktree_entries_changed(this, &worktree, changes, cx);                    }                    worktree::Event::UpdatedGitRepositories(updated_repos) => {                        Self::local_worktree_git_repos_changed(                            this,                            worktree.clone(),                            updated_repos,                            cx,                        )                    }                    _ => {}                }            }        })        .detach();    }     fn local_worktree_entries_changed(        this: &mut BufferStore,        worktree_handle: &Entity<Worktree>,

Lines 1–500 of 2558.