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