779 lines · 26.6 KB · Rust
RawDownload
use std::{path::PathBuf, str::FromStr, sync::Arc}; use anyhow::{Context as _, Result, bail}; use async_trait::async_trait;use collections::{BTreeMap, IndexSet}; use gpui::{    App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Subscription, Task, WeakEntity,};use language::{    LanguageName, LanguageRegistry, LanguageToolchainStore, ManifestDelegate, Toolchain,    ToolchainList, ToolchainScope,};use rpc::{    AnyProtoClient, TypedEnvelope,    proto::{        self, ResolveToolchainResponse,        resolve_toolchain_response::Response as ResolveResponsePayload,    },};use settings::WorktreeId;use task::Shell;use util::{ResultExt as _, rel_path::RelPath}; use crate::{    ProjectEnvironment, ProjectPath,    manifest_tree::{ManifestQueryDelegate, ManifestTree},    worktree_store::WorktreeStore,}; pub struct ToolchainStore {    mode: ToolchainStoreInner,    user_toolchains: BTreeMap<ToolchainScope, IndexSet<Toolchain>>,    worktree_store: Entity<WorktreeStore>,    _sub: Subscription,} enum ToolchainStoreInner {    Local(Entity<LocalToolchainStore>),    Remote(Entity<RemoteToolchainStore>),} pub struct Toolchains {    /// Auto-detected toolchains.    pub toolchains: ToolchainList,    /// Path of the project root at which we ran the automatic toolchain detection.    pub root_path: Arc<RelPath>,    pub user_toolchains: BTreeMap<ToolchainScope, IndexSet<Toolchain>>,}impl EventEmitter<ToolchainStoreEvent> for ToolchainStore {}impl ToolchainStore {    pub fn init(client: &AnyProtoClient) {        client.add_entity_request_handler(Self::handle_activate_toolchain);        client.add_entity_request_handler(Self::handle_list_toolchains);        client.add_entity_request_handler(Self::handle_active_toolchain);        client.add_entity_request_handler(Self::handle_resolve_toolchain);    }     pub fn local(        languages: Arc<LanguageRegistry>,        worktree_store: Entity<WorktreeStore>,        project_environment: Entity<ProjectEnvironment>,        manifest_tree: Entity<ManifestTree>,        cx: &mut Context<Self>,    ) -> Self {        let entity = cx.new(|_| LocalToolchainStore {            languages,            worktree_store: worktree_store.clone(),            project_environment,            active_toolchains: Default::default(),            manifest_tree,        });        let _sub = cx.subscribe(&entity, |_, _, e: &ToolchainStoreEvent, cx| {            cx.emit(e.clone())        });        Self {            mode: ToolchainStoreInner::Local(entity),            worktree_store,            user_toolchains: Default::default(),            _sub,        }    }     pub(super) fn remote(        project_id: u64,        worktree_store: Entity<WorktreeStore>,        client: AnyProtoClient,        cx: &mut Context<Self>,    ) -> Self {        let entity = cx.new(|_| RemoteToolchainStore { client, project_id });        let _sub = cx.subscribe(&entity, |_, _, e: &ToolchainStoreEvent, cx| {            cx.emit(e.clone())        });        Self {            mode: ToolchainStoreInner::Remote(entity),            user_toolchains: Default::default(),            worktree_store,            _sub,        }    }    pub(crate) fn activate_toolchain(        &self,        path: ProjectPath,        toolchain: Toolchain,        cx: &mut App,    ) -> Task<Option<()>> {        match &self.mode {            ToolchainStoreInner::Local(local) => {                local.update(cx, |this, cx| this.activate_toolchain(path, toolchain, cx))            }            ToolchainStoreInner::Remote(remote) => {                remote.update(cx, |this, cx| this.activate_toolchain(path, toolchain, cx))            }        }    }     pub(crate) fn user_toolchains(&self) -> BTreeMap<ToolchainScope, IndexSet<Toolchain>> {        self.user_toolchains.clone()    }    pub(crate) fn add_toolchain(        &mut self,        toolchain: Toolchain,        scope: ToolchainScope,        cx: &mut Context<Self>,    ) {        let did_insert = self            .user_toolchains            .entry(scope)            .or_default()            .insert(toolchain);        if did_insert {            cx.emit(ToolchainStoreEvent::CustomToolchainsModified);        }    }     pub(crate) fn remove_toolchain(        &mut self,        toolchain: Toolchain,        scope: ToolchainScope,        cx: &mut Context<Self>,    ) {        let mut did_remove = false;        self.user_toolchains            .entry(scope)            .and_modify(|toolchains| did_remove = toolchains.shift_remove(&toolchain));        if did_remove {            cx.emit(ToolchainStoreEvent::CustomToolchainsModified);        }    }     pub(crate) fn resolve_toolchain(        &self,        abs_path: PathBuf,        language_name: LanguageName,        cx: &mut Context<Self>,    ) -> Task<Result<Toolchain>> {        debug_assert!(abs_path.is_absolute());        match &self.mode {            ToolchainStoreInner::Local(local) => local.update(cx, |this, cx| {                this.resolve_toolchain(abs_path, language_name, cx)            }),            ToolchainStoreInner::Remote(remote) => remote.update(cx, |this, cx| {                this.resolve_toolchain(abs_path, language_name, cx)            }),        }    }    pub(crate) fn list_toolchains(        &self,        path: ProjectPath,        language_name: LanguageName,        cx: &mut Context<Self>,    ) -> Task<Option<Toolchains>> {        let Some(worktree) = self            .worktree_store            .read(cx)            .worktree_for_id(path.worktree_id, cx)        else {            return Task::ready(None);        };        let target_root_path = worktree.read_with(cx, |this, _| this.abs_path());         let user_toolchains = self            .user_toolchains            .iter()            .filter(|(scope, _)| {                if let ToolchainScope::Subproject(subproject_root_path, relative_path) = scope {                    target_root_path == *subproject_root_path                        && relative_path.starts_with(&path.path)                } else {                    true                }            })            .map(|(scope, toolchains)| {                (                    scope.clone(),                    toolchains                        .iter()                        .filter(|toolchain| toolchain.language_name == language_name)                        .cloned()                        .collect::<IndexSet<_>>(),                )            })            .collect::<BTreeMap<_, _>>();        let task = match &self.mode {            ToolchainStoreInner::Local(local) => {                local.update(cx, |this, cx| this.list_toolchains(path, language_name, cx))            }            ToolchainStoreInner::Remote(remote) => {                remote.read(cx).list_toolchains(path, language_name, cx)            }        };        cx.spawn(async move |_, _| {            let (mut toolchains, root_path) = task.await?;            toolchains.toolchains.retain(|toolchain| {                !user_toolchains                    .values()                    .any(|toolchains| toolchains.contains(toolchain))            });             Some(Toolchains {                toolchains,                root_path,                user_toolchains,            })        })    }     pub(crate) fn active_toolchain(        &self,        path: ProjectPath,        language_name: LanguageName,        cx: &App,    ) -> Task<Option<Toolchain>> {        match &self.mode {            ToolchainStoreInner::Local(local) => Task::ready(local.read(cx).active_toolchain(                path.worktree_id,                &path.path,                language_name,            )),            ToolchainStoreInner::Remote(remote) => {                remote.read(cx).active_toolchain(path, language_name, cx)            }        }    }    async fn handle_activate_toolchain(        this: Entity<Self>,        envelope: TypedEnvelope<proto::ActivateToolchain>,        mut cx: AsyncApp,    ) -> Result<proto::Ack> {        this.update(&mut cx, |this, cx| {            let language_name = LanguageName::from_proto(envelope.payload.language_name);            let Some(toolchain) = envelope.payload.toolchain else {                bail!("Missing `toolchain` in payload");            };            let toolchain = Toolchain {                name: toolchain.name.into(),                // todo(windows)                // Do we need to convert path to native string?                path: toolchain.path.into(),                as_json: serde_json::Value::from_str(&toolchain.raw_json)?,                language_name,            };            let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);            let path = if let Some(path) = envelope.payload.path {                RelPath::from_unix_str(&path)?.into()            } else {                RelPath::empty_arc()            };            Ok(this.activate_toolchain(ProjectPath { worktree_id, path }, toolchain, cx))        })?        .await;        Ok(proto::Ack {})    }    async fn handle_active_toolchain(        this: Entity<Self>,        envelope: TypedEnvelope<proto::ActiveToolchain>,        mut cx: AsyncApp,    ) -> Result<proto::ActiveToolchainResponse> {        let path = RelPath::from_unix_str(envelope.payload.path.as_deref().unwrap_or(""))?;        let toolchain = this            .update(&mut cx, |this, cx| {                let language_name = LanguageName::from_proto(envelope.payload.language_name);                let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);                this.active_toolchain(                    ProjectPath {                        worktree_id,                        path: Arc::from(path),                    },                    language_name,                    cx,                )            })            .await;         Ok(proto::ActiveToolchainResponse {            toolchain: toolchain.map(|toolchain| {                let path = PathBuf::from(toolchain.path.to_string());                proto::Toolchain {                    name: toolchain.name.into(),                    path: path.to_string_lossy().into_owned(),                    raw_json: toolchain.as_json.to_string(),                }            }),        })    }     async fn handle_list_toolchains(        this: Entity<Self>,        envelope: TypedEnvelope<proto::ListToolchains>,        mut cx: AsyncApp,    ) -> Result<proto::ListToolchainsResponse> {        let toolchains = this            .update(&mut cx, |this, cx| {                let language_name = LanguageName::from_proto(envelope.payload.language_name);                let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);                let path =                    RelPath::from_unix_str(envelope.payload.path.as_deref().unwrap_or(""))?.into();                anyhow::Ok(this.list_toolchains(                    ProjectPath { worktree_id, path },                    language_name,                    cx,                ))            })?            .await;        let has_values = toolchains.is_some();        let groups = if let Some(Toolchains { toolchains, .. }) = &toolchains {            toolchains                .groups                .iter()                .filter_map(|group| {                    Some(proto::ToolchainGroup {                        start_index: u64::try_from(group.0).ok()?,                        name: String::from(group.1.as_ref()),                    })                })                .collect()        } else {            vec![]        };        let (toolchains, relative_path) = if let Some(Toolchains {            toolchains,            root_path: relative_path,            ..        }) = toolchains        {            let toolchains = toolchains                .toolchains                .into_iter()                .map(|toolchain| {                    let path = PathBuf::from(toolchain.path.to_string());                    proto::Toolchain {                        name: toolchain.name.to_string(),                        path: path.to_string_lossy().into_owned(),                        raw_json: toolchain.as_json.to_string(),                    }                })                .collect::<Vec<_>>();            (toolchains, relative_path)        } else {            (vec![], Arc::from(RelPath::empty()))        };         Ok(proto::ListToolchainsResponse {            has_values,            toolchains,            groups,            relative_worktree_path: Some(relative_path.as_unix_str().to_owned()),        })    }     async fn handle_resolve_toolchain(        this: Entity<Self>,        envelope: TypedEnvelope<proto::ResolveToolchain>,        mut cx: AsyncApp,    ) -> Result<proto::ResolveToolchainResponse> {        let toolchain = this            .update(&mut cx, |this, cx| {                let language_name = LanguageName::from_proto(envelope.payload.language_name);                let path = PathBuf::from(envelope.payload.abs_path);                this.resolve_toolchain(path, language_name, cx)            })            .await;        let response = match toolchain {            Ok(toolchain) => {                let toolchain = proto::Toolchain {                    name: toolchain.name.to_string(),                    path: toolchain.path.to_string(),                    raw_json: toolchain.as_json.to_string(),                };                ResolveResponsePayload::Toolchain(toolchain)            }            Err(e) => ResolveResponsePayload::Error(e.to_string()),        };        Ok(ResolveToolchainResponse {            response: Some(response),        })    }     pub fn as_language_toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> {        match &self.mode {            ToolchainStoreInner::Local(local) => Arc::new(LocalStore(local.downgrade())),            ToolchainStoreInner::Remote(remote) => Arc::new(RemoteStore(remote.downgrade())),        }    }    pub fn as_local_store(&self) -> Option<&Entity<LocalToolchainStore>> {        match &self.mode {            ToolchainStoreInner::Local(local) => Some(local),            ToolchainStoreInner::Remote(_) => None,        }    }} pub struct LocalToolchainStore {    languages: Arc<LanguageRegistry>,    worktree_store: Entity<WorktreeStore>,    project_environment: Entity<ProjectEnvironment>,    active_toolchains: BTreeMap<(WorktreeId, LanguageName), BTreeMap<Arc<RelPath>, Toolchain>>,    manifest_tree: Entity<ManifestTree>,} #[async_trait(?Send)]impl language::LocalLanguageToolchainStore for LocalStore {    fn active_toolchain(        self: Arc<Self>,        worktree_id: WorktreeId,        path: &Arc<RelPath>,        language_name: LanguageName,        cx: &mut AsyncApp,    ) -> Option<Toolchain> {        self.0            .update(cx, |this, _| {                this.active_toolchain(worktree_id, path, language_name)            })            .ok()?    }} #[async_trait(?Send)]impl language::LanguageToolchainStore for RemoteStore {    async fn active_toolchain(        self: Arc<Self>,        worktree_id: WorktreeId,        path: Arc<RelPath>,        language_name: LanguageName,        cx: &mut AsyncApp,    ) -> Option<Toolchain> {        self.0            .update(cx, |this, cx| {                this.active_toolchain(ProjectPath { worktree_id, path }, language_name, cx)            })            .ok()?            .await    }} pub struct EmptyToolchainStore;impl language::LocalLanguageToolchainStore for EmptyToolchainStore {    fn active_toolchain(        self: Arc<Self>,        _: WorktreeId,        _: &Arc<RelPath>,        _: LanguageName,        _: &mut AsyncApp,    ) -> Option<Toolchain> {        None    }}pub(crate) struct LocalStore(WeakEntity<LocalToolchainStore>);struct RemoteStore(WeakEntity<RemoteToolchainStore>); #[derive(Clone)]pub enum ToolchainStoreEvent {    ToolchainActivated,    CustomToolchainsModified,} impl EventEmitter<ToolchainStoreEvent> for LocalToolchainStore {} impl LocalToolchainStore {    pub(crate) fn activate_toolchain(        &self,        path: ProjectPath,        toolchain: Toolchain,        cx: &mut Context<Self>,    ) -> Task<Option<()>> {        cx.spawn(async move |this, cx| {            this.update(cx, |this, cx| {                this.active_toolchains                    .entry((path.worktree_id, toolchain.language_name.clone()))                    .or_default()                    .insert(path.path, toolchain.clone());                cx.emit(ToolchainStoreEvent::ToolchainActivated);            })            .ok();            Some(())        })    }    pub(crate) fn list_toolchains(        &mut self,

Lines 1–500 of 779.