openlawlibrary / pygls

A pythonic generic language server
https://pygls.readthedocs.io/en/latest/
Apache License 2.0
546 stars 101 forks source link

Unable to register custom command #447

Closed gsabran closed 3 months ago

gsabran commented 3 months ago

I'm trying to register a custom command, following the documentation as well as I can. But when the server receives this command it fails. What should I do differently?

import logging
from pygls.server import LanguageServer as ls

logging.basicConfig(filename='pygls.log', filemode='w', level=logging.DEBUG)

server = ls('example-server', 'v0.1')

@server.command('saySomething')
def saySomething(ls, *args):
    """Returns completion items."""
    return "hello"

server.start_io()

The error log is:

ERROR:pygls.protocol.json_rpc:Failed to handle request 3 saySomething Object(lastToken=None, params=None)
Traceback (most recent call last):
  File ".../pygls/protocol/json_rpc.py", line 218, in _get_handler
    return self.fm.builtin_features[feature_name]
           ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
KeyError: 'saySomething'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ".../pygls/protocol/json_rpc.py", line 221, in _get_handler
    return self.fm.features[feature_name]
           ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
KeyError: 'saySomething'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ".../pygls/protocol/json_rpc.py", line 260, in _handle_request
    handler = self._get_handler(method_name)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../pygls/protocol/json_rpc.py", line 223, in _get_handler
    raise JsonRpcMethodNotFound.of(feature_name)
pygls.exceptions.JsonRpcMethodNotFound: Method Not Found: saySomething
INFO:pygls.protocol.json_rpc:Sending data: {"error": {"code": -32601, "message": "Method Not Found: saySomething"}, "jsonrpc": "2.0", "id": 3}

Thanks a lot for the support.

alcarney commented 3 months ago

Your server code looks fine, the issue is probably with how the client is interacting with it.

Commands in LSP cannot be called directly, instead the client needs to send a workspace/executeCommand request, specifying the name of the command it wishes to call.

For example

import asyncio
import sys

from lsprotocol import types
from pygls.lsp.client import BaseLanguageClient

async def main():
    client = BaseLanguageClient("client", "v1")
    await client.start_io(sys.executable, "test.py")

    response = await client.initialize_async(
        types.InitializeParams(capabilities=types.ClientCapabilities())
    )
    available_commands = response.capabilities.execute_command_provider.commands
    print(f"Available commands: {available_commands}")

    command_name = available_commands[0]
    print(f"Calling command: {command_name!r}")

    response = await client.workspace_execute_command_async(
        types.ExecuteCommandParams(
            command=command_name, arguments=[]
        )
    )
    print(f"Result: {response!r}")

    await client.shutdown_async(None)
    client.exit(None)

    await client.stop()

asyncio.run(main())

Saving your code to a file called test.py I was able to use the code above to get the following result

$ python client.py
Available commands: ['saySomething']
Calling command: 'saySomething'
Result: 'hello'

Hope that helps! :)

gsabran commented 3 months ago

You are right, thanks a lot!