openlawlibrary / pygls

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

`workspace.update_text_document` not working properly #423

Closed mathieumontgomery closed 8 months ago

mathieumontgomery commented 8 months ago

Hello, it seems that ls.workspace.update_text_document() does not work properly. When I delete a character, it sometimes deletes 2 characters, and when I add one, it sometimes adds the same twice. I've tested using this server:

from lsprotocol.types import PositionEncodingKind
from pygls.server import LanguageServer
from lsprotocol import types as lsp

server = LanguageServer("lsp", "v0.1")
LS = LanguageServer

def _get_source_from_workspace(ls: LS, uri: str):
    return list(ls.workspace.get_text_document(uri).source.split("\n"))

@server.feature(lsp.TEXT_DOCUMENT_DID_OPEN)
def did_open(ls: LS, params: lsp.DidOpenTextDocumentParams):
    ls.show_message("did open triggered")
    ls.workspace.put_text_document(params.text_document)
    ls.show_message(f"text: {_get_source_from_workspace(ls, params.text_document.uri)}")

@server.feature(lsp.TEXT_DOCUMENT_DID_CHANGE)
def did_change(ls: LS, params: lsp.DidChangeTextDocumentParams):
    ls.show_message("did change triggered")
    ls.show_message(f"changes: {params.content_changes}")

    for change in params.content_changes:
        ls.workspace.update_text_document(params.text_document, change)

    ls.show_message(f"content: {_get_source_from_workspace(ls, params.text_document.uri)}")

if __name__ == "__main__":
    server.start_io()

I tested in IntelliJ IDEA and VS Code with the same results. Example in a file named test.txt with this content:

hello.

If I remove the . at the end, I will get these logs:

...:did change triggered
...: changes: [TextDocumentContentChangeEvent_Type1(range=0:5-0:6, text='', range_length=None)]
...: content: ['helloo']

If I add an o at the end, I will see:

...: did change triggered
...: changes: [TextDocumentContentChangeEvent_Type1(range=0:6-0:6, text='o', range_length=None)]
...: content: ['hello.oo']

I'm using pygls version 1.2.1 in python 3.8.

alcarney commented 8 months ago

This might sound strange, but if you comment out the calls to ls.workspace.update_text_document and ls.workspace.put_text_document does it work as expected?

The LanguageServer class automatically handles these messages for you, so chances are in your server these changes are being applied twice - resulting in the hello.oo you see in your second example

mathieumontgomery commented 8 months ago

Oh... Yes, now it's working. I've been blindly following the documentation of the DidChangeTextDocumentParams class, assuming I had to apply it to the workspace...

    content_changes: List[TextDocumentContentChangeEvent] = attrs.field()
    """The actual content changes. The content changes describe single state changes
    to the document. So if there are two content changes c1 (at array index 0) and
    c2 (at array index 1) for a document in state S then c1 moves the document from
    S to S' and c2 from S' to S''. So c1 is computed on the state S and c2 is computed
    on the state S'.

    To mirror the content of a document using change events use the following approach:
    - start with the same initial content
    - apply the 'textDocument/didChange' notifications in the order you receive them.
    - apply the `TextDocumentContentChangeEvent`s in a single notification in the order
      you receive them."""

Thank you for the quick answer, and sorry for the inconvenience !

alcarney commented 8 months ago

No worries!