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    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    /// 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                            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    /// Send a cancel message to a running bg thread
291    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    /// Check results from background thread
302    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    /// Create a clone of the initial_engine_state with:
331    ///
332    /// * PWD set to the parent directory of given uri. Fallback to `$env.PWD` if None.
333    /// * `StateDelta` cache merged
334    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        // merge the cached `StateDelta` if text not changed
351        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            // TODO: incremental parsing
366            // add block to working_set for later references
367            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        // For `const foo = path self .`
409        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(&params)
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    /// Initialize the language server for test purposes
474    ///
475    /// # Arguments
476    /// - `nu_config_code`: Optional user defined `config.nu` that is loaded on start
477    /// - `params`: Optional client side capability parameters
478    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    /// merge_input executes the given input into the engine
524    /// and merges the state
525    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        // Merge environment into the permanent state
553        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}