texlab/tests/integration/lsp/client.rs
2022-10-15 20:46:59 +02:00

171 lines
5 KiB
Rust

use std::sync::Once;
use anyhow::{bail, Result};
use crossbeam_channel::{Receiver, Sender};
use lsp_server::{Connection, Message, Notification, Request, Response};
use lsp_types::{
notification::{Exit, Initialized},
request::{Initialize, Shutdown},
ClientCapabilities, ClientInfo, DidOpenTextDocumentParams, InitializeParams, InitializeResult,
InitializedParams, Url,
};
use tempfile::{tempdir, TempDir};
use texlab::Server;
static INIT_LOGGER: Once = Once::new();
pub struct IncomingHandler {
_handle: jod_thread::JoinHandle<Result<()>>,
pub requests: Receiver<Request>,
pub notifications: Receiver<Notification>,
pub responses: Receiver<Response>,
}
impl IncomingHandler {
pub fn spawn(receiver: Receiver<Message>) -> Result<Self> {
let (req_sender, req_receiver) = crossbeam_channel::unbounded();
let (not_sender, not_receiver) = crossbeam_channel::unbounded();
let (res_sender, res_receiver) = crossbeam_channel::unbounded();
let _handle = jod_thread::spawn(move || {
for message in &receiver {
match message {
Message::Request(req) => req_sender.send(req)?,
Message::Response(res) => res_sender.send(res)?,
Message::Notification(not) => not_sender.send(not)?,
};
}
Ok(())
});
Ok(Self {
_handle,
requests: req_receiver,
notifications: not_receiver,
responses: res_receiver,
})
}
}
pub struct ClientResult {
pub directory: TempDir,
pub incoming: IncomingHandler,
}
pub struct Client {
outgoing: Sender<Message>,
incoming: IncomingHandler,
directory: TempDir,
request_id: i32,
_handle: jod_thread::JoinHandle,
}
impl Client {
pub fn spawn() -> Result<Self> {
INIT_LOGGER.call_once(|| env_logger::init());
let directory = tempdir()?;
let (client, server) = Connection::memory();
let incoming = IncomingHandler::spawn(client.receiver)?;
let outgoing = client.sender;
let server = Server::new(server, directory.path().to_path_buf());
let _handle = jod_thread::spawn(move || {
server.run().expect("server failed to run");
});
Ok(Self {
outgoing,
incoming,
directory,
request_id: 0,
_handle,
})
}
#[allow(deprecated)]
pub fn initialize(
&mut self,
client_capabilities: ClientCapabilities,
client_info: Option<ClientInfo>,
) -> Result<InitializeResult> {
let result = self.request::<Initialize>(InitializeParams {
process_id: None,
root_path: None,
root_uri: None,
initialization_options: Some(serde_json::json!({ "skipDistro": true })),
capabilities: client_capabilities,
trace: None,
workspace_folders: None,
client_info,
locale: None,
})?;
self.notify::<Initialized>(InitializedParams {})?;
Ok(result)
}
pub fn request<R: lsp_types::request::Request>(
&mut self,
params: R::Params,
) -> Result<R::Result> {
self.request_id += 1;
self.outgoing
.send(Request::new(self.request_id.into(), R::METHOD.into(), params).into())?;
let response = self.incoming.responses.recv()?;
assert_eq!(response.id, self.request_id.into());
let result = match response.result {
Some(result) => result,
None => bail!("request failed: {:?}", response.error),
};
Ok(serde_json::from_value(result)?)
}
pub fn notify<N: lsp_types::notification::Notification>(
&mut self,
params: N::Params,
) -> Result<()> {
self.outgoing
.send(Notification::new(N::METHOD.into(), serde_json::to_value(params)?).into())?;
Ok(())
}
pub fn open(&mut self, name: &str, language_id: &str, text: String) -> Result<()> {
self.notify::<lsp_types::notification::DidOpenTextDocument>(DidOpenTextDocumentParams {
text_document: lsp_types::TextDocumentItem {
uri: self.uri(name)?,
language_id: language_id.to_string(),
version: 0,
text,
},
})?;
Ok(())
}
pub fn store_on_disk(&mut self, name: &str, text: &str) -> Result<()> {
let path = self.directory.path().join(name);
std::fs::create_dir_all(path.parent().unwrap())?;
std::fs::write(path, text)?;
Ok(())
}
pub fn shutdown(mut self) -> Result<ClientResult> {
self.request::<Shutdown>(())?;
self.notify::<Exit>(())?;
Ok(ClientResult {
directory: self.directory,
incoming: self.incoming,
})
}
pub fn uri(&self, name: &str) -> Result<Url> {
Url::from_file_path(self.directory.path().join(name))
.map_err(|()| anyhow::anyhow!("failed to create uri"))
}
}