JSON Server

In addition to implementing a variety of LSP methods, this larger example demonstrates a number of pygls’ capabilities including

  • Defining custom commands

  • Progress updates

  • Fetching configuration values from the client

  • Async methods

  • Dynamic method (un)registration

  • Starting a TCP/WebSocket server.

This is left over from a time where pygls tried to have a single example server to demonstrate all of its features. Eventually this example will be broken up in smaller, more focused examples and how to guides.

import asyncio
import uuid
from functools import partial
from typing import Optional

from lsprotocol import types as lsp

from pygls.cli import start_server
from pygls.lsp.server import LanguageServer


class JsonLanguageServer(LanguageServer):
    CMD_PROGRESS = "progress"
    CMD_REGISTER_COMPLETIONS = "registerCompletions"
    CMD_SHOW_CONFIGURATION_ASYNC = "showConfigurationAsync"
    CMD_SHOW_CONFIGURATION_CALLBACK = "showConfigurationCallback"
    CMD_SHOW_CONFIGURATION_THREAD = "showConfigurationThread"
    CMD_UNREGISTER_COMPLETIONS = "unregisterCompletions"

    CONFIGURATION_SECTION = "pygls.jsonServer"

    def __init__(self, *args):
        super().__init__(*args)


json_server = JsonLanguageServer("pygls-json-example", "v0.1")


@json_server.feature(
    lsp.TEXT_DOCUMENT_COMPLETION,
    lsp.CompletionOptions(trigger_characters=[","], all_commit_characters=[":"]),
)
def completions(params: Optional[lsp.CompletionParams] = None) -> lsp.CompletionList:
    """Returns completion items."""
    return lsp.CompletionList(
        is_incomplete=False,
        items=[
            lsp.CompletionItem(label='"'),
            lsp.CompletionItem(label="["),
            lsp.CompletionItem(label="]"),
            lsp.CompletionItem(label="{"),
            lsp.CompletionItem(label="}"),
        ],
    )


@json_server.command(JsonLanguageServer.CMD_PROGRESS)
async def progress(ls: JsonLanguageServer, *args):
    """Create and start the progress on the client."""
    token = str(uuid.uuid4())
    # Create
    await ls.work_done_progress.create_async(token)
    # Begin
    ls.work_done_progress.begin(
        token,
        lsp.WorkDoneProgressBegin(title="Indexing", percentage=0, cancellable=True),
    )
    # Report
    for i in range(1, 10):
        # Check for cancellation from client
        if ls.work_done_progress.tokens[token].cancelled():
            # ... and stop the computation if client cancelled
            return
        ls.work_done_progress.report(
            token,
            lsp.WorkDoneProgressReport(message=f"{i * 10}%", percentage=i * 10),
        )
        await asyncio.sleep(2)
    # End
    ls.work_done_progress.end(token, lsp.WorkDoneProgressEnd(message="Finished"))


@json_server.command(JsonLanguageServer.CMD_REGISTER_COMPLETIONS)
async def register_completions(ls: JsonLanguageServer, *args):
    """Register completions method on the client."""
    params = lsp.RegistrationParams(
        registrations=[
            lsp.Registration(
                id=str(uuid.uuid4()),
                method=lsp.TEXT_DOCUMENT_COMPLETION,
                register_options={"triggerCharacters": "[':']"},
            )
        ]
    )

    try:
        await ls.client_register_capability_async(params)
        ls.window_show_message(
            lsp.ShowMessageParams(
                message="Successfully registered completions method",
                type=lsp.MessageType.Info,
            ),
        )
    except Exception:
        ls.window_show_message(
            lsp.ShowMessageParams(
                message="Error happened during completions registration.",
                type=lsp.MessageType.Error,
            ),
        )


@json_server.command(JsonLanguageServer.CMD_UNREGISTER_COMPLETIONS)
async def unregister_completions(ls: JsonLanguageServer, *args):
    """Unregister completions method on the client."""
    params = lsp.UnregistrationParams(
        unregisterations=[
            lsp.Unregistration(
                id=str(uuid.uuid4()),
                method=lsp.TEXT_DOCUMENT_COMPLETION,
            ),
        ],
    )

    try:
        await ls.client_unregister_capability_async(params)
        ls.window_show_message(
            lsp.ShowMessageParams(
                message="Successfully unregistered completions method",
                type=lsp.MessageType.Info,
            ),
        )
    except Exception:
        ls.window_show_message(
            lsp.ShowMessageParams(
                message="Error happened during completions unregistration.",
                type=lsp.MessageType.Error,
            ),
        )


def handle_config(ls: JsonLanguageServer, config):
    """Handle the configuration sent by the client."""
    try:
        example_config = config[0].get("exampleConfiguration")

        ls.window_show_message(
            lsp.ShowMessageParams(
                message=f"jsonServer.exampleConfiguration value: {example_config}",
                type=lsp.MessageType.Info,
            ),
        )

    except Exception as e:
        ls.window_log_message(
            lsp.LogMessageParams(
                message=f"Error ocurred: {e}",
                type=lsp.MessageType.Error,
            ),
        )


@json_server.command(JsonLanguageServer.CMD_SHOW_CONFIGURATION_ASYNC)
async def show_configuration_async(ls: JsonLanguageServer, *args):
    """Gets exampleConfiguration from the client settings using coroutines."""
    config = await ls.workspace_configuration_async(
        lsp.ConfigurationParams(
            items=[
                lsp.ConfigurationItem(
                    scope_uri="",
                    section=JsonLanguageServer.CONFIGURATION_SECTION,
                ),
            ]
        )
    )
    handle_config(ls, config)


@json_server.command(JsonLanguageServer.CMD_SHOW_CONFIGURATION_CALLBACK)
def show_configuration_callback(ls: JsonLanguageServer, *args):
    """Gets exampleConfiguration from the client settings using callback."""

    ls.workspace_configuration(
        lsp.ConfigurationParams(
            items=[
                lsp.ConfigurationItem(
                    scope_uri="",
                    section=JsonLanguageServer.CONFIGURATION_SECTION,
                ),
            ]
        ),
        callback=partial(handle_config, ls),
    )


@json_server.thread()
@json_server.command(JsonLanguageServer.CMD_SHOW_CONFIGURATION_THREAD)
def show_configuration_thread(ls: JsonLanguageServer, *args):
    """Gets exampleConfiguration from the client settings using thread pool."""
    config = ls.workspace_configuration(
        lsp.ConfigurationParams(
            items=[
                lsp.ConfigurationItem(
                    scope_uri="",
                    section=JsonLanguageServer.CONFIGURATION_SECTION,
                ),
            ],
        )
    ).result(2)
    handle_config(ls, config)


if __name__ == "__main__":
    start_server(json_server)