1#![doc = include_str!("../README.md")]
2use lsp_server::{Connection, IoThreads, Message, Response, ResponseError};
3use lsp_textdocument::{FullTextDocument, TextDocuments};
4use lsp_types::{
5 request::{self, Request},
6 InlayHint, OneOf, Position, Range, ReferencesOptions, RenameOptions, SemanticToken,
7 SemanticTokenType, SemanticTokensLegend, SemanticTokensOptions,
8 SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelpOptions,
9 TextDocumentSyncKind, Uri, WorkDoneProgressOptions, WorkspaceFolder,
10 WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
11};
12use miette::{miette, IntoDiagnostic, Result};
13use nu_protocol::{
14 ast::{Block, PathMember},
15 engine::{EngineState, StateDelta, StateWorkingSet},
16 DeclId, ModuleId, Span, Type, Value, VarId,
17};
18use std::{
19 collections::BTreeMap,
20 path::{Path, PathBuf},
21 str::FromStr,
22 sync::Arc,
23 sync::Mutex,
24 time::Duration,
25};
26use symbols::SymbolCache;
27use workspace::{InternalMessage, RangePerDoc};
28
29mod ast;
30mod completion;
31mod diagnostics;
32mod goto;
33mod hints;
34mod hover;
35mod notification;
36mod semantic_tokens;
37mod signature;
38mod symbols;
39mod workspace;
40
41#[derive(Debug, Clone, PartialEq)]
42pub(crate) enum Id {
43 Variable(VarId, Box<[u8]>),
44 Declaration(DeclId),
45 Value(Type),
46 Module(ModuleId, Box<[u8]>),
47 CellPath(VarId, Vec<PathMember>),
48 External(String),
49}
50
51pub struct LanguageServer {
52 connection: Connection,
53 io_threads: Option<IoThreads>,
54 docs: Arc<Mutex<TextDocuments>>,
55 initial_engine_state: EngineState,
56 symbol_cache: SymbolCache,
57 inlay_hints: BTreeMap<Uri, Vec<InlayHint>>,
58 semantic_tokens: BTreeMap<Uri, Vec<SemanticToken>>,
59 workspace_folders: BTreeMap<String, WorkspaceFolder>,
60 occurrences: BTreeMap<Uri, Vec<Range>>,
62 channels: Option<(
63 crossbeam_channel::Sender<bool>,
64 Arc<crossbeam_channel::Receiver<InternalMessage>>,
65 )>,
66 need_parse: bool,
68 cached_state_delta: Option<StateDelta>,
70}
71
72pub(crate) fn path_to_uri(path: impl AsRef<Path>) -> Uri {
73 Uri::from_str(
74 url::Url::from_file_path(path)
75 .expect("Failed to convert path to Url")
76 .as_str(),
77 )
78 .expect("Failed to convert Url to lsp_types::Uri.")
79}
80
81pub(crate) fn uri_to_path(uri: &Uri) -> PathBuf {
82 url::Url::from_str(uri.as_str())
83 .expect("Failed to convert Uri to Url")
84 .to_file_path()
85 .expect("Failed to convert Url to path")
86}
87
88pub(crate) fn span_to_range(span: &Span, file: &FullTextDocument, offset: usize) -> Range {
89 let start = file.position_at(span.start.saturating_sub(offset) as u32);
90 let end = file.position_at(span.end.saturating_sub(offset) as u32);
91 Range { start, end }
92}
93
94impl LanguageServer {
95 pub fn initialize_stdio_connection(engine_state: EngineState) -> Result<Self> {
96 let (connection, io_threads) = Connection::stdio();
97 Self::initialize_connection(connection, Some(io_threads), engine_state)
98 }
99
100 fn initialize_connection(
101 connection: Connection,
102 io_threads: Option<IoThreads>,
103 engine_state: EngineState,
104 ) -> Result<Self> {
105 Ok(Self {
106 connection,
107 io_threads,
108 docs: Arc::new(Mutex::new(TextDocuments::new())),
109 initial_engine_state: engine_state,
110 symbol_cache: SymbolCache::new(),
111 inlay_hints: BTreeMap::new(),
112 semantic_tokens: BTreeMap::new(),
113 workspace_folders: BTreeMap::new(),
114 occurrences: BTreeMap::new(),
115 channels: None,
116 need_parse: true,
117 cached_state_delta: None,
118 })
119 }
120
121 pub fn serve_requests(mut self) -> Result<()> {
122 let work_done_progress_options = WorkDoneProgressOptions {
123 work_done_progress: Some(true),
124 };
125 let server_capabilities = serde_json::to_value(ServerCapabilities {
126 completion_provider: Some(lsp_types::CompletionOptions::default()),
127 definition_provider: Some(OneOf::Left(true)),
128 document_highlight_provider: Some(OneOf::Left(true)),
129 document_symbol_provider: Some(OneOf::Left(true)),
130 hover_provider: Some(lsp_types::HoverProviderCapability::Simple(true)),
131 inlay_hint_provider: Some(OneOf::Left(true)),
132 references_provider: Some(OneOf::Right(ReferencesOptions {
133 work_done_progress_options,
134 })),
135 rename_provider: Some(OneOf::Right(RenameOptions {
136 prepare_provider: Some(true),
137 work_done_progress_options,
138 })),
139 text_document_sync: Some(lsp_types::TextDocumentSyncCapability::Kind(
140 TextDocumentSyncKind::INCREMENTAL,
141 )),
142 workspace: Some(WorkspaceServerCapabilities {
143 workspace_folders: Some(WorkspaceFoldersServerCapabilities {
144 supported: Some(true),
145 change_notifications: Some(OneOf::Left(true)),
146 }),
147 ..Default::default()
148 }),
149 workspace_symbol_provider: Some(OneOf::Left(true)),
150 semantic_tokens_provider: Some(
151 SemanticTokensServerCapabilities::SemanticTokensOptions(SemanticTokensOptions {
152 legend: SemanticTokensLegend {
154 token_types: vec![SemanticTokenType::FUNCTION],
155 token_modifiers: vec![],
156 },
157 full: Some(lsp_types::SemanticTokensFullOptions::Bool(true)),
158 ..Default::default()
159 }),
160 ),
161 signature_help_provider: Some(SignatureHelpOptions::default()),
162 ..Default::default()
163 })
164 .expect("Must be serializable");
165 let init_params = self
166 .connection
167 .initialize_while(server_capabilities, || {
168 !self.initial_engine_state.signals().interrupted()
169 })
170 .into_diagnostic()?;
171 self.initialize_workspace_folders(init_params);
172
173 while !self.initial_engine_state.signals().interrupted() {
174 self.handle_internal_messages()?;
176
177 let msg = match self
178 .connection
179 .receiver
180 .recv_timeout(Duration::from_secs(1))
181 {
182 Ok(msg) => {
183 self.cancel_background_thread();
185 msg
186 }
187 Err(crossbeam_channel::RecvTimeoutError::Timeout) => {
188 continue;
189 }
190 Err(_) => break,
191 };
192
193 match msg {
194 Message::Request(request) => {
195 if self
196 .connection
197 .handle_shutdown(&request)
198 .into_diagnostic()?
199 {
200 return Ok(());
201 }
202
203 let resp = match request.method.as_str() {
204 request::Completion::METHOD => {
205 Self::handle_lsp_request(request, |params| self.complete(params))
206 }
207 request::DocumentHighlightRequest::METHOD => {
208 Self::handle_lsp_request(request, |params| {
209 self.document_highlight(params)
210 })
211 }
212 request::DocumentSymbolRequest::METHOD => {
213 Self::handle_lsp_request(request, |params| self.document_symbol(params))
214 }
215 request::GotoDefinition::METHOD => {
216 Self::handle_lsp_request(request, |params| self.goto_definition(params))
217 }
218 request::HoverRequest::METHOD => {
219 Self::handle_lsp_request(request, |params| self.hover(params))
220 }
221 request::InlayHintRequest::METHOD => {
222 Self::handle_lsp_request(request, |params| self.get_inlay_hints(params))
223 }
224 request::PrepareRenameRequest::METHOD => {
225 let id = request.id.clone();
226 if let Err(e) = self.prepare_rename(request) {
227 self.send_error_message(id, 2, e.to_string())?
228 }
229 continue;
230 }
231 request::References::METHOD => {
232 Self::handle_lsp_request(request, |params| {
233 self.references(params, 5000)
234 })
235 }
236 request::Rename::METHOD => {
237 Self::handle_lsp_request(request, |params| self.rename(params))
238 }
239 request::SemanticTokensFullRequest::METHOD => {
240 Self::handle_lsp_request(request, |params| {
241 self.get_semantic_tokens(params)
242 })
243 }
244 request::SignatureHelpRequest::METHOD => {
245 Self::handle_lsp_request(request, |params| {
246 self.get_signature_help(params)
247 })
248 }
249 request::WorkspaceSymbolRequest::METHOD => {
250 Self::handle_lsp_request(request, |params| {
251 self.workspace_symbol(params)
252 })
253 }
254 _ => {
255 continue;
256 }
257 };
258
259 self.connection
260 .sender
261 .send(Message::Response(resp))
262 .into_diagnostic()?;
263 }
264 Message::Response(_) => {}
265 Message::Notification(notification) => {
266 if let Some(updated_file) = self.handle_lsp_notification(notification) {
267 self.need_parse = true;
268 self.symbol_cache.mark_dirty(updated_file.clone(), true);
269 self.publish_diagnostics_for_file(updated_file)?;
270 }
271 }
272 }
273 }
274
275 if let Some(io_threads) = self.io_threads {
276 io_threads.join().into_diagnostic()?;
277 }
278
279 Ok(())
280 }
281
282 pub(crate) fn cancel_background_thread(&mut self) {
284 if let Some((sender, _)) = &self.channels {
285 sender.send(true).ok();
286 }
287 }
288
289 pub(crate) fn handle_internal_messages(&mut self) -> Result<bool> {
291 let mut reset = false;
292 if let Some((_, receiver)) = &self.channels {
293 for im in receiver.try_iter() {
294 match im {
295 InternalMessage::RangeMessage(RangePerDoc { uri, ranges }) => {
296 self.occurrences.insert(uri, ranges);
297 }
298 InternalMessage::OnGoing(token, progress) => {
299 self.send_progress_report(token, progress, None)?;
300 }
301 InternalMessage::Finished(token) => {
302 reset = true;
303 self.send_progress_end(token, Some("Finished.".to_string()))?;
304 }
305 InternalMessage::Cancelled(token) => {
306 reset = true;
307 self.send_progress_end(token, Some("interrupted.".to_string()))?;
308 }
309 }
310 }
311 }
312 if reset {
313 self.channels = None;
314 }
315 Ok(reset)
316 }
317
318 pub(crate) fn new_engine_state(&self, uri: Option<&Uri>) -> EngineState {
323 let mut engine_state = self.initial_engine_state.clone();
324 match uri {
325 Some(uri) => {
326 let path = uri_to_path(uri);
327 if let Some(path) = path.parent() {
328 engine_state
329 .add_env_var("PWD".into(), Value::test_string(path.to_string_lossy()))
330 };
331 }
332 None => {
333 let cwd =
334 std::env::current_dir().expect("Could not get current working directory.");
335 engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
336 }
337 }
338 if !self.need_parse {
340 engine_state
341 .merge_delta(
342 self.cached_state_delta
343 .to_owned()
344 .expect("Tried to merge a non-existing state delta"),
345 )
346 .expect("Failed to merge state delta");
347 }
348 engine_state
349 }
350
351 fn cache_parsed_block(&mut self, working_set: &mut StateWorkingSet, block: Arc<Block>) {
352 if self.need_parse {
353 working_set.add_block(block);
356 self.cached_state_delta = Some(working_set.delta.clone());
357 self.need_parse = false;
358 }
359 }
360
361 pub(crate) fn parse_and_find<'a>(
362 &mut self,
363 engine_state: &'a mut EngineState,
364 uri: &Uri,
365 pos: Position,
366 ) -> Result<(StateWorkingSet<'a>, Id, Span, Span)> {
367 let (block, file_span, working_set) = self
368 .parse_file(engine_state, uri, false)
369 .ok_or_else(|| miette!("\nFailed to parse current file"))?;
370
371 let docs = match self.docs.lock() {
372 Ok(it) => it,
373 Err(err) => return Err(miette!(err.to_string())),
374 };
375 let file = docs
376 .get_document(uri)
377 .ok_or_else(|| miette!("\nFailed to get document"))?;
378 let location = file.offset_at(pos) as usize + file_span.start;
379 let (id, span) = ast::find_id(&block, &working_set, &location)
380 .ok_or_else(|| miette!("\nFailed to find current name"))?;
381 Ok((working_set, id, span, file_span))
382 }
383
384 pub(crate) fn parse_file<'a>(
385 &mut self,
386 engine_state: &'a mut EngineState,
387 uri: &Uri,
388 need_extra_info: bool,
389 ) -> Option<(Arc<Block>, Span, StateWorkingSet<'a>)> {
390 let mut working_set = StateWorkingSet::new(engine_state);
391 let docs = self.docs.lock().ok()?;
392 let file = docs.get_document(uri)?;
393 let file_path = uri_to_path(uri);
394 let file_path_str = file_path.to_str()?;
395 let contents = file.get_content(None).as_bytes();
396 let _ = working_set.files.push(file_path.clone(), Span::unknown());
397 let block = nu_parser::parse(&mut working_set, Some(file_path_str), contents, false);
398 let span = working_set.get_span_for_filename(file_path_str)?;
399 if need_extra_info {
400 let file_inlay_hints =
401 Self::extract_inlay_hints(&working_set, &block, span.start, file);
402 self.inlay_hints.insert(uri.clone(), file_inlay_hints);
403 let file_semantic_tokens =
404 Self::extract_semantic_tokens(&working_set, &block, span.start, file);
405 self.semantic_tokens
406 .insert(uri.clone(), file_semantic_tokens);
407 }
408 drop(docs);
409 self.cache_parsed_block(&mut working_set, block.clone());
410 Some((block, span, working_set))
411 }
412
413 fn handle_lsp_request<P, H, R>(req: lsp_server::Request, mut param_handler: H) -> Response
414 where
415 P: serde::de::DeserializeOwned,
416 H: FnMut(&P) -> Option<R>,
417 R: serde::ser::Serialize,
418 {
419 match serde_json::from_value::<P>(req.params) {
420 Ok(params) => Response {
421 id: req.id,
422 result: Some(
423 param_handler(¶ms)
424 .and_then(|response| serde_json::to_value(response).ok())
425 .unwrap_or(serde_json::Value::Null),
426 ),
427 error: None,
428 },
429
430 Err(err) => Response {
431 id: req.id,
432 result: None,
433 error: Some(ResponseError {
434 code: 1,
435 message: err.to_string(),
436 data: None,
437 }),
438 },
439 }
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446 use lsp_types::{
447 notification::{
448 DidChangeTextDocument, DidOpenTextDocument, Exit, Initialized, Notification,
449 },
450 request::{HoverRequest, Initialize, Request, Shutdown},
451 DidChangeTextDocumentParams, DidOpenTextDocumentParams, HoverParams, InitializedParams,
452 TextDocumentContentChangeEvent, TextDocumentIdentifier, TextDocumentItem,
453 TextDocumentPositionParams, WorkDoneProgressParams,
454 };
455 use nu_protocol::{debugger::WithoutDebug, engine::Stack, PipelineData, ShellError, Value};
456 use nu_std::load_standard_library;
457 use std::sync::mpsc::{self, Receiver};
458 use std::time::Duration;
459
460 pub(crate) fn initialize_language_server(
466 nu_config_code: Option<&str>,
467 params: Option<serde_json::Value>,
468 ) -> (Connection, Receiver<Result<()>>) {
469 let engine_state = nu_cmd_lang::create_default_context();
470 let mut engine_state = nu_command::add_shell_command_context(engine_state);
471 engine_state.generate_nu_constant();
472 assert!(load_standard_library(&mut engine_state).is_ok());
473 let cwd = std::env::current_dir().expect("Could not get current working directory.");
474 engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
475 if let Some(code) = nu_config_code {
476 assert!(merge_input(code.as_bytes(), &mut engine_state, &mut Stack::new()).is_ok());
477 }
478
479 let (client_connection, server_connection) = Connection::memory();
480 let lsp_server =
481 LanguageServer::initialize_connection(server_connection, None, engine_state).unwrap();
482
483 let (send, recv) = mpsc::channel();
484 std::thread::spawn(move || send.send(lsp_server.serve_requests()));
485
486 client_connection
487 .sender
488 .send(Message::Request(lsp_server::Request {
489 id: 1.into(),
490 method: Initialize::METHOD.to_string(),
491 params: params.unwrap_or(serde_json::Value::Null),
492 }))
493 .unwrap();
494 client_connection
495 .sender
496 .send(Message::Notification(lsp_server::Notification {
497 method: Initialized::METHOD.to_string(),
498 params: serde_json::to_value(InitializedParams {}).unwrap(),
499 }))
500 .unwrap();
501
502 let _initialize_response = client_connection
503 .receiver
504 .recv_timeout(Duration::from_secs(2))
505 .unwrap();
506
507 (client_connection, recv)
508 }
509
510 fn merge_input(
513 input: &[u8],
514 engine_state: &mut EngineState,
515 stack: &mut Stack,
516 ) -> Result<(), ShellError> {
517 let (block, delta) = {
518 let mut working_set = StateWorkingSet::new(engine_state);
519
520 let block = nu_parser::parse(&mut working_set, None, input, false);
521
522 assert!(working_set.parse_errors.is_empty());
523
524 (block, working_set.render())
525 };
526
527 engine_state.merge_delta(delta)?;
528
529 assert!(nu_engine::eval_block::<WithoutDebug>(
530 engine_state,
531 stack,
532 &block,
533 PipelineData::Value(Value::nothing(Span::unknown()), None),
534 )
535 .is_ok());
536
537 engine_state.merge_env(stack)
539 }
540
541 #[test]
542 fn shutdown_on_request() {
543 let (client_connection, recv) = initialize_language_server(None, None);
544
545 client_connection
546 .sender
547 .send(Message::Request(lsp_server::Request {
548 id: 2.into(),
549 method: Shutdown::METHOD.to_string(),
550 params: serde_json::Value::Null,
551 }))
552 .unwrap();
553 client_connection
554 .sender
555 .send(Message::Notification(lsp_server::Notification {
556 method: Exit::METHOD.to_string(),
557 params: serde_json::Value::Null,
558 }))
559 .unwrap();
560
561 assert!(recv.recv_timeout(Duration::from_secs(2)).unwrap().is_ok());
562 }
563
564 pub(crate) fn open_unchecked(
565 client_connection: &Connection,
566 uri: Uri,
567 ) -> lsp_server::Notification {
568 open(client_connection, uri).unwrap()
569 }
570
571 pub(crate) fn open(
572 client_connection: &Connection,
573 uri: Uri,
574 ) -> Result<lsp_server::Notification, String> {
575 let text = std::fs::read_to_string(uri_to_path(&uri)).map_err(|e| e.to_string())?;
576
577 client_connection
578 .sender
579 .send(Message::Notification(lsp_server::Notification {
580 method: DidOpenTextDocument::METHOD.to_string(),
581 params: serde_json::to_value(DidOpenTextDocumentParams {
582 text_document: TextDocumentItem {
583 uri,
584 language_id: String::from("nu"),
585 version: 1,
586 text,
587 },
588 })
589 .unwrap(),
590 }))
591 .map_err(|e| e.to_string())?;
592
593 let notification = client_connection
594 .receiver
595 .recv_timeout(Duration::from_secs(2))
596 .map_err(|e| e.to_string())?;
597
598 if let Message::Notification(n) = notification {
599 Ok(n)
600 } else {
601 Err(String::from("Did not receive a notification from server"))
602 }
603 }
604
605 pub(crate) fn update(
606 client_connection: &Connection,
607 uri: Uri,
608 text: String,
609 range: Option<Range>,
610 ) -> lsp_server::Notification {
611 client_connection
612 .sender
613 .send(lsp_server::Message::Notification(
614 lsp_server::Notification {
615 method: DidChangeTextDocument::METHOD.to_string(),
616 params: serde_json::to_value(DidChangeTextDocumentParams {
617 text_document: lsp_types::VersionedTextDocumentIdentifier {
618 uri,
619 version: 2,
620 },
621 content_changes: vec![TextDocumentContentChangeEvent {
622 range,
623 range_length: None,
624 text,
625 }],
626 })
627 .unwrap(),
628 },
629 ))
630 .unwrap();
631
632 let notification = client_connection
633 .receiver
634 .recv_timeout(Duration::from_secs(2))
635 .unwrap();
636
637 if let Message::Notification(n) = notification {
638 n
639 } else {
640 panic!();
641 }
642 }
643
644 pub(crate) fn result_from_message(message: lsp_server::Message) -> serde_json::Value {
645 match message {
646 Message::Response(Response { result, .. }) => result.expect("Empty result!"),
647 _ => panic!("Unexpected message type!"),
648 }
649 }
650
651 pub(crate) fn send_hover_request(
652 client_connection: &Connection,
653 uri: Uri,
654 line: u32,
655 character: u32,
656 ) -> Message {
657 client_connection
658 .sender
659 .send(Message::Request(lsp_server::Request {
660 id: 2.into(),
661 method: HoverRequest::METHOD.to_string(),
662 params: serde_json::to_value(HoverParams {
663 text_document_position_params: TextDocumentPositionParams {
664 text_document: TextDocumentIdentifier { uri },
665 position: Position { line, character },
666 },
667 work_done_progress_params: WorkDoneProgressParams::default(),
668 })
669 .unwrap(),
670 }))
671 .unwrap();
672
673 client_connection
674 .receiver
675 .recv_timeout(Duration::from_secs(3))
676 .unwrap()
677 }
678}