1use 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 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); 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 capabilities: Arc<RwLock<HashMap<String, (usize, Capabilities)>>>,
278 pending: HashMap<(usize, RequestId), Pending>,
280 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}