Piotr OsiewiczRust 1.85 (#25272)e4e758d1 year agoHistory
2853 lines · 104.3 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 buffer_diff::{BufferDiff, BufferDiffEvent};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, 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::{self, ToProto},    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;use util::{debug_panic, maybe, ResultExt as _, TryFutureExt};use worktree::{File, PathChange, ProjectEntryId, UpdatedGitRepositoriesSet, Worktree, WorktreeId}; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]enum DiffKind {    Unstaged,    Uncommitted,} /// 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_diffs:        HashMap<(BufferId, DiffKind), Shared<Task<Result<Entity<BufferDiff>, 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>,    diff: Option<Entity<BufferDiff>>,    lsp_handle: Option<OpenLspBufferHandle>,} #[derive(Default)]struct BufferDiffState {    unstaged_diff: Option<WeakEntity<BufferDiff>>,    uncommitted_diff: Option<WeakEntity<BufferDiff>>,    recalculate_diff_task: Option<Task<Result<()>>>,    language: Option<Arc<Language>>,    language_registry: Option<Arc<LanguageRegistry>>,    diff_updated_futures: Vec<oneshot::Sender<()>>,     head_text: Option<Arc<String>>,    index_text: Option<Arc<String>>,    head_changed: bool,    index_changed: bool,    language_changed: bool,} #[derive(Clone, Debug)]enum DiffBasesChange {    SetIndex(Option<String>),    SetHead(Option<String>),    SetEach {        index: Option<String>,        head: Option<String>,    },    SetBoth(Option<String>),} impl BufferDiffState {    fn buffer_language_changed(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {        self.language = buffer.read(cx).language().cloned();        self.language_changed = true;        let _ = self.recalculate_diffs(buffer.read(cx).text_snapshot(), cx);    }     fn unstaged_diff(&self) -> Option<Entity<BufferDiff>> {        self.unstaged_diff.as_ref().and_then(|set| set.upgrade())    }     fn uncommitted_diff(&self) -> Option<Entity<BufferDiff>> {        self.uncommitted_diff.as_ref().and_then(|set| set.upgrade())    }     fn handle_base_texts_updated(        &mut self,        buffer: text::BufferSnapshot,        message: proto::UpdateDiffBases,        cx: &mut Context<Self>,    ) {        use proto::update_diff_bases::Mode;         let Some(mode) = Mode::from_i32(message.mode) else {            return;        };         let diff_bases_change = match mode {            Mode::HeadOnly => DiffBasesChange::SetHead(message.committed_text),            Mode::IndexOnly => DiffBasesChange::SetIndex(message.staged_text),            Mode::IndexMatchesHead => DiffBasesChange::SetBoth(message.committed_text),            Mode::IndexAndHead => DiffBasesChange::SetEach {                index: message.staged_text,                head: message.committed_text,            },        };         let _ = self.diff_bases_changed(buffer, diff_bases_change, cx);    }     fn diff_bases_changed(        &mut self,        buffer: text::BufferSnapshot,        diff_bases_change: DiffBasesChange,        cx: &mut Context<Self>,    ) -> oneshot::Receiver<()> {        match diff_bases_change {            DiffBasesChange::SetIndex(index) => {                self.index_text = index.map(|mut index| {                    text::LineEnding::normalize(&mut index);                    Arc::new(index)                });                self.index_changed = true;            }            DiffBasesChange::SetHead(head) => {                self.head_text = head.map(|mut head| {                    text::LineEnding::normalize(&mut head);                    Arc::new(head)                });                self.head_changed = true;            }            DiffBasesChange::SetBoth(text) => {                let text = text.map(|mut text| {                    text::LineEnding::normalize(&mut text);                    Arc::new(text)                });                self.head_text = text.clone();                self.index_text = text;                self.head_changed = true;                self.index_changed = true;            }            DiffBasesChange::SetEach { index, head } => {                self.index_text = index.map(|mut index| {                    text::LineEnding::normalize(&mut index);                    Arc::new(index)                });                self.index_changed = true;                self.head_text = head.map(|mut head| {                    text::LineEnding::normalize(&mut head);                    Arc::new(head)                });                self.head_changed = true;            }        }         self.recalculate_diffs(buffer, cx)    }     fn recalculate_diffs(        &mut self,        buffer: text::BufferSnapshot,        cx: &mut Context<Self>,    ) -> oneshot::Receiver<()> {        log::debug!("recalculate diffs");        let (tx, rx) = oneshot::channel();        self.diff_updated_futures.push(tx);         let language = self.language.clone();        let language_registry = self.language_registry.clone();        let unstaged_diff = self.unstaged_diff();        let uncommitted_diff = self.uncommitted_diff();        let head = self.head_text.clone();        let index = self.index_text.clone();        let index_changed = self.index_changed;        let head_changed = self.head_changed;        let language_changed = self.language_changed;        let index_matches_head = match (self.index_text.as_ref(), self.head_text.as_ref()) {            (Some(index), Some(head)) => Arc::ptr_eq(index, head),            (None, None) => true,            _ => false,        };        self.recalculate_diff_task = Some(cx.spawn(|this, mut cx| async move {            let mut unstaged_changed_range = None;            if let Some(unstaged_diff) = &unstaged_diff {                unstaged_changed_range = BufferDiff::update_diff(                    unstaged_diff.clone(),                    buffer.clone(),                    index,                    index_changed,                    language_changed,                    language.clone(),                    language_registry.clone(),                    &mut cx,                )                .await?;                 unstaged_diff.update(&mut cx, |_, cx| {                    if language_changed {                        cx.emit(BufferDiffEvent::LanguageChanged);                    }                    if let Some(changed_range) = unstaged_changed_range.clone() {                        cx.emit(BufferDiffEvent::DiffChanged {                            changed_range: Some(changed_range),                        })                    }                })?;            }             if let Some(uncommitted_diff) = &uncommitted_diff {                let uncommitted_changed_range =                    if let (Some(unstaged_diff), true) = (&unstaged_diff, index_matches_head) {                        uncommitted_diff.update(&mut cx, |uncommitted_diff, cx| {                            uncommitted_diff.update_diff_from(&buffer, unstaged_diff, cx)                        })?                    } else {                        BufferDiff::update_diff(                            uncommitted_diff.clone(),                            buffer.clone(),                            head,                            head_changed,                            language_changed,                            language.clone(),                            language_registry.clone(),                            &mut cx,                        )                        .await?                    };                 uncommitted_diff.update(&mut cx, |uncommitted_diff, cx| {                    if language_changed {                        cx.emit(BufferDiffEvent::LanguageChanged);                    }                    let changed_range = match (unstaged_changed_range, uncommitted_changed_range) {                        (None, None) => None,                        (Some(unstaged_range), None) => {                            uncommitted_diff.range_to_hunk_range(unstaged_range, &buffer, cx)                        }                        (None, Some(uncommitted_range)) => Some(uncommitted_range),                        (Some(unstaged_range), Some(uncommitted_range)) => {                            let mut start = uncommitted_range.start;                            let mut end = uncommitted_range.end;                            if let Some(unstaged_range) =                                uncommitted_diff.range_to_hunk_range(unstaged_range, &buffer, cx)                            {                                start = unstaged_range.start.min(&uncommitted_range.start, &buffer);                                end = unstaged_range.end.max(&uncommitted_range.end, &buffer);                            }                            Some(start..end)                        }                    };                    cx.emit(BufferDiffEvent::DiffChanged { changed_range });                })?;            }             if let Some(this) = this.upgrade() {                this.update(&mut cx, |this, _| {                    this.index_changed = false;                    this.head_changed = false;                    this.language_changed = false;                    for tx in this.diff_updated_futures.drain(..) {                        tx.send(()).ok();                    }                })?;            }             Ok(())        }));         rx    }} 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>,        diff_state: Entity<BufferDiffState>,    },    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 open_unstaged_diff(&self, buffer_id: BufferId, cx: &App) -> Task<Result<Option<String>>> {        let project_id = self.project_id;        let client = self.upstream_client.clone();        cx.background_spawn(async move {            let response = client                .request(proto::OpenUnstagedDiff {                    project_id,                    buffer_id: buffer_id.to_proto(),                })                .await?;            Ok(response.staged_text)        })    }     fn open_uncommitted_diff(        &self,        buffer_id: BufferId,        cx: &App,    ) -> Task<Result<DiffBasesChange>> {        use proto::open_uncommitted_diff_response::Mode;         let project_id = self.project_id;        let client = self.upstream_client.clone();        cx.background_spawn(async move {            let response = client                .request(proto::OpenUncommittedDiff {                    project_id,                    buffer_id: buffer_id.to_proto(),                })                .await?;            let mode = Mode::from_i32(response.mode).ok_or_else(|| anyhow!("Invalid mode"))?;            let bases = match mode {                Mode::IndexMatchesHead => DiffBasesChange::SetBoth(response.committed_text),                Mode::IndexAndHead => DiffBasesChange::SetEach {                    head: response.committed_text,                    index: response.staged_text,                },            };            Ok(bases)        })    }     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_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

Lines 1–500 of 2853.