Add language toolchains (#19576)
This PR adds support for selecting toolchains for a given language (e.g. Rust toolchains or Python virtual environments) with support for SSH projects provided out of the box. For Python we piggy-back off of [PET](https://github.com/microsoft/python-environment-tools), a library maintained by Microsoft. Closes #16421 Closes #7646 Release Notes: - Added toolchain selector to the status bar (with initial support for Python virtual environments)
This commit is contained in:
+102
-33
@@ -7,10 +7,11 @@ use crate::{
|
||||
prettier_store::{self, PrettierStore, PrettierStoreEvent},
|
||||
project_settings::{LspSettings, ProjectSettings},
|
||||
relativize_path, resolve_path,
|
||||
toolchain_store::{EmptyToolchainStore, ToolchainStoreEvent},
|
||||
worktree_store::{WorktreeStore, WorktreeStoreEvent},
|
||||
yarn::YarnPathStore,
|
||||
CodeAction, Completion, CoreCompletion, Hover, InlayHint, Item as _, ProjectPath,
|
||||
ProjectTransaction, ResolveState, Symbol,
|
||||
ProjectTransaction, ResolveState, Symbol, ToolchainStore,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use async_trait::async_trait;
|
||||
@@ -36,9 +37,9 @@ use language::{
|
||||
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
||||
range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel, Diagnostic,
|
||||
DiagnosticEntry, DiagnosticSet, Diff, Documentation, File as _, Language, LanguageName,
|
||||
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName, LocalFile, LspAdapter,
|
||||
LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
|
||||
Unclipped,
|
||||
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName, LanguageToolchainStore,
|
||||
LocalFile, LspAdapter, LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset,
|
||||
ToPointUtf16, Transaction, Unclipped,
|
||||
};
|
||||
use lsp::{
|
||||
CodeActionKind, CompletionContext, DiagnosticSeverity, DiagnosticTag,
|
||||
@@ -707,12 +708,13 @@ pub struct LspStore {
|
||||
nonce: u128,
|
||||
buffer_store: Model<BufferStore>,
|
||||
worktree_store: Model<WorktreeStore>,
|
||||
toolchain_store: Option<Model<ToolchainStore>>,
|
||||
buffer_snapshots: HashMap<BufferId, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
|
||||
pub languages: Arc<LanguageRegistry>,
|
||||
language_server_ids: HashMap<(WorktreeId, LanguageServerName), LanguageServerId>,
|
||||
pub language_server_statuses: BTreeMap<LanguageServerId, LanguageServerStatus>,
|
||||
active_entry: Option<ProjectEntryId>,
|
||||
_maintain_workspace_config: Task<Result<()>>,
|
||||
_maintain_workspace_config: (Task<Result<()>>, watch::Sender<()>),
|
||||
_maintain_buffer_languages: Task<()>,
|
||||
next_diagnostic_group_id: usize,
|
||||
diagnostic_summaries:
|
||||
@@ -871,6 +873,7 @@ impl LspStore {
|
||||
buffer_store: Model<BufferStore>,
|
||||
worktree_store: Model<WorktreeStore>,
|
||||
prettier_store: Model<PrettierStore>,
|
||||
toolchain_store: Model<ToolchainStore>,
|
||||
environment: Model<ProjectEnvironment>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
@@ -884,9 +887,15 @@ impl LspStore {
|
||||
.detach();
|
||||
cx.subscribe(&prettier_store, Self::on_prettier_store_event)
|
||||
.detach();
|
||||
cx.subscribe(&toolchain_store, Self::on_toolchain_store_event)
|
||||
.detach();
|
||||
cx.observe_global::<SettingsStore>(Self::on_settings_changed)
|
||||
.detach();
|
||||
|
||||
let _maintain_workspace_config = {
|
||||
let (sender, receiver) = watch::channel();
|
||||
(Self::maintain_workspace_config(receiver, cx), sender)
|
||||
};
|
||||
Self {
|
||||
mode: LspStoreMode::Local(LocalLspStore {
|
||||
supplementary_language_servers: Default::default(),
|
||||
@@ -909,6 +918,7 @@ impl LspStore {
|
||||
downstream_client: None,
|
||||
buffer_store,
|
||||
worktree_store,
|
||||
toolchain_store: Some(toolchain_store),
|
||||
languages: languages.clone(),
|
||||
language_server_ids: Default::default(),
|
||||
language_server_statuses: Default::default(),
|
||||
@@ -919,7 +929,7 @@ impl LspStore {
|
||||
diagnostics: Default::default(),
|
||||
active_entry: None,
|
||||
|
||||
_maintain_workspace_config: Self::maintain_workspace_config(cx),
|
||||
_maintain_workspace_config,
|
||||
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
|
||||
}
|
||||
}
|
||||
@@ -942,9 +952,10 @@ impl LspStore {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_remote(
|
||||
pub(super) fn new_remote(
|
||||
buffer_store: Model<BufferStore>,
|
||||
worktree_store: Model<WorktreeStore>,
|
||||
toolchain_store: Option<Model<ToolchainStore>>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
upstream_client: AnyProtoClient,
|
||||
project_id: u64,
|
||||
@@ -954,7 +965,10 @@ impl LspStore {
|
||||
.detach();
|
||||
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
|
||||
.detach();
|
||||
|
||||
let _maintain_workspace_config = {
|
||||
let (sender, receiver) = watch::channel();
|
||||
(Self::maintain_workspace_config(receiver, cx), sender)
|
||||
};
|
||||
Self {
|
||||
mode: LspStoreMode::Remote(RemoteLspStore {
|
||||
upstream_client: Some(upstream_client),
|
||||
@@ -972,7 +986,8 @@ impl LspStore {
|
||||
diagnostic_summaries: Default::default(),
|
||||
diagnostics: Default::default(),
|
||||
active_entry: None,
|
||||
_maintain_workspace_config: Self::maintain_workspace_config(cx),
|
||||
toolchain_store,
|
||||
_maintain_workspace_config,
|
||||
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
|
||||
}
|
||||
}
|
||||
@@ -1063,6 +1078,22 @@ impl LspStore {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_toolchain_store_event(
|
||||
&mut self,
|
||||
_: Model<ToolchainStore>,
|
||||
event: &ToolchainStoreEvent,
|
||||
_: &mut ModelContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
ToolchainStoreEvent::ToolchainActivated { .. } => {
|
||||
self.request_workspace_config_refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn request_workspace_config_refresh(&mut self) {
|
||||
*self._maintain_workspace_config.1.borrow_mut() = ();
|
||||
}
|
||||
// todo!
|
||||
pub fn prettier_store(&self) -> Option<Model<PrettierStore>> {
|
||||
self.as_local().map(|local| local.prettier_store.clone())
|
||||
@@ -3029,17 +3060,13 @@ impl LspStore {
|
||||
None
|
||||
}
|
||||
|
||||
fn maintain_workspace_config(cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
let (mut settings_changed_tx, mut settings_changed_rx) = watch::channel();
|
||||
let _ = postage::stream::Stream::try_recv(&mut settings_changed_rx);
|
||||
|
||||
let settings_observation = cx.observe_global::<SettingsStore>(move |_, _| {
|
||||
*settings_changed_tx.borrow_mut() = ();
|
||||
});
|
||||
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
while let Some(()) = settings_changed_rx.next().await {
|
||||
let servers = this.update(&mut cx, |this, cx| {
|
||||
pub(crate) async fn refresh_workspace_configurations(
|
||||
this: &WeakModel<Self>,
|
||||
mut cx: AsyncAppContext,
|
||||
) {
|
||||
maybe!(async move {
|
||||
let servers = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.language_server_ids
|
||||
.iter()
|
||||
.filter_map(|((worktree_id, _), server_id)| {
|
||||
@@ -3061,17 +3088,52 @@ impl LspStore {
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})?;
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
for (adapter, server, delegate) in servers {
|
||||
let settings = adapter.workspace_configuration(&delegate, &mut cx).await?;
|
||||
let toolchain_store = this
|
||||
.update(&mut cx, |this, cx| this.toolchain_store(cx))
|
||||
.ok()?;
|
||||
for (adapter, server, delegate) in servers {
|
||||
let settings = adapter
|
||||
.workspace_configuration(&delegate, toolchain_store.clone(), &mut cx)
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
server
|
||||
.notify::<lsp::notification::DidChangeConfiguration>(
|
||||
lsp::DidChangeConfigurationParams { settings },
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
server
|
||||
.notify::<lsp::notification::DidChangeConfiguration>(
|
||||
lsp::DidChangeConfigurationParams { settings },
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
Some(())
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
fn toolchain_store(&self, cx: &AppContext) -> Arc<dyn LanguageToolchainStore> {
|
||||
if let Some(toolchain_store) = self.toolchain_store.as_ref() {
|
||||
toolchain_store.read(cx).as_language_toolchain_store()
|
||||
} else {
|
||||
Arc::new(EmptyToolchainStore)
|
||||
}
|
||||
}
|
||||
fn maintain_workspace_config(
|
||||
external_refresh_requests: watch::Receiver<()>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let (mut settings_changed_tx, mut settings_changed_rx) = watch::channel();
|
||||
let _ = postage::stream::Stream::try_recv(&mut settings_changed_rx);
|
||||
|
||||
let settings_observation = cx.observe_global::<SettingsStore>(move |_, _| {
|
||||
*settings_changed_tx.borrow_mut() = ();
|
||||
});
|
||||
|
||||
let mut joint_future =
|
||||
futures::stream::select(settings_changed_rx, external_refresh_requests);
|
||||
cx.spawn(move |this, cx| async move {
|
||||
while let Some(()) = joint_future.next().await {
|
||||
Self::refresh_workspace_configurations(&this, cx.clone()).await;
|
||||
}
|
||||
|
||||
drop(settings_observation);
|
||||
@@ -5517,6 +5579,9 @@ impl LspStore {
|
||||
let delegate = delegate.clone();
|
||||
let adapter = adapter.clone();
|
||||
let this = this.clone();
|
||||
let toolchains = this
|
||||
.update(&mut cx, |this, cx| this.toolchain_store(cx))
|
||||
.ok()?;
|
||||
let mut cx = cx.clone();
|
||||
async move {
|
||||
let language_server = pending_server.await?;
|
||||
@@ -5524,7 +5589,7 @@ impl LspStore {
|
||||
let workspace_config = adapter
|
||||
.adapter
|
||||
.clone()
|
||||
.workspace_configuration(&delegate, &mut cx)
|
||||
.workspace_configuration(&delegate, toolchains.clone(), &mut cx)
|
||||
.await?;
|
||||
|
||||
let mut initialization_options = adapter
|
||||
@@ -5864,17 +5929,21 @@ impl LspStore {
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
language_server
|
||||
.on_request::<lsp::request::WorkspaceConfiguration, _, _>({
|
||||
let adapter = adapter.adapter.clone();
|
||||
let delegate = delegate.clone();
|
||||
let this = this.clone();
|
||||
move |params, mut cx| {
|
||||
let adapter = adapter.clone();
|
||||
let delegate = delegate.clone();
|
||||
let this = this.clone();
|
||||
async move {
|
||||
let workspace_config =
|
||||
adapter.workspace_configuration(&delegate, &mut cx).await?;
|
||||
let toolchains =
|
||||
this.update(&mut cx, |this, cx| this.toolchain_store(cx))?;
|
||||
let workspace_config = adapter
|
||||
.workspace_configuration(&delegate, toolchains, &mut cx)
|
||||
.await?;
|
||||
Ok(params
|
||||
.items
|
||||
.into_iter()
|
||||
|
||||
@@ -11,6 +11,7 @@ pub mod search;
|
||||
mod task_inventory;
|
||||
pub mod task_store;
|
||||
pub mod terminals;
|
||||
pub mod toolchain_store;
|
||||
pub mod worktree_store;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -44,8 +45,8 @@ use itertools::Itertools;
|
||||
use language::{
|
||||
language_settings::InlayHintKind, proto::split_operations, Buffer, BufferEvent,
|
||||
CachedLspAdapter, Capability, CodeLabel, DiagnosticEntry, Documentation, File as _, Language,
|
||||
LanguageRegistry, LanguageServerName, PointUtf16, ToOffset, ToPointUtf16, Transaction,
|
||||
Unclipped,
|
||||
LanguageName, LanguageRegistry, LanguageServerName, PointUtf16, ToOffset, ToPointUtf16,
|
||||
Toolchain, ToolchainList, Transaction, Unclipped,
|
||||
};
|
||||
use lsp::{
|
||||
CompletionContext, CompletionItemKind, DocumentHighlightKind, LanguageServer, LanguageServerId,
|
||||
@@ -101,7 +102,7 @@ pub use lsp_store::{
|
||||
LanguageServerStatus, LanguageServerToQuery, LspStore, LspStoreEvent,
|
||||
SERVER_PROGRESS_THROTTLE_TIMEOUT,
|
||||
};
|
||||
|
||||
pub use toolchain_store::ToolchainStore;
|
||||
const MAX_PROJECT_SEARCH_HISTORY_SIZE: usize = 500;
|
||||
const MAX_SEARCH_RESULT_FILES: usize = 5_000;
|
||||
const MAX_SEARCH_RESULT_RANGES: usize = 10_000;
|
||||
@@ -158,6 +159,7 @@ pub struct Project {
|
||||
snippets: Model<SnippetProvider>,
|
||||
environment: Model<ProjectEnvironment>,
|
||||
settings_observer: Model<SettingsObserver>,
|
||||
toolchain_store: Option<Model<ToolchainStore>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -579,6 +581,7 @@ impl Project {
|
||||
LspStore::init(&client);
|
||||
SettingsObserver::init(&client);
|
||||
TaskStore::init(Some(&client));
|
||||
ToolchainStore::init(&client);
|
||||
}
|
||||
|
||||
pub fn local(
|
||||
@@ -635,12 +638,15 @@ impl Project {
|
||||
});
|
||||
cx.subscribe(&settings_observer, Self::on_settings_observer_event)
|
||||
.detach();
|
||||
|
||||
let toolchain_store = cx.new_model(|cx| {
|
||||
ToolchainStore::local(languages.clone(), worktree_store.clone(), cx)
|
||||
});
|
||||
let lsp_store = cx.new_model(|cx| {
|
||||
LspStore::new_local(
|
||||
buffer_store.clone(),
|
||||
worktree_store.clone(),
|
||||
prettier_store.clone(),
|
||||
toolchain_store.clone(),
|
||||
environment.clone(),
|
||||
languages.clone(),
|
||||
client.http_client(),
|
||||
@@ -681,6 +687,8 @@ impl Project {
|
||||
|
||||
search_included_history: Self::new_search_history(),
|
||||
search_excluded_history: Self::new_search_history(),
|
||||
|
||||
toolchain_store: Some(toolchain_store),
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -737,10 +745,14 @@ impl Project {
|
||||
.detach();
|
||||
|
||||
let environment = ProjectEnvironment::new(&worktree_store, None, cx);
|
||||
let toolchain_store = Some(cx.new_model(|cx| {
|
||||
ToolchainStore::remote(SSH_PROJECT_ID, ssh.read(cx).proto_client(), cx)
|
||||
}));
|
||||
let lsp_store = cx.new_model(|cx| {
|
||||
LspStore::new_remote(
|
||||
buffer_store.clone(),
|
||||
worktree_store.clone(),
|
||||
toolchain_store.clone(),
|
||||
languages.clone(),
|
||||
ssh_proto.clone(),
|
||||
SSH_PROJECT_ID,
|
||||
@@ -798,6 +810,8 @@ impl Project {
|
||||
|
||||
search_included_history: Self::new_search_history(),
|
||||
search_excluded_history: Self::new_search_history(),
|
||||
|
||||
toolchain_store,
|
||||
};
|
||||
|
||||
let ssh = ssh.read(cx);
|
||||
@@ -818,6 +832,7 @@ impl Project {
|
||||
LspStore::init(&ssh_proto);
|
||||
SettingsObserver::init(&ssh_proto);
|
||||
TaskStore::init(Some(&ssh_proto));
|
||||
ToolchainStore::init(&ssh_proto);
|
||||
|
||||
this
|
||||
})
|
||||
@@ -905,6 +920,7 @@ impl Project {
|
||||
let mut lsp_store = LspStore::new_remote(
|
||||
buffer_store.clone(),
|
||||
worktree_store.clone(),
|
||||
None,
|
||||
languages.clone(),
|
||||
client.clone().into(),
|
||||
remote_id,
|
||||
@@ -993,6 +1009,7 @@ impl Project {
|
||||
search_excluded_history: Self::new_search_history(),
|
||||
environment: ProjectEnvironment::new(&worktree_store, None, cx),
|
||||
remotely_created_models: Arc::new(Mutex::new(RemotelyCreatedModels::default())),
|
||||
toolchain_store: None,
|
||||
};
|
||||
this.set_role(role, cx);
|
||||
for worktree in worktrees {
|
||||
@@ -2346,6 +2363,46 @@ impl Project {
|
||||
.map_err(|e| anyhow!(e))
|
||||
}
|
||||
|
||||
pub fn available_toolchains(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
language_name: LanguageName,
|
||||
cx: &AppContext,
|
||||
) -> Task<Option<ToolchainList>> {
|
||||
if let Some(toolchain_store) = self.toolchain_store.as_ref() {
|
||||
toolchain_store
|
||||
.read(cx)
|
||||
.list_toolchains(worktree_id, language_name, cx)
|
||||
} else {
|
||||
Task::ready(None)
|
||||
}
|
||||
}
|
||||
pub fn activate_toolchain(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
toolchain: Toolchain,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Option<()>> {
|
||||
let Some(toolchain_store) = self.toolchain_store.clone() else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
toolchain_store.update(cx, |this, cx| {
|
||||
this.activate_toolchain(worktree_id, toolchain, cx)
|
||||
})
|
||||
}
|
||||
pub fn active_toolchain(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
language_name: LanguageName,
|
||||
cx: &AppContext,
|
||||
) -> Task<Option<Toolchain>> {
|
||||
let Some(toolchain_store) = self.toolchain_store.clone() else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
toolchain_store
|
||||
.read(cx)
|
||||
.active_toolchain(worktree_id, language_name, cx)
|
||||
}
|
||||
pub fn language_server_statuses<'a>(
|
||||
&'a self,
|
||||
cx: &'a AppContext,
|
||||
|
||||
@@ -0,0 +1,416 @@
|
||||
use std::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;
|
||||
|
||||
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>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let model = cx.new_model(|_| LocalToolchainStore {
|
||||
languages,
|
||||
worktree_store,
|
||||
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(),
|
||||
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(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
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(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
Ok(proto::ListToolchainsResponse {
|
||||
has_values,
|
||||
toolchains,
|
||||
groups,
|
||||
})
|
||||
}
|
||||
pub(crate) 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>,
|
||||
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);
|
||||
};
|
||||
cx.spawn(|_| async move {
|
||||
let language = registry.language_for_name(&language_name.0).await.ok()?;
|
||||
let toolchains = language.toolchain_lister()?.list(root.to_path_buf()).await;
|
||||
Some(toolchains)
|
||||
})
|
||||
}
|
||||
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(),
|
||||
}),
|
||||
})
|
||||
.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()
|
||||
.map(|toolchain| Toolchain {
|
||||
language_name: language_name.clone(),
|
||||
name: toolchain.name.into(),
|
||||
path: toolchain.path.into(),
|
||||
})
|
||||
.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.map(|toolchain| Toolchain {
|
||||
language_name: language_name.clone(),
|
||||
name: toolchain.name.into(),
|
||||
path: toolchain.path.into(),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user