diff options
Diffstat (limited to 'support/texlab/tests/integration/lsp/client.rs')
-rw-r--r-- | support/texlab/tests/integration/lsp/client.rs | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/support/texlab/tests/integration/lsp/client.rs b/support/texlab/tests/integration/lsp/client.rs new file mode 100644 index 0000000000..dc6b66795c --- /dev/null +++ b/support/texlab/tests/integration/lsp/client.rs @@ -0,0 +1,158 @@ +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; + +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> { + let directory = tempdir()?; + let (client, server) = Connection::memory(); + let incoming = IncomingHandler::spawn(client.receiver)?; + let outgoing = client.sender; + let server = Server::with_connection(server, directory.path().to_path_buf(), false); + 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: None, + 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 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")) + } +} |