Detect and fix broken lsp installations (#2646)
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Closes https://linear.app/zed-industries/issue/Z-665/add-a-mechanism-for-detecting-and-fixing-broken-language-server Fixes https://github.com/zed-industries/community/issues/1671 Fixes https://github.com/zed-industries/community/issues/1691 Fixes https://github.com/zed-industries/community/issues/1524 Fixes https://github.com/zed-industries/community/issues/1352 Fixes https://github.com/zed-industries/community/issues/1109 Fixes https://github.com/zed-industries/community/issues/996 Fixes https://github.com/zed-industries/community/issues/782 Things this PR does: - Updates our elixir-ls fetching to use new release name format - Detect when a server fails to launch - If the adapter claims to be reinstallable, get a test binary - If the test binary fails to launch or returns a failure error code - Clear container dir and reinstall - Detect/fix broken Node Things it does not do: - Restart server on failure, I have most of the stuff for this already so it should be a fast follow up - Detect/fix broken Copilot Node and Copilot shouldn't be too bad, they are handled via different mechanisms. Originally I put effort into detecting failure of the server during normal operation post launch, but that's not really needed. If the server gets borked while running then we'll catch that on next startup. Realizing that allowed for pruning a bunch of the work I did and made the overall system a lot nicer Copilot is nominally a language server but does not have an adapter and does not run through the same mechanism in the project. We're going to have an issue with multiple language server instances in different projects once we add a "Reinstall Language Server" action, which is why it's not in this PR. Each project has its own list of server instances and is currently vaguely responsible for managing the installations which means they can step on each others toes. This should change, probably Release Notes: - Added a mechanism to detect and reinstall broken language servers ([#1691](https://github.com/zed-industries/community/issues/1691)) ([#1524](https://github.com/zed-industries/community/issues/1524)) ([#1352](https://github.com/zed-industries/community/issues/1352)) ([#1109](https://github.com/zed-industries/community/issues/1109)) ([#996](https://github.com/zed-industries/community/issues/996)) ([#782](https://github.com/zed-industries/community/issues/782)).
This commit is contained in:
Generated
+1
@@ -4239,6 +4239,7 @@ dependencies = [
|
||||
"async-tar",
|
||||
"futures 0.3.28",
|
||||
"gpui",
|
||||
"log",
|
||||
"parking_lot 0.11.2",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
|
||||
@@ -207,16 +207,11 @@ impl ActivityIndicator {
|
||||
let mut checking_for_update = SmallVec::<[_; 3]>::new();
|
||||
let mut failed = SmallVec::<[_; 3]>::new();
|
||||
for status in &self.statuses {
|
||||
let name = status.name.clone();
|
||||
match status.status {
|
||||
LanguageServerBinaryStatus::CheckingForUpdate => {
|
||||
checking_for_update.push(status.name.clone());
|
||||
}
|
||||
LanguageServerBinaryStatus::Downloading => {
|
||||
downloading.push(status.name.clone());
|
||||
}
|
||||
LanguageServerBinaryStatus::Failed { .. } => {
|
||||
failed.push(status.name.clone());
|
||||
}
|
||||
LanguageServerBinaryStatus::CheckingForUpdate => checking_for_update.push(name),
|
||||
LanguageServerBinaryStatus::Downloading => downloading.push(name),
|
||||
LanguageServerBinaryStatus::Failed { .. } => failed.push(name),
|
||||
LanguageServerBinaryStatus::Downloaded | LanguageServerBinaryStatus::Cached => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use language::{
|
||||
ToPointUtf16,
|
||||
};
|
||||
use log::{debug, error};
|
||||
use lsp::{LanguageServer, LanguageServerId};
|
||||
use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId};
|
||||
use node_runtime::NodeRuntime;
|
||||
use request::{LogMessage, StatusNotification};
|
||||
use settings::SettingsStore;
|
||||
@@ -340,7 +340,7 @@ impl Copilot {
|
||||
let http = util::http::FakeHttpClient::create(|_| async { unreachable!() });
|
||||
let this = cx.add_model(|cx| Self {
|
||||
http: http.clone(),
|
||||
node_runtime: NodeRuntime::new(http, cx.background().clone()),
|
||||
node_runtime: NodeRuntime::instance(http, cx.background().clone()),
|
||||
server: CopilotServer::Running(RunningCopilotServer {
|
||||
lsp: Arc::new(server),
|
||||
sign_in_status: SignInStatus::Authorized,
|
||||
@@ -361,11 +361,14 @@ impl Copilot {
|
||||
let start_language_server = async {
|
||||
let server_path = get_copilot_lsp(http).await?;
|
||||
let node_path = node_runtime.binary_path().await?;
|
||||
let arguments: &[OsString] = &[server_path.into(), "--stdio".into()];
|
||||
let arguments: Vec<OsString> = vec![server_path.into(), "--stdio".into()];
|
||||
let binary = LanguageServerBinary {
|
||||
path: node_path,
|
||||
arguments,
|
||||
};
|
||||
let server = LanguageServer::new(
|
||||
LanguageServerId(0),
|
||||
&node_path,
|
||||
arguments,
|
||||
binary,
|
||||
Path::new("/"),
|
||||
None,
|
||||
cx.clone(),
|
||||
|
||||
+115
-55
@@ -20,7 +20,7 @@ use futures::{
|
||||
use gpui::{executor::Background, AppContext, AsyncAppContext, Task};
|
||||
use highlight_map::HighlightMap;
|
||||
use lazy_static::lazy_static;
|
||||
use lsp::CodeActionKind;
|
||||
use lsp::{CodeActionKind, LanguageServerBinary};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use postage::watch;
|
||||
use regex::Regex;
|
||||
@@ -30,7 +30,6 @@ use std::{
|
||||
any::Any,
|
||||
borrow::Cow,
|
||||
cell::RefCell,
|
||||
ffi::OsString,
|
||||
fmt::Debug,
|
||||
hash::Hash,
|
||||
mem,
|
||||
@@ -86,12 +85,6 @@ pub trait ToLspPosition {
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct LanguageServerName(pub Arc<str>);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct LanguageServerBinary {
|
||||
pub path: PathBuf,
|
||||
pub arguments: Vec<OsString>,
|
||||
}
|
||||
|
||||
/// Represents a Language Server, with certain cached sync properties.
|
||||
/// Uses [`LspAdapter`] under the hood, but calls all 'static' methods
|
||||
/// once at startup, and caches the results.
|
||||
@@ -167,6 +160,17 @@ impl CachedLspAdapter {
|
||||
.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()
|
||||
}
|
||||
@@ -249,6 +253,15 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary>;
|
||||
|
||||
fn can_be_reinstalled(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary>;
|
||||
|
||||
async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
|
||||
|
||||
async fn process_completion(&self, _: &mut lsp::CompletionItem) {}
|
||||
@@ -576,7 +589,8 @@ struct LanguageRegistryState {
|
||||
|
||||
pub struct PendingLanguageServer {
|
||||
pub server_id: LanguageServerId,
|
||||
pub task: Task<Result<lsp::LanguageServer>>,
|
||||
pub task: Task<Result<Option<lsp::LanguageServer>>>,
|
||||
pub container_dir: Option<Arc<Path>>,
|
||||
}
|
||||
|
||||
impl LanguageRegistry {
|
||||
@@ -848,7 +862,7 @@ impl LanguageRegistry {
|
||||
self.state.read().languages.iter().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn start_language_server(
|
||||
pub fn create_pending_language_server(
|
||||
self: &Arc<Self>,
|
||||
language: Arc<Language>,
|
||||
adapter: Arc<CachedLspAdapter>,
|
||||
@@ -858,7 +872,7 @@ impl LanguageRegistry {
|
||||
) -> Option<PendingLanguageServer> {
|
||||
let server_id = self.state.write().next_language_server_id();
|
||||
log::info!(
|
||||
"starting language server name:{}, path:{root_path:?}, id:{server_id}",
|
||||
"starting language server {:?}, path: {root_path:?}, id: {server_id}",
|
||||
adapter.name.0
|
||||
);
|
||||
|
||||
@@ -888,66 +902,81 @@ impl LanguageRegistry {
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
Ok(server)
|
||||
|
||||
Ok(Some(server))
|
||||
});
|
||||
|
||||
return Some(PendingLanguageServer { server_id, task });
|
||||
return Some(PendingLanguageServer {
|
||||
server_id,
|
||||
task,
|
||||
container_dir: None,
|
||||
});
|
||||
}
|
||||
|
||||
let download_dir = self
|
||||
.language_server_download_dir
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("language server download directory has not been assigned"))
|
||||
.ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server"))
|
||||
.log_err()?;
|
||||
let this = self.clone();
|
||||
let language = language.clone();
|
||||
let download_dir = download_dir.clone();
|
||||
let container_dir: Arc<Path> = Arc::from(download_dir.join(adapter.name.0.as_ref()));
|
||||
let root_path = root_path.clone();
|
||||
let adapter = adapter.clone();
|
||||
let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone();
|
||||
let login_shell_env_loaded = self.login_shell_env_loaded.clone();
|
||||
|
||||
let task = cx.spawn(|mut cx| async move {
|
||||
login_shell_env_loaded.await;
|
||||
let task = {
|
||||
let container_dir = container_dir.clone();
|
||||
cx.spawn(|mut cx| async move {
|
||||
login_shell_env_loaded.await;
|
||||
|
||||
let entry = this
|
||||
.lsp_binary_paths
|
||||
.lock()
|
||||
.entry(adapter.name.clone())
|
||||
.or_insert_with(|| {
|
||||
cx.spawn(|cx| {
|
||||
get_binary(
|
||||
adapter.clone(),
|
||||
language.clone(),
|
||||
delegate.clone(),
|
||||
download_dir,
|
||||
lsp_binary_statuses,
|
||||
cx,
|
||||
)
|
||||
.map_err(Arc::new)
|
||||
let mut lock = this.lsp_binary_paths.lock();
|
||||
let entry = lock
|
||||
.entry(adapter.name.clone())
|
||||
.or_insert_with(|| {
|
||||
cx.spawn(|cx| {
|
||||
get_binary(
|
||||
adapter.clone(),
|
||||
language.clone(),
|
||||
delegate.clone(),
|
||||
container_dir,
|
||||
lsp_binary_statuses,
|
||||
cx,
|
||||
)
|
||||
.map_err(Arc::new)
|
||||
})
|
||||
.shared()
|
||||
})
|
||||
.shared()
|
||||
})
|
||||
.clone();
|
||||
let binary = entry.clone().map_err(|e| anyhow!(e)).await?;
|
||||
.clone();
|
||||
drop(lock);
|
||||
|
||||
if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
|
||||
task.await?;
|
||||
}
|
||||
let binary = match entry.clone().await.log_err() {
|
||||
Some(binary) => binary,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let server = lsp::LanguageServer::new(
|
||||
server_id,
|
||||
&binary.path,
|
||||
&binary.arguments,
|
||||
&root_path,
|
||||
adapter.code_action_kinds(),
|
||||
cx,
|
||||
)?;
|
||||
if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
|
||||
if task.await.log_err().is_none() {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(server)
|
||||
});
|
||||
Ok(Some(lsp::LanguageServer::new(
|
||||
server_id,
|
||||
binary,
|
||||
&root_path,
|
||||
adapter.code_action_kinds(),
|
||||
cx,
|
||||
)?))
|
||||
})
|
||||
};
|
||||
|
||||
Some(PendingLanguageServer { server_id, task })
|
||||
Some(PendingLanguageServer {
|
||||
server_id,
|
||||
task,
|
||||
container_dir: Some(container_dir),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn language_server_binary_statuses(
|
||||
@@ -955,6 +984,30 @@ impl LanguageRegistry {
|
||||
) -> async_broadcast::Receiver<(Arc<Language>, LanguageServerBinaryStatus)> {
|
||||
self.lsp_binary_statuses_rx.clone()
|
||||
}
|
||||
|
||||
pub fn delete_server_container(
|
||||
&self,
|
||||
adapter: Arc<CachedLspAdapter>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<()> {
|
||||
log::info!("deleting server container");
|
||||
|
||||
let mut lock = self.lsp_binary_paths.lock();
|
||||
lock.remove(&adapter.name);
|
||||
|
||||
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();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl LanguageRegistryState {
|
||||
@@ -1005,11 +1058,10 @@ async fn get_binary(
|
||||
adapter: Arc<CachedLspAdapter>,
|
||||
language: Arc<Language>,
|
||||
delegate: Arc<dyn LspAdapterDelegate>,
|
||||
download_dir: Arc<Path>,
|
||||
container_dir: Arc<Path>,
|
||||
statuses: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let container_dir = download_dir.join(adapter.name.0.as_ref());
|
||||
if !container_dir.exists() {
|
||||
smol::fs::create_dir_all(&container_dir)
|
||||
.await
|
||||
@@ -1030,14 +1082,14 @@ async fn get_binary(
|
||||
.await;
|
||||
|
||||
if let Err(error) = binary.as_ref() {
|
||||
if let Some(cached) = adapter
|
||||
.cached_server_binary(container_dir, delegate.as_ref())
|
||||
if let Some(binary) = adapter
|
||||
.cached_server_binary(container_dir.to_path_buf(), delegate.as_ref())
|
||||
.await
|
||||
{
|
||||
statuses
|
||||
.broadcast((language.clone(), LanguageServerBinaryStatus::Cached))
|
||||
.await?;
|
||||
return Ok(cached);
|
||||
return Ok(binary);
|
||||
} else {
|
||||
statuses
|
||||
.broadcast((
|
||||
@@ -1049,6 +1101,7 @@ async fn get_binary(
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
binary
|
||||
}
|
||||
|
||||
@@ -1066,16 +1119,19 @@ async fn fetch_latest_binary(
|
||||
LanguageServerBinaryStatus::CheckingForUpdate,
|
||||
))
|
||||
.await?;
|
||||
|
||||
let version_info = adapter.fetch_latest_server_version(delegate).await?;
|
||||
lsp_binary_statuses_tx
|
||||
.broadcast((language.clone(), LanguageServerBinaryStatus::Downloading))
|
||||
.await?;
|
||||
|
||||
let binary = adapter
|
||||
.fetch_server_binary(version_info, container_dir.to_path_buf(), delegate)
|
||||
.await?;
|
||||
lsp_binary_statuses_tx
|
||||
.broadcast((language.clone(), LanguageServerBinaryStatus::Downloaded))
|
||||
.await?;
|
||||
|
||||
Ok(binary)
|
||||
}
|
||||
|
||||
@@ -1617,6 +1673,10 @@ impl LspAdapter for Arc<FakeLspAdapter> {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
|
||||
|
||||
async fn disk_based_diagnostic_sources(&self) -> Vec<String> {
|
||||
|
||||
+15
-8
@@ -16,6 +16,7 @@ use smol::{
|
||||
process::{self, Child},
|
||||
};
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
fmt,
|
||||
future::Future,
|
||||
io::Write,
|
||||
@@ -36,6 +37,12 @@ type NotificationHandler = Box<dyn Send + FnMut(Option<usize>, &str, AsyncAppCon
|
||||
type ResponseHandler = Box<dyn Send + FnOnce(Result<String, Error>)>;
|
||||
type IoHandler = Box<dyn Send + FnMut(bool, &str)>;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct LanguageServerBinary {
|
||||
pub path: PathBuf,
|
||||
pub arguments: Vec<OsString>,
|
||||
}
|
||||
|
||||
pub struct LanguageServer {
|
||||
server_id: LanguageServerId,
|
||||
next_id: AtomicUsize,
|
||||
@@ -51,7 +58,7 @@ pub struct LanguageServer {
|
||||
io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>,
|
||||
output_done_rx: Mutex<Option<barrier::Receiver>>,
|
||||
root_path: PathBuf,
|
||||
_server: Option<Child>,
|
||||
_server: Option<Mutex<Child>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
@@ -119,10 +126,9 @@ struct Error {
|
||||
}
|
||||
|
||||
impl LanguageServer {
|
||||
pub fn new<T: AsRef<std::ffi::OsStr>>(
|
||||
pub fn new(
|
||||
server_id: LanguageServerId,
|
||||
binary_path: &Path,
|
||||
arguments: &[T],
|
||||
binary: LanguageServerBinary,
|
||||
root_path: &Path,
|
||||
code_action_kinds: Option<Vec<CodeActionKind>>,
|
||||
cx: AsyncAppContext,
|
||||
@@ -133,9 +139,9 @@ impl LanguageServer {
|
||||
root_path.parent().unwrap_or_else(|| Path::new("/"))
|
||||
};
|
||||
|
||||
let mut server = process::Command::new(binary_path)
|
||||
let mut server = process::Command::new(&binary.path)
|
||||
.current_dir(working_dir)
|
||||
.args(arguments)
|
||||
.args(binary.arguments)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::inherit())
|
||||
@@ -167,9 +173,10 @@ impl LanguageServer {
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(name) = binary_path.file_name() {
|
||||
if let Some(name) = binary.path.file_name() {
|
||||
server.name = name.to_string_lossy().to_string();
|
||||
}
|
||||
|
||||
Ok(server)
|
||||
}
|
||||
|
||||
@@ -231,7 +238,7 @@ impl LanguageServer {
|
||||
io_tasks: Mutex::new(Some((input_task, output_task))),
|
||||
output_done_rx: Mutex::new(Some(output_done_rx)),
|
||||
root_path: root_path.to_path_buf(),
|
||||
_server: server,
|
||||
_server: server.map(|server| Mutex::new(server)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,3 +20,4 @@ serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
smol.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use futures::lock::Mutex;
|
||||
use futures::{future::Shared, FutureExt};
|
||||
use gpui::{executor::Background, Task};
|
||||
use parking_lot::Mutex;
|
||||
use serde::Deserialize;
|
||||
use smol::{fs, io::BufReader, process::Command};
|
||||
use std::process::Output;
|
||||
use std::{
|
||||
env::consts,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
use util::http::HttpClient;
|
||||
use util::{http::HttpClient, ResultExt};
|
||||
|
||||
const VERSION: &str = "v18.15.0";
|
||||
|
||||
#[derive(Deserialize)]
|
||||
static RUNTIME_INSTANCE: OnceLock<Arc<NodeRuntime>> = OnceLock::new();
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct NpmInfo {
|
||||
#[serde(default)]
|
||||
@@ -23,7 +26,7 @@ pub struct NpmInfo {
|
||||
versions: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
pub struct NpmInfoDistTags {
|
||||
latest: Option<String>,
|
||||
}
|
||||
@@ -35,12 +38,16 @@ pub struct NodeRuntime {
|
||||
}
|
||||
|
||||
impl NodeRuntime {
|
||||
pub fn new(http: Arc<dyn HttpClient>, background: Arc<Background>) -> Arc<NodeRuntime> {
|
||||
Arc::new(NodeRuntime {
|
||||
http,
|
||||
background,
|
||||
installation_path: Mutex::new(None),
|
||||
})
|
||||
pub fn instance(http: Arc<dyn HttpClient>, background: Arc<Background>) -> Arc<NodeRuntime> {
|
||||
RUNTIME_INSTANCE
|
||||
.get_or_init(|| {
|
||||
Arc::new(NodeRuntime {
|
||||
http,
|
||||
background,
|
||||
installation_path: Mutex::new(None),
|
||||
})
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub async fn binary_path(&self) -> Result<PathBuf> {
|
||||
@@ -50,55 +57,74 @@ impl NodeRuntime {
|
||||
|
||||
pub async fn run_npm_subcommand(
|
||||
&self,
|
||||
directory: &Path,
|
||||
directory: Option<&Path>,
|
||||
subcommand: &str,
|
||||
args: &[&str],
|
||||
) -> Result<()> {
|
||||
) -> Result<Output> {
|
||||
let attempt = |installation_path: PathBuf| async move {
|
||||
let node_binary = installation_path.join("bin/node");
|
||||
let npm_file = installation_path.join("bin/npm");
|
||||
|
||||
if smol::fs::metadata(&node_binary).await.is_err() {
|
||||
return Err(anyhow!("missing node binary file"));
|
||||
}
|
||||
|
||||
if smol::fs::metadata(&npm_file).await.is_err() {
|
||||
return Err(anyhow!("missing npm file"));
|
||||
}
|
||||
|
||||
let mut command = Command::new(node_binary);
|
||||
command.arg(npm_file).arg(subcommand).args(args);
|
||||
|
||||
if let Some(directory) = directory {
|
||||
command.current_dir(directory);
|
||||
}
|
||||
|
||||
command.output().await.map_err(|e| anyhow!("{e}"))
|
||||
};
|
||||
|
||||
let installation_path = self.install_if_needed().await?;
|
||||
let node_binary = installation_path.join("bin/node");
|
||||
let npm_file = installation_path.join("bin/npm");
|
||||
|
||||
let output = Command::new(node_binary)
|
||||
.arg(npm_file)
|
||||
.arg(subcommand)
|
||||
.args(args)
|
||||
.current_dir(directory)
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"failed to execute npm {subcommand} subcommand:\nstdout: {:?}\nstderr: {:?}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
let mut output = attempt(installation_path).await;
|
||||
if output.is_err() {
|
||||
let installation_path = self.reinstall().await?;
|
||||
output = attempt(installation_path).await;
|
||||
if output.is_err() {
|
||||
return Err(anyhow!(
|
||||
"failed to launch npm subcommand {subcommand} subcommand"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
if let Ok(output) = &output {
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"failed to execute npm {subcommand} subcommand:\nstdout: {:?}\nstderr: {:?}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
output.map_err(|e| anyhow!("{e}"))
|
||||
}
|
||||
|
||||
pub async fn npm_package_latest_version(&self, name: &str) -> Result<String> {
|
||||
let installation_path = self.install_if_needed().await?;
|
||||
let node_binary = installation_path.join("bin/node");
|
||||
let npm_file = installation_path.join("bin/npm");
|
||||
|
||||
let output = Command::new(node_binary)
|
||||
.arg(npm_file)
|
||||
.args(["-fetch-retry-mintimeout", "2000"])
|
||||
.args(["-fetch-retry-maxtimeout", "5000"])
|
||||
.args(["-fetch-timeout", "5000"])
|
||||
.args(["info", name, "--json"])
|
||||
.output()
|
||||
.await
|
||||
.context("failed to run npm info")?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"failed to execute npm info:\nstdout: {:?}\nstderr: {:?}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
let output = self
|
||||
.run_npm_subcommand(
|
||||
None,
|
||||
"info",
|
||||
&[
|
||||
name,
|
||||
"--json",
|
||||
"-fetch-retry-mintimeout",
|
||||
"2000",
|
||||
"-fetch-retry-maxtimeout",
|
||||
"5000",
|
||||
"-fetch-timeout",
|
||||
"5000",
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?;
|
||||
info.dist_tags
|
||||
@@ -112,41 +138,54 @@ impl NodeRuntime {
|
||||
directory: &Path,
|
||||
packages: impl IntoIterator<Item = (&str, &str)>,
|
||||
) -> Result<()> {
|
||||
let installation_path = self.install_if_needed().await?;
|
||||
let node_binary = installation_path.join("bin/node");
|
||||
let npm_file = installation_path.join("bin/npm");
|
||||
let packages: Vec<_> = packages
|
||||
.into_iter()
|
||||
.map(|(name, version)| format!("{name}@{version}"))
|
||||
.collect();
|
||||
|
||||
let output = Command::new(node_binary)
|
||||
.arg(npm_file)
|
||||
.args(["-fetch-retry-mintimeout", "2000"])
|
||||
.args(["-fetch-retry-maxtimeout", "5000"])
|
||||
.args(["-fetch-timeout", "5000"])
|
||||
.arg("install")
|
||||
.arg("--prefix")
|
||||
.arg(directory)
|
||||
.args(
|
||||
packages
|
||||
.into_iter()
|
||||
.map(|(name, version)| format!("{name}@{version}")),
|
||||
)
|
||||
.output()
|
||||
.await
|
||||
.context("failed to run npm install")?;
|
||||
let mut arguments: Vec<_> = packages.iter().map(|p| p.as_str()).collect();
|
||||
arguments.extend_from_slice(&[
|
||||
"-fetch-retry-mintimeout",
|
||||
"2000",
|
||||
"-fetch-retry-maxtimeout",
|
||||
"5000",
|
||||
"-fetch-timeout",
|
||||
"5000",
|
||||
]);
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"failed to execute npm install:\nstdout: {:?}\nstderr: {:?}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
self.run_npm_subcommand(Some(directory), "install", &arguments)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn reinstall(&self) -> Result<PathBuf> {
|
||||
log::info!("beginnning to reinstall Node runtime");
|
||||
let mut installation_path = self.installation_path.lock().await;
|
||||
|
||||
if let Some(task) = installation_path.as_ref().cloned() {
|
||||
if let Ok(installation_path) = task.await {
|
||||
smol::fs::remove_dir_all(&installation_path)
|
||||
.await
|
||||
.context("node dir removal")
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
let http = self.http.clone();
|
||||
let task = self
|
||||
.background
|
||||
.spawn(async move { Self::install(http).await.map_err(Arc::new) })
|
||||
.shared();
|
||||
|
||||
*installation_path = Some(task.clone());
|
||||
task.await.map_err(|e| anyhow!("{}", e))
|
||||
}
|
||||
|
||||
async fn install_if_needed(&self) -> Result<PathBuf> {
|
||||
let task = self
|
||||
.installation_path
|
||||
.lock()
|
||||
.await
|
||||
.get_or_insert_with(|| {
|
||||
let http = self.http.clone();
|
||||
self.background
|
||||
@@ -155,13 +194,11 @@ impl NodeRuntime {
|
||||
})
|
||||
.clone();
|
||||
|
||||
match task.await {
|
||||
Ok(path) => Ok(path),
|
||||
Err(error) => Err(anyhow!("{}", error)),
|
||||
}
|
||||
task.await.map_err(|e| anyhow!("{}", e))
|
||||
}
|
||||
|
||||
async fn install(http: Arc<dyn HttpClient>) -> Result<PathBuf> {
|
||||
log::info!("installing Node runtime");
|
||||
let arch = match consts::ARCH {
|
||||
"x86_64" => "x64",
|
||||
"aarch64" => "arm64",
|
||||
|
||||
+639
-398
File diff suppressed because it is too large
Load Diff
+12
-2
@@ -118,14 +118,15 @@ pub fn merge_non_null_json_value_into(source: serde_json::Value, target: &mut se
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ResultExt {
|
||||
pub trait ResultExt<E> {
|
||||
type Ok;
|
||||
|
||||
fn log_err(self) -> Option<Self::Ok>;
|
||||
fn warn_on_err(self) -> Option<Self::Ok>;
|
||||
fn inspect_error(self, func: impl FnOnce(&E)) -> Self;
|
||||
}
|
||||
|
||||
impl<T, E> ResultExt for Result<T, E>
|
||||
impl<T, E> ResultExt<E> for Result<T, E>
|
||||
where
|
||||
E: std::fmt::Debug,
|
||||
{
|
||||
@@ -152,6 +153,15 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// https://doc.rust-lang.org/std/result/enum.Result.html#method.inspect_err
|
||||
fn inspect_error(self, func: impl FnOnce(&E)) -> Self {
|
||||
if let Err(err) = &self {
|
||||
func(err);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TryFutureExt {
|
||||
|
||||
@@ -2,6 +2,7 @@ use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
pub use language::*;
|
||||
use lsp::LanguageServerBinary;
|
||||
use smol::fs::{self, File};
|
||||
use std::{any::Any, path::PathBuf, sync::Arc};
|
||||
use util::{
|
||||
@@ -86,31 +87,19 @@ impl super::LspAdapter for CLspAdapter {
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let mut last_clangd_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_dir() {
|
||||
last_clangd_dir = Some(entry.path());
|
||||
}
|
||||
}
|
||||
let clangd_dir = last_clangd_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||
let clangd_bin = clangd_dir.join("bin/clangd");
|
||||
if clangd_bin.exists() {
|
||||
Ok(LanguageServerBinary {
|
||||
path: clangd_bin,
|
||||
arguments: vec![],
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing clangd binary in directory {:?}",
|
||||
clangd_dir
|
||||
))
|
||||
}
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
get_cached_server_binary(container_dir).await
|
||||
}
|
||||
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir)
|
||||
.await
|
||||
.map(|mut binary| {
|
||||
binary.arguments = vec!["--help".into()];
|
||||
binary
|
||||
})
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
@@ -250,6 +239,34 @@ impl super::LspAdapter for CLspAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let mut last_clangd_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_dir() {
|
||||
last_clangd_dir = Some(entry.path());
|
||||
}
|
||||
}
|
||||
let clangd_dir = last_clangd_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||
let clangd_bin = clangd_dir.join("bin/clangd");
|
||||
if clangd_bin.exists() {
|
||||
Ok(LanguageServerBinary {
|
||||
path: clangd_bin,
|
||||
arguments: vec![],
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing clangd binary in directory {:?}",
|
||||
clangd_dir
|
||||
))
|
||||
}
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpui::TestAppContext;
|
||||
|
||||
@@ -3,7 +3,7 @@ use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use gpui::{AsyncAppContext, Task};
|
||||
pub use language::*;
|
||||
use lsp::{CompletionItemKind, SymbolKind};
|
||||
use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind};
|
||||
use smol::fs::{self, File};
|
||||
use std::{
|
||||
any::Any,
|
||||
@@ -140,20 +140,14 @@ impl LspAdapter for ElixirLspAdapter {
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let mut last = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
last = Some(entry?.path());
|
||||
}
|
||||
last.map(|path| LanguageServerBinary {
|
||||
path,
|
||||
arguments: vec![],
|
||||
})
|
||||
.ok_or_else(|| anyhow!("no cached binary"))
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
get_cached_server_binary(container_dir).await
|
||||
}
|
||||
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir).await
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
@@ -239,3 +233,20 @@ impl LspAdapter for ElixirLspAdapter {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let mut last = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
last = Some(entry?.path());
|
||||
}
|
||||
last.map(|path| LanguageServerBinary {
|
||||
path,
|
||||
arguments: vec![],
|
||||
})
|
||||
.ok_or_else(|| anyhow!("no cached binary"))
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use futures::StreamExt;
|
||||
use gpui::{AsyncAppContext, Task};
|
||||
pub use language::*;
|
||||
use lazy_static::lazy_static;
|
||||
use lsp::LanguageServerBinary;
|
||||
use regex::Regex;
|
||||
use smol::{fs, process};
|
||||
use std::{
|
||||
@@ -148,32 +149,19 @@ impl super::LspAdapter for GoLspAdapter {
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let mut last_binary_path = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_file()
|
||||
&& entry
|
||||
.file_name()
|
||||
.to_str()
|
||||
.map_or(false, |name| name.starts_with("gopls_"))
|
||||
{
|
||||
last_binary_path = Some(entry.path());
|
||||
}
|
||||
}
|
||||
get_cached_server_binary(container_dir).await
|
||||
}
|
||||
|
||||
if let Some(path) = last_binary_path {
|
||||
Ok(LanguageServerBinary {
|
||||
path,
|
||||
arguments: server_binary_arguments(),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!("no cached binary"))
|
||||
}
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir)
|
||||
.await
|
||||
.map(|mut binary| {
|
||||
binary.arguments = vec!["--help".into()];
|
||||
binary
|
||||
})
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
@@ -336,6 +324,35 @@ impl super::LspAdapter for GoLspAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let mut last_binary_path = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_file()
|
||||
&& entry
|
||||
.file_name()
|
||||
.to_str()
|
||||
.map_or(false, |name| name.starts_with("gopls_"))
|
||||
{
|
||||
last_binary_path = Some(entry.path());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = last_binary_path {
|
||||
Ok(LanguageServerBinary {
|
||||
path,
|
||||
arguments: server_binary_arguments(),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!("no cached binary"))
|
||||
}
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
|
||||
fn adjust_runs(
|
||||
delta: usize,
|
||||
mut runs: Vec<(Range<usize>, HighlightId)>,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use language::{LanguageServerBinary, LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use node_runtime::NodeRuntime;
|
||||
use serde_json::json;
|
||||
use smol::fs;
|
||||
@@ -13,6 +14,9 @@ use std::{
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
const SERVER_PATH: &'static str =
|
||||
"node_modules/vscode-langservers-extracted/bin/vscode-html-language-server";
|
||||
|
||||
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
vec![server_path.into(), "--stdio".into()]
|
||||
}
|
||||
@@ -22,9 +26,6 @@ pub struct HtmlLspAdapter {
|
||||
}
|
||||
|
||||
impl HtmlLspAdapter {
|
||||
const SERVER_PATH: &'static str =
|
||||
"node_modules/vscode-langservers-extracted/bin/vscode-html-language-server";
|
||||
|
||||
pub fn new(node: Arc<NodeRuntime>) -> Self {
|
||||
HtmlLspAdapter { node }
|
||||
}
|
||||
@@ -54,7 +55,7 @@ impl LspAdapter for HtmlLspAdapter {
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let version = version.downcast::<String>().unwrap();
|
||||
let server_path = container_dir.join(Self::SERVER_PATH);
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
|
||||
if fs::metadata(&server_path).await.is_err() {
|
||||
self.node
|
||||
@@ -76,31 +77,14 @@ impl LspAdapter for HtmlLspAdapter {
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_dir() {
|
||||
last_version_dir = Some(entry.path());
|
||||
}
|
||||
}
|
||||
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||
let server_path = last_version_dir.join(Self::SERVER_PATH);
|
||||
if server_path.exists() {
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing executable in directory {:?}",
|
||||
last_version_dir
|
||||
))
|
||||
}
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
get_cached_server_binary(container_dir, &self.node).await
|
||||
}
|
||||
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir, &self.node).await
|
||||
}
|
||||
|
||||
async fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
@@ -109,3 +93,34 @@ impl LspAdapter for HtmlLspAdapter {
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(
|
||||
container_dir: PathBuf,
|
||||
node: &NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_dir() {
|
||||
last_version_dir = Some(entry.path());
|
||||
}
|
||||
}
|
||||
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||
let server_path = last_version_dir.join(SERVER_PATH);
|
||||
if server_path.exists() {
|
||||
Ok(LanguageServerBinary {
|
||||
path: node.binary_path().await?,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing executable in directory {:?}",
|
||||
last_version_dir
|
||||
))
|
||||
}
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@ use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use futures::{future::BoxFuture, FutureExt, StreamExt};
|
||||
use gpui::AppContext;
|
||||
use language::{
|
||||
LanguageRegistry, LanguageServerBinary, LanguageServerName, LspAdapter, LspAdapterDelegate,
|
||||
};
|
||||
use language::{LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use node_runtime::NodeRuntime;
|
||||
use serde_json::json;
|
||||
use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore};
|
||||
@@ -84,32 +83,14 @@ impl LspAdapter for JsonLspAdapter {
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_dir() {
|
||||
last_version_dir = Some(entry.path());
|
||||
}
|
||||
}
|
||||
get_cached_server_binary(container_dir, &self.node).await
|
||||
}
|
||||
|
||||
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||
let server_path = last_version_dir.join(SERVER_PATH);
|
||||
if server_path.exists() {
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing executable in directory {:?}",
|
||||
last_version_dir
|
||||
))
|
||||
}
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir, &self.node).await
|
||||
}
|
||||
|
||||
async fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
@@ -162,6 +143,38 @@ impl LspAdapter for JsonLspAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(
|
||||
container_dir: PathBuf,
|
||||
node: &NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_dir() {
|
||||
last_version_dir = Some(entry.path());
|
||||
}
|
||||
}
|
||||
|
||||
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||
let server_path = last_version_dir.join(SERVER_PATH);
|
||||
if server_path.exists() {
|
||||
Ok(LanguageServerBinary {
|
||||
path: node.binary_path().await?,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing executable in directory {:?}",
|
||||
last_version_dir
|
||||
))
|
||||
}
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
|
||||
fn schema_file_match(path: &Path) -> &Path {
|
||||
path.strip_prefix(path.parent().unwrap().parent().unwrap())
|
||||
.unwrap()
|
||||
|
||||
@@ -3,7 +3,8 @@ use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use futures::lock::Mutex;
|
||||
use gpui::executor::Background;
|
||||
use language::{LanguageServerBinary, LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn};
|
||||
use std::{any::Any, path::PathBuf, sync::Arc};
|
||||
use util::ResultExt;
|
||||
@@ -129,6 +130,14 @@ impl LspAdapter for PluginLspAdapter {
|
||||
.await
|
||||
}
|
||||
|
||||
fn can_be_reinstalled(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
let string: String = self
|
||||
.runtime
|
||||
|
||||
@@ -3,7 +3,8 @@ use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use futures::{io::BufReader, StreamExt};
|
||||
use language::{LanguageServerBinary, LanguageServerName, LspAdapterDelegate};
|
||||
use language::{LanguageServerName, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use smol::fs;
|
||||
use std::{any::Any, env::consts, ffi::OsString, path::PathBuf};
|
||||
use util::{
|
||||
@@ -91,31 +92,47 @@ impl super::LspAdapter for LuaLspAdapter {
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
async_iife!({
|
||||
let mut last_binary_path = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_file()
|
||||
&& entry
|
||||
.file_name()
|
||||
.to_str()
|
||||
.map_or(false, |name| name == "lua-language-server")
|
||||
{
|
||||
last_binary_path = Some(entry.path());
|
||||
}
|
||||
}
|
||||
get_cached_server_binary(container_dir).await
|
||||
}
|
||||
|
||||
if let Some(path) = last_binary_path {
|
||||
Ok(LanguageServerBinary {
|
||||
path,
|
||||
arguments: server_binary_arguments(),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!("no cached binary"))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.log_err()
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir)
|
||||
.await
|
||||
.map(|mut binary| {
|
||||
binary.arguments = vec!["--version".into()];
|
||||
binary
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
async_iife!({
|
||||
let mut last_binary_path = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_file()
|
||||
&& entry
|
||||
.file_name()
|
||||
.to_str()
|
||||
.map_or(false, |name| name == "lua-language-server")
|
||||
{
|
||||
last_binary_path = Some(entry.path());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = last_binary_path {
|
||||
Ok(LanguageServerBinary {
|
||||
path,
|
||||
arguments: server_binary_arguments(),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!("no cached binary"))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use language::{LanguageServerBinary, LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use node_runtime::NodeRuntime;
|
||||
use smol::fs;
|
||||
use std::{
|
||||
@@ -12,6 +13,8 @@ use std::{
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
const SERVER_PATH: &'static str = "node_modules/pyright/langserver.index.js";
|
||||
|
||||
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
vec![server_path.into(), "--stdio".into()]
|
||||
}
|
||||
@@ -21,8 +24,6 @@ pub struct PythonLspAdapter {
|
||||
}
|
||||
|
||||
impl PythonLspAdapter {
|
||||
const SERVER_PATH: &'static str = "node_modules/pyright/langserver.index.js";
|
||||
|
||||
pub fn new(node: Arc<NodeRuntime>) -> Self {
|
||||
PythonLspAdapter { node }
|
||||
}
|
||||
@@ -48,7 +49,7 @@ impl LspAdapter for PythonLspAdapter {
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let version = version.downcast::<String>().unwrap();
|
||||
let server_path = container_dir.join(Self::SERVER_PATH);
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
|
||||
if fs::metadata(&server_path).await.is_err() {
|
||||
self.node
|
||||
@@ -67,31 +68,14 @@ impl LspAdapter for PythonLspAdapter {
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_dir() {
|
||||
last_version_dir = Some(entry.path());
|
||||
}
|
||||
}
|
||||
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||
let server_path = last_version_dir.join(Self::SERVER_PATH);
|
||||
if server_path.exists() {
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing executable in directory {:?}",
|
||||
last_version_dir
|
||||
))
|
||||
}
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
get_cached_server_binary(container_dir, &self.node).await
|
||||
}
|
||||
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir, &self.node).await
|
||||
}
|
||||
|
||||
async fn process_completion(&self, item: &mut lsp::CompletionItem) {
|
||||
@@ -170,6 +154,37 @@ impl LspAdapter for PythonLspAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(
|
||||
container_dir: PathBuf,
|
||||
node: &NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_dir() {
|
||||
last_version_dir = Some(entry.path());
|
||||
}
|
||||
}
|
||||
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||
let server_path = last_version_dir.join(SERVER_PATH);
|
||||
if server_path.exists() {
|
||||
Ok(LanguageServerBinary {
|
||||
path: node.binary_path().await?,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing executable in directory {:?}",
|
||||
last_version_dir
|
||||
))
|
||||
}
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpui::{ModelContext, TestAppContext};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use language::{LanguageServerBinary, LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use std::{any::Any, path::PathBuf, sync::Arc};
|
||||
|
||||
pub struct RubyLanguageServer;
|
||||
@@ -38,6 +39,14 @@ impl LspAdapter for RubyLanguageServer {
|
||||
})
|
||||
}
|
||||
|
||||
fn can_be_reinstalled(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
&self,
|
||||
item: &lsp::CompletionItem,
|
||||
|
||||
@@ -4,6 +4,7 @@ use async_trait::async_trait;
|
||||
use futures::{io::BufReader, StreamExt};
|
||||
pub use language::*;
|
||||
use lazy_static::lazy_static;
|
||||
use lsp::LanguageServerBinary;
|
||||
use regex::Regex;
|
||||
use smol::fs::{self, File};
|
||||
use std::{any::Any, borrow::Cow, env::consts, path::PathBuf, str, sync::Arc};
|
||||
@@ -78,20 +79,19 @@ impl LspAdapter for RustLspAdapter {
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let mut last = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
last = Some(entry?.path());
|
||||
}
|
||||
get_cached_server_binary(container_dir).await
|
||||
}
|
||||
|
||||
anyhow::Ok(LanguageServerBinary {
|
||||
path: last.ok_or_else(|| anyhow!("no cached binary"))?,
|
||||
arguments: Default::default(),
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir)
|
||||
.await
|
||||
.map(|mut binary| {
|
||||
binary.arguments = vec!["--help".into()];
|
||||
binary
|
||||
})
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
|
||||
async fn disk_based_diagnostic_sources(&self) -> Vec<String> {
|
||||
@@ -258,6 +258,22 @@ impl LspAdapter for RustLspAdapter {
|
||||
})
|
||||
}
|
||||
}
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let mut last = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
last = Some(entry?.path());
|
||||
}
|
||||
|
||||
anyhow::Ok(LanguageServerBinary {
|
||||
path: last.ok_or_else(|| anyhow!("no cached binary"))?,
|
||||
arguments: Default::default(),
|
||||
})
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -4,8 +4,8 @@ use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use futures::{future::BoxFuture, FutureExt};
|
||||
use gpui::AppContext;
|
||||
use language::{LanguageServerBinary, LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::CodeActionKind;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary};
|
||||
use node_runtime::NodeRuntime;
|
||||
use serde_json::{json, Value};
|
||||
use smol::{fs, io::BufReader, stream::StreamExt};
|
||||
@@ -104,28 +104,14 @@ impl LspAdapter for TypeScriptLspAdapter {
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let old_server_path = container_dir.join(Self::OLD_SERVER_PATH);
|
||||
let new_server_path = container_dir.join(Self::NEW_SERVER_PATH);
|
||||
if new_server_path.exists() {
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
arguments: typescript_server_binary_arguments(&new_server_path),
|
||||
})
|
||||
} else if old_server_path.exists() {
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
arguments: typescript_server_binary_arguments(&old_server_path),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing executable in directory {:?}",
|
||||
container_dir
|
||||
))
|
||||
}
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
get_cached_ts_server_binary(container_dir, &self.node).await
|
||||
}
|
||||
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_ts_server_binary(container_dir, &self.node).await
|
||||
}
|
||||
|
||||
fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
|
||||
@@ -173,6 +159,34 @@ impl LspAdapter for TypeScriptLspAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_ts_server_binary(
|
||||
container_dir: PathBuf,
|
||||
node: &NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let old_server_path = container_dir.join(TypeScriptLspAdapter::OLD_SERVER_PATH);
|
||||
let new_server_path = container_dir.join(TypeScriptLspAdapter::NEW_SERVER_PATH);
|
||||
if new_server_path.exists() {
|
||||
Ok(LanguageServerBinary {
|
||||
path: node.binary_path().await?,
|
||||
arguments: typescript_server_binary_arguments(&new_server_path),
|
||||
})
|
||||
} else if old_server_path.exists() {
|
||||
Ok(LanguageServerBinary {
|
||||
path: node.binary_path().await?,
|
||||
arguments: typescript_server_binary_arguments(&old_server_path),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing executable in directory {:?}",
|
||||
container_dir
|
||||
))
|
||||
}
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
|
||||
pub struct EsLintLspAdapter {
|
||||
node: Arc<NodeRuntime>,
|
||||
}
|
||||
@@ -249,11 +263,11 @@ impl LspAdapter for EsLintLspAdapter {
|
||||
fs::rename(first.path(), &repo_root).await?;
|
||||
|
||||
self.node
|
||||
.run_npm_subcommand(&repo_root, "install", &[])
|
||||
.run_npm_subcommand(Some(&repo_root), "install", &[])
|
||||
.await?;
|
||||
|
||||
self.node
|
||||
.run_npm_subcommand(&repo_root, "run-script", &["compile"])
|
||||
.run_npm_subcommand(Some(&repo_root), "run-script", &["compile"])
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -268,21 +282,14 @@ impl LspAdapter for EsLintLspAdapter {
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
// This is unfortunate but we don't know what the version is to build a path directly
|
||||
let mut dir = fs::read_dir(&container_dir).await?;
|
||||
let first = dir.next().await.ok_or(anyhow!("missing first file"))??;
|
||||
if !first.file_type().await?.is_dir() {
|
||||
return Err(anyhow!("First entry is not a directory"));
|
||||
}
|
||||
get_cached_eslint_server_binary(container_dir, &self.node).await
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: first.path().join(Self::SERVER_PATH),
|
||||
arguments: Default::default(),
|
||||
})
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_eslint_server_binary(container_dir, &self.node).await
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
@@ -298,6 +305,28 @@ impl LspAdapter for EsLintLspAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_eslint_server_binary(
|
||||
container_dir: PathBuf,
|
||||
node: &NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
// This is unfortunate but we don't know what the version is to build a path directly
|
||||
let mut dir = fs::read_dir(&container_dir).await?;
|
||||
let first = dir.next().await.ok_or(anyhow!("missing first file"))??;
|
||||
if !first.file_type().await?.is_dir() {
|
||||
return Err(anyhow!("First entry is not a directory"));
|
||||
}
|
||||
let server_path = first.path().join(EsLintLspAdapter::SERVER_PATH);
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: node.binary_path().await?,
|
||||
arguments: eslint_server_binary_arguments(&server_path),
|
||||
})
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpui::TestAppContext;
|
||||
|
||||
@@ -3,9 +3,9 @@ use async_trait::async_trait;
|
||||
use futures::{future::BoxFuture, FutureExt, StreamExt};
|
||||
use gpui::AppContext;
|
||||
use language::{
|
||||
language_settings::all_language_settings, LanguageServerBinary, LanguageServerName, LspAdapter,
|
||||
LspAdapterDelegate,
|
||||
language_settings::all_language_settings, LanguageServerName, LspAdapter, LspAdapterDelegate,
|
||||
};
|
||||
use lsp::LanguageServerBinary;
|
||||
use node_runtime::NodeRuntime;
|
||||
use serde_json::Value;
|
||||
use smol::fs;
|
||||
@@ -18,6 +18,8 @@ use std::{
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
const SERVER_PATH: &'static str = "node_modules/yaml-language-server/bin/yaml-language-server";
|
||||
|
||||
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
vec![server_path.into(), "--stdio".into()]
|
||||
}
|
||||
@@ -27,8 +29,6 @@ pub struct YamlLspAdapter {
|
||||
}
|
||||
|
||||
impl YamlLspAdapter {
|
||||
const SERVER_PATH: &'static str = "node_modules/yaml-language-server/bin/yaml-language-server";
|
||||
|
||||
pub fn new(node: Arc<NodeRuntime>) -> Self {
|
||||
YamlLspAdapter { node }
|
||||
}
|
||||
@@ -58,7 +58,7 @@ impl LspAdapter for YamlLspAdapter {
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let version = version.downcast::<String>().unwrap();
|
||||
let server_path = container_dir.join(Self::SERVER_PATH);
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
|
||||
if fs::metadata(&server_path).await.is_err() {
|
||||
self.node
|
||||
@@ -77,33 +77,15 @@ impl LspAdapter for YamlLspAdapter {
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_dir() {
|
||||
last_version_dir = Some(entry.path());
|
||||
}
|
||||
}
|
||||
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||
let server_path = last_version_dir.join(Self::SERVER_PATH);
|
||||
if server_path.exists() {
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing executable in directory {:?}",
|
||||
last_version_dir
|
||||
))
|
||||
}
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
get_cached_server_binary(container_dir, &self.node).await
|
||||
}
|
||||
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir, &self.node).await
|
||||
}
|
||||
fn workspace_configuration(&self, cx: &mut AppContext) -> Option<BoxFuture<'static, Value>> {
|
||||
let tab_size = all_language_settings(None, cx)
|
||||
.language(Some("YAML"))
|
||||
@@ -121,3 +103,34 @@ impl LspAdapter for YamlLspAdapter {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(
|
||||
container_dir: PathBuf,
|
||||
node: &NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_dir() {
|
||||
last_version_dir = Some(entry.path());
|
||||
}
|
||||
}
|
||||
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||
let server_path = last_version_dir.join(SERVER_PATH);
|
||||
if server_path.exists() {
|
||||
Ok(LanguageServerBinary {
|
||||
path: node.binary_path().await?,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing executable in directory {:?}",
|
||||
last_version_dir
|
||||
))
|
||||
}
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ fn main() {
|
||||
languages.set_executor(cx.background().clone());
|
||||
languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone());
|
||||
let languages = Arc::new(languages);
|
||||
let node_runtime = NodeRuntime::new(http.clone(), cx.background().to_owned());
|
||||
let node_runtime = NodeRuntime::instance(http.clone(), cx.background().to_owned());
|
||||
|
||||
languages::init(languages.clone(), node_runtime.clone());
|
||||
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
|
||||
|
||||
@@ -2144,7 +2144,7 @@ mod tests {
|
||||
languages.set_executor(cx.background().clone());
|
||||
let languages = Arc::new(languages);
|
||||
let http = FakeHttpClient::with_404_response();
|
||||
let node_runtime = NodeRuntime::new(http, cx.background().to_owned());
|
||||
let node_runtime = NodeRuntime::instance(http, cx.background().to_owned());
|
||||
languages::init(languages.clone(), node_runtime);
|
||||
for name in languages.language_names() {
|
||||
languages.language_for_name(&name);
|
||||
|
||||
Reference in New Issue
Block a user