Detailed changes
@@ -34,11 +34,6 @@ jobs:
with:
clean: false
- - name: Download rust-analyzer
- run: |
- script/download-rust-analyzer
- echo "$PWD/vendor/bin" >> $GITHUB_PATH
-
- name: Run tests
run: cargo test --workspace --no-fail-fast
@@ -69,14 +64,11 @@ jobs:
uses: actions/checkout@v2
with:
clean: false
-
+
- name: Validate version
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
run: script/validate-version
- - name: Download rust-analyzer
- run: script/download-rust-analyzer
-
- name: Create app bundle
run: script/bundle
@@ -178,6 +178,17 @@ dependencies = [
"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]]
name = "async-channel"
version = "1.6.1"
@@ -1926,9 +1937,9 @@ dependencies = [
[[package]]
name = "futures-core"
-version = "0.3.12"
+version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65"
+checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
[[package]]
name = "futures-executor"
@@ -2619,7 +2630,9 @@ name = "language"
version = "0.1.0"
dependencies = [
"anyhow",
+ "async-broadcast",
"async-trait",
+ "client",
"clock",
"collections",
"ctor",
@@ -5701,6 +5714,7 @@ dependencies = [
"client",
"clock",
"collections",
+ "futures",
"gpui",
"language",
"log",
@@ -5741,6 +5755,7 @@ name = "zed"
version = "0.15.2"
dependencies = [
"anyhow",
+ "async-compression",
"async-recursion",
"async-trait",
"chat_panel",
@@ -224,6 +224,10 @@ impl Client {
self.id
}
+ pub fn http_client(&self) -> Arc<dyn HttpClient> {
+ self.http.clone()
+ }
+
#[cfg(any(test, feature = "test-support"))]
pub fn override_authenticate<F>(&mut self, authenticate: F) -> &mut Self
where
@@ -410,8 +410,6 @@ impl View for DiagnosticMessage {
diagnostic.message.split('\n').next().unwrap().to_string(),
theme.diagnostic_message.clone(),
)
- .contained()
- .with_margin_left(theme.item_spacing)
.boxed()
} else {
Empty::new().boxed()
@@ -9,6 +9,7 @@ path = "src/language.rs"
[features]
test-support = [
"rand",
+ "client/test-support",
"collections/test-support",
"lsp/test-support",
"text/test-support",
@@ -17,6 +18,7 @@ test-support = [
]
[dependencies]
+client = { path = "../client" }
clock = { path = "../clock" }
collections = { path = "../collections" }
fuzzy = { path = "../fuzzy" }
@@ -28,6 +30,7 @@ text = { path = "../text" }
theme = { path = "../theme" }
util = { path = "../util" }
anyhow = "1.0.38"
+async-broadcast = "0.3.4"
async-trait = "0.1"
futures = "0.3"
lazy_static = "1.4"
@@ -44,6 +47,7 @@ tree-sitter = "0.20"
tree-sitter-rust = { version = "0.20.0", optional = true }
[dev-dependencies]
+client = { path = "../client", features = ["test-support"] }
collections = { path = "../collections", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
@@ -7,15 +7,27 @@ pub mod proto;
mod tests;
use anyhow::{anyhow, Result};
+use client::http::{self, HttpClient};
use collections::HashSet;
-use gpui::AppContext;
+use futures::{
+ future::{BoxFuture, Shared},
+ FutureExt, TryFutureExt,
+};
+use gpui::{AppContext, Task};
use highlight_map::HighlightMap;
use lazy_static::lazy_static;
use parking_lot::Mutex;
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 tree_sitter::{self, Query};
+use util::ResultExt;
#[cfg(any(test, feature = "test-support"))]
use futures::channel::mpsc;
@@ -47,7 +59,23 @@ pub trait ToLspPosition {
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 label_for_completion(
&self,
@@ -77,7 +105,6 @@ pub struct LanguageConfig {
#[derive(Default, Deserialize)]
pub struct LanguageServerConfig {
- pub binary: String,
pub disk_based_diagnostic_sources: HashSet<String>,
pub disk_based_diagnostics_progress_token: Option<String>,
#[cfg(any(test, feature = "test-support"))]
@@ -103,7 +130,8 @@ pub struct BracketPair {
pub struct Language {
pub(crate) config: LanguageConfig,
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 {
@@ -115,18 +143,35 @@ pub struct Grammar {
pub(crate) highlight_map: Mutex<HighlightMap>,
}
-#[derive(Default)]
+#[derive(Clone)]
+pub enum LanguageServerBinaryStatus {
+ CheckingForUpdate,
+ Downloading,
+ Downloaded,
+ Cached,
+ Failed,
+}
+
pub struct LanguageRegistry {
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 {
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>) {
- self.languages.push(language);
+ self.languages.push(language.clone());
}
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>> {
self.languages
.iter()
@@ -154,6 +203,140 @@ impl LanguageRegistry {
.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 {
@@ -170,7 +353,8 @@ impl Language {
highlight_map: Default::default(),
})
}),
- lsp_post_processor: None,
+ lsp_ext: None,
+ lsp_binary_path: Default::default(),
}
}
@@ -214,8 +398,8 @@ impl Language {
Ok(self)
}
- pub fn with_lsp_post_processor(mut self, processor: impl LspPostProcessor) -> Self {
- self.lsp_post_processor = Some(Box::new(processor));
+ pub fn with_lsp_ext(mut self, lsp_ext: impl LspExt) -> Self {
+ self.lsp_ext = Some(Arc::new(lsp_ext));
self
}
@@ -227,50 +411,6 @@ impl Language {
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>> {
self.config
.language_server
@@ -286,7 +426,7 @@ impl Language {
}
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);
}
}
@@ -295,7 +435,7 @@ impl Language {
&self,
completion: &lsp::CompletionItem,
) -> Option<CompletionLabel> {
- self.lsp_post_processor
+ self.lsp_ext
.as_ref()?
.label_for_completion(completion, self)
}
@@ -22,28 +22,25 @@ fn init_logger() {
}
}
-#[test]
+#[gpui::test]
fn test_select_language() {
- let registry = LanguageRegistry {
- languages: vec![
- Arc::new(Language::new(
- LanguageConfig {
- name: "Rust".to_string(),
- path_suffixes: vec!["rs".to_string()],
- ..Default::default()
- },
- Some(tree_sitter_rust::language()),
- )),
- Arc::new(Language::new(
- LanguageConfig {
- name: "Make".to_string(),
- path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
- ..Default::default()
- },
- Some(tree_sitter_rust::language()),
- )),
- ],
- };
+ let mut registry = LanguageRegistry::new();
+ registry.add(Arc::new(Language::new(
+ LanguageConfig {
+ name: "Rust".to_string(),
+ path_suffixes: vec!["rs".to_string()],
+ ..Default::default()
+ },
+ Some(tree_sitter_rust::language()),
+ )));
+ registry.add(Arc::new(Language::new(
+ LanguageConfig {
+ name: "Make".to_string(),
+ path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
+ ..Default::default()
+ },
+ Some(tree_sitter_rust::language()),
+ )));
// matching file extension
assert_eq!(
@@ -700,8 +700,6 @@ impl FakeLanguageServer {
mod tests {
use super::*;
use gpui::TestAppContext;
- use unindent::Unindent;
- use util::test::temp_tree;
#[ctor::ctor]
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]
async fn test_fake(cx: TestAppContext) {
let (server, mut fake) = LanguageServer::fake(cx.background());
@@ -828,19 +768,6 @@ mod tests {
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 {}
impl notification::Notification for ServerStatusNotification {
@@ -7,7 +7,7 @@ use anyhow::{anyhow, Context, Result};
use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
use clock::ReplicaId;
use collections::{hash_map, HashMap, HashSet};
-use futures::Future;
+use futures::{future::Shared, Future, FutureExt};
use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
use gpui::{
AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task,
@@ -39,6 +39,8 @@ pub struct Project {
active_entry: Option<ProjectEntry>,
languages: Arc<LanguageRegistry>,
language_servers: HashMap<(WorktreeId, String), Arc<LanguageServer>>,
+ started_language_servers:
+ HashMap<(WorktreeId, String), Shared<Task<Option<Arc<LanguageServer>>>>>,
client: Arc<client::Client>,
user_store: ModelHandle<UserStore>,
fs: Arc<dyn Fs>,
@@ -258,6 +260,7 @@ impl Project {
fs,
language_servers_with_diagnostics_running: 0,
language_servers: Default::default(),
+ started_language_servers: Default::default(),
}
})
}
@@ -309,6 +312,7 @@ impl Project {
},
language_servers_with_diagnostics_running: 0,
language_servers: Default::default(),
+ started_language_servers: Default::default(),
};
for worktree in worktrees {
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 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.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()) {
let worktree_id = local_worktree.id();
let worktree_abs_path = local_worktree.abs_path().clone();
-
- let language_server = match self
- .language_servers
- .entry((worktree_id, language.name().to_string()))
- {
- hash_map::Entry::Occupied(e) => Some(e.get().clone()),
- hash_map::Entry::Vacant(e) => Self::start_language_server(
- self.client.clone(),
- language.clone(),
- &worktree_abs_path,
- cx,
- )
- .map(|server| e.insert(server).clone()),
- };
-
- buffer.update(cx, |buffer, cx| {
- buffer.set_language_server(language_server, cx);
- });
+ let buffer = buffer.downgrade();
+ let language_server =
+ self.start_language_server(worktree_id, worktree_abs_path, language, cx);
+
+ cx.spawn_weak(|_, mut cx| async move {
+ if let Some(language_server) = language_server.await {
+ if let Some(buffer) = buffer.upgrade(&cx) {
+ buffer.update(&mut cx, |buffer, cx| {
+ buffer.set_language_server(Some(language_server), cx);
+ });
+ }
+ }
+ })
+ .detach();
}
}
@@ -819,116 +819,151 @@ impl Project {
}
fn start_language_server(
- rpc: Arc<Client>,
+ &mut self,
+ worktree_id: WorktreeId,
+ worktree_path: Arc<Path>,
language: Arc<Language>,
- worktree_path: &Path,
cx: &mut ModelContext<Self>,
- ) -> Option<Arc<LanguageServer>> {
+ ) -> Shared<Task<Option<Arc<LanguageServer>>>> {
enum LspEvent {
DiagnosticsStart,
DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
DiagnosticsFinish,
}
- let language_server = language
- .start_server(worktree_path, cx)
- .log_err()
- .flatten()?;
- let disk_based_sources = language
- .disk_based_diagnostic_sources()
- .cloned()
- .unwrap_or_default();
- let disk_based_diagnostics_progress_token =
- language.disk_based_diagnostics_progress_token().cloned();
- let has_disk_based_diagnostic_progress_token =
- disk_based_diagnostics_progress_token.is_some();
- let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded();
-
- // Listen for `PublishDiagnostics` notifications.
- language_server
- .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();
+ let key = (worktree_id, language.name().to_string());
+ self.started_language_servers
+ .entry(key.clone())
+ .or_insert_with(|| {
+ let language_server = self.languages.start_language_server(
+ &language,
+ worktree_path,
+ self.client.http_client(),
+ cx,
+ );
+ let rpc = self.client.clone();
+ cx.spawn_weak(|this, mut cx| async move {
+ let language_server = language_server?.await.log_err()?;
+ if let Some(this) = this.upgrade(&cx) {
+ this.update(&mut cx, |this, _| {
+ this.language_servers.insert(key, language_server.clone());
+ });
}
- }
- })
- .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 {
+ let disk_based_sources = language
+ .disk_based_diagnostic_sources()
+ .cloned()
+ .unwrap_or_default();
+ let disk_based_diagnostics_progress_token =
+ language.disk_based_diagnostics_progress_token().cloned();
+ let has_disk_based_diagnostic_progress_token =
+ disk_based_diagnostics_progress_token.is_some();
+ let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded();
+
+ // Listen for `PublishDiagnostics` notifications.
+ language_server
+ .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();
}
- }
- lsp::WorkDoneProgress::End(_) => {
- running_jobs_for_this_server -= 1;
- if running_jobs_for_this_server == 0 {
+ 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();
+ })
+ .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),
+ };
- // 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();
+ 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();
+ }
+ }
+ _ => {}
+ }
+ }
+ }
}
- });
- }
- 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();
+ })
+ .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(())
+ })
+ .detach();
- Some(language_server)
+ Some(language_server)
+ })
+ .shared()
+ })
+ .clone()
}
pub fn update_diagnostics(
@@ -2857,8 +2892,6 @@ impl Entity for Project {
&mut self,
_: &mut MutableAppContext,
) -> Option<std::pin::Pin<Box<dyn 'static + Future<Output = ()>>>> {
- use futures::FutureExt;
-
let shutdown_futures = self
.language_servers
.drain()
@@ -2001,9 +2001,8 @@ mod tests {
// Set up a fake language server.
let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
- Arc::get_mut(&mut lang_registry)
- .unwrap()
- .add(Arc::new(Language::new(
+ Arc::get_mut(&mut lang_registry).unwrap().add(
+ Arc::new(Language::new(
LanguageConfig {
name: "Rust".to_string(),
path_suffixes: vec!["rs".to_string()],
@@ -2011,7 +2010,9 @@ mod tests {
..Default::default()
},
Some(tree_sitter_rust::language()),
- )));
+ )),
+
+ );
// Connect to a server as 2 clients.
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
@@ -2232,9 +2233,8 @@ mod tests {
}),
..Default::default()
});
- Arc::get_mut(&mut lang_registry)
- .unwrap()
- .add(Arc::new(Language::new(
+ Arc::get_mut(&mut lang_registry).unwrap().add(
+ Arc::new(Language::new(
LanguageConfig {
name: "Rust".to_string(),
path_suffixes: vec!["rs".to_string()],
@@ -2242,7 +2242,9 @@ mod tests {
..Default::default()
},
Some(tree_sitter_rust::language()),
- )));
+ )),
+
+ );
// Connect to a server as 2 clients.
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
@@ -2434,9 +2436,8 @@ mod tests {
// Set up a fake language server.
let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
- Arc::get_mut(&mut lang_registry)
- .unwrap()
- .add(Arc::new(Language::new(
+ Arc::get_mut(&mut lang_registry).unwrap().add(
+ Arc::new(Language::new(
LanguageConfig {
name: "Rust".to_string(),
path_suffixes: vec!["rs".to_string()],
@@ -2444,7 +2445,9 @@ mod tests {
..Default::default()
},
Some(tree_sitter_rust::language()),
- )));
+ )),
+
+ );
// Connect to a server as 2 clients.
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
@@ -2551,9 +2554,8 @@ mod tests {
// Set up a fake language server.
let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
- Arc::get_mut(&mut lang_registry)
- .unwrap()
- .add(Arc::new(Language::new(
+ Arc::get_mut(&mut lang_registry).unwrap().add(
+ Arc::new(Language::new(
LanguageConfig {
name: "Rust".to_string(),
path_suffixes: vec!["rs".to_string()],
@@ -2561,7 +2563,9 @@ mod tests {
..Default::default()
},
Some(tree_sitter_rust::language()),
- )));
+ )),
+
+ );
// Connect to a server as 2 clients.
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
@@ -2699,9 +2703,8 @@ mod tests {
// Set up a fake language server.
let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
- Arc::get_mut(&mut lang_registry)
- .unwrap()
- .add(Arc::new(Language::new(
+ Arc::get_mut(&mut lang_registry).unwrap().add(
+ Arc::new(Language::new(
LanguageConfig {
name: "Rust".to_string(),
path_suffixes: vec!["rs".to_string()],
@@ -2709,7 +2712,9 @@ mod tests {
..Default::default()
},
Some(tree_sitter_rust::language()),
- )));
+ )),
+
+ );
// Connect to a server as 2 clients.
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
@@ -2800,9 +2805,8 @@ mod tests {
// Set up a fake language server.
let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
- Arc::get_mut(&mut lang_registry)
- .unwrap()
- .add(Arc::new(Language::new(
+ Arc::get_mut(&mut lang_registry).unwrap().add(
+ Arc::new(Language::new(
LanguageConfig {
name: "Rust".to_string(),
path_suffixes: vec!["rs".to_string()],
@@ -2810,7 +2814,9 @@ mod tests {
..Default::default()
},
Some(tree_sitter_rust::language()),
- )));
+ )),
+
+ );
// Connect to a server as 2 clients.
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
@@ -3039,9 +3045,8 @@ mod tests {
// Set up a fake language server.
let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake();
- Arc::get_mut(&mut lang_registry)
- .unwrap()
- .add(Arc::new(Language::new(
+ Arc::get_mut(&mut lang_registry).unwrap().add(
+ Arc::new(Language::new(
LanguageConfig {
name: "Rust".to_string(),
path_suffixes: vec!["rs".to_string()],
@@ -3049,7 +3054,9 @@ mod tests {
..Default::default()
},
Some(tree_sitter_rust::language()),
- )));
+ )),
+
+ );
// Connect to a server as 2 clients.
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)
- .unwrap()
- .add(Arc::new(Language::new(
+ Arc::get_mut(&mut host_lang_registry).unwrap().add(
+ Arc::new(Language::new(
LanguageConfig {
name: "Rust".to_string(),
path_suffixes: vec!["rs".to_string()],
@@ -3855,7 +3861,9 @@ mod tests {
..Default::default()
},
None,
- )));
+ )),
+
+ );
let fs = FakeFs::new(cx.background());
fs.insert_tree(
@@ -141,6 +141,7 @@ pub struct StatusBar {
pub item_spacing: f32,
pub cursor_position: TextStyle,
pub diagnostic_message: TextStyle,
+ pub lsp_message: TextStyle,
}
#[derive(Deserialize, Default)]
@@ -19,6 +19,7 @@ project = { path = "../project" }
theme = { path = "../theme" }
util = { path = "../util" }
anyhow = "1.0.38"
+futures = "0.3"
log = "0.4"
parking_lot = "0.11.1"
postage = { version = "0.4.1", features = ["futures-traits"] }
@@ -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 {
let theme = &self.settings.borrow().theme.workspace.status_bar;
Flex::row()
- .with_children(
- self.left_items
- .iter()
- .map(|i| ChildView::new(i.as_ref()).aligned().boxed()),
- )
+ .with_children(self.left_items.iter().map(|i| {
+ ChildView::new(i.as_ref())
+ .aligned()
+ .contained()
+ .with_margin_right(theme.item_spacing)
+ .boxed()
+ }))
.with_child(Empty::new().flexible(1., true).boxed())
- .with_children(
- self.right_items
- .iter()
- .map(|i| ChildView::new(i.as_ref()).aligned().boxed()),
- )
+ .with_children(self.right_items.iter().map(|i| {
+ ChildView::new(i.as_ref())
+ .aligned()
+ .contained()
+ .with_margin_left(theme.item_spacing)
+ .boxed()
+ }))
.contained()
.with_style(theme.container)
.constrained()
@@ -1,3 +1,4 @@
+pub mod lsp_status;
pub mod menu;
pub mod pane;
pub mod pane_group;
@@ -55,6 +55,7 @@ theme_selector = { path = "../theme_selector" }
util = { path = "../util" }
workspace = { path = "../workspace" }
anyhow = "1.0.38"
+async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
async-recursion = "0.3"
async-trait = "0.1"
crossbeam-channel = "0.5.0"
@@ -77,9 +77,10 @@ border = { width = 1, color = "$border.0", left = true }
[workspace.status_bar]
padding = { left = 6, right = 6 }
height = 24
-item_spacing = 24
+item_spacing = 8
cursor_position = "$text.2"
diagnostic_message = "$text.2"
+lsp_message = "$text.2"
[workspace.toolbar]
height = 44
@@ -188,7 +189,7 @@ corner_radius = 6
[project_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]
text = "$text.1"
@@ -11,6 +11,5 @@ brackets = [
]
[language_server]
-binary = "rust-analyzer"
disk_based_diagnostic_sources = ["rustc"]
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::*;
use lazy_static::lazy_static;
use regex::Regex;
use rust_embed::RustEmbed;
-use std::borrow::Cow;
-use std::{str, sync::Arc};
+use serde::Deserialize;
+use smol::fs::{self, File};
+use std::{
+ borrow::Cow,
+ env::consts,
+ path::{Path, PathBuf},
+ str,
+ sync::Arc,
+};
+use util::{ResultExt, TryFutureExt};
#[derive(RustEmbed)]
#[folder = "languages"]
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) {
lazy_static! {
static ref REGEX: Regex = Regex::new("(?m)`([^`]+)\n`$").unwrap();
@@ -113,7 +236,12 @@ impl LspPostProcessor for RustPostProcessor {
}
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(markdown()));
languages
@@ -131,7 +259,7 @@ fn rust() -> Language {
.unwrap()
.with_outline_query(load_query("rust/outline.scm").as_ref())
.unwrap()
- .with_lsp_post_processor(RustPostProcessor)
+ .with_lsp_ext(RustLsp)
}
fn markdown() -> Language {
@@ -153,7 +281,7 @@ fn load_query(path: &str) -> Cow<'static, str> {
mod tests {
use super::*;
use gpui::color::Color;
- use language::LspPostProcessor;
+ use language::LspExt;
use theme::SyntaxTheme;
#[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`");
@@ -26,14 +26,17 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc<AppState> {
let client = Client::new(http.clone());
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
let mut languages = LanguageRegistry::new();
- languages.add(Arc::new(language::Language::new(
- language::LanguageConfig {
- name: "Rust".to_string(),
- path_suffixes: vec!["rs".to_string()],
- ..Default::default()
- },
- Some(tree_sitter_rust::language()),
- )));
+ languages.add(
+ Arc::new(language::Language::new(
+ language::LanguageConfig {
+ name: "Rust".to_string(),
+ path_suffixes: vec!["rs".to_string()],
+ ..Default::default()
+ },
+ Some(tree_sitter_rust::language()),
+ )),
+
+ );
Arc::new(AppState {
settings_tx: Arc::new(Mutex::new(settings_tx)),
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![
Binding::new("cmd-=", AdjustBufferFontSize(1.), None),
Binding::new("cmd--", AdjustBufferFontSize(-1.), None),
@@ -97,11 +99,19 @@ pub fn build_workspace(
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 =
cx.add_view(|_| editor::items::CursorPosition::new(app_state.settings.clone()));
workspace.status_bar().update(cx, |status_bar, cx| {
status_bar.add_left_item(diagnostic_summary, 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);
});
@@ -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
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.
if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTARIZATION_USERNAME && -n $APPLE_NOTARIZATION_PASSWORD ]]; then
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
rm /tmp/zed-certificate.p12
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
security default-keychain -s login.keychain
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-*