Skip to main content

lsp_max/
service.rs

1//! Service abstraction for language servers.
2
3pub use self::cancellation::{CancellationGuard, OnDrop};
4pub use self::client::{progress, Client, ClientSocket, RequestStream, ResponseSink};
5
6pub use self::pending::Pending;
7pub use self::state::{ServerState, State};
8
9use std::fmt::{self, Debug, Display, Formatter};
10use std::sync::Arc;
11use std::task::{Context, Poll};
12
13use futures::future::{self, BoxFuture, FutureExt};
14use serde_json::Value;
15use tower::Service;
16
17use crate::jsonrpc::{
18    Error, ErrorCode, FromParams, IntoResponse, Method, Request, Response, Router,
19};
20use crate::LanguageServer;
21
22pub(crate) mod layers;
23
24mod cancellation;
25mod client;
26mod pending;
27mod state;
28mod watchdog;
29
30/// Error that occurs when attempting to call the language server after it has already exited.
31///
32/// See also: `examples/transport_utilities_explained.rs` — a run-to-exit witness that
33/// demonstrates `ExitedError`, `ClientSocket`, and `Loopback` with real `assert!`s.
34#[derive(Clone, Debug, Eq, PartialEq)]
35#[repr(transparent)]
36pub struct ExitedError(pub i32);
37
38impl std::error::Error for ExitedError {}
39
40impl Display for ExitedError {
41    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
42        write!(f, "language server has exited with status: {}", self.0)
43    }
44}
45
46impl ExitedError {
47    /// Returns the exit status code.
48    pub fn code(&self) -> i32 {
49        self.0
50    }
51}
52
53/// Service abstraction for the Language Server Protocol.
54///
55/// This service takes an incoming JSON-RPC message as input and produces an outgoing message as
56/// output. If the incoming message is a server notification or a client response, then the
57/// corresponding response will be `None`.
58///
59/// This implements [`tower::Service`] in order to remain independent from the underlying transport
60/// and to facilitate further abstraction with middleware.
61///
62/// Pending requests can be canceled by issuing a [`$/cancelRequest`] notification.
63///
64/// [`$/cancelRequest`]: https://microsoft.github.io/language-server-protocol/specification#cancelRequest
65///
66/// The service shuts down and stops serving requests after the [`exit`] notification is received.
67///
68/// [`exit`]: https://microsoft.github.io/language-server-protocol/specification#exit
69#[derive(Debug)]
70pub struct LspService<S> {
71    inner: layers::CatchUnwindService<Router<S, ExitedError>>,
72    state: Arc<ServerState>,
73}
74
75impl<S: LanguageServer> LspService<S> {
76    /// Creates a new `LspService` with the given server backend, also returning a channel for
77    /// server-to-client communication.
78    pub fn new<F>(init: F) -> (Self, ClientSocket)
79    where
80        F: FnOnce(Client) -> S,
81    {
82        LspService::build(init).finish()
83    }
84
85    /// Starts building a new `LspService`.
86    ///
87    /// Returns an `LspServiceBuilder`, which allows adding custom JSON-RPC methods to the server.
88    pub fn build<F>(init: F) -> LspServiceBuilder<S>
89    where
90        F: FnOnce(Client) -> S,
91    {
92        let state = Arc::new(ServerState::new());
93
94        let (client, socket) = Client::new(state.clone());
95        let inner = Router::new(init(client.clone()));
96        let pending = Arc::new(Pending::new());
97        let doc_sync = layers::DocumentSync::new();
98
99        LspServiceBuilder {
100            inner: crate::language_server::RegisterLspMethods::register_lsp_methods(
101                inner,
102                state.clone(),
103                pending.clone(),
104                client,
105                doc_sync.clone(),
106            ),
107            state,
108            pending,
109            socket,
110            doc_sync,
111        }
112    }
113
114    /// Returns a reference to the inner server.
115    pub fn inner(&self) -> &S {
116        self.inner.inner().inner()
117    }
118}
119
120impl<S: LanguageServer> Service<Request> for LspService<S> {
121    type Response = Option<Response>;
122    type Error = ExitedError;
123    type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
124
125    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
126        // The service is ready only after initialization completes.
127        // An already-exited service propagates the exit error through inner.poll_ready.
128        let cur_state = self.state.get();
129        if cur_state == State::Exited {
130            let code = self.state.get_exit_code();
131            return Poll::Ready(Err(ExitedError(code)));
132        }
133        if self.state.poll_initializing(cx).is_pending() {
134            return Poll::Pending;
135        }
136        tracing::trace!(
137            "LspService::poll_ready delegating to inner (state={:?})",
138            cur_state
139        );
140        self.inner.poll_ready(cx)
141    }
142
143    fn call(&mut self, req: Request) -> Self::Future {
144        if self.state.get() == State::Exited {
145            let code = self.state.get_exit_code();
146            return future::err(ExitedError(code)).boxed();
147        }
148
149        let fut = self.inner.call(req);
150
151        Box::pin(async move {
152            let response = fut.await?;
153
154            match response.as_ref().and_then(|res| res.error()) {
155                Some(Error {
156                    code: ErrorCode::MethodNotFound,
157                    data: Some(Value::String(m)),
158                    ..
159                }) if m.starts_with("$/") => Ok(None),
160                _ => Ok(response),
161            }
162        })
163    }
164}
165
166/// A builder to customize the properties of an `LspService`.
167///
168/// To construct an `LspServiceBuilder`, refer to [`LspService::build`].
169pub struct LspServiceBuilder<S> {
170    inner: Router<S, ExitedError>,
171    state: Arc<ServerState>,
172    pending: Arc<Pending>,
173    socket: ClientSocket,
174    doc_sync: layers::DocumentSync,
175}
176
177impl<S: LanguageServer> LspServiceBuilder<S> {
178    /// Defines a custom JSON-RPC request or notification with the given method `name` and handler.
179    ///
180    /// # Handler varieties
181    ///
182    /// Fundamentally, any inherent `async fn(&self)` method defined directly on the language
183    /// server backend could be considered a valid method handler.
184    ///
185    /// Handlers may optionally include a single `params` argument. This argument may be of any
186    /// type that implements [`Serialize`](serde::Serialize).
187    ///
188    /// Handlers which return `()` are treated as **notifications**, while those which return
189    /// [`jsonrpc::Result<T>`](crate::jsonrpc::Result) are treated as **requests**.
190    ///
191    /// Similar to the `params` argument, the `T` in the `Result<T>` return values may be of any
192    /// type which implements [`DeserializeOwned`](serde::de::DeserializeOwned). Additionally, this
193    /// type _must_ be convertible into a [`serde_json::Value`] using [`serde_json::to_value`]. If
194    /// this latter constraint is not met, the client will receive a JSON-RPC error response with
195    /// code `-32603` (Internal Error) instead of the expected response.
196    ///
197    /// # Examples
198    ///
199    /// ```rust,no_run
200    /// use serde_json::{json, Value};
201    /// use lsp_max::jsonrpc::Result;
202    /// use lsp_max::lsp_types_max::*;
203    /// use lsp_max::{LanguageServer, LspService};
204    ///
205    /// struct Mock;
206    ///
207    /// // Implementation of `LanguageServer` omitted...
208    /// # #[lsp_max::async_trait]
209    /// # impl LanguageServer for Mock {
210    /// #     async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
211    /// #         Ok(InitializeResult::default())
212    /// #     }
213    /// #
214    /// #     async fn shutdown(&self) -> Result<()> {
215    /// #         Ok(())
216    /// #     }
217    /// # }
218    ///
219    /// impl Mock {
220    ///     async fn request(&self) -> Result<i32> {
221    ///         Ok(123)
222    ///     }
223    ///
224    ///     async fn request_params(&self, params: Vec<String>) -> Result<Value> {
225    ///         Ok(json!({"num_elems":params.len()}))
226    ///     }
227    ///
228    ///     async fn notification(&self) {
229    ///         // ...
230    ///     }
231    ///
232    ///     async fn notification_params(&self, params: Value) {
233    ///         // ...
234    /// #       let _ = params;
235    ///     }
236    /// }
237    ///
238    /// let (service, socket) = LspService::build(|_| Mock)
239    ///     .custom_method("custom/request", Mock::request)
240    ///     .custom_method("custom/requestParams", Mock::request_params)
241    ///     .custom_method("custom/notification", Mock::notification)
242    ///     .custom_method("custom/notificationParams", Mock::notification_params)
243    ///     .finish();
244    /// ```
245    pub fn custom_method<P, R, F>(mut self, name: &'static str, callback: F) -> Self
246    where
247        P: FromParams,
248        R: IntoResponse,
249        F: for<'a> Method<&'a S, P, R> + Clone + Send + Sync + 'static,
250    {
251        let layer = tower::ServiceBuilder::new()
252            .layer(layers::Normal::new(
253                self.state.clone(),
254                self.pending.clone(),
255            ))
256            .layer(self.doc_sync.clone())
257            .into_inner();
258        self.inner.method(name, callback, layer);
259        self
260    }
261
262    /// Registers a catch-all handler for any JSON-RPC method not handled by registered routes.
263    ///
264    /// Used by the compositor to forward unknown LSP requests to the primary child server
265    /// so that the editor never receives a `MethodNotFound` response for methods the compositor
266    /// does not explicitly implement but a child server does.
267    ///
268    /// The closure receives the full `Request` and must return an `Option<Response>`:
269    /// - `Some(response)` to send a response back to the editor
270    /// - `None` to silently drop the request (appropriate for notifications)
271    pub fn fallback_method<F>(mut self, f: F) -> Self
272    where
273        F: Fn(
274                crate::jsonrpc::Request,
275            ) -> futures::future::BoxFuture<
276                'static,
277                Result<Option<crate::jsonrpc::Response>, ExitedError>,
278            > + Send
279            + Sync
280            + 'static,
281    {
282        self.inner.set_fallback(f);
283        self
284    }
285
286    /// Constructs the `LspService` and returns it, along with a channel for server-to-client
287    /// communication.
288    pub fn finish(self) -> (LspService<S>, ClientSocket) {
289        let LspServiceBuilder {
290            inner,
291            state,
292            socket,
293            ..
294        } = self;
295
296        let inner = tower::Layer::layer(&layers::CatchUnwind, inner);
297
298        (LspService { inner, state }, socket)
299    }
300}
301
302impl<S: Debug> Debug for LspServiceBuilder<S> {
303    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
304        f.debug_struct("LspServiceBuilder")
305            .field("inner", &self.inner)
306            .finish_non_exhaustive()
307    }
308}
309
310#[allow(dead_code)]
311fn handle_mesh_rpc(
312    state: &ServerState,
313    method: &str,
314    params: Option<Value>,
315) -> Result<Value, String> {
316    let mut registry = crate::get_registry()
317        .lock()
318        .unwrap_or_else(|p| p.into_inner());
319    crate::update_diagnostics(&mut registry);
320
321    let mut mesh = state.mesh.lock().unwrap_or_else(|p| p.into_inner());
322
323    // Sync registry -> mesh
324    let instance_id = "LSP_1";
325    if !mesh.instances.contains_key(instance_id) {
326        mesh.add_instance(crate::max_runtime::LspInstance::new(instance_id));
327    }
328    {
329        let instance = match mesh.instances.get_mut(instance_id) {
330            Some(inst) => inst,
331            None => return Err("LSP_1 instance not found".to_string()),
332        };
333        instance.phase = match registry.current_state {
334            State::Uninitialized => crate::max_runtime::LspPhase::Uninitialized,
335            State::Initializing => crate::max_runtime::LspPhase::Initializing,
336            State::Initialized => crate::max_runtime::LspPhase::Initialized,
337            State::ShutDown => crate::max_runtime::LspPhase::ShutDown,
338            State::Exited => crate::max_runtime::LspPhase::Exited,
339        };
340        instance.diagnostics = registry.diagnostics.values().cloned().collect();
341        let mut sorted_receipts: Vec<_> = registry.receipts.values().cloned().collect();
342        sorted_receipts.sort_by_key(|r| {
343            if r.receipt_id == "rcpt-uninitialized" {
344                0
345            } else if r
346                .receipt_id
347                .starts_with("rcpt-uninitialized-to-initializing:")
348            {
349                1
350            } else if r
351                .receipt_id
352                .starts_with("rcpt-initializing-to-initialized:")
353            {
354                2
355            } else if r.receipt_id == "rcpt-initialized-to-shutdown" {
356                3
357            } else if r.receipt_id == "rcpt-shutdown-to-exited" {
358                4
359            } else {
360                5
361            }
362        });
363        instance.receipts = sorted_receipts;
364    }
365
366    // Dispatch RPC
367    let result = mesh.dispatch_rpc(instance_id, method, params.unwrap_or(Value::Null))?;
368
369    // Sync mesh -> registry
370    if let Some(instance) = mesh.instances.get(instance_id) {
371        let mesh_diagnostic_ids: std::collections::HashSet<String> = instance
372            .diagnostics
373            .iter()
374            .map(|d| d.diagnostic_id.clone())
375            .collect();
376        for id in registry
377            .diagnostics
378            .keys()
379            .cloned()
380            .collect::<Vec<String>>()
381        {
382            if !mesh_diagnostic_ids.contains(&id) {
383                registry.diagnostics.remove(&id);
384                registry.cleared_diagnostics.insert(id);
385            }
386        }
387        for d in &instance.diagnostics {
388            registry
389                .diagnostics
390                .insert(d.diagnostic_id.clone(), d.clone());
391        }
392        for r in &instance.receipts {
393            registry.receipts.insert(r.receipt_id.clone(), r.clone());
394        }
395    }
396
397    // Custom post-sync logic for snapshot: register snapshot in registry
398    if method == "max/snapshot" {
399        let snapshot_id: crate::max_protocol::SnapshotId =
400            serde_json::from_value(result.clone())
401                .map_err(|e| format!("Invalid snapshot ID: {}", e))?;
402
403        let capability_vector = crate::max_protocol::MaxCapabilityVector {
404            client: registry.client_capabilities.clone().unwrap_or_default(),
405            server: registry.server_capabilities.clone().unwrap_or_default(),
406            negotiated: serde_json::json!({
407                "conformance": "maximal",
408                "law_framework": "v1"
409            }),
410            experimental: serde_json::json!({}),
411            gaps: vec![],
412        };
413
414        let diagnostics = registry.diagnostics.values().cloned().collect();
415        let actions = registry.repair_plans.values().flatten().cloned().collect();
416
417        let score = if registry.diagnostics.is_empty() {
418            100.0
419        } else {
420            let severity_penalty: f64 = registry
421                .diagnostics
422                .values()
423                .map(|d| match d.lsp.severity {
424                    Some(crate::lsp_types_max::DiagnosticSeverity::ERROR) => 30.0,
425                    Some(crate::lsp_types_max::DiagnosticSeverity::WARNING) => 15.0,
426                    _ => 5.0,
427                })
428                .sum();
429            (100.0 - severity_penalty).max(0.0)
430        };
431
432        let (refused_diags, admitted_diags): (Vec<_>, Vec<_>) =
433            registry.diagnostics.values().partition(|d| {
434                matches!(
435                    d.lsp.severity,
436                    Some(crate::lsp_types_max::DiagnosticSeverity::ERROR)
437                )
438            });
439        let refused: Vec<max_protocol::LawAxis> =
440            refused_diags.iter().map(|d| d.law_axis.clone()).collect();
441        let admitted: Vec<max_protocol::LawAxis> =
442            admitted_diags.iter().map(|d| d.law_axis.clone()).collect();
443        let derived_score = if admitted.is_empty() && refused.is_empty() {
444            None
445        } else {
446            let total = (admitted.len() + refused.len()) as f64;
447            Some(100.0 * admitted.len() as f64 / total)
448        };
449        let _ = score; // superseded by derived_score
450        let conformance_vector = crate::max_protocol::ConformanceVector {
451            admitted,
452            refused,
453            unknown: Vec::new(),
454            score: derived_score,
455            strict_mode: true,
456            process_quality: None,
457            ..Default::default()
458        };
459
460        let receipts = registry.receipts.values().cloned().collect();
461
462        let record = crate::SnapshotRecord {
463            id: snapshot_id.clone(),
464            capability_vector,
465            diagnostics,
466            actions,
467            conformance_vector,
468            receipts,
469        };
470
471        registry.snapshots.insert(snapshot_id.0.clone(), record);
472    }
473
474    Ok(result)
475}
476
477#[cfg(test)]
478mod tests;
479
480#[cfg(test)]
481mod unit_tests {
482    use super::*;
483
484    #[test]
485    fn exited_error_code() {
486        let e = ExitedError(42);
487        assert_eq!(e.code(), 42);
488    }
489
490    #[test]
491    fn exited_error_display() {
492        let e = ExitedError(1);
493        assert!(e.to_string().contains("1"));
494    }
495
496    #[test]
497    fn exited_error_ne() {
498        assert_ne!(ExitedError(0), ExitedError(1));
499    }
500}