Skip to main content

fml_lsp/
stdio.rs

1//! The base protocol over standard input and output: `Content-Length` headers and JSON bodies.
2
3use std::io::{self, BufRead, Write};
4
5use serde_json::Value;
6
7use crate::Server;
8
9/// Runs `server` over standard input and output until the client sends `exit`, and returns the
10/// exit code.
11pub fn run_stdio(server: Server) -> io::Result<i32> {
12    let stdin = io::stdin();
13    let stdout = io::stdout();
14    run(server, &mut stdin.lock(), &mut stdout.lock())
15}
16
17/// Runs `server` over any reader and writer.
18pub fn run(
19    mut server: Server,
20    input: &mut impl BufRead,
21    output: &mut impl Write,
22) -> io::Result<i32> {
23    while let Some(body) = read_message(input)? {
24        let Ok(message) = serde_json::from_slice::<Value>(&body) else {
25            continue;
26        };
27        for reply in server.handle(&message) {
28            write_message(output, &reply)?;
29        }
30        if server.exited() {
31            return Ok(server.exit_code());
32        }
33    }
34    // The client went away without `exit`.
35    Ok(1)
36}
37
38fn read_message(input: &mut impl BufRead) -> io::Result<Option<Vec<u8>>> {
39    let mut length = None;
40    let mut line = String::new();
41    loop {
42        line.clear();
43        if input.read_line(&mut line)? == 0 {
44            return Ok(None);
45        }
46        let header = line.trim_end();
47        if header.is_empty() {
48            if length.is_some() {
49                break;
50            }
51            continue;
52        }
53        if let Some((name, value)) = header.split_once(':')
54            && name.eq_ignore_ascii_case("content-length")
55        {
56            length = value.trim().parse::<usize>().ok();
57        }
58    }
59    let mut body = vec![0; length.unwrap_or(0)];
60    input.read_exact(&mut body)?;
61    Ok(Some(body))
62}
63
64fn write_message(output: &mut impl Write, message: &Value) -> io::Result<()> {
65    let body = serde_json::to_vec(message)?;
66    write!(output, "Content-Length: {}\r\n\r\n", body.len())?;
67    output.write_all(&body)?;
68    output.flush()
69}