aether-lspd 0.1.21

LSP daemon for sharing language servers across multiple agents
Documentation
mod common;

use aether_lspd::LanguageId;
use common::{CargoProject, DaemonHarness, TestProject, hover_text, use_fake_rust_server};
use lsp_types::{DocumentSymbolResponse, GotoDefinitionResponse};

#[tokio::test]
async fn request_helpers_round_trip_through_fake_server() {
    use_fake_rust_server();

    let project = CargoProject::new("request_contracts").expect("Failed to create project");
    let content = r#"fn example_fn() -> i32 {
    7
}

fn main() {
    let value = example_fn();
    println!("{}", value);
}
"#;
    project.add_file("src/main.rs", content).expect("Failed to add file");

    let harness = DaemonHarness::spawn(project.root(), LanguageId::Rust).await.expect("Failed to spawn daemon");
    let client = harness.connect().await.expect("Failed to connect");
    let uri = project.file_uri("src/main.rs");

    let hover = hover_text(client.hover(uri.clone(), 0, 0).await.expect("Hover request failed"));
    assert!(hover.contains("open_count=1"));
    assert!(hover.contains("example_fn"));

    let definition = client.goto_definition(uri.clone(), 4, 16).await.expect("Goto definition failed");
    match definition {
        GotoDefinitionResponse::Array(locations) => {
            assert_eq!(locations.len(), 1);
            assert_eq!(locations[0].uri, uri);
            assert_eq!(locations[0].range.start.line, 0);
        }
        other => panic!("Expected array goto definition result, got {other:?}"),
    }

    let implementation = client.goto_implementation(uri.clone(), 4, 16).await.expect("Goto implementation failed");
    match implementation {
        GotoDefinitionResponse::Array(locations) => {
            assert_eq!(locations.len(), 1);
            assert_eq!(locations[0].uri, uri);
            assert_eq!(locations[0].range.start.line, 0);
        }
        other => panic!("Expected array goto implementation result, got {other:?}"),
    }

    let references = client.find_references(uri.clone(), 0, 3, true).await.expect("Find references failed");
    assert_eq!(references.len(), 2);
    assert!(references.iter().all(|location| location.uri == uri));

    let document_symbols = client.document_symbol(uri.clone()).await.expect("Document symbol failed");
    match document_symbols {
        DocumentSymbolResponse::Flat(symbols) => {
            assert!(symbols.iter().any(|symbol| symbol.name == "ExampleStruct"));
            assert!(symbols.iter().any(|symbol| symbol.name == "example_fn"));
        }
        other @ DocumentSymbolResponse::Nested(_) => panic!("Expected flat document symbols, got {other:?}"),
    }

    let workspace_symbols = client.workspace_symbol("example".to_string()).await.expect("Workspace symbol failed");
    assert!(workspace_symbols.iter().any(|symbol| symbol.name == "example_fn"));

    let call_items = client.prepare_call_hierarchy(uri.clone(), 4, 16).await.expect("Prepare call hierarchy failed");
    assert_eq!(call_items.len(), 1);
    assert_eq!(call_items[0].name, "example_fn");

    let incoming = client.incoming_calls(call_items[0].clone()).await.expect("Incoming calls failed");
    assert_eq!(incoming.len(), 1);
    assert_eq!(incoming[0].from.name, "caller_fn");

    let outgoing = client.outgoing_calls(call_items[0].clone()).await.expect("Outgoing calls failed");
    assert_eq!(outgoing.len(), 1);
    assert_eq!(outgoing[0].to.name, "callee_fn");

    let rename = client
        .rename(uri.clone(), 0, 3, "renamed_fn".to_string())
        .await
        .expect("Rename failed")
        .expect("Rename should return a workspace edit");
    #[allow(clippy::mutable_key_type)]
    let changes = rename.changes.expect("Rename should contain changes");
    let edits = changes.get(&uri).expect("Rename changes should include the file");
    assert_eq!(edits.len(), 1);
    assert_eq!(edits[0].new_text, "renamed_fn");

    harness.kill().await.expect("Failed to kill daemon");
}

#[tokio::test]
async fn diagnostic_helpers_round_trip_through_fake_server() {
    use_fake_rust_server();

    let project = CargoProject::new("request_diagnostics").expect("Failed to create project");
    project.add_file("src/main.rs", "fn main() { let error = 1; }\n").expect("Failed to add file");

    let harness = DaemonHarness::spawn(project.root(), LanguageId::Rust).await.expect("Failed to spawn daemon");
    let client = harness.connect().await.expect("Failed to connect");
    let uri = project.file_uri("src/main.rs");

    client.queue_diagnostic_refresh(uri.clone()).await.expect("Queue diagnostic refresh failed");

    let diagnostics = client.get_diagnostics(None).await.expect("Get diagnostics failed");
    assert_eq!(diagnostics.len(), 1);
    assert_eq!(diagnostics[0].uri, uri);
    assert_eq!(diagnostics[0].diagnostics.len(), 1);
    assert_eq!(diagnostics[0].diagnostics[0].message, "error token");

    harness.kill().await.expect("Failed to kill daemon");
}