Skip to main content

lsp_max/client/
builder.rs

1use crate::client::server_handle::ServerHandle;
2use crate::client::LanguageClient;
3use serde_json::Value;
4use std::collections::HashMap;
5use std::sync::atomic::AtomicU64;
6use std::sync::Arc;
7use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader};
8use tokio::sync::{mpsc, oneshot, Mutex};
9
10/// Builder for constructing a client connection to a Language Server.
11pub struct ClientBuilder {
12    // Configuration options could go here
13}
14
15impl ClientBuilder {
16    /// Create a new ClientBuilder.
17    pub fn new() -> Self {
18        Self {}
19    }
20
21    /// Build the client connection. Takes a type implementing `LanguageClient`
22    /// to handle inbound server messages, and the I/O streams for the transport.
23    /// `input` is the server's stdout (we read responses from it).
24    /// `output` is the server's stdin (we write requests to it).
25    /// Returns the `ServerHandle` which can be used to send outbound requests.
26    pub fn build<C, I, O>(self, _client: C, input: I, output: O) -> ServerHandle
27    where
28        C: LanguageClient + Send + 'static,
29        I: AsyncRead + Unpin + Send + 'static,
30        O: AsyncWrite + Unpin + Send + 'static,
31    {
32        let pending: Arc<Mutex<HashMap<u64, oneshot::Sender<Value>>>> =
33            Arc::new(Mutex::new(HashMap::new()));
34        let next_id = Arc::new(AtomicU64::new(1));
35        let (tx, rx) = mpsc::channel::<Value>(64);
36
37        let handle = ServerHandle::new(tx, Arc::clone(&pending), Arc::clone(&next_id));
38
39        // Spawn write loop: read from rx, serialize as LSP framing, write to output
40        tokio::spawn(write_loop(rx, output));
41
42        // Spawn read loop: read LSP-framed messages from input, dispatch responses/notifications
43        tokio::spawn(read_loop(input, Arc::clone(&pending), _client));
44
45        handle
46    }
47}
48
49impl Default for ClientBuilder {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55/// Write loop: dequeues outbound messages and writes them with LSP Content-Length framing.
56async fn write_loop<O: AsyncWrite + Unpin>(mut rx: mpsc::Receiver<Value>, mut output: O) {
57    while let Some(msg) = rx.recv().await {
58        match serde_json::to_vec(&msg) {
59            Ok(body) => {
60                let header = format!("Content-Length: {}\r\n\r\n", body.len());
61                if output.write_all(header.as_bytes()).await.is_err() {
62                    break;
63                }
64                if output.write_all(&body).await.is_err() {
65                    break;
66                }
67            }
68            Err(e) => {
69                tracing::warn!(
70                    "lsp-max-client: failed to serialize outbound message: {}",
71                    e
72                );
73            }
74        }
75    }
76}
77
78/// Read loop: reads LSP-framed messages from input, dispatches responses to pending map
79/// and notifications to the LanguageClient handler.
80async fn read_loop<I: AsyncRead + Unpin, C: LanguageClient>(
81    input: I,
82    pending: Arc<Mutex<HashMap<u64, oneshot::Sender<Value>>>>,
83    _client: C,
84) {
85    let mut reader = BufReader::new(input);
86
87    loop {
88        // Parse headers: read lines until blank line (\r\n)
89        let mut content_length: Option<usize> = None;
90        loop {
91            let mut line = String::new();
92            match reader.read_line(&mut line).await {
93                Ok(0) => return, // EOF
94                Err(e) => {
95                    tracing::debug!("lsp-max-client: read_line error: {}", e);
96                    return;
97                }
98                Ok(_) => {}
99            }
100            let trimmed = line.trim_end_matches(['\r', '\n']);
101            if trimmed.is_empty() {
102                // Blank line — end of headers
103                break;
104            }
105            if let Some(rest) = trimmed.strip_prefix("Content-Length:") {
106                match rest.trim().parse::<usize>() {
107                    Ok(n) => content_length = Some(n),
108                    Err(e) => {
109                        tracing::warn!("lsp-max-client: bad Content-Length: {}", e);
110                    }
111                }
112            }
113        }
114
115        let length = match content_length {
116            Some(n) => n,
117            None => {
118                tracing::warn!("lsp-max-client: no Content-Length header found, skipping");
119                continue;
120            }
121        };
122
123        // Read body
124        let mut body = vec![0u8; length];
125        if let Err(e) = reader.read_exact(&mut body).await {
126            tracing::debug!("lsp-max-client: read_exact error: {}", e);
127            return;
128        }
129
130        let msg: Value = match serde_json::from_slice(&body) {
131            Ok(v) => v,
132            Err(e) => {
133                tracing::warn!("lsp-max-client: failed to parse JSON body: {}", e);
134                continue;
135            }
136        };
137
138        // Dispatch: response vs notification
139        let has_id = msg.get("id").map(|v| !v.is_null()).unwrap_or(false);
140        let has_method = msg.get("method").is_some();
141
142        if has_id && !has_method {
143            // JSON-RPC response
144            if let Some(id) = msg["id"].as_u64() {
145                let result = msg.get("result").cloned().unwrap_or(Value::Null);
146                if let Some(tx) = pending.lock().await.remove(&id) {
147                    let _ = tx.send(result);
148                }
149            }
150        } else if has_method {
151            // Notification or server-to-client request
152            let method = msg["method"].as_str().unwrap_or("").to_owned();
153            tracing::debug!("lsp-max-client: notification from server: {}", method);
154            // Future: dispatch to _client trait methods based on method name
155        }
156    }
157}