mirror of
https://github.com/zed-industries/zed.git
synced 2025-02-09 12:01:58 +00:00
Potentially fixes #21404 This is a speculative fix, as while I was trying to repro this issue I've noticed that introducing artificial delays in ToolchainLister::list could impact apps responsiveness. These delays were essentially there to stimulate PET taking a while to find venvs. Release Notes: - Improved app responsiveness in environments with multiple Python virtual environments
443 lines
14 KiB
Rust
443 lines
14 KiB
Rust
use std::{str::FromStr, sync::Arc};
|
|
|
|
use anyhow::{bail, Result};
|
|
|
|
use async_trait::async_trait;
|
|
use collections::BTreeMap;
|
|
use gpui::{
|
|
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,
|
|
WeakModel,
|
|
};
|
|
use language::{LanguageName, LanguageRegistry, LanguageToolchainStore, Toolchain, ToolchainList};
|
|
use rpc::{proto, AnyProtoClient, TypedEnvelope};
|
|
use settings::WorktreeId;
|
|
use util::ResultExt as _;
|
|
|
|
use crate::{worktree_store::WorktreeStore, ProjectEnvironment};
|
|
|
|
pub struct ToolchainStore(ToolchainStoreInner);
|
|
enum ToolchainStoreInner {
|
|
Local(Model<LocalToolchainStore>, #[allow(dead_code)] Subscription),
|
|
Remote(Model<RemoteToolchainStore>),
|
|
}
|
|
|
|
impl EventEmitter<ToolchainStoreEvent> for ToolchainStore {}
|
|
impl ToolchainStore {
|
|
pub fn init(client: &AnyProtoClient) {
|
|
client.add_model_request_handler(Self::handle_activate_toolchain);
|
|
client.add_model_request_handler(Self::handle_list_toolchains);
|
|
client.add_model_request_handler(Self::handle_active_toolchain);
|
|
}
|
|
|
|
pub fn local(
|
|
languages: Arc<LanguageRegistry>,
|
|
worktree_store: Model<WorktreeStore>,
|
|
project_environment: Model<ProjectEnvironment>,
|
|
cx: &mut ModelContext<Self>,
|
|
) -> Self {
|
|
let model = cx.new_model(|_| LocalToolchainStore {
|
|
languages,
|
|
worktree_store,
|
|
project_environment,
|
|
active_toolchains: Default::default(),
|
|
});
|
|
let subscription = cx.subscribe(&model, |_, _, e: &ToolchainStoreEvent, cx| {
|
|
cx.emit(e.clone())
|
|
});
|
|
Self(ToolchainStoreInner::Local(model, subscription))
|
|
}
|
|
pub(super) fn remote(project_id: u64, client: AnyProtoClient, cx: &mut AppContext) -> Self {
|
|
Self(ToolchainStoreInner::Remote(
|
|
cx.new_model(|_| RemoteToolchainStore { client, project_id }),
|
|
))
|
|
}
|
|
pub(crate) fn activate_toolchain(
|
|
&self,
|
|
worktree_id: WorktreeId,
|
|
toolchain: Toolchain,
|
|
cx: &mut AppContext,
|
|
) -> Task<Option<()>> {
|
|
match &self.0 {
|
|
ToolchainStoreInner::Local(local, _) => local.update(cx, |this, cx| {
|
|
this.activate_toolchain(worktree_id, toolchain, cx)
|
|
}),
|
|
ToolchainStoreInner::Remote(remote) => {
|
|
remote
|
|
.read(cx)
|
|
.activate_toolchain(worktree_id, toolchain, cx)
|
|
}
|
|
}
|
|
}
|
|
pub(crate) fn list_toolchains(
|
|
&self,
|
|
worktree_id: WorktreeId,
|
|
language_name: LanguageName,
|
|
cx: &AppContext,
|
|
) -> Task<Option<ToolchainList>> {
|
|
match &self.0 {
|
|
ToolchainStoreInner::Local(local, _) => {
|
|
local
|
|
.read(cx)
|
|
.list_toolchains(worktree_id, language_name, cx)
|
|
}
|
|
ToolchainStoreInner::Remote(remote) => {
|
|
remote
|
|
.read(cx)
|
|
.list_toolchains(worktree_id, language_name, cx)
|
|
}
|
|
}
|
|
}
|
|
pub(crate) fn active_toolchain(
|
|
&self,
|
|
worktree_id: WorktreeId,
|
|
language_name: LanguageName,
|
|
cx: &AppContext,
|
|
) -> Task<Option<Toolchain>> {
|
|
match &self.0 {
|
|
ToolchainStoreInner::Local(local, _) => {
|
|
local
|
|
.read(cx)
|
|
.active_toolchain(worktree_id, language_name, cx)
|
|
}
|
|
ToolchainStoreInner::Remote(remote) => {
|
|
remote
|
|
.read(cx)
|
|
.active_toolchain(worktree_id, language_name, cx)
|
|
}
|
|
}
|
|
}
|
|
async fn handle_activate_toolchain(
|
|
this: Model<Self>,
|
|
envelope: TypedEnvelope<proto::ActivateToolchain>,
|
|
mut cx: AsyncAppContext,
|
|
) -> 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(),
|
|
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);
|
|
Ok(this.activate_toolchain(worktree_id, toolchain, cx))
|
|
})??
|
|
.await;
|
|
Ok(proto::Ack {})
|
|
}
|
|
async fn handle_active_toolchain(
|
|
this: Model<Self>,
|
|
envelope: TypedEnvelope<proto::ActiveToolchain>,
|
|
mut cx: AsyncAppContext,
|
|
) -> Result<proto::ActiveToolchainResponse> {
|
|
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(worktree_id, language_name, cx)
|
|
})?
|
|
.await;
|
|
|
|
Ok(proto::ActiveToolchainResponse {
|
|
toolchain: toolchain.map(|toolchain| proto::Toolchain {
|
|
name: toolchain.name.into(),
|
|
path: toolchain.path.into(),
|
|
raw_json: toolchain.as_json.to_string(),
|
|
}),
|
|
})
|
|
}
|
|
|
|
async fn handle_list_toolchains(
|
|
this: Model<Self>,
|
|
envelope: TypedEnvelope<proto::ListToolchains>,
|
|
mut cx: AsyncAppContext,
|
|
) -> 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);
|
|
this.list_toolchains(worktree_id, language_name, cx)
|
|
})?
|
|
.await;
|
|
let has_values = toolchains.is_some();
|
|
let groups = if let Some(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 = if let Some(toolchains) = toolchains {
|
|
toolchains
|
|
.toolchains
|
|
.into_iter()
|
|
.map(|toolchain| proto::Toolchain {
|
|
name: toolchain.name.to_string(),
|
|
path: toolchain.path.to_string(),
|
|
raw_json: toolchain.as_json.to_string(),
|
|
})
|
|
.collect::<Vec<_>>()
|
|
} else {
|
|
vec![]
|
|
};
|
|
|
|
Ok(proto::ListToolchainsResponse {
|
|
has_values,
|
|
toolchains,
|
|
groups,
|
|
})
|
|
}
|
|
pub fn as_language_toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> {
|
|
match &self.0 {
|
|
ToolchainStoreInner::Local(local, _) => Arc::new(LocalStore(local.downgrade())),
|
|
ToolchainStoreInner::Remote(remote) => Arc::new(RemoteStore(remote.downgrade())),
|
|
}
|
|
}
|
|
}
|
|
|
|
struct LocalToolchainStore {
|
|
languages: Arc<LanguageRegistry>,
|
|
worktree_store: Model<WorktreeStore>,
|
|
project_environment: Model<ProjectEnvironment>,
|
|
active_toolchains: BTreeMap<(WorktreeId, LanguageName), Toolchain>,
|
|
}
|
|
|
|
#[async_trait(?Send)]
|
|
impl language::LanguageToolchainStore for LocalStore {
|
|
async fn active_toolchain(
|
|
self: Arc<Self>,
|
|
worktree_id: WorktreeId,
|
|
language_name: LanguageName,
|
|
cx: &mut AsyncAppContext,
|
|
) -> Option<Toolchain> {
|
|
self.0
|
|
.update(cx, |this, cx| {
|
|
this.active_toolchain(worktree_id, language_name, cx)
|
|
})
|
|
.ok()?
|
|
.await
|
|
}
|
|
}
|
|
|
|
#[async_trait(?Send)]
|
|
impl language::LanguageToolchainStore for RemoteStore {
|
|
async fn active_toolchain(
|
|
self: Arc<Self>,
|
|
worktree_id: WorktreeId,
|
|
language_name: LanguageName,
|
|
cx: &mut AsyncAppContext,
|
|
) -> Option<Toolchain> {
|
|
self.0
|
|
.update(cx, |this, cx| {
|
|
this.active_toolchain(worktree_id, language_name, cx)
|
|
})
|
|
.ok()?
|
|
.await
|
|
}
|
|
}
|
|
|
|
pub(crate) struct EmptyToolchainStore;
|
|
#[async_trait(?Send)]
|
|
impl language::LanguageToolchainStore for EmptyToolchainStore {
|
|
async fn active_toolchain(
|
|
self: Arc<Self>,
|
|
_: WorktreeId,
|
|
_: LanguageName,
|
|
_: &mut AsyncAppContext,
|
|
) -> Option<Toolchain> {
|
|
None
|
|
}
|
|
}
|
|
struct LocalStore(WeakModel<LocalToolchainStore>);
|
|
struct RemoteStore(WeakModel<RemoteToolchainStore>);
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) enum ToolchainStoreEvent {
|
|
ToolchainActivated,
|
|
}
|
|
|
|
impl EventEmitter<ToolchainStoreEvent> for LocalToolchainStore {}
|
|
|
|
impl LocalToolchainStore {
|
|
pub(crate) fn activate_toolchain(
|
|
&self,
|
|
worktree_id: WorktreeId,
|
|
toolchain: Toolchain,
|
|
cx: &mut ModelContext<Self>,
|
|
) -> Task<Option<()>> {
|
|
cx.spawn(move |this, mut cx| async move {
|
|
this.update(&mut cx, |this, cx| {
|
|
this.active_toolchains.insert(
|
|
(worktree_id, toolchain.language_name.clone()),
|
|
toolchain.clone(),
|
|
);
|
|
cx.emit(ToolchainStoreEvent::ToolchainActivated);
|
|
})
|
|
.ok();
|
|
Some(())
|
|
})
|
|
}
|
|
pub(crate) fn list_toolchains(
|
|
&self,
|
|
worktree_id: WorktreeId,
|
|
language_name: LanguageName,
|
|
cx: &AppContext,
|
|
) -> Task<Option<ToolchainList>> {
|
|
let registry = self.languages.clone();
|
|
let Some(root) = self
|
|
.worktree_store
|
|
.read(cx)
|
|
.worktree_for_id(worktree_id, cx)
|
|
.map(|worktree| worktree.read(cx).abs_path())
|
|
else {
|
|
return Task::ready(None);
|
|
};
|
|
|
|
let environment = self.project_environment.clone();
|
|
cx.spawn(|mut cx| async move {
|
|
let project_env = environment
|
|
.update(&mut cx, |environment, cx| {
|
|
environment.get_environment(Some(worktree_id), Some(root.clone()), cx)
|
|
})
|
|
.ok()?
|
|
.await;
|
|
|
|
cx.background_executor()
|
|
.spawn(async move {
|
|
let language = registry.language_for_name(&language_name.0).await.ok()?;
|
|
let toolchains = language.toolchain_lister()?;
|
|
Some(toolchains.list(root.to_path_buf(), project_env).await)
|
|
})
|
|
.await
|
|
})
|
|
}
|
|
pub(crate) fn active_toolchain(
|
|
&self,
|
|
worktree_id: WorktreeId,
|
|
language_name: LanguageName,
|
|
_: &AppContext,
|
|
) -> Task<Option<Toolchain>> {
|
|
Task::ready(
|
|
self.active_toolchains
|
|
.get(&(worktree_id, language_name))
|
|
.cloned(),
|
|
)
|
|
}
|
|
}
|
|
struct RemoteToolchainStore {
|
|
client: AnyProtoClient,
|
|
project_id: u64,
|
|
}
|
|
|
|
impl RemoteToolchainStore {
|
|
pub(crate) fn activate_toolchain(
|
|
&self,
|
|
worktree_id: WorktreeId,
|
|
toolchain: Toolchain,
|
|
cx: &AppContext,
|
|
) -> Task<Option<()>> {
|
|
let project_id = self.project_id;
|
|
let client = self.client.clone();
|
|
cx.spawn(move |_| async move {
|
|
let _ = client
|
|
.request(proto::ActivateToolchain {
|
|
project_id,
|
|
worktree_id: worktree_id.to_proto(),
|
|
language_name: toolchain.language_name.into(),
|
|
toolchain: Some(proto::Toolchain {
|
|
name: toolchain.name.into(),
|
|
path: toolchain.path.into(),
|
|
raw_json: toolchain.as_json.to_string(),
|
|
}),
|
|
})
|
|
.await
|
|
.log_err()?;
|
|
Some(())
|
|
})
|
|
}
|
|
|
|
pub(crate) fn list_toolchains(
|
|
&self,
|
|
worktree_id: WorktreeId,
|
|
language_name: LanguageName,
|
|
cx: &AppContext,
|
|
) -> Task<Option<ToolchainList>> {
|
|
let project_id = self.project_id;
|
|
let client = self.client.clone();
|
|
cx.spawn(move |_| async move {
|
|
let response = client
|
|
.request(proto::ListToolchains {
|
|
project_id,
|
|
worktree_id: worktree_id.to_proto(),
|
|
language_name: language_name.clone().into(),
|
|
})
|
|
.await
|
|
.log_err()?;
|
|
if !response.has_values {
|
|
return None;
|
|
}
|
|
let toolchains = response
|
|
.toolchains
|
|
.into_iter()
|
|
.filter_map(|toolchain| {
|
|
Some(Toolchain {
|
|
language_name: language_name.clone(),
|
|
name: toolchain.name.into(),
|
|
path: toolchain.path.into(),
|
|
as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
|
|
})
|
|
})
|
|
.collect();
|
|
let groups = response
|
|
.groups
|
|
.into_iter()
|
|
.filter_map(|group| {
|
|
Some((usize::try_from(group.start_index).ok()?, group.name.into()))
|
|
})
|
|
.collect();
|
|
Some(ToolchainList {
|
|
toolchains,
|
|
default: None,
|
|
groups,
|
|
})
|
|
})
|
|
}
|
|
pub(crate) fn active_toolchain(
|
|
&self,
|
|
worktree_id: WorktreeId,
|
|
language_name: LanguageName,
|
|
cx: &AppContext,
|
|
) -> Task<Option<Toolchain>> {
|
|
let project_id = self.project_id;
|
|
let client = self.client.clone();
|
|
cx.spawn(move |_| async move {
|
|
let response = client
|
|
.request(proto::ActiveToolchain {
|
|
project_id,
|
|
worktree_id: worktree_id.to_proto(),
|
|
language_name: language_name.clone().into(),
|
|
})
|
|
.await
|
|
.log_err()?;
|
|
|
|
response.toolchain.and_then(|toolchain| {
|
|
Some(Toolchain {
|
|
language_name: language_name.clone(),
|
|
name: toolchain.name.into(),
|
|
path: toolchain.path.into(),
|
|
as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
|
|
})
|
|
})
|
|
})
|
|
}
|
|
}
|