diff options
Diffstat (limited to 'test_util/src/lsp.rs')
-rw-r--r-- | test_util/src/lsp.rs | 1104 |
1 files changed, 0 insertions, 1104 deletions
diff --git a/test_util/src/lsp.rs b/test_util/src/lsp.rs deleted file mode 100644 index 6b8256fc1..000000000 --- a/test_util/src/lsp.rs +++ /dev/null @@ -1,1104 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use crate::deno_exe_path; -use crate::jsr_registry_url; -use crate::npm_registry_url; -use crate::PathRef; - -use super::TempDir; - -use anyhow::Result; -use lsp_types as lsp; -use lsp_types::ClientCapabilities; -use lsp_types::ClientInfo; -use lsp_types::CodeActionCapabilityResolveSupport; -use lsp_types::CodeActionClientCapabilities; -use lsp_types::CodeActionKindLiteralSupport; -use lsp_types::CodeActionLiteralSupport; -use lsp_types::CompletionClientCapabilities; -use lsp_types::CompletionItemCapability; -use lsp_types::FoldingRangeClientCapabilities; -use lsp_types::InitializeParams; -use lsp_types::TextDocumentClientCapabilities; -use lsp_types::TextDocumentSyncClientCapabilities; -use lsp_types::Url; -use lsp_types::WorkspaceClientCapabilities; -use once_cell::sync::Lazy; -use parking_lot::Condvar; -use parking_lot::Mutex; -use regex::Regex; -use serde::de; -use serde::Deserialize; -use serde::Serialize; -use serde_json::json; -use serde_json::to_value; -use serde_json::Value; -use std::collections::HashSet; -use std::io; -use std::io::BufRead; -use std::io::BufReader; -use std::io::Write; -use std::path::Path; -use std::process::Child; -use std::process::ChildStdin; -use std::process::ChildStdout; -use std::process::Command; -use std::process::Stdio; -use std::sync::mpsc; -use std::sync::Arc; -use std::time::Duration; -use std::time::Instant; - -static CONTENT_TYPE_REG: Lazy<Regex> = - lazy_regex::lazy_regex!(r"(?i)^content-length:\s+(\d+)"); - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct LspResponseError { - code: i32, - message: String, - data: Option<Value>, -} - -#[derive(Clone, Debug)] -pub enum LspMessage { - Notification(String, Option<Value>), - Request(u64, String, Option<Value>), - Response(u64, Option<Value>, Option<LspResponseError>), -} - -impl<'a> From<&'a [u8]> for LspMessage { - fn from(s: &'a [u8]) -> Self { - let value: Value = serde_json::from_slice(s).unwrap(); - let obj = value.as_object().unwrap(); - if obj.contains_key("id") && obj.contains_key("method") { - let id = obj.get("id").unwrap().as_u64().unwrap(); - let method = obj.get("method").unwrap().as_str().unwrap().to_string(); - Self::Request(id, method, obj.get("params").cloned()) - } else if obj.contains_key("id") { - let id = obj.get("id").unwrap().as_u64().unwrap(); - let maybe_error: Option<LspResponseError> = obj - .get("error") - .map(|v| serde_json::from_value(v.clone()).unwrap()); - Self::Response(id, obj.get("result").cloned(), maybe_error) - } else { - assert!(obj.contains_key("method")); - let method = obj.get("method").unwrap().as_str().unwrap().to_string(); - Self::Notification(method, obj.get("params").cloned()) - } - } -} - -#[derive(Debug, Deserialize)] -struct DiagnosticBatchNotificationParams { - batch_index: usize, - messages_len: usize, -} - -fn read_message<R>(reader: &mut R) -> Result<Option<Vec<u8>>> -where - R: io::Read + io::BufRead, -{ - let mut content_length = 0_usize; - loop { - let mut buf = String::new(); - if reader.read_line(&mut buf)? == 0 { - return Ok(None); - } - if let Some(captures) = CONTENT_TYPE_REG.captures(&buf) { - let content_length_match = captures - .get(1) - .ok_or_else(|| anyhow::anyhow!("missing capture"))?; - content_length = content_length_match.as_str().parse::<usize>()?; - } - if &buf == "\r\n" { - break; - } - } - - let mut msg_buf = vec![0_u8; content_length]; - reader.read_exact(&mut msg_buf)?; - Ok(Some(msg_buf)) -} - -struct LspStdoutReader { - pending_messages: Arc<(Mutex<Vec<LspMessage>>, Condvar)>, - read_messages: Vec<LspMessage>, -} - -impl LspStdoutReader { - pub fn new(mut buf_reader: io::BufReader<ChildStdout>) -> Self { - let messages: Arc<(Mutex<Vec<LspMessage>>, Condvar)> = Default::default(); - std::thread::spawn({ - let messages = messages.clone(); - move || { - while let Ok(Some(msg_buf)) = read_message(&mut buf_reader) { - let msg = LspMessage::from(msg_buf.as_slice()); - let cvar = &messages.1; - { - let mut messages = messages.0.lock(); - messages.push(msg); - } - cvar.notify_all(); - } - } - }); - - LspStdoutReader { - pending_messages: messages, - read_messages: Vec::new(), - } - } - - pub fn pending_len(&self) -> usize { - self.pending_messages.0.lock().len() - } - - pub fn output_pending_messages(&self) { - let messages = self.pending_messages.0.lock(); - eprintln!("{:?}", messages); - } - - pub fn had_message(&self, is_match: impl Fn(&LspMessage) -> bool) -> bool { - self.read_messages.iter().any(&is_match) - || self.pending_messages.0.lock().iter().any(&is_match) - } - - pub fn read_message<R>( - &mut self, - mut get_match: impl FnMut(&LspMessage) -> Option<R>, - ) -> R { - let (msg_queue, cvar) = &*self.pending_messages; - let mut msg_queue = msg_queue.lock(); - loop { - for i in 0..msg_queue.len() { - let msg = &msg_queue[i]; - if let Some(result) = get_match(msg) { - let msg = msg_queue.remove(i); - self.read_messages.push(msg); - return result; - } - } - cvar.wait(&mut msg_queue); - } - } - - pub fn read_latest_message<R>( - &mut self, - mut get_match: impl FnMut(&LspMessage) -> Option<R>, - ) -> R { - let (msg_queue, cvar) = &*self.pending_messages; - let mut msg_queue = msg_queue.lock(); - loop { - for i in (0..msg_queue.len()).rev() { - let msg = &msg_queue[i]; - if let Some(result) = get_match(msg) { - let msg = msg_queue.remove(i); - self.read_messages.push(msg); - return result; - } - } - cvar.wait(&mut msg_queue); - } - } -} - -pub struct InitializeParamsBuilder { - params: InitializeParams, -} - -impl InitializeParamsBuilder { - #[allow(clippy::new_without_default)] - pub fn new(config: Value) -> Self { - let mut config_as_options = json!({}); - if let Some(object) = config.as_object() { - if let Some(deno) = object.get("deno") { - if let Some(deno) = deno.as_object() { - config_as_options = json!(deno.clone()); - } - } - let config_as_options = config_as_options.as_object_mut().unwrap(); - if let Some(typescript) = object.get("typescript") { - config_as_options.insert("typescript".to_string(), typescript.clone()); - } - if let Some(javascript) = object.get("javascript") { - config_as_options.insert("javascript".to_string(), javascript.clone()); - } - } - Self { - params: InitializeParams { - process_id: None, - client_info: Some(ClientInfo { - name: "test-harness".to_string(), - version: Some("1.0.0".to_string()), - }), - root_uri: None, - initialization_options: Some(config_as_options), - capabilities: ClientCapabilities { - text_document: Some(TextDocumentClientCapabilities { - code_action: Some(CodeActionClientCapabilities { - code_action_literal_support: Some(CodeActionLiteralSupport { - code_action_kind: CodeActionKindLiteralSupport { - value_set: vec![ - "quickfix".to_string(), - "refactor".to_string(), - ], - }, - }), - is_preferred_support: Some(true), - data_support: Some(true), - disabled_support: Some(true), - resolve_support: Some(CodeActionCapabilityResolveSupport { - properties: vec!["edit".to_string()], - }), - ..Default::default() - }), - completion: Some(CompletionClientCapabilities { - completion_item: Some(CompletionItemCapability { - snippet_support: Some(true), - ..Default::default() - }), - ..Default::default() - }), - folding_range: Some(FoldingRangeClientCapabilities { - line_folding_only: Some(true), - ..Default::default() - }), - synchronization: Some(TextDocumentSyncClientCapabilities { - dynamic_registration: Some(true), - will_save: Some(true), - will_save_wait_until: Some(true), - did_save: Some(true), - }), - ..Default::default() - }), - workspace: Some(WorkspaceClientCapabilities { - configuration: Some(true), - workspace_folders: Some(true), - ..Default::default() - }), - experimental: Some(json!({ - "testingApi": true - })), - ..Default::default() - }, - ..Default::default() - }, - } - } - - pub fn set_maybe_root_uri(&mut self, value: Option<Url>) -> &mut Self { - self.params.root_uri = value; - self - } - - pub fn set_root_uri(&mut self, value: Url) -> &mut Self { - self.set_maybe_root_uri(Some(value)) - } - - pub fn set_workspace_folders( - &mut self, - folders: Vec<lsp_types::WorkspaceFolder>, - ) -> &mut Self { - self.params.workspace_folders = Some(folders); - self - } - - pub fn enable_inlay_hints(&mut self) -> &mut Self { - let options = self.initialization_options_mut(); - options.insert( - "inlayHints".to_string(), - json!({ - "parameterNames": { - "enabled": "all" - }, - "parameterTypes": { - "enabled": true - }, - "variableTypes": { - "enabled": true - }, - "propertyDeclarationTypes": { - "enabled": true - }, - "functionLikeReturnTypes": { - "enabled": true - }, - "enumMemberValues": { - "enabled": true - } - }), - ); - self - } - - pub fn disable_testing_api(&mut self) -> &mut Self { - let obj = self - .params - .capabilities - .experimental - .as_mut() - .unwrap() - .as_object_mut() - .unwrap(); - obj.insert("testingApi".to_string(), false.into()); - let options = self.initialization_options_mut(); - options.remove("testing"); - self - } - - pub fn set_cache(&mut self, value: impl AsRef<str>) -> &mut Self { - let options = self.initialization_options_mut(); - options.insert("cache".to_string(), value.as_ref().to_string().into()); - self - } - - pub fn set_code_lens( - &mut self, - value: Option<serde_json::Value>, - ) -> &mut Self { - let options = self.initialization_options_mut(); - if let Some(value) = value { - options.insert("codeLens".to_string(), value); - } else { - options.remove("codeLens"); - } - self - } - - pub fn set_config(&mut self, value: impl AsRef<str>) -> &mut Self { - let options = self.initialization_options_mut(); - options.insert("config".to_string(), value.as_ref().to_string().into()); - self - } - - pub fn set_disable_paths(&mut self, value: Vec<String>) -> &mut Self { - let options = self.initialization_options_mut(); - options.insert("disablePaths".to_string(), value.into()); - self - } - - pub fn set_enable_paths(&mut self, value: Vec<String>) -> &mut Self { - let options = self.initialization_options_mut(); - options.insert("enablePaths".to_string(), value.into()); - self - } - - pub fn set_deno_enable(&mut self, value: bool) -> &mut Self { - let options = self.initialization_options_mut(); - options.insert("enable".to_string(), value.into()); - self - } - - pub fn set_import_map(&mut self, value: impl AsRef<str>) -> &mut Self { - let options = self.initialization_options_mut(); - options.insert("importMap".to_string(), value.as_ref().to_string().into()); - self - } - - pub fn set_preload_limit(&mut self, arg: usize) -> &mut Self { - let options = self.initialization_options_mut(); - options.insert("documentPreloadLimit".to_string(), arg.into()); - self - } - - pub fn set_tls_certificate(&mut self, value: impl AsRef<str>) -> &mut Self { - let options = self.initialization_options_mut(); - options.insert( - "tlsCertificate".to_string(), - value.as_ref().to_string().into(), - ); - self - } - - pub fn set_unstable(&mut self, value: bool) -> &mut Self { - let options = self.initialization_options_mut(); - options.insert("unstable".to_string(), value.into()); - self - } - - pub fn add_test_server_suggestions(&mut self) -> &mut Self { - self.set_suggest_imports_hosts(vec![( - "http://localhost:4545/".to_string(), - true, - )]) - } - - pub fn set_suggest_imports_hosts( - &mut self, - values: Vec<(String, bool)>, - ) -> &mut Self { - let options = self.initialization_options_mut(); - let suggest = options.get_mut("suggest").unwrap().as_object_mut().unwrap(); - let imports = suggest.get_mut("imports").unwrap().as_object_mut().unwrap(); - let hosts = imports.get_mut("hosts").unwrap().as_object_mut().unwrap(); - hosts.clear(); - for (key, value) in values { - hosts.insert(key, value.into()); - } - self - } - - pub fn with_capabilities( - &mut self, - mut action: impl FnMut(&mut ClientCapabilities), - ) -> &mut Self { - action(&mut self.params.capabilities); - self - } - - fn initialization_options_mut( - &mut self, - ) -> &mut serde_json::Map<String, serde_json::Value> { - let options = self.params.initialization_options.as_mut().unwrap(); - options.as_object_mut().unwrap() - } - - pub fn build(&self) -> InitializeParams { - self.params.clone() - } -} - -pub struct LspClientBuilder { - print_stderr: bool, - capture_stderr: bool, - deno_exe: PathRef, - root_dir: PathRef, - use_diagnostic_sync: bool, - deno_dir: TempDir, -} - -impl LspClientBuilder { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - Self::new_with_dir(TempDir::new()) - } - - pub fn new_with_dir(deno_dir: TempDir) -> Self { - Self { - print_stderr: false, - capture_stderr: false, - deno_exe: deno_exe_path(), - root_dir: deno_dir.path().clone(), - use_diagnostic_sync: true, - deno_dir, - } - } - - pub fn deno_exe(mut self, exe_path: impl AsRef<Path>) -> Self { - self.deno_exe = PathRef::new(exe_path); - self - } - - // not deprecated, this is just here so you don't accidentally - // commit code with this enabled - #[deprecated] - pub fn print_stderr(mut self) -> Self { - self.print_stderr = true; - self - } - - pub fn capture_stderr(mut self) -> Self { - self.capture_stderr = true; - self - } - - /// Whether to use the synchronization messages to better sync diagnostics - /// between the test client and server. - pub fn use_diagnostic_sync(mut self, value: bool) -> Self { - self.use_diagnostic_sync = value; - self - } - - pub fn set_root_dir(mut self, root_dir: PathRef) -> Self { - self.root_dir = root_dir; - self - } - - pub fn build(&self) -> LspClient { - self.build_result().unwrap() - } - - pub fn build_result(&self) -> Result<LspClient> { - let deno_dir = self.deno_dir.clone(); - let mut command = Command::new(&self.deno_exe); - command - .env("DENO_DIR", deno_dir.path()) - .env("NPM_CONFIG_REGISTRY", npm_registry_url()) - .env("JSR_URL", jsr_registry_url()) - // turn on diagnostic synchronization communication - .env( - "DENO_DONT_USE_INTERNAL_LSP_DIAGNOSTIC_SYNC_FLAG", - if self.use_diagnostic_sync { "1" } else { "" }, - ) - .env("DENO_NO_UPDATE_CHECK", "1") - .arg("lsp") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()); - if self.capture_stderr { - command.stderr(Stdio::piped()); - } else if !self.print_stderr { - command.stderr(Stdio::null()); - } - let mut child = command.spawn()?; - let stdout = child.stdout.take().unwrap(); - let buf_reader = io::BufReader::new(stdout); - let reader = LspStdoutReader::new(buf_reader); - - let stdin = child.stdin.take().unwrap(); - let writer = io::BufWriter::new(stdin); - - let stderr_lines_rx = if self.capture_stderr { - let stderr = child.stderr.take().unwrap(); - let print_stderr = self.print_stderr; - let (tx, rx) = mpsc::channel::<String>(); - std::thread::spawn(move || { - let stderr = BufReader::new(stderr); - for line in stderr.lines() { - match line { - Ok(line) => { - if print_stderr { - eprintln!("{}", line); - } - tx.send(line).unwrap(); - } - Err(err) => { - panic!("failed to read line from stderr: {:#}", err); - } - } - } - }); - Some(rx) - } else { - None - }; - - Ok(LspClient { - child, - reader, - request_id: 1, - start: Instant::now(), - root_dir: self.root_dir.clone(), - writer, - deno_dir, - stderr_lines_rx, - config: json!("{}"), - supports_workspace_configuration: false, - }) - } -} - -pub struct LspClient { - child: Child, - reader: LspStdoutReader, - request_id: u64, - start: Instant, - writer: io::BufWriter<ChildStdin>, - deno_dir: TempDir, - root_dir: PathRef, - stderr_lines_rx: Option<mpsc::Receiver<String>>, - config: serde_json::Value, - supports_workspace_configuration: bool, -} - -impl Drop for LspClient { - fn drop(&mut self) { - match self.child.try_wait() { - Ok(None) => { - self.child.kill().unwrap(); - let _ = self.child.wait(); - } - Ok(Some(status)) => panic!("deno lsp exited unexpectedly {status}"), - Err(e) => panic!("pebble error: {e}"), - } - } -} - -impl LspClient { - pub fn deno_dir(&self) -> &TempDir { - &self.deno_dir - } - - pub fn duration(&self) -> Duration { - self.start.elapsed() - } - - pub fn queue_is_empty(&self) -> bool { - self.reader.pending_len() == 0 - } - - pub fn queue_len(&self) -> usize { - self.reader.output_pending_messages(); - self.reader.pending_len() - } - - #[track_caller] - pub fn wait_until_stderr_line(&self, condition: impl Fn(&str) -> bool) { - let timeout_time = - Instant::now().checked_add(Duration::from_secs(5)).unwrap(); - let lines_rx = self - .stderr_lines_rx - .as_ref() - .expect("must setup with client_builder.capture_stderr()"); - let mut found_lines = Vec::new(); - while Instant::now() < timeout_time { - if let Ok(line) = lines_rx.try_recv() { - if condition(&line) { - return; - } - found_lines.push(line); - } - std::thread::sleep(Duration::from_millis(20)); - } - - eprintln!("==== STDERR OUTPUT ===="); - for line in found_lines { - eprintln!("{}", line) - } - eprintln!("== END STDERR OUTPUT =="); - - panic!("Timed out waiting on condition.") - } - - pub fn initialize_default(&mut self) { - self.initialize(|_| {}) - } - - pub fn initialize( - &mut self, - do_build: impl Fn(&mut InitializeParamsBuilder), - ) { - self.initialize_with_config( - do_build, - json!({ "deno": { - "enable": true, - "cache": null, - "certificateStores": null, - "codeLens": { - "implementations": true, - "references": true, - "test": true, - }, - "config": null, - "importMap": null, - "lint": true, - "suggest": { - "autoImports": true, - "completeFunctionCalls": false, - "names": true, - "paths": true, - "imports": { - "hosts": {}, - }, - }, - "testing": { - "args": [ - "--allow-all" - ], - "enable": true, - }, - "tlsCertificate": null, - "unsafelyIgnoreCertificateErrors": null, - "unstable": false, - } }), - ) - } - - pub fn initialize_with_config( - &mut self, - do_build: impl Fn(&mut InitializeParamsBuilder), - mut config: Value, - ) { - let mut builder = InitializeParamsBuilder::new(config.clone()); - builder.set_root_uri(self.root_dir.uri_dir()); - do_build(&mut builder); - let params: InitializeParams = builder.build(); - // `config` must be updated to account for the builder changes. - // TODO(nayeemrmn): Remove config-related methods from builder. - if let Some(options) = ¶ms.initialization_options { - if let Some(options) = options.as_object() { - if let Some(config) = config.as_object_mut() { - let mut deno = options.clone(); - let typescript = options.get("typescript"); - let javascript = options.get("javascript"); - deno.remove("typescript"); - deno.remove("javascript"); - config.insert("deno".to_string(), json!(deno)); - if let Some(typescript) = typescript { - config.insert("typescript".to_string(), typescript.clone()); - } - if let Some(javascript) = javascript { - config.insert("javascript".to_string(), javascript.clone()); - } - } - } - } - self.supports_workspace_configuration = match ¶ms.capabilities.workspace - { - Some(workspace) => workspace.configuration == Some(true), - _ => false, - }; - self.write_request("initialize", params); - self.write_notification("initialized", json!({})); - self.config = config; - if self.supports_workspace_configuration { - self.handle_configuration_request(); - } - } - - pub fn did_open(&mut self, params: Value) -> CollectedDiagnostics { - self.did_open_raw(params); - self.read_diagnostics() - } - - pub fn did_open_raw(&mut self, params: Value) { - self.write_notification("textDocument/didOpen", params); - } - - pub fn change_configuration(&mut self, config: Value) { - self.config = config; - if self.supports_workspace_configuration { - self.write_notification( - "workspace/didChangeConfiguration", - json!({ "settings": {} }), - ); - self.handle_configuration_request(); - } else { - self.write_notification( - "workspace/didChangeConfiguration", - json!({ "settings": &self.config }), - ); - } - } - - pub fn handle_configuration_request(&mut self) { - let (id, method, args) = self.read_request::<Value>(); - assert_eq!(method, "workspace/configuration"); - let params = args.as_ref().unwrap().as_object().unwrap(); - let items = params.get("items").unwrap().as_array().unwrap(); - let config_object = self.config.as_object().unwrap(); - let mut result = vec![]; - for item in items { - let item = item.as_object().unwrap(); - let section = item.get("section").unwrap().as_str().unwrap(); - result.push(config_object.get(section).cloned().unwrap_or_default()); - } - self.write_response(id, result); - } - - pub fn did_save(&mut self, params: Value) { - self.write_notification("textDocument/didSave", params); - } - - pub fn did_change_watched_files(&mut self, params: Value) { - self.write_notification("workspace/didChangeWatchedFiles", params); - } - - fn get_latest_diagnostic_batch_index(&mut self) -> usize { - let result = self - .write_request("deno/internalLatestDiagnosticBatchIndex", json!(null)); - result.as_u64().unwrap() as usize - } - - /// Reads the latest diagnostics. It's assumed that - pub fn read_diagnostics(&mut self) -> CollectedDiagnostics { - // wait for three (deno, lint, and typescript diagnostics) batch - // notification messages for that index - let mut read = 0; - let mut total_messages_len = 0; - while read < 3 { - let (method, response) = - self.read_notification::<DiagnosticBatchNotificationParams>(); - assert_eq!(method, "deno/internalTestDiagnosticBatch"); - let response = response.unwrap(); - if response.batch_index == self.get_latest_diagnostic_batch_index() { - read += 1; - total_messages_len += response.messages_len; - } - } - - // now read the latest diagnostic messages - let mut all_diagnostics = Vec::with_capacity(total_messages_len); - let mut seen_files = HashSet::new(); - for _ in 0..total_messages_len { - let (method, response) = - self.read_latest_notification::<lsp::PublishDiagnosticsParams>(); - assert_eq!(method, "textDocument/publishDiagnostics"); - let response = response.unwrap(); - if seen_files.insert(response.uri.to_string()) { - all_diagnostics.push(response); - } - } - - CollectedDiagnostics(all_diagnostics) - } - - pub fn shutdown(&mut self) { - self.write_request("shutdown", json!(null)); - self.write_notification("exit", json!(null)); - } - - // it's flaky to assert for a notification because a notification - // might arrive a little later, so only provide a method for asserting - // that there is no notification - pub fn assert_no_notification(&mut self, searching_method: &str) { - assert!(!self.reader.had_message(|message| match message { - LspMessage::Notification(method, _) => method == searching_method, - _ => false, - })) - } - - pub fn read_notification<R>(&mut self) -> (String, Option<R>) - where - R: de::DeserializeOwned, - { - self.reader.read_message(|msg| match msg { - LspMessage::Notification(method, maybe_params) => { - let params = serde_json::from_value(maybe_params.clone()?).ok()?; - Some((method.to_string(), params)) - } - _ => None, - }) - } - - pub fn read_latest_notification<R>(&mut self) -> (String, Option<R>) - where - R: de::DeserializeOwned, - { - self.reader.read_latest_message(|msg| match msg { - LspMessage::Notification(method, maybe_params) => { - let params = serde_json::from_value(maybe_params.clone()?).ok()?; - Some((method.to_string(), params)) - } - _ => None, - }) - } - - pub fn read_notification_with_method<R>( - &mut self, - expected_method: &str, - ) -> Option<R> - where - R: de::DeserializeOwned, - { - self.reader.read_message(|msg| match msg { - LspMessage::Notification(method, maybe_params) => { - if method != expected_method { - None - } else { - serde_json::from_value(maybe_params.clone()?).ok() - } - } - _ => None, - }) - } - - pub fn read_request<R>(&mut self) -> (u64, String, Option<R>) - where - R: de::DeserializeOwned, - { - self.reader.read_message(|msg| match msg { - LspMessage::Request(id, method, maybe_params) => Some(( - *id, - method.to_owned(), - maybe_params - .clone() - .map(|p| serde_json::from_value(p).unwrap()), - )), - _ => None, - }) - } - - fn write(&mut self, value: Value) { - let value_str = value.to_string(); - let msg = format!( - "Content-Length: {}\r\n\r\n{}", - value_str.as_bytes().len(), - value_str - ); - self.writer.write_all(msg.as_bytes()).unwrap(); - self.writer.flush().unwrap(); - } - - pub fn get_completion( - &mut self, - uri: impl AsRef<str>, - position: (usize, usize), - context: Value, - ) -> lsp::CompletionResponse { - self.write_request_with_res_as::<lsp::CompletionResponse>( - "textDocument/completion", - json!({ - "textDocument": { - "uri": uri.as_ref(), - }, - "position": { "line": position.0, "character": position.1 }, - "context": context, - }), - ) - } - - pub fn get_completion_list( - &mut self, - uri: impl AsRef<str>, - position: (usize, usize), - context: Value, - ) -> lsp::CompletionList { - let res = self.get_completion(uri, position, context); - if let lsp::CompletionResponse::List(list) = res { - list - } else { - panic!("unexpected response"); - } - } - - pub fn write_request_with_res_as<R>( - &mut self, - method: impl AsRef<str>, - params: impl Serialize, - ) -> R - where - R: de::DeserializeOwned, - { - let result = self.write_request(method, params); - serde_json::from_value(result).unwrap() - } - - pub fn write_request( - &mut self, - method: impl AsRef<str>, - params: impl Serialize, - ) -> Value { - let value = if to_value(¶ms).unwrap().is_null() { - json!({ - "jsonrpc": "2.0", - "id": self.request_id, - "method": method.as_ref(), - }) - } else { - json!({ - "jsonrpc": "2.0", - "id": self.request_id, - "method": method.as_ref(), - "params": params, - }) - }; - self.write(value); - - self.reader.read_message(|msg| match msg { - LspMessage::Response(id, maybe_result, maybe_error) => { - assert_eq!(*id, self.request_id); - self.request_id += 1; - if let Some(error) = maybe_error { - panic!("LSP ERROR: {error:?}"); - } - Some(maybe_result.clone().unwrap()) - } - _ => None, - }) - } - - pub fn write_response<V>(&mut self, id: u64, result: V) - where - V: Serialize, - { - let value = json!({ - "jsonrpc": "2.0", - "id": id, - "result": result - }); - self.write(value); - } - - pub fn write_notification<S, V>(&mut self, method: S, params: V) - where - S: AsRef<str>, - V: Serialize, - { - let value = json!({ - "jsonrpc": "2.0", - "method": method.as_ref(), - "params": params, - }); - self.write(value); - } -} - -#[derive(Debug, Clone)] -pub struct CollectedDiagnostics(Vec<lsp::PublishDiagnosticsParams>); - -impl CollectedDiagnostics { - /// Gets the diagnostics that the editor will see after all the publishes. - pub fn all(&self) -> Vec<lsp::Diagnostic> { - self - .all_messages() - .into_iter() - .flat_map(|m| m.diagnostics) - .collect() - } - - /// Gets the messages that the editor will see after all the publishes. - pub fn all_messages(&self) -> Vec<lsp::PublishDiagnosticsParams> { - self.0.clone() - } - - pub fn messages_with_source( - &self, - source: &str, - ) -> lsp::PublishDiagnosticsParams { - self - .all_messages() - .iter() - .find(|p| { - p.diagnostics - .iter() - .any(|d| d.source == Some(source.to_string())) - }) - .map(ToOwned::to_owned) - .unwrap() - } - - #[track_caller] - pub fn messages_with_file_and_source( - &self, - specifier: &str, - source: &str, - ) -> lsp::PublishDiagnosticsParams { - let specifier = Url::parse(specifier).unwrap(); - self - .all_messages() - .iter() - .find(|p| { - p.uri == specifier - && p - .diagnostics - .iter() - .any(|d| d.source == Some(source.to_string())) - }) - .map(ToOwned::to_owned) - .unwrap() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_read_message() { - let msg1 = b"content-length: 11\r\n\r\nhello world"; - let mut reader1 = std::io::Cursor::new(msg1); - assert_eq!(read_message(&mut reader1).unwrap().unwrap(), b"hello world"); - - let msg2 = b"content-length: 5\r\n\r\nhello world"; - let mut reader2 = std::io::Cursor::new(msg2); - assert_eq!(read_message(&mut reader2).unwrap().unwrap(), b"hello"); - } - - #[test] - #[should_panic(expected = "failed to fill whole buffer")] - fn test_invalid_read_message() { - let msg1 = b"content-length: 12\r\n\r\nhello world"; - let mut reader1 = std::io::Cursor::new(msg1); - read_message(&mut reader1).unwrap(); - } -} |