Arpit Pandey
Arpit Pandey

Reputation: 11

How to get live suggestions from an LSP like pyright and metals without any code editor

I am trying to create a web based code editor which can edit a single python file. Now while editing, i wanted to integrate pyright which is an LSP for python but i am unable to find any documentation which describes how we can use pyright like a language server. All i could find was how to run pyright directly through terminal and get one time suggestions like static analysis, type checking, etc. However, i am looking for full fledged lsp experience where i can send pyright standard JSONRPC calls like textDocument/{onChange,didOpen,didSave,...}, initialize, textDocument/completion, etc. I came across running pyright as pyright-langserver --stdio but i am unable to get it to work at all. Like, how am i supposed to pass these Json calls to it? I tried creating a python script to simulate the JsonRPC calls but it just blocks after outputting it's standard three initialization lines:

Content-Length: 119

{"jsonrpc":"2.0","method":"window/logMessage","params":{"type":3,"message":"Pyright language server 1.1.391 starting"}}Content-Length: 152

{"jsonrpc":"2.0","method":"window/logMessage","params":{"type":3,"message":"Server root directory: file:///opt/homebrew/lib/node_modules/pyright/dist"}}

And any further input i give to it's stdin is just blocked and the program hangs.

The script which i am using:

import subprocess
import json
import time

process = subprocess.Popen(
    ["pyright-langserver", "--stdio"],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
)


def send_request(id, method, params):
    request = json.dumps(
        {"jsonrpc": "2.0", "id": id, "method": method, "params": params}
    )
    request += "\n"  # Add newline to signify end of request
    print(f"Sending request: {request}")
    process.stdin.write(request.encode())
    process.stdin.flush()


def read_response():
    buffer = b""
    while b"\r\n\r\n" not in buffer:
        buffer += process.stdout.read(1)
    headers, content = buffer.split(b"\r\n\r\n", 1)
    content_length = next(
        int(header.split(b":")[1].strip())
        for header in headers.split(b"\r\n")
        if header.startswith(b"Content-Length")
    )
    while len(content) < content_length:
        content += process.stdout.read(1)
    print(f"Raw JSON response: {content.decode()}")
    return json.loads(content.decode())


# init request
send_request(
    1,
    "initialize",
    {"rootUri": "file:///Users/arpit/Programming/Python/test-project"},
)
response = read_response()
print("Initialize response:", response)

# Sending a simple completion request
send_request(
    2,
    "textDocument/completion",
    {
        "textDocument": {
            "uri": "file:///Users/arpit/Programming/Python/test-project/main.py"
        },
        "position": {"line": 7, "character": 20},
    },
)
response = read_response()
print("Completion response:", response)

# Sending a simple completion request again
send_request(
    2,
    "textDocument/completion",
    {
        "textDocument": {
            "uri": "file:///Users/arpit/Programming/Python/test-project/main.py"
        },
        "position": {"line": 5, "character": 10},
    },
)
response = read_response()
print("Completion response:", response)

Any help in this is greatly appreciated! All i am getting from searching around the internet is how to integrate pyright into existing code editors like neovim, vim, emacs, etc.

I tried to simulate some JsonRPC calls by giving them through a python script to the pyright process. However, no output comes as a consequence of my input commands. The output which i get is the standard initialization output which we get even if we start the process from terminal, and it just blocks on any further input commands.

Upvotes: 1

Views: 63

Answers (1)

Renaud
Renaud

Reputation: 21

I had the same problem and after a lot of tries, I finally found why the pyright-langserver didn't respond.

Your requests lack the "header" part. In fact, LSP requires to send requests like this :

Content-Length: 124

{"jsonrpc":"2.0","id":2,"method":"initialize","params":{"processId":35844,"rootUri":"file:///workspace/","capabilities":{}}}

The content length is the length of the JSON object. For my use case, I need to have a pyright-langserver process that I can reach through websockets. I implemented a small docker image that runs a websocket proxy (which writes all the messages it receives into the pyright-langserver stdin). When the websocket proxy receives a new message, it checks if the Content-Length header is present and if not, it concatenates it in the message pushed in the stdin like this :

const request = `Content-Length: ${Buffer.byteLength(jsonMessage, "utf8")}\r\n\r\n${jsonMessage}`;

This way, the pyright-langserver will parse correctly the LSP request and returns a proper answer.

I hope this helps you !

Upvotes: 2

Related Questions