Display language server name in status bar

This commit is contained in:
Antonio Scandurra 2022-03-10 16:45:13 +01:00
parent 4243f0c339
commit 45fb470f4d
7 changed files with 228 additions and 86 deletions

View File

@ -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
}

View File

@ -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::{
@ -51,7 +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>>>>,
pending_language_server_work: HashMap<(usize, String), LspWorkProgress>,
pending_language_server_work: BTreeMap<(usize, String), LanguageServerProgress>,
language_server_names: HashMap<usize, String>,
next_language_server_id: usize,
client: Arc<client::Client>,
user_store: ModelHandle<UserStore>,
@ -116,13 +117,13 @@ pub enum Event {
DiagnosticsUpdated(ProjectPath),
}
enum LspEvent {
enum LanguageServerEvent {
WorkStart {
token: String,
},
WorkProgress {
token: String,
progress: LspWorkProgress,
progress: LanguageServerProgress,
},
WorkEnd {
token: String,
@ -131,7 +132,7 @@ enum LspEvent {
}
#[derive(Clone, Default)]
pub struct LspWorkProgress {
pub struct LanguageServerProgress {
pub message: Option<String>,
pub percentage: Option<usize>,
}
@ -224,7 +225,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_lsp_event);
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);
@ -325,6 +327,7 @@ impl Project {
language_servers: Default::default(),
started_language_servers: Default::default(),
pending_language_server_work: Default::default(),
language_server_names: Default::default(),
next_language_server_id: 0,
nonce: StdRng::from_entropy().gen(),
}
@ -396,6 +399,11 @@ impl Project {
language_servers: Default::default(),
started_language_servers: Default::default(),
pending_language_server_work: Default::default(),
language_server_names: response
.language_servers
.into_iter()
.map(|s| (s.id as usize, s.name))
.collect(),
next_language_server_id: 0,
opened_buffers: Default::default(),
buffer_snapshots: Default::default(),
@ -1193,14 +1201,15 @@ impl Project {
cx.spawn_weak(|this, mut cx| async move {
let mut language_server = language_server?.await.log_err()?;
let this = this.upgrade(&cx)?;
let (lsp_events_tx, lsp_events_rx) = smol::channel::unbounded();
let (language_server_events_tx, language_server_events_rx) =
smol::channel::unbounded();
language_server
.on_notification::<lsp::notification::PublishDiagnostics, _>({
let lsp_events_tx = lsp_events_tx.clone();
let language_server_events_tx = language_server_events_tx.clone();
move |params| {
lsp_events_tx
.try_send(LspEvent::DiagnosticsUpdate(params))
language_server_events_tx
.try_send(LanguageServerEvent::DiagnosticsUpdate(params))
.ok();
}
})
@ -1219,13 +1228,15 @@ impl Project {
match params.value {
lsp::ProgressParamsValue::WorkDone(progress) => match progress {
lsp::WorkDoneProgress::Begin(_) => {
lsp_events_tx.try_send(LspEvent::WorkStart { token }).ok();
language_server_events_tx
.try_send(LanguageServerEvent::WorkStart { token })
.ok();
}
lsp::WorkDoneProgress::Report(report) => {
lsp_events_tx
.try_send(LspEvent::WorkProgress {
language_server_events_tx
.try_send(LanguageServerEvent::WorkProgress {
token,
progress: LspWorkProgress {
progress: LanguageServerProgress {
message: report.message,
percentage: report
.percentage
@ -1235,7 +1246,9 @@ impl Project {
.ok();
}
lsp::WorkDoneProgress::End(_) => {
lsp_events_tx.try_send(LspEvent::WorkEnd { token }).ok();
language_server_events_tx
.try_send(LanguageServerEvent::WorkEnd { token })
.ok();
}
},
}
@ -1246,10 +1259,10 @@ impl Project {
cx.spawn(|mut cx| {
let this = this.downgrade();
async move {
while let Ok(event) = lsp_events_rx.recv().await {
while let Ok(event) = language_server_events_rx.recv().await {
let this = this.upgrade(&cx)?;
this.update(&mut cx, |this, cx| {
this.on_local_lsp_event(server_id, event, &language, cx)
this.on_lsp_event(server_id, event, &language, cx)
});
}
Some(())
@ -1261,6 +1274,20 @@ impl Project {
this.update(&mut cx, |this, cx| {
this.language_servers
.insert(key.clone(), language_server.clone());
this.language_server_names
.insert(server_id, language_server.name().to_string());
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() {
@ -1315,6 +1342,7 @@ impl Project {
}
}
cx.notify();
Some(())
});
@ -1323,33 +1351,35 @@ impl Project {
});
}
fn on_local_lsp_event(
fn on_lsp_event(
&mut self,
language_server_id: usize,
event: LspEvent,
event: LanguageServerEvent,
language: &Arc<Language>,
cx: &mut ModelContext<Self>,
) {
let disk_diagnostics_token = language.disk_based_diagnostics_progress_token();
match event {
LspEvent::WorkStart { token } => {
LanguageServerEvent::WorkStart { token } => {
if Some(&token) == disk_diagnostics_token {
self.disk_based_diagnostics_started(cx);
self.broadcast_lsp_event(
self.broadcast_language_server_update(
language_server_id,
proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating(
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(
proto::LspDiskBasedDiagnosticsUpdating {},
),
);
} else {
self.on_lsp_work_start(language_server_id, token.clone(), cx);
self.broadcast_lsp_event(
self.broadcast_language_server_update(
language_server_id,
proto::lsp_event::Variant::WorkStart(proto::LspWorkStart { token }),
proto::update_language_server::Variant::WorkStart(proto::LspWorkStart {
token,
}),
);
}
}
LspEvent::WorkProgress { token, progress } => {
LanguageServerEvent::WorkProgress { token, progress } => {
if Some(&token) != disk_diagnostics_token {
self.on_lsp_work_progress(
language_server_id,
@ -1357,41 +1387,45 @@ impl Project {
progress.clone(),
cx,
);
self.broadcast_lsp_event(
self.broadcast_language_server_update(
language_server_id,
proto::lsp_event::Variant::WorkProgress(proto::LspWorkProgress {
token,
message: progress.message,
percentage: progress.percentage.map(|p| p as u32),
}),
proto::update_language_server::Variant::WorkProgress(
proto::LspWorkProgress {
token,
message: progress.message,
percentage: progress.percentage.map(|p| p as u32),
},
),
);
}
}
LspEvent::WorkEnd { token } => {
LanguageServerEvent::WorkEnd { token } => {
if Some(&token) == disk_diagnostics_token {
self.disk_based_diagnostics_finished(cx);
self.broadcast_lsp_event(
self.broadcast_language_server_update(
language_server_id,
proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated(
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
proto::LspDiskBasedDiagnosticsUpdated {},
),
);
} else {
self.on_lsp_work_end(language_server_id, token.clone(), cx);
self.broadcast_lsp_event(
self.broadcast_language_server_update(
language_server_id,
proto::lsp_event::Variant::WorkEnd(proto::LspWorkEnd { token }),
proto::update_language_server::Variant::WorkEnd(proto::LspWorkEnd {
token,
}),
);
}
}
LspEvent::DiagnosticsUpdate(mut params) => {
LanguageServerEvent::DiagnosticsUpdate(mut params) => {
language.process_diagnostics(&mut params);
if disk_diagnostics_token.is_none() {
self.disk_based_diagnostics_started(cx);
self.broadcast_lsp_event(
self.broadcast_language_server_update(
language_server_id,
proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating(
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(
proto::LspDiskBasedDiagnosticsUpdating {},
),
);
@ -1406,9 +1440,9 @@ impl Project {
.log_err();
if disk_diagnostics_token.is_none() {
self.disk_based_diagnostics_finished(cx);
self.broadcast_lsp_event(
self.broadcast_language_server_update(
language_server_id,
proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated(
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
proto::LspDiskBasedDiagnosticsUpdated {},
),
);
@ -1425,7 +1459,7 @@ impl Project {
) {
self.pending_language_server_work.insert(
(language_server_id, token),
LspWorkProgress {
LanguageServerProgress {
message: None,
percentage: None,
},
@ -1437,7 +1471,7 @@ impl Project {
&mut self,
language_server_id: usize,
token: String,
progress: LspWorkProgress,
progress: LanguageServerProgress,
cx: &mut ModelContext<Self>,
) {
self.pending_language_server_work
@ -1456,10 +1490,14 @@ impl Project {
cx.notify();
}
fn broadcast_lsp_event(&self, language_server_id: usize, event: proto::lsp_event::Variant) {
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::LspEvent {
.send(proto::UpdateLanguageServer {
project_id,
language_server_id: language_server_id as u64,
variant: Some(event),
@ -1468,10 +1506,15 @@ impl Project {
}
}
pub fn pending_language_server_work(&self) -> impl Iterator<Item = (&str, &LspWorkProgress)> {
self.pending_language_server_work
.iter()
.map(|((_, token), progress)| (token.as_str(), progress))
pub fn pending_language_server_work(
&self,
) -> impl Iterator<Item = (&str, &str, &LanguageServerProgress)> {
self.pending_language_server_work.iter().filter_map(
|((language_server_id, token), progress)| {
let name = self.language_server_names.get(language_server_id)?;
Some((name.as_str(), token.as_str(), progress))
},
)
}
pub fn update_diagnostics(
@ -3212,9 +3255,27 @@ impl Project {
})
}
async fn handle_lsp_event(
async fn handle_start_language_server(
this: ModelHandle<Self>,
envelope: TypedEnvelope<proto::LspEvent>,
envelope: TypedEnvelope<proto::StartLanguageServer>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
let server = envelope
.payload
.server
.ok_or_else(|| anyhow!("invalid server"))?;
this.update(&mut cx, |this, cx| {
this.language_server_names
.insert(server.id as usize, server.name);
cx.notify();
});
Ok(())
}
async fn handle_update_language_server(
this: ModelHandle<Self>,
envelope: TypedEnvelope<proto::UpdateLanguageServer>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
@ -3224,29 +3285,35 @@ impl Project {
.variant
.ok_or_else(|| anyhow!("invalid variant"))?
{
proto::lsp_event::Variant::WorkStart(payload) => this.update(&mut cx, |this, cx| {
this.on_lsp_work_start(language_server_id, payload.token, cx);
}),
proto::lsp_event::Variant::WorkProgress(payload) => this.update(&mut cx, |this, cx| {
this.on_lsp_work_progress(
language_server_id,
payload.token,
LspWorkProgress {
message: payload.message,
percentage: payload.percentage.map(|p| p as usize),
},
cx,
);
}),
proto::lsp_event::Variant::WorkEnd(payload) => this.update(&mut cx, |this, cx| {
this.on_lsp_work_end(language_server_id, payload.token, cx);
}),
proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating(_) => {
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),
},
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::lsp_event::Variant::DiskBasedDiagnosticsUpdated(_) => {
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(_) => {
this.update(&mut cx, |this, cx| this.disk_based_diagnostics_finished(cx));
}
}

View File

@ -37,7 +37,8 @@ message Envelope {
UnregisterWorktree unregister_worktree = 29;
UpdateWorktree update_worktree = 31;
UpdateDiagnosticSummary update_diagnostic_summary = 32;
LspEvent lsp_event = 33;
StartLanguageServer start_language_server = 33;
UpdateLanguageServer update_language_server = 34;
OpenBuffer open_buffer = 35;
OpenBufferResponse open_buffer_response = 36;
@ -121,6 +122,7 @@ message JoinProjectResponse {
uint32 replica_id = 1;
repeated Worktree worktrees = 2;
repeated Collaborator collaborators = 3;
repeated LanguageServer language_servers = 4;
}
message LeaveProject {
@ -409,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;
@ -423,7 +435,7 @@ message DiagnosticSummary {
uint32 hint_count = 5;
}
message LspEvent {
message UpdateLanguageServer {
uint64 project_id = 1;
uint64 language_server_id = 2;
oneof variant {

View File

@ -171,9 +171,10 @@ messages!(
(JoinChannelResponse, Foreground),
(JoinProject, Foreground),
(JoinProjectResponse, Foreground),
(StartLanguageServer, Foreground),
(UpdateLanguageServer, Foreground),
(LeaveChannel, Foreground),
(LeaveProject, Foreground),
(LspEvent, Background),
(OpenBuffer, Background),
(OpenBufferForSymbol, Background),
(OpenBufferForSymbolResponse, Background),
@ -254,7 +255,6 @@ entity_messages!(
GetProjectSymbols,
JoinProject,
LeaveProject,
LspEvent,
OpenBuffer,
OpenBufferForSymbol,
PerformRename,
@ -262,11 +262,13 @@ entity_messages!(
RemoveProjectCollaborator,
SaveBuffer,
SearchProject,
StartLanguageServer,
UnregisterWorktree,
UnshareProject,
UpdateBuffer,
UpdateBufferFile,
UpdateDiagnosticSummary,
UpdateLanguageServer,
RegisterWorktree,
UpdateWorktree,
);

View File

@ -83,8 +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::lsp_event)
.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>)
@ -385,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();
@ -534,9 +536,29 @@ impl Server {
Ok(())
}
async fn lsp_event(
async fn start_language_server(
mut self: Arc<Server>,
request: TypedEnvelope<proto::StartLanguageServer>,
) -> tide::Result<()> {
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())
})?;
Ok(())
}
async fn update_language_server(
self: Arc<Server>,
request: TypedEnvelope<proto::LspEvent>,
request: TypedEnvelope<proto::UpdateLanguageServer>,
) -> tide::Result<()> {
let receiver_ids = self
.state()

View File

@ -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,

View File

@ -96,11 +96,16 @@ impl View for LspStatus {
let theme = &self.settings_rx.borrow().theme;
let mut pending_work = self.project.read(cx).pending_language_server_work();
if let Some((progress_token, progress)) = pending_work.next() {
let mut message = progress
.message
.clone()
.unwrap_or_else(|| progress_token.to_string());
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();
}