Publish Diagnostics¶
This implements the publish model of diagnostics.
The original and most widely supported model of diagnostics in LSP, the publish model allows the server to update the client whenever it is ready. Unlike the push-model however, there is no way for the client to help the server prioritize which documents it should be computing the diagnostics for.
This server scans a document for sums e.g. 1 + 2 = 3, highlighting any that are
either missing answers (warnings) or incorrect (errors).
import logging
import re
from lsprotocol import types
from pygls.cli import start_server
from pygls.lsp.server import LanguageServer
from pygls.workspace import TextDocument
ADDITION = re.compile(r"^\s*(\d+)\s*\+\s*(\d+)\s*=\s*(\d+)?$")
class PublishDiagnosticServer(LanguageServer):
"""Language server demonstrating "push-model" diagnostics."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.diagnostics = {}
def parse(self, document: TextDocument):
diagnostics = []
for idx, line in enumerate(document.lines):
match = ADDITION.match(line)
if match is not None:
left = int(match.group(1))
right = int(match.group(2))
expected_answer = left + right
actual_answer = match.group(3)
if actual_answer is not None and expected_answer == int(actual_answer):
continue
if actual_answer is None:
message = "Missing answer"
severity = types.DiagnosticSeverity.Warning
else:
message = f"Incorrect answer: {actual_answer}"
severity = types.DiagnosticSeverity.Error
diagnostics.append(
types.Diagnostic(
message=message,
severity=severity,
range=types.Range(
start=types.Position(line=idx, character=0),
end=types.Position(line=idx, character=len(line) - 1),
),
)
)
self.diagnostics[document.uri] = (document.version, diagnostics)
# logging.info("%s", self.diagnostics)
server = PublishDiagnosticServer("diagnostic-server", "v1")
@server.feature(types.TEXT_DOCUMENT_DID_OPEN)
def did_open(ls: PublishDiagnosticServer, params: types.DidOpenTextDocumentParams):
"""Parse each document when it is opened"""
doc = ls.workspace.get_text_document(params.text_document.uri)
ls.parse(doc)
for uri, (version, diagnostics) in ls.diagnostics.items():
ls.text_document_publish_diagnostics(
types.PublishDiagnosticsParams(
uri=uri,
version=version,
diagnostics=diagnostics,
)
)
@server.feature(types.TEXT_DOCUMENT_DID_CHANGE)
def did_change(ls: PublishDiagnosticServer, params: types.DidOpenTextDocumentParams):
"""Parse each document when it is changed"""
doc = ls.workspace.get_text_document(params.text_document.uri)
ls.parse(doc)
for uri, (version, diagnostics) in ls.diagnostics.items():
ls.text_document_publish_diagnostics(
types.PublishDiagnosticsParams(
uri=uri,
version=version,
diagnostics=diagnostics,
)
)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO, format="%(message)s")
start_server(server)