diff --git a/crates/nu-lsp/src/configuration.rs b/crates/nu-lsp/src/configuration.rs new file mode 100644 index 0000000000..1ef1f46371 --- /dev/null +++ b/crates/nu-lsp/src/configuration.rs @@ -0,0 +1,77 @@ +use crate::LanguageServer; +use nu_cli::eval_config_contents; +use nu_protocol::engine::EngineState; +use serde::Deserialize; +use std::path::PathBuf; + +pub(crate) const CONFIG_REQUEST_ID: &'static str = "config"; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct NuConfiguration { + include_paths: Vec, +} + +impl LanguageServer { + pub(crate) fn configure_engine(engine_state: &mut EngineState, config: serde_json::Value) { + let Ok(config) = serde_json::from_value::(config) else { + // TODO: warn the client? Whet does the spec recommend? + return; + }; + + for config_path in config.include_paths { + let mut stack = nu_protocol::engine::Stack::new(); + eval_config_contents(config_path, engine_state, &mut stack); + } + } +} + +#[cfg(test)] +mod tests { + use crate::tests::{complete, initialize_language_server, open_unchecked}; + use assert_json_diff::assert_json_include; + use lsp_server::Message; + use lsp_types::Url; + use nu_test_support::fs::fixtures; + + #[test] + fn complete_with_include_paths() { + let mut include_path = fixtures(); + include_path.push("formats"); + include_path.push("sample_def.nu"); + + let (client_connection, _recv) = initialize_language_server(Some(serde_json::json!({ + "includePaths": [ + include_path + ] + }))); + + let mut script = fixtures(); + script.push("lsp"); + script.push("completion"); + script.push("include.nu"); + let script = Url::from_file_path(script).unwrap(); + + open_unchecked(&client_connection, script.clone()); + + let Message::Response(resp) = complete(&client_connection, script, 0, 3) else { + panic!() + }; + + assert_json_include!( + actual: resp.result, + expected: serde_json::json!([ + { + "label": "greet", + "textEdit": { + "newText": "greet", + "range": { + "start": { "character": 0, "line": 0 }, + "end": { "character": 3, "line": 0 } + } + } + } + ]) + ); + } +} diff --git a/crates/nu-lsp/src/diagnostics.rs b/crates/nu-lsp/src/diagnostics.rs index 423fffbf6c..6da6093aa8 100644 --- a/crates/nu-lsp/src/diagnostics.rs +++ b/crates/nu-lsp/src/diagnostics.rs @@ -71,7 +71,7 @@ mod tests { #[test] fn publish_diagnostics_variable_does_not_exists() { - let (client_connection, _recv) = initialize_language_server(); + let (client_connection, _recv) = initialize_language_server(None); let mut script = fixtures(); script.push("lsp"); @@ -102,7 +102,7 @@ mod tests { #[test] fn publish_diagnostics_fixed_unknown_variable() { - let (client_connection, _recv) = initialize_language_server(); + let (client_connection, _recv) = initialize_language_server(None); let mut script = fixtures(); script.push("lsp"); diff --git a/crates/nu-lsp/src/lib.rs b/crates/nu-lsp/src/lib.rs index 44eeeb5756..028f734e5d 100644 --- a/crates/nu-lsp/src/lib.rs +++ b/crates/nu-lsp/src/lib.rs @@ -1,10 +1,10 @@ -use lsp_server::{Connection, IoThreads, Message, Response, ResponseError}; +use lsp_server::{Connection, IoThreads, Message, RequestId, Response, ResponseError}; use lsp_types::{ - request::{Completion, GotoDefinition, HoverRequest, Request}, + request::{Completion, GotoDefinition, HoverRequest, Request, WorkspaceConfiguration}, CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse, CompletionTextEdit, - GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams, Location, - MarkupContent, MarkupKind, OneOf, Range, ServerCapabilities, TextDocumentSyncKind, TextEdit, - Url, + GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams, + InitializeParams, Location, MarkupContent, MarkupKind, OneOf, Range, ServerCapabilities, + TextDocumentSyncKind, TextEdit, Url, }; use miette::{IntoDiagnostic, Result}; use nu_cli::{NuCompleter, SuggestionKind}; @@ -24,6 +24,7 @@ use std::{ time::Duration, }; +mod configuration; mod diagnostics; mod notification; @@ -73,11 +74,33 @@ impl LanguageServer { }) .expect("Must be serializable"); - let _initialization_params = self - .connection - .initialize_while(server_capabilities, || !ctrlc.load(Ordering::SeqCst)) - .into_diagnostic()?; + let initialize_params: InitializeParams = serde_json::from_value( + self.connection + .initialize_while(server_capabilities, || !ctrlc.load(Ordering::SeqCst)) + .into_diagnostic()?, + ) + .into_diagnostic()?; + if initialize_params + .capabilities + .workspace + .as_ref() + .and_then(|workspace| workspace.configuration) + .unwrap_or(false) + { + self.connection + .sender + .send(lsp_server::Message::Request(lsp_server::Request { + id: RequestId::from(configuration::CONFIG_REQUEST_ID.to_string()), + method: WorkspaceConfiguration::METHOD.to_string(), + params: serde_json::json!({ + "items": [{ "section": "nu" }] + }), + })) + .into_diagnostic()?; + } + + let mut engine_state = engine_state; while !ctrlc.load(Ordering::SeqCst) { let msg = match self .connection @@ -128,7 +151,14 @@ impl LanguageServer { .send(Message::Response(resp)) .into_diagnostic()?; } - Message::Response(_) => {} + Message::Response(response) => { + if response.id == RequestId::from(configuration::CONFIG_REQUEST_ID.to_string()) + { + if let Some(config) = response.result { + Self::configure_engine(&mut engine_state, config) + } + } + } Message::Notification(notification) => { if let Some(updated_file) = self.handle_lsp_notification(notification) { let mut engine_state = engine_state.clone(); @@ -622,7 +652,9 @@ mod tests { use nu_test_support::fs::{fixtures, root}; use std::sync::mpsc::Receiver; - pub fn initialize_language_server() -> (Connection, Receiver>) { + pub fn initialize_language_server( + config: Option, + ) -> (Connection, Receiver>) { use std::sync::mpsc; let (client_connection, server_connection) = Connection::memory(); let lsp_server = LanguageServer::initialize_connection(server_connection, None).unwrap(); @@ -630,7 +662,14 @@ mod tests { let (send, recv) = mpsc::channel(); std::thread::spawn(move || { let engine_state = nu_cmd_lang::create_default_context(); - let engine_state = nu_command::add_shell_command_context(engine_state); + let mut engine_state = nu_command::add_shell_command_context(engine_state); + + let cwd = std::env::current_dir().expect("Could not get current working directory."); + engine_state.add_env_var( + "PWD".into(), + nu_protocol::Value::test_string(cwd.to_string_lossy()), + ); + send.send(lsp_server.serve_requests(engine_state, Arc::new(AtomicBool::new(false)))) }); @@ -640,6 +679,13 @@ mod tests { id: 1.into(), method: Initialize::METHOD.to_string(), params: serde_json::to_value(InitializeParams { + capabilities: lsp_types::ClientCapabilities { + workspace: Some(lsp_types::WorkspaceClientCapabilities { + configuration: Some(true), + ..Default::default() + }), + ..Default::default() + }, ..Default::default() }) .unwrap(), @@ -658,12 +704,34 @@ mod tests { .recv_timeout(std::time::Duration::from_secs(2)) .unwrap(); + let configuration_response = client_connection + .receiver + .recv_timeout(std::time::Duration::from_secs(2)) + .unwrap(); + let Message::Request(request) = configuration_response else { + panic!() + }; + + assert_eq!(request.method, WorkspaceConfiguration::METHOD); + + client_connection + .sender + .send_timeout( + Message::Response(lsp_server::Response { + id: request.id, + result: config, + error: None, + }), + std::time::Duration::from_secs(2), + ) + .unwrap(); + (client_connection, recv) } #[test] fn shutdown_on_request() { - let (client_connection, recv) = initialize_language_server(); + let (client_connection, recv) = initialize_language_server(None); client_connection .sender @@ -689,7 +757,7 @@ mod tests { #[test] fn goto_definition_for_none_existing_file() { - let (client_connection, _recv) = initialize_language_server(); + let (client_connection, _recv) = initialize_language_server(None); let mut none_existent_path = root(); none_existent_path.push("none-existent.nu"); @@ -838,7 +906,7 @@ mod tests { #[test] fn goto_definition_of_variable() { - let (client_connection, _recv) = initialize_language_server(); + let (client_connection, _recv) = initialize_language_server(None); let mut script = fixtures(); script.push("lsp"); @@ -869,7 +937,7 @@ mod tests { #[test] fn goto_definition_of_command() { - let (client_connection, _recv) = initialize_language_server(); + let (client_connection, _recv) = initialize_language_server(None); let mut script = fixtures(); script.push("lsp"); @@ -900,7 +968,7 @@ mod tests { #[test] fn goto_definition_of_command_parameter() { - let (client_connection, _recv) = initialize_language_server(); + let (client_connection, _recv) = initialize_language_server(None); let mut script = fixtures(); script.push("lsp"); @@ -954,7 +1022,7 @@ mod tests { #[test] fn hover_on_variable() { - let (client_connection, _recv) = initialize_language_server(); + let (client_connection, _recv) = initialize_language_server(None); let mut script = fixtures(); script.push("lsp"); @@ -981,7 +1049,7 @@ mod tests { #[test] fn hover_on_custom_command() { - let (client_connection, _recv) = initialize_language_server(); + let (client_connection, _recv) = initialize_language_server(None); let mut script = fixtures(); script.push("lsp"); @@ -1011,7 +1079,7 @@ mod tests { #[test] fn hover_on_str_join() { - let (client_connection, _recv) = initialize_language_server(); + let (client_connection, _recv) = initialize_language_server(None); let mut script = fixtures(); script.push("lsp"); @@ -1039,7 +1107,12 @@ mod tests { ); } - fn complete(client_connection: &Connection, uri: Url, line: u32, character: u32) -> Message { + pub(crate) fn complete( + client_connection: &Connection, + uri: Url, + line: u32, + character: u32, + ) -> Message { client_connection .sender .send(Message::Request(lsp_server::Request { @@ -1066,7 +1139,7 @@ mod tests { #[test] fn complete_on_variable() { - let (client_connection, _recv) = initialize_language_server(); + let (client_connection, _recv) = initialize_language_server(None); let mut script = fixtures(); script.push("lsp"); @@ -1103,7 +1176,7 @@ mod tests { #[test] fn complete_command_with_space() { - let (client_connection, _recv) = initialize_language_server(); + let (client_connection, _recv) = initialize_language_server(None); let mut script = fixtures(); script.push("lsp"); @@ -1141,7 +1214,7 @@ mod tests { #[test] fn complete_command_with_utf_line() { - let (client_connection, _recv) = initialize_language_server(); + let (client_connection, _recv) = initialize_language_server(None); let mut script = fixtures(); script.push("lsp"); @@ -1179,7 +1252,7 @@ mod tests { #[test] fn complete_keyword() { - let (client_connection, _recv) = initialize_language_server(); + let (client_connection, _recv) = initialize_language_server(None); let mut script = fixtures(); script.push("lsp"); diff --git a/crates/nu-lsp/src/notification.rs b/crates/nu-lsp/src/notification.rs index a715a67d4f..6d22e41c8b 100644 --- a/crates/nu-lsp/src/notification.rs +++ b/crates/nu-lsp/src/notification.rs @@ -99,7 +99,7 @@ mod tests { #[test] fn hover_correct_documentation_on_let() { - let (client_connection, _recv) = initialize_language_server(); + let (client_connection, _recv) = initialize_language_server(None); let mut script = fixtures(); script.push("lsp"); @@ -129,7 +129,7 @@ mod tests { #[test] fn hover_on_command_after_full_content_change() { - let (client_connection, _recv) = initialize_language_server(); + let (client_connection, _recv) = initialize_language_server(None); let mut script = fixtures(); script.push("lsp"); @@ -170,7 +170,7 @@ hello"#, #[test] fn hover_on_command_after_partial_content_change() { - let (client_connection, _recv) = initialize_language_server(); + let (client_connection, _recv) = initialize_language_server(None); let mut script = fixtures(); script.push("lsp"); @@ -215,7 +215,7 @@ hello"#, #[test] fn open_document_with_utf_char() { - let (client_connection, _recv) = initialize_language_server(); + let (client_connection, _recv) = initialize_language_server(None); let mut script = fixtures(); script.push("lsp"); diff --git a/tests/fixtures/lsp/completion/include.nu b/tests/fixtures/lsp/completion/include.nu new file mode 100644 index 0000000000..a601882bb1 --- /dev/null +++ b/tests/fixtures/lsp/completion/include.nu @@ -0,0 +1 @@ +gre