lsp-cli 0.1.6

Command-line tool for talking to Language Server Protocol (LSP) servers from the terminal.
use super::TestDir;
use crate::lsp::LspClient;
use crate::lsp::transport::{read_message, write_message};
use serde_json::{Value, json};
use std::collections::BTreeSet;
use std::io::BufReader;
use std::os::unix::net::{UnixListener, UnixStream};
use std::thread::{self, JoinHandle};
use std::time::Duration;

pub(crate) struct LspPeer {
    reader: BufReader<UnixStream>,
    writer: UnixStream,
    opened: BTreeSet<String>,
}

impl LspPeer {
    pub(crate) fn spawn(
        dir: &TestDir,
        timeout: Duration,
        serve: impl FnOnce(&mut Self) + Send + 'static,
    ) -> (LspClient, JoinHandle<()>) {
        let path = dir.path().join("peer.sock");
        let listener = UnixListener::bind(&path).expect("bind fake server");
        let server = thread::spawn(move || {
            let (writer, _) = listener.accept().expect("accept client");
            writer
                .set_read_timeout(Some(Duration::from_secs(5)))
                .expect("set timeout");
            writer
                .set_write_timeout(Some(Duration::from_secs(5)))
                .expect("set timeout");
            let reader = BufReader::new(writer.try_clone().expect("clone socket"));
            serve(&mut Self {
                reader,
                writer,
                opened: BTreeSet::new(),
            });
        });
        (
            LspClient::connect_unix(&path, false, timeout).expect("connect client"),
            server,
        )
    }

    pub(crate) fn read(&mut self) -> Value {
        read_message(&mut self.reader)
            .expect("read message")
            .expect("message exists")
    }

    pub(crate) fn send(&mut self, message: &Value) {
        write_message(&mut self.writer, message).expect("send message");
    }

    pub(crate) fn reply(&mut self, request: &Value, result: Value) {
        let mut response = json!({"jsonrpc":"2.0", "id":request["id"]});
        response["result"] = result;
        self.send(&response);
    }

    pub(crate) fn document_request(&mut self) -> Value {
        loop {
            let message = self.read();
            match message["method"].as_str() {
                Some("textDocument/didOpen") => {
                    self.opened.insert(
                        message["params"]["textDocument"]["uri"]
                            .as_str()
                            .expect("uri")
                            .into(),
                    );
                }
                Some("textDocument/documentSymbol") => {
                    assert!(
                        self.opened.contains(
                            message["params"]["textDocument"]["uri"]
                                .as_str()
                                .expect("uri")
                        )
                    );
                    return message;
                }
                _ => panic!("unexpected message: {message}"),
            }
        }
    }

    pub(crate) fn finish(&mut self) {
        let shutdown = self.read();
        assert_eq!(shutdown["method"], "shutdown");
        self.reply(&shutdown, Value::Null);
        assert_eq!(self.read()["method"], "exit");
    }
}