Open a buffer for every language server error when clicking on status

This commit is contained in:
Antonio Scandurra 2022-06-16 09:59:47 +02:00
parent 7239aac532
commit 4e4210ac39
9 changed files with 180 additions and 109 deletions

20
Cargo.lock generated
View file

@ -2563,6 +2563,25 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "lsp_status"
version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"editor",
"futures",
"gpui",
"language",
"postage",
"project",
"settings",
"smallvec",
"theme",
"util",
"workspace",
]
[[package]] [[package]]
name = "malloc_buf" name = "malloc_buf"
version = "0.0.6" version = "0.0.6"
@ -6048,6 +6067,7 @@ dependencies = [
"libc", "libc",
"log", "log",
"lsp", "lsp",
"lsp_status",
"num_cpus", "num_cpus",
"outline", "outline",
"parking_lot 0.11.2", "parking_lot 0.11.2",

View file

@ -184,7 +184,7 @@ pub enum LanguageServerBinaryStatus {
Downloading, Downloading,
Downloaded, Downloaded,
Cached, Cached,
Failed, Failed { error: String },
} }
pub struct LanguageRegistry { pub struct LanguageRegistry {
@ -382,7 +382,7 @@ async fn get_server_binary_path(
statuses.clone(), statuses.clone(),
) )
.await; .await;
if path.is_err() { if let Err(error) = path.as_ref() {
if let Some(cached_path) = adapter.cached_server_binary(container_dir).await { if let Some(cached_path) = adapter.cached_server_binary(container_dir).await {
statuses statuses
.broadcast((language.clone(), LanguageServerBinaryStatus::Cached)) .broadcast((language.clone(), LanguageServerBinaryStatus::Cached))
@ -390,7 +390,12 @@ async fn get_server_binary_path(
return Ok(cached_path); return Ok(cached_path);
} else { } else {
statuses statuses
.broadcast((language.clone(), LanguageServerBinaryStatus::Failed)) .broadcast((
language.clone(),
LanguageServerBinaryStatus::Failed {
error: format!("{:?}", error),
},
))
.await?; .await?;
} }
} }

View file

@ -0,0 +1,23 @@
[package]
name = "lsp_status"
version = "0.1.0"
edition = "2021"
[lib]
path = "src/lsp_status.rs"
doctest = false
[dependencies]
collections = { path = "../collections" }
editor = { path = "../editor" }
language = { path = "../language" }
gpui = { path = "../gpui" }
project = { path = "../project" }
settings = { path = "../settings" }
theme = { path = "../theme" }
util = { path = "../util" }
workspace = { path = "../workspace" }
anyhow = "1.0"
futures = "0.3"
postage = { version = "0.4", features = ["futures-traits"] }
smallvec = { version = "1.6", features = ["union"] }

View file

@ -1,6 +1,6 @@
use crate::{ItemHandle, StatusItemView}; use editor::Editor;
use futures::StreamExt; use futures::StreamExt;
use gpui::{actions, AppContext, EventContext}; use gpui::{actions, AppContext, EventContext, ViewHandle};
use gpui::{ use gpui::{
elements::*, platform::CursorStyle, Entity, ModelHandle, MutableAppContext, RenderContext, elements::*, platform::CursorStyle, Entity, ModelHandle, MutableAppContext, RenderContext,
View, ViewContext, View, ViewContext,
@ -12,73 +12,100 @@ use smallvec::SmallVec;
use std::cmp::Reverse; use std::cmp::Reverse;
use std::fmt::Write; use std::fmt::Write;
use std::sync::Arc; use std::sync::Arc;
use util::ResultExt;
use workspace::{ItemHandle, StatusItemView, Workspace};
actions!(lsp_status, [DismissErrorMessage]); actions!(lsp_status, [ShowErrorMessage]);
pub struct LspStatus { pub enum Event {
checking_for_update: Vec<String>, ShowError { lsp_name: Arc<str>, error: String },
downloading: Vec<String>, }
failed: Vec<String>,
pub struct LspStatusItem {
statuses: Vec<LspStatus>,
project: ModelHandle<Project>, project: ModelHandle<Project>,
} }
pub fn init(cx: &mut MutableAppContext) { struct LspStatus {
cx.add_action(LspStatus::dismiss_error_message); name: Arc<str>,
status: LanguageServerBinaryStatus,
} }
impl LspStatus { pub fn init(cx: &mut MutableAppContext) {
cx.add_action(LspStatusItem::show_error_message);
}
impl LspStatusItem {
pub fn new( pub fn new(
project: &ModelHandle<Project>, workspace: &mut Workspace,
languages: Arc<LanguageRegistry>, languages: Arc<LanguageRegistry>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Workspace>,
) -> Self { ) -> ViewHandle<LspStatusItem> {
let mut status_events = languages.language_server_binary_statuses(); let project = workspace.project().clone();
cx.spawn_weak(|this, mut cx| async move { let this = cx.add_view(|cx: &mut ViewContext<Self>| {
while let Some((language, event)) = status_events.next().await { let mut status_events = languages.language_server_binary_statuses();
if let Some(this) = this.upgrade(&cx) { cx.spawn_weak(|this, mut cx| async move {
this.update(&mut cx, |this, cx| { while let Some((language, event)) = status_events.next().await {
for vector in [ if let Some(this) = this.upgrade(&cx) {
&mut this.checking_for_update, this.update(&mut cx, |this, cx| {
&mut this.downloading, this.statuses.retain(|s| s.name != language.name());
&mut this.failed, this.statuses.push(LspStatus {
] { name: language.name(),
vector.retain(|name| name != language.name().as_ref()); status: event,
} });
cx.notify();
});
} else {
break;
}
}
})
.detach();
cx.observe(&project, |_, _, cx| cx.notify()).detach();
match event { Self {
LanguageServerBinaryStatus::CheckingForUpdate => { statuses: Default::default(),
this.checking_for_update.push(language.name().to_string()); project: project.clone(),
} }
LanguageServerBinaryStatus::Downloading => { });
this.downloading.push(language.name().to_string()); cx.subscribe(&this, move |workspace, _, event, cx| match event {
} Event::ShowError { lsp_name, error } => {
LanguageServerBinaryStatus::Failed => { if let Some(buffer) = project
this.failed.push(language.name().to_string()); .update(cx, |project, cx| project.create_buffer(&error, None, cx))
} .log_err()
LanguageServerBinaryStatus::Downloaded {
| LanguageServerBinaryStatus::Cached => {} buffer.update(cx, |buffer, cx| {
} buffer.edit(
[(0..0, format!("Language server error: {}\n\n", lsp_name))],
cx.notify(); cx,
);
}); });
} else { workspace.add_item(
break; Box::new(
cx.add_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
),
cx,
);
} }
} }
}) })
.detach(); .detach();
cx.observe(project, |_, _, cx| cx.notify()).detach(); this
Self {
checking_for_update: Default::default(),
downloading: Default::default(),
failed: Default::default(),
project: project.clone(),
}
} }
fn dismiss_error_message(&mut self, _: &DismissErrorMessage, cx: &mut ViewContext<Self>) { fn show_error_message(&mut self, _: &ShowErrorMessage, cx: &mut ViewContext<Self>) {
self.failed.clear(); self.statuses.retain(|status| {
if let LanguageServerBinaryStatus::Failed { error } = &status.status {
cx.emit(Event::ShowError {
lsp_name: status.name.clone(),
error: error.clone(),
});
false
} else {
true
}
});
cx.notify(); cx.notify();
} }
@ -107,11 +134,11 @@ impl LspStatus {
} }
} }
impl Entity for LspStatus { impl Entity for LspStatusItem {
type Event = (); type Event = Event;
} }
impl View for LspStatus { impl View for LspStatusItem {
fn ui_name() -> &'static str { fn ui_name() -> &'static str {
"LspStatus" "LspStatus"
} }
@ -143,33 +170,51 @@ impl View for LspStatus {
} else { } else {
drop(pending_work); drop(pending_work);
if !self.downloading.is_empty() { let mut downloading = SmallVec::<[_; 3]>::new();
let mut checking_for_update = SmallVec::<[_; 3]>::new();
let mut failed = SmallVec::<[_; 3]>::new();
for status in &self.statuses {
match status.status {
LanguageServerBinaryStatus::CheckingForUpdate => {
checking_for_update.push(status.name.clone());
}
LanguageServerBinaryStatus::Downloading => {
downloading.push(status.name.clone());
}
LanguageServerBinaryStatus::Failed { .. } => {
failed.push(status.name.clone());
}
LanguageServerBinaryStatus::Downloaded | LanguageServerBinaryStatus::Cached => {
}
}
}
if !downloading.is_empty() {
icon = Some("icons/download-solid-14.svg"); icon = Some("icons/download-solid-14.svg");
message = format!( message = format!(
"Downloading {} language server{}...", "Downloading {} language server{}...",
self.downloading.join(", "), downloading.join(", "),
if self.downloading.len() > 1 { "s" } else { "" } if downloading.len() > 1 { "s" } else { "" }
); );
} else if !self.checking_for_update.is_empty() { } else if !checking_for_update.is_empty() {
icon = Some("icons/download-solid-14.svg"); icon = Some("icons/download-solid-14.svg");
message = format!( message = format!(
"Checking for updates to {} language server{}...", "Checking for updates to {} language server{}...",
self.checking_for_update.join(", "), checking_for_update.join(", "),
if self.checking_for_update.len() > 1 { if checking_for_update.len() > 1 {
"s" "s"
} else { } else {
"" ""
} }
); );
} else if !self.failed.is_empty() { } else if !failed.is_empty() {
icon = Some("icons/warning-solid-14.svg"); icon = Some("icons/warning-solid-14.svg");
message = format!( message = format!(
"Failed to download {} language server{}. Click to dismiss.", "Failed to download {} language server{}. Click to show error.",
self.failed.join(", "), failed.join(", "),
if self.failed.len() > 1 { "s" } else { "" } if failed.len() > 1 { "s" } else { "" }
); );
handler = handler = Some(|_, _, cx: &mut EventContext| cx.dispatch_action(ShowErrorMessage));
Some(|_, _, cx: &mut EventContext| cx.dispatch_action(DismissErrorMessage));
} else { } else {
return Empty::new().boxed(); return Empty::new().boxed();
} }
@ -217,6 +262,6 @@ impl View for LspStatus {
} }
} }
impl StatusItemView for LspStatus { impl StatusItemView for LspStatusItem {
fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext<Self>) {} fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext<Self>) {}
} }

View file

@ -1,4 +1,3 @@
pub mod lsp_status;
pub mod pane; pub mod pane;
pub mod pane_group; pub mod pane_group;
pub mod sidebar; pub mod sidebar;

View file

@ -37,6 +37,7 @@ gpui = { path = "../gpui" }
journal = { path = "../journal" } journal = { path = "../journal" }
language = { path = "../language" } language = { path = "../language" }
lsp = { path = "../lsp" } lsp = { path = "../lsp" }
lsp_status = { path = "../lsp_status" }
outline = { path = "../outline" } outline = { path = "../outline" }
project = { path = "../project" } project = { path = "../project" }
project_panel = { path = "../project_panel" } project_panel = { path = "../project_panel" }

View file

@ -39,10 +39,12 @@ pub async fn npm_package_latest_version(name: &str) -> Result<String> {
let output = smol::process::Command::new("npm") let output = smol::process::Command::new("npm")
.args(["info", name, "--json"]) .args(["info", name, "--json"])
.output() .output()
.await?; .await
.context("failed to run npm info")?;
if !output.status.success() { if !output.status.success() {
Err(anyhow!( Err(anyhow!(
"failed to execute npm info: {:?}", "failed to execute npm info:\nstdout: {:?}\nstderr: {:?}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr) String::from_utf8_lossy(&output.stderr)
))?; ))?;
} }
@ -71,7 +73,8 @@ pub async fn npm_install_packages(
.context("failed to run npm install")?; .context("failed to run npm install")?;
if !output.status.success() { if !output.status.success() {
Err(anyhow!( Err(anyhow!(
"failed to execute npm install: {:?}", "failed to execute npm install:\nstdout: {:?}\nstderr: {:?}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr) String::from_utf8_lossy(&output.stderr)
))?; ))?;
} }

View file

@ -1,8 +1,8 @@
use super::installation::{npm_install_packages, npm_package_latest_version};
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use client::http::HttpClient; use client::http::HttpClient;
use futures::{future::BoxFuture, FutureExt, StreamExt}; use futures::{future::BoxFuture, FutureExt, StreamExt};
use language::{LanguageServerName, LspAdapter}; use language::{LanguageServerName, LspAdapter};
use serde::Deserialize;
use serde_json::json; use serde_json::json;
use smol::fs; use smol::fs;
use std::{ use std::{
@ -33,25 +33,7 @@ impl LspAdapter for JsonLspAdapter {
_: Arc<dyn HttpClient>, _: Arc<dyn HttpClient>,
) -> BoxFuture<'static, Result<Box<dyn 'static + Any + Send>>> { ) -> BoxFuture<'static, Result<Box<dyn 'static + Any + Send>>> {
async move { async move {
#[derive(Deserialize)] Ok(Box::new(npm_package_latest_version("vscode-json-languageserver").await?) as Box<_>)
struct NpmInfo {
versions: Vec<String>,
}
let output = smol::process::Command::new("npm")
.args(["info", "vscode-json-languageserver", "--json"])
.output()
.await?;
if !output.status.success() {
Err(anyhow!("failed to execute npm info"))?;
}
let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?;
Ok(Box::new(
info.versions
.pop()
.ok_or_else(|| anyhow!("no versions found in npm info"))?,
) as Box<_>)
} }
.boxed() .boxed()
} }
@ -71,16 +53,11 @@ impl LspAdapter for JsonLspAdapter {
let binary_path = version_dir.join(Self::BIN_PATH); let binary_path = version_dir.join(Self::BIN_PATH);
if fs::metadata(&binary_path).await.is_err() { if fs::metadata(&binary_path).await.is_err() {
let output = smol::process::Command::new("npm") npm_install_packages(
.current_dir(&version_dir) [("vscode-json-languageserver", version.as_str())],
.arg("install") &version_dir,
.arg(format!("vscode-json-languageserver@{}", version)) )
.output() .await?;
.await
.context("failed to run npm install")?;
if !output.status.success() {
Err(anyhow!("failed to install vscode-json-languageserver"))?;
}
if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() {
while let Some(entry) = entries.next().await { while let Some(entry) = entries.next().await {

View file

@ -129,7 +129,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
}, },
); );
workspace::lsp_status::init(cx); lsp_status::init(cx);
settings::KeymapFileContent::load_defaults(cx); settings::KeymapFileContent::load_defaults(cx);
} }
@ -209,9 +209,7 @@ pub fn initialize_workspace(
let diagnostic_summary = let diagnostic_summary =
cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace.project(), cx)); cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace.project(), cx));
let lsp_status = cx.add_view(|cx| { let lsp_status = lsp_status::LspStatusItem::new(workspace, app_state.languages.clone(), cx);
workspace::lsp_status::LspStatus::new(workspace.project(), app_state.languages.clone(), cx)
});
let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new());
let auto_update = cx.add_view(|cx| auto_update::AutoUpdateIndicator::new(cx)); let auto_update = cx.add_view(|cx| auto_update::AutoUpdateIndicator::new(cx));
let feedback_link = cx.add_view(|_| feedback::FeedbackLink); let feedback_link = cx.add_view(|_| feedback::FeedbackLink);