Skip to main content

dada_lsp_server/
lsp.rs

1use std::sync::atomic::{AtomicBool, Ordering};
2
3use dada_util::Fallible;
4use dispatch::LspDispatch;
5use lsp_server::Connection;
6use lsp_types::{
7    InitializeParams, InitializeResult, PublishDiagnosticsParams, ServerCapabilities, ServerInfo,
8    notification, request,
9};
10
11mod dispatch;
12
13/// LSP server handlers.
14pub trait Lsp: Sized {
15    /// The server is "forked" to handle incoming "read" requests (e.g., goto-def).
16    /// "Read" requests are requests that do not modify document state.
17    type Fork: LspFork;
18
19    fn run() -> Fallible<()> {
20        run_server::<Self>()
21    }
22
23    fn new(params: InitializeParams) -> Fallible<Self>;
24
25    /// Capabilities to report to the editor.
26    fn server_capabilities(&mut self) -> Fallible<ServerCapabilities>;
27
28    /// Server info to report to the editor.
29    fn server_info(&mut self) -> Fallible<Option<ServerInfo>>;
30
31    /// Create a "fork" of the LSP server that can be used from another thread.
32    fn fork(&mut self) -> Self::Fork;
33
34    /// Open reported for the given URI.
35    fn did_open(
36        &mut self,
37        editor: &mut dyn Editor<Self>,
38        item: lsp_types::DidOpenTextDocumentParams,
39    ) -> Fallible<()>;
40
41    /// Modification reported to the given URI.
42    fn did_change(
43        &mut self,
44        editor: &mut dyn Editor<Self>,
45        item: lsp_types::DidChangeTextDocumentParams,
46    ) -> Fallible<()>;
47
48    /// Handle hover requests.
49    fn hover(
50        &mut self,
51        editor: &mut dyn Editor<Self>,
52        params: lsp_types::HoverParams,
53    ) -> Fallible<Option<lsp_types::Hover>>;
54}
55
56pub trait LspFork: Sized + Send {
57    #[expect(dead_code)]
58    fn fork(&self) -> Self;
59}
60
61/// Allows your LSP server to make requests of the "editor".
62///
63/// The "editor" here includes the actual editor but also our dispatch loop,
64/// which to you are not distinguishable.
65pub trait Editor<L: Lsp> {
66    /// Display a message to the user.
67    fn show_message(
68        &mut self,
69        message_type: lsp_types::MessageType,
70        message: String,
71    ) -> Fallible<()>;
72
73    fn publish_diagnostics(&mut self, params: PublishDiagnosticsParams) -> Fallible<()>;
74
75    /// Enqueue a task to execute in parallel. The task may not start executing immediately.
76    /// The task will be given a fork of the lsp along with an editor of its own.
77    #[allow(clippy::type_complexity)]
78    fn spawn(&mut self, task: Box<dyn FnOnce(&L::Fork, &mut dyn Editor<L>) -> Fallible<()> + Send>);
79}
80
81pub fn run_server<L: Lsp>() -> Fallible<()> {
82    // Create the transport. Includes the stdio (stdin and stdout) versions but this could
83    // also be implemented to use sockets or HTTP.
84    let (connection, io_threads) = Connection::stdio();
85
86    let lsp = initialize_server::<L>(&connection)?;
87
88    LspDispatch::new(connection, lsp)
89        .on_notification::<notification::DidOpenTextDocument>(Lsp::did_open)
90        .on_notification::<notification::DidChangeTextDocument>(Lsp::did_change)
91        .on_request::<request::HoverRequest>(Lsp::hover)
92        .execute()?;
93
94    io_threads.join()?;
95
96    // Shut down gracefully.
97    eprintln!("shutting down server");
98    Ok(())
99}
100
101static CANCEL: AtomicBool = AtomicBool::new(false);
102
103fn not_canceled() -> bool {
104    !CANCEL.load(Ordering::Relaxed)
105}
106
107fn initialize_server<L: Lsp>(connection: &Connection) -> Fallible<L> {
108    let (initialize_id, initialize_params) = connection.initialize_start_while(not_canceled)?;
109    let initialize_params: InitializeParams = serde_json::from_value(initialize_params)?;
110
111    let mut server = L::new(initialize_params)?;
112
113    let initialize_result = InitializeResult {
114        capabilities: server.server_capabilities()?,
115        server_info: server.server_info()?,
116    };
117
118    connection.initialize_finish_while(
119        initialize_id,
120        serde_json::to_value(initialize_result)?,
121        not_canceled,
122    )?;
123
124    Ok(server)
125}