ra-multiplex 0.2.6

share one language server instance between multiple LSP clients to save resources
Documentation
use std::env;

use anyhow::{bail, Context, Result};
use serde::de::{DeserializeOwned, IgnoredAny};
use tokio::io::BufReader;

use crate::config::Config;
use crate::lsp::ext::{self, LspMuxOptions, StatusResponse};
use crate::lsp::jsonrpc::{Message, Request, RequestId, Version};
use crate::lsp::transport::{LspReader, LspWriter};
use crate::lsp::{InitializationOptions, InitializeParams};
use crate::socketwrapper::Stream;

pub async fn ext_request<T>(config: &Config, method: ext::Request) -> Result<T>
where
    T: DeserializeOwned,
{
    let (reader, writer) = Stream::connect(&config.connect)
        .await
        .context("connect")?
        .into_split();
    let mut writer = LspWriter::new(writer, "lspmux");
    let mut reader = LspReader::new(BufReader::new(reader), "lspmux");

    writer
        .write_message(
            &Request {
                jsonrpc: Version,
                method: "initialize".into(),
                params: serde_json::to_value(InitializeParams {
                    initialization_options: Some(InitializationOptions {
                        lsp_mux: Some(LspMuxOptions {
                            version: LspMuxOptions::PROTOCOL_VERSION.into(),
                            method,
                        }),
                        other_options: serde_json::Map::default(),
                    }),
                    process_id: None,
                    client_info: None,
                    locale: None,
                    root_path: None,
                    root_uri: None,
                    capabilities: None,
                    trace: None,
                    workspace_folders: Vec::new(),
                })
                .unwrap(),
                id: RequestId::Number(0),
            }
            .into(),
        )
        .await
        .context("send lspmux request")?;

    match reader
        .read_message()
        .await
        .context("read lspmux response")?
        .context("stream ended")?
        .into_response()
        .context("received message was not a response")?
    {
        Ok(success) => serde_json::from_value(success.result).context("parse response result"),
        Err(error) => bail!(
            "received error response: {msg:?}",
            msg = Message::ResponseError(error),
        ),
    }
}

pub fn config(config: &Config) -> Result<()> {
    println!("{config:#?}");
    Ok(())
}

pub async fn status(config: &Config, json: bool) -> Result<()> {
    let res = ext_request::<StatusResponse>(config, ext::Request::Status {}).await?;

    if json {
        let json = serde_json::to_string(&res).unwrap();
        println!("{json}");
        return Ok(());
    }

    for instance in res.instances {
        println!("- Instance");
        println!("  pid: {}", instance.pid);
        println!("  server: {:?} {:?}", instance.server, instance.args);
        if !instance.env.is_empty() {
            println!("  server env:");
            for (key, val) in instance.env {
                println!("    {key} = {val}");
            }
        }
        println!("  path: {:?}", instance.workspace_root);
        let now = time::OffsetDateTime::now_utc().unix_timestamp();
        println!("  last used: {}s ago", now - instance.last_used);
        println!("  registered dynamic capabilities:");
        for cap in instance.registered_dyn_capabilities {
            println!("    - {}", cap);
        }
        println!("  clients:");
        for client in instance.clients {
            println!("    - Client");
            println!("      id: {}", client.id);
            println!("      files:");
            for file in client.files {
                println!("        - {}", file);
            }
        }
    }
    Ok(())
}

pub async fn reload(config: &Config) -> Result<()> {
    let cwd = env::current_dir()
        .context("unable to get current_dir")?
        .to_str()
        .context("current_dir is not valid utf-8")?
        .to_owned();
    ext_request::<IgnoredAny>(config, ext::Request::Reload { cwd }).await?;
    Ok(())
}