# Pygls
> .. include:: ../../CHANGELOG.md
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/changelog.rst
.. include:: ../../CHANGELOG.md
:parser: myst_parser.sphinx_
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/clients/index.rst
Coming Soon\ :sup:`TM`
======================
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/contributing/howto/run-pyodide-test-suite.rst
How To Run the Pyodide Test Suite
=================================
.. highlight:: none
This guide outlines how to setup the environment needed to run the `Pyodide `__ test suite.
#. If you haven't done so already, install `uv `__ to manage dependencies and tasks.
#. In order to run Pyodide outside of the browser you will need `NodeJs `__ installed.
#. Additionaly you will need to install the required node dependencies (which are specified in ``tests/pyodide/package.json``)::
$ cd tests/pyodide
tests/pyodide $ npm ci
#. To bootstrap the Python environment within the Pyodide runtime the test suite needs to install ``pygls`` from its wheel archive.
From the repository root, use ``uv`` to package the current development version::
$ uv build
This will place the required ``*.whl`` file in the ``dist/`` folder
#. Finally, to run the end-to-end tests against Pyodide, pass the ``--lsp-runtime pyodide`` option to ``pytest``::
$ uv run pytest --lsp-runtime pyodide
============================================ test session starts ============================================
platform linux -- Python 3.13.0, pytest-8.3.3, pluggy-1.5.0
rootdir: /var/home/alex/Projects/openlawlibrary/pygls/main
configfile: pyproject.toml
plugins: cov-5.0.0, asyncio-0.24.0
asyncio: mode=Mode.AUTO, default_loop_scope=None
pygls: runtime='pyodide', transport='stdio'
collected 216 items
tests/e2e/test_code_action.py . [ 0%]
tests/e2e/test_code_lens.py ... [ 1%]
tests/e2e/test_colors.py .. [ 2%]
tests/e2e/test_completion.py . [ 3%]
tests/e2e/test_declaration.py . [ 3%]
tests/e2e/test_definition.py . [ 4%]
tests/e2e/test_formatting.py .... [ 6%]
tests/e2e/test_hover.py ... [ 7%]
tests/e2e/test_implementation.py . [ 7%]
tests/e2e/test_inlay_hints.py . [ 8%]
tests/e2e/test_links.py .. [ 9%]
tests/e2e/test_publish_diagnostics.py . [ 9%]
tests/e2e/test_pull_diagnostics.py ... [ 11%]
tests/e2e/test_references.py . [ 11%]
tests/e2e/test_rename.py .... [ 13%]
tests/e2e/test_semantic_tokens.py ...... [ 16%]
tests/e2e/test_symbols.py ... [ 17%]
tests/e2e/test_threaded_handlers.py .ss [ 18%]
tests/e2e/test_type_definition.py . [ 19%]
tests/lsp/test_call_hierarchy.py ..... [ 21%]
tests/lsp/test_document_highlight.py ... [ 23%]
tests/lsp/test_errors.py ..... [ 25%]
tests/lsp/test_folding_range.py ... [ 26%]
tests/lsp/test_linked_editing_range.py ... [ 28%]
tests/lsp/test_moniker.py ... [ 29%]
tests/lsp/test_progress.py ....... [ 32%]
tests/lsp/test_selection_range.py ... [ 34%]
tests/lsp/test_signature_help.py .ss [ 35%]
tests/lsp/test_type_hierarchy.py ..... [ 37%]
tests/test_client.py ... [ 39%]
tests/test_document.py ....................... [ 50%]
tests/test_feature_manager.py ....................................... [ 70%]
tests/test_language_server.py ....... [ 73%]
tests/test_protocol.py .................... [ 82%]
tests/test_server_connection.py ... [ 84%]
tests/test_types.py ... [ 85%]
tests/test_uris.py .......sssss [ 91%]
tests/test_workspace.py ................... [100%]
================================ 207 passed, 9 skipped in 102.04s (0:01:42) =================================
.. tip::
You can find logs from the Pyodide environment in a file called ``pyodide.log`` in the repository root::
$ tail -f
Loading micropip, packaging
Loaded micropip, packaging
Loading attrs, six
Loaded attrs, six
Starting sync IO server
Language server initialized InitializeParams(capabilities=ClientCapabilities(workspace=None, text_document=None, notebook_document=None, window=None, general=None, experimental=None), process_id=None, client_info=None, locale=None, root_path=None, root_uri='file:///workspace', initialization_options=None, trace=None, work_done_token=None, workspace_folders=None)
Sending data: {"id": "5f4b70c6-fd2f-4806-985c-ce059c6a1c38", "jsonrpc": "2.0", "result": {"capabilities": {"positionEncoding": "utf-16", "textDocumentSync": {"openClose": true, "change": 2, "save": false}, "declarationProvider": true, "definitionProvider": true, "typeDefinitionProvider": true, "implementationProvider": true, "referencesProvider": true, "executeCommandProvider": {"commands": []}, "workspace": {"workspaceFolders": {"supported": true, "changeNotifications": true}, "fileOperations": {}}}, "serverInfo": {"name": "goto-server", "version": "v1"}}}
Index: {'file:///workspace/code.txt': {'types': {'Rectangle': 0:5-0:14, 'Square': 1:5-1:11}, 'functions': {'area': 3:3-3:7, 'volume': 5:3-5:9}}}
Sending data: {"id": "f61d6b6a-9dab-4c56-b2c4-f751bfbb52da", "jsonrpc": "2.0", "result": null}
Sending data: {"id": "db8b8009-adca-4b48-86a5-b04b622d6426", "jsonrpc": "2.0", "result": {"uri": "file:///workspace/code.txt", "range": {"start": {"line": 0, "character": 5}, "end": {"line": 0, "character": 14}}}}
Sending data: {"id": "e6de9938-5af2-45bf-a730-19e29e6a0465", "jsonrpc": "2.0", "result": null}
Shutting down the server
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/contributing/howto.rst
How To
======
.. toctree::
:glob:
Run the Pyodide Tests
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/history.rst
.. include:: ../../HISTORY.md
:parser: myst_parser.sphinx_
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/implementations.rst
.. include:: ../../Implementations.md
:parser: myst_parser.sphinx_
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/index.rst
*pygls*
=======
`pygls`_ (pronounced like “pie glass”) is a generic implementation of
the `Language Server Protocol`_ written in the Python programming language. It
allows you to write your own `language server`_ in just a few lines of code
.. literalinclude:: ../../examples/hello-world/main.py
:language: python
*pygls* supports
- Python 3.9+ on Windows, MacOS and Linux
- **Experimental** support for Pyodide
- STDIO, TCP/IP and WEBSOCKET communication
- Both sync and async styles of programming
- Running code in background threads
- Automatic text and notebook document syncronisation
.. toctree::
:hidden:
:caption: Language Servers
servers/getting-started
How To
servers/reference
.. toctree::
:hidden:
:caption: Language Clients
clients/index
.. toctree::
:hidden:
:caption: The Protocol
protocol/howto
.. toctree::
:hidden:
:caption: The Library
pygls/howto
pygls/reference
pygls/api-reference
.. toctree::
:hidden:
:caption: Contributing
contributing/howto
.. toctree::
:hidden:
:caption: About
implementations
history
changelog
Navigation
----------
*The pygls documentation tries to (with varying degrees of success!) follow the* `Diátaxis `__ *approach to writing documentation*
This documentation site is divided up into the following sections
.. grid:: 1 2 2 2
:gutter: 2
.. grid-item-card:: Language Servers
:link: servers/getting-started
:link-type: doc
:text-align: center
Documentation specific to implementing Language Servers using *pygls*.
.. grid-item-card:: Language Clients
:text-align: center
Documentation specific to implementing Language Clients using *pygls*.
Coming Soon\ :sup:`TM`!
.. grid-item-card:: The Protocol
:link: protocol/howto
:link-type: doc
:text-align: center
Additional articles that explain some aspect of the Language Server Protocol in general.
.. grid-item-card:: The Library
:link: pygls/howto
:link-type: doc
:text-align: center
Documentation that applies to the *pygls* library itself e.g. migration guides.
.. grid-item-card:: Contributing
:link: contributing/howto
:link-type: doc
:text-align: center
Guides on how to contribute to *pygls*.
.. grid-item-card:: About
:text-align: center
Additional context on the *pygls* project.
.. _Language Server Protocol: https://microsoft.github.io/language-server-protocol/specification
.. _Language server: https://langserver.org/
.. _pygls: https://github.com/openlawlibrary/pygls
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/protocol/howto/interpret-semantic-tokens.rst
.. _howto-semantic-tokens:
How To Interpret Semantic Tokens
================================
.. seealso::
:ref:`Example Server `
An example implementation of semantic tokens
:lsp:`textDocument/semanticTokens`
Semantic tokens in the LSP Specification
Semantic Tokens can be thought of as "Syntax Highlighting++".
Traditional syntax highlighting is usually implemented as a large collection of :mod:`regular expressions ` and can use the language's grammar rules to tell the difference between say a string, variable or function.
However, regular expressions are not powerful enough to tell if
- a variable is read-only
- a given function is deprecated
- a class is part of the language's standard library
This is where the *Semantic* part of Semantic Tokens comes in.
How are tokens represented?
---------------------------
Unlike most parts of the Language Server Protocol, semantic tokens are not represented by a structured object with nicely named fields.
Instead each token is represented by a sequence of 5 integers::
[0, 2, 1, 0, 3, 0, 4, 2, 1, 0, ...]
^-----------^ ^-----------^
1st token 2nd token etc.
In order to explain their meaning, it's probably best to work with an example.
Let's consider the following code snippet::
c = sqrt(
a^2 + b^2
)
Token Position
--------------
The first three numbers are dedicated to encoding a token's posisition in the document.
The first 2 integers encode the line and character offsets of the token, while the third encodes its length.
The trick however, is that these offsets are **relative to the position of start of the previous token**.
*Hover over each of the tokens below to see how their offsets are computed*
.. raw:: html
:file: tokens/positions.html
Some additional notes
- For the ``c`` token, there was no previous token so its position is calculated relative to ``(0, 0)``
- For the tokens ``a`` and ``)``, moving to a new line resets the column offset, so it's calculated relative to ``0``
Token Types
-----------
The 4th number represents the token's type.
A type indicates if a given token represents a string, variable, function etc.
When a server declares it supports semantic tokens (as part of the :lsp:`initialize` request) it must send the client a :class:`~lsprotocol.types.SemanticTokensLegend` which includes a list of token types that the server will use.
.. tip::
See :lsp:`semanticTokenTypes` in the specification for a list of all predefiend types.
To encode a token's type, the 4th number should be set to the index of the corresponding type in the :attr:`SemanticTokensLegend.token_types ` list sent to the client.
*Hover over each of the tokens below to see their corresponding type*
.. raw:: html
:file: ./tokens/types.html
Token Modifiers
---------------
So far, we have only managed to re-create traditional syntax highlighting.
It's only with the 5th and final number for the token do we get to the semantic part of semantic tokens.
Tokens can have zero or more modifiers applied to them that provide additional context for a token, such as marking is as deprecated or read-only.
As with the token types above, a server must include a list of modifiers it is going to use as part of its :class:`~lsprotocol.types.SemanticTokensLegend`.
.. tip::
See :lsp:`semanticTokenModifiers` in the specification for a list of all predefiend modifiers.
However, since we can provide more than one modifier and we only have one number to do it with, the encoding cannot be as simple as the list index of the modifer(s) we wish to apply.
To quote the specification:
.. pull-quote::
Since a token type can have n modifiers, multiple token modifiers can be set by using bit flags, so a tokenModifier value of 3 is first viewed as binary ``0b00000011``, which means ``[tokenModifiers[0], tokenModifiers[1]]`` because bits ``0`` and ``1`` are set.
*Hover over each of the tokens below to see how their modifiers are computed*
.. raw:: html
:file: ./tokens/modifiers.html
Finally! We have managed to construct the values we need to apply semantic tokens to the snippet of code we considered at the start
.. figure:: ../../../assets/semantic-tokens-example.png
:align: center
Our semantic tokens example implemented in VSCode
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/protocol/howto.rst
How To
======
.. toctree::
:maxdepth: 1
:glob:
Interpret Semantic Tokens
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/pygls/api-reference/clients.rst
Clients
=======
.. autoclass:: pygls.lsp.client.BaseLanguageClient
:members:
.. autoclass:: pygls.client.JsonRPCClient
:members:
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/pygls/api-reference/io.rst
IO
==
.. automodule:: pygls.io_
:members:
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/pygls/api-reference/protocol.rst
Protocol
========
.. autoclass:: pygls.protocol.LanguageServerProtocol
:members:
.. autoclass:: pygls.protocol.JsonRPCProtocol
:members:
.. autofunction:: pygls.protocol.default_converter
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/pygls/api-reference/servers.rst
Servers
=======
.. autoclass:: pygls.lsp.server.LanguageServer
:members:
:inherited-members:
.. autofunction:: pygls.cli.start_server
.. autoclass:: pygls.progress.Progress
:members:
.. autoclass:: pygls.server.JsonRPCServer
:members:
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/pygls/api-reference/types.rst
Types
=====
LSP type definitions in ``pygls`` are provided by the `lsprotocol `__ library
.. automodule:: lsprotocol.types
:members:
:undoc-members:
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/pygls/api-reference/uris.rst
URIs
====
.. currentmodule:: pygls.uris
.. autofunction:: from_fs_path
.. autofunction:: to_fs_path
.. autofunction:: uri_scheme
.. autofunction:: uri_with
.. autofunction:: urlparse
.. autofunction:: urlunparse
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/pygls/api-reference/workspace.rst
Workspace
=========
.. autoclass:: pygls.workspace.TextDocument
:members:
.. autoclass:: pygls.workspace.Workspace
:members:
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/pygls/api-reference.rst
Python API
==========
In this section you will find reference documentation on pygls' Python API
.. toctree::
:hidden:
:glob:
api-reference/*
.. grid:: 1 2 2 2
:gutter: 2
.. grid-item-card:: Clients
:link: api-reference/clients
:link-type: doc
:text-align: center
pygls' Language Client APIs.
.. grid-item-card:: Servers
:link: api-reference/servers
:link-type: doc
:text-align: center
pygls' Language Server APIs
.. grid-item-card:: LSP Types
:link: api-reference/types
:link-type: doc
:text-align: center
LSP type definitions, as provided by the *lsprotocol* library
.. grid-item-card:: URIs
:link: api-reference/types
:link-type: doc
:text-align: center
Helper functions for working with URIs
.. grid-item-card:: Workspace
:link: api-reference/workspace
:link-type: doc
:text-align: center
pygls' workspace API
.. grid-item-card:: Protocol
:link: api-reference/protocol
:link-type: doc
:text-align: center
pygls' low-level protocol APIs
.. grid-item-card:: IO
:link: api-reference/io
:link-type: doc
:text-align: center
pygls' low-level input/output APIs
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/pygls/howto/migrate-to-v1.rst
How To Migrate to v1.0
======================
The most notable change of the ``v1.0`` release of ``pygls`` is the removal of its hand written LSP type and method definitions in favour of relying on the types provided by the `lsprotocol`_ library which are automatically generated from the LSP specification.
As as side effect this has also meant the removal of `pydantic`_ as a dependency, since ``lsprotocol`` uses `attrs`_ and `cattrs`_ for serialisation and validation.
This guide outlines how to adapt an existing server to the breaking changes introduced in this release.
Known Migrations
----------------
You may find insight and inspiration from these projects that have already successfully migrated to v1:
* `jedi-language-server`_
* `vscode-ruff`_
* `esbonio`_
* `yara-language-server`_
Updating Imports
----------------
The ``pygls.lsp.methods`` and ``pygls.lsp.types`` modules no longer exist.
Instead, all types and method names should now be imported from the ``lsprotocol.types`` module.
Additionally, the following types and constants have been renamed.
================================================================== ==============
pygls lsprotocol
================================================================== ==============
``CODE_ACTION`` ``TEXT_DOCUMENT_CODE_ACTION``
``CODE_LENS`` ``TEXT_DOCUMENT_CODE_LENS``
``COLOR_PRESENTATION`` ``TEXT_DOCUMENT_COLOR_PRESENTATION``
``COMPLETION`` ``TEXT_DOCUMENT_COMPLETION``
``DECLARATION`` ``TEXT_DOCUMENT_DECLARATION``
``DEFINITION`` ``TEXT_DOCUMENT_DEFINITION``
``DOCUMENT_COLOR`` ``TEXT_DOCUMENT_DOCUMENT_COLOR``
``DOCUMENT_HIGHLIGHT`` ``TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT``
``DOCUMENT_LINK`` ``TEXT_DOCUMENT_DOCUMENT_LINK``
``DOCUMENT_SYMBOL`` ``TEXT_DOCUMENT_DOCUMENT_SYMBOL``
``FOLDING_RANGE`` ``TEXT_DOCUMENT_FOLDING_RANGE``
``FORMATTING`` ``TEXT_DOCUMENT_FORMATTING``
``HOVER`` ``TEXT_DOCUMENT_HOVER``
``IMPLEMENTATION`` ``TEXT_DOCUMENT_IMPLEMENTATION``
``LOG_TRACE_NOTIFICATION`` ``LOG_TRACE``
``ON_TYPE_FORMATTING`` ``TEXT_DOCUMENT_ON_TYPE_FORMATTING``
``PREPARE_RENAME`` ``TEXT_DOCUMENT_PREPARE_RENAME``
``PROGRESS_NOTIFICATION`` ``PROGRESS``
``RANGE_FORMATTING`` ``TEXT_DOCUMENT_RANGE_FORMATTING``
``REFERENCES`` ``TEXT_DOCUMENT_REFERENCES``
``RENAME`` ``TEXT_DOCUMENT_RENAME``
``SELECTION_RANGE`` ``TEXT_DOCUMENT_SELECTION_RANGE``
``SET_TRACE_NOTIFICATION`` ``SET_TRACE``
``SIGNATURE_HELP`` ``TEXT_DOCUMENT_SIGNATURE_HELP``
``TEXT_DOCUMENT_CALL_HIERARCHY_INCOMING_CALLS`` ``CALL_HIERARCHY_INCOMING_CALLS``
``TEXT_DOCUMENT_CALL_HIERARCHY_OUTGOING_CALLS`` ``CALL_HIERARCHY_OUTGOING_CALLS``
``TEXT_DOCUMENT_CALL_HIERARCHY_PREPARE`` ``TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY``
``TYPE_DEFINITION`` ``TEXT_DOCUMENT_TYPE_DEFINITION``
``WORKSPACE_FOLDERS`` ``WORKSPACE_WORKSPACE_FOLDERS``
``ApplyWorkspaceEditResponse`` ``ApplyWorkspaceEditResult``
``ClientInfo`` ``InitializeParamsClientInfoType``
``CodeActionDisabled`` ``CodeActionDisabledType``
``CodeActionLiteralSupportActionKindClientCapabilities`` ``CodeActionClientCapabilitiesCodeActionLiteralSupportTypeCodeActionKindType``
``CodeActionLiteralSupportClientCapabilities`` ``CodeActionClientCapabilitiesCodeActionLiteralSupportType``
``CompletionItemClientCapabilities`` ``CompletionClientCapabilitiesCompletionItemType``
``CompletionItemKindClientCapabilities`` ``CompletionClientCapabilitiesCompletionItemKindType``
``CompletionTagSupportClientCapabilities`` ``CompletionClientCapabilitiesCompletionItemTypeTagSupportType``
``DocumentSymbolCapabilitiesTagSupport`` ``DocumentSymbolClientCapabilitiesTagSupportType``
``InsertTextModeSupportClientCapabilities`` ``CompletionClientCapabilitiesCompletionItemTypeInsertTextModeSupportType``
``MarkedStringType`` ``MarkedString``
``MarkedString`` ``MarkedString_Type1``
``PrepareRename`` ``PrepareRenameResult_Type1``
``PublishDiagnosticsTagSupportClientCapabilities`` ``PublishDiagnosticsClientCapabilitiesTagSupportType``
``ResolveSupportClientCapabilities`` ``CodeActionClientCapabilitiesResolveSupportType``
``SemanticTokensRequestsFull`` ``SemanticTokensRegistrationOptionsFullType1``
``SemanticTokensRequests`` ``SemanticTokensClientCapabilitiesRequestsType``
``ServerInfo`` ``InitializeResultServerInfoType``
``ShowMessageRequestActionItem`` ``ShowMessageRequestClientCapabilitiesMessageActionItemType``
``SignatureHelpInformationClientCapabilities`` ``SignatureHelpClientCapabilitiesSignatureInformationType``
``SignatureHelpInformationParameterInformationClientCapabilities`` ``SignatureHelpClientCapabilitiesSignatureInformationTypeParameterInformationType``
``TextDocumentContentChangeEvent`` ``TextDocumentContentChangeEvent_Type1``
``TextDocumentContentChangeTextEvent`` ``TextDocumentContentChangeEvent_Type2``
``TextDocumentSyncOptionsServerCapabilities`` ``TextDocumentSyncOptions``
``Trace`` ``TraceValues``
``URI`` ``str``
``WorkspaceCapabilitiesSymbolKind`` ``WorkspaceSymbolClientCapabilitiesSymbolKindType``
``WorkspaceCapabilitiesTagSupport`` ``WorkspaceSymbolClientCapabilitiesTagSupportType``
``WorkspaceFileOperationsServerCapabilities`` ``FileOperationOptions``
``WorkspaceServerCapabilities`` ``ServerCapabilitiesWorkspaceType``
================================================================== ==============
Custom Models
-------------
One of the most obvious changes is the switch to `attrs`_ and `cattrs`_ for serialization and deserialisation.
This means that any custom models used by your language server will need to be converted to an ``attrs`` style class.
.. code-block:: python
# Before
from pydantic import BaseModel, Field
class ExampleConfig(BaseModel):
build_dir: Optional[str] = Field(None, alias="buildDir")
builder_name: str = Field("html", alias="builderName")
conf_dir: Optional[str] = Field(None, alias="confDir")
.. code-block:: python
# After
import attrs
@attrs.define
class ExampleConfig:
build_dir: Optional[str] = attrs.field(default=None)
builder_name: str = attrs.field(default="html")
conf_dir: Optional[str] = attrs.field(default=None)
Pygls provides a default `converter`_ that it will use when converting your models to/from JSON, which should be sufficient for most scenarios.
.. code-block:: pycon
>>> from pygls.protocol import default_converter
>>> converter = default_converter()
>>> config = ExampleConfig(builder_name='epub', conf_dir='/path/to/conf')
>>> converter.unstructure(config)
{'builderName': 'epub', 'confDir': '/path/to/conf'} # Note how snake_case is converted to camelCase
>>> converter.structure({'builderName': 'epub', 'confDir': '/path/to/conf'}, ExampleConfig)
ExampleConfig(build_dir=None, builder_name='epub', conf_dir='/path/to/conf')
However, depending on the complexity of your type definitions you may find the default converter fail to parse some of your types.
.. code-block:: pycon
>>> from typing import Literal, Union
>>> @attrs.define
... class ExampleConfig:
... num_jobs: Union[Literal["auto"], int] = attrs.field(default='auto')
...
>>> converter.structure({'numJobs': 'auto'}, ExampleConfig)
+ Exception Group Traceback (most recent call last):
| File "", line 1, in
| File "/.../python3.10/site-packages/cattrs/converters.py", li
ne 309, in structure
| return self._structure_func.dispatch(cl)(obj, cl)
| File "", line 10, in structure_ExampleConfig
| if errors: raise __c_cve('While structuring ' + 'ExampleConfig', errors, __cl)
| cattrs.errors.ClassValidationError: While structuring ExampleConfig (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "", line 6, in structure_ExampleConfig
| res['num_jobs'] = __c_structure_num_jobs(o['numJobs'], __c_type_num_jobs)
| File "/.../python3.10/site-packages/cattrs/converters.py",
line 377, in _structure_error
| raise StructureHandlerNotFoundError(msg, type_=cl)
| cattrs.errors.StructureHandlerNotFoundError: Unsupported type: typing.Union[typing.Literal['auto'], int].
Register a structure hook for it.
| Structuring class ExampleConfig @ attribute num_jobs
+------------------------------------
In which case you can extend the converter provided by ``pygls`` with your own `structure hooks`_
.. code-block:: python
from pygls.protocol import default_converter
def custom_converter():
converter = default_converter()
converter.register_structure_hook(Union[Literal['auto', int], lambda obj, _: obj)
return converter
You can then override the default converter used by ``pygls`` when constructing your language server instance
.. code-block:: python
server = LanguageServer(
name="my-language-server", version="v1.0", converter_factory=custom_converter
)
See the `hooks.py`_ module in ``lsprotocol`` for some example structure hooks
Miscellaneous
-------------
Mandatory ``name`` and ``version``
""""""""""""""""""""""""""""""""""
It is now necessary to provide a name and version when constructing an instance of the ``LanguageServer`` class
.. code-block:: python
from pygls.server import LanguageServer
server = LanguageServer(name="my-language-server", version="v1.0")
``ClientCapabilities.get_capability`` is now ``get_capability``
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.. code-block:: python
# Before
from pygls.lsp.types import ClientCapabilities
client_capabilities = ClientCapabilities()
commit_character_support = client_capabilities.get_capability(
"text_document.completion.completion_item.commit_characters_support", False
)
.. code-block:: python
# After
from lsprotocol.types import ClientCapabilities
from pygls.capabilities import get_capability
client_capabilities = ClientCapabilities()
commit_character_support = get_capability(
client_capabilities,
"text_document.completion.completion_item.commit_characters_support",
False
)
.. _attrs: https://www.attrs.org/en/stable/index.html
.. _cattrs: https://cattrs.readthedocs.io/en/stable/
.. _converter: https://cattrs.readthedocs.io/en/stable/converters.html
.. _hooks.py: https://github.com/microsoft/lsprotocol/blob/main/lsprotocol/_hooks.py
.. _lsprotocol: https://github.com/microsoft/lsprotocol
.. _pydantic: https://pydantic-docs.helpmanual.io/
.. _structure hooks: https://cattrs.readthedocs.io/en/stable/structuring.html#registering-custom-structuring-hooks
.. _jedi-language-server: https://github.com/pappasam/jedi-language-server/pull/230
.. _yara-language-server: https://github.com/avast/yls/pull/34
.. _vscode-ruff: https://github.com/charliermarsh/vscode-ruff/pull/37
.. _esbonio: https://github.com/swyddfa/esbonio/pull/484
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/pygls/howto/migrate-to-v2.rst
How To Migrate to v2.0
======================
The highlight of the *pygls* v2 release is upgrading ``lsprotocol`` to ``v2025.x`` bringing with it support for the proposed LSP v3.18 types and methods.
The new version includes standardised object names (so no more classes like ``NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2CellsType``!)
With the major version bump, this release also takes the opportunity to clean up the codebase by removing deprecated code and renaming a few things to try and improve overall consistency.
This guide outlines how to adapt an existing server to the breaking changes introduced in this release.
**Known Migrations**
.. admonition:: Finished your migration?
:class: tip
Have you migrated your server to ``v2``?
Feel free to open a pull request and add yours to the list below!
You may find these projects that have already successfully migrated to v2 a useful reference:
- Our `example servers `__
- `pytest-lsp `__
- `esbonio `__
Python Support
--------------
*pygls v2*
- Removes support for Python 3.8
- Adds support for Python 3.13 and 3.14 (with the GIL, you are welcome to try a free-threaded build just note that it has not been tested yet!)
URI Handling
------------
The :func:`pygls.uris.to_fs_path` will now return ``None`` for URIs that do not have a ``file:`` scheme.
Removed Deprecated Functions
----------------------------
The following methods and functions have been deprecated for some time and have now been removed in *pygls v2*.
================================================== ==============
**pygls v1** **pygls v2**
================================================== ==============
``pygls.workspace.Document`` ``pygls.workspace.TextDocument``
``pygls.workspace.utf16_unit_offset`` ``TextDocument.position_codec.utf16_unit_offset`` or ``Workspace.position_codec.utf16_unit_offset``
``pygls.workspace.utf16_num_units`` ``TextDocument.position_codec.client_num_units`` or ``Workspace.position_codec.client_num_units``
``pygls.workspace.position_from_utf16`` ``TextDocument.position_codec.position_from_client_units`` or ``Workspace.position_codec.position_from_client_units``
``pygls.workspace.position_to_utf16`` ``TextDocument.position_codec.position_to_client_units`` or ``Workspace.position_codec.position_to_client_units``
``pygls.workspace.range_from_utf16`` ``TextDocument.position_codec.range_from_client_units`` or ``Workspace.position_codec.range_from_client_units``
``pygls.workspace.range_to_utf16`` ``TextDocument.position_codec.range_to_client_units`` or ``Workspace.position_codec.range_to_client_units``
``Workspace.documents`` ``Workspace.text_documents``
``Workspace.get_document`` ``Workspace.get_text_document``
``Workspace.put_document`` ``Workspace.put_text_document``
``Workspace.remove_document`` ``Workspace.remove_text_document``
``Workspace.update_document`` ``Workspace.update_text_document``
================================================== ==============
Sending Custom Notifications
----------------------------
The ``send_notification`` method on the ``LanguageServer`` has been removed without a prior deprecation notice (sorry!)
To send custom notifications in v2, use the ``notify`` method on the underlying protocol object.
.. code-block:: python
# Before
server.send_notification("my/customNotification", {"example": "data"})
# After
server.protocol.notify("my/customNotification", {"example": "data"})
See :ref:`howto-send-custom-messages` for more details
Server commands are now called with individual arguments and can use type annotations
-------------------------------------------------------------------------------------
Instead of calling sever commands with a single arguments of type list containing all the command arguments, *pygls v2* now unpacks the arguments and passes them as individual parameters to the command method.
*pygls* will now inspect a function's type annotations when handling ``workspace/executeCommand`` requests, automatically converting JSON values to ``attrs`` class instances, or responding with an error if appropriate.
It is **not mandatory** to start using type annotations in your command definitions, but you will notice a difference in how *pygls* calls your server command methods if they take arguments.
**Before**
::
@server.command("codeLens.evaluateSum")
def evaluate_sum(ls: LanguageServer, args):
logging.info("arguments: %s", args) # here args is a list of dict
arguments = args[0]
document = ls.workspace.get_text_document(arguments["uri"])
line = document.lines[arguments["line"]]
# Compute the edit that will update the document with the result.
answer = arguments["left"] + arguments["right"]
edit = types.TextDocumentEdit(
text_document=types.OptionalVersionedTextDocumentIdentifier(
uri=arguments["uri"],
version=document.version,
),
edits=[
types.TextEdit(
new_text=f"{line.strip()} {answer}\n",
range=types.Range(
start=types.Position(line=arguments["line"], character=0),
end=types.Position(line=arguments["line"] + 1, character=0),
),
)
],
)
# Apply the edit.
ls.workspace_apply_edit(
types.ApplyWorkspaceEditParams(
edit=types.WorkspaceEdit(document_changes=[edit]),
),
)
**After**
::
@attrs.define
class EvaluateSumArgs:
"""Represents the arguments to pass to the ``codeLens.evaluateSum`` command"""
uri: str
"""The uri of the document to edit"""
left: int
"""The left argument to ``+``"""
right: int
"""The right argument to ``+``"""
line: int
"""The line number to edit"""
@server.command("codeLens.evaluateSum")
def evaluate_sum(ls: LanguageServer, args: EvaluateSumArgs):
logging.info("arguments: %s", args) # here args is an instance of EvaluateSumArgs
document = ls.workspace.get_text_document(args.uri)
line = document.lines[args.line]
# Compute the edit that will update the document with the result.
answer = args.left + args.right
edit = types.TextDocumentEdit(
text_document=types.OptionalVersionedTextDocumentIdentifier(
uri=args.uri,
version=document.version,
),
edits=[
types.TextEdit(
new_text=f"{line.strip()} {answer}\n",
range=types.Range(
start=types.Position(line=args.line, character=0),
end=types.Position(line=args.line + 1, character=0),
),
)
],
)
# Apply the edit.
ls.workspace_apply_edit(
types.ApplyWorkspaceEditParams(
edit=types.WorkspaceEdit(document_changes=[edit]),
),
)
Renamed ``LanguageServer`` Methods
----------------------------------
The :class:`~pygls.lsp.server.LanuageServer` class has been moved to the ``pygls.lsp`` module::
# Before
from pygls.server import LanguageServer
server = LanguageServer(name="my-language-server", version="v1.0")
# After
from pygls.lsp.server import LanguageServer
server = LanguageServer(name="my-language-server", version="v1.0")
All LSP requests and notifications that can be sent by a server are now automatically generated from the specification, as a result the following methods have been renamed
================================================== ==============
**pygls v1** **pygls v2**
================================================== ==============
``LanguageServer.apply_edit`` ``LanguageServer.workspace_apply_edit``
``LanguageServer.apply_edit_async`` ``LanguageServer.workspace_apply_edit_async``
``LanguageServer.get_configuration`` ``LanguageServer.workspace_configuration``
``LanguageServer.get_configuration_async`` ``LanguageServer.workspace_configuration_async``
``LanguageServer.publish_diagnostics`` ``LanguageServer.text_document_publish_diagnostics``
``LanguageServer.register_capability`` ``LanguageServer.client_register_capability``
``LanguageServer.register_capability_async`` ``LanguageServer.client_register_capability_async``
``LanguageServer.semantic_tokens_refresh`` ``LanguageServer.workspace_semantic_tokens_refresh``
``LanguageServer.semantic_tokens_refresh_async`` ``LanguageServer.workspace_semantic_tokens_refresh_async``
``LanguageServer.show_document`` ``LanguageServer.window_show_document``
``LanguageServer.show_document_async`` ``LanguageServer.window_show_document_async``
``LanguageServer.show_message`` ``LanguageServer.window_show_message``
``LanguageServer.show_message_log`` ``LanguageServer.window_log_message``
``LanguageServer.unregister_capability`` ``LanguageServer.client_unregister_capability``
``LanguageServer.unregister_capability_async`` ``LanguageServer.client_unregister_capability_async``
================================================== ==============
Additionally all LSP method signatures now require an instance of the corresponding ``params`` object for the method.
For example::
# Before
from pygls.server import LanguageServer
server = LanguageServer(name="my-language-server", version="v1.0")
server.publish_diagnostics(uri='...', diagnostics=[...])
# After
from lsprotocol import types
from pygls.lsp.server import LanguageServer
server = LanguageServer(name="my-language-server", version="v1.0")
server.text_document_publish_diagnostics(
types.PublishDiagnosticsParams(
uri='...',
diagnostics=[...],
)
)
Renamed ``LanguageServer.progress``
-----------------------------------
A consequence of the automatic method generation ``LanguageServer.progress`` now sends a ``$/progress`` notification, rather than giving access to pygls' :class:`~pygls.progress.Progress` helper.
The helper is now accessed via ``LanguageServer.work_done_progress``
**Before**
::
from lsprotocol import types
from pygls.server import LanguageServer
server = LanguageServer(name="my-language-server", version="v1.0")
@server.command('progress.example')
async def progress(ls: LanguageServer, *args):
"""Create and start the progress on the client."""
token = str(uuid.uuid4())
# Create
await ls.progress.create_async(token)
# Begin
ls.progress.begin(
token,
types.WorkDoneProgressBegin(title="Indexing", percentage=0, cancellable=True),
)
# Report
for i in range(1, 10):
# Check for cancellation from client
if ls.progress.tokens[token].cancelled():
# ... and stop the computation if client cancelled
return
ls.progress.report(
token,
types.WorkDoneProgressReport(message=f"{i * 10}%", percentage=i * 10),
)
await asyncio.sleep(2)
# End
ls.progress.end(token, types.WorkDoneProgressEnd(message="Finished"))
**After**
::
from lsprotocol import types
from pygls.lsp.server import LanguageServer
server = LanguageServer(name="my-language-server", version="v1.0")
@server.command('progress.example')
async def progress(ls: LanguageServer, *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,
types.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,
types.WorkDoneProgressReport(message=f"{i * 10}%", percentage=i * 10),
)
await asyncio.sleep(2)
# End
ls.work_done_progress.end(token, types.WorkDoneProgressEnd(message="Finished"))
Renamed LSP Types
-----------------
As part of the update to ``lsprotocol v2025``, the following types have been renamed.
=================================================================================== ==============
**lsprotocol 2023.x** **lsprotocol 2025.x**
=================================================================================== ==============
``CancelRequestNotification`` ``CancelNotification``
``ClientRegisterCapabilityRequest`` ``RegistrationRequest``
``ClientRegisterCapabilityResponse`` ``RegistrationResponse``
``ClientUnregisterCapabilityRequest`` ``UnregistrationRequest``
``ClientUnregisterCapabilityResponse`` ``UnregistrationResponse``
``CodeActionClientCapabilitiesCodeActionLiteralSupportType`` ``ClientCodeActionLiteralOptions``
``CodeActionClientCapabilitiesCodeActionLiteralSupportTypeCodeActionKindType`` ``ClientCodeActionKindOptions``
``CodeActionClientCapabilitiesResolveSupportType`` ``ClientCodeActionResolveOptions``
``CodeActionDisabledType`` ``CodeActionDisabled``
``CompletionClientCapabilitiesCompletionItemKindType`` ``ClientCompletionItemOptionsKind``
``CompletionClientCapabilitiesCompletionItemType`` ``ClientCompletionItemOptions``
``CompletionClientCapabilitiesCompletionItemTypeInsertTextModeSupportType`` ``ClientCompletionItemInsertTextModeOptions``
``CompletionClientCapabilitiesCompletionItemTypeResolveSupportType`` ``ClientSymbolResolveOptions``
``CompletionClientCapabilitiesCompletionItemTypeTagSupportType`` ``CompletionItemTagOptions``
``CompletionClientCapabilitiesCompletionListType`` ``CompletionListCapabilities``
``CompletionItemResolveRequest`` ``CompletionResolveRequest``
``CompletionItemResolveResponse`` ``CompletionResolveResponse``
``CompletionListItemDefaultsType`` ``CompletionItemDefaults``
``CompletionListItemDefaultsTypeEditRangeType1`` ``EditRangeWithInsertReplace``
``CompletionOptionsCompletionItemType`` ``ServerCompletionItemOptions``
``CompletionRegistrationOptionsCompletionItemType`` ``ServerCompletionItemOptions``
``DocumentSymbolClientCapabilitiesSymbolKindType`` ``ClientSymbolKindOptions``
``DocumentSymbolClientCapabilitiesTagSupportType`` ``ClientSymbolTagOptions``
``FoldingRangeClientCapabilitiesFoldingRangeKindType`` ``ClientFoldingRangeKindOptions``
``FoldingRangeClientCapabilitiesFoldingRangeType`` ``ClientFoldingRangeOptions``
``GeneralClientCapabilitiesStaleRequestSupportType`` ``StaleRequestSupportOptions``
``InitializeParamsClientInfoType`` ``ClientInfo``
``InitializeResultServerInfoType`` ``ServerInfo``
``InlayHintClientCapabilitiesResolveSupportType`` ``ClientInlayHintResolveOptions``
``MarkedString_Type1`` ``MarkedStringWithLanguage``
``NotebookDocumentChangeEventCellsType`` ``NotebookDocumentCellChanges``
``NotebookDocumentChangeEventCellsTypeStructureType`` ``NotebookDocumentCellChangeStructure``
``NotebookDocumentChangeEventCellsTypeTextContentType`` ``NotebookDocumentCellContentChanges``
``NotebookDocumentDidChangeNotification`` ``DidChangeNotebookDocumentNotification``
``NotebookDocumentDidCloseNotification`` ``DidCloseNotebookDocumentNotification``
``NotebookDocumentDidOpenNotification`` ``DidOpenNotebookDocumentNotification``
``NotebookDocumentDidSaveNotification`` ``DidSaveNotebookDocumentNotification``
``NotebookDocumentFilter_Type1`` ``NotebookDocumentFilterNotebookType``
``NotebookDocumentFilter_Type2`` ``NotebookDocumentFilterScheme``
``NotebookDocumentFilter_Type3`` ``NotebookDocumentFilterPattern``
``NotebookDocumentSyncOptionsNotebookSelectorType1`` ``NotebookDocumentFilterWithNotebook``
``NotebookDocumentSyncOptionsNotebookSelectorType1CellsType`` ``NotebookCellLanguage``
``NotebookDocumentSyncOptionsNotebookSelectorType2`` ``NotebookDocumentFilterWithCells``
``NotebookDocumentSyncOptionsNotebookSelectorType2CellsType`` ``NotebookCellLanguage``
``NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1`` ``NotebookDocumentFilterWithNotebook``
``NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1CellsType`` ``NotebookCellLanguage``
``NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2`` ``NotebookDocumentFilterWithCells``
``NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2CellsType`` ``NotebookCellLanguage``
``PrepareRenameResult_Type1`` ``PrepareRenamePlaceholder``
``PrepareRenameResult_Type2`` ``PrepareRenameDefaultBehavior``
``PublishDiagnosticsClientCapabilitiesTagSupportType`` ``ClientDiagnosticsTagOptions``
``SemanticTokensClientCapabilitiesRequestsType`` ``ClientSemanticTokensRequestOptions``
``SemanticTokensClientCapabilitiesRequestsTypeFullType1`` ``ClientSemanticTokensRequestFullDelta``
``SemanticTokensOptionsFullType1`` ``SemanticTokensFullDelta``
``SemanticTokensRegistrationOptionsFullType1`` ``SemanticTokensFullDelta``
``ServerCapabilitiesWorkspaceType`` ``WorkspaceOptions``
``ShowMessageRequestClientCapabilitiesMessageActionItemType`` ``ClientShowMessageActionItemOptions``
``SignatureHelpClientCapabilitiesSignatureInformationType`` ``ClientSignatureInformationOptions``
``SignatureHelpClientCapabilitiesSignatureInformationTypeParameterInformationType`` ``ClientSignatureParameterInformationOptions``
``TextDocumentCodeActionRequest`` ``CodeActionRequest``
``TextDocumentCodeActionResponse`` ``CodeActionResponse``
``TextDocumentCodeLensRequest`` ``CodeLensRequest``
``TextDocumentCodeLensResponse`` ``CodeLensResponse``
``TextDocumentColorPresentationOptions`` ``ColorPresentationRequestOptions``
``TextDocumentColorPresentationRequest`` ``ColorPresentationRequest``
``TextDocumentColorPresentationResponse`` ``ColorPresentationResponse``
``TextDocumentCompletionRequest`` ``CompletionRequest``
``TextDocumentCompletionResponse`` ``CompletionResponse``
``TextDocumentContentChangeEvent_Type1`` ``TextDocumentContentChangePartial``
``TextDocumentContentChangeEvent_Type2`` ``TextDocumentContentChangeWholeDocument``
``TextDocumentDeclarationRequest`` ``DeclarationRequest``
``TextDocumentDeclarationResponse`` ``DeclarationResponse``
``TextDocumentDefinitionRequest`` ``DefinitionRequest``
``TextDocumentDefinitionResponse`` ``DefinitionResponse``
``TextDocumentDiagnosticRequest`` ``DocumentDiagnosticRequest``
``TextDocumentDiagnosticResponse`` ``DocumentDiagnosticResponse``
``TextDocumentDidChangeNotification`` ``DidChangeTextDocumentNotification``
``TextDocumentDidCloseNotification`` ``DidCloseTextDocumentNotification``
``TextDocumentDidOpenNotification`` ``DidOpenTextDocumentNotification``
``TextDocumentDidSaveNotification`` ``DidSaveTextDocumentNotification``
``TextDocumentDocumentColorRequest`` ``DocumentColorRequest``
``TextDocumentDocumentColorResponse`` ``DocumentColorResponse``
``TextDocumentDocumentHighlightRequest`` ``DocumentHighlightRequest``
``TextDocumentDocumentHighlightResponse`` ``DocumentHighlightResponse``
``TextDocumentDocumentLinkRequest`` ``DocumentLinkRequest``
``TextDocumentDocumentLinkResponse`` ``DocumentLinkResponse``
``TextDocumentDocumentSymbolRequest`` ``DocumentSymbolRequest``
``TextDocumentDocumentSymbolResponse`` ``DocumentSymbolResponse``
``TextDocumentFilter_Type1`` ``TextDocumentFilterLanguage``
``TextDocumentFilter_Type2`` ``TextDocumentFilterScheme``
``TextDocumentFilter_Type3`` ``TextDocumentFilterPattern``
``TextDocumentFoldingRangeRequest`` ``FoldingRangeRequest``
``TextDocumentFoldingRangeResponse`` ``FoldingRangeResponse``
``TextDocumentFormattingRequest`` ``DocumentFormattingRequest``
``TextDocumentFormattingResponse`` ``DocumentFormattingResponse``
``TextDocumentHoverRequest`` ``HoverRequest``
``TextDocumentHoverResponse`` ``HoverResponse``
``TextDocumentImplementationRequest`` ``ImplementationRequest``
``TextDocumentImplementationResponse`` ``ImplementationResponse``
``TextDocumentInlayHintRequest`` ``InlayHintRequest``
``TextDocumentInlayHintResponse`` ``InlayHintResponse``
``TextDocumentInlineCompletionRequest`` ``InlineCompletionRequest``
``TextDocumentInlineCompletionResponse`` ``InlineCompletionResponse``
``TextDocumentInlineValueRequest`` ``InlineValueRequest``
``TextDocumentInlineValueResponse`` ``InlineValueResponse``
``TextDocumentLinkedEditingRangeRequest`` ``LinkedEditingRangeRequest``
``TextDocumentLinkedEditingRangeResponse`` ``LinkedEditingRangeResponse``
``TextDocumentMonikerRequest`` ``MonikerRequest``
``TextDocumentMonikerResponse`` ``MonikerResponse``
``TextDocumentOnTypeFormattingRequest`` ``DocumentOnTypeFormattingRequest``
``TextDocumentOnTypeFormattingResponse`` ``DocumentOnTypeFormattingResponse``
``TextDocumentPrepareCallHierarchyRequest`` ``CallHierarchyPrepareRequest``
``TextDocumentPrepareCallHierarchyResponse`` ``CallHierarchyPrepareResponse``
``TextDocumentPrepareRenameRequest`` ``PrepareRenameRequest``
``TextDocumentPrepareRenameResponse`` ``PrepareRenameResponse``
``TextDocumentPrepareTypeHierarchyRequest`` ``TypeHierarchyPrepareRequest``
``TextDocumentPrepareTypeHierarchyResponse`` ``TypeHierarchyPrepareResponse``
``TextDocumentPublishDiagnosticsNotification`` ``PublishDiagnosticsNotification``
``TextDocumentRangeFormattingRequest`` ``DocumentRangeFormattingRequest``
``TextDocumentRangeFormattingResponse`` ``DocumentRangeFormattingResponse``
``TextDocumentRangesFormattingRequest`` ``DocumentRangesFormattingRequest``
``TextDocumentRangesFormattingResponse`` ``DocumentRangesFormattingResponse``
``TextDocumentReferencesRequest`` ``ReferencesRequest``
``TextDocumentReferencesResponse`` ``ReferencesResponse``
``TextDocumentRenameRequest`` ``RenameRequest``
``TextDocumentRenameResponse`` ``RenameResponse``
``TextDocumentSelectionRangeRequest`` ``SelectionRangeRequest``
``TextDocumentSelectionRangeResponse`` ``SelectionRangeResponse``
``TextDocumentSemanticTokensFullDeltaRequest`` ``SemanticTokensDeltaRequest``
``TextDocumentSemanticTokensFullDeltaResponse`` ``SemanticTokensDeltaResponse``
``TextDocumentSemanticTokensFullRequest`` ``SemanticTokensRequest``
``TextDocumentSemanticTokensFullResponse`` ``SemanticTokensResponse``
``TextDocumentSemanticTokensRangeRequest`` ``SemanticTokensRangeRequest``
``TextDocumentSemanticTokensRangeResponse`` ``SemanticTokensRangeResponse``
``TextDocumentSignatureHelpRequest`` ``SignatureHelpRequest``
``TextDocumentSignatureHelpResponse`` ``SignatureHelpResponse``
``TextDocumentTypeDefinitionRequest`` ``TypeDefinitionRequest``
``TextDocumentTypeDefinitionResponse`` ``TypeDefinitionResponse``
``TextDocumentWillSaveNotification`` ``WillSaveTextDocumentNotification``
``TextDocumentWillSaveWaitUntilRequest`` ``WillSaveTextDocumentWaitUntilRequest``
``TextDocumentWillSaveWaitUntilResponse`` ``WillSaveTextDocumentWaitUntilResponse``
``TraceValues`` ``TraceValue``
``WindowLogMessageNotification`` ``LogMessageNotification``
``WindowShowDocumentRequest`` ``ShowDocumentRequest``
``WindowShowDocumentResponse`` ``ShowDocumentResponse``
``WindowShowMessageNotification`` ``ShowMessageNotification``
``WindowShowMessageRequestRequest`` ``ShowMessageRequest``
``WindowShowMessageRequestResponse`` ``ShowMessageResponse``
``WindowWorkDoneProgressCancelNotification`` ``WorkDoneProgressCancelNotification``
``WindowWorkDoneProgressCreateRequest`` ``WorkDoneProgressCreateRequest``
``WindowWorkDoneProgressCreateResponse`` ``WorkDoneProgressCreateResponse``
``WorkspaceApplyEditRequest`` ``ApplyWorkspaceEditRequest``
``WorkspaceApplyEditResponse`` ``ApplyWorkspaceEditResponse``
``WorkspaceCodeLensRefreshRequest`` ``CodeLensRefreshRequest``
``WorkspaceCodeLensRefreshResponse`` ``CodeLensRefreshResponse``
``WorkspaceConfigurationParams`` ``ConfigurationParams``
``WorkspaceConfigurationRequest`` ``ConfigurationRequest``
``WorkspaceConfigurationResponse`` ``ConfigurationResponse``
``WorkspaceDiagnosticRefreshRequest`` ``DiagnosticRefreshRequest``
``WorkspaceDiagnosticRefreshResponse`` ``DiagnosticRefreshResponse``
``WorkspaceDidChangeConfigurationNotification`` ``DidChangeConfigurationNotification``
``WorkspaceDidChangeWatchedFilesNotification`` ``DidChangeWatchedFilesNotification``
``WorkspaceDidChangeWorkspaceFoldersNotification`` ``DidChangeWorkspaceFoldersNotification``
``WorkspaceDidCreateFilesNotification`` ``DidCreateFilesNotification``
``WorkspaceDidDeleteFilesNotification`` ``DidDeleteFilesNotification``
``WorkspaceDidRenameFilesNotification`` ``DidRenameFilesNotification``
``WorkspaceEditClientCapabilitiesChangeAnnotationSupportType`` ``ChangeAnnotationsSupportOptions``
``WorkspaceExecuteCommandRequest`` ``ExecuteCommandRequest``
``WorkspaceExecuteCommandResponse`` ``ExecuteCommandResponse``
``WorkspaceFoldingRangeRefreshRequest`` ``FoldingRangeRefreshRequest``
``WorkspaceFoldingRangeRefreshResponse`` ``FoldingRangeRefreshResponse``
``WorkspaceInlayHintRefreshRequest`` ``InlayHintRefreshRequest``
``WorkspaceInlayHintRefreshResponse`` ``InlayHintRefreshResponse``
``WorkspaceInlineValueRefreshRequest`` ``InlineValueRefreshRequest``
``WorkspaceInlineValueRefreshResponse`` ``InlineValueRefreshResponse``
``WorkspaceSemanticTokensRefreshRequest`` ``SemanticTokensRefreshRequest``
``WorkspaceSemanticTokensRefreshResponse`` ``SemanticTokensRefreshResponse``
``WorkspaceSymbolClientCapabilitiesResolveSupportType`` ``ClientSymbolResolveOptions``
``WorkspaceSymbolClientCapabilitiesSymbolKindType`` ``ClientSymbolKindOptions``
``WorkspaceSymbolClientCapabilitiesTagSupportType`` ``ClientSymbolTagOptions``
``WorkspaceSymbolLocationType1`` ``LocationUriOnly``
``WorkspaceWillCreateFilesRequest`` ``WillCreateFilesRequest``
``WorkspaceWillCreateFilesResponse`` ``WillCreateFilesResponse``
``WorkspaceWillDeleteFilesRequest`` ``WillDeleteFilesRequest``
``WorkspaceWillDeleteFilesResponse`` ``WillDeleteFilesResponse``
``WorkspaceWillRenameFilesRequest`` ``WillRenameFilesRequest``
``WorkspaceWillRenameFilesResponse`` ``WillRenameFilesResponse``
``WorkspaceWorkspaceFoldersRequest`` ``WorkspaceFoldersRequest``
``WorkspaceWorkspaceFoldersResponse`` ``WorkspaceFoldersResponse``
=================================================================================== ==============
Low Level Changes
-----------------
The following changes are unlikely to affect you directly, but have been included for completeness.
``LanguageServer.lsp`` is now ``LanguageServer.protocol``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you need to access the underlying protocol object this is now via the ``protocol`` attribute.
``pygls.server.Server`` is now ``pygls.server.JsonRPCServer``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pygls' base server class has been renamed
Removed ``loop`` argument from ``pygls.server.JsonRPCServer``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Servers and clients in pygls v2 now both use the high level asyncio API, removing the need for an explicit ``loop`` argument to be passed in.
If you need control over the event loop used by pygls you can use functions like :external:py:func:`asyncio.set_event_loop` before starting the server/client.
Removed ``pygls.protocol.lsp_meta`` module
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The implementation of pygls' built-in handlers has changed in v2 and no longer relies on the ``LSPMeta`` metaclass and associated ``call_user_feature`` function.
Therefore both items and the containing module has been removed.
Removed ``multiprocessing.pool.ThreadPool``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The :external:py:class:`multiprocessing.pool.ThreadPool` instance has been removed, *pygls* now makes use of :external:py:class:`concurrent.futures.ThreadPoolExecutor` for all threaded tasks.
The ``thread_pool_executor`` attribute of the base ``JsonRPCServer`` class has been removed, the ``ThreadPoolExecutor`` can be accessed via the ``thread_pool`` attribute instead.
``JsonRPCProtocol`` is no longer an ``asyncio.Protocol``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Now the pygls v2 uses the high-level asyncio APIs, it no longer makes sense for the ``JsonRPCProtocol`` class to inherit from ``asyncio.Protocol``.
Similarly, "output" classes are now called writers rather than transports. The ``connection_made`` method has been replaced with a corresponding ``set_writer`` method.
New ``pygls.io_`` module
^^^^^^^^^^^^^^^^^^^^^^^^
There is a new ``pygls.io_`` module containing main message parsing loop code common to both client and server
- The equivlaent to pygls v1's ``pygls.server.aio_readline`` function is now ``pygls.io_.run_async``
- It now contains classes like v1's ``WebsocketTransportAdapter``, which have been renamed to ``WebSocketWriter``
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/pygls/howto/send-custom-messages.rst
.. _howto-send-custom-messages:
How To Send Custom Messages
===========================
.. admonition:: Work in Progress
:class: note
This guide needs more detail
A custom notification can be sent to the client (or server) using the :meth:`~pygls.protocol.JsonRPCProtocol.notify` method of the underlying protocol object
.. code-block:: python
server.protocol.notify('my/customNotification', {"example": "data"})
Simiarly, the :meth:`~pygls.protocol.JsonRPCProtocol.send_request` and :meth:`~pygls.protocol.JsonRPCProtocol.send_request_async` methods can be used to send a custom request
.. code-block:: python
response = client.protocol.send_request('my/customRequest', {"example": "data"})
response = await client.protocol.send_request_async('my/customRequest', {"example": "data"})
Depending on your use case this may be sufficient however, you lose all typing information this way.
It is possible to extend the :class:`~pygls.protocol.LanguageServerProtocol` object used by *pygls* (or in fact define a completely custom :class:`~pygls.protocol.JsonRPCProtocol`!)
Extending ``LanguageServerProtocol``
------------------------------------
.. code-block:: python
from pygls.protocol import LanguageServerProtocol
class MyCustomExtension(LanguageServerProtocol):
def __init__(self, server: LanguageServer, converter: Converter):
super().__init__(server, converter)
def get_message_type(self, method: str) -> Type[Any] | None:
"""Return my custom message types, falling back to ``LanguageServerProtocol``
where needed"""
if method == "my/customRequest":
return MyCustomRequest
return super().get_message_type(method)
def get_result_type(self, method: str) -> Type[Any] | None:
"""Return my custom response types, falling back to ``LanguageServerProtocol``
where needed"""
if method == "my/customRequest":
return MyCustomResponse
return super().get_result_type(method)
To use your custom protocol type, pass it to the :class:`~pygls.lsp.server.LanguageServer`
.. code-block::
server = LanguageServer(
name="my-language-server",
version="v1.0",
protocol_cls=MyCustomExtension,
)
Defining a Custom Protocol
--------------------------
A similar approach is needed to define an entirely custom JSON-RPC protocol
.. code-block:: python
from pygls.protocol import JsonRPCProtocol, default_converter
from pygls.server import JsonRPCServer
class MyCustomProtocol(JsonRPCProtocol):
def __init__(self, server: LanguageServer, converter: Converter):
super().__init__(server, converter)
def get_message_type(self, method: str) -> Type[Any] | None:
...
def get_result_type(self, method: str) -> Type[Any] | None:
...
server = JsonRPCServer(
protocol_cls=MyCustomProtocol,
converter_factory=default_converter,
)
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/pygls/howto/use-custom-converter.rst
.. _howto-use-custom-converter:
How To Use a Custom Converter
=============================
.. admonition:: Help Wanted
:class: tip
This page needs reworking slightly, it currently gives an approach for handling invalid data, but it should also be made clear that this approach can also be used to handle custom data types.
``pygls`` relies on `lsprotocol `__ for all of its type definitions.
``lsprotocol`` in turn builds on `attrs `__ and `cattrs `__ to provide the serialisation and deserialisation of types to/from JSON.
This is done through a ``converter`` object::
>>> from lsprotocol.types import Position
>>> from pygls.protocol import default_converter
>>> converter = default_converter()
>>> p = converter.structure({"line": 1, "character": 2}, Position)
>>> p.line
1
>>> p.character
2
Each language client/server receives a :func:`~pygls.protocol.default_converter` which is derived from the converter provided by ``lsprotocol``.
This means it will follow the Language Server Protocol exactly.
.. highlight:: python
Therefore, clients/servers written with *pygls* are pedantic and will complain loudly when given data they consider to be invalid
.. dropdown:: Example Data
:open:
.. code-block:: json
{
"jsonrpc": "2.0",
"id": 10,
"method": "textDocument/codeAction",
"params": {
"textDocument": {
"uri": "file:///path/to/file.txt"
},
"range": {
"start": { "line": 7, "character": 14 },
"end": { "line": 7, "character": 14 }
},
"context": {
"diagnostics": [
{
"range": {
"start": {
"line": null, // Invalid!
"character": 0
},
"end":{
"line": 1,
"character": 65535
}
},
"message": "an example message",
"severity": 2,
"source": "example"
}
]
}
}
}
.. dropdown:: Example Error
::
ERROR:pygls.protocol:Error receiving data
+ Exception Group Traceback (most recent call last):
| ...
| cattrs.errors.ClassValidationError: While structuring TextDocumentCodeActionRequest (1 sub-exception)
+-+---------------- 1 ----------------
| Exception Group Traceback (most recent call last):
| ...
| cattrs.errors.ClassValidationError: While structuring CodeActionParams (1 sub-exception)
| Structuring class TextDocumentCodeActionRequest @ attribute params
+-+---------------- 1 ----------------
| Exception Group Traceback (most recent call last):
| ...
| cattrs.errors.ClassValidationError: While structuring CodeActionContext (1 sub-exception)
| Structuring class CodeActionParams @ attribute context
+-+---------------- 1 ----------------
| Exception Group Traceback (most recent call last):
| ...
| cattrs.errors.IterableValidationError: While structuring typing.List[lsprotocol.types.Diagnostic] (2 sub-exceptions)
| Structuring class CodeActionContext @ attribute diagnostics
+-+---------------- 1 ----------------
| Exception Group Traceback (most recent call last):
| ...
| cattrs.errors.ClassValidationError: While structuring Diagnostic (1 sub-exception)
| Structuring typing.List[lsprotocol.types.Diagnostic] @ index 0
+-+---------------- 1 ----------------
| Exception Group Traceback (most recent call last):
| ...
| cattrs.errors.ClassValidationError: While structuring Range (2 sub-exceptions)
| Structuring class Diagnostic @ attribute range
+-+---------------- 1 ----------------
| Exception Group Traceback (most recent call last):
| ...
| cattrs.errors.ClassValidationError: While structuring Position (1 sub-exception)
| Structuring class Range @ attribute start
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| ...
| TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
| Structuring class Position @ attribute line
+------------------------------------
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
...
pygls.exceptions.JsonRpcInvalidParams: Invalid Params
Structure Hooks
---------------
By registering your own `structure hooks `__ you can take control over how malformed types should be handled.
Using the example data above, let's define a custom converter which includes a hook to silently ignore any diagnostics that are rejected when parsing the ``context`` field of a :lsp:`textDocument/codeAction` request.
.. code-block:: python
from lsprotocol import types
from pygls.protocol import default_converter
def my_converter_factory():
converter = default_converter()
def code_action_context_hook(obj, type_):
diagnostics = []
raw_diagnostics = obj.get("diagnostics", []) or []
for d in raw_diagnostics:
try:
diagnostics.append(converter.structure(d, Diagnostic))
except Exception:
pass
return CodeActionContext(diagnostics=diagnostics)
converter.register_structure_hook(CodeActionContext, code_action_context_hook)
return converter
To use this custom converter with a language server set ``my_converter_factory`` as the server's ``converter_factory``.
.. code-block:: python
server = LanguageServer(
name="my-language-server",
version="v1.0",
converter_factory=my_converter_factory,
)
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/pygls/howto/use-the-pygls-playground.rst
.. _howto-use-pygls-playground:
How To Use the ``pygls-playground`` Extension
=============================================
.. figure:: https://user-images.githubusercontent.com/2675694/260591942-b7001a7b-3081-439d-b702-5f8a489856db.png
:target: https://user-images.githubusercontent.com/2675694/260591942-b7001a7b-3081-439d-b702-5f8a489856db.png
:align: center
Screenshot of the pygls-playground extension in action
The ``pygls-playground`` VSCode extension has two main goals.
- Provide an environment in which you can easily experiment with the pygls framework by trying some of our example servers - or by writing your own
- Provide a minimal example of what it takes to integrate a pygls powered language server into VSCode.
.. tip::
For a more complete example of a VSCode client, check out Microsoft's `template extension for Python tools `__.
Setup
-----
.. highlight:: none
Since the ``pygls-playground`` is not a general purpose extension it is distributed as a `local workspace `__ extension.
This does mean, you will have to build the extension yourself before you can use it.
.. _howto-use-pygls-playground-install-pygls:
Install *pygls*
^^^^^^^^^^^^^^^
#. If you have not done so already, clone the `pygls `__ GitHub repository::
$ git clone https://github.com/openlawlibrary/pygls
#. Open a terminal in the repository's root directory and create a virtual environment::
$ python3 -m venv env
#. Activate the environment::
$ source ./env/bin/activate
#. Install `pygls`::
(env) $ python -m pip install -e .
Compile the Extension
^^^^^^^^^^^^^^^^^^^^^
.. note::
This step requires you to have `Node JS v18+ `__ installed.
Open terminal in the ``.vscode/extensions/pygls-playground/`` folder and execute following commands
#. Install dependencies::
.vscode/extensions/pygls-playground/ $ npm install --no-save
#. Compile the extension::
.vscode/extensions/pygls-playground/ $ npm run compile
Alternatively you can run ``npm run watch`` if you are going to be making changes to the extension itself.
Install the Extension
^^^^^^^^^^^^^^^^^^^^^
.. important::
In order for VSCode to recognise ``pygls-playground`` as a valid extension, you need to complete the build step above **before** opening the *pygls* repository inside VSCode.
If you did open VSCode before building the extension, you will have to run the ``Developer: Reload Window`` command through the command palette (:kbd:`Ctrl+Shift+P`)
The following steps will depend on your VSCode version
.. tab-set::
.. tab-item:: VSCode v1.89+
#. Open your copy of the *pygls* repository in VSCode and goto the :guilabel:`Extensions` tab (:kbd:`Ctrl+Shift+X`)
#. Find the ``pygls-playground`` extension in the :guilabel:`Recommended` section (not by searching in the marketplace!) and click the :guilabel:`Install Workspace Extension` button.
**If the button only says "Install", you have not found the right version of the extension**
#. Make sure that VSCode is using the virtual environment you created during the :ref:`howto-use-pygls-playground-install-pygls` step.
If necessary, the :guilabel:`Python: Select Interpreter` command can be used to pick a different environment.
Alternatively, you can set the ``pygls.server.pythonPath`` option in the ``.vscode/settings.json`` file in the repository
.. tab-item:: VSCode v1.88 and older
#. Open ``.vscode/extensions/vscode-playground/`` directory in VS Code
#. The ``pygls-playground`` relies on the `Python extension for VSCode`_ for choosing the appropriate Python environment in which to run the example language servers.
If you haven't already, you will need to install it and reload the window.
#. Open the Run and Debug view (:kbd:`Ctrl+Shift+D`)
#. Select :guilabel:`Launch Client` and press :kbd:`F5`, this will open a second VSCode window with the ``pygls-playground`` extension enabled.
#. You will need to make sure that VSCode is using the virtual environment you created during the :ref:`howto-use-pygls-playground-install-pygls` step.
If necessary, the :guilabel:`Python: Select Interpreter` command can be used to pick a different environment.
Alternatively, you can set the ``pygls.server.pythonPath`` option in the ``.vscode/settings.json`` file
Basic Usage
-----------
By default, the ``pygls-playground`` extension is configured to run the example ``code_actions.py`` server which you can find in the ``examples/servers`` folder of the *pygls* repository.
Try opening the ``examples/servers/workspace/sums.txt`` file.
The playground will automatically start the language server in the background, after a few seconds you should be able to put your cursor on one of the equations and see a code action lightbulb appear.
Open the ``examples/servers/code_actions.py`` file, make a change to the code and save the file.
The playground will detect that the file was changed and automatically restart the server to apply your changes.
Accessing the Server's Logs
^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can access the logs for both the client and server in the :guilabel:`pygls` Output Channel
- Run :guilabel:`Output: Show Output Channels...` from the command palette (:kbd:`Ctrl+Shift+P`)
- Select :guilabel:`pygls` from the list
The :guilabel:`Developer: Set Log Level...` command can be used to adjust the verbosity of the log messages.
Calling a Custom Command
^^^^^^^^^^^^^^^^^^^^^^^^
.. note::
The ``pygls-playground`` can only call custom commands that do not require any arguments
To call a custom :meth:`@server.command `
- Run :guilabel:`pygls: Execute command` via the command palette (:kbd:`Ctrl+Shift+P`)
- Select your command from the list of options
Configuration
-------------
The settings defined in the ``.vscode/settings.json`` can control most aspects of the playground's behavior.
Selecting a server
^^^^^^^^^^^^^^^^^^
.. tip::
See :ref:`example-servers` for details on the available servers and which files they work best with.
To select a different server, change the ``pygls.server.launchScript`` setting to the name of the server you wish to run.
This should be a path relative to the ``pygls.server.cwd`` setting.
If everything works as expected, the ``pygls-playground`` extension will default to using the ``examples/servers/`` folder as its working directory.
.. tip::
Cryptic ``Error: spawn /.../python ENOENT`` messages are often due to the extension using an incorrect working directory.
Debugging a server
^^^^^^^^^^^^^^^^^^
To debug the currently active language server set the ``pygls.server.debug`` option to ``true``.
The server will restart and the debugger connect automatically.
You can control the host and port that the debugger uses through the ``pygls.server.debugHost`` and ``pygls.server.debugPort`` options.
Selecting documents
^^^^^^^^^^^^^^^^^^^
Language servers typically specialise in a relatively small number of file types, so a client will only ask a server about documents it thinks the server is capable of handling.
Most of our example servers are designed to work with ``plaintext``.
To use a server with different set of documents you can modify the ``pygls.client.documentSelector`` option
For example to use a server with ``json`` files
.. code-block:: json
"pygls.client.documentSelector": [
{
"scheme": "file",
"language": "json"
},
],
You can find the full list of known language identifiers `here `__.
See the `LSP Specification `__ for details on all the available options that can be passed to the ``pygls.client.documentSelector`` option.
.. _Python extension for VSCode: https://marketplace.visualstudio.com/items?itemName=ms-python.python
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/pygls/howto.rst
How To
======
.. toctree::
:maxdepth: 1
:glob:
Migrate to v1
Migrate to v2
Send Custom Messages
Use Custom Converter
Use the pygls-playground
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/pygls/reference/logging.rst
.. _pygls-logging:
Logging
~~~~~~~
Logs are useful for tracing client requests, finding out errors and
measuring time needed to return results to the client.
*pygls* uses the built-in python :external+python:py:mod:`logging` module which has to be configured before server is started.
Below is a minimal setup to setup logging in *pygls*:
.. code:: python
import logging
from pygls.lsp.server import LanguageServer
server = LanguageServer('example-server', 'v0.1')
if __name__ == '__main__':
logging.basicConfig(message="[%(levelname)s]: %(message)s", level=logging.DEBUG)
server.start_io()
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/pygls/reference/message-handler-types.rst
.. _message-handlers:
Message Handler Types
=====================
*pygls* supports executing many types of message handler functions.
- :ref:`message-handler-async`
- :ref:`message-handler-sync`
- :ref:`message-handler-thread`
.. _message-handler-async:
*Asynchronous* Functions (*Coroutines*)
---------------------------------------
The code snippet below shows how to register a command as a coroutine:
.. code:: python
@server.command('countdown.async')
async def count_down_10_seconds_non_blocking(ls, *args):
# Omitted
Registering a *feature* as a coroutine is exactly the same.
Coroutines are functions that are executed as tasks in *pygls*'s *event
loop*. They should contain at least one *await* expression (see
`awaitables `__
for details) which tells event loop to switch to another task while
waiting. This allows *pygls* to listen for client requests in a
*non blocking* way, while still only running in the *main* thread.
Tasks can be canceled by the client if they didn't start executing (see
`Cancellation
Support `__).
.. warning::
Using computation intensive operations will *block* the main thread and
should be *avoided* inside coroutines. Take a look at
`threaded functions <#threaded-functions>`__ for more details.
.. _message-handler-sync:
*Synchronous* Functions
^^^^^^^^^^^^^^^^^^^^^^^
Synchronous functions are regular functions which *blocks* the *main*
thread until they are executed.
`Built-in features <#built-in-features>`__ are registered as regular
functions to ensure correct state of language server initialization and
workspace.
The code snippet below shows how to register a command as a regular
function:
.. code:: python
@server.command('countdown.blocking')
def count_down_10_seconds_blocking(ls, *args):
# Omitted
Registering *feature* as a regular function is exactly the same.
.. warning::
Using computation intensive operations will *block* the main thread and
should be *avoided* inside regular functions. Take a look at
`threaded functions <#threaded-functions>`__ for more details.
.. _message-handler-thread:
*Threaded* Functions
^^^^^^^^^^^^^^^^^^^^
*Threaded* functions are just regular functions, but marked with
*pygls*'s ``thread`` decorator:
.. code:: python
# Decorator order is not important in this case
@server.thread()
@server.command('countdown.threaded')
def count_down_10_seconds_blocking(ls, *args):
# Omitted
*pygls* uses its own *thread pool* to execute above function in *daemon*
thread and it is *lazy* initialized first time when function marked with
``thread`` decorator is fired.
*Threaded* functions can be used to run blocking operations. If it has been a
while or you are new to threading in Python, check out Python's
``multithreading`` and `GIL `__
before messing with threads.
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/pygls/reference.rst
Reference
=========
In this section you will find reference documentation on aspects of *pygls* that aren't specific to language clients or servers.
.. toctree::
:maxdepth: 1
reference/logging
reference/message-handler-types
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/examples/code-actions.rst
Code Actions
============
.. example-server:: code_actions.py
:start-at: import re
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/examples/code-lens.rst
Code Lens
=========
.. example-server:: code_lens.py
:start-at: import logging
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/examples/colors.rst
Document Color
==============
.. example-server:: colors.py
:start-at: import logging
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/examples/formatting.rst
Document Formatting
===================
.. example-server:: formatting.py
:start-at: import logging
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/examples/goto.rst
Goto "X" and Find References
============================
.. example-server:: goto.py
:start-at: import logging
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/examples/hover.rst
Hover
=====
.. example-server:: hover.py
:start-at: import logging
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/examples/inlay-hints.rst
Inlay Hints
===========
.. example-server:: inlay_hints.py
:start-at: import re
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/examples/json-server.rst
JSON Server
===========
.. example-server:: json_server.py
:start-at: import asyncio
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/examples/links.rst
Document Links
==============
.. example-server:: links.py
:start-at: import logging
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/examples/publish-diagnostics.rst
Publish Diagnostics
===================
.. example-server:: publish_diagnostics.py
:start-at: import logging
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/examples/pull-diagnostics.rst
Pull Diagnostics
================
.. example-server:: pull_diagnostics.py
:start-at: import logging
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/examples/rename.rst
Rename
======
.. example-server:: rename.py
:start-at: import logging
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/examples/semantic-tokens.rst
.. _example-semantic-tokens:
Semantic Tokens
===============
.. example-server:: semantic_tokens.py
:start-at: import enum
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/examples/symbols.rst
Document & Workspace Symbols
============================
.. example-server:: symbols.py
:start-at: import logging
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/examples/threaded-handlers.rst
Threaded Handlers
=================
.. example-server:: threaded_handlers.py
:start-at: import time
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/getting-started.rst
Getting Started
===============
.. _example-servers:
Example Servers
---------------
.. toctree::
:hidden:
:glob:
examples/*
.. tip::
If you use VSCode, we recommend you try these servers out in the :ref:`pygls-playground ` extension
Each of the following example servers are focused on implementing a particular subset of the Language Server Protocol.
.. grid:: 1 2 2 4
:gutter: 2
.. grid-item-card:: Code Actions
:link: examples/code-actions
:link-type: doc
:text-align: center
:octicon:`light-bulb`
.. grid-item-card:: Code Lens
:link: examples/code-lens
:link-type: doc
:text-align: center
:octicon:`eye`
.. grid-item-card:: Colors
:link: examples/colors
:link-type: doc
:text-align: center
:octicon:`paintbrush`
.. grid-item-card:: Formatting
:link: examples/formatting
:link-type: doc
:text-align: center
:octicon:`typography`
.. grid-item-card:: Goto "X"
:link: examples/goto
:link-type: doc
:text-align: center
:octicon:`search`
.. grid-item-card:: Hover
:link: examples/hover
:link-type: doc
:text-align: center
:octicon:`book`
.. grid-item-card:: Inlay Hints
:link: examples/inlay-hints
:link-type: doc
:text-align: center
:octicon:`info`
.. grid-item-card:: Links
:link: examples/links
:link-type: doc
:text-align: center
:octicon:`link`
.. grid-item-card:: Publish Diagnostics
:link: examples/publish-diagnostics
:link-type: doc
:text-align: center
:octicon:`alert`
.. grid-item-card:: Pull Diagnostics
:link: examples/pull-diagnostics
:link-type: doc
:text-align: center
:octicon:`alert`
.. grid-item-card:: Rename
:link: examples/rename
:link-type: doc
:text-align: center
:octicon:`pencil`
.. grid-item-card:: Semantic Tokens
:link: examples/semantic-tokens
:link-type: doc
:text-align: center
:octicon:`file-binary`
.. grid-item-card:: Symbols
:link: examples/symbols
:link-type: doc
:text-align: center
:octicon:`code`
These servers are dedicated to demonstrating features of *pygls* itself
.. grid:: 1 2 2 4
:gutter: 2
.. grid-item-card:: JSON Server
:link: examples/json-server
:link-type: doc
:text-align: center
:octicon:`code`
.. grid-item-card:: Threaded Handlers
:link: examples/threaded-handlers
:link-type: doc
:text-align: center
:octicon:`columns`
Tutorial
--------
.. note::
Coming soon\ :sup:`TM`
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/howto/access-server-instance.rst
.. _howto-access-server-instance:
How To Access the Server Instance
=================================
Using language server methods inside registered features and commands are quite common.
We recommend adding language server as a **first parameter** of a registered function.
.. admonition:: Why not use ``server`` inside the function?
:class: tip
Using the outer ``server`` instance inside registered function will make writing unit :ref:`tests ` more difficult as you can no longer simulate different scenarios by modifying the server instance you pass to the handler function.
There are two ways of doing this:
- Add a **type annotation** to first parameter (recommended)
Add the **LanguageServer** class (or any class derived from it) as the type annotation to first parameter of a function, pygls will detect this and automatically pass the language server instance.
.. code-block:: python
@server.command('countdown.blocking')
def count_down_10_seconds_blocking(ser: JsonLanguageServer, *args):
# Omitted
- **ls** (**l**\anguage **s**\erver) naming convention (preserved for backwards compatibility)
Name the first parameter **ls**, *pygls* will automatically pass the language server instance.
.. code-block:: python
@server.command('countdown.blocking')
def count_down_10_seconds_blocking(ls, *args):
# Omitted
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/howto/add-notebook-support.rst
.. _howto-support-notebooks:
How To Add Notebook Support to a Language Server
=================================================
.. admonition:: Help Wanted!
:class: tip
This guide is incomplete and needs to be expanded upon to provide more details to cover topics including:
- The fallback sync mechanism for notebook documents vs "proper" document synchronization
- Accessing other details of a notebook document, such as metadata and execution results
- Reconstructing the JSON representation of a notebook document - `see thie issue comment `__
If this is something you would like to help with, please open an issue or pull request (even if it is a draft!) on our `GitHub `_, so that we don't accicdentally duplicate your work.
.. seealso::
:lsp:`notebookDocument/synchronization`
The section of the LSP specification describing how notebook documents are handled
This guide explains how to add support for notebook documents to your language server.
- A notebook's structure, metadata etc. is represented using the :class:`~lsprotocol.types.NotebookDocument` class from ``lsprotocol``.
- The contents of a single notebook cell is represented using a standard :class:`~pygls.workspace.TextDocument`
In order to receive notebook documents from the client, your language server must provide an instance of :class:`~lsprotocol.types.NotebookDocumentSyncOptions` which declares the kind of notebooks it is interested in
.. code-block:: python
server = LanguageServer(
name="example-server",
version="v0.1",
notebook_document_sync=types.NotebookDocumentSyncOptions(
notebook_selector=[
types.NotebookDocumentFilterWithCells(
cells=[
types.NotebookCellLanguage(
language="python"
),
],
),
],
),
)
To access the contents of a notebook cell you would call the workspace's :meth:`~pygls.workspace.Workspace.get_text_document` method as normal.
.. code-block:: python
cell_doc = ls.workspace.get_text_document(cell_uri)
To access the notebook itself call the workspace's :meth:`~pygls.workspace.Workspace.get_notebook_document` method with either the uri of the notebook *or* the uri of any of its cells.
.. code-block:: python
notebook_doc = ls.workspace.get_notebook_document(notebook_uri=notebook_uri)
# -- OR --
notebook_doc = ls.workspace.get_notebook_document(cell_uri=cell_uri)
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/howto/customise-error-reporting.rst
.. _howto-customise-error-reporting:
How To Customise Error Reporting
================================
The default :class:`~pygls.lsp.server.LanguageServer` will send a :lsp:`window/showMessage` notification to the client to display any uncaught exceptions in the server.
To override this behaviour define your own :meth:`~pygls.lsp.server.LanguageServer.report_server_error` method like so:
.. code:: python
from pygls.exceptions import PyglsError, JsonRpcException
from pygls.lsp.server import LanguageServer
class CustomLanguageServer(LanguageServer):
def report_server_error(self, error: Exception, source: PyglsError | JsonRpcException):
pass
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/howto/get-client-configuration.rst
.. howto-get-client-configuration:
How To Get Client Configuration
===============================
.. admonition:: Help Wanted!
:class: tip
This guide is incomplete and needs to be expanded upon to provide more details to cover topics including:
- Elaborate more on sections and configuration scopes.
If this is something you would like to help with, please open an issue or pull request (even if it is a draft!) on our `GitHub `_, so that we don't accicdentally duplicate your work.
The LSP specification allows servers to request configuration options from the client.
This is done using the :lsp:`workspace/configuration` request.
You can use the :meth:`~pygls.lsp.server.LanguageServer.workspace_configuration` or :meth:`~pygls.lsp.server.LanguageServer.workspace_configuration_async` methods to request configuration from the client:
- *asynchronous* functions (*coroutines*)
.. code:: python
# await keyword tells event loop to switch to another task until notification is received
config = await ls.workspace_configuration_async(
types.ConfigurationParams(
items=[
types.ConfigurationItem(scope_uri='doc_uri_here', section='section')
]
)
)
- *synchronous* functions
.. code:: python
# callback is called when notification is received
def callback(config):
# Omitted
params = types.ConfigurationParams(
items=[
types.ConfigurationItem(scope_uri='doc_uri_here', section='section')
]
)
config = ls.workspace_configuration(params, callback)
- *threaded* functions
.. code:: python
# .result() will block the thread
config = ls.workspace_configuration(
types.ConfigurationParams(
items=[
types.ConfigurationItem(scope_uri='doc_uri_here', section='section')
]
)
).result()
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/howto/give-user-feedback.rst
.. _howto-give-user-feedback:
How To Give User Feedback
=========================
.. admonition:: Help Wanted!
:class: tip
This guide is incomplete and needs to be expanded upon to provide more details to cover topics including:
- Work done progress
- Show document
- Show message (request)
If this is something you would like to help with, please open an issue or pull request (even if it is a draft!) on our `GitHub `_, so that we don't accicdentally duplicate your work.
The LSP protocol provides a number of ways to give feedback to the user.
Show Message (Notification)
---------------------------
:lsp:`window/showMessage` is a notification that is sent from the server to the client to display a prominant text message. e.g. VSCode will render this as a notification popup
.. code:: python
@server.command('countdown.async')
async def count_down_10_seconds_non_blocking(ls, *args):
for i in range(10):
# Sends message notification to the client
ls.show_message(f"Counting down... {10 - i}")
await asyncio.sleep(1)
Show Message Log
----------------
:lsp:`window/logMessage` is a notification that is sent from the server to the client to display a discrete text message. e.g. VSCode will display the message in an :guilabel:`Output` channel.
.. code:: python
@server.command('countdown.async')
async def count_down_10_seconds_non_blocking(ls, *args):
for i in range(10):
# Sends message log notification to the client
ls.show_message_log(f"Counting down... {10 - i}")
await asyncio.sleep(1)
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/howto/implement-diagnostics.rst
.. _howto-implement-diagnostics:
How To Implement Diagnostics
============================
.. admonition:: Help Wanted!
:class: tip
This guide is incomplete and needs to be expanded upon to provide more details to cover topics including:
- Discussion of pull vs push diagnostics
If this is something you would like to help with, please open an issue or pull request (even if it is a draft!) on our `GitHub `_, so that we don't accicdentally duplicate your work.
Publish (Push Model) Diagnostics
--------------------------------
:lsp:`textDocument/publishDiagnostics` notifications are sent from the server to the client to highlight errors or potential issues. e.g. syntax errors or unused variables.
Usually this notification is sent after document is opened, or on document content change:
.. code:: python
@server.feature(TEXT_DOCUMENT_DID_OPEN)
async def did_open(ls, params: DidOpenTextDocumentParams):
"""Text document did open notification."""
ls.show_message("Text Document Did Open")
ls.show_message_log("Validating json...")
# Get document from workspace
text_doc = ls.workspace.get_text_document(params.text_document.uri)
diagnostic = Diagnostic(
range=Range(
start=Position(line-1, col-1),
end=Position(line-1, col)
),
message="Custom validation message",
source="Json Server"
)
# Send diagnostics
ls.publish_diagnostics(text_doc.uri, [diagnostic])
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/howto/implement-workspace-commands.rst
.. _howto-implement-commands:
How To Implement Workspace Commands
===================================
This guide explains how to implement workspace commands in your language server.
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/howto/run-a-server-in-pyodide.rst
.. _howto-use-pyodide:
How To Run a Server with Pyodide
================================
.. highlight:: none
`Pyodide `__ provides a version of the CPython interpreter compiled for WebAssembly, allowing you to execute Python programs either in a web browser or in NodeJS.
This guide outlines how to run your *pygls* server in such an environment.
.. important::
This environment imposes some `restrictions and limitations `__ to consider.
The most obvious restrictions are:
- only the STDIO method of communication is supported
- threads are unavailable, so your server cannot use the :meth:`@server.thread() ` decorator
- while it *is* possible to use async-await syntax in Pyodide, *pygls* does not currently enable it by default.
The setup is slightly different depending on if you are running your server via the :ref:`Browser ` or :ref:`NodeJs `
.. _howto-use-pyodide-in-node:
Using NodeJS
------------
The most likely use case for using NodeJS is testing that your server works in Pyodide without requiring the use of a browser testing tool like `Selenium `__.
In fact, this is how we test that *pygls* works correctly when running under Pyodide.
To help illustrate the steps required, we will use pygls' test suite as an example.
.. tip::
You can find the complete setup in the `tests/pyodide `__ folder of the pygls repository.
Writing our Python code as normal, each server is executed with the help of a wrapper script::
$ node run_server.js /path/to/server.py
The simplest wrapper script might look something like the following
.. code-block:: javascript
const fs = require('fs');
const { loadPyodide } = require('pyodide');
async function runServer(serverCode) {
// Initialize pyodide.
const pyodide = await loadPyodide()
// Install dependencies
await pyodide.loadPackage("micropip")
const micropip = pyodide.pyimport("micropip")
await micropip.install("pygls")
// Run the server
await pyodide.runPythonAsync(serverCode)
}
if (process.argv.length < 3) {
console.error("Missing server.py file")
process.exit(1)
}
// Read the contents of the given `server.py` file.
const serverCode = fs.readFileSync(process.argv[2], 'utf8')
runServer(serverCode).then(() => {
process.exit(0)
}).catch(err => {
process.exit(1);
})
The above code is assuming that the given Python script ends with a call to your server's :meth:`~pygls.server.JsonRPCServer.start_io` method.
Redirecting Output
^^^^^^^^^^^^^^^^^^
Unfortunately, if you tried the above script you will find that your language client wouldn't be able to establish a connection with the server.
This is due to fact Pyodide will print some log messages to ``stdout`` interfering with the client's communication with the server::
Loading micropip, packaging
Loaded micropip, packaging
Loading attrs, six
Loaded attrs, six
...
To work around this in ``run_server.js`` we create a function that will write to a log file.
.. code-block:: javascript
const consoleLog = console.log
const logFile = fs.createWriteStream("pyodide.log")
function writeToFile(...args) {
logFile.write(args[0] + `\n`);
}
And we use it to temporarily override ``console.log`` during startup
.. code-block:: javascript
async function runServer(serverCode) {
// Annoyingly, while we can redirect stderr/stdout to a file during this setup stage
// it doesn't prevent `micropip.install` from indirectly writing to console.log.
//
// Internally, `micropip.install` calls `pyodide.loadPackage` and doesn't expose loadPackage's
// options for redirecting output i.e. messageCallback.
//
// So instead, we override console.log globally.
console.log = writeToFile
const pyodide = await loadPyodide({
// stdin:
stderr: writeToFile,
})
await pyodide.loadPackage("micropip")
const micropip = pyodide.pyimport("micropip")
await micropip.install("pygls")
// Restore the original console.log
console.log = consoleLog
await pyodide.runPythonAsync(serverCode)
}
While we're redirecting output, we may as well also pass the ``writeToFile`` function to pyodide's ``stderr`` channel.
That way we're also able to see the server's logging output while it's running!
.. important::
Since node's ``fs`` API is asynchronous, don't forget to only start the server once the log file has been opened!
.. code-block:: javascript
logFile.once('open', (fd) => {
runServer(serverCode).then(() => {
logFile.end();
process.exit(0)
}).catch(err => {
logFile.write(`Error in server process\n${err}`)
logFile.end();
process.exit(1);
})
})
Workspace Access
^^^^^^^^^^^^^^^^
.. seealso::
- :external+pyodide:std:doc:`usage/file-system`
- :external+pyodide:std:ref:`accessing_files_quickref`
At this point we're able to get a server up and running however, it wouldn't be able to access any files!
There are many ways to approach exposing your files to the server (see the above resources), but for the pygls test suite we copy them into Pyodide's in-memory filesystem before starting the server.
.. code-block:: javascript
const path = require('path')
const WORKSPACE = path.join(__dirname, "..", "..", "examples", "servers", "workspace")
function loadWorkspace(pyodide) {
const FS = pyodide.FS
// Create a folder for the workspace to be copied into.
FS.mkdir('/workspace')
const workspace = fs.readdirSync(WORKSPACE)
workspace.forEach((file) => {
try {
const filename = "/" + path.join("workspace", file)
// consoleLog(`${file} -> ${filename}`)
const stream = FS.open(filename, 'w+')
const data = fs.readFileSync(path.join(WORKSPACE, file))
FS.write(stream, data, 0, data.length, 0)
FS.close(stream)
} catch (err) {
consoleLog(err)
}
})
}
async function runServer() {
// ...
loadWorkspace(pyodide)
// ...
}
It's important to note that this **WILL NOT** synchronise any changes made within the Pyodide runtime back to the source filesystem, but for the purpose of pygls' test suite it is sufficient.
It's also important to note that your language client will need to send URIs that make sense to server's environment i.e. ``file:///workspace/sums.txt`` and not ``file:///home/username/Projects/pygls/examples/servers/workspace/sums.txt``.
.. _howto-use-pyodide-in-browser:
Using the Browser
-----------------
.. seealso::
`monaco-languageclient `__ GitHub repository
For plenty of examples on how to build an in-browser client on top of the `monaco editor `__
`This commit `__
For an (outdated!) example on building a simple language client for pygls servers in the browser.
Getting your pygls server to run in a web browser using Pyodide as the runtime *is possible*.
Unfortunately, it is not necessarily *easy* - mostly because you will most likely have to build your own language client at the same time!
While building an in-browser language client is beyond the scope of this article, we can provide some suggestions to get you started - and if you figure out a nicer way please let us know!
WebWorkers
^^^^^^^^^^
Running your language server in the browser's main thread is not a great idea since any time your server is processing some message it will block the UI.
Instead we can run the server in a `WebWorker `__, which we can think of as the browser's version of a background thread.
Using the `monaco-editor-wrapper `__ project, connecting your server to the client can be as simple as a few lines of configuration
.. code-block:: typescript
import '@codingame/monaco-vscode-python-default-extension';
import { MonacoEditorLanguageClientWrapper, UserConfig } from 'monaco-editor-wrapper'
export async function run(containerId: string) {
const wrapper = new MonacoEditorLanguageClientWrapper()
const userConfig: UserConfig = {
wrapperConfig: {
editorAppConfig: {
$type: 'extended',
codeResources: {
main: {
text: '1 + 1 =',
uri: '/workspace/sums.txt',
enforceLanguageId: 'plaintext'
}
}
}
},
languageClientConfig: {
languageId: 'plaintext',
options: {
$type: 'WorkerDirect',
worker: new Worker('/run_server.js')
},
}
}
const container = document.getElementById(containerId)
await wrapper.initAndStart(userConfig, container)
}
Where ``run_server.js`` is a slightly different version of the wrapper script we used for the NodeJS section above.
Overview
^^^^^^^^
.. seealso::
:external+pyodide:std:doc:`usage/webworker`
Unlike all the other ways you will have run a pygls server up until now, the client and server will not be communicating by reading/writing bytes to/from each other.
Intead they will be passing JSON objects directly using the ``onmessage`` event and ``postMessage`` functions.
As a result, we will not be calling one of the server's ``start_xx`` methods either, instead we will rely on the events we receive from the client "drive" the server.
.. raw:: html
Also note that since our server code is running in a WebWorker, we will need to use the `importScripts `__ function to pull in the Pyodide library.
.. code-block:: typescript
importScripts("https://cdn.jsdelivr.net/pyodide//full/pyodide.js");
async function initPyodide() {
// TODO
}
const pyodidePromise = initPyodide()
onmessage = async (event) => {
let pyodide = await pyodidePromise
// TODO
}
By awaiting ``pyodidePromise`` in the ``onmessage``, we ensure that Pyodide and all our server code is ready before attempting to handle any messages.
Initializing Pyodide
^^^^^^^^^^^^^^^^^^^^
The ``initPyodide`` function is fairly similar to the ``runServer`` function from the NodeJS example above.
The main differences are
- We are now redirecting ``stderr`` to ``console.log`` rather than a file
- We are now also redirecting ``stdout``, parsing the JSON objects being written out and passing them to the ``postMessage`` function to send them onto the client.
- We **are not** calling ``server.start_io`` in our server init code.
.. code-block:: typescript
async function initPyodide() {
console.log("Initializing pyodide.")
/* @ts-ignore */
let pyodide = await loadPyodide({
stderr: console.log
})
console.log("Installing dependencies.")
await pyodide.loadPackage(["micropip"])
await pyodide.runPythonAsync(`
import micropip
await micropip.install('pygls')
`)
// See https://pyodide.org/en/stable/usage/api/js-api.html#pyodide.setStdout
pyodide.setStdout({ batched: (msg) => postMessage(JSON.parse(msg)) })
console.log("Loading server.")
await pyodide.runPythonAsync(`<>`)
return pyodide
}
Initializing the Server
^^^^^^^^^^^^^^^^^^^^^^^
Since we are not calling the server's ``start_io`` method, we need to configure the server to tell it where to write its messages.
Ideally, this would be done by calling the :meth:`~pygls.protocol.JsonRPCProtocol.set_writer` method on the server's ``protocol`` object.
However, at the time of writing there is `a bug `__ in Pyodide where output is not flushed correctly, even if you call a method like ``sys.stdout.flush()``
To work around this, we will instead override one of the ``protocol`` object's methods to output the server's messages as a sequence of newline separated JSON strings.
.. code-block:: python
# Hack to workaround https://github.com/pyodide/pyodide/issues/4139
def send_data(data):
body = json.dumps(data, default=server.protocol._serialize_message)
sys.stdout.write(f"{body}\n")
sys.stdout.flush()
server.protocol._send_data = send_data
The above code snippet should be included along with your server's init code.
Handling Messages
^^^^^^^^^^^^^^^^^
Finally, with the server prepped to send messages, the only thing left to do is to implement the ``onmessage`` handler.
.. code-block:: typescript
const pyodidePromise = initPyodide()
onmessage = async (event) => {
let pyodide = await pyodidePromise
console.log(event.data)
/* @ts-ignore */
self.client_message = JSON.stringify(event.data)
// Run Python synchronously to ensure that messages are processed in the correct order.
pyodide.runPython(`
from js import client_message
message = json.loads(client_message, object_hook=server.protocol.structure_message)
server.protocol.handle_message(message)
`)
}
The above handler
- Converts incoming JSON objects to a string and stores them in the ``client_message`` attribute on the WebWorker itself
- Our server code is then able to access the ``client_message`` via the ``js`` module provided by Pyodide
- The server parses and handles the given message.
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/howto/run-a-server.rst
.. _howto-run-server:
How To Run A Server with Python
===============================
This guide explains how to run a language server using a standard CPython interpreter.
Connection Types
----------------
*pygls* in this environment supports :ref:`ls-stdio`, :ref:`ls-tcp` and :ref:`ls-websocket` connections.
.. _ls-stdio:
STDIO
^^^^^
STDIO connections are the default connection type used by most editors and IDEs.
In this mode, the language client is responsible for starting the server as a child process and communicates with it using the stdin and stdout streams.
The :meth:`~pygls.server.JsonRPCServer.start_io` method is used to start the server in *STDIO* mode.
.. code:: python
from pygls.lsp.server import LanguageServer
server = LanguageServer('example-server', 'v0.1')
if __name__ == '__main__':
server.start_io()
.. _ls-tcp:
TCP
^^^
TCP connections, if supported by the client, allow you to run the server as a separate process independent from the client itself.
In this mode you would first start the language server and then give the server's ``host`` and ``port`` number to the client so that it can establish the connection.
The :meth:`~pygls.server.JsonRPCServer.start_tcp` method is used to start the server in *TCP* mode.
.. code:: python
from pygls.lsp.server import LanguageServer
server = LanguageServer('example-server', 'v0.1')
if __name__ == '__main__':
server.start_tcp('127.0.0.1', 8080)
.. _ls-websocket:
WEBSOCKETS
^^^^^^^^^^
.. important::
This connection type requires additional dependencies to be installed.
Be sure to include the ``ws`` extra when installing *pygls*:
.. code::
pip install pygls[ws]
`WEBSOCKET `__ connections can be used to expose a language server to a language client running in a web browser.
**Note:** This does NOT mean that the server itself runs in the browser, if you want to run the server itself in the browser, see :ref:`howto-use-pyodide`.
The :meth:`~pygls.server.JsonRPCServer.start_ws` method is used to start the server in *WEBSOCKET* mode.
.. code:: python
from pygls.lsp.server import LanguageServer
server = LanguageServer('example-server', 'v0.1')
if __name__ == '__main__':
server.start_ws('0.0.0.0', 1234)
CLI Wrapper
-----------
*pygls* provides a simple command line wrapper around these methods allowing the user of your server to select which connection type to use.
.. code:: none
usage: my-lsp-server [-h] [--tcp] [--ws] [--host HOST] [--port PORT]
start a LanguageServer instance
options:
-h, --help show this help message and exit
--tcp start a TCP server
--ws start a WebSocket server
--host HOST bind to this address
--port PORT bind to this port
Unless given an option like ``--tcp`` or ``--ws``, the wrapper will start the server in *STDIO* mode.
To use it, pass your server instance to the :func:`~pygls.cli.start_server` function.
.. code:: python
from pygls.cli import run_server
from pygls.lsp.server import LanguageServer
server = LanguageServer('example-server', 'v0.1')
if __name__ == '__main__':
start_server(server)
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/howto/work-with-text-documents.rst
.. _howto-work-with-text-documents:
How To Work with Text Documents
===============================
.. admonition:: Help Wanted!
:class: tip
This guide is incomplete and needs to be expanded upon to provide more details to cover topics including:
- (Breif!) explanation of the position encoding handshake during ``initialize``
- Using the ``PositionCodec`` to convert between LSP and Python positions (and why it's necessary)
If this is something you would like to help with, please open an issue or pull request (even if it is a draft!) on our `GitHub `_, so that we don't accicdentally duplicate your work.
This guide explains how to access :class:`TextDocuments ` via the :class:`~pygls.workspace.Workspace` in your language server.
Accessing Text Documents
------------------------
The state of all :class:`TextDocuments ` in the user's project are managed by the :class:`~pygls.workspace.Workspace` object attached to every language server.
Text documents are identified by their uri which is typically included with the ``params`` object for the current message.
Passing the uri to the :meth:`~pygls.workspace.Workspace.get_text_document` method will return the corresponding document.
.. code:: python
@server.feature(types.TEXT_DOCUMENT_DID_OPEN)
async def did_open(ls, params: types.DidOpenTextDocumentParams):
# Get document from workspace
text_doc = ls.workspace.get_text_document(params.text_document.uri)
Accessing Document Contents
---------------------------
Once you have a :class:`TextDocument ` you can access its contents via the :attr:`~pygls.workspace.TextDocument.source` attribute.
.. code:: python
@server.feature(types.TEXT_DOCUMENT_DID_OPEN)
async def did_open(ls, params: types.DidOpenTextDocumentParams):
text_doc = ls.workspace.get_text_document(params.text_document.uri)
contents = text_doc.source
...
It's common to want to process the individual lines of a text document, in which case you can use the :attr:`~pygls.workspace.TextDocument.lines` property to access all of the lines
.. code:: python
@server.feature(types.TEXT_DOCUMENT_DID_OPEN)
async def did_open(ls, params: types.DidOpenTextDocumentParams):
text_doc = ls.workspace.get_text_document(params.text_document.uri)
for line in text_doc.lines:
...
Editing Text Documents
----------------------
There are scenarios where you may want to edit the contents of a text document, for example when implementing a quickfix or refactoring command.
This is done by instructing the client to apply an edit on your behalf using the :lsp:`workspace/applyEdit` request.
The following example is taken from our example :doc:`/servers/examples/code-lens` server
.. literalinclude:: ../../../../examples/servers/code_lens.py
:language: python
:start-at: @server.command
:end-before: if __name__
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/howto.rst
How To Guides
=============
This section contains focused guides on how to perform language server related tasks using *pygls*.
.. toctree::
:maxdepth: 1
Access the Server Instance
Add Notebook Support
Customise Error Reporting
Get Client Configuration
Give User Feedback
Implement Workspace Commands
Run a Server
Run a Server with Pyodide
Work with Text Documents
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/reference/built-in-features.rst
Built-In Features
=================
The base :class:`~pygls.lsp.server.LanguageServer` class comes out of the box with many low-level LSP features already implemented.
This page lists all of these features and the behaviour that *pygls* implements.
.. note::
It is **NOT** possible to override or disable these features, but you can shadow (most of) them by registering your own handlers using the standard :deco:`server.feature ` decorator.
*pygls* will arrange to call your handler at an appropriate time, see the description of the individual features below for details on exactly when your handler will be called.
Lifecycle Messages
------------------
*pygls* automatically handles the messages that control the overall lifecycle of an LSP session
``initialize``
^^^^^^^^^^^^^^
The :lsp:`initialize` request is the first message sent in a LSP session.
As part of the request, the client provides details about the user's project (e.g. which folders are in the workspace) as well as details about the client itself (e.g. which features of the LSP specification it supports).
In response, the server provides which features it supports so the client knows what information it can expect the server to provide.
Along with initializing the :class:`~pygls.workspace.Workspace`, *pygls* automatically computes your server's capabilities based on the features you have registered.
.. admonition:: Custom Handler
If you provide your own ``initialize`` handler, it will be called **after** the server has initialized the workspace,
but **before** computing your server's capabilities.
This means you can register additional features based on the client's capabilities or provided initialization options.
.. literalinclude:: ../../../../examples/servers/register_during_initialize.py
:language: python
:start-at: @server.feature
:end-at: return None
``shutdown``
^^^^^^^^^^^^
The :lsp:`shutdown` request is sent from the client to the server to ask the server to shutdown.
If you have any resources that need to be cleaned up at the end of a session, doing so within a ``shutdown`` handler is the ideal place.
.. admonition:: Custom Handler
If you provide your own ``shutdown`` handler, it will be called **before** the built-in handler.
``exit``
^^^^^^^^
The :lsp:`exit` notification is sent from client to the server to ask the server to exit the process. *pygls* automatically stops the process by calling :external+python:py:func:`sys.exit`
.. admonition:: Custom Handler
If you provide your own ``exit`` handler, it will be called **before** the built-in handler.
Text Document Synchronization
-----------------------------
*pygls* automatically handles the messages used to ensure that both the language client and server agree on the contents of plain text documents in the user's workspace.
.. note::
In addition to the features listed below, `specification `__ defines messages like :lsp:`textDocument/didSave` and :lsp:`textDocument/willSave`.
*pygls* does not currently provide any default behaviour for these.
``textDocument/didOpen``
^^^^^^^^^^^^^^^^^^^^^^^^
The :lsp:`textDocument/didOpen` notification informs *pygls* that the client has taken ownership of the contents of the given text document and that the copy of the document on disk should not be trusted.
.. admonition:: Custom Handler
If you provide your own ``textDocument/didOpen`` handler, it will be called **after** the built-in handler.
``testDocument/didChange``
^^^^^^^^^^^^^^^^^^^^^^^^^^
The :lsp:`textDocument/didChange` notification informs *pygls* of changes made to the document's contents.
.. admonition:: Custom Handler
If you provide your own ``textDocument/didChange`` handler, it will be called **after** the built-in handler.
Therefore, your handler will see the document with the changes provided by the client already applied.
``textDocument/didClose``
^^^^^^^^^^^^^^^^^^^^^^^^^
The :lsp:`textDocument/didClose` notification informs *pygls* that the client has released ownership of the document and that the copy of the document on disk can be trusted again.
.. admonition:: Custom Handler
If you provide your own ``textDocument/didClose`` handler, it will be called **after** the built-in handler.
Notebook Document Synchronization
---------------------------------
*pygls* automatically handles the messages used to ensure that both the language client and server agree on the contents and structure of notebook documents in the user's workspace.
.. tip::
See :ref:`howto-support-notebooks` for details on adding notebook document support to your language server.
``notebookDocument/didOpen``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The :lsp:`notebookDocument/didOpen` notification informs *pygls* that the client has taken ownership of the contents of the given notebook document and that the copy of the document on disk should not be trusted.
.. admonition:: Custom Handler
If you provide your own ``notebookDocument/didOpen`` handler, it will be called **after** the built-in handler.
``notebookDocument/didChange``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The :lsp:`notebookDocument/didChange` notification informs *pygls* of changes made to the document's content, metadata, execution results and cell structure.
.. admonition:: Custom Handler
If you provide your own ``notebookDocument/didChange`` handler, it will be called **after** the built-in handler.
Therefore, your handler will see the document with the changes provided by the client already applied.
``notebookDocument/didClose``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The :lsp:`notebookDocument/didClose` notification informs *pygls* that the client has released ownership of the document and that the copy of the document on disk can be trusted again.
.. admonition:: Custom Handler
If you provide your own ``notebookDocument/didClose`` handler, it will be called **after** the built-in handler.
Miscellanous
------------
*pygls* also handles the following messages
``workspace/didChangeWorkspaceFolders``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The :lsp:`workspace/didChangeWorkspaceFolders` notification informs *pygls* when the user adds or removes folders from the workspace.
.. admonition:: Custom Handler
If you provide your own ``workspace/didChangeWorkspaceFolders`` handler, it will be called **after** the built-in handler.
``workspace/executeCommand``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. tip::
See :ref:`howto-implement-commands` for details on how to implement custom commands in your language server.
The :lsp:`workspace/executeCommand` request is sent when the client wants to invoke a custom command provided by the server.
.. admonition:: Custom Handler
:class: warning
It is **not** possible to provide a custom handler for this request.
``$/cancelRequest``
^^^^^^^^^^^^^^^^^^^
The :lsp:`$/cancelRequest` notification informs *pygls* that the client is no longer interested in the result of a request it previously sent and if possible it should stop processing it.
.. admonition:: Custom Handler
:class: warning
It is **not** possible to provide a custom handler for this request.
``$/setTrace``
^^^^^^^^^^^^^^
The :lsp:`$/setTrace` notification tells *pygls* to update the server's :class:`TraceValue `.
.. admonition:: Custom Handler
If you provide your own ``$/setTrace`` handler, it will be called **after** the built-in handler.
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/reference.rst
Reference
=========
This section provides language server specific reference material
.. toctree::
:maxdepth: 1
reference/built-in-features.rst
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/tutorial/0-setup.rst
Project Setup
=============
By the end of this stage you will have everything you need setup in order to follow the rest of this tutorial, including a simple "Hello, World" language server.
**Required Software**
Before continuing with the setup you need following software installed:
* `Visual Studio Code `__
* `Python 3.8+ `__
* `Node JS 18+ `__
* `Git `__
Your First Language Server
--------------------------
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/tutorial/y-testing.rst
.. _testing:
Testing
=======
Unit Tests
----------
Writing unit tests for registered features and commands are easy and you don't
have to mock the whole language server. If you skipped the advanced usage page,
take a look at :ref:`passing language server instance `
section for more details.
Integration Tests
-----------------
Integration tests coverage includes the whole workflow, from sending the client
request, to getting the result from the server. Since the *Language Server
Protocol* defines bidirectional communication between the client and the
server, we used *pygls* to simulate the client and send desired requests to the
server. To get a better understanding of how to set it up, take a look at our test
`fixtures`_.
.. _fixtures: https://github.com/openlawlibrary/pygls/blob/main/tests/conftest.py
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/tutorial/z-next-steps.rst
Next Steps
==========
If you decide you want to publish your language server on the VSCode marketplace this
`template extension `__
from Microsoft a useful starting point.
---
# Source: https://github.com/openlawlibrary/pygls/blob/main/docs/source/servers/tutorial.rst
.. _tutorial:
Tutorial
========
Here we have a tutorial
.. toctree::
:glob:
:maxdepth: 1
tutorial/*