Skip to main content

ad_editor/lsp/
mod.rs

1//! Built-in minimal LSP support for ad
2//!
3//! See the LSP spec for details of semantics:
4//!   <https://microsoft.github.io/language-server-protocol/specification>
5use crate::{
6    buffer::{Buffer, Buffers},
7    config::{FtypeConfig, LspConfig, ftype_config_for_path_and_first_line},
8    die,
9    editor::{Action, MbSelect},
10    input::Event,
11    lsp::{
12        capabilities::{Capabilities, PositionEncoding},
13        client::{LspClient, LspMessage},
14        messages::{
15            Diagnostic, Diagnostics, LspNotification, LspRequest, NotificationHandler,
16            OpenDocument, PendingLspRequest, PreparedLspNotification, PreparedLspRequest,
17            RequestHandler, txt_doc_id,
18        },
19        rpc::{Message, Notification, Request, RequestId, Response},
20    },
21    util::ReadOnlyLock,
22};
23use lsp_types::{NumberOrString, Uri, notification as notif, request as req, request::Initialize};
24use std::{
25    collections::HashMap,
26    path::Path,
27    sync::{
28        Arc, RwLock,
29        mpsc::{Receiver, Sender, channel},
30    },
31    thread::{sleep, spawn},
32    time::Duration,
33};
34use tracing::{debug, error, warn};
35
36mod capabilities;
37mod client;
38mod messages;
39mod rpc;
40
41pub use capabilities::Coords;
42
43const LSP_FILE: &str = "+lsp";
44
45#[derive(Debug)]
46pub(crate) enum PreparedMessage {
47    Request(Box<dyn PreparedLspRequest>),
48    Notification(Box<dyn PreparedLspNotification>),
49}
50
51#[derive(Debug)]
52pub(crate) enum Req {
53    Start {
54        ftype: String,
55        cmd: String,
56        args: Vec<String>,
57        init_opts: Option<serde_json::Value>,
58        root: String,
59        open_docs: Vec<OpenDocument>,
60    },
61    Stop {
62        lsp_id: usize,
63    },
64    Prepared(PreparedMessage),
65    Message(LspMessage),
66}
67
68#[derive(Debug)]
69pub struct LspManagerHandle {
70    tx_req: Sender<Req>,
71    capabilities: ReadOnlyLock<HashMap<String, (usize, Capabilities)>>,
72    diagnostics: ReadOnlyLock<HashMap<Uri, Vec<Diagnostic>>>,
73    configs: HashMap<String, FtypeConfig>,
74    autostart: bool,
75}
76
77impl LspManagerHandle {
78    pub(crate) fn new_stubbed(tx_req: Sender<Req>) -> Self {
79        Self {
80            tx_req,
81            capabilities: Default::default(),
82            diagnostics: Default::default(),
83            configs: Default::default(),
84            autostart: false,
85        }
86    }
87
88    fn send_req(&self, req: impl PreparedLspRequest) {
89        let msg = PreparedMessage::Request(Box::new(req));
90        if let Err(e) = self.tx_req.send(Req::Prepared(msg)) {
91            die!("LSP manager died: {e}")
92        }
93    }
94
95    fn send_notification(&self, notif: impl PreparedLspNotification) {
96        let msg = PreparedMessage::Notification(Box::new(notif));
97        if let Err(e) = self.tx_req.send(Req::Prepared(msg)) {
98            die!("LSP manager died: {e}")
99        }
100    }
101
102    /// Will return None if there is no active client with recorded capabilities for the
103    /// given language.
104    fn lsp_id_and_encoding_for(&self, b: &Buffer) -> Option<(usize, PositionEncoding)> {
105        let (ftype, _) = &self.config_for_buffer(b)?;
106
107        self.capabilities
108            .read()
109            .unwrap()
110            .get(ftype.as_str())
111            .map(|(id, caps)| (*id, caps.position_encoding))
112    }
113
114    fn lsp_id_and_encoding_for_ftype(&self, ftype: &str) -> Option<(usize, PositionEncoding)> {
115        self.capabilities
116            .read()
117            .unwrap()
118            .get(ftype)
119            .map(|(id, caps)| (*id, caps.position_encoding))
120    }
121
122    fn config_for_path_and_first_line(
123        &self,
124        path: &Path,
125        first_line: &str,
126    ) -> Option<(&String, &LspConfig)> {
127        ftype_config_for_path_and_first_line(path, first_line, &self.configs)
128            .and_then(|(name, c)| c.lsp.as_ref().map(|lsp| (name, lsp)))
129    }
130
131    fn config_for_buffer(&self, b: &Buffer) -> Option<(&String, &LspConfig)> {
132        let first_line = b.line(0).map(|l| l.to_string()).unwrap_or_default();
133
134        self.config_for_path_and_first_line(b.path()?, &first_line)
135    }
136
137    fn start_req_for_buf(&self, bs: &Buffers) -> Option<Req> {
138        let b = bs.active();
139        let (ftype, config) = self.config_for_buffer(b)?;
140        let root = config.root_for_buffer(b)?.to_str()?.to_owned();
141        let open_docs: Vec<_> = bs
142            .iter()
143            .flat_map(|b| match self.config_for_buffer(b) {
144                Some((bftype, _)) if bftype == ftype => Some(OpenDocument {
145                    ftype: ftype.to_owned(),
146                    path: b.full_name().to_owned(),
147                    content: b.str_contents(),
148                }),
149                _ => None,
150            })
151            .collect();
152
153        Some(Req::Start {
154            ftype: ftype.to_owned(),
155            cmd: config.command.clone(),
156            args: config.args.clone(),
157            init_opts: config.init_opts.clone(),
158            root,
159            open_docs,
160        })
161    }
162
163    pub fn start_client(&self, bs: &Buffers) -> Option<&'static str> {
164        match self.start_req_for_buf(bs) {
165            Some(req) => {
166                debug!("starting LSP server");
167                if let Err(e) = self.tx_req.send(req) {
168                    die!("LSP manager died: {e}")
169                }
170                None
171            }
172
173            None => Some("no LSP available for buffer"),
174        }
175    }
176
177    pub fn stop_client(&self, b: &Buffer) {
178        if let Some((lsp_id, _)) = self.lsp_id_and_encoding_for(b) {
179            debug!("stopping LSP server {lsp_id}");
180            if let Err(e) = self.tx_req.send(Req::Stop { lsp_id }) {
181                die!("LSP manager died: {e}")
182            }
183        };
184    }
185
186    pub fn show_server_capabilities(&self, b: &Buffer) -> Option<(&'static str, String)> {
187        let (ftype, _) = &self.config_for_buffer(b)?;
188        let txt = self
189            .capabilities
190            .read()
191            .unwrap()
192            .get(ftype.as_str())?
193            .1
194            .as_pretty_json()?;
195
196        Some((LSP_FILE, txt))
197    }
198
199    pub fn show_diagnostics(&self, b: &Buffer) -> Action {
200        if b.dirty {
201            // give diagnostics a chance to update
202            self.document_changed(b);
203            sleep(Duration::from_millis(300));
204        }
205
206        debug!("showing LSP diagnostics");
207        let guard = self.diagnostics.read().unwrap();
208        let mut diags: Vec<Diagnostic> = guard.values().flatten().cloned().collect();
209        diags.sort_unstable();
210
211        Action::MbSelect(Diagnostics(diags).into_selector())
212    }
213
214    /// Notify an attached LSP server that a document has been opened.
215    ///
216    /// If the `editor.lsp_autostart` config value is true then attempt to start the server if one
217    /// is not already running.
218    pub fn document_opened(&self, bs: &Buffers) {
219        let b = bs.active();
220        let ftype = match self.config_for_buffer(b) {
221            Some((ftype, _)) => ftype,
222            None => return,
223        };
224
225        match self.lsp_id_and_encoding_for_ftype(ftype) {
226            Some((lsp_id, _)) => {
227                debug!("sending LSP textDocument/didOpen ({lsp_id})");
228                let path = b.full_name().to_string();
229                let content = b.str_contents();
230
231                self.send_notification(notif::DidOpenTextDocument::data(
232                    lsp_id,
233                    (ftype.clone(), path, content),
234                ));
235            }
236
237            None if self.autostart => {
238                self.start_client(bs);
239            }
240
241            None => (),
242        }
243    }
244
245    pub fn document_closed(&self, b: &Buffer) {
246        if let Some((lsp_id, _)) = self.lsp_id_and_encoding_for(b) {
247            debug!("sending LSP textDocument/didClose ({lsp_id})");
248            let path = b.full_name().to_string();
249
250            self.send_notification(notif::DidCloseTextDocument::data(lsp_id, path));
251        }
252    }
253
254    pub fn document_changed(&self, b: &Buffer) {
255        if let Some((lsp_id, _)) = self.lsp_id_and_encoding_for(b) {
256            debug!("sending LSP textDocument/didChange ({lsp_id})");
257            let path = b.full_name().to_string();
258            let content = b.str_contents();
259            let version = b.next_edit_version() as i32;
260
261            self.send_notification(notif::DidChangeTextDocument::data(
262                lsp_id,
263                (path, content, version),
264            ));
265        }
266    }
267
268    pub fn document_saved(&self, b: &Buffer) {
269        if let Some((lsp_id, _)) = self.lsp_id_and_encoding_for(b) {
270            debug!("sending LSP textDocument/didSave ({lsp_id})");
271            let path = b.full_name().to_string();
272
273            self.send_notification(notif::DidSaveTextDocument::data(lsp_id, path));
274        }
275    }
276
277    pub fn goto_declaration(&self, b: &Buffer) {
278        if let Some((lsp_id, enc)) = self.lsp_id_and_encoding_for(b) {
279            if b.dirty {
280                self.document_changed(b);
281            }
282
283            debug!("sending LSP textDocument/declaration ({lsp_id})");
284            self.send_req(req::GotoDeclaration::data(lsp_id, enc.buffer_pos(b), ()));
285        }
286    }
287
288    pub fn goto_definition(&self, b: &Buffer) {
289        if let Some((lsp_id, enc)) = self.lsp_id_and_encoding_for(b) {
290            if b.dirty {
291                self.document_changed(b);
292            }
293
294            debug!("sending LSP textDocument/definition ({lsp_id})");
295            self.send_req(req::GotoDefinition::data(lsp_id, enc.buffer_pos(b), ()));
296        }
297    }
298
299    pub fn goto_type_definition(&self, b: &Buffer) {
300        if let Some((lsp_id, enc)) = self.lsp_id_and_encoding_for(b) {
301            if b.dirty {
302                self.document_changed(b);
303            }
304
305            debug!("sending LSP textDocument/typeDefinition ({lsp_id})");
306            self.send_req(req::GotoTypeDefinition::data(lsp_id, enc.buffer_pos(b), ()));
307        }
308    }
309
310    pub fn hover(&self, b: &Buffer) {
311        if let Some((lsp_id, enc)) = self.lsp_id_and_encoding_for(b) {
312            if b.dirty {
313                self.document_changed(b);
314            }
315
316            debug!("sending LSP textDocument/hover ({lsp_id})");
317            self.send_req(req::HoverRequest::data(lsp_id, enc.buffer_pos(b), ()));
318        }
319    }
320
321    pub fn completion(&self, b: &Buffer) {
322        if let Some((lsp_id, enc)) = self.lsp_id_and_encoding_for(b) {
323            if b.dirty {
324                self.document_changed(b);
325            }
326
327            debug!("sending LSP textDocument/completion ({lsp_id})");
328            let pos = enc.buffer_pos(b);
329            self.send_req(req::Completion::data(lsp_id, pos.clone(), pos));
330        }
331    }
332
333    pub fn find_references(&self, b: &Buffer) {
334        if let Some((lsp_id, enc)) = self.lsp_id_and_encoding_for(b) {
335            if b.dirty {
336                self.document_changed(b);
337            }
338
339            debug!("sending LSP textDocument/references ({lsp_id})");
340            self.send_req(req::References::data(lsp_id, enc.buffer_pos(b), ()));
341        }
342    }
343
344    pub fn format(&self, b: &Buffer) {
345        if let Some((lsp_id, _)) = self.lsp_id_and_encoding_for(b) {
346            if b.dirty {
347                self.document_changed(b);
348            }
349
350            debug!("sending LSP textDocument/formatting ({lsp_id})");
351            self.send_req(req::Formatting::data(
352                lsp_id,
353                (txt_doc_id(b.full_name()), b.tabstop() as u32),
354                (),
355            ));
356        }
357    }
358
359    pub fn prepare_rename(&self, b: &Buffer) {
360        if let Some((lsp_id, enc)) = self.lsp_id_and_encoding_for(b) {
361            if b.dirty {
362                self.document_changed(b);
363            }
364
365            debug!("sending LSP textDocument/prepareRename ({lsp_id})");
366            let pos = enc.buffer_pos(b);
367            self.send_req(req::PrepareRenameRequest::data(lsp_id, pos, ()));
368        }
369    }
370
371    pub fn rename(&self, b: &Buffer, new_name: String) {
372        if let Some((lsp_id, enc)) = self.lsp_id_and_encoding_for(b) {
373            if b.dirty {
374                self.document_changed(b);
375            }
376
377            debug!("sending LSP textDocument/rename ({lsp_id})");
378            let pos = enc.buffer_pos(b);
379            self.send_req(req::Rename::data(
380                lsp_id,
381                (pos.clone(), new_name),
382                (pos, b.id),
383            ));
384        }
385    }
386}
387
388#[derive(Debug)]
389pub struct LspManager {
390    clients: HashMap<usize, LspClient>,
391    // ftype -> (lspID, server capabilities)
392    capabilities: Arc<RwLock<HashMap<String, (usize, Capabilities)>>>,
393    // (lspID, ReqID) -> in-flight requests we need a response for
394    pending: HashMap<(usize, RequestId), Box<dyn PendingLspRequest>>,
395    // lspID -> map of progress token -> title
396    progress_tokens: HashMap<usize, HashMap<NumberOrString, String>>,
397    diagnostics: Arc<RwLock<HashMap<Uri, Vec<Diagnostic>>>>,
398    pub(super) tx_req: Sender<Req>,
399    tx_events: Sender<Event>,
400    next_id: usize,
401}
402
403impl LspManager {
404    pub fn spawn(
405        configs: HashMap<String, FtypeConfig>,
406        tx_events: Sender<Event>,
407        autostart: bool,
408    ) -> LspManagerHandle {
409        let (tx_req, rx_req) = channel();
410        let manager = Self {
411            clients: Default::default(),
412            capabilities: Default::default(),
413            pending: Default::default(),
414            progress_tokens: Default::default(),
415            diagnostics: Default::default(),
416            tx_req: tx_req.clone(),
417            tx_events,
418            next_id: 0,
419        };
420
421        let capabilities = ReadOnlyLock::new(manager.capabilities.clone());
422        let diagnostics = ReadOnlyLock::new(manager.diagnostics.clone());
423        spawn(move || manager.run(rx_req));
424
425        LspManagerHandle {
426            tx_req,
427            capabilities,
428            diagnostics,
429            configs,
430            autostart,
431        }
432    }
433
434    fn run(mut self, rx_req: Receiver<Req>) {
435        for r in rx_req.into_iter() {
436            match r {
437                Req::Start {
438                    ftype,
439                    cmd,
440                    args,
441                    init_opts,
442                    root,
443                    open_docs,
444                } => self.start_client(ftype, cmd, args, init_opts, root, open_docs),
445                Req::Stop { lsp_id } => self.stop_client(lsp_id),
446                Req::Prepared(p) => self.handle_prepared_message(p),
447                Req::Message(LspMessage { lsp_id, msg }) => match msg {
448                    Message::Request(r) => self.handle_request(lsp_id, r),
449                    Message::Response(r) => self.handle_response(lsp_id, r),
450                    Message::Notification(n) => self.handle_notification(lsp_id, n),
451                },
452            }
453        }
454    }
455
456    fn handle_prepared_message(&mut self, msg: PreparedMessage) {
457        match msg {
458            PreparedMessage::Request(mut req) => req.send(self),
459            PreparedMessage::Notification(mut notif) => notif.send(self),
460        }
461    }
462
463    fn handle_request(&mut self, lsp_id: usize, req: Request) {
464        use lsp_types::request as req;
465
466        RequestHandler {
467            lsp_id,
468            r: Some(req),
469            man: self,
470        }
471        .handle::<req::WorkDoneProgressCreate>()
472        .log_unhandled();
473    }
474
475    fn handle_response(&mut self, lsp_id: usize, res: Response) {
476        let mut p = match self.pending.remove(&(lsp_id, res.id())) {
477            Some(p) => p,
478            None => {
479                warn!("LSP - got response for unknown request: {res:?}");
480                return;
481            }
482        };
483
484        if let Some(actions) = p.handle(res, self)
485            && self.tx_events.send(Event::Actions(actions)).is_err()
486        {
487            error!("LSP - sender actions channel closed: exiting");
488        }
489    }
490
491    pub fn handle_notification(&mut self, lsp_id: usize, n: Notification) {
492        use lsp_types::notification as notif;
493
494        NotificationHandler {
495            lsp_id,
496            n: Some(n),
497            man: self,
498        }
499        .handle::<notif::Progress>()
500        .handle::<notif::PublishDiagnostics>()
501        .log_unhandled();
502    }
503
504    pub(super) fn progress_tokens(&mut self, lsp_id: usize) -> &mut HashMap<RequestId, String> {
505        self.progress_tokens.entry(lsp_id).or_default()
506    }
507
508    fn next_id(&mut self) -> usize {
509        let id = self.next_id;
510        self.next_id += 1;
511
512        id
513    }
514
515    fn send_status(&self, message: impl Into<String>) {
516        _ = self.tx_events.send(Event::Action(Action::SetStatusMessage {
517            message: message.into(),
518        }));
519    }
520
521    #[inline]
522    fn report_error(&self, message: impl Into<String>) {
523        let message = message.into();
524        error!("{message}");
525        self.send_status(message);
526    }
527
528    fn start_client(
529        &mut self,
530        ftype: String,
531        cmd: String,
532        args: Vec<String>,
533        init_opts: Option<serde_json::Value>,
534        root: String,
535        open_bufs: Vec<OpenDocument>,
536    ) {
537        let lsp_id = self.next_id();
538        match LspClient::new(lsp_id, &cmd, args, self.tx_req.clone()) {
539            Ok(client) => self.clients.insert(lsp_id, client),
540            Err(e) => {
541                return self.report_error(format!("failed to start LSP server: {e}"));
542            }
543        };
544
545        Initialize::send(lsp_id, (root, init_opts), (ftype, open_bufs), self);
546        self.send_status("LSP server started");
547    }
548
549    fn stop_client(&mut self, lsp_id: usize) {
550        use lsp_types::{notification::Exit, request::Shutdown};
551
552        Shutdown::send(lsp_id, (), (), self);
553        Exit::send(lsp_id, (), self);
554
555        match self.clients.remove(&lsp_id) {
556            Some(client) => client.join(),
557            None => self.report_error("no attached LSP server"),
558        }
559    }
560}
561
562#[derive(Debug, Clone)]
563pub(crate) struct Pos {
564    pub(crate) file: String,
565    pub(crate) line: u32,
566    pub(crate) character: u32,
567}
568
569impl Pos {
570    fn new(file: impl Into<String>, line: u32, character: u32) -> Self {
571        Self {
572            file: file.into(),
573            line,
574            character,
575        }
576    }
577}