1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-05 05:49:20 -05:00
denoland-deno/tests/util/server/src/lsp.rs

1105 lines
30 KiB
Rust
Raw Normal View History

// 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) = &params.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 &params.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(&params).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();
}
}