openlawlibrary / pygls

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

Cannot send a TEXT_DOCUMENT_DID_OPEN to the language server for testing purposes #312

Open dcermak opened 1 year ago

dcermak commented 1 year ago

I am currently migrating salt-lsp from pygls 0.11 to 1.0, which so far went without major issues except that my testing code can no longer open a file on the server by sending a TEXT_DOCUMENT_DID_OPEN. I was using this code:

def open_file(
    client: LanguageServer, file_path: str, request_timeout: int = 5
) -> None:
    with open(file_path) as base_sls:
        proto: SaltLspProto = client.lsp
        proto.send_request(
            TEXT_DOCUMENT_DID_OPEN,
            DidOpenTextDocumentParams(
                text_document=TextDocumentItem(
                    uri="file://" + file_path,
                    language_id="sls",
                    version=0,
                    text=base_sls.read(-1),
                )
            ),
        ).result(timeout=request_timeout)

This used to work with the previous pygls version, but that now fails with:


_________________________________________________________________________________________________________________________________________________________________ test_find_id_in_indirect_include __________________________________________________________________________________________________________________________________________________________________

salt_client_server = (<pygls.server.LanguageServer object at 0x7ffa9669f210>, <salt_lsp.server.SaltServer object at 0x7ffa96682cd0>), sample_workspace = PosixPath('/tmp/pytest-of-dan/pytest-6/test_find_id_in_indirect_inclu0')                                                                                                                                    

    def test_find_id_in_indirect_include(salt_client_server, sample_workspace):                                                                                                                                                                                                                                                                                      
        client, server = salt_client_server                                                                                                                                                                                                                                                                                                                          

        open_workspace(client, f"file://{sample_workspace}")                                                                                                                                                                                                                                                                                                         

        foo_sls_path = f"{sample_workspace}/foo.sls"                                                                                                                                                                                                                                                                                                                 
        foo_sls_uri = "file://" + foo_sls_path                                                                                                                                                                                                                                                                                                                       

>       open_file(client, foo_sls_path)                                                                                                                                                                                                                                                                                                                              

tests/test_find_ids.py:60:                                                                                                                                                                                                                                                                                                                                           
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/conftest.py:647: in open_file                                                                                                                                                                                                                                                                                                                                  
    proto.send_request(                                                                                                                                                                                                                                                                                                                                              
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <pygls.protocol.LanguageServerProtocol object at 0x7ffa987e18d0>, method = 'textDocument/didOpen', params = DidOpenTextDocumentParams(text_document=TextDocumentItem(uri='file:///tmp/pytest-of-dan/pytest-6/test_find_id_in_indirect_inclu0/foo.sls', language_id='sls', version=0, text='include:\n  - bar\n  - baz\n')), callback = None                   
msg_id = '0f0051b2-d26c-401f-94ea-3b6f46c9e5a6'                                                                                                                                                                                                                                                                                                                      

    def send_request(self, method, params=None, callback=None, msg_id=None):                                                                                                                                                                                                                                                                                         
        """Sends a JSON RPC request to the client.                                                                                                                                                                                                                                                                                                                   

        Args:                                                                                                                                                                                                                                                                                                                                                        
            method(str): The method name of the message to send                                                                                                                                                                                                                                                                                                      
            params(any): The payload of the message                                                                                                                                                                                                                                                                                                                  

        Returns:                                                                                                                                                                                                                                                                                                                                                     
            Future that will be resolved once a response has been received                                                                                                                                                                                                                                                                                           
        """                                                                                                                                                                                                                                                                                                                                                          

        if msg_id is None:                                                                                                                                                                                                                                                                                                                                           
            msg_id = str(uuid.uuid4())                                                                                                                                                                                                                                                                                                                               

        request_type = self.get_message_type(method) or JsonRPCRequestMessage                                                                                                                                                                                                                                                                                        
        logger.debug('Sending request with id "%s": %s %s', msg_id, method, params)                                                                                                                                                                                                                                                                                  

>       request = request_type(                                                                                                                                                                                                                                                                                                                                      
            id=msg_id,                                                                                                                                                                                                                                                                                                                                               
            method=method,                                                                                                                                                                                                                                                                                                                                           
            params=params,                                                                                                                                                                                                                                                                                                                                           
            jsonrpc=JsonRPCProtocol.VERSION,                                                                                                                                                                                                                                                                                                                         
        )                                                                                                                                                                                                                                                                                                                                                            
E       TypeError: TextDocumentDidOpenNotification.__init__() got an unexpected keyword argument 'id'                                                                                                                                                                                                                                                                

/home/dan/.cache/pypoetry/virtualenvs/salt-lsp-qRQ_K-3P-py3.11/lib/python3.11/site-packages/pygls/protocol.py:581: TypeError                                                                                                                                                                                                                                         
tombh commented 1 year ago

Ohh is this what https://github.com/openlawlibrary/pygls/pull/313 fixes?

dcermak commented 1 year ago

Yes

alcarney commented 1 year ago

This is expected as textDocument/didOpen is a notification, not a request (see spec) There is the notify method that you can use instead.

This means that the changes in #313 shouldn't be necessary.

tombh commented 1 year ago

Thank you @alcarney 🙇 I suspect this will resolve the issue, if so then we'll close the issue and PR.

dcermak commented 1 year ago

This is expected as textDocument/didOpen is a notification, not a request (see spec) There is the notify method that you can use instead.

Ah, thank you! I have switched to using notify, which fixes the runtime error, but sadly it creates a new one: The notify function is synchronous, but the processing of that notification happens asynchronously in the background. So the tests that run immediately after that fail, as the document has not yet been loaded. This worked with send_request, albeit of course only accidentally. Can I somehow wait for the result of that notification to be processed?

alcarney commented 1 year ago

Unfortunately, I don't know of anything in the spec to help with this.

The solution I came up with for esbonio was to have the server send a custom notification of its own after it finished what I was waiting for and then extend the LanguageServerProtocol object used client side to add a mechanism to wait for a given notification