agentmux_srv\server/
lsp_handlers.rs

1// Copyright 2025-2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::sync::Arc;
5
6use crate::backend::rpc::engine::WshRpcEngine;
7use crate::backend::base::expand_home_dir_safe;
8
9use super::AppState;
10
11pub fn register_lsp_handlers(engine: &Arc<WshRpcEngine>, state: &AppState) {
12    // ── LSP RPCs ───────────────────────────────────────────────────
13    // Three handlers backing the editor pane's LSP integration:
14    //   * lspstart — spawn (or attach to) the server for a file
15    //   * lspsend  — forward an LSP JSON-RPC message to the server's stdin
16    //   * lspstop  — refcount-decrement; server exits when count hits 0
17    // Server-pushed notifications (publishDiagnostics, $/progress, …)
18    // arrive via WS event `lsp:message` from the supervisor's reader task.
19    // Spec: specs/SPEC_EDITOR_LSP_AND_THEMES_2026-05-26.md
20
21    let lsp_supervisor_start = state.lsp_supervisor.clone();
22    engine.register_handler(
23        "lspstart",
24        Box::new(move |data, _ctx| {
25            let supervisor = lsp_supervisor_start.clone();
26            Box::pin(async move {
27                #[derive(serde::Deserialize)]
28                struct Cmd {
29                    language: String,
30                    file_path: String,
31                }
32                let cmd: Cmd = serde_json::from_value(data)
33                    .map_err(|e| format!("lspstart: {e}"))?;
34                let expanded = expand_home_dir_safe(&cmd.file_path);
35                let workspace_root =
36                    crate::backend::lsp::workspace::detect_workspace_root(expanded.as_path());
37                let result = supervisor
38                    .start(crate::backend::lsp::StartArgs {
39                        language: cmd.language,
40                        workspace_root,
41                    })
42                    .await
43                    .map_err(|e| e.to_wire_string())?;
44                Ok(Some(serde_json::json!({
45                    "server_id": result.server_id,
46                    "workspace_root": result.workspace_root,
47                })))
48            })
49        }),
50    );
51
52    let lsp_supervisor_send = state.lsp_supervisor.clone();
53    engine.register_handler(
54        "lspsend",
55        Box::new(move |data, _ctx| {
56            let supervisor = lsp_supervisor_send.clone();
57            Box::pin(async move {
58                #[derive(serde::Deserialize)]
59                struct Cmd {
60                    server_id: String,
61                    message: serde_json::Value,
62                }
63                let cmd: Cmd = serde_json::from_value(data)
64                    .map_err(|e| format!("lspsend: {e}"))?;
65                let message_json = serde_json::to_string(&cmd.message)
66                    .map_err(|e| format!("lspsend serialize: {e}"))?;
67                supervisor
68                    .send(&cmd.server_id, &message_json)
69                    .await
70                    .map_err(|e| e.to_wire_string())?;
71                Ok(None)
72            })
73        }),
74    );
75
76    let lsp_supervisor_stop = state.lsp_supervisor.clone();
77    engine.register_handler(
78        "lspstop",
79        Box::new(move |data, _ctx| {
80            let supervisor = lsp_supervisor_stop.clone();
81            Box::pin(async move {
82                #[derive(serde::Deserialize)]
83                struct Cmd {
84                    server_id: String,
85                }
86                let cmd: Cmd = serde_json::from_value(data)
87                    .map_err(|e| format!("lspstop: {e}"))?;
88                supervisor
89                    .stop(&cmd.server_id)
90                    .await
91                    .map_err(|e| e.to_wire_string())?;
92                Ok(None)
93            })
94        }),
95    );
96}