1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 07:14:47 -05:00

refactor(tests/lsp): consolidate more into test LspClient and reduce verbosity (#18100)

This commit is contained in:
David Sherret 2023-03-09 15:09:03 -05:00 committed by GitHub
parent 2f81e555d8
commit 47012bd931
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 2880 additions and 3854 deletions

View file

@ -1,6 +1,5 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use deno_core::error::AnyError;
use deno_core::serde::Deserialize; use deno_core::serde::Deserialize;
use deno_core::serde_json; use deno_core::serde_json;
use deno_core::serde_json::json; use deno_core::serde_json::json;
@ -10,7 +9,6 @@ use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use std::time::Duration; use std::time::Duration;
use test_util::lsp::LspClientBuilder; use test_util::lsp::LspClientBuilder;
use test_util::lsp::LspResponseError;
use tower_lsp::lsp_types as lsp; use tower_lsp::lsp_types as lsp;
static FIXTURE_CODE_LENS_TS: &str = include_str!("testdata/code_lens.ts"); static FIXTURE_CODE_LENS_TS: &str = include_str!("testdata/code_lens.ts");
@ -41,7 +39,7 @@ struct FixtureMessage {
/// A benchmark that opens a 8000+ line TypeScript document, adds a function to /// A benchmark that opens a 8000+ line TypeScript document, adds a function to
/// the end of the document and does a level of hovering and gets quick fix /// the end of the document and does a level of hovering and gets quick fix
/// code actions. /// code actions.
fn bench_big_file_edits(deno_exe: &Path) -> Result<Duration, AnyError> { fn bench_big_file_edits(deno_exe: &Path) -> Duration {
let mut client = LspClientBuilder::new().deno_exe(deno_exe).build(); let mut client = LspClientBuilder::new().deno_exe(deno_exe).build();
client.initialize_default(); client.initialize_default();
@ -55,9 +53,9 @@ fn bench_big_file_edits(deno_exe: &Path) -> Result<Duration, AnyError> {
"text": FIXTURE_DB_TS "text": FIXTURE_DB_TS
} }
}), }),
)?; );
let (id, method, _): (u64, String, Option<Value>) = client.read_request()?; let (id, method, _): (u64, String, Option<Value>) = client.read_request();
assert_eq!(method, "workspace/configuration"); assert_eq!(method, "workspace/configuration");
client.write_response( client.write_response(
@ -65,58 +63,45 @@ fn bench_big_file_edits(deno_exe: &Path) -> Result<Duration, AnyError> {
json!({ json!({
"enable": true "enable": true
}), }),
)?; );
let (method, _): (String, Option<Value>) = client.read_notification()?; let (method, _): (String, Option<Value>) = client.read_notification();
assert_eq!(method, "textDocument/publishDiagnostics"); assert_eq!(method, "textDocument/publishDiagnostics");
let (method, _): (String, Option<Value>) = client.read_notification()?; let (method, _): (String, Option<Value>) = client.read_notification();
assert_eq!(method, "textDocument/publishDiagnostics"); assert_eq!(method, "textDocument/publishDiagnostics");
let (method, _): (String, Option<Value>) = client.read_notification()?; let (method, _): (String, Option<Value>) = client.read_notification();
assert_eq!(method, "textDocument/publishDiagnostics"); assert_eq!(method, "textDocument/publishDiagnostics");
let messages: Vec<FixtureMessage> = let messages: Vec<FixtureMessage> =
serde_json::from_slice(FIXTURE_DB_MESSAGES)?; serde_json::from_slice(FIXTURE_DB_MESSAGES).unwrap();
for msg in messages { for msg in messages {
match msg.fixture_type { match msg.fixture_type {
FixtureType::Action => { FixtureType::Action => {
client.write_request::<_, _, Value>( client.write_request("textDocument/codeAction", msg.params);
"textDocument/codeAction",
msg.params,
)?;
} }
FixtureType::Change => { FixtureType::Change => {
client.write_notification("textDocument/didChange", msg.params)?; client.write_notification("textDocument/didChange", msg.params);
} }
FixtureType::Completion => { FixtureType::Completion => {
client.write_request::<_, _, Value>( client.write_request("textDocument/completion", msg.params);
"textDocument/completion",
msg.params,
)?;
} }
FixtureType::Highlight => { FixtureType::Highlight => {
client.write_request::<_, _, Value>( client.write_request("textDocument/documentHighlight", msg.params);
"textDocument/documentHighlight",
msg.params,
)?;
} }
FixtureType::Hover => { FixtureType::Hover => {
client client.write_request("textDocument/hover", msg.params);
.write_request::<_, _, Value>("textDocument/hover", msg.params)?;
} }
} }
} }
let (_, response_error): (Option<Value>, Option<LspResponseError>) = client.write_request("shutdown", json!(null));
client.write_request("shutdown", json!(null))?; client.write_notification("exit", json!(null));
assert!(response_error.is_none());
client.write_notification("exit", json!(null))?; client.duration()
Ok(client.duration())
} }
fn bench_code_lens(deno_exe: &Path) -> Result<Duration, AnyError> { fn bench_code_lens(deno_exe: &Path) -> Duration {
let mut client = LspClientBuilder::new().deno_exe(deno_exe).build(); let mut client = LspClientBuilder::new().deno_exe(deno_exe).build();
client.initialize_default(); client.initialize_default();
@ -130,9 +115,9 @@ fn bench_code_lens(deno_exe: &Path) -> Result<Duration, AnyError> {
"text": FIXTURE_CODE_LENS_TS "text": FIXTURE_CODE_LENS_TS
} }
}), }),
)?; );
let (id, method, _): (u64, String, Option<Value>) = client.read_request()?; let (id, method, _): (u64, String, Option<Value>) = client.read_request();
assert_eq!(method, "workspace/configuration"); assert_eq!(method, "workspace/configuration");
client.write_response( client.write_response(
@ -140,42 +125,33 @@ fn bench_code_lens(deno_exe: &Path) -> Result<Duration, AnyError> {
json!({ json!({
"enable": true "enable": true
}), }),
)?; );
let (method, _): (String, Option<Value>) = client.read_notification()?; let (method, _): (String, Option<Value>) = client.read_notification();
assert_eq!(method, "textDocument/publishDiagnostics"); assert_eq!(method, "textDocument/publishDiagnostics");
let (method, _): (String, Option<Value>) = client.read_notification()?; let (method, _): (String, Option<Value>) = client.read_notification();
assert_eq!(method, "textDocument/publishDiagnostics"); assert_eq!(method, "textDocument/publishDiagnostics");
let (method, _): (String, Option<Value>) = client.read_notification()?; let (method, _): (String, Option<Value>) = client.read_notification();
assert_eq!(method, "textDocument/publishDiagnostics"); assert_eq!(method, "textDocument/publishDiagnostics");
let (maybe_res, maybe_err) = client let res = client.write_request_with_res_as::<Vec<lsp::CodeLens>>(
.write_request::<_, _, Vec<lsp::CodeLens>>( "textDocument/codeLens",
"textDocument/codeLens", json!({
json!({ "textDocument": {
"textDocument": { "uri": "file:///testdata/code_lens.ts"
"uri": "file:///testdata/code_lens.ts" }
} }),
}), );
)
.unwrap();
assert!(maybe_err.is_none());
assert!(maybe_res.is_some());
let res = maybe_res.unwrap();
assert!(!res.is_empty()); assert!(!res.is_empty());
for code_lens in res { for code_lens in res {
let (maybe_res, maybe_err) = client client.write_request("codeLens/resolve", code_lens);
.write_request::<_, _, lsp::CodeLens>("codeLens/resolve", code_lens)
.unwrap();
assert!(maybe_err.is_none());
assert!(maybe_res.is_some());
} }
Ok(client.duration()) client.duration()
} }
fn bench_find_replace(deno_exe: &Path) -> Result<Duration, AnyError> { fn bench_find_replace(deno_exe: &Path) -> Duration {
let mut client = LspClientBuilder::new().deno_exe(deno_exe).build(); let mut client = LspClientBuilder::new().deno_exe(deno_exe).build();
client.initialize_default(); client.initialize_default();
@ -190,17 +166,17 @@ fn bench_find_replace(deno_exe: &Path) -> Result<Duration, AnyError> {
"text": "console.log(\"000\");\n" "text": "console.log(\"000\");\n"
} }
}), }),
)?; );
} }
for _ in 0..10 { for _ in 0..10 {
let (id, method, _) = client.read_request::<Value>()?; let (id, method, _) = client.read_request::<Value>();
assert_eq!(method, "workspace/configuration"); assert_eq!(method, "workspace/configuration");
client.write_response(id, json!({ "enable": true }))?; client.write_response(id, json!({ "enable": true }));
} }
for _ in 0..3 { for _ in 0..3 {
let (method, _): (String, Option<Value>) = client.read_notification()?; let (method, _): (String, Option<Value>) = client.read_notification();
assert_eq!(method, "textDocument/publishDiagnostics"); assert_eq!(method, "textDocument/publishDiagnostics");
} }
@ -228,12 +204,12 @@ fn bench_find_replace(deno_exe: &Path) -> Result<Duration, AnyError> {
text: "111".to_string(), text: "111".to_string(),
}], }],
}, },
)?; );
} }
for i in 0..10 { for i in 0..10 {
let file_name = format!("file:///a/file_{i}.ts"); let file_name = format!("file:///a/file_{i}.ts");
let (maybe_res, maybe_err) = client.write_request::<_, _, Value>( client.write_request(
"textDocument/formatting", "textDocument/formatting",
lsp::DocumentFormattingParams { lsp::DocumentFormattingParams {
text_document: lsp::TextDocumentIdentifier { text_document: lsp::TextDocumentIdentifier {
@ -246,27 +222,22 @@ fn bench_find_replace(deno_exe: &Path) -> Result<Duration, AnyError> {
}, },
work_done_progress_params: Default::default(), work_done_progress_params: Default::default(),
}, },
)?; );
assert!(maybe_err.is_none());
assert!(maybe_res.is_some());
} }
for _ in 0..3 { for _ in 0..3 {
let (method, _): (String, Option<Value>) = client.read_notification()?; let (method, _): (String, Option<Value>) = client.read_notification();
assert_eq!(method, "textDocument/publishDiagnostics"); assert_eq!(method, "textDocument/publishDiagnostics");
} }
let (_, response_error): (Option<Value>, Option<LspResponseError>) = client.write_request("shutdown", json!(null));
client.write_request("shutdown", json!(null))?; client.write_notification("exit", json!(null));
assert!(response_error.is_none());
client.write_notification("exit", json!(null))?; client.duration()
Ok(client.duration())
} }
/// A test that starts up the LSP, opens a single line document, and exits. /// A test that starts up the LSP, opens a single line document, and exits.
fn bench_startup_shutdown(deno_exe: &Path) -> Result<Duration, AnyError> { fn bench_startup_shutdown(deno_exe: &Path) -> Duration {
let mut client = LspClientBuilder::new().deno_exe(deno_exe).build(); let mut client = LspClientBuilder::new().deno_exe(deno_exe).build();
client.initialize_default(); client.initialize_default();
@ -280,9 +251,9 @@ fn bench_startup_shutdown(deno_exe: &Path) -> Result<Duration, AnyError> {
"text": "console.log(Deno.args);\n" "text": "console.log(Deno.args);\n"
} }
}), }),
)?; );
let (id, method, _) = client.read_request::<Value>()?; let (id, method, _) = client.read_request::<Value>();
assert_eq!(method, "workspace/configuration"); assert_eq!(method, "workspace/configuration");
client.write_response( client.write_response(
@ -290,33 +261,31 @@ fn bench_startup_shutdown(deno_exe: &Path) -> Result<Duration, AnyError> {
json!({ json!({
"enable": true "enable": true
}), }),
)?; );
let (method, _): (String, Option<Value>) = client.read_notification()?; let (method, _): (String, Option<Value>) = client.read_notification();
assert_eq!(method, "textDocument/publishDiagnostics"); assert_eq!(method, "textDocument/publishDiagnostics");
let (method, _): (String, Option<Value>) = client.read_notification()?; let (method, _): (String, Option<Value>) = client.read_notification();
assert_eq!(method, "textDocument/publishDiagnostics"); assert_eq!(method, "textDocument/publishDiagnostics");
let (method, _): (String, Option<Value>) = client.read_notification()?; let (method, _): (String, Option<Value>) = client.read_notification();
assert_eq!(method, "textDocument/publishDiagnostics"); assert_eq!(method, "textDocument/publishDiagnostics");
let (_, response_error): (Option<Value>, Option<LspResponseError>) = client.write_request("shutdown", json!(null));
client.write_request("shutdown", json!(null))?;
assert!(response_error.is_none());
client.write_notification("exit", json!(null))?; client.write_notification("exit", json!(null));
Ok(client.duration()) client.duration()
} }
/// Generate benchmarks for the LSP server. /// Generate benchmarks for the LSP server.
pub fn benchmarks(deno_exe: &Path) -> Result<HashMap<String, i64>, AnyError> { pub fn benchmarks(deno_exe: &Path) -> HashMap<String, i64> {
println!("-> Start benchmarking lsp"); println!("-> Start benchmarking lsp");
let mut exec_times = HashMap::new(); let mut exec_times = HashMap::new();
println!(" - Simple Startup/Shutdown "); println!(" - Simple Startup/Shutdown ");
let mut times = Vec::new(); let mut times = Vec::new();
for _ in 0..10 { for _ in 0..10 {
times.push(bench_startup_shutdown(deno_exe)?); times.push(bench_startup_shutdown(deno_exe));
} }
let mean = let mean =
(times.iter().sum::<Duration>() / times.len() as u32).as_millis() as i64; (times.iter().sum::<Duration>() / times.len() as u32).as_millis() as i64;
@ -326,7 +295,7 @@ pub fn benchmarks(deno_exe: &Path) -> Result<HashMap<String, i64>, AnyError> {
println!(" - Big Document/Several Edits "); println!(" - Big Document/Several Edits ");
let mut times = Vec::new(); let mut times = Vec::new();
for _ in 0..5 { for _ in 0..5 {
times.push(bench_big_file_edits(deno_exe)?); times.push(bench_big_file_edits(deno_exe));
} }
let mean = let mean =
(times.iter().sum::<Duration>() / times.len() as u32).as_millis() as i64; (times.iter().sum::<Duration>() / times.len() as u32).as_millis() as i64;
@ -336,7 +305,7 @@ pub fn benchmarks(deno_exe: &Path) -> Result<HashMap<String, i64>, AnyError> {
println!(" - Find/Replace"); println!(" - Find/Replace");
let mut times = Vec::new(); let mut times = Vec::new();
for _ in 0..10 { for _ in 0..10 {
times.push(bench_find_replace(deno_exe)?); times.push(bench_find_replace(deno_exe));
} }
let mean = let mean =
(times.iter().sum::<Duration>() / times.len() as u32).as_millis() as i64; (times.iter().sum::<Duration>() / times.len() as u32).as_millis() as i64;
@ -346,7 +315,7 @@ pub fn benchmarks(deno_exe: &Path) -> Result<HashMap<String, i64>, AnyError> {
println!(" - Code Lens"); println!(" - Code Lens");
let mut times = Vec::new(); let mut times = Vec::new();
for _ in 0..10 { for _ in 0..10 {
times.push(bench_code_lens(deno_exe)?); times.push(bench_code_lens(deno_exe));
} }
let mean = let mean =
(times.iter().sum::<Duration>() / times.len() as u32).as_millis() as i64; (times.iter().sum::<Duration>() / times.len() as u32).as_millis() as i64;
@ -355,5 +324,5 @@ pub fn benchmarks(deno_exe: &Path) -> Result<HashMap<String, i64>, AnyError> {
println!("<- End benchmarking lsp"); println!("<- End benchmarking lsp");
Ok(exec_times) exec_times
} }

View file

@ -14,34 +14,29 @@ fn incremental_change_wait(bench: &mut Bencher) {
let mut client = LspClientBuilder::new().build(); let mut client = LspClientBuilder::new().build();
client.initialize_default(); client.initialize_default();
client client.write_notification(
.write_notification( "textDocument/didOpen",
"textDocument/didOpen", json!({
json!({ "textDocument": {
"textDocument": { "uri": "file:///testdata/express-router.js",
"uri": "file:///testdata/express-router.js", "languageId": "javascript",
"languageId": "javascript", "version": 0,
"version": 0, "text": include_str!("testdata/express-router.js")
"text": include_str!("testdata/express-router.js") }
} }),
}), );
)
.unwrap();
let (id, method, _): (u64, String, Option<Value>) = let (id, method, _): (u64, String, Option<Value>) = client.read_request();
client.read_request().unwrap();
assert_eq!(method, "workspace/configuration"); assert_eq!(method, "workspace/configuration");
client client.write_response(
.write_response( id,
id, json!({
json!({ "enable": true
"enable": true }),
}), );
)
.unwrap();
let (method, _maybe_diag): (String, Option<Value>) = let (method, _maybe_diag): (String, Option<Value>) =
client.read_notification().unwrap(); client.read_notification();
assert_eq!(method, "textDocument/publishDiagnostics"); assert_eq!(method, "textDocument/publishDiagnostics");
let mut document_version: u64 = 0; let mut document_version: u64 = 0;
@ -61,7 +56,7 @@ fn incremental_change_wait(bench: &mut Bencher) {
{"text": text, "range":{"start":{"line":509,"character":10},"end":{"line":509,"character":16}}} {"text": text, "range":{"start":{"line":509,"character":10},"end":{"line":509,"character":16}}}
] ]
}) })
).unwrap(); );
wait_for_deno_lint_diagnostic(document_version, &mut client); wait_for_deno_lint_diagnostic(document_version, &mut client);
@ -75,7 +70,7 @@ fn wait_for_deno_lint_diagnostic(
) { ) {
loop { loop {
let (method, maybe_diag): (String, Option<Value>) = let (method, maybe_diag): (String, Option<Value>) =
client.read_notification().unwrap(); client.read_notification();
if method == "textDocument/publishDiagnostics" { if method == "textDocument/publishDiagnostics" {
let d = maybe_diag.unwrap(); let d = maybe_diag.unwrap();
let msg = d.as_object().unwrap(); let msg = d.as_object().unwrap();

View file

@ -472,7 +472,7 @@ async fn main() -> Result<()> {
} }
if benchmarks.contains(&"lsp") { if benchmarks.contains(&"lsp") {
let lsp_exec_times = lsp::benchmarks(&deno_exe)?; let lsp_exec_times = lsp::benchmarks(&deno_exe);
new_data.lsp_exec_time = lsp_exec_times; new_data.lsp_exec_time = lsp_exec_times;
} }

File diff suppressed because it is too large Load diff

View file

@ -10,6 +10,7 @@ use super::TempDir;
use anyhow::Result; use anyhow::Result;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use lsp_types as lsp;
use lsp_types::ClientCapabilities; use lsp_types::ClientCapabilities;
use lsp_types::ClientInfo; use lsp_types::ClientInfo;
use lsp_types::CodeActionCapabilityResolveSupport; use lsp_types::CodeActionCapabilityResolveSupport;
@ -33,6 +34,7 @@ use serde::Serialize;
use serde_json::json; use serde_json::json;
use serde_json::to_value; use serde_json::to_value;
use serde_json::Value; use serde_json::Value;
use std::collections::HashSet;
use std::io; use std::io;
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;
@ -496,6 +498,7 @@ impl LspClientBuilder {
.unwrap_or_else(|| TestContextBuilder::new().build()), .unwrap_or_else(|| TestContextBuilder::new().build()),
writer, writer,
deno_dir, deno_dir,
diagnosable_open_file_count: 0,
}) })
} }
} }
@ -508,6 +511,7 @@ pub struct LspClient {
writer: io::BufWriter<ChildStdin>, writer: io::BufWriter<ChildStdin>,
deno_dir: TempDir, deno_dir: TempDir,
context: TestContext, context: TestContext,
diagnosable_open_file_count: usize,
} }
impl Drop for LspClient { impl Drop for LspClient {
@ -523,58 +527,6 @@ impl Drop for LspClient {
} }
} }
fn notification_result<R>(
method: String,
maybe_params: Option<Value>,
) -> Result<(String, Option<R>)>
where
R: de::DeserializeOwned,
{
let maybe_params = match maybe_params {
Some(params) => {
Some(serde_json::from_value(params.clone()).map_err(|err| {
anyhow::anyhow!(
"Could not deserialize message '{}': {}\n\n{:?}",
method,
err,
params
)
})?)
}
None => None,
};
Ok((method, maybe_params))
}
fn request_result<R>(
id: u64,
method: String,
maybe_params: Option<Value>,
) -> Result<(u64, String, Option<R>)>
where
R: de::DeserializeOwned,
{
let maybe_params = match maybe_params {
Some(params) => Some(serde_json::from_value(params)?),
None => None,
};
Ok((id, method, maybe_params))
}
fn response_result<R>(
maybe_result: Option<Value>,
maybe_error: Option<LspResponseError>,
) -> Result<(Option<R>, Option<LspResponseError>)>
where
R: de::DeserializeOwned,
{
let maybe_result = match maybe_result {
Some(result) => Some(serde_json::from_value(result)?),
None => None,
};
Ok((maybe_result, maybe_error))
}
impl LspClient { impl LspClient {
pub fn deno_dir(&self) -> &TempDir { pub fn deno_dir(&self) -> &TempDir {
&self.deno_dir &self.deno_dir
@ -603,17 +555,67 @@ impl LspClient {
let mut builder = InitializeParamsBuilder::new(); let mut builder = InitializeParamsBuilder::new();
builder.set_root_uri(self.context.deno_dir().uri()); builder.set_root_uri(self.context.deno_dir().uri());
do_build(&mut builder); do_build(&mut builder);
self self.write_request("initialize", builder.build());
.write_request::<_, _, Value>("initialize", builder.build()) self.write_notification("initialized", json!({}));
}
pub fn did_open(&mut self, params: Value) -> CollectedDiagnostics {
self.did_open_with_config(
params,
json!([{
"enable": true,
"codeLens": {
"test": true
}
}]),
)
}
pub fn did_open_with_config(
&mut self,
params: Value,
config: Value,
) -> CollectedDiagnostics {
self.did_open_raw(params);
self.handle_configuration_request(config);
self.read_diagnostics()
}
pub fn did_open_raw(&mut self, params: Value) {
let text_doc = params
.as_object()
.unwrap()
.get("textDocument")
.unwrap()
.as_object()
.unwrap(); .unwrap();
self.write_notification("initialized", json!({})).unwrap(); if matches!(
text_doc.get("languageId").unwrap().as_str().unwrap(),
"typescript" | "javascript"
) {
self.diagnosable_open_file_count += 1;
}
self.write_notification("textDocument/didOpen", params);
}
pub fn handle_configuration_request(&mut self, result: Value) {
let (id, method, _) = self.read_request::<Value>();
assert_eq!(method, "workspace/configuration");
self.write_response(id, result);
}
pub fn read_diagnostics(&mut self) -> CollectedDiagnostics {
let mut all_diagnostics = Vec::new();
for _ in 0..self.diagnosable_open_file_count {
all_diagnostics.extend(read_diagnostics(self).0);
}
CollectedDiagnostics(all_diagnostics)
} }
pub fn shutdown(&mut self) { pub fn shutdown(&mut self) {
self self.write_request("shutdown", json!(null));
.write_request::<_, _, Value>("shutdown", json!(null)) self.write_notification("exit", json!(null));
.unwrap();
self.write_notification("exit", json!(null)).unwrap();
} }
// it's flaky to assert for a notification because a notification // it's flaky to assert for a notification because a notification
@ -626,54 +628,114 @@ impl LspClient {
})) }))
} }
pub fn read_notification<R>(&mut self) -> Result<(String, Option<R>)> pub fn read_notification<R>(&mut self) -> (String, Option<R>)
where where
R: de::DeserializeOwned, R: de::DeserializeOwned,
{ {
self.reader.read_message(|msg| match msg { self.reader.read_message(|msg| match msg {
LspMessage::Notification(method, maybe_params) => Some( LspMessage::Notification(method, maybe_params) => {
notification_result(method.to_owned(), maybe_params.to_owned()), let params = serde_json::from_value(maybe_params.clone()?).ok()?;
), Some((method.to_string(), params))
}
_ => None, _ => None,
}) })
} }
pub fn read_request<R>(&mut self) -> Result<(u64, String, Option<R>)> pub fn read_notification_with_method<R>(
&mut self,
expected_method: &str,
) -> Option<R>
where where
R: de::DeserializeOwned, R: de::DeserializeOwned,
{ {
self.reader.read_message(|msg| match msg { self.reader.read_message(|msg| match msg {
LspMessage::Request(id, method, maybe_params) => Some(request_result( 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, *id,
method.to_owned(), method.to_owned(),
maybe_params.to_owned(), maybe_params
.clone()
.map(|p| serde_json::from_value(p).unwrap()),
)), )),
_ => None, _ => None,
}) })
} }
fn write(&mut self, value: Value) -> Result<()> { fn write(&mut self, value: Value) {
let value_str = value.to_string(); let value_str = value.to_string();
let msg = format!( let msg = format!(
"Content-Length: {}\r\n\r\n{}", "Content-Length: {}\r\n\r\n{}",
value_str.as_bytes().len(), value_str.as_bytes().len(),
value_str value_str
); );
self.writer.write_all(msg.as_bytes())?; self.writer.write_all(msg.as_bytes()).unwrap();
self.writer.flush()?; self.writer.flush().unwrap();
Ok(())
} }
pub fn write_request<S, V, R>( pub fn get_completion(
&mut self, &mut self,
method: S, uri: impl AsRef<str>,
params: V, position: (usize, usize),
) -> Result<(Option<R>, Option<LspResponseError>)> 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 where
S: AsRef<str>,
V: Serialize,
R: de::DeserializeOwned, 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() { let value = if to_value(&params).unwrap().is_null() {
json!({ json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
@ -688,22 +750,22 @@ impl LspClient {
"params": params, "params": params,
}) })
}; };
self.write(value)?; self.write(value);
self.reader.read_message(|msg| match msg { self.reader.read_message(|msg| match msg {
LspMessage::Response(id, maybe_result, maybe_error) => { LspMessage::Response(id, maybe_result, maybe_error) => {
assert_eq!(*id, self.request_id); assert_eq!(*id, self.request_id);
self.request_id += 1; self.request_id += 1;
Some(response_result( if let Some(error) = maybe_error {
maybe_result.to_owned(), panic!("LSP ERROR: {error:?}");
maybe_error.to_owned(), }
)) Some(maybe_result.clone().unwrap())
} }
_ => None, _ => None,
}) })
} }
pub fn write_response<V>(&mut self, id: u64, result: V) -> Result<()> pub fn write_response<V>(&mut self, id: u64, result: V)
where where
V: Serialize, V: Serialize,
{ {
@ -712,10 +774,10 @@ impl LspClient {
"id": id, "id": id,
"result": result "result": result
}); });
self.write(value) self.write(value);
} }
pub fn write_notification<S, V>(&mut self, method: S, params: V) -> Result<()> pub fn write_notification<S, V>(&mut self, method: S, params: V)
where where
S: AsRef<str>, S: AsRef<str>,
V: Serialize, V: Serialize,
@ -725,11 +787,83 @@ impl LspClient {
"method": method.as_ref(), "method": method.as_ref(),
"params": params, "params": params,
}); });
self.write(value)?; self.write(value);
Ok(())
} }
} }
#[derive(Debug, Clone)]
pub struct CollectedDiagnostics(pub Vec<lsp::PublishDiagnosticsParams>);
impl CollectedDiagnostics {
/// Gets the diagnostics that the editor will see after all the publishes.
pub fn viewed(&self) -> Vec<lsp::Diagnostic> {
self
.viewed_messages()
.into_iter()
.flat_map(|m| m.diagnostics)
.collect()
}
/// Gets the messages that the editor will see after all the publishes.
pub fn viewed_messages(&self) -> Vec<lsp::PublishDiagnosticsParams> {
// go over the publishes in reverse order in order to get
// the final messages that will be shown in the editor
let mut messages = Vec::new();
let mut had_specifier = HashSet::new();
for message in self.0.iter().rev() {
if had_specifier.insert(message.uri.clone()) {
messages.insert(0, message.clone());
}
}
messages
}
pub fn with_source(&self, source: &str) -> lsp::PublishDiagnosticsParams {
self
.viewed_messages()
.iter()
.find(|p| {
p.diagnostics
.iter()
.any(|d| d.source == Some(source.to_string()))
})
.map(ToOwned::to_owned)
.unwrap()
}
pub fn with_file_and_source(
&self,
specifier: &str,
source: &str,
) -> lsp::PublishDiagnosticsParams {
let specifier = Url::parse(specifier).unwrap();
self
.viewed_messages()
.iter()
.find(|p| {
p.uri == specifier
&& p
.diagnostics
.iter()
.any(|d| d.source == Some(source.to_string()))
})
.map(ToOwned::to_owned)
.unwrap()
}
}
fn read_diagnostics(client: &mut LspClient) -> CollectedDiagnostics {
// diagnostics come in batches of three unless they're cancelled
let mut diagnostics = vec![];
for _ in 0..3 {
let (method, response) =
client.read_notification::<lsp::PublishDiagnosticsParams>();
assert_eq!(method, "textDocument/publishDiagnostics");
diagnostics.push(response.unwrap());
}
CollectedDiagnostics(diagnostics)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;