nu_lsp/
lib.rs

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    /// for workspace wide requests
61    occurrences: BTreeMap<Uri, Vec<Range>>,
62    channels: Option<(
63        crossbeam_channel::Sender<bool>,
64        Arc<crossbeam_channel::Receiver<InternalMessage>>,
65    )>,
66    /// set to true when text changes
67    need_parse: bool,
68    /// cache `StateDelta` to avoid repeated parsing
69    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                    // NOTE: only internal command names with space supported for now
153                    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            // first check new messages from child thread
175            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                    // cancel execution if other messages received before job done
184                    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    /// Send a cancel message to a running bg thread
283    pub(crate) fn cancel_background_thread(&mut self) {
284        if let Some((sender, _)) = &self.channels {
285            sender.send(true).ok();
286        }
287    }
288
289    /// Check results from background thread
290    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    /// Create a clone of the initial_engine_state with:
319    ///
320    /// * PWD set to the parent directory of given uri. Fallback to `$env.PWD` if None.
321    /// * `StateDelta` cache merged
322    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        // merge the cached `StateDelta` if text not changed
339        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            // TODO: incremental parsing
354            // add block to working_set for later references
355            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(&params)
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    /// Initialize the language server for test purposes
461    ///
462    /// # Arguments
463    /// - `nu_config_code`: Optional user defined `config.nu` that is loaded on start
464    /// - `params`: Optional client side capability parameters
465    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    /// merge_input executes the given input into the engine
511    /// and merges the state
512    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        // Merge environment into the permanent state
538        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}