1#![doc = include_str!("../README.md")]
2use lsp_server::{Connection, IoThreads, Message, Response, ResponseError};
3use lsp_textdocument::{FullTextDocument, TextDocuments};
4use lsp_types::{
5 InlayHint, MessageType, OneOf, Position, Range, ReferencesOptions, RenameOptions,
6 SemanticToken, SemanticTokenType, SemanticTokensLegend, SemanticTokensOptions,
7 SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelpOptions,
8 TextDocumentSyncKind, Uri, WorkDoneProgressOptions, WorkspaceFolder,
9 WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
10 request::{self, Request},
11};
12use miette::{IntoDiagnostic, Result, miette};
13use nu_protocol::{
14 DeclId, ModuleId, Span, Type, Value, VarId,
15 ast::{Block, PathMember},
16 engine::{EngineState, StateDelta, StateWorkingSet},
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 if self.channels.is_some() {
238 self.send_error_message(
239 request.id.clone(),
240 3,
241 "Please wait for renaming preparation to complete.".into(),
242 )?;
243 continue;
244 }
245 Self::handle_lsp_request(request, |params| self.rename(params))
246 }
247 request::SemanticTokensFullRequest::METHOD => {
248 Self::handle_lsp_request(request, |params| {
249 self.get_semantic_tokens(params)
250 })
251 }
252 request::SignatureHelpRequest::METHOD => {
253 Self::handle_lsp_request(request, |params| {
254 self.get_signature_help(params)
255 })
256 }
257 request::WorkspaceSymbolRequest::METHOD => {
258 Self::handle_lsp_request(request, |params| {
259 self.workspace_symbol(params)
260 })
261 }
262 _ => {
263 continue;
264 }
265 };
266
267 self.connection
268 .sender
269 .send(Message::Response(resp))
270 .into_diagnostic()?;
271 }
272 Message::Response(_) => {}
273 Message::Notification(notification) => {
274 if let Some(updated_file) = self.handle_lsp_notification(notification) {
275 self.need_parse = true;
276 self.symbol_cache.mark_dirty(updated_file.clone(), true);
277 self.publish_diagnostics_for_file(updated_file)?;
278 }
279 }
280 }
281 }
282
283 if let Some(io_threads) = self.io_threads {
284 io_threads.join().into_diagnostic()?;
285 }
286
287 Ok(())
288 }
289
290 pub(crate) fn cancel_background_thread(&mut self) {
292 if let Some((sender, _)) = &self.channels {
293 sender.send(true).ok();
294 let _ = self.send_log_message(
295 MessageType::WARNING,
296 "Workspace-wide search took too long!".into(),
297 );
298 }
299 }
300
301 pub(crate) fn handle_internal_messages(&mut self) -> Result<bool> {
303 let mut reset = false;
304 if let Some((_, receiver)) = &self.channels {
305 for im in receiver.try_iter() {
306 match im {
307 InternalMessage::RangeMessage(RangePerDoc { uri, ranges }) => {
308 self.occurrences.insert(uri, ranges);
309 }
310 InternalMessage::OnGoing(token, progress) => {
311 self.send_progress_report(token, progress, None)?;
312 }
313 InternalMessage::Finished(token) => {
314 reset = true;
315 self.send_progress_end(token, Some("Finished.".to_string()))?;
316 }
317 InternalMessage::Cancelled(token) => {
318 reset = true;
319 self.send_progress_end(token, Some("interrupted.".to_string()))?;
320 }
321 }
322 }
323 }
324 if reset {
325 self.channels = None;
326 }
327 Ok(reset)
328 }
329
330 pub(crate) fn new_engine_state(&self, uri: Option<&Uri>) -> EngineState {
335 let mut engine_state = self.initial_engine_state.clone();
336 match uri {
337 Some(uri) => {
338 let path = uri_to_path(uri);
339 if let Some(path) = path.parent() {
340 engine_state
341 .add_env_var("PWD".into(), Value::test_string(path.to_string_lossy()))
342 };
343 }
344 None => {
345 let cwd =
346 std::env::current_dir().expect("Could not get current working directory.");
347 engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
348 }
349 }
350 if !self.need_parse {
352 engine_state
353 .merge_delta(
354 self.cached_state_delta
355 .to_owned()
356 .expect("Tried to merge a non-existing state delta"),
357 )
358 .expect("Failed to merge state delta");
359 }
360 engine_state
361 }
362
363 fn cache_parsed_block(&mut self, working_set: &mut StateWorkingSet, block: Arc<Block>) {
364 if self.need_parse {
365 working_set.add_block(block);
368 self.cached_state_delta = Some(working_set.delta.clone());
369 self.need_parse = false;
370 }
371 }
372
373 pub(crate) fn parse_and_find<'a>(
374 &mut self,
375 engine_state: &'a mut EngineState,
376 uri: &Uri,
377 pos: Position,
378 ) -> Result<(StateWorkingSet<'a>, Id, Span, Span)> {
379 let (block, file_span, working_set) = self
380 .parse_file(engine_state, uri, false)
381 .ok_or_else(|| miette!("\nFailed to parse current file"))?;
382
383 let docs = match self.docs.lock() {
384 Ok(it) => it,
385 Err(err) => return Err(miette!(err.to_string())),
386 };
387 let file = docs
388 .get_document(uri)
389 .ok_or_else(|| miette!("\nFailed to get document"))?;
390 let location = file.offset_at(pos) as usize + file_span.start;
391 let (id, span) = ast::find_id(&block, &working_set, &location)
392 .ok_or_else(|| miette!("\nFailed to find current name"))?;
393 Ok((working_set, id, span, file_span))
394 }
395
396 pub(crate) fn parse_file<'a>(
397 &mut self,
398 engine_state: &'a mut EngineState,
399 uri: &Uri,
400 need_extra_info: bool,
401 ) -> Option<(Arc<Block>, Span, StateWorkingSet<'a>)> {
402 let mut working_set = StateWorkingSet::new(engine_state);
403 let docs = self.docs.lock().ok()?;
404 let file = docs.get_document(uri)?;
405 let file_path = uri_to_path(uri);
406 let file_path_str = file_path.to_str()?;
407 let contents = file.get_content(None).as_bytes();
408 let _ = working_set.files.push(file_path.clone(), Span::unknown());
410 let block = nu_parser::parse(&mut working_set, Some(file_path_str), contents, false);
411 let span = working_set.get_span_for_filename(file_path_str)?;
412 if need_extra_info {
413 let file_inlay_hints =
414 Self::extract_inlay_hints(&working_set, &block, span.start, file);
415 self.inlay_hints.insert(uri.clone(), file_inlay_hints);
416 let file_semantic_tokens =
417 Self::extract_semantic_tokens(&working_set, &block, span.start, file);
418 self.semantic_tokens
419 .insert(uri.clone(), file_semantic_tokens);
420 }
421 drop(docs);
422 self.cache_parsed_block(&mut working_set, block.clone());
423 Some((block, span, working_set))
424 }
425
426 fn handle_lsp_request<P, H, R>(req: lsp_server::Request, mut param_handler: H) -> Response
427 where
428 P: serde::de::DeserializeOwned,
429 H: FnMut(&P) -> Option<R>,
430 R: serde::ser::Serialize,
431 {
432 match serde_json::from_value::<P>(req.params) {
433 Ok(params) => Response {
434 id: req.id,
435 result: Some(
436 param_handler(¶ms)
437 .and_then(|response| serde_json::to_value(response).ok())
438 .unwrap_or(serde_json::Value::Null),
439 ),
440 error: None,
441 },
442
443 Err(err) => Response {
444 id: req.id,
445 result: None,
446 error: Some(ResponseError {
447 code: 1,
448 message: err.to_string(),
449 data: None,
450 }),
451 },
452 }
453 }
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459 use lsp_types::{
460 DidChangeTextDocumentParams, DidOpenTextDocumentParams, HoverParams, InitializedParams,
461 TextDocumentContentChangeEvent, TextDocumentIdentifier, TextDocumentItem,
462 TextDocumentPositionParams, WorkDoneProgressParams,
463 notification::{
464 DidChangeTextDocument, DidOpenTextDocument, Exit, Initialized, Notification,
465 },
466 request::{HoverRequest, Initialize, Request, Shutdown},
467 };
468 use nu_protocol::{PipelineData, ShellError, Value, debugger::WithoutDebug, engine::Stack};
469 use nu_std::load_standard_library;
470 use std::sync::mpsc::{self, Receiver};
471 use std::time::Duration;
472
473 pub(crate) fn initialize_language_server(
479 nu_config_code: Option<&str>,
480 params: Option<serde_json::Value>,
481 ) -> (Connection, Receiver<Result<()>>) {
482 let engine_state = nu_cmd_lang::create_default_context();
483 let mut engine_state = nu_command::add_shell_command_context(engine_state);
484 engine_state.generate_nu_constant();
485 assert!(load_standard_library(&mut engine_state).is_ok());
486 let cwd = std::env::current_dir().expect("Could not get current working directory.");
487 engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
488 if let Some(code) = nu_config_code {
489 assert!(merge_input(code.as_bytes(), &mut engine_state, &mut Stack::new()).is_ok());
490 }
491
492 let (client_connection, server_connection) = Connection::memory();
493 let lsp_server =
494 LanguageServer::initialize_connection(server_connection, None, engine_state).unwrap();
495
496 let (send, recv) = mpsc::channel();
497 std::thread::spawn(move || send.send(lsp_server.serve_requests()));
498
499 client_connection
500 .sender
501 .send(Message::Request(lsp_server::Request {
502 id: 1.into(),
503 method: Initialize::METHOD.to_string(),
504 params: params.unwrap_or(serde_json::Value::Null),
505 }))
506 .unwrap();
507 client_connection
508 .sender
509 .send(Message::Notification(lsp_server::Notification {
510 method: Initialized::METHOD.to_string(),
511 params: serde_json::to_value(InitializedParams {}).unwrap(),
512 }))
513 .unwrap();
514
515 let _initialize_response = client_connection
516 .receiver
517 .recv_timeout(Duration::from_secs(5))
518 .unwrap();
519
520 (client_connection, recv)
521 }
522
523 fn merge_input(
526 input: &[u8],
527 engine_state: &mut EngineState,
528 stack: &mut Stack,
529 ) -> Result<(), ShellError> {
530 let (block, delta) = {
531 let mut working_set = StateWorkingSet::new(engine_state);
532
533 let block = nu_parser::parse(&mut working_set, None, input, false);
534
535 assert!(working_set.parse_errors.is_empty());
536
537 (block, working_set.render())
538 };
539
540 engine_state.merge_delta(delta)?;
541
542 assert!(
543 nu_engine::eval_block::<WithoutDebug>(
544 engine_state,
545 stack,
546 &block,
547 PipelineData::value(Value::nothing(Span::unknown()), None),
548 )
549 .is_ok()
550 );
551
552 engine_state.merge_env(stack)
554 }
555
556 #[test]
557 fn shutdown_on_request() {
558 let (client_connection, recv) = initialize_language_server(None, None);
559
560 client_connection
561 .sender
562 .send(Message::Request(lsp_server::Request {
563 id: 2.into(),
564 method: Shutdown::METHOD.to_string(),
565 params: serde_json::Value::Null,
566 }))
567 .unwrap();
568 client_connection
569 .sender
570 .send(Message::Notification(lsp_server::Notification {
571 method: Exit::METHOD.to_string(),
572 params: serde_json::Value::Null,
573 }))
574 .unwrap();
575
576 assert!(recv.recv_timeout(Duration::from_secs(2)).unwrap().is_ok());
577 }
578
579 pub(crate) fn open_unchecked(
580 client_connection: &Connection,
581 uri: Uri,
582 ) -> lsp_server::Notification {
583 open(client_connection, uri, None).unwrap()
584 }
585
586 pub(crate) fn open(
587 client_connection: &Connection,
588 uri: Uri,
589 new_text: Option<String>,
590 ) -> Result<lsp_server::Notification, String> {
591 let text = new_text
592 .or_else(|| std::fs::read_to_string(uri_to_path(&uri)).ok())
593 .ok_or("Failed to read file.")?;
594
595 client_connection
596 .sender
597 .send(Message::Notification(lsp_server::Notification {
598 method: DidOpenTextDocument::METHOD.to_string(),
599 params: serde_json::to_value(DidOpenTextDocumentParams {
600 text_document: TextDocumentItem {
601 uri,
602 language_id: String::from("nu"),
603 version: 1,
604 text,
605 },
606 })
607 .unwrap(),
608 }))
609 .map_err(|e| e.to_string())?;
610
611 let notification = client_connection
612 .receiver
613 .recv_timeout(Duration::from_secs(2))
614 .map_err(|e| e.to_string())?;
615
616 if let Message::Notification(n) = notification {
617 Ok(n)
618 } else {
619 Err(String::from("Did not receive a notification from server"))
620 }
621 }
622
623 pub(crate) fn update(
624 client_connection: &Connection,
625 uri: Uri,
626 text: String,
627 range: Option<Range>,
628 ) -> lsp_server::Notification {
629 client_connection
630 .sender
631 .send(lsp_server::Message::Notification(
632 lsp_server::Notification {
633 method: DidChangeTextDocument::METHOD.to_string(),
634 params: serde_json::to_value(DidChangeTextDocumentParams {
635 text_document: lsp_types::VersionedTextDocumentIdentifier {
636 uri,
637 version: 2,
638 },
639 content_changes: vec![TextDocumentContentChangeEvent {
640 range,
641 range_length: None,
642 text,
643 }],
644 })
645 .unwrap(),
646 },
647 ))
648 .unwrap();
649
650 let notification = client_connection
651 .receiver
652 .recv_timeout(Duration::from_secs(2))
653 .unwrap();
654
655 if let Message::Notification(n) = notification {
656 n
657 } else {
658 panic!();
659 }
660 }
661
662 pub(crate) fn result_from_message(message: lsp_server::Message) -> serde_json::Value {
663 match message {
664 Message::Response(Response { result, .. }) => result.expect("Empty result!"),
665 _ => panic!("Unexpected message type!"),
666 }
667 }
668
669 pub(crate) fn send_hover_request(
670 client_connection: &Connection,
671 uri: Uri,
672 line: u32,
673 character: u32,
674 ) -> Message {
675 client_connection
676 .sender
677 .send(Message::Request(lsp_server::Request {
678 id: 2.into(),
679 method: HoverRequest::METHOD.to_string(),
680 params: serde_json::to_value(HoverParams {
681 text_document_position_params: TextDocumentPositionParams {
682 text_document: TextDocumentIdentifier { uri },
683 position: Position { line, character },
684 },
685 work_done_progress_params: WorkDoneProgressParams::default(),
686 })
687 .unwrap(),
688 }))
689 .unwrap();
690
691 client_connection
692 .receiver
693 .recv_timeout(Duration::from_secs(3))
694 .unwrap()
695 }
696}