Source code for pain001.lsp.server

# Copyright (C) 2023-2026 Pain001. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
# Licensed under either of the Apache License, Version 2.0 or the MIT
# License, at your option. You may not use this file except in
# compliance with one of those licences. Copies are provided in
# LICENSE-APACHE and LICENSE-MIT.
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the Licences is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied. See the applicable Licence for the specific language
# governing permissions and limitations.

"""Language Server (stdio) for Pain001 payment CSV files.

Wraps the pure :mod:`pain001.lsp.diagnostics` engine in a ``pygls`` server
so editors get live IBAN/BIC/currency/charset and missing-column diagnostics
as they type. Run it with ``pain001-lsp`` after ``pip install "pain001[lsp]"``.
"""

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

from pain001 import __version__
from pain001.lsp.diagnostics import Diagnostic, diagnostics_for_csv

server = LanguageServer("pain001-lsp", __version__)


def _to_lsp_diagnostic(diagnostic: Diagnostic) -> lsp.Diagnostic:
    """Convert an internal diagnostic to an LSP diagnostic.

    Args:
        diagnostic: The engine-produced diagnostic.

    Returns:
        The equivalent ``lsprotocol`` diagnostic.
    """
    return lsp.Diagnostic(
        range=lsp.Range(
            start=lsp.Position(
                line=diagnostic.line, character=diagnostic.col_start
            ),
            end=lsp.Position(
                line=diagnostic.line, character=diagnostic.col_end
            ),
        ),
        message=diagnostic.message,
        severity=lsp.DiagnosticSeverity(diagnostic.severity.value),
        code=diagnostic.code,
        source="pain001",
    )


def _publish(ls: LanguageServer, uri: str) -> None:
    """Lint a document and publish its diagnostics to the client.

    Args:
        ls: The active language server.
        uri: URI of the document to lint.
    """
    document = ls.workspace.get_text_document(uri)
    diagnostics = [
        _to_lsp_diagnostic(d) for d in diagnostics_for_csv(document.source)
    ]
    ls.text_document_publish_diagnostics(
        lsp.PublishDiagnosticsParams(uri=uri, diagnostics=diagnostics)
    )


[docs] @server.feature(lsp.TEXT_DOCUMENT_DID_OPEN) def did_open( ls: LanguageServer, params: lsp.DidOpenTextDocumentParams ) -> None: # pragma: no cover - thin pygls event binding """Lint a document when it is opened. Args: ls: The active language server. params: The open-document notification parameters. """ _publish(ls, params.text_document.uri)
[docs] @server.feature(lsp.TEXT_DOCUMENT_DID_CHANGE) def did_change( ls: LanguageServer, params: lsp.DidChangeTextDocumentParams ) -> None: # pragma: no cover - thin pygls event binding """Re-lint a document when it changes. Args: ls: The active language server. params: The change-document notification parameters. """ _publish(ls, params.text_document.uri)
[docs] def main() -> None: # pragma: no cover - process entry point """Start the language server over stdio.""" server.start_io()
if __name__ == "__main__": # pragma: no cover - script entry point main()