Merge pull request #459 from zed-industries/spurious-macro-errors
Download language servers dynamically on startup
This commit is contained in:
commit
c752383042
23 changed files with 743 additions and 362 deletions
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
|
@ -34,11 +34,6 @@ jobs:
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
- name: Download rust-analyzer
|
|
||||||
run: |
|
|
||||||
script/download-rust-analyzer
|
|
||||||
echo "$PWD/vendor/bin" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test --workspace --no-fail-fast
|
run: cargo test --workspace --no-fail-fast
|
||||||
|
|
||||||
|
@ -69,14 +64,11 @@ jobs:
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
- name: Validate version
|
- name: Validate version
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
run: script/validate-version
|
run: script/validate-version
|
||||||
|
|
||||||
- name: Download rust-analyzer
|
|
||||||
run: script/download-rust-analyzer
|
|
||||||
|
|
||||||
- name: Create app bundle
|
- name: Create app bundle
|
||||||
run: script/bundle
|
run: script/bundle
|
||||||
|
|
||||||
|
|
19
Cargo.lock
generated
19
Cargo.lock
generated
|
@ -178,6 +178,17 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-broadcast"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90622698a1218e0b2fb846c97b5f19a0831f6baddee73d9454156365ccfa473b"
|
||||||
|
dependencies = [
|
||||||
|
"easy-parallel",
|
||||||
|
"event-listener",
|
||||||
|
"futures-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-channel"
|
name = "async-channel"
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
|
@ -1926,9 +1937,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.12"
|
version = "0.3.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65"
|
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-executor"
|
name = "futures-executor"
|
||||||
|
@ -2619,7 +2630,9 @@ name = "language"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-broadcast",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
"ctor",
|
"ctor",
|
||||||
|
@ -5701,6 +5714,7 @@ dependencies = [
|
||||||
"client",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
|
"futures",
|
||||||
"gpui",
|
"gpui",
|
||||||
"language",
|
"language",
|
||||||
"log",
|
"log",
|
||||||
|
@ -5741,6 +5755,7 @@ name = "zed"
|
||||||
version = "0.15.2"
|
version = "0.15.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-compression",
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"chat_panel",
|
"chat_panel",
|
||||||
|
|
|
@ -224,6 +224,10 @@ impl Client {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn http_client(&self) -> Arc<dyn HttpClient> {
|
||||||
|
self.http.clone()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub fn override_authenticate<F>(&mut self, authenticate: F) -> &mut Self
|
pub fn override_authenticate<F>(&mut self, authenticate: F) -> &mut Self
|
||||||
where
|
where
|
||||||
|
|
|
@ -410,8 +410,6 @@ impl View for DiagnosticMessage {
|
||||||
diagnostic.message.split('\n').next().unwrap().to_string(),
|
diagnostic.message.split('\n').next().unwrap().to_string(),
|
||||||
theme.diagnostic_message.clone(),
|
theme.diagnostic_message.clone(),
|
||||||
)
|
)
|
||||||
.contained()
|
|
||||||
.with_margin_left(theme.item_spacing)
|
|
||||||
.boxed()
|
.boxed()
|
||||||
} else {
|
} else {
|
||||||
Empty::new().boxed()
|
Empty::new().boxed()
|
||||||
|
|
|
@ -9,6 +9,7 @@ path = "src/language.rs"
|
||||||
[features]
|
[features]
|
||||||
test-support = [
|
test-support = [
|
||||||
"rand",
|
"rand",
|
||||||
|
"client/test-support",
|
||||||
"collections/test-support",
|
"collections/test-support",
|
||||||
"lsp/test-support",
|
"lsp/test-support",
|
||||||
"text/test-support",
|
"text/test-support",
|
||||||
|
@ -17,6 +18,7 @@ test-support = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
client = { path = "../client" }
|
||||||
clock = { path = "../clock" }
|
clock = { path = "../clock" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
fuzzy = { path = "../fuzzy" }
|
fuzzy = { path = "../fuzzy" }
|
||||||
|
@ -28,6 +30,7 @@ text = { path = "../text" }
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
|
async-broadcast = "0.3.4"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
|
@ -44,6 +47,7 @@ tree-sitter = "0.20"
|
||||||
tree-sitter-rust = { version = "0.20.0", optional = true }
|
tree-sitter-rust = { version = "0.20.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
client = { path = "../client", features = ["test-support"] }
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
lsp = { path = "../lsp", features = ["test-support"] }
|
lsp = { path = "../lsp", features = ["test-support"] }
|
||||||
|
|
|
@ -7,15 +7,27 @@ pub mod proto;
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use client::http::{self, HttpClient};
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use gpui::AppContext;
|
use futures::{
|
||||||
|
future::{BoxFuture, Shared},
|
||||||
|
FutureExt, TryFutureExt,
|
||||||
|
};
|
||||||
|
use gpui::{AppContext, Task};
|
||||||
use highlight_map::HighlightMap;
|
use highlight_map::HighlightMap;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{cell::RefCell, ops::Range, path::Path, str, sync::Arc};
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
|
ops::Range,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
str,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
use theme::SyntaxTheme;
|
use theme::SyntaxTheme;
|
||||||
use tree_sitter::{self, Query};
|
use tree_sitter::{self, Query};
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
use futures::channel::mpsc;
|
use futures::channel::mpsc;
|
||||||
|
@ -47,7 +59,23 @@ pub trait ToLspPosition {
|
||||||
fn to_lsp_position(self) -> lsp::Position;
|
fn to_lsp_position(self) -> lsp::Position;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait LspPostProcessor: 'static + Send + Sync {
|
pub struct LspBinaryVersion {
|
||||||
|
pub name: String,
|
||||||
|
pub url: http::Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait LspExt: 'static + Send + Sync {
|
||||||
|
fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
http: Arc<dyn HttpClient>,
|
||||||
|
) -> BoxFuture<'static, Result<LspBinaryVersion>>;
|
||||||
|
fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
version: LspBinaryVersion,
|
||||||
|
http: Arc<dyn HttpClient>,
|
||||||
|
download_dir: Arc<Path>,
|
||||||
|
) -> BoxFuture<'static, Result<PathBuf>>;
|
||||||
|
fn cached_server_binary(&self, download_dir: Arc<Path>) -> BoxFuture<'static, Option<PathBuf>>;
|
||||||
fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams);
|
fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams);
|
||||||
fn label_for_completion(
|
fn label_for_completion(
|
||||||
&self,
|
&self,
|
||||||
|
@ -77,7 +105,6 @@ pub struct LanguageConfig {
|
||||||
|
|
||||||
#[derive(Default, Deserialize)]
|
#[derive(Default, Deserialize)]
|
||||||
pub struct LanguageServerConfig {
|
pub struct LanguageServerConfig {
|
||||||
pub binary: String,
|
|
||||||
pub disk_based_diagnostic_sources: HashSet<String>,
|
pub disk_based_diagnostic_sources: HashSet<String>,
|
||||||
pub disk_based_diagnostics_progress_token: Option<String>,
|
pub disk_based_diagnostics_progress_token: Option<String>,
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
@ -103,7 +130,8 @@ pub struct BracketPair {
|
||||||
pub struct Language {
|
pub struct Language {
|
||||||
pub(crate) config: LanguageConfig,
|
pub(crate) config: LanguageConfig,
|
||||||
pub(crate) grammar: Option<Arc<Grammar>>,
|
pub(crate) grammar: Option<Arc<Grammar>>,
|
||||||
pub(crate) lsp_post_processor: Option<Box<dyn LspPostProcessor>>,
|
pub(crate) lsp_ext: Option<Arc<dyn LspExt>>,
|
||||||
|
lsp_binary_path: Mutex<Option<Shared<BoxFuture<'static, Result<PathBuf, Arc<anyhow::Error>>>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Grammar {
|
pub struct Grammar {
|
||||||
|
@ -115,18 +143,35 @@ pub struct Grammar {
|
||||||
pub(crate) highlight_map: Mutex<HighlightMap>,
|
pub(crate) highlight_map: Mutex<HighlightMap>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Clone)]
|
||||||
|
pub enum LanguageServerBinaryStatus {
|
||||||
|
CheckingForUpdate,
|
||||||
|
Downloading,
|
||||||
|
Downloaded,
|
||||||
|
Cached,
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct LanguageRegistry {
|
pub struct LanguageRegistry {
|
||||||
languages: Vec<Arc<Language>>,
|
languages: Vec<Arc<Language>>,
|
||||||
|
language_server_download_dir: Option<Arc<Path>>,
|
||||||
|
lsp_binary_statuses_tx: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
|
||||||
|
lsp_binary_statuses_rx: async_broadcast::Receiver<(Arc<Language>, LanguageServerBinaryStatus)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageRegistry {
|
impl LanguageRegistry {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
let (lsp_binary_statuses_tx, lsp_binary_statuses_rx) = async_broadcast::broadcast(16);
|
||||||
|
Self {
|
||||||
|
language_server_download_dir: None,
|
||||||
|
languages: Default::default(),
|
||||||
|
lsp_binary_statuses_tx,
|
||||||
|
lsp_binary_statuses_rx,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(&mut self, language: Arc<Language>) {
|
pub fn add(&mut self, language: Arc<Language>) {
|
||||||
self.languages.push(language);
|
self.languages.push(language.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_theme(&self, theme: &SyntaxTheme) {
|
pub fn set_theme(&self, theme: &SyntaxTheme) {
|
||||||
|
@ -135,6 +180,10 @@ impl LanguageRegistry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_language_server_download_dir(&mut self, path: impl Into<Arc<Path>>) {
|
||||||
|
self.language_server_download_dir = Some(path.into());
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_language(&self, name: &str) -> Option<&Arc<Language>> {
|
pub fn get_language(&self, name: &str) -> Option<&Arc<Language>> {
|
||||||
self.languages
|
self.languages
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -154,6 +203,140 @@ impl LanguageRegistry {
|
||||||
.any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
|
.any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn start_language_server(
|
||||||
|
&self,
|
||||||
|
language: &Arc<Language>,
|
||||||
|
root_path: Arc<Path>,
|
||||||
|
http_client: Arc<dyn HttpClient>,
|
||||||
|
cx: &AppContext,
|
||||||
|
) -> Option<Task<Result<Arc<lsp::LanguageServer>>>> {
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
if let Some(config) = &language.config.language_server {
|
||||||
|
if let Some(fake_config) = &config.fake_config {
|
||||||
|
use postage::prelude::Stream;
|
||||||
|
|
||||||
|
let (server, mut fake_server) = lsp::LanguageServer::fake_with_capabilities(
|
||||||
|
fake_config.capabilities.clone(),
|
||||||
|
cx.background().clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(initalizer) = &fake_config.initializer {
|
||||||
|
initalizer(&mut fake_server);
|
||||||
|
}
|
||||||
|
|
||||||
|
let servers_tx = fake_config.servers_tx.clone();
|
||||||
|
let mut initialized = server.capabilities();
|
||||||
|
cx.background()
|
||||||
|
.spawn(async move {
|
||||||
|
while initialized.recv().await.is_none() {}
|
||||||
|
servers_tx.unbounded_send(fake_server).ok();
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
return Some(Task::ready(Ok(server.clone())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let download_dir = self
|
||||||
|
.language_server_download_dir
|
||||||
|
.clone()
|
||||||
|
.ok_or_else(|| anyhow!("language server download directory has not been assigned"))
|
||||||
|
.log_err()?;
|
||||||
|
|
||||||
|
let lsp_ext = language.lsp_ext.clone()?;
|
||||||
|
let background = cx.background().clone();
|
||||||
|
let server_binary_path = {
|
||||||
|
Some(
|
||||||
|
language
|
||||||
|
.lsp_binary_path
|
||||||
|
.lock()
|
||||||
|
.get_or_insert_with(|| {
|
||||||
|
get_server_binary_path(
|
||||||
|
lsp_ext,
|
||||||
|
language.clone(),
|
||||||
|
http_client,
|
||||||
|
download_dir,
|
||||||
|
self.lsp_binary_statuses_tx.clone(),
|
||||||
|
)
|
||||||
|
.map_err(Arc::new)
|
||||||
|
.boxed()
|
||||||
|
.shared()
|
||||||
|
})
|
||||||
|
.clone()
|
||||||
|
.map_err(|e| anyhow!(e)),
|
||||||
|
)
|
||||||
|
}?;
|
||||||
|
Some(cx.background().spawn(async move {
|
||||||
|
let server_binary_path = server_binary_path.await?;
|
||||||
|
let server = lsp::LanguageServer::new(&server_binary_path, &root_path, background)?;
|
||||||
|
Ok(server)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn language_server_binary_statuses(
|
||||||
|
&self,
|
||||||
|
) -> async_broadcast::Receiver<(Arc<Language>, LanguageServerBinaryStatus)> {
|
||||||
|
self.lsp_binary_statuses_rx.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_server_binary_path(
|
||||||
|
lsp_ext: Arc<dyn LspExt>,
|
||||||
|
language: Arc<Language>,
|
||||||
|
http_client: Arc<dyn HttpClient>,
|
||||||
|
download_dir: Arc<Path>,
|
||||||
|
statuses: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
|
||||||
|
) -> Result<PathBuf> {
|
||||||
|
let path = fetch_latest_server_binary_path(
|
||||||
|
lsp_ext.clone(),
|
||||||
|
language.clone(),
|
||||||
|
http_client,
|
||||||
|
download_dir.clone(),
|
||||||
|
statuses.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
if path.is_err() {
|
||||||
|
if let Some(cached_path) = lsp_ext.cached_server_binary(download_dir).await {
|
||||||
|
statuses
|
||||||
|
.broadcast((language.clone(), LanguageServerBinaryStatus::Cached))
|
||||||
|
.await?;
|
||||||
|
return Ok(cached_path);
|
||||||
|
} else {
|
||||||
|
statuses
|
||||||
|
.broadcast((language.clone(), LanguageServerBinaryStatus::Failed))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_server_binary_path(
|
||||||
|
lsp_ext: Arc<dyn LspExt>,
|
||||||
|
language: Arc<Language>,
|
||||||
|
http_client: Arc<dyn HttpClient>,
|
||||||
|
download_dir: Arc<Path>,
|
||||||
|
lsp_binary_statuses_tx: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
|
||||||
|
) -> Result<PathBuf> {
|
||||||
|
lsp_binary_statuses_tx
|
||||||
|
.broadcast((
|
||||||
|
language.clone(),
|
||||||
|
LanguageServerBinaryStatus::CheckingForUpdate,
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
let version_info = lsp_ext
|
||||||
|
.fetch_latest_server_version(http_client.clone())
|
||||||
|
.await?;
|
||||||
|
lsp_binary_statuses_tx
|
||||||
|
.broadcast((language.clone(), LanguageServerBinaryStatus::Downloading))
|
||||||
|
.await?;
|
||||||
|
let path = lsp_ext
|
||||||
|
.fetch_server_binary(version_info, http_client, download_dir)
|
||||||
|
.await?;
|
||||||
|
lsp_binary_statuses_tx
|
||||||
|
.broadcast((language.clone(), LanguageServerBinaryStatus::Downloaded))
|
||||||
|
.await?;
|
||||||
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Language {
|
impl Language {
|
||||||
|
@ -170,7 +353,8 @@ impl Language {
|
||||||
highlight_map: Default::default(),
|
highlight_map: Default::default(),
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
lsp_post_processor: None,
|
lsp_ext: None,
|
||||||
|
lsp_binary_path: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,8 +398,8 @@ impl Language {
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_lsp_post_processor(mut self, processor: impl LspPostProcessor) -> Self {
|
pub fn with_lsp_ext(mut self, lsp_ext: impl LspExt) -> Self {
|
||||||
self.lsp_post_processor = Some(Box::new(processor));
|
self.lsp_ext = Some(Arc::new(lsp_ext));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,50 +411,6 @@ impl Language {
|
||||||
self.config.line_comment.as_deref()
|
self.config.line_comment.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_server(
|
|
||||||
&self,
|
|
||||||
root_path: &Path,
|
|
||||||
cx: &AppContext,
|
|
||||||
) -> Result<Option<Arc<lsp::LanguageServer>>> {
|
|
||||||
if let Some(config) = &self.config.language_server {
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
if let Some(fake_config) = &config.fake_config {
|
|
||||||
use postage::prelude::Stream;
|
|
||||||
|
|
||||||
let (server, mut fake_server) = lsp::LanguageServer::fake_with_capabilities(
|
|
||||||
fake_config.capabilities.clone(),
|
|
||||||
cx.background().clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(initalizer) = &fake_config.initializer {
|
|
||||||
initalizer(&mut fake_server);
|
|
||||||
}
|
|
||||||
|
|
||||||
let servers_tx = fake_config.servers_tx.clone();
|
|
||||||
let mut initialized = server.capabilities();
|
|
||||||
cx.background()
|
|
||||||
.spawn(async move {
|
|
||||||
while initialized.recv().await.is_none() {}
|
|
||||||
servers_tx.unbounded_send(fake_server).ok();
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
return Ok(Some(server.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
const ZED_BUNDLE: Option<&'static str> = option_env!("ZED_BUNDLE");
|
|
||||||
let binary_path = if ZED_BUNDLE.map_or(Ok(false), |b| b.parse())? {
|
|
||||||
cx.platform()
|
|
||||||
.path_for_resource(Some(&config.binary), None)?
|
|
||||||
} else {
|
|
||||||
Path::new(&config.binary).to_path_buf()
|
|
||||||
};
|
|
||||||
lsp::LanguageServer::new(&binary_path, root_path, cx.background().clone()).map(Some)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet<String>> {
|
pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet<String>> {
|
||||||
self.config
|
self.config
|
||||||
.language_server
|
.language_server
|
||||||
|
@ -286,7 +426,7 @@ impl Language {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) {
|
pub fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) {
|
||||||
if let Some(processor) = self.lsp_post_processor.as_ref() {
|
if let Some(processor) = self.lsp_ext.as_ref() {
|
||||||
processor.process_diagnostics(diagnostics);
|
processor.process_diagnostics(diagnostics);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -295,7 +435,7 @@ impl Language {
|
||||||
&self,
|
&self,
|
||||||
completion: &lsp::CompletionItem,
|
completion: &lsp::CompletionItem,
|
||||||
) -> Option<CompletionLabel> {
|
) -> Option<CompletionLabel> {
|
||||||
self.lsp_post_processor
|
self.lsp_ext
|
||||||
.as_ref()?
|
.as_ref()?
|
||||||
.label_for_completion(completion, self)
|
.label_for_completion(completion, self)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,28 +22,25 @@ fn init_logger() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[gpui::test]
|
||||||
fn test_select_language() {
|
fn test_select_language() {
|
||||||
let registry = LanguageRegistry {
|
let mut registry = LanguageRegistry::new();
|
||||||
languages: vec![
|
registry.add(Arc::new(Language::new(
|
||||||
Arc::new(Language::new(
|
LanguageConfig {
|
||||||
LanguageConfig {
|
name: "Rust".to_string(),
|
||||||
name: "Rust".to_string(),
|
path_suffixes: vec!["rs".to_string()],
|
||||||
path_suffixes: vec!["rs".to_string()],
|
..Default::default()
|
||||||
..Default::default()
|
},
|
||||||
},
|
Some(tree_sitter_rust::language()),
|
||||||
Some(tree_sitter_rust::language()),
|
)));
|
||||||
)),
|
registry.add(Arc::new(Language::new(
|
||||||
Arc::new(Language::new(
|
LanguageConfig {
|
||||||
LanguageConfig {
|
name: "Make".to_string(),
|
||||||
name: "Make".to_string(),
|
path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
|
||||||
path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
|
..Default::default()
|
||||||
..Default::default()
|
},
|
||||||
},
|
Some(tree_sitter_rust::language()),
|
||||||
Some(tree_sitter_rust::language()),
|
)));
|
||||||
)),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
// matching file extension
|
// matching file extension
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -700,8 +700,6 @@ impl FakeLanguageServer {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
use unindent::Unindent;
|
|
||||||
use util::test::temp_tree;
|
|
||||||
|
|
||||||
#[ctor::ctor]
|
#[ctor::ctor]
|
||||||
fn init_logger() {
|
fn init_logger() {
|
||||||
|
@ -710,64 +708,6 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_rust_analyzer(cx: TestAppContext) {
|
|
||||||
let lib_source = r#"
|
|
||||||
fn fun() {
|
|
||||||
let hello = "world";
|
|
||||||
}
|
|
||||||
"#
|
|
||||||
.unindent();
|
|
||||||
let root_dir = temp_tree(json!({
|
|
||||||
"Cargo.toml": r#"
|
|
||||||
[package]
|
|
||||||
name = "temp"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2018"
|
|
||||||
"#.unindent(),
|
|
||||||
"src": {
|
|
||||||
"lib.rs": &lib_source
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
let lib_file_uri = Url::from_file_path(root_dir.path().join("src/lib.rs")).unwrap();
|
|
||||||
|
|
||||||
let server =
|
|
||||||
LanguageServer::new(Path::new("rust-analyzer"), root_dir.path(), cx.background())
|
|
||||||
.unwrap();
|
|
||||||
server.next_idle_notification().await;
|
|
||||||
|
|
||||||
server
|
|
||||||
.notify::<notification::DidOpenTextDocument>(DidOpenTextDocumentParams {
|
|
||||||
text_document: TextDocumentItem::new(
|
|
||||||
lib_file_uri.clone(),
|
|
||||||
"rust".to_string(),
|
|
||||||
0,
|
|
||||||
lib_source,
|
|
||||||
),
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let hover = server
|
|
||||||
.request::<request::HoverRequest>(HoverParams {
|
|
||||||
text_document_position_params: TextDocumentPositionParams {
|
|
||||||
text_document: TextDocumentIdentifier::new(lib_file_uri),
|
|
||||||
position: Position::new(1, 21),
|
|
||||||
},
|
|
||||||
work_done_progress_params: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
hover.contents,
|
|
||||||
HoverContents::Markup(MarkupContent {
|
|
||||||
kind: MarkupKind::PlainText,
|
|
||||||
value: "&str".to_string()
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_fake(cx: TestAppContext) {
|
async fn test_fake(cx: TestAppContext) {
|
||||||
let (server, mut fake) = LanguageServer::fake(cx.background());
|
let (server, mut fake) = LanguageServer::fake(cx.background());
|
||||||
|
@ -828,19 +768,6 @@ mod tests {
|
||||||
fake.receive_notification::<notification::Exit>().await;
|
fake.receive_notification::<notification::Exit>().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageServer {
|
|
||||||
async fn next_idle_notification(self: &Arc<Self>) {
|
|
||||||
let (tx, rx) = channel::unbounded();
|
|
||||||
let _subscription =
|
|
||||||
self.on_notification::<ServerStatusNotification, _>(move |params| {
|
|
||||||
if params.quiescent {
|
|
||||||
tx.try_send(()).unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let _ = rx.recv().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ServerStatusNotification {}
|
pub enum ServerStatusNotification {}
|
||||||
|
|
||||||
impl notification::Notification for ServerStatusNotification {
|
impl notification::Notification for ServerStatusNotification {
|
||||||
|
|
|
@ -7,7 +7,7 @@ use anyhow::{anyhow, Context, Result};
|
||||||
use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
|
use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{hash_map, HashMap, HashSet};
|
use collections::{hash_map, HashMap, HashSet};
|
||||||
use futures::Future;
|
use futures::{future::Shared, Future, FutureExt};
|
||||||
use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
|
use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task,
|
AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task,
|
||||||
|
@ -39,6 +39,8 @@ pub struct Project {
|
||||||
active_entry: Option<ProjectEntry>,
|
active_entry: Option<ProjectEntry>,
|
||||||
languages: Arc<LanguageRegistry>,
|
languages: Arc<LanguageRegistry>,
|
||||||
language_servers: HashMap<(WorktreeId, String), Arc<LanguageServer>>,
|
language_servers: HashMap<(WorktreeId, String), Arc<LanguageServer>>,
|
||||||
|
started_language_servers:
|
||||||
|
HashMap<(WorktreeId, String), Shared<Task<Option<Arc<LanguageServer>>>>>,
|
||||||
client: Arc<client::Client>,
|
client: Arc<client::Client>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: ModelHandle<UserStore>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
|
@ -258,6 +260,7 @@ impl Project {
|
||||||
fs,
|
fs,
|
||||||
language_servers_with_diagnostics_running: 0,
|
language_servers_with_diagnostics_running: 0,
|
||||||
language_servers: Default::default(),
|
language_servers: Default::default(),
|
||||||
|
started_language_servers: Default::default(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -309,6 +312,7 @@ impl Project {
|
||||||
},
|
},
|
||||||
language_servers_with_diagnostics_running: 0,
|
language_servers_with_diagnostics_running: 0,
|
||||||
language_servers: Default::default(),
|
language_servers: Default::default(),
|
||||||
|
started_language_servers: Default::default(),
|
||||||
};
|
};
|
||||||
for worktree in worktrees {
|
for worktree in worktrees {
|
||||||
this.add_worktree(&worktree, cx);
|
this.add_worktree(&worktree, cx);
|
||||||
|
@ -776,7 +780,7 @@ impl Project {
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the buffer has a language, set it and start/assign the language server
|
// If the buffer has a language, set it and start/assign the language server
|
||||||
if let Some(language) = self.languages.select_language(&full_path) {
|
if let Some(language) = self.languages.select_language(&full_path).cloned() {
|
||||||
buffer.update(cx, |buffer, cx| {
|
buffer.update(cx, |buffer, cx| {
|
||||||
buffer.set_language(Some(language.clone()), cx);
|
buffer.set_language(Some(language.clone()), cx);
|
||||||
});
|
});
|
||||||
|
@ -786,24 +790,20 @@ impl Project {
|
||||||
if let Some(local_worktree) = worktree.and_then(|w| w.read(cx).as_local()) {
|
if let Some(local_worktree) = worktree.and_then(|w| w.read(cx).as_local()) {
|
||||||
let worktree_id = local_worktree.id();
|
let worktree_id = local_worktree.id();
|
||||||
let worktree_abs_path = local_worktree.abs_path().clone();
|
let worktree_abs_path = local_worktree.abs_path().clone();
|
||||||
|
let buffer = buffer.downgrade();
|
||||||
|
let language_server =
|
||||||
|
self.start_language_server(worktree_id, worktree_abs_path, language, cx);
|
||||||
|
|
||||||
let language_server = match self
|
cx.spawn_weak(|_, mut cx| async move {
|
||||||
.language_servers
|
if let Some(language_server) = language_server.await {
|
||||||
.entry((worktree_id, language.name().to_string()))
|
if let Some(buffer) = buffer.upgrade(&cx) {
|
||||||
{
|
buffer.update(&mut cx, |buffer, cx| {
|
||||||
hash_map::Entry::Occupied(e) => Some(e.get().clone()),
|
buffer.set_language_server(Some(language_server), cx);
|
||||||
hash_map::Entry::Vacant(e) => Self::start_language_server(
|
});
|
||||||
self.client.clone(),
|
}
|
||||||
language.clone(),
|
}
|
||||||
&worktree_abs_path,
|
})
|
||||||
cx,
|
.detach();
|
||||||
)
|
|
||||||
.map(|server| e.insert(server).clone()),
|
|
||||||
};
|
|
||||||
|
|
||||||
buffer.update(cx, |buffer, cx| {
|
|
||||||
buffer.set_language_server(language_server, cx);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -819,116 +819,151 @@ impl Project {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_language_server(
|
fn start_language_server(
|
||||||
rpc: Arc<Client>,
|
&mut self,
|
||||||
|
worktree_id: WorktreeId,
|
||||||
|
worktree_path: Arc<Path>,
|
||||||
language: Arc<Language>,
|
language: Arc<Language>,
|
||||||
worktree_path: &Path,
|
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Option<Arc<LanguageServer>> {
|
) -> Shared<Task<Option<Arc<LanguageServer>>>> {
|
||||||
enum LspEvent {
|
enum LspEvent {
|
||||||
DiagnosticsStart,
|
DiagnosticsStart,
|
||||||
DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
|
DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
|
||||||
DiagnosticsFinish,
|
DiagnosticsFinish,
|
||||||
}
|
}
|
||||||
|
|
||||||
let language_server = language
|
let key = (worktree_id, language.name().to_string());
|
||||||
.start_server(worktree_path, cx)
|
self.started_language_servers
|
||||||
.log_err()
|
.entry(key.clone())
|
||||||
.flatten()?;
|
.or_insert_with(|| {
|
||||||
let disk_based_sources = language
|
let language_server = self.languages.start_language_server(
|
||||||
.disk_based_diagnostic_sources()
|
&language,
|
||||||
.cloned()
|
worktree_path,
|
||||||
.unwrap_or_default();
|
self.client.http_client(),
|
||||||
let disk_based_diagnostics_progress_token =
|
cx,
|
||||||
language.disk_based_diagnostics_progress_token().cloned();
|
);
|
||||||
let has_disk_based_diagnostic_progress_token =
|
let rpc = self.client.clone();
|
||||||
disk_based_diagnostics_progress_token.is_some();
|
cx.spawn_weak(|this, mut cx| async move {
|
||||||
let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded();
|
let language_server = language_server?.await.log_err()?;
|
||||||
|
if let Some(this) = this.upgrade(&cx) {
|
||||||
// Listen for `PublishDiagnostics` notifications.
|
this.update(&mut cx, |this, _| {
|
||||||
language_server
|
this.language_servers.insert(key, language_server.clone());
|
||||||
.on_notification::<lsp::notification::PublishDiagnostics, _>({
|
});
|
||||||
let diagnostics_tx = diagnostics_tx.clone();
|
|
||||||
move |params| {
|
|
||||||
if !has_disk_based_diagnostic_progress_token {
|
|
||||||
block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok();
|
|
||||||
}
|
}
|
||||||
block_on(diagnostics_tx.send(LspEvent::DiagnosticsUpdate(params))).ok();
|
|
||||||
if !has_disk_based_diagnostic_progress_token {
|
|
||||||
block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
// Listen for `Progress` notifications. Send an event when the language server
|
let disk_based_sources = language
|
||||||
// transitions between running jobs and not running any jobs.
|
.disk_based_diagnostic_sources()
|
||||||
let mut running_jobs_for_this_server: i32 = 0;
|
.cloned()
|
||||||
language_server
|
.unwrap_or_default();
|
||||||
.on_notification::<lsp::notification::Progress, _>(move |params| {
|
let disk_based_diagnostics_progress_token =
|
||||||
let token = match params.token {
|
language.disk_based_diagnostics_progress_token().cloned();
|
||||||
lsp::NumberOrString::Number(_) => None,
|
let has_disk_based_diagnostic_progress_token =
|
||||||
lsp::NumberOrString::String(token) => Some(token),
|
disk_based_diagnostics_progress_token.is_some();
|
||||||
};
|
let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded();
|
||||||
|
|
||||||
if token == disk_based_diagnostics_progress_token {
|
// Listen for `PublishDiagnostics` notifications.
|
||||||
match params.value {
|
language_server
|
||||||
lsp::ProgressParamsValue::WorkDone(progress) => match progress {
|
.on_notification::<lsp::notification::PublishDiagnostics, _>({
|
||||||
lsp::WorkDoneProgress::Begin(_) => {
|
let diagnostics_tx = diagnostics_tx.clone();
|
||||||
running_jobs_for_this_server += 1;
|
move |params| {
|
||||||
if running_jobs_for_this_server == 1 {
|
if !has_disk_based_diagnostic_progress_token {
|
||||||
block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok();
|
block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok();
|
||||||
}
|
}
|
||||||
}
|
block_on(diagnostics_tx.send(LspEvent::DiagnosticsUpdate(params)))
|
||||||
lsp::WorkDoneProgress::End(_) => {
|
.ok();
|
||||||
running_jobs_for_this_server -= 1;
|
if !has_disk_based_diagnostic_progress_token {
|
||||||
if running_jobs_for_this_server == 0 {
|
|
||||||
block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok();
|
block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
})
|
||||||
},
|
.detach();
|
||||||
}
|
|
||||||
}
|
// Listen for `Progress` notifications. Send an event when the language server
|
||||||
|
// transitions between running jobs and not running any jobs.
|
||||||
|
let mut running_jobs_for_this_server: i32 = 0;
|
||||||
|
language_server
|
||||||
|
.on_notification::<lsp::notification::Progress, _>(move |params| {
|
||||||
|
let token = match params.token {
|
||||||
|
lsp::NumberOrString::Number(_) => None,
|
||||||
|
lsp::NumberOrString::String(token) => Some(token),
|
||||||
|
};
|
||||||
|
|
||||||
|
if token == disk_based_diagnostics_progress_token {
|
||||||
|
match params.value {
|
||||||
|
lsp::ProgressParamsValue::WorkDone(progress) => {
|
||||||
|
match progress {
|
||||||
|
lsp::WorkDoneProgress::Begin(_) => {
|
||||||
|
running_jobs_for_this_server += 1;
|
||||||
|
if running_jobs_for_this_server == 1 {
|
||||||
|
block_on(
|
||||||
|
diagnostics_tx
|
||||||
|
.send(LspEvent::DiagnosticsStart),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lsp::WorkDoneProgress::End(_) => {
|
||||||
|
running_jobs_for_this_server -= 1;
|
||||||
|
if running_jobs_for_this_server == 0 {
|
||||||
|
block_on(
|
||||||
|
diagnostics_tx
|
||||||
|
.send(LspEvent::DiagnosticsFinish),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
// Process all the LSP events.
|
||||||
|
cx.spawn(|mut cx| async move {
|
||||||
|
while let Ok(message) = diagnostics_rx.recv().await {
|
||||||
|
let this = this.upgrade(&cx)?;
|
||||||
|
match message {
|
||||||
|
LspEvent::DiagnosticsStart => {
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.disk_based_diagnostics_started(cx);
|
||||||
|
if let Some(project_id) = this.remote_id() {
|
||||||
|
rpc.send(proto::DiskBasedDiagnosticsUpdating {
|
||||||
|
project_id,
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
LspEvent::DiagnosticsUpdate(mut params) => {
|
||||||
|
language.process_diagnostics(&mut params);
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.update_diagnostics(params, &disk_based_sources, cx)
|
||||||
|
.log_err();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
LspEvent::DiagnosticsFinish => {
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.disk_based_diagnostics_finished(cx);
|
||||||
|
if let Some(project_id) = this.remote_id() {
|
||||||
|
rpc.send(proto::DiskBasedDiagnosticsUpdated {
|
||||||
|
project_id,
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(())
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
Some(language_server)
|
||||||
|
})
|
||||||
|
.shared()
|
||||||
})
|
})
|
||||||
.detach();
|
.clone()
|
||||||
|
|
||||||
// Process all the LSP events.
|
|
||||||
cx.spawn_weak(|this, mut cx| async move {
|
|
||||||
while let Ok(message) = diagnostics_rx.recv().await {
|
|
||||||
let this = this.upgrade(&cx)?;
|
|
||||||
match message {
|
|
||||||
LspEvent::DiagnosticsStart => {
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.disk_based_diagnostics_started(cx);
|
|
||||||
if let Some(project_id) = this.remote_id() {
|
|
||||||
rpc.send(proto::DiskBasedDiagnosticsUpdating { project_id })
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
LspEvent::DiagnosticsUpdate(mut params) => {
|
|
||||||
language.process_diagnostics(&mut params);
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.update_diagnostics(params, &disk_based_sources, cx)
|
|
||||||
.log_err();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
LspEvent::DiagnosticsFinish => {
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.disk_based_diagnostics_finished(cx);
|
|
||||||
if let Some(project_id) = this.remote_id() {
|
|
||||||
rpc.send(proto::DiskBasedDiagnosticsUpdated { project_id })
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(())
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
Some(language_server)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_diagnostics(
|
pub fn update_diagnostics(
|
||||||
|
@ -2857,8 +2892,6 @@ impl Entity for Project {
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &mut MutableAppContext,
|
_: &mut MutableAppContext,
|
||||||
) -> Option<std::pin::Pin<Box<dyn 'static + Future<Output = ()>>>> {
|
) -> Option<std::pin::Pin<Box<dyn 'static + Future<Output = ()>>>> {
|
||||||
use futures::FutureExt;
|
|
||||||
|
|
||||||
let shutdown_futures = self
|
let shutdown_futures = self
|
||||||
.language_servers
|
.language_servers
|
||||||
.drain()
|
.drain()
|
||||||
|
|
|
@ -2001,9 +2001,8 @@ mod tests {
|
||||||
|
|
||||||
// Set up a fake language server.
|
// Set up a fake language server.
|
||||||
let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
|
let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
|
||||||
Arc::get_mut(&mut lang_registry)
|
Arc::get_mut(&mut lang_registry).unwrap().add(
|
||||||
.unwrap()
|
Arc::new(Language::new(
|
||||||
.add(Arc::new(Language::new(
|
|
||||||
LanguageConfig {
|
LanguageConfig {
|
||||||
name: "Rust".to_string(),
|
name: "Rust".to_string(),
|
||||||
path_suffixes: vec!["rs".to_string()],
|
path_suffixes: vec!["rs".to_string()],
|
||||||
|
@ -2011,7 +2010,9 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::language()),
|
||||||
)));
|
)),
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
// Connect to a server as 2 clients.
|
// Connect to a server as 2 clients.
|
||||||
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
|
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
|
||||||
|
@ -2232,9 +2233,8 @@ mod tests {
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
Arc::get_mut(&mut lang_registry)
|
Arc::get_mut(&mut lang_registry).unwrap().add(
|
||||||
.unwrap()
|
Arc::new(Language::new(
|
||||||
.add(Arc::new(Language::new(
|
|
||||||
LanguageConfig {
|
LanguageConfig {
|
||||||
name: "Rust".to_string(),
|
name: "Rust".to_string(),
|
||||||
path_suffixes: vec!["rs".to_string()],
|
path_suffixes: vec!["rs".to_string()],
|
||||||
|
@ -2242,7 +2242,9 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::language()),
|
||||||
)));
|
)),
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
// Connect to a server as 2 clients.
|
// Connect to a server as 2 clients.
|
||||||
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
|
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
|
||||||
|
@ -2434,9 +2436,8 @@ mod tests {
|
||||||
|
|
||||||
// Set up a fake language server.
|
// Set up a fake language server.
|
||||||
let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
|
let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
|
||||||
Arc::get_mut(&mut lang_registry)
|
Arc::get_mut(&mut lang_registry).unwrap().add(
|
||||||
.unwrap()
|
Arc::new(Language::new(
|
||||||
.add(Arc::new(Language::new(
|
|
||||||
LanguageConfig {
|
LanguageConfig {
|
||||||
name: "Rust".to_string(),
|
name: "Rust".to_string(),
|
||||||
path_suffixes: vec!["rs".to_string()],
|
path_suffixes: vec!["rs".to_string()],
|
||||||
|
@ -2444,7 +2445,9 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::language()),
|
||||||
)));
|
)),
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
// Connect to a server as 2 clients.
|
// Connect to a server as 2 clients.
|
||||||
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
|
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
|
||||||
|
@ -2551,9 +2554,8 @@ mod tests {
|
||||||
|
|
||||||
// Set up a fake language server.
|
// Set up a fake language server.
|
||||||
let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
|
let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
|
||||||
Arc::get_mut(&mut lang_registry)
|
Arc::get_mut(&mut lang_registry).unwrap().add(
|
||||||
.unwrap()
|
Arc::new(Language::new(
|
||||||
.add(Arc::new(Language::new(
|
|
||||||
LanguageConfig {
|
LanguageConfig {
|
||||||
name: "Rust".to_string(),
|
name: "Rust".to_string(),
|
||||||
path_suffixes: vec!["rs".to_string()],
|
path_suffixes: vec!["rs".to_string()],
|
||||||
|
@ -2561,7 +2563,9 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::language()),
|
||||||
)));
|
)),
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
// Connect to a server as 2 clients.
|
// Connect to a server as 2 clients.
|
||||||
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
|
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
|
||||||
|
@ -2699,9 +2703,8 @@ mod tests {
|
||||||
// Set up a fake language server.
|
// Set up a fake language server.
|
||||||
let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
|
let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
|
||||||
|
|
||||||
Arc::get_mut(&mut lang_registry)
|
Arc::get_mut(&mut lang_registry).unwrap().add(
|
||||||
.unwrap()
|
Arc::new(Language::new(
|
||||||
.add(Arc::new(Language::new(
|
|
||||||
LanguageConfig {
|
LanguageConfig {
|
||||||
name: "Rust".to_string(),
|
name: "Rust".to_string(),
|
||||||
path_suffixes: vec!["rs".to_string()],
|
path_suffixes: vec!["rs".to_string()],
|
||||||
|
@ -2709,7 +2712,9 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::language()),
|
||||||
)));
|
)),
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
// Connect to a server as 2 clients.
|
// Connect to a server as 2 clients.
|
||||||
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
|
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
|
||||||
|
@ -2800,9 +2805,8 @@ mod tests {
|
||||||
|
|
||||||
// Set up a fake language server.
|
// Set up a fake language server.
|
||||||
let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
|
let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
|
||||||
Arc::get_mut(&mut lang_registry)
|
Arc::get_mut(&mut lang_registry).unwrap().add(
|
||||||
.unwrap()
|
Arc::new(Language::new(
|
||||||
.add(Arc::new(Language::new(
|
|
||||||
LanguageConfig {
|
LanguageConfig {
|
||||||
name: "Rust".to_string(),
|
name: "Rust".to_string(),
|
||||||
path_suffixes: vec!["rs".to_string()],
|
path_suffixes: vec!["rs".to_string()],
|
||||||
|
@ -2810,7 +2814,9 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::language()),
|
||||||
)));
|
)),
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
// Connect to a server as 2 clients.
|
// Connect to a server as 2 clients.
|
||||||
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
|
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
|
||||||
|
@ -3039,9 +3045,8 @@ mod tests {
|
||||||
|
|
||||||
// Set up a fake language server.
|
// Set up a fake language server.
|
||||||
let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
|
let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
|
||||||
Arc::get_mut(&mut lang_registry)
|
Arc::get_mut(&mut lang_registry).unwrap().add(
|
||||||
.unwrap()
|
Arc::new(Language::new(
|
||||||
.add(Arc::new(Language::new(
|
|
||||||
LanguageConfig {
|
LanguageConfig {
|
||||||
name: "Rust".to_string(),
|
name: "Rust".to_string(),
|
||||||
path_suffixes: vec!["rs".to_string()],
|
path_suffixes: vec!["rs".to_string()],
|
||||||
|
@ -3049,7 +3054,9 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::language()),
|
||||||
)));
|
)),
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
// Connect to a server as 2 clients.
|
// Connect to a server as 2 clients.
|
||||||
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
|
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
|
||||||
|
@ -3845,9 +3852,8 @@ mod tests {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Arc::get_mut(&mut host_lang_registry)
|
Arc::get_mut(&mut host_lang_registry).unwrap().add(
|
||||||
.unwrap()
|
Arc::new(Language::new(
|
||||||
.add(Arc::new(Language::new(
|
|
||||||
LanguageConfig {
|
LanguageConfig {
|
||||||
name: "Rust".to_string(),
|
name: "Rust".to_string(),
|
||||||
path_suffixes: vec!["rs".to_string()],
|
path_suffixes: vec!["rs".to_string()],
|
||||||
|
@ -3855,7 +3861,9 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
)));
|
)),
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.background());
|
let fs = FakeFs::new(cx.background());
|
||||||
fs.insert_tree(
|
fs.insert_tree(
|
||||||
|
|
|
@ -141,6 +141,7 @@ pub struct StatusBar {
|
||||||
pub item_spacing: f32,
|
pub item_spacing: f32,
|
||||||
pub cursor_position: TextStyle,
|
pub cursor_position: TextStyle,
|
||||||
pub diagnostic_message: TextStyle,
|
pub diagnostic_message: TextStyle,
|
||||||
|
pub lsp_message: TextStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default)]
|
#[derive(Deserialize, Default)]
|
||||||
|
|
|
@ -19,6 +19,7 @@ project = { path = "../project" }
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
|
futures = "0.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||||
|
|
137
crates/workspace/src/lsp_status.rs
Normal file
137
crates/workspace/src/lsp_status.rs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
use crate::{ItemViewHandle, Settings, StatusItemView};
|
||||||
|
use futures::StreamExt;
|
||||||
|
use gpui::{
|
||||||
|
action, elements::*, platform::CursorStyle, Entity, MutableAppContext, RenderContext, View,
|
||||||
|
ViewContext,
|
||||||
|
};
|
||||||
|
use language::{LanguageRegistry, LanguageServerBinaryStatus};
|
||||||
|
use postage::watch;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
action!(DismissErrorMessage);
|
||||||
|
|
||||||
|
pub struct LspStatus {
|
||||||
|
settings_rx: watch::Receiver<Settings>,
|
||||||
|
checking_for_update: Vec<String>,
|
||||||
|
downloading: Vec<String>,
|
||||||
|
failed: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
|
cx.add_action(LspStatus::dismiss_error_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LspStatus {
|
||||||
|
pub fn new(
|
||||||
|
languages: Arc<LanguageRegistry>,
|
||||||
|
settings_rx: watch::Receiver<Settings>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Self {
|
||||||
|
let mut status_events = languages.language_server_binary_statuses();
|
||||||
|
cx.spawn_weak(|this, mut cx| async move {
|
||||||
|
while let Some((language, event)) = status_events.next().await {
|
||||||
|
if let Some(this) = this.upgrade(&cx) {
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
for vector in [
|
||||||
|
&mut this.checking_for_update,
|
||||||
|
&mut this.downloading,
|
||||||
|
&mut this.failed,
|
||||||
|
] {
|
||||||
|
vector.retain(|name| name != language.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
match event {
|
||||||
|
LanguageServerBinaryStatus::CheckingForUpdate => {
|
||||||
|
this.checking_for_update.push(language.name().to_string());
|
||||||
|
}
|
||||||
|
LanguageServerBinaryStatus::Downloading => {
|
||||||
|
this.downloading.push(language.name().to_string());
|
||||||
|
}
|
||||||
|
LanguageServerBinaryStatus::Failed => {
|
||||||
|
this.failed.push(language.name().to_string());
|
||||||
|
}
|
||||||
|
LanguageServerBinaryStatus::Downloaded
|
||||||
|
| LanguageServerBinaryStatus::Cached => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
Self {
|
||||||
|
settings_rx,
|
||||||
|
checking_for_update: Default::default(),
|
||||||
|
downloading: Default::default(),
|
||||||
|
failed: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dismiss_error_message(&mut self, _: &DismissErrorMessage, cx: &mut ViewContext<Self>) {
|
||||||
|
self.failed.clear();
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entity for LspStatus {
|
||||||
|
type Event = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View for LspStatus {
|
||||||
|
fn ui_name() -> &'static str {
|
||||||
|
"LspStatus"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||||
|
let theme = &self.settings_rx.borrow().theme;
|
||||||
|
if !self.downloading.is_empty() {
|
||||||
|
Label::new(
|
||||||
|
format!(
|
||||||
|
"Downloading {} language server{}...",
|
||||||
|
self.downloading.join(", "),
|
||||||
|
if self.downloading.len() > 1 { "s" } else { "" }
|
||||||
|
),
|
||||||
|
theme.workspace.status_bar.lsp_message.clone(),
|
||||||
|
)
|
||||||
|
.boxed()
|
||||||
|
} else if !self.checking_for_update.is_empty() {
|
||||||
|
Label::new(
|
||||||
|
format!(
|
||||||
|
"Checking for updates to {} language server{}...",
|
||||||
|
self.checking_for_update.join(", "),
|
||||||
|
if self.checking_for_update.len() > 1 {
|
||||||
|
"s"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
),
|
||||||
|
theme.workspace.status_bar.lsp_message.clone(),
|
||||||
|
)
|
||||||
|
.boxed()
|
||||||
|
} else if !self.failed.is_empty() {
|
||||||
|
MouseEventHandler::new::<Self, _, _>(0, cx, |_, _| {
|
||||||
|
Label::new(
|
||||||
|
format!(
|
||||||
|
"Failed to download {} language server{}. Click to dismiss.",
|
||||||
|
self.failed.join(", "),
|
||||||
|
if self.failed.len() > 1 { "s" } else { "" }
|
||||||
|
),
|
||||||
|
theme.workspace.status_bar.lsp_message.clone(),
|
||||||
|
)
|
||||||
|
.boxed()
|
||||||
|
})
|
||||||
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
|
.on_click(|cx| cx.dispatch_action(DismissErrorMessage))
|
||||||
|
.boxed()
|
||||||
|
} else {
|
||||||
|
Empty::new().boxed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusItemView for LspStatus {
|
||||||
|
fn set_active_pane_item(&mut self, _: Option<&dyn ItemViewHandle>, _: &mut ViewContext<Self>) {}
|
||||||
|
}
|
|
@ -42,17 +42,21 @@ impl View for StatusBar {
|
||||||
fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
|
fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
|
||||||
let theme = &self.settings.borrow().theme.workspace.status_bar;
|
let theme = &self.settings.borrow().theme.workspace.status_bar;
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_children(
|
.with_children(self.left_items.iter().map(|i| {
|
||||||
self.left_items
|
ChildView::new(i.as_ref())
|
||||||
.iter()
|
.aligned()
|
||||||
.map(|i| ChildView::new(i.as_ref()).aligned().boxed()),
|
.contained()
|
||||||
)
|
.with_margin_right(theme.item_spacing)
|
||||||
|
.boxed()
|
||||||
|
}))
|
||||||
.with_child(Empty::new().flexible(1., true).boxed())
|
.with_child(Empty::new().flexible(1., true).boxed())
|
||||||
.with_children(
|
.with_children(self.right_items.iter().map(|i| {
|
||||||
self.right_items
|
ChildView::new(i.as_ref())
|
||||||
.iter()
|
.aligned()
|
||||||
.map(|i| ChildView::new(i.as_ref()).aligned().boxed()),
|
.contained()
|
||||||
)
|
.with_margin_left(theme.item_spacing)
|
||||||
|
.boxed()
|
||||||
|
}))
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(theme.container)
|
.with_style(theme.container)
|
||||||
.constrained()
|
.constrained()
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod lsp_status;
|
||||||
pub mod menu;
|
pub mod menu;
|
||||||
pub mod pane;
|
pub mod pane;
|
||||||
pub mod pane_group;
|
pub mod pane_group;
|
||||||
|
|
|
@ -55,6 +55,7 @@ theme_selector = { path = "../theme_selector" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
|
async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
|
||||||
async-recursion = "0.3"
|
async-recursion = "0.3"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
crossbeam-channel = "0.5.0"
|
crossbeam-channel = "0.5.0"
|
||||||
|
|
|
@ -77,9 +77,10 @@ border = { width = 1, color = "$border.0", left = true }
|
||||||
[workspace.status_bar]
|
[workspace.status_bar]
|
||||||
padding = { left = 6, right = 6 }
|
padding = { left = 6, right = 6 }
|
||||||
height = 24
|
height = 24
|
||||||
item_spacing = 24
|
item_spacing = 8
|
||||||
cursor_position = "$text.2"
|
cursor_position = "$text.2"
|
||||||
diagnostic_message = "$text.2"
|
diagnostic_message = "$text.2"
|
||||||
|
lsp_message = "$text.2"
|
||||||
|
|
||||||
[workspace.toolbar]
|
[workspace.toolbar]
|
||||||
height = 44
|
height = 44
|
||||||
|
@ -188,7 +189,7 @@ corner_radius = 6
|
||||||
|
|
||||||
[project_panel]
|
[project_panel]
|
||||||
extends = "$panel"
|
extends = "$panel"
|
||||||
padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2
|
padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2
|
||||||
|
|
||||||
[project_panel.entry]
|
[project_panel.entry]
|
||||||
text = "$text.1"
|
text = "$text.1"
|
||||||
|
|
|
@ -11,6 +11,5 @@ brackets = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[language_server]
|
[language_server]
|
||||||
binary = "rust-analyzer"
|
|
||||||
disk_based_diagnostic_sources = ["rustc"]
|
disk_based_diagnostic_sources = ["rustc"]
|
||||||
disk_based_diagnostics_progress_token = "rustAnalyzer/cargo check"
|
disk_based_diagnostics_progress_token = "rustAnalyzer/cargo check"
|
||||||
|
|
|
@ -1,17 +1,140 @@
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use async_compression::futures::bufread::GzipDecoder;
|
||||||
|
use client::http::{self, HttpClient, Method};
|
||||||
|
use futures::{future::BoxFuture, FutureExt, StreamExt};
|
||||||
pub use language::*;
|
pub use language::*;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
use std::borrow::Cow;
|
use serde::Deserialize;
|
||||||
use std::{str, sync::Arc};
|
use smol::fs::{self, File};
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
env::consts,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
str,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use util::{ResultExt, TryFutureExt};
|
||||||
|
|
||||||
#[derive(RustEmbed)]
|
#[derive(RustEmbed)]
|
||||||
#[folder = "languages"]
|
#[folder = "languages"]
|
||||||
struct LanguageDir;
|
struct LanguageDir;
|
||||||
|
|
||||||
struct RustPostProcessor;
|
struct RustLsp;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct GithubRelease {
|
||||||
|
name: String,
|
||||||
|
assets: Vec<GithubReleaseAsset>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct GithubReleaseAsset {
|
||||||
|
name: String,
|
||||||
|
browser_download_url: http::Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LspExt for RustLsp {
|
||||||
|
fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
http: Arc<dyn HttpClient>,
|
||||||
|
) -> BoxFuture<'static, Result<LspBinaryVersion>> {
|
||||||
|
async move {
|
||||||
|
let release = http
|
||||||
|
.send(
|
||||||
|
surf::RequestBuilder::new(
|
||||||
|
Method::Get,
|
||||||
|
http::Url::parse(
|
||||||
|
"https://api.github.com/repos/rust-analyzer/rust-analyzer/releases/latest",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.middleware(surf::middleware::Redirect::default())
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|err| anyhow!("error fetching latest release: {}", err))?
|
||||||
|
.body_json::<GithubRelease>()
|
||||||
|
.await
|
||||||
|
.map_err(|err| anyhow!("error parsing latest release: {}", err))?;
|
||||||
|
let asset_name = format!("rust-analyzer-{}-apple-darwin.gz", consts::ARCH);
|
||||||
|
let asset = release
|
||||||
|
.assets
|
||||||
|
.iter()
|
||||||
|
.find(|asset| asset.name == asset_name)
|
||||||
|
.ok_or_else(|| anyhow!("no release found matching {:?}", asset_name))?;
|
||||||
|
Ok(LspBinaryVersion {
|
||||||
|
name: release.name,
|
||||||
|
url: asset.browser_download_url.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
version: LspBinaryVersion,
|
||||||
|
http: Arc<dyn HttpClient>,
|
||||||
|
download_dir: Arc<Path>,
|
||||||
|
) -> BoxFuture<'static, Result<PathBuf>> {
|
||||||
|
async move {
|
||||||
|
let destination_dir_path = download_dir.join("rust-analyzer");
|
||||||
|
fs::create_dir_all(&destination_dir_path).await?;
|
||||||
|
let destination_path =
|
||||||
|
destination_dir_path.join(format!("rust-analyzer-{}", version.name));
|
||||||
|
|
||||||
|
if fs::metadata(&destination_path).await.is_err() {
|
||||||
|
let response = http
|
||||||
|
.send(
|
||||||
|
surf::RequestBuilder::new(Method::Get, version.url)
|
||||||
|
.middleware(surf::middleware::Redirect::default())
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|err| anyhow!("error downloading release: {}", err))?;
|
||||||
|
let decompressed_bytes = GzipDecoder::new(response);
|
||||||
|
let mut file = File::create(&destination_path).await?;
|
||||||
|
futures::io::copy(decompressed_bytes, &mut file).await?;
|
||||||
|
fs::set_permissions(
|
||||||
|
&destination_path,
|
||||||
|
<fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Some(mut entries) = fs::read_dir(&destination_dir_path).await.log_err() {
|
||||||
|
while let Some(entry) = entries.next().await {
|
||||||
|
if let Some(entry) = entry.log_err() {
|
||||||
|
let entry_path = entry.path();
|
||||||
|
if entry_path.as_path() != destination_path {
|
||||||
|
fs::remove_file(&entry_path).await.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(destination_path)
|
||||||
|
}
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cached_server_binary(&self, download_dir: Arc<Path>) -> BoxFuture<'static, Option<PathBuf>> {
|
||||||
|
async move {
|
||||||
|
let destination_dir_path = download_dir.join("rust-analyzer");
|
||||||
|
fs::create_dir_all(&destination_dir_path).await?;
|
||||||
|
|
||||||
|
let mut last = None;
|
||||||
|
let mut entries = fs::read_dir(&destination_dir_path).await?;
|
||||||
|
while let Some(entry) = entries.next().await {
|
||||||
|
last = Some(entry?.path());
|
||||||
|
}
|
||||||
|
last.ok_or_else(|| anyhow!("no cached binary"))
|
||||||
|
}
|
||||||
|
.log_err()
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
impl LspPostProcessor for RustPostProcessor {
|
|
||||||
fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
|
fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref REGEX: Regex = Regex::new("(?m)`([^`]+)\n`$").unwrap();
|
static ref REGEX: Regex = Regex::new("(?m)`([^`]+)\n`$").unwrap();
|
||||||
|
@ -113,7 +236,12 @@ impl LspPostProcessor for RustPostProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_language_registry() -> LanguageRegistry {
|
pub fn build_language_registry() -> LanguageRegistry {
|
||||||
let mut languages = LanguageRegistry::default();
|
let mut languages = LanguageRegistry::new();
|
||||||
|
languages.set_language_server_download_dir(
|
||||||
|
dirs::home_dir()
|
||||||
|
.expect("failed to determine home directory")
|
||||||
|
.join(".zed"),
|
||||||
|
);
|
||||||
languages.add(Arc::new(rust()));
|
languages.add(Arc::new(rust()));
|
||||||
languages.add(Arc::new(markdown()));
|
languages.add(Arc::new(markdown()));
|
||||||
languages
|
languages
|
||||||
|
@ -131,7 +259,7 @@ fn rust() -> Language {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_outline_query(load_query("rust/outline.scm").as_ref())
|
.with_outline_query(load_query("rust/outline.scm").as_ref())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_lsp_post_processor(RustPostProcessor)
|
.with_lsp_ext(RustLsp)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn markdown() -> Language {
|
fn markdown() -> Language {
|
||||||
|
@ -153,7 +281,7 @@ fn load_query(path: &str) -> Cow<'static, str> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use gpui::color::Color;
|
use gpui::color::Color;
|
||||||
use language::LspPostProcessor;
|
use language::LspExt;
|
||||||
use theme::SyntaxTheme;
|
use theme::SyntaxTheme;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -180,7 +308,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
RustPostProcessor.process_diagnostics(&mut params);
|
RustLsp.process_diagnostics(&mut params);
|
||||||
|
|
||||||
assert_eq!(params.diagnostics[0].message, "use of moved value `a`");
|
assert_eq!(params.diagnostics[0].message, "use of moved value `a`");
|
||||||
|
|
||||||
|
|
|
@ -26,14 +26,17 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc<AppState> {
|
||||||
let client = Client::new(http.clone());
|
let client = Client::new(http.clone());
|
||||||
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
|
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
|
||||||
let mut languages = LanguageRegistry::new();
|
let mut languages = LanguageRegistry::new();
|
||||||
languages.add(Arc::new(language::Language::new(
|
languages.add(
|
||||||
language::LanguageConfig {
|
Arc::new(language::Language::new(
|
||||||
name: "Rust".to_string(),
|
language::LanguageConfig {
|
||||||
path_suffixes: vec!["rs".to_string()],
|
name: "Rust".to_string(),
|
||||||
..Default::default()
|
path_suffixes: vec!["rs".to_string()],
|
||||||
},
|
..Default::default()
|
||||||
Some(tree_sitter_rust::language()),
|
},
|
||||||
)));
|
Some(tree_sitter_rust::language()),
|
||||||
|
)),
|
||||||
|
|
||||||
|
);
|
||||||
Arc::new(AppState {
|
Arc::new(AppState {
|
||||||
settings_tx: Arc::new(Mutex::new(settings_tx)),
|
settings_tx: Arc::new(Mutex::new(settings_tx)),
|
||||||
settings,
|
settings,
|
||||||
|
|
|
@ -43,6 +43,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
workspace::lsp_status::init(cx);
|
||||||
|
|
||||||
cx.add_bindings(vec![
|
cx.add_bindings(vec![
|
||||||
Binding::new("cmd-=", AdjustBufferFontSize(1.), None),
|
Binding::new("cmd-=", AdjustBufferFontSize(1.), None),
|
||||||
Binding::new("cmd--", AdjustBufferFontSize(-1.), None),
|
Binding::new("cmd--", AdjustBufferFontSize(-1.), None),
|
||||||
|
@ -97,11 +99,19 @@ pub fn build_workspace(
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
let lsp_status = cx.add_view(|cx| {
|
||||||
|
workspace::lsp_status::LspStatus::new(
|
||||||
|
app_state.languages.clone(),
|
||||||
|
app_state.settings.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
let cursor_position =
|
let cursor_position =
|
||||||
cx.add_view(|_| editor::items::CursorPosition::new(app_state.settings.clone()));
|
cx.add_view(|_| editor::items::CursorPosition::new(app_state.settings.clone()));
|
||||||
workspace.status_bar().update(cx, |status_bar, cx| {
|
workspace.status_bar().update(cx, |status_bar, cx| {
|
||||||
status_bar.add_left_item(diagnostic_summary, cx);
|
status_bar.add_left_item(diagnostic_summary, cx);
|
||||||
status_bar.add_left_item(diagnostic_message, cx);
|
status_bar.add_left_item(diagnostic_message, cx);
|
||||||
|
status_bar.add_left_item(lsp_status, cx);
|
||||||
status_bar.add_right_item(cursor_position, cx);
|
status_bar.add_right_item(cursor_position, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,6 @@ cargo build --release --target aarch64-apple-darwin
|
||||||
# Replace the bundle's binary with a "fat binary" that combines the two architecture-specific binaries
|
# Replace the bundle's binary with a "fat binary" that combines the two architecture-specific binaries
|
||||||
lipo -create target/x86_64-apple-darwin/release/Zed target/aarch64-apple-darwin/release/Zed -output target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/MacOS/zed
|
lipo -create target/x86_64-apple-darwin/release/Zed target/aarch64-apple-darwin/release/Zed -output target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/MacOS/zed
|
||||||
|
|
||||||
# Bundle rust-analyzer
|
|
||||||
cp vendor/bin/rust-analyzer target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/Resources/
|
|
||||||
|
|
||||||
# Sign the app bundle with an ad-hoc signature so it runs on the M1. We need a real certificate but this works for now.
|
# Sign the app bundle with an ad-hoc signature so it runs on the M1. We need a real certificate but this works for now.
|
||||||
if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTARIZATION_USERNAME && -n $APPLE_NOTARIZATION_PASSWORD ]]; then
|
if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTARIZATION_USERNAME && -n $APPLE_NOTARIZATION_PASSWORD ]]; then
|
||||||
echo "Signing bundle with Apple-issued certificate"
|
echo "Signing bundle with Apple-issued certificate"
|
||||||
|
@ -34,7 +31,6 @@ if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTAR
|
||||||
security import /tmp/zed-certificate.p12 -k zed.keychain -P $MACOS_CERTIFICATE_PASSWORD -T /usr/bin/codesign
|
security import /tmp/zed-certificate.p12 -k zed.keychain -P $MACOS_CERTIFICATE_PASSWORD -T /usr/bin/codesign
|
||||||
rm /tmp/zed-certificate.p12
|
rm /tmp/zed-certificate.p12
|
||||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $MACOS_CERTIFICATE_PASSWORD zed.keychain
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $MACOS_CERTIFICATE_PASSWORD zed.keychain
|
||||||
/usr/bin/codesign --force --deep --timestamp --options runtime --sign "Zed Industries, Inc." target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/Resources/rust-analyzer -v
|
|
||||||
/usr/bin/codesign --force --deep --timestamp --options runtime --sign "Zed Industries, Inc." target/x86_64-apple-darwin/release/bundle/osx/Zed.app -v
|
/usr/bin/codesign --force --deep --timestamp --options runtime --sign "Zed Industries, Inc." target/x86_64-apple-darwin/release/bundle/osx/Zed.app -v
|
||||||
security default-keychain -s login.keychain
|
security default-keychain -s login.keychain
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
export RUST_ANALYZER_URL="https://github.com/rust-analyzer/rust-analyzer/releases/download/2022-01-24/"
|
|
||||||
|
|
||||||
function download {
|
|
||||||
local filename="rust-analyzer-$1"
|
|
||||||
curl -L $RUST_ANALYZER_URL/$filename.gz | gunzip > vendor/bin/$filename
|
|
||||||
chmod +x vendor/bin/$filename
|
|
||||||
}
|
|
||||||
|
|
||||||
mkdir -p vendor/bin
|
|
||||||
download "x86_64-apple-darwin"
|
|
||||||
download "aarch64-apple-darwin"
|
|
||||||
|
|
||||||
cd vendor/bin
|
|
||||||
lipo -create rust-analyzer-* -output rust-analyzer
|
|
||||||
rm rust-analyzer-*
|
|
Loading…
Add table
Add a link
Reference in a new issue