Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support analysis / types in Rust LSP #881

Open
sonnyp opened this issue Feb 11, 2024 · 2 comments · May be fixed by #883
Open

Support analysis / types in Rust LSP #881

sonnyp opened this issue Feb 11, 2024 · 2 comments · May be fixed by #883
Labels

Comments

@sonnyp
Copy link
Contributor

sonnyp commented Feb 11, 2024

In #868 we added rust-analyzer LSP support.

For some reason it only does syntax diagnostics, not types.

@sonnyp sonnyp added the rust label Feb 11, 2024
sonnyp added a commit that referenced this issue Feb 12, 2024
Fixes #520 

Only supports syntax diagnostics for now. I made a follow up for types
#881
@Hofer-Julian
Copy link
Contributor

I've created two small reproducer scripts in Python and JavaScript.
They show the same behavior:

Python

# %%
import json
from subprocess import Popen, PIPE
import re
from typing import Any
import uuid
from pathlib import Path

# Path to the Rust Analyzer executable and the file to analyze
rust_analyzer_path = "rust-analyzer"
file_path = Path("src/main.rs").absolute()


def extract_content_length(response: str) -> int:
    # Split the response into lines
    lines = response.split("\r\n")

    # Find the line that starts with "Content-Length:"
    content_length_line = next(
        (line for line in lines if line.startswith("Content-Length:")), None
    )

    if content_length_line is not None:
        # Extract the number from the line using a regular expression
        match = re.search(r"Content-Length: (\d+)", content_length_line)
        if match:
            return int(match.group(1))
        else:
            raise ValueError("No number found in Content-Length line")
    else:
        raise ValueError("No Content-Length line found")


def send_request(server: Popen, request: dict[str, Any]) -> Any:
    send_notification(server, request)
    return receive_response(server)


def receive_response(server: Popen) -> Any:
    response = ""
    assert server.stdout is not None
    while not response.endswith("\r\n\r\n"):
        response += server.stdout.read(1).decode()
    content_length = extract_content_length(response)
    return json.loads(server.stdout.read(content_length).decode())


def send_notification(server: Popen, request: dict[str, Any]) -> None:
    request_json = json.dumps(request)
    header = f"Content-Length: {len(request_json.encode())}\r\n\r\n"
    assert server.stdin is not None
    server.stdin.write(header.encode())
    server.stdin.write(request_json.encode())
    server.stdin.flush()


# %%
# Start the Rust Analyzer server
server = Popen(
    [rust_analyzer_path],
    stdin=PIPE,
    stdout=PIPE,
)

# %%
# Initialize the LSP client
initialize_params: dict[str, Any] = {
    "processId": None,
    "capabilities": {},
    "initializationOptions": {"checkOnSave": {"enable": False}},
    "rootUri": f"file://{file_path.parent}",
}
initalize_request_id = uuid.uuid4()
initialize_request = {
    "jsonrpc": "2.0",
    "id": str(initalize_request_id),
    "method": "initialize",
    "params": initialize_params,
}

result = send_request(server, initialize_request)
print(result)
# %%
send_notification(server, {"jsonrpc": "2.0", "method": "initialized"})

# # %%
# # Send textDocument/didOpen notification
did_open_params = {
    "textDocument": {
        "uri": f"file://{file_path}",
        "languageId": "rust",
        "version": 1,
        "text": file_path.read_text(),
    }
}
did_open_notification = {
    "jsonrpc": "2.0",
    "method": "textDocument/didOpen",
    "params": did_open_params,
}

send_notification(server, did_open_notification)
# %%
result = receive_response(server)
print(result)

# %%
# Send textDocument/didChange notification
did_change_params = {
    "textDocument": {
        "uri": f"file://{file_path}",
        "version": 2,
    },
    "contentChanges": [
        {
            "text": file_path.read_text(),
        }
    ],
}
did_change_notification = {
    "jsonrpc": "2.0",
    "method": "textDocument/didChange",
    "params": did_change_params,
}

send_notification(server, did_change_notification)

# %%
result = receive_response(server)
print(result)

# %%
exit_request = json.dumps(
    {
        "jsonrpc": "2.0",
        "method": "exit",
    }
)
send_notification(server, exit_request)

server.terminate()

NodeJs

const { spawn } = require('child_process');
const path = require('path');

// Path to the Rust Analyzer executable and the file to analyze
const rustAnalyzerPath = 'rust-analyzer';
const filePath = path.resolve('src/main.rs');

// Start the Rust Analyzer server
const server = spawn(rustAnalyzerPath, [], { stdio: ['pipe', 'pipe', process.stderr] });

// Function to send a JSON-RPC request to the server
function sendRequest(method, params, id) {
    const request = JSON.stringify({ jsonrpc: '2.0', method, params, id });
    const contentLength = Buffer.byteLength(request, 'utf8');
    server.stdin.write(`Content-Length: ${contentLength}\r\n\r\n${request}`);
}

// Function to send a JSON-RPC notification to the server
function sendNotification(method, params) {
    const notification = JSON.stringify({ jsonrpc: '2.0', method, params });
    const contentLength = Buffer.byteLength(notification, 'utf8');
    server.stdin.write(`Content-Length: ${contentLength}\r\n\r\n${notification}`);
}


// Initialize the LSP client
sendRequest('initialize', {
    processId: process.pid,
    rootPath: path.dirname(filePath),
    rootUri: `file://${path.dirname(filePath)}`,
    capabilities: {},
}, 1);

// Send the initialized notification
sendNotification('initialized', {});

// Open the file
sendNotification('textDocument/didOpen', {
    textDocument: {
        uri: `file://${filePath}`,
        languageId: 'rust',
        version: 1,
        text: require('fs').readFileSync(filePath, 'utf8'),
    },
});

// Read the server's responses
server.stdout.on('data', (data) => {
    console.log('Server response:', data.toString());
});

// Keep the Node.js process running
setInterval(() => {}, 1000);

@Hofer-Julian
Copy link
Contributor

This is the communication between vscode and r-a (see https://rust-analyzer.github.io/manual.html#troubleshooting)
vscode-ra-communication.txt

Hofer-Julian added a commit that referenced this issue Feb 14, 2024
Fixes #881
It still takes a bit until diagnostics arrive, but this way error messages from `cargo` are included as well.
@Hofer-Julian Hofer-Julian linked a pull request Feb 14, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants