#![allow(dead_code)]
use serde_json::{Value, json};
use tower_lsp::lsp_types::Url;
use tokio::io::AsyncWriteExt;
use super::client::{TestClient, frame, read_msg, spawn_server};
use super::fixture::{self, Cursor, Fixture, Range as FixtureRange};
use super::render::{
assert_highlights_match, assert_locations_match, canonicalize_workspace_edit,
collect_navigation_annotations, render_call_hierarchy, render_code_actions, render_code_lens,
render_completion, render_document_symbols, render_folding_ranges, render_hover,
render_inlay_hints, render_locations, render_prepare_call_hierarchy, render_prepare_rename,
render_signature_help, render_type_hierarchy, render_workspace_symbols,
};
fn copy_dir_recursive(src: &std::path::Path, dst: &std::path::Path) -> std::io::Result<()> {
std::fs::create_dir_all(dst)?;
for entry in std::fs::read_dir(src)? {
let entry = entry?;
let ft = entry.file_type()?;
let from = entry.path();
let to = dst.join(entry.file_name());
if ft.is_dir() {
copy_dir_recursive(&from, &to)?;
} else if ft.is_file() {
std::fs::copy(&from, &to)?;
}
}
Ok(())
}
pub struct TestServer {
client: TestClient,
root: Option<std::path::PathBuf>,
_fixture_dir: Option<tempfile::TempDir>,
}
impl TestServer {
pub async fn new() -> Self {
let mut client = spawn_server();
Self::do_initialize(&mut client, None).await;
TestServer {
client,
root: None,
_fixture_dir: None,
}
}
pub async fn with_root(root: impl AsRef<std::path::Path>) -> Self {
let root = root.as_ref().to_path_buf();
let mut client = spawn_server();
Self::do_initialize(&mut client, Some(&root)).await;
TestServer {
client,
root: Some(root),
_fixture_dir: None,
}
}
pub async fn with_fixture(name: &str) -> Self {
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let source = manifest_dir.join("tests/fixtures").join(name);
assert!(
source.is_dir(),
"fixture {name} not found at {} — did you run the fixture acquisition script?",
source.display()
);
let tmp = tempfile::tempdir().expect("create TempDir");
copy_dir_recursive(&source, tmp.path()).expect("copy fixture");
let root = tmp.path().to_path_buf();
let mut client = spawn_server();
Self::do_initialize(&mut client, Some(&root)).await;
TestServer {
client,
root: Some(root),
_fixture_dir: Some(tmp),
}
}
async fn do_initialize(client: &mut TestClient, root: Option<&std::path::Path>) {
Self::do_initialize_with(client, root, json!({ "diagnostics": { "enabled": true } })).await;
}
async fn do_initialize_with(
client: &mut TestClient,
root: Option<&std::path::Path>,
initialization_options: Value,
) -> Value {
let root_uri = root.map(|p| Url::from_file_path(p).unwrap());
let root_val = root_uri
.as_ref()
.map(|u| json!(u.as_str()))
.unwrap_or(json!(null));
let resp = client
.request(
"initialize",
json!({
"processId": null,
"rootUri": root_val,
"capabilities": {
"textDocument": {
"hover": { "contentFormat": ["markdown", "plaintext"] },
"completion": { "completionItem": { "snippetSupport": true } }
}
},
"initializationOptions": initialization_options,
}),
)
.await;
client.notify("initialized", json!({})).await;
resp
}
pub async fn new_with_options(initialization_options: Value) -> (Self, Value) {
let mut client = spawn_server();
let resp = Self::do_initialize_with(&mut client, None, initialization_options).await;
let server = TestServer {
client,
root: None,
_fixture_dir: None,
};
(server, resp)
}
pub async fn with_fixture_and_options(name: &str, initialization_options: Value) -> Self {
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let source = manifest_dir.join("tests/fixtures").join(name);
assert!(
source.is_dir(),
"fixture {name} not found at {}",
source.display()
);
let tmp = tempfile::tempdir().expect("create TempDir");
copy_dir_recursive(&source, tmp.path()).expect("copy fixture");
let root = tmp.path().to_path_buf();
let mut client = spawn_server();
Self::do_initialize_with(&mut client, Some(&root), initialization_options).await;
TestServer {
client,
root: Some(root),
_fixture_dir: Some(tmp),
}
}
pub async fn with_root_and_options(
root: impl AsRef<std::path::Path>,
initialization_options: Value,
) -> Self {
let root = root.as_ref().to_path_buf();
let mut client = spawn_server();
Self::do_initialize_with(&mut client, Some(&root), initialization_options).await;
TestServer {
client,
root: Some(root),
_fixture_dir: None,
}
}
pub fn client(&mut self) -> &mut TestClient {
&mut self.client
}
pub fn uri(&self, path: &str) -> String {
if let Some(root) = &self.root {
let full = root.join(path);
Url::from_file_path(full).unwrap().to_string()
} else {
format!("file:///{path}")
}
}
pub async fn open(&mut self, path: &str, text: &str) -> Value {
let uri = self.uri(path);
self.client
.notify(
"textDocument/didOpen",
json!({
"textDocument": {
"uri": uri,
"languageId": "php",
"version": 1,
"text": text,
}
}),
)
.await;
self.client.wait_for_diagnostics(&uri).await
}
pub async fn change(&mut self, path: &str, version: i32, text: &str) -> Value {
let uri = self.uri(path);
self.client
.notify(
"textDocument/didChange",
json!({
"textDocument": { "uri": uri, "version": version },
"contentChanges": [{ "text": text }],
}),
)
.await;
self.client.wait_for_diagnostics(&uri).await
}
pub async fn wait_for_index_ready(&mut self) -> &mut Self {
self.client.wait_for_index_ready().await;
self
}
pub async fn hover(&mut self, path: &str, line: u32, character: u32) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/hover",
json!({
"textDocument": { "uri": uri },
"position": { "line": line, "character": character },
}),
)
.await
}
pub async fn definition(&mut self, path: &str, line: u32, character: u32) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/definition",
json!({
"textDocument": { "uri": uri },
"position": { "line": line, "character": character },
}),
)
.await
}
pub async fn completion(&mut self, path: &str, line: u32, character: u32) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/completion",
json!({
"textDocument": { "uri": uri },
"position": { "line": line, "character": character },
"context": { "triggerKind": 1 },
}),
)
.await
}
pub async fn references(
&mut self,
path: &str,
line: u32,
character: u32,
include_declaration: bool,
) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/references",
json!({
"textDocument": { "uri": uri },
"position": { "line": line, "character": character },
"context": { "includeDeclaration": include_declaration },
}),
)
.await
}
pub async fn implementation(&mut self, path: &str, line: u32, character: u32) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/implementation",
json!({
"textDocument": { "uri": uri },
"position": { "line": line, "character": character },
}),
)
.await
}
pub async fn type_definition(&mut self, path: &str, line: u32, character: u32) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/typeDefinition",
json!({
"textDocument": { "uri": uri },
"position": { "line": line, "character": character },
}),
)
.await
}
pub async fn document_symbols(&mut self, path: &str) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/documentSymbol",
json!({ "textDocument": { "uri": uri } }),
)
.await
}
pub async fn workspace_symbols(&mut self, query: &str) -> Value {
self.client
.request("workspace/symbol", json!({ "query": query }))
.await
}
pub async fn prepare_call_hierarchy(&mut self, path: &str, line: u32, character: u32) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/prepareCallHierarchy",
json!({
"textDocument": { "uri": uri },
"position": { "line": line, "character": character },
}),
)
.await
}
pub async fn incoming_calls(&mut self, item: Value) -> Value {
self.client
.request("callHierarchy/incomingCalls", json!({ "item": item }))
.await
}
pub async fn prepare_type_hierarchy(&mut self, path: &str, line: u32, character: u32) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/prepareTypeHierarchy",
json!({
"textDocument": { "uri": uri },
"position": { "line": line, "character": character },
}),
)
.await
}
pub async fn supertypes(&mut self, item: Value) -> Value {
self.client
.request("typeHierarchy/supertypes", json!({ "item": item }))
.await
}
pub async fn subtypes(&mut self, item: Value) -> Value {
self.client
.request("typeHierarchy/subtypes", json!({ "item": item }))
.await
}
pub async fn semantic_tokens_full(&mut self, path: &str) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/semanticTokens/full",
json!({ "textDocument": { "uri": uri } }),
)
.await
}
pub async fn semantic_tokens_range(
&mut self,
path: &str,
start_line: u32,
start_char: u32,
end_line: u32,
end_char: u32,
) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/semanticTokens/range",
json!({
"textDocument": { "uri": uri },
"range": {
"start": { "line": start_line, "character": start_char },
"end": { "line": end_line, "character": end_char },
},
}),
)
.await
}
pub async fn semantic_tokens_full_delta(
&mut self,
path: &str,
previous_result_id: &str,
) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/semanticTokens/full/delta",
json!({
"textDocument": { "uri": uri },
"previousResultId": previous_result_id,
}),
)
.await
}
pub async fn outgoing_calls(&mut self, item: Value) -> Value {
self.client
.request("callHierarchy/outgoingCalls", json!({ "item": item }))
.await
}
pub async fn declaration(&mut self, path: &str, line: u32, character: u32) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/declaration",
json!({
"textDocument": { "uri": uri },
"position": { "line": line, "character": character },
}),
)
.await
}
pub async fn signature_help(&mut self, path: &str, line: u32, character: u32) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/signatureHelp",
json!({
"textDocument": { "uri": uri },
"position": { "line": line, "character": character },
}),
)
.await
}
pub async fn document_highlight(&mut self, path: &str, line: u32, character: u32) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/documentHighlight",
json!({
"textDocument": { "uri": uri },
"position": { "line": line, "character": character },
}),
)
.await
}
pub async fn inlay_hints(
&mut self,
path: &str,
start_line: u32,
start_char: u32,
end_line: u32,
end_char: u32,
) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/inlayHint",
json!({
"textDocument": { "uri": uri },
"range": {
"start": { "line": start_line, "character": start_char },
"end": { "line": end_line, "character": end_char },
},
}),
)
.await
}
pub async fn inlay_hint_resolve(&mut self, hint: Value) -> Value {
self.client.request("inlayHint/resolve", hint).await
}
pub async fn workspace_symbol_resolve(&mut self, symbol: Value) -> Value {
self.client.request("workspaceSymbol/resolve", symbol).await
}
pub async fn completion_resolve(&mut self, item: Value) -> Value {
self.client.request("completionItem/resolve", item).await
}
pub async fn rename(&mut self, path: &str, line: u32, character: u32, new_name: &str) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/rename",
json!({
"textDocument": { "uri": uri },
"position": { "line": line, "character": character },
"newName": new_name,
}),
)
.await
}
pub async fn prepare_rename(&mut self, path: &str, line: u32, character: u32) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/prepareRename",
json!({
"textDocument": { "uri": uri },
"position": { "line": line, "character": character },
}),
)
.await
}
pub async fn folding_range(&mut self, path: &str) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/foldingRange",
json!({ "textDocument": { "uri": uri } }),
)
.await
}
pub async fn code_lens(&mut self, path: &str) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/codeLens",
json!({ "textDocument": { "uri": uri } }),
)
.await
}
pub async fn selection_range(&mut self, path: &str, positions: Vec<(u32, u32)>) -> Value {
let uri = self.uri(path);
let positions: Vec<Value> = positions
.into_iter()
.map(|(l, c)| json!({ "line": l, "character": c }))
.collect();
self.client
.request(
"textDocument/selectionRange",
json!({
"textDocument": { "uri": uri },
"positions": positions,
}),
)
.await
}
pub async fn document_link(&mut self, path: &str) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/documentLink",
json!({ "textDocument": { "uri": uri } }),
)
.await
}
pub async fn inline_value(
&mut self,
path: &str,
start_line: u32,
start_char: u32,
end_line: u32,
end_char: u32,
) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/inlineValue",
json!({
"textDocument": { "uri": uri },
"range": {
"start": { "line": start_line, "character": start_char },
"end": { "line": end_line, "character": end_char },
},
"context": {
"frameId": 0,
"stoppedLocation": {
"start": { "line": start_line, "character": start_char },
"end": { "line": end_line, "character": end_char },
},
},
}),
)
.await
}
pub async fn pull_diagnostics(&mut self, path: &str) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/diagnostic",
json!({ "textDocument": { "uri": uri } }),
)
.await
}
pub async fn workspace_diagnostic(&mut self) -> Value {
self.client
.request("workspace/diagnostic", json!({ "previousResultIds": [] }))
.await
}
pub async fn moniker(&mut self, path: &str, line: u32, character: u32) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/moniker",
json!({
"textDocument": { "uri": uri },
"position": { "line": line, "character": character },
}),
)
.await
}
pub async fn linked_editing_range(&mut self, path: &str, line: u32, character: u32) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/linkedEditingRange",
json!({
"textDocument": { "uri": uri },
"position": { "line": line, "character": character },
}),
)
.await
}
pub async fn formatting(&mut self, path: &str) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/formatting",
json!({
"textDocument": { "uri": uri },
"options": { "tabSize": 4, "insertSpaces": true },
}),
)
.await
}
pub async fn range_formatting(
&mut self,
path: &str,
start_line: u32,
start_char: u32,
end_line: u32,
end_char: u32,
) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/rangeFormatting",
json!({
"textDocument": { "uri": uri },
"range": {
"start": { "line": start_line, "character": start_char },
"end": { "line": end_line, "character": end_char },
},
"options": { "tabSize": 4, "insertSpaces": true },
}),
)
.await
}
pub async fn on_type_formatting(
&mut self,
path: &str,
line: u32,
character: u32,
ch: &str,
) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/onTypeFormatting",
json!({
"textDocument": { "uri": uri },
"position": { "line": line, "character": character },
"ch": ch,
"options": { "tabSize": 4, "insertSpaces": true },
}),
)
.await
}
pub async fn code_action(
&mut self,
path: &str,
start_line: u32,
start_char: u32,
end_line: u32,
end_char: u32,
) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/codeAction",
json!({
"textDocument": { "uri": uri },
"range": {
"start": { "line": start_line, "character": start_char },
"end": { "line": end_line, "character": end_char },
},
"context": { "diagnostics": [] },
}),
)
.await
}
pub async fn code_action_at(&mut self, r: &FixtureRange) -> Value {
self.code_action(
&r.path,
r.start_line,
r.start_character,
r.end_line,
r.end_character,
)
.await
}
pub async fn close(&mut self, path: &str) {
let uri = self.uri(path);
self.client
.notify(
"textDocument/didClose",
json!({
"textDocument": { "uri": uri }
}),
)
.await;
}
pub async fn save(&mut self, path: &str) -> Value {
let uri = self.uri(path);
self.client
.notify(
"textDocument/didSave",
json!({
"textDocument": { "uri": uri }
}),
)
.await;
self.client.wait_for_diagnostics(&uri).await
}
pub async fn will_save_wait_until(&mut self, path: &str) -> Value {
let uri = self.uri(path);
self.client
.request(
"textDocument/willSaveWaitUntil",
json!({
"textDocument": { "uri": uri },
"reason": 1
}),
)
.await
}
pub async fn will_rename_files(&mut self, renames: Vec<(String, String)>) -> Value {
let files: Vec<Value> = renames
.into_iter()
.map(|(old, new)| json!({ "oldUri": old, "newUri": new }))
.collect();
self.client
.request("workspace/willRenameFiles", json!({ "files": files }))
.await
}
pub async fn will_create_files(&mut self, uris: Vec<String>) -> Value {
let files: Vec<Value> = uris.into_iter().map(|u| json!({ "uri": u })).collect();
self.client
.request("workspace/willCreateFiles", json!({ "files": files }))
.await
}
pub async fn will_delete_files(&mut self, uris: Vec<String>) -> Value {
let files: Vec<Value> = uris.into_iter().map(|u| json!({ "uri": u })).collect();
self.client
.request("workspace/willDeleteFiles", json!({ "files": files }))
.await
}
pub async fn did_rename_files(&mut self, renames: Vec<(String, String)>) {
let files: Vec<Value> = renames
.into_iter()
.map(|(old, new)| json!({ "oldUri": old, "newUri": new }))
.collect();
self.client
.notify("workspace/didRenameFiles", json!({ "files": files }))
.await;
}
pub async fn did_create_files(&mut self, uris: Vec<String>) {
let files: Vec<Value> = uris.into_iter().map(|u| json!({ "uri": u })).collect();
self.client
.notify("workspace/didCreateFiles", json!({ "files": files }))
.await;
}
pub async fn did_delete_files(&mut self, uris: Vec<String>) -> Vec<Value> {
let cloned = uris.clone();
let files: Vec<Value> = uris.into_iter().map(|u| json!({ "uri": u })).collect();
self.client
.notify("workspace/didDeleteFiles", json!({ "files": files }))
.await;
let mut results = Vec::new();
for uri in &cloned {
results.push(self.client.wait_for_diagnostics(uri).await);
}
results
}
pub async fn add_workspace_folder(&mut self, folder_uri: &str) {
self.client
.notify(
"workspace/didChangeWorkspaceFolders",
json!({
"event": {
"added": [{ "uri": folder_uri, "name": folder_uri }],
"removed": [],
}
}),
)
.await;
}
pub async fn remove_workspace_folder(&mut self, folder_uri: &str) {
self.client
.notify(
"workspace/didChangeWorkspaceFolders",
json!({
"event": {
"added": [],
"removed": [{ "uri": folder_uri, "name": folder_uri }],
}
}),
)
.await;
}
pub async fn did_change_watched_files(&mut self, changes: Vec<(String, u32)>) {
let changes_json: Vec<Value> = changes
.into_iter()
.map(|(uri, typ)| json!({ "uri": uri, "type": typ }))
.collect();
self.client
.notify(
"workspace/didChangeWatchedFiles",
json!({ "changes": changes_json }),
)
.await;
}
pub fn write_file(&self, path: &str, content: &str) {
let full = self.root.as_ref().expect("server has no root").join(path);
if let Some(parent) = full.parent() {
std::fs::create_dir_all(parent).expect("create parent dirs");
}
std::fs::write(&full, content).expect("write file");
}
pub fn remove_file(&self, path: &str) {
let full = self.root.as_ref().expect("server has no root").join(path);
std::fs::remove_file(&full).ok();
}
pub async fn snapshot_workspace_symbols(&mut self, query: &str) -> String {
let resp = self.workspace_symbols(query).await;
super::render::render_workspace_symbols(&resp, &self.uri(""))
}
pub async fn change_configuration(&mut self, value: Value) -> Value {
self.client
.notify(
"workspace/didChangeConfiguration",
json!({ "settings": null }),
)
.await;
let (req_id, _) = self
.client
.expect_server_request("workspace/configuration")
.await;
self.client
.reply_to_server_request(req_id, json!([value]))
.await;
tokio::time::timeout(std::time::Duration::from_secs(5), async {
loop {
let msg = read_msg(&mut self.client.read).await;
if msg.get("method").is_some() {
if let Some(srv_id) = msg.get("id") {
self.client
.write
.write_all(&frame(&json!({
"jsonrpc": "2.0",
"id": srv_id,
"result": null,
})))
.await
.unwrap();
}
if msg["method"] == json!("window/logMessage")
&& msg["params"]["message"]
.as_str()
.unwrap_or("")
.starts_with("php-lsp: using PHP ")
{
return msg;
}
}
}
})
.await
.unwrap_or_else(|_| {
panic!("timed out waiting for 'php-lsp: using PHP' log after change_configuration")
})
}
pub async fn shutdown(&mut self) -> Value {
self.client.request_no_params("shutdown").await
}
pub fn locate(&self, path: &str, needle: &str, occurrence: usize) -> (String, u32, u32) {
let full = match &self.root {
Some(r) => r.join(path),
None => std::path::PathBuf::from("/").join(path),
};
let text = std::fs::read_to_string(&full)
.unwrap_or_else(|e| panic!("read {}: {e}", full.display()));
let mut pos = 0usize;
let mut byte_pos = None;
for _ in 0..=occurrence {
let idx = text[pos..].find(needle).unwrap_or_else(|| {
panic!("needle {needle:?} missing occurrence {occurrence} in {path}")
});
byte_pos = Some(pos + idx);
pos += idx + needle.len();
}
let byte_pos = byte_pos.unwrap();
let before = &text[..byte_pos];
let line = before.bytes().filter(|b| *b == b'\n').count() as u32;
let character = before.rsplit('\n').next().unwrap_or("").chars().count() as u32;
(text, line, character)
}
}
pub struct OpenedFixture {
pub fixture: Fixture,
pub diagnostics: std::collections::HashMap<String, Value>,
}
impl OpenedFixture {
pub fn cursor(&self) -> &Cursor {
self.fixture
.cursor
.as_ref()
.expect("fixture has no $0 cursor marker")
}
pub fn range(&self) -> &FixtureRange {
self.fixture
.range
.as_ref()
.expect("fixture has no $0…$0 range; put two $0 markers to form a selection")
}
pub fn diagnostics_for(&self, path: &str) -> &Value {
self.diagnostics
.get(path)
.unwrap_or_else(|| panic!("no diagnostics recorded for {path}"))
}
}
impl TestServer {
pub async fn open_fixture(&mut self, src: &str) -> OpenedFixture {
let fx = fixture::parse(src);
let mut diagnostics = std::collections::HashMap::new();
for file in &fx.files {
let notif = self.open(&file.path, &file.text).await;
diagnostics.insert(file.path.clone(), notif);
}
OpenedFixture {
fixture: fx,
diagnostics,
}
}
#[track_caller]
pub async fn check_diagnostics(&mut self, src: &str) {
let opened = self.open_fixture(src).await;
for file in &opened.fixture.files {
fixture::assert_diagnostics(opened.diagnostics_for(&file.path), &file.annotations);
}
}
#[track_caller]
pub async fn check_hover(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let resp = self.hover(&c.path, c.line, c.character).await;
render_hover(&resp)
}
#[track_caller]
pub async fn check_completion(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let resp = self.completion(&c.path, c.line, c.character).await;
render_completion(&resp)
}
#[track_caller]
pub async fn check_definition(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let resp = self.definition(&c.path, c.line, c.character).await;
render_locations(&resp, &self.uri(""))
}
#[track_caller]
pub async fn check_references(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let resp = self.references(&c.path, c.line, c.character, true).await;
render_locations(&resp, &self.uri(""))
}
#[track_caller]
pub async fn check_document_symbols(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let path = opened.fixture.files[0].path.clone();
let resp = self.document_symbols(&path).await;
render_document_symbols(&resp)
}
#[track_caller]
pub async fn check_workspace_symbols(&mut self, src: &str, query: &str) -> String {
let _ = self.open_fixture(src).await;
let resp = self.workspace_symbols(query).await;
render_workspace_symbols(&resp, &self.uri(""))
}
#[track_caller]
pub async fn check_signature_help(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let resp = self.signature_help(&c.path, c.line, c.character).await;
render_signature_help(&resp)
}
#[track_caller]
pub async fn check_inlay_hints(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let path = opened.fixture.files[0].path.clone();
let line_count = opened.fixture.files[0].text.lines().count() as u32;
let resp = self.inlay_hints(&path, 0, 0, line_count + 1, 0).await;
render_inlay_hints(&resp)
}
#[track_caller]
pub async fn check_declaration(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let resp = self.declaration(&c.path, c.line, c.character).await;
render_locations(&resp, &self.uri(""))
}
#[track_caller]
pub async fn check_type_definition(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let resp = self.type_definition(&c.path, c.line, c.character).await;
render_locations(&resp, &self.uri(""))
}
#[track_caller]
pub async fn check_implementation(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let resp = self.implementation(&c.path, c.line, c.character).await;
render_locations(&resp, &self.uri(""))
}
#[track_caller]
pub async fn check_code_actions(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let resp = if let Some(r) = opened.fixture.range.clone() {
self.code_action_at(&r).await
} else {
let c = opened.cursor().clone();
self.code_action(&c.path, c.line, c.character, c.line, c.character)
.await
};
render_code_actions(&resp)
}
#[track_caller]
pub async fn check_folding(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let path = opened.fixture.files[0].path.clone();
let resp = self.folding_range(&path).await;
render_folding_ranges(&resp)
}
#[track_caller]
pub async fn check_code_lens(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let path = opened.fixture.files[0].path.clone();
let resp = self.code_lens(&path).await;
render_code_lens(&resp)
}
#[track_caller]
pub async fn check_prepare_type_hierarchy(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let resp = self
.prepare_type_hierarchy(&c.path, c.line, c.character)
.await;
render_type_hierarchy(&resp, &self.uri(""))
}
#[track_caller]
pub async fn check_supertypes(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let prep = self
.prepare_type_hierarchy(&c.path, c.line, c.character)
.await;
let Some(item) = prep["result"].get(0).cloned() else {
return "<no prepared item>".to_owned();
};
if !item.is_object() {
return "<no prepared item>".to_owned();
}
let resp = self.supertypes(item).await;
render_type_hierarchy(&resp, &self.uri(""))
}
#[track_caller]
pub async fn check_subtypes(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let prep = self
.prepare_type_hierarchy(&c.path, c.line, c.character)
.await;
let Some(item) = prep["result"].get(0).cloned() else {
return "<no prepared item>".to_owned();
};
if !item.is_object() {
return "<no prepared item>".to_owned();
}
let resp = self.subtypes(item).await;
render_type_hierarchy(&resp, &self.uri(""))
}
#[track_caller]
pub async fn check_rename(&mut self, src: &str, new_name: &str) -> String {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let resp = self.rename(&c.path, c.line, c.character, new_name).await;
if let Some(err) = resp.get("error").filter(|e| !e.is_null()) {
return format!("error: {err}");
}
canonicalize_workspace_edit(&resp["result"], &self.uri(""))
}
#[track_caller]
pub async fn check_prepare_rename(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let resp = self.prepare_rename(&c.path, c.line, c.character).await;
render_prepare_rename(&resp)
}
#[track_caller]
pub async fn check_references_annotated(&mut self, src: &str) {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let resp = self.references(&c.path, c.line, c.character, true).await;
let expected = collect_navigation_annotations(&opened.fixture, &["def", "ref"]);
assert_locations_match(&resp, &expected, &self.uri(""), "references");
}
#[track_caller]
pub async fn check_definition_annotated(&mut self, src: &str) {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let resp = self.definition(&c.path, c.line, c.character).await;
let expected = collect_navigation_annotations(&opened.fixture, &["def"]);
assert_locations_match(&resp, &expected, &self.uri(""), "definition");
}
#[track_caller]
pub async fn check_highlight_annotated(&mut self, src: &str) {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let resp = self.document_highlight(&c.path, c.line, c.character).await;
let expected = collect_navigation_annotations(&opened.fixture, &["read", "write", "ref"]);
assert_highlights_match(&resp, &expected, &c.path, "document_highlight");
}
#[track_caller]
pub async fn check_prepare_call_hierarchy(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let resp = self
.prepare_call_hierarchy(&c.path, c.line, c.character)
.await;
render_prepare_call_hierarchy(&resp, &self.uri(""))
}
#[track_caller]
pub async fn check_incoming_calls(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let prep = self
.prepare_call_hierarchy(&c.path, c.line, c.character)
.await;
let Some(item) = prep["result"].get(0).cloned() else {
return "<no prepared item>".to_owned();
};
if !item.is_object() {
return "<no prepared item>".to_owned();
}
let resp = self.incoming_calls(item).await;
render_call_hierarchy(&resp, "from", &self.uri(""))
}
#[track_caller]
pub async fn check_outgoing_calls(&mut self, src: &str) -> String {
let opened = self.open_fixture(src).await;
let c = opened.cursor().clone();
let prep = self
.prepare_call_hierarchy(&c.path, c.line, c.character)
.await;
let Some(item) = prep["result"].get(0).cloned() else {
return "<no prepared item>".to_owned();
};
if !item.is_object() {
return "<no prepared item>".to_owned();
}
let resp = self.outgoing_calls(item).await;
render_call_hierarchy(&resp, "to", &self.uri(""))
}
}