lsp: Remove reinstall, update config (#18318)
Release Notes: - Fixed overriding the path of a language server binary for all language servers. `{"lsp":{"<lsp-name>":{"binary":{"path": "_"}}}}` will now work for all language servers including those defined by extensions. - (breaking change) To disable finding lsp adapters in your path, you must now specify `{"lsp":{"<lsp-name>":{"binary":{"ignore_system_version": true}}}}`. Previously this was `{"lsp":{"<lsp-name>":{"binary":{"path_lookup": false}}}}`. Note that this setting still does not apply to extensions. - Removed automatic reinstallation of language servers. (It mostly didn't work) --------- Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
parent
1f54fde4d2
commit
dc48af0ca1
21 changed files with 405 additions and 940 deletions
|
@ -29,7 +29,7 @@ use gpui::{AppContext, AsyncAppContext, Model, SharedString, Task};
|
|||
pub use highlight_map::HighlightMap;
|
||||
use http_client::HttpClient;
|
||||
pub use language_registry::LanguageName;
|
||||
use lsp::{CodeActionKind, LanguageServerBinary};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions};
|
||||
use parking_lot::Mutex;
|
||||
use regex::Regex;
|
||||
use schemars::{
|
||||
|
@ -69,7 +69,7 @@ pub use buffer::*;
|
|||
pub use diagnostic_set::DiagnosticEntry;
|
||||
pub use language_registry::{
|
||||
AvailableLanguage, LanguageNotFound, LanguageQueries, LanguageRegistry,
|
||||
LanguageServerBinaryStatus, PendingLanguageServer, QUERY_FILENAME_PREFIXES,
|
||||
LanguageServerBinaryStatus, QUERY_FILENAME_PREFIXES,
|
||||
};
|
||||
pub use lsp::LanguageServerId;
|
||||
pub use outline::*;
|
||||
|
@ -249,28 +249,17 @@ impl CachedLspAdapter {
|
|||
|
||||
pub async fn get_language_server_command(
|
||||
self: Arc<Self>,
|
||||
container_dir: Option<Arc<Path>>,
|
||||
delegate: Arc<dyn LspAdapterDelegate>,
|
||||
binary_options: LanguageServerBinaryOptions,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let cached_binary = self.cached_binary.lock().await;
|
||||
self.adapter
|
||||
.clone()
|
||||
.get_language_server_command(container_dir, delegate, cached_binary, cx)
|
||||
.get_language_server_command(delegate, binary_options, cached_binary, cx)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn can_be_reinstalled(&self) -> bool {
|
||||
self.adapter.can_be_reinstalled()
|
||||
}
|
||||
|
||||
pub async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
self.adapter.installation_test_binary(container_dir).await
|
||||
}
|
||||
|
||||
pub fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
|
||||
self.adapter.code_action_kinds()
|
||||
}
|
||||
|
@ -322,6 +311,7 @@ pub trait LspAdapterDelegate: Send + Sync {
|
|||
fn worktree_id(&self) -> WorktreeId;
|
||||
fn worktree_root_path(&self) -> &Path;
|
||||
fn update_status(&self, language: LanguageServerName, status: LanguageServerBinaryStatus);
|
||||
async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option<Arc<Path>>;
|
||||
|
||||
async fn which(&self, command: &OsStr) -> Option<PathBuf>;
|
||||
async fn shell_env(&self) -> HashMap<String, String>;
|
||||
|
@ -335,8 +325,8 @@ pub trait LspAdapter: 'static + Send + Sync {
|
|||
|
||||
fn get_language_server_command<'a>(
|
||||
self: Arc<Self>,
|
||||
container_dir: Option<Arc<Path>>,
|
||||
delegate: Arc<dyn LspAdapterDelegate>,
|
||||
binary_options: LanguageServerBinaryOptions,
|
||||
mut cached_binary: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
|
||||
cx: &'a mut AsyncAppContext,
|
||||
) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
|
||||
|
@ -352,30 +342,30 @@ pub trait LspAdapter: 'static + Send + Sync {
|
|||
// We only want to cache when we fall back to the global one,
|
||||
// because we don't want to download and overwrite our global one
|
||||
// for each worktree we might have open.
|
||||
if let Some(binary) = self.check_if_user_installed(delegate.as_ref(), cx).await {
|
||||
log::info!(
|
||||
"found user-installed language server for {}. path: {:?}, arguments: {:?}",
|
||||
self.name().0,
|
||||
binary.path,
|
||||
binary.arguments
|
||||
);
|
||||
return Ok(binary);
|
||||
if binary_options.allow_path_lookup {
|
||||
if let Some(binary) = self.check_if_user_installed(delegate.as_ref(), cx).await {
|
||||
log::info!(
|
||||
"found user-installed language server for {}. path: {:?}, arguments: {:?}",
|
||||
self.name().0,
|
||||
binary.path,
|
||||
binary.arguments
|
||||
);
|
||||
return Ok(binary);
|
||||
}
|
||||
}
|
||||
|
||||
if !binary_options.allow_binary_download {
|
||||
return Err(anyhow!("downloading language servers disabled"));
|
||||
}
|
||||
|
||||
if let Some(cached_binary) = cached_binary.as_ref() {
|
||||
return Ok(cached_binary.clone());
|
||||
}
|
||||
|
||||
let Some(container_dir) = container_dir else {
|
||||
let Some(container_dir) = delegate.language_server_download_dir(&self.name()).await else {
|
||||
anyhow::bail!("cannot download language servers for remotes (yet)")
|
||||
};
|
||||
|
||||
if !container_dir.exists() {
|
||||
smol::fs::create_dir_all(&container_dir)
|
||||
.await
|
||||
.context("failed to create container directory")?;
|
||||
}
|
||||
|
||||
let mut binary = try_fetch_server_binary(self.as_ref(), &delegate, container_dir.to_path_buf(), cx).await;
|
||||
|
||||
if let Err(error) = binary.as_ref() {
|
||||
|
@ -443,21 +433,6 @@ pub trait LspAdapter: 'static + Send + Sync {
|
|||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary>;
|
||||
|
||||
/// Returns `true` if a language server can be reinstalled.
|
||||
///
|
||||
/// If language server initialization fails, a reinstallation will be attempted unless the value returned from this method is `false`.
|
||||
///
|
||||
/// Implementations that rely on software already installed on user's system
|
||||
/// should have [`can_be_reinstalled`](Self::can_be_reinstalled) return `false`.
|
||||
fn can_be_reinstalled(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary>;
|
||||
|
||||
fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
|
||||
|
||||
/// Post-processes completions provided by the language server.
|
||||
|
@ -1711,8 +1686,8 @@ impl LspAdapter for FakeLspAdapter {
|
|||
|
||||
fn get_language_server_command<'a>(
|
||||
self: Arc<Self>,
|
||||
_: Option<Arc<Path>>,
|
||||
_: Arc<dyn LspAdapterDelegate>,
|
||||
_: LanguageServerBinaryOptions,
|
||||
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
|
||||
_: &'a mut AsyncAppContext,
|
||||
) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
|
||||
|
@ -1743,10 +1718,6 @@ impl LspAdapter for FakeLspAdapter {
|
|||
unreachable!();
|
||||
}
|
||||
|
||||
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
|
||||
|
||||
fn disk_based_diagnostic_sources(&self) -> Vec<String> {
|
||||
|
|
|
@ -4,18 +4,17 @@ use crate::{
|
|||
},
|
||||
task_context::ContextProvider,
|
||||
with_parser, CachedLspAdapter, File, Language, LanguageConfig, LanguageId, LanguageMatcher,
|
||||
LanguageServerName, LspAdapter, LspAdapterDelegate, PLAIN_TEXT,
|
||||
LanguageServerName, LspAdapter, PLAIN_TEXT,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
future::Shared,
|
||||
Future,
|
||||
};
|
||||
use globset::GlobSet;
|
||||
use gpui::{AppContext, BackgroundExecutor, Task};
|
||||
use gpui::{AppContext, BackgroundExecutor};
|
||||
use lsp::LanguageServerId;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use postage::watch;
|
||||
|
@ -118,12 +117,6 @@ pub enum LanguageServerBinaryStatus {
|
|||
Failed { error: String },
|
||||
}
|
||||
|
||||
pub struct PendingLanguageServer {
|
||||
pub server_id: LanguageServerId,
|
||||
pub task: Task<Result<(lsp::LanguageServer, Option<serde_json::Value>)>>,
|
||||
pub container_dir: Option<Arc<Path>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AvailableLanguage {
|
||||
id: LanguageId,
|
||||
|
@ -882,123 +875,53 @@ impl LanguageRegistry {
|
|||
self.lsp_binary_status_tx.send(server_name, status);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn create_pending_language_server(
|
||||
self: &Arc<Self>,
|
||||
stderr_capture: Arc<Mutex<Option<String>>>,
|
||||
_language_name_for_tests: LanguageName,
|
||||
adapter: Arc<CachedLspAdapter>,
|
||||
root_path: Arc<Path>,
|
||||
delegate: Arc<dyn LspAdapterDelegate>,
|
||||
project_environment: Shared<Task<Option<HashMap<String, String>>>>,
|
||||
cx: &mut AppContext,
|
||||
) -> Option<PendingLanguageServer> {
|
||||
let server_id = self.state.write().next_language_server_id();
|
||||
log::info!(
|
||||
"attempting to start language server {:?}, path: {root_path:?}, id: {server_id}",
|
||||
adapter.name.0
|
||||
);
|
||||
pub fn next_language_server_id(&self) -> LanguageServerId {
|
||||
self.state.write().next_language_server_id()
|
||||
}
|
||||
|
||||
let container_dir: Option<Arc<Path>> = self
|
||||
.language_server_download_dir
|
||||
pub fn language_server_download_dir(&self, name: &LanguageServerName) -> Option<Arc<Path>> {
|
||||
self.language_server_download_dir
|
||||
.as_ref()
|
||||
.map(|dir| Arc::from(dir.join(adapter.name.0.as_ref())));
|
||||
let root_path = root_path.clone();
|
||||
let this = Arc::downgrade(self);
|
||||
.map(|dir| Arc::from(dir.join(name.0.as_ref())))
|
||||
}
|
||||
|
||||
let task = cx.spawn({
|
||||
let container_dir = container_dir.clone();
|
||||
move |mut cx| async move {
|
||||
let project_environment = project_environment.await;
|
||||
|
||||
let binary_result = adapter
|
||||
.clone()
|
||||
.get_language_server_command(container_dir, delegate.clone(), &mut cx)
|
||||
.await;
|
||||
|
||||
delegate.update_status(adapter.name.clone(), LanguageServerBinaryStatus::None);
|
||||
|
||||
let mut binary = binary_result?;
|
||||
|
||||
// If we do have a project environment (either by spawning a shell in in the project directory
|
||||
// or by getting it from the CLI) and the language server command itself
|
||||
// doesn't have an environment (which it would have, if it was found in $PATH), then
|
||||
// we use the project environment.
|
||||
if binary.env.is_none() && project_environment.is_some() {
|
||||
log::info!(
|
||||
"using project environment for language server {:?}, id: {server_id}",
|
||||
adapter.name.0
|
||||
);
|
||||
binary.env = project_environment.clone();
|
||||
}
|
||||
|
||||
let options = adapter
|
||||
.adapter
|
||||
.clone()
|
||||
.initialization_options(&delegate)
|
||||
.await?;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
if true {
|
||||
if let Some(this) = this.upgrade() {
|
||||
if let Some(fake_entry) = this
|
||||
.state
|
||||
.write()
|
||||
.fake_server_entries
|
||||
.get_mut(&adapter.name)
|
||||
{
|
||||
let (server, mut fake_server) = lsp::FakeLanguageServer::new(
|
||||
server_id,
|
||||
binary,
|
||||
adapter.name.0.to_string(),
|
||||
fake_entry.capabilities.clone(),
|
||||
cx.clone(),
|
||||
);
|
||||
fake_entry._server = Some(fake_server.clone());
|
||||
|
||||
if let Some(initializer) = &fake_entry.initializer {
|
||||
initializer(&mut fake_server);
|
||||
}
|
||||
|
||||
let tx = fake_entry.tx.clone();
|
||||
cx.background_executor()
|
||||
.spawn(async move {
|
||||
if fake_server
|
||||
.try_receive_notification::<lsp::notification::Initialized>(
|
||||
)
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
tx.unbounded_send(fake_server.clone()).ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
return Ok((server, options));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drop(this);
|
||||
Ok((
|
||||
lsp::LanguageServer::new(
|
||||
stderr_capture,
|
||||
server_id,
|
||||
binary,
|
||||
&root_path,
|
||||
adapter.code_action_kinds(),
|
||||
cx,
|
||||
)?,
|
||||
options,
|
||||
))
|
||||
}
|
||||
});
|
||||
|
||||
Some(PendingLanguageServer {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn create_fake_language_server(
|
||||
&self,
|
||||
server_id: LanguageServerId,
|
||||
name: &LanguageServerName,
|
||||
binary: lsp::LanguageServerBinary,
|
||||
cx: gpui::AsyncAppContext,
|
||||
) -> Option<lsp::LanguageServer> {
|
||||
let mut state = self.state.write();
|
||||
let fake_entry = state.fake_server_entries.get_mut(&name)?;
|
||||
let (server, mut fake_server) = lsp::FakeLanguageServer::new(
|
||||
server_id,
|
||||
task,
|
||||
container_dir,
|
||||
})
|
||||
binary,
|
||||
name.0.to_string(),
|
||||
fake_entry.capabilities.clone(),
|
||||
cx.clone(),
|
||||
);
|
||||
fake_entry._server = Some(fake_server.clone());
|
||||
|
||||
if let Some(initializer) = &fake_entry.initializer {
|
||||
initializer(&mut fake_server);
|
||||
}
|
||||
|
||||
let tx = fake_entry.tx.clone();
|
||||
cx.background_executor()
|
||||
.spawn(async move {
|
||||
if fake_server
|
||||
.try_receive_notification::<lsp::notification::Initialized>()
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
tx.unbounded_send(fake_server.clone()).ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Some(server)
|
||||
}
|
||||
|
||||
pub fn language_server_binary_statuses(
|
||||
|
@ -1007,29 +930,16 @@ impl LanguageRegistry {
|
|||
self.lsp_binary_status_tx.subscribe()
|
||||
}
|
||||
|
||||
pub fn delete_server_container(
|
||||
&self,
|
||||
adapter: Arc<CachedLspAdapter>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<()> {
|
||||
pub async fn delete_server_container(&self, name: LanguageServerName) {
|
||||
log::info!("deleting server container");
|
||||
let Some(dir) = self.language_server_download_dir(&name) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let download_dir = self
|
||||
.language_server_download_dir
|
||||
.clone()
|
||||
.expect("language server download directory has not been assigned before deleting server container");
|
||||
|
||||
cx.spawn(|_| async move {
|
||||
let container_dir = download_dir.join(adapter.name.0.as_ref());
|
||||
smol::fs::remove_dir_all(container_dir)
|
||||
.await
|
||||
.context("server container removal")
|
||||
.log_err();
|
||||
})
|
||||
}
|
||||
|
||||
pub fn next_language_server_id(&self) -> LanguageServerId {
|
||||
self.state.write().next_language_server_id()
|
||||
smol::fs::remove_dir_all(dir)
|
||||
.await
|
||||
.context("server container removal")
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue