Merge pull request #587 from zed-industries/lsp-progress
Show language server progress in the status bar
This commit is contained in:
commit
fd36b25db1
11 changed files with 515 additions and 130 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -5852,6 +5852,7 @@ dependencies = [
|
|||
"postage",
|
||||
"project",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
"theme",
|
||||
"util",
|
||||
]
|
||||
|
|
|
@ -631,6 +631,9 @@ impl Client {
|
|||
} else {
|
||||
log::info!("unhandled message {}", type_name);
|
||||
}
|
||||
|
||||
// Don't starve the main thread when receiving lots of messages at once.
|
||||
smol::future::yield_now().await;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -35,6 +35,7 @@ type ResponseHandler = Box<dyn Send + FnOnce(Result<&str, Error>)>;
|
|||
pub struct LanguageServer {
|
||||
next_id: AtomicUsize,
|
||||
outbound_tx: channel::Sender<Vec<u8>>,
|
||||
name: String,
|
||||
capabilities: ServerCapabilities,
|
||||
notification_handlers: Arc<RwLock<HashMap<&'static str, NotificationHandler>>>,
|
||||
response_handlers: Arc<Mutex<HashMap<usize, ResponseHandler>>>,
|
||||
|
@ -118,9 +119,11 @@ impl LanguageServer {
|
|||
.spawn()?;
|
||||
let stdin = server.stdin.take().unwrap();
|
||||
let stdout = server.stdout.take().unwrap();
|
||||
Ok(Self::new_internal(
|
||||
stdin, stdout, root_path, options, background,
|
||||
))
|
||||
let mut server = Self::new_internal(stdin, stdout, root_path, options, background);
|
||||
if let Some(name) = binary_path.file_name() {
|
||||
server.name = name.to_string_lossy().to_string();
|
||||
}
|
||||
Ok(server)
|
||||
}
|
||||
|
||||
fn new_internal<Stdin, Stdout>(
|
||||
|
@ -222,6 +225,7 @@ impl LanguageServer {
|
|||
Self {
|
||||
notification_handlers,
|
||||
response_handlers,
|
||||
name: Default::default(),
|
||||
capabilities: Default::default(),
|
||||
next_id: Default::default(),
|
||||
outbound_tx,
|
||||
|
@ -292,7 +296,13 @@ impl LanguageServer {
|
|||
};
|
||||
|
||||
let response = this.request::<request::Initialize>(params).await?;
|
||||
Arc::get_mut(&mut this).unwrap().capabilities = response.capabilities;
|
||||
{
|
||||
let this = Arc::get_mut(&mut this).unwrap();
|
||||
if let Some(info) = response.server_info {
|
||||
this.name = info.name;
|
||||
}
|
||||
this.capabilities = response.capabilities;
|
||||
}
|
||||
this.notify::<notification::Initialized>(InitializedParams {})?;
|
||||
Ok(this)
|
||||
}
|
||||
|
@ -355,6 +365,10 @@ impl LanguageServer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn name<'a>(self: &'a Arc<Self>) -> &'a str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn capabilities<'a>(self: &'a Arc<Self>) -> &'a ServerCapabilities {
|
||||
&self.capabilities
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ pub mod worktree;
|
|||
use anyhow::{anyhow, Context, Result};
|
||||
use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
|
||||
use clock::ReplicaId;
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use collections::{hash_map, BTreeMap, HashMap, HashSet};
|
||||
use futures::{future::Shared, Future, FutureExt, StreamExt, TryFutureExt};
|
||||
use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
|
||||
use gpui::{
|
||||
|
@ -28,7 +28,6 @@ use rand::prelude::*;
|
|||
use search::SearchQuery;
|
||||
use sha2::{Digest, Sha256};
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
use smol::block_on;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
cmp::{self, Ordering},
|
||||
|
@ -52,6 +51,8 @@ pub struct Project {
|
|||
languages: Arc<LanguageRegistry>,
|
||||
language_servers: HashMap<(WorktreeId, Arc<str>), Arc<LanguageServer>>,
|
||||
started_language_servers: HashMap<(WorktreeId, Arc<str>), Task<Option<Arc<LanguageServer>>>>,
|
||||
language_server_statuses: BTreeMap<usize, LanguageServerStatus>,
|
||||
next_language_server_id: usize,
|
||||
client: Arc<client::Client>,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
fs: Arc<dyn Fs>,
|
||||
|
@ -115,6 +116,33 @@ pub enum Event {
|
|||
DiagnosticsUpdated(ProjectPath),
|
||||
}
|
||||
|
||||
enum LanguageServerEvent {
|
||||
WorkStart {
|
||||
token: String,
|
||||
},
|
||||
WorkProgress {
|
||||
token: String,
|
||||
progress: LanguageServerProgress,
|
||||
},
|
||||
WorkEnd {
|
||||
token: String,
|
||||
},
|
||||
DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
|
||||
}
|
||||
|
||||
pub struct LanguageServerStatus {
|
||||
pub name: String,
|
||||
pub pending_work: BTreeMap<String, LanguageServerProgress>,
|
||||
pending_diagnostic_updates: isize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LanguageServerProgress {
|
||||
pub message: Option<String>,
|
||||
pub percentage: Option<usize>,
|
||||
pub last_update_at: Instant,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||
pub struct ProjectPath {
|
||||
pub worktree_id: WorktreeId,
|
||||
|
@ -203,8 +231,8 @@ impl Project {
|
|||
client.add_entity_message_handler(Self::handle_add_collaborator);
|
||||
client.add_entity_message_handler(Self::handle_buffer_reloaded);
|
||||
client.add_entity_message_handler(Self::handle_buffer_saved);
|
||||
client.add_entity_message_handler(Self::handle_disk_based_diagnostics_updated);
|
||||
client.add_entity_message_handler(Self::handle_disk_based_diagnostics_updating);
|
||||
client.add_entity_message_handler(Self::handle_start_language_server);
|
||||
client.add_entity_message_handler(Self::handle_update_language_server);
|
||||
client.add_entity_message_handler(Self::handle_remove_collaborator);
|
||||
client.add_entity_message_handler(Self::handle_register_worktree);
|
||||
client.add_entity_message_handler(Self::handle_unregister_worktree);
|
||||
|
@ -304,6 +332,8 @@ impl Project {
|
|||
language_servers_with_diagnostics_running: 0,
|
||||
language_servers: Default::default(),
|
||||
started_language_servers: Default::default(),
|
||||
language_server_statuses: Default::default(),
|
||||
next_language_server_id: 0,
|
||||
nonce: StdRng::from_entropy().gen(),
|
||||
}
|
||||
})
|
||||
|
@ -373,6 +403,21 @@ impl Project {
|
|||
language_servers_with_diagnostics_running: 0,
|
||||
language_servers: Default::default(),
|
||||
started_language_servers: Default::default(),
|
||||
language_server_statuses: response
|
||||
.language_servers
|
||||
.into_iter()
|
||||
.map(|server| {
|
||||
(
|
||||
server.id as usize,
|
||||
LanguageServerStatus {
|
||||
name: server.name,
|
||||
pending_work: Default::default(),
|
||||
pending_diagnostic_updates: 0,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
next_language_server_id: 0,
|
||||
opened_buffers: Default::default(),
|
||||
buffer_snapshots: Default::default(),
|
||||
nonce: StdRng::from_entropy().gen(),
|
||||
|
@ -1155,92 +1200,71 @@ impl Project {
|
|||
language: Arc<Language>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
enum LspEvent {
|
||||
DiagnosticsStart,
|
||||
DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
|
||||
DiagnosticsFinish,
|
||||
}
|
||||
|
||||
let key = (worktree_id, language.name());
|
||||
self.started_language_servers
|
||||
.entry(key.clone())
|
||||
.or_insert_with(|| {
|
||||
let server_id = post_inc(&mut self.next_language_server_id);
|
||||
let language_server = self.languages.start_language_server(
|
||||
language.clone(),
|
||||
worktree_path,
|
||||
self.client.http_client(),
|
||||
cx,
|
||||
);
|
||||
let rpc = self.client.clone();
|
||||
cx.spawn_weak(|this, mut cx| async move {
|
||||
let mut language_server = language_server?.await.log_err()?;
|
||||
let this = this.upgrade(&cx)?;
|
||||
let (language_server_events_tx, language_server_events_rx) =
|
||||
smol::channel::unbounded();
|
||||
|
||||
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();
|
||||
let language_server_events_tx = language_server_events_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)))
|
||||
language_server_events_tx
|
||||
.try_send(LanguageServerEvent::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
|
||||
// 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),
|
||||
lsp::NumberOrString::String(token) => token,
|
||||
lsp::NumberOrString::Number(token) => {
|
||||
log::info!("skipping numeric progress token {}", token);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
match params.value {
|
||||
lsp::ProgressParamsValue::WorkDone(progress) => match progress {
|
||||
lsp::WorkDoneProgress::Begin(_) => {
|
||||
language_server_events_tx
|
||||
.try_send(LanguageServerEvent::WorkStart { token })
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
lsp::WorkDoneProgress::Report(report) => {
|
||||
language_server_events_tx
|
||||
.try_send(LanguageServerEvent::WorkProgress {
|
||||
token,
|
||||
progress: LanguageServerProgress {
|
||||
message: report.message,
|
||||
percentage: report
|
||||
.percentage
|
||||
.map(|p| p as usize),
|
||||
last_update_at: Instant::now(),
|
||||
},
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
lsp::WorkDoneProgress::End(_) => {
|
||||
language_server_events_tx
|
||||
.try_send(LanguageServerEvent::WorkEnd { token })
|
||||
.ok();
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
@ -1249,43 +1273,14 @@ impl Project {
|
|||
cx.spawn(|mut cx| {
|
||||
let this = this.downgrade();
|
||||
async move {
|
||||
while let Ok(message) = diagnostics_rx.recv().await {
|
||||
while let Ok(event) = language_server_events_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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.on_lsp_event(server_id, event, &language, cx)
|
||||
});
|
||||
|
||||
// Don't starve the main thread when lots of events arrive all at once.
|
||||
smol::future::yield_now().await;
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
@ -1296,6 +1291,26 @@ impl Project {
|
|||
this.update(&mut cx, |this, cx| {
|
||||
this.language_servers
|
||||
.insert(key.clone(), language_server.clone());
|
||||
this.language_server_statuses.insert(
|
||||
server_id,
|
||||
LanguageServerStatus {
|
||||
name: language_server.name().to_string(),
|
||||
pending_work: Default::default(),
|
||||
pending_diagnostic_updates: 0,
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(project_id) = this.remote_id() {
|
||||
this.client
|
||||
.send(proto::StartLanguageServer {
|
||||
project_id,
|
||||
server: Some(proto::LanguageServer {
|
||||
id: server_id as u64,
|
||||
name: language_server.name().to_string(),
|
||||
}),
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
// Tell the language server about every open buffer in the worktree that matches the language.
|
||||
for buffer in this.opened_buffers.values() {
|
||||
|
@ -1350,6 +1365,7 @@ impl Project {
|
|||
}
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
Some(())
|
||||
});
|
||||
|
||||
|
@ -1358,6 +1374,185 @@ impl Project {
|
|||
});
|
||||
}
|
||||
|
||||
fn on_lsp_event(
|
||||
&mut self,
|
||||
language_server_id: usize,
|
||||
event: LanguageServerEvent,
|
||||
language: &Arc<Language>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let disk_diagnostics_token = language.disk_based_diagnostics_progress_token();
|
||||
let language_server_status =
|
||||
if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
|
||||
status
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
match event {
|
||||
LanguageServerEvent::WorkStart { token } => {
|
||||
if Some(&token) == disk_diagnostics_token {
|
||||
language_server_status.pending_diagnostic_updates += 1;
|
||||
if language_server_status.pending_diagnostic_updates == 1 {
|
||||
self.disk_based_diagnostics_started(cx);
|
||||
self.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(
|
||||
proto::LspDiskBasedDiagnosticsUpdating {},
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.on_lsp_work_start(language_server_id, token.clone(), cx);
|
||||
self.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::WorkStart(proto::LspWorkStart {
|
||||
token,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
LanguageServerEvent::WorkProgress { token, progress } => {
|
||||
if Some(&token) != disk_diagnostics_token {
|
||||
self.on_lsp_work_progress(
|
||||
language_server_id,
|
||||
token.clone(),
|
||||
progress.clone(),
|
||||
cx,
|
||||
);
|
||||
self.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::WorkProgress(
|
||||
proto::LspWorkProgress {
|
||||
token,
|
||||
message: progress.message,
|
||||
percentage: progress.percentage.map(|p| p as u32),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
LanguageServerEvent::WorkEnd { token } => {
|
||||
if Some(&token) == disk_diagnostics_token {
|
||||
language_server_status.pending_diagnostic_updates -= 1;
|
||||
if language_server_status.pending_diagnostic_updates == 0 {
|
||||
self.disk_based_diagnostics_finished(cx);
|
||||
self.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
|
||||
proto::LspDiskBasedDiagnosticsUpdated {},
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.on_lsp_work_end(language_server_id, token.clone(), cx);
|
||||
self.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::WorkEnd(proto::LspWorkEnd {
|
||||
token,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
LanguageServerEvent::DiagnosticsUpdate(mut params) => {
|
||||
language.process_diagnostics(&mut params);
|
||||
|
||||
if disk_diagnostics_token.is_none() {
|
||||
self.disk_based_diagnostics_started(cx);
|
||||
self.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(
|
||||
proto::LspDiskBasedDiagnosticsUpdating {},
|
||||
),
|
||||
);
|
||||
}
|
||||
self.update_diagnostics(
|
||||
params,
|
||||
language
|
||||
.disk_based_diagnostic_sources()
|
||||
.unwrap_or(&Default::default()),
|
||||
cx,
|
||||
)
|
||||
.log_err();
|
||||
if disk_diagnostics_token.is_none() {
|
||||
self.disk_based_diagnostics_finished(cx);
|
||||
self.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
|
||||
proto::LspDiskBasedDiagnosticsUpdated {},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_lsp_work_start(
|
||||
&mut self,
|
||||
language_server_id: usize,
|
||||
token: String,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
|
||||
status.pending_work.insert(
|
||||
token,
|
||||
LanguageServerProgress {
|
||||
message: None,
|
||||
percentage: None,
|
||||
last_update_at: Instant::now(),
|
||||
},
|
||||
);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_lsp_work_progress(
|
||||
&mut self,
|
||||
language_server_id: usize,
|
||||
token: String,
|
||||
progress: LanguageServerProgress,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
|
||||
status.pending_work.insert(token, progress);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_lsp_work_end(
|
||||
&mut self,
|
||||
language_server_id: usize,
|
||||
token: String,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
|
||||
status.pending_work.remove(&token);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn broadcast_language_server_update(
|
||||
&self,
|
||||
language_server_id: usize,
|
||||
event: proto::update_language_server::Variant,
|
||||
) {
|
||||
if let Some(project_id) = self.remote_id() {
|
||||
self.client
|
||||
.send(proto::UpdateLanguageServer {
|
||||
project_id,
|
||||
language_server_id: language_server_id as u64,
|
||||
variant: Some(event),
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn language_server_statuses(
|
||||
&self,
|
||||
) -> impl DoubleEndedIterator<Item = &LanguageServerStatus> {
|
||||
self.language_server_statuses.values()
|
||||
}
|
||||
|
||||
pub fn update_diagnostics(
|
||||
&mut self,
|
||||
params: lsp::PublishDiagnosticsParams,
|
||||
|
@ -3096,23 +3291,76 @@ impl Project {
|
|||
})
|
||||
}
|
||||
|
||||
async fn handle_disk_based_diagnostics_updating(
|
||||
async fn handle_start_language_server(
|
||||
this: ModelHandle<Self>,
|
||||
_: TypedEnvelope<proto::DiskBasedDiagnosticsUpdating>,
|
||||
envelope: TypedEnvelope<proto::StartLanguageServer>,
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, cx| this.disk_based_diagnostics_started(cx));
|
||||
let server = envelope
|
||||
.payload
|
||||
.server
|
||||
.ok_or_else(|| anyhow!("invalid server"))?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.language_server_statuses.insert(
|
||||
server.id as usize,
|
||||
LanguageServerStatus {
|
||||
name: server.name,
|
||||
pending_work: Default::default(),
|
||||
pending_diagnostic_updates: 0,
|
||||
},
|
||||
);
|
||||
cx.notify();
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_disk_based_diagnostics_updated(
|
||||
async fn handle_update_language_server(
|
||||
this: ModelHandle<Self>,
|
||||
_: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,
|
||||
envelope: TypedEnvelope<proto::UpdateLanguageServer>,
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, cx| this.disk_based_diagnostics_finished(cx));
|
||||
let language_server_id = envelope.payload.language_server_id as usize;
|
||||
match envelope
|
||||
.payload
|
||||
.variant
|
||||
.ok_or_else(|| anyhow!("invalid variant"))?
|
||||
{
|
||||
proto::update_language_server::Variant::WorkStart(payload) => {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.on_lsp_work_start(language_server_id, payload.token, cx);
|
||||
})
|
||||
}
|
||||
proto::update_language_server::Variant::WorkProgress(payload) => {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.on_lsp_work_progress(
|
||||
language_server_id,
|
||||
payload.token,
|
||||
LanguageServerProgress {
|
||||
message: payload.message,
|
||||
percentage: payload.percentage.map(|p| p as usize),
|
||||
last_update_at: Instant::now(),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
})
|
||||
}
|
||||
proto::update_language_server::Variant::WorkEnd(payload) => {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.on_lsp_work_end(language_server_id, payload.token, cx);
|
||||
})
|
||||
}
|
||||
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(_) => {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.disk_based_diagnostics_started(cx);
|
||||
})
|
||||
}
|
||||
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(_) => {
|
||||
this.update(&mut cx, |this, cx| this.disk_based_diagnostics_finished(cx));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -37,8 +37,8 @@ message Envelope {
|
|||
UnregisterWorktree unregister_worktree = 29;
|
||||
UpdateWorktree update_worktree = 31;
|
||||
UpdateDiagnosticSummary update_diagnostic_summary = 32;
|
||||
DiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 33;
|
||||
DiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 34;
|
||||
StartLanguageServer start_language_server = 33;
|
||||
UpdateLanguageServer update_language_server = 34;
|
||||
|
||||
OpenBuffer open_buffer = 35;
|
||||
OpenBufferResponse open_buffer_response = 36;
|
||||
|
@ -122,6 +122,7 @@ message JoinProjectResponse {
|
|||
uint32 replica_id = 1;
|
||||
repeated Worktree worktrees = 2;
|
||||
repeated Collaborator collaborators = 3;
|
||||
repeated LanguageServer language_servers = 4;
|
||||
}
|
||||
|
||||
message LeaveProject {
|
||||
|
@ -410,6 +411,16 @@ message LocalTimestamp {
|
|||
uint32 value = 2;
|
||||
}
|
||||
|
||||
message LanguageServer {
|
||||
uint64 id = 1;
|
||||
string name = 2;
|
||||
}
|
||||
|
||||
message StartLanguageServer {
|
||||
uint64 project_id = 1;
|
||||
LanguageServer server = 2;
|
||||
}
|
||||
|
||||
message UpdateDiagnosticSummary {
|
||||
uint64 project_id = 1;
|
||||
uint64 worktree_id = 2;
|
||||
|
@ -424,14 +435,36 @@ message DiagnosticSummary {
|
|||
uint32 hint_count = 5;
|
||||
}
|
||||
|
||||
message DiskBasedDiagnosticsUpdating {
|
||||
message UpdateLanguageServer {
|
||||
uint64 project_id = 1;
|
||||
uint64 language_server_id = 2;
|
||||
oneof variant {
|
||||
LspWorkStart work_start = 3;
|
||||
LspWorkProgress work_progress = 4;
|
||||
LspWorkEnd work_end = 5;
|
||||
LspDiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 6;
|
||||
LspDiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 7;
|
||||
}
|
||||
}
|
||||
|
||||
message DiskBasedDiagnosticsUpdated {
|
||||
uint64 project_id = 1;
|
||||
message LspWorkStart {
|
||||
string token = 1;
|
||||
}
|
||||
|
||||
message LspWorkProgress {
|
||||
string token = 1;
|
||||
optional string message = 2;
|
||||
optional uint32 percentage = 3;
|
||||
}
|
||||
|
||||
message LspWorkEnd {
|
||||
string token = 1;
|
||||
}
|
||||
|
||||
message LspDiskBasedDiagnosticsUpdating {}
|
||||
|
||||
message LspDiskBasedDiagnosticsUpdated {}
|
||||
|
||||
message GetChannels {}
|
||||
|
||||
message GetChannelsResponse {
|
||||
|
|
|
@ -146,8 +146,6 @@ messages!(
|
|||
(BufferReloaded, Foreground),
|
||||
(BufferSaved, Foreground),
|
||||
(ChannelMessageSent, Foreground),
|
||||
(DiskBasedDiagnosticsUpdated, Background),
|
||||
(DiskBasedDiagnosticsUpdating, Background),
|
||||
(Error, Foreground),
|
||||
(FormatBuffers, Foreground),
|
||||
(FormatBuffersResponse, Foreground),
|
||||
|
@ -173,6 +171,8 @@ messages!(
|
|||
(JoinChannelResponse, Foreground),
|
||||
(JoinProject, Foreground),
|
||||
(JoinProjectResponse, Foreground),
|
||||
(StartLanguageServer, Foreground),
|
||||
(UpdateLanguageServer, Foreground),
|
||||
(LeaveChannel, Foreground),
|
||||
(LeaveProject, Foreground),
|
||||
(OpenBuffer, Background),
|
||||
|
@ -246,8 +246,6 @@ entity_messages!(
|
|||
ApplyCompletionAdditionalEdits,
|
||||
BufferReloaded,
|
||||
BufferSaved,
|
||||
DiskBasedDiagnosticsUpdated,
|
||||
DiskBasedDiagnosticsUpdating,
|
||||
FormatBuffers,
|
||||
GetCodeActions,
|
||||
GetCompletions,
|
||||
|
@ -264,11 +262,13 @@ entity_messages!(
|
|||
RemoveProjectCollaborator,
|
||||
SaveBuffer,
|
||||
SearchProject,
|
||||
StartLanguageServer,
|
||||
UnregisterWorktree,
|
||||
UnshareProject,
|
||||
UpdateBuffer,
|
||||
UpdateBufferFile,
|
||||
UpdateDiagnosticSummary,
|
||||
UpdateLanguageServer,
|
||||
RegisterWorktree,
|
||||
UpdateWorktree,
|
||||
);
|
||||
|
|
|
@ -83,9 +83,9 @@ impl Server {
|
|||
.add_request_handler(Server::register_worktree)
|
||||
.add_message_handler(Server::unregister_worktree)
|
||||
.add_request_handler(Server::update_worktree)
|
||||
.add_message_handler(Server::start_language_server)
|
||||
.add_message_handler(Server::update_language_server)
|
||||
.add_message_handler(Server::update_diagnostic_summary)
|
||||
.add_message_handler(Server::disk_based_diagnostics_updating)
|
||||
.add_message_handler(Server::disk_based_diagnostics_updated)
|
||||
.add_request_handler(Server::forward_project_request::<proto::GetDefinition>)
|
||||
.add_request_handler(Server::forward_project_request::<proto::GetReferences>)
|
||||
.add_request_handler(Server::forward_project_request::<proto::SearchProject>)
|
||||
|
@ -386,6 +386,7 @@ impl Server {
|
|||
worktrees,
|
||||
replica_id: joined.replica_id as u32,
|
||||
collaborators,
|
||||
language_servers: joined.project.language_servers.clone(),
|
||||
};
|
||||
let connection_ids = joined.project.connection_ids();
|
||||
let contact_user_ids = joined.project.authorized_user_ids();
|
||||
|
@ -535,13 +536,19 @@ impl Server {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn disk_based_diagnostics_updating(
|
||||
self: Arc<Server>,
|
||||
request: TypedEnvelope<proto::DiskBasedDiagnosticsUpdating>,
|
||||
async fn start_language_server(
|
||||
mut self: Arc<Server>,
|
||||
request: TypedEnvelope<proto::StartLanguageServer>,
|
||||
) -> tide::Result<()> {
|
||||
let receiver_ids = self
|
||||
.state()
|
||||
.project_connection_ids(request.payload.project_id, request.sender_id)?;
|
||||
let receiver_ids = self.state_mut().start_language_server(
|
||||
request.payload.project_id,
|
||||
request.sender_id,
|
||||
request
|
||||
.payload
|
||||
.server
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("invalid language server"))?,
|
||||
)?;
|
||||
broadcast(request.sender_id, receiver_ids, |connection_id| {
|
||||
self.peer
|
||||
.forward_send(request.sender_id, connection_id, request.payload.clone())
|
||||
|
@ -549,9 +556,9 @@ impl Server {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn disk_based_diagnostics_updated(
|
||||
async fn update_language_server(
|
||||
self: Arc<Server>,
|
||||
request: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,
|
||||
request: TypedEnvelope<proto::UpdateLanguageServer>,
|
||||
) -> tide::Result<()> {
|
||||
let receiver_ids = self
|
||||
.state()
|
||||
|
|
|
@ -25,6 +25,7 @@ pub struct Project {
|
|||
pub host_user_id: UserId,
|
||||
pub share: Option<ProjectShare>,
|
||||
pub worktrees: HashMap<u64, Worktree>,
|
||||
pub language_servers: Vec<proto::LanguageServer>,
|
||||
}
|
||||
|
||||
pub struct Worktree {
|
||||
|
@ -240,6 +241,7 @@ impl Store {
|
|||
host_user_id,
|
||||
share: None,
|
||||
worktrees: Default::default(),
|
||||
language_servers: Default::default(),
|
||||
},
|
||||
);
|
||||
self.next_project_id += 1;
|
||||
|
@ -438,6 +440,24 @@ impl Store {
|
|||
Err(anyhow!("no such worktree"))?
|
||||
}
|
||||
|
||||
pub fn start_language_server(
|
||||
&mut self,
|
||||
project_id: u64,
|
||||
connection_id: ConnectionId,
|
||||
language_server: proto::LanguageServer,
|
||||
) -> tide::Result<Vec<ConnectionId>> {
|
||||
let project = self
|
||||
.projects
|
||||
.get_mut(&project_id)
|
||||
.ok_or_else(|| anyhow!("no such project"))?;
|
||||
if project.host_connection_id == connection_id {
|
||||
project.language_servers.push(language_server);
|
||||
return Ok(project.connection_ids());
|
||||
}
|
||||
|
||||
Err(anyhow!("no such project"))?
|
||||
}
|
||||
|
||||
pub fn join_project(
|
||||
&mut self,
|
||||
connection_id: ConnectionId,
|
||||
|
|
|
@ -24,6 +24,7 @@ futures = "0.3"
|
|||
log = "0.4"
|
||||
parking_lot = "0.11.1"
|
||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
|
||||
[dev-dependencies]
|
||||
client = { path = "../client", features = ["test-support"] }
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
use crate::{ItemViewHandle, Settings, StatusItemView};
|
||||
use futures::StreamExt;
|
||||
use gpui::AppContext;
|
||||
use gpui::{
|
||||
action, elements::*, platform::CursorStyle, Entity, MutableAppContext, RenderContext, View,
|
||||
ViewContext,
|
||||
action, elements::*, platform::CursorStyle, Entity, ModelHandle, MutableAppContext,
|
||||
RenderContext, View, ViewContext,
|
||||
};
|
||||
use language::{LanguageRegistry, LanguageServerBinaryStatus};
|
||||
use postage::watch;
|
||||
use project::{LanguageServerProgress, Project};
|
||||
use smallvec::SmallVec;
|
||||
use std::cmp::Reverse;
|
||||
use std::fmt::Write;
|
||||
use std::sync::Arc;
|
||||
|
||||
action!(DismissErrorMessage);
|
||||
|
@ -15,6 +20,7 @@ pub struct LspStatus {
|
|||
checking_for_update: Vec<String>,
|
||||
downloading: Vec<String>,
|
||||
failed: Vec<String>,
|
||||
project: ModelHandle<Project>,
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
|
@ -23,6 +29,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
|||
|
||||
impl LspStatus {
|
||||
pub fn new(
|
||||
project: &ModelHandle<Project>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
settings_rx: watch::Receiver<Settings>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
|
@ -62,11 +69,14 @@ impl LspStatus {
|
|||
}
|
||||
})
|
||||
.detach();
|
||||
cx.observe(project, |_, _, cx| cx.notify()).detach();
|
||||
|
||||
Self {
|
||||
settings_rx,
|
||||
checking_for_update: Default::default(),
|
||||
downloading: Default::default(),
|
||||
failed: Default::default(),
|
||||
project: project.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,6 +84,30 @@ impl LspStatus {
|
|||
self.failed.clear();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn pending_language_server_work<'a>(
|
||||
&self,
|
||||
cx: &'a AppContext,
|
||||
) -> impl Iterator<Item = (&'a str, &'a str, &'a LanguageServerProgress)> {
|
||||
self.project
|
||||
.read(cx)
|
||||
.language_server_statuses()
|
||||
.rev()
|
||||
.filter_map(|status| {
|
||||
if status.pending_work.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let mut pending_work = status
|
||||
.pending_work
|
||||
.iter()
|
||||
.map(|(token, progress)| (status.name.as_str(), token.as_str(), progress))
|
||||
.collect::<SmallVec<[_; 4]>>();
|
||||
pending_work.sort_by_key(|(_, _, progress)| Reverse(progress.last_update_at));
|
||||
Some(pending_work)
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for LspStatus {
|
||||
|
@ -87,7 +121,29 @@ impl View for LspStatus {
|
|||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
let theme = &self.settings_rx.borrow().theme;
|
||||
if !self.downloading.is_empty() {
|
||||
|
||||
let mut pending_work = self.pending_language_server_work(cx);
|
||||
if let Some((lang_server_name, progress_token, progress)) = pending_work.next() {
|
||||
let mut message = lang_server_name.to_string();
|
||||
|
||||
message.push_str(": ");
|
||||
if let Some(progress_message) = progress.message.as_ref() {
|
||||
message.push_str(progress_message);
|
||||
} else {
|
||||
message.push_str(progress_token);
|
||||
}
|
||||
|
||||
if let Some(percentage) = progress.percentage {
|
||||
write!(&mut message, " ({}%)", percentage).unwrap();
|
||||
}
|
||||
|
||||
let additional_work_count = pending_work.count();
|
||||
if additional_work_count > 0 {
|
||||
write!(&mut message, " + {} more", additional_work_count).unwrap();
|
||||
}
|
||||
|
||||
Label::new(message, theme.workspace.status_bar.lsp_message.clone()).boxed()
|
||||
} else if !self.downloading.is_empty() {
|
||||
Label::new(
|
||||
format!(
|
||||
"Downloading {} language server{}...",
|
||||
|
@ -112,6 +168,7 @@ impl View for LspStatus {
|
|||
)
|
||||
.boxed()
|
||||
} else if !self.failed.is_empty() {
|
||||
drop(pending_work);
|
||||
MouseEventHandler::new::<Self, _, _>(0, cx, |_, _| {
|
||||
Label::new(
|
||||
format!(
|
||||
|
|
|
@ -101,6 +101,7 @@ pub fn build_workspace(
|
|||
});
|
||||
let lsp_status = cx.add_view(|cx| {
|
||||
workspace::lsp_status::LspStatus::new(
|
||||
workspace.project(),
|
||||
app_state.languages.clone(),
|
||||
app_state.settings.clone(),
|
||||
cx,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue