Add NotificationDispatcher

This commit is contained in:
Aleksey Kladov 2020-06-25 17:50:47 +02:00
parent 22098127c4
commit f5ea35a271
3 changed files with 85 additions and 81 deletions

View File

@ -1,3 +1,4 @@
//! A visitor for downcasting arbitrary request (JSON) into a specific type.
use std::{panic, time::Instant}; use std::{panic, time::Instant};
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
@ -135,3 +136,41 @@ where
}; };
Task::Respond(response) Task::Respond(response)
} }
pub(crate) struct NotificationDispatcher<'a> {
pub(crate) not: Option<lsp_server::Notification>,
pub(crate) global_state: &'a mut GlobalState,
}
impl<'a> NotificationDispatcher<'a> {
pub(crate) fn on<N>(
&mut self,
f: fn(&mut GlobalState, N::Params) -> Result<()>,
) -> Result<&mut Self>
where
N: lsp_types::notification::Notification + 'static,
N::Params: DeserializeOwned + Send + 'static,
{
let not = match self.not.take() {
Some(it) => it,
None => return Ok(self),
};
let params = match not.extract::<N::Params>(N::METHOD) {
Ok(it) => it,
Err(not) => {
self.not = Some(not);
return Ok(self);
}
};
f(self.global_state, params)?;
Ok(self)
}
pub(crate) fn finish(&mut self) {
if let Some(not) = &self.not {
if !not.method.starts_with("$/") {
log::error!("unhandled notification: {:?}", not);
}
}
}
}

View File

@ -1,12 +1,13 @@
//! Utilities for LSP-related boilerplate code. //! Utilities for LSP-related boilerplate code.
use std::{error::Error, ops::Range}; use std::{error::Error, ops::Range};
use crate::from_proto;
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
use lsp_server::{Message, Notification}; use lsp_server::{Message, Notification};
use ra_db::Canceled; use ra_db::Canceled;
use ra_ide::LineIndex; use ra_ide::LineIndex;
use serde::{de::DeserializeOwned, Serialize}; use serde::Serialize;
use crate::from_proto;
pub fn show_message( pub fn show_message(
typ: lsp_types::MessageType, typ: lsp_types::MessageType,
@ -29,14 +30,6 @@ pub(crate) fn notification_is<N: lsp_types::notification::Notification>(
notification.method == N::METHOD notification.method == N::METHOD
} }
pub(crate) fn notification_cast<N>(notification: Notification) -> Result<N::Params, Notification>
where
N: lsp_types::notification::Notification,
N::Params: DeserializeOwned,
{
notification.extract(N::METHOD)
}
pub(crate) fn notification_new<N>(params: N::Params) -> Notification pub(crate) fn notification_new<N>(params: N::Params) -> Notification
where where
N: lsp_types::notification::Notification, N: lsp_types::notification::Notification,

View File

@ -6,8 +6,8 @@ use std::{
}; };
use crossbeam_channel::{never, select, Receiver}; use crossbeam_channel::{never, select, Receiver};
use lsp_server::{Connection, Notification, Request, RequestId, Response}; use lsp_server::{Connection, Notification, Request, Response};
use lsp_types::{notification::Notification as _, request::Request as _, NumberOrString}; use lsp_types::{notification::Notification as _, request::Request as _};
use ra_db::VfsPath; use ra_db::VfsPath;
use ra_ide::{Canceled, FileId}; use ra_ide::{Canceled, FileId};
use ra_prof::profile; use ra_prof::profile;
@ -16,13 +16,12 @@ use ra_project_model::{PackageRoot, ProjectWorkspace};
use crate::{ use crate::{
config::{Config, FilesWatcher, LinkedProject}, config::{Config, FilesWatcher, LinkedProject},
diagnostics::DiagnosticTask, diagnostics::DiagnosticTask,
dispatch::RequestDispatcher, dispatch::{NotificationDispatcher, RequestDispatcher},
from_proto, from_proto,
global_state::{file_id_to_url, GlobalState, Status}, global_state::{file_id_to_url, GlobalState, Status},
handlers, lsp_ext, handlers, lsp_ext,
lsp_utils::{ lsp_utils::{
apply_document_changes, is_canceled, notification_cast, notification_is, notification_new, apply_document_changes, is_canceled, notification_is, notification_new, show_message,
show_message,
}, },
request_metrics::RequestMetrics, request_metrics::RequestMetrics,
Result, Result,
@ -240,9 +239,7 @@ impl GlobalState {
} }
fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()> { fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()> {
let mut pool_dispatcher = RequestDispatcher { req: Some(req), global_state: self, request_received }
RequestDispatcher { req: Some(req), global_state: self, request_received };
pool_dispatcher
.on_sync::<lsp_ext::CollectGarbage>(|s, ()| Ok(s.collect_garbage()))? .on_sync::<lsp_ext::CollectGarbage>(|s, ()| Ok(s.collect_garbage()))?
.on_sync::<lsp_ext::JoinLines>(|s, p| handlers::handle_join_lines(s.snapshot(), p))? .on_sync::<lsp_ext::JoinLines>(|s, p| handlers::handle_join_lines(s.snapshot(), p))?
.on_sync::<lsp_ext::OnEnter>(|s, p| handlers::handle_on_enter(s.snapshot(), p))? .on_sync::<lsp_ext::OnEnter>(|s, p| handlers::handle_on_enter(s.snapshot(), p))?
@ -298,56 +295,47 @@ impl GlobalState {
Ok(()) Ok(())
} }
fn on_notification(&mut self, not: Notification) -> Result<()> { fn on_notification(&mut self, not: Notification) -> Result<()> {
let not = match notification_cast::<lsp_types::notification::Cancel>(not) { NotificationDispatcher { not: Some(not), global_state: self }
Ok(params) => { .on::<lsp_types::notification::Cancel>(|this, params| {
let id: RequestId = match params.id { let id: lsp_server::RequestId = match params.id {
NumberOrString::Number(id) => id.into(), lsp_types::NumberOrString::Number(id) => id.into(),
NumberOrString::String(id) => id.into(), lsp_types::NumberOrString::String(id) => id.into(),
}; };
if let Some(response) = self.req_queue.incoming.cancel(id) { if let Some(response) = this.req_queue.incoming.cancel(id) {
self.send(response.into()) this.send(response.into());
} }
return Ok(()); Ok(())
} })?
Err(not) => not, .on::<lsp_types::notification::DidOpenTextDocument>(|this, params| {
};
let not = match notification_cast::<lsp_types::notification::DidOpenTextDocument>(not) {
Ok(params) => {
if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) { if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
if !self.mem_docs.insert(path.clone()) { if !this.mem_docs.insert(path.clone()) {
log::error!("duplicate DidOpenTextDocument: {}", path) log::error!("duplicate DidOpenTextDocument: {}", path)
} }
self.vfs this.vfs
.write() .write()
.0 .0
.set_file_contents(path, Some(params.text_document.text.into_bytes())); .set_file_contents(path, Some(params.text_document.text.into_bytes()));
} }
return Ok(()); Ok(())
} })?
Err(not) => not, .on::<lsp_types::notification::DidChangeTextDocument>(|this, params| {
};
let not = match notification_cast::<lsp_types::notification::DidChangeTextDocument>(not) {
Ok(params) => {
if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) { if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
assert!(self.mem_docs.contains(&path)); assert!(this.mem_docs.contains(&path));
let vfs = &mut self.vfs.write().0; let vfs = &mut this.vfs.write().0;
let file_id = vfs.file_id(&path).unwrap(); let file_id = vfs.file_id(&path).unwrap();
let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap(); let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap();
apply_document_changes(&mut text, params.content_changes); apply_document_changes(&mut text, params.content_changes);
vfs.set_file_contents(path, Some(text.into_bytes())) vfs.set_file_contents(path, Some(text.into_bytes()))
} }
return Ok(()); Ok(())
} })?
Err(not) => not, .on::<lsp_types::notification::DidCloseTextDocument>(|this, params| {
};
let not = match notification_cast::<lsp_types::notification::DidCloseTextDocument>(not) {
Ok(params) => {
if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) { if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
if !self.mem_docs.remove(&path) { if !this.mem_docs.remove(&path) {
log::error!("orphan DidCloseTextDocument: {}", path) log::error!("orphan DidCloseTextDocument: {}", path)
} }
if let Some(path) = path.as_path() { if let Some(path) = path.as_path() {
self.loader.invalidate(path.to_path_buf()); this.loader.invalidate(path.to_path_buf());
} }
} }
let params = lsp_types::PublishDiagnosticsParams { let params = lsp_types::PublishDiagnosticsParams {
@ -356,25 +344,19 @@ impl GlobalState {
version: None, version: None,
}; };
let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params); let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params);
self.send(not.into()); this.send(not.into());
return Ok(()); Ok(())
} })?
Err(not) => not, .on::<lsp_types::notification::DidSaveTextDocument>(|this, _params| {
}; if let Some(flycheck) = &this.flycheck {
let not = match notification_cast::<lsp_types::notification::DidSaveTextDocument>(not) {
Ok(_params) => {
if let Some(flycheck) = &self.flycheck {
flycheck.0.update(); flycheck.0.update();
} }
return Ok(()); Ok(())
} })?
Err(not) => not, .on::<lsp_types::notification::DidChangeConfiguration>(|this, _params| {
};
let not = match notification_cast::<lsp_types::notification::DidChangeConfiguration>(not) {
Ok(_) => {
// As stated in https://github.com/microsoft/language-server-protocol/issues/676, // As stated in https://github.com/microsoft/language-server-protocol/issues/676,
// this notification's parameters should be ignored and the actual config queried separately. // this notification's parameters should be ignored and the actual config queried separately.
let request = self.req_queue.outgoing.register( let request = this.req_queue.outgoing.register(
lsp_types::request::WorkspaceConfiguration::METHOD.to_string(), lsp_types::request::WorkspaceConfiguration::METHOD.to_string(),
lsp_types::ConfigurationParams { lsp_types::ConfigurationParams {
items: vec![lsp_types::ConfigurationItem { items: vec![lsp_types::ConfigurationItem {
@ -403,30 +385,21 @@ impl GlobalState {
} }
}, },
); );
self.send(request.into()); this.send(request.into());
return Ok(()); return Ok(());
} })?
Err(not) => not, .on::<lsp_types::notification::DidChangeWatchedFiles>(|this, params| {
};
let not = match notification_cast::<lsp_types::notification::DidChangeWatchedFiles>(not) {
Ok(params) => {
for change in params.changes { for change in params.changes {
if let Ok(path) = from_proto::abs_path(&change.uri) { if let Ok(path) = from_proto::abs_path(&change.uri) {
self.loader.invalidate(path) this.loader.invalidate(path);
} }
} }
return Ok(()); Ok(())
} })?
Err(not) => not, .finish();
};
if not.method.starts_with("$/") {
return Ok(());
}
log::error!("unhandled notification: {:?}", not);
Ok(()) Ok(())
} }
// TODO
pub(crate) fn on_task(&mut self, task: Task) { pub(crate) fn on_task(&mut self, task: Task) {
match task { match task {
Task::Respond(response) => { Task::Respond(response) => {
@ -481,7 +454,6 @@ impl GlobalState {
} }
} }
// TODO
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum Task { pub(crate) enum Task {
Respond(Response), Respond(Response),