summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 20:21:34 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 20:21:34 +0000
commitfe1438b06234f8e5ecd4caa7eedfeec585b6f77c (patch)
tree5c2a9ff683189a61e0855ca3f24df319e7e03b7f
parentInitial commit. (diff)
downloadpygls-fe1438b06234f8e5ecd4caa7eedfeec585b6f77c.tar.xz
pygls-fe1438b06234f8e5ecd4caa7eedfeec585b6f77c.zip
Adding upstream version 1.3.0.upstream/1.3.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--.editorconfig12
-rw-r--r--.git-blame-ignore-revs29
-rw-r--r--.gitattributes38
-rw-r--r--.github/FUNDING.yml3
-rw-r--r--.github/workflows/ci.yml153
-rw-r--r--.github/workflows/json-extension.yml74
-rw-r--r--.github/workflows/release.yml53
-rw-r--r--.gitignore120
-rw-r--r--.readthedocs.yaml25
-rw-r--r--CHANGELOG.md628
-rw-r--r--CODE_OF_CONDUCT.md46
-rw-r--r--CONTRIBUTING.md68
-rw-r--r--CONTRIBUTORS.md31
-rw-r--r--HISTORY.md17
-rw-r--r--Implementations.md32
-rw-r--r--LICENSE.txt201
-rw-r--r--PULL_REQUEST_TEMPLATE.md14
-rw-r--r--README.md78
-rw-r--r--ThirdPartyNotices.txt53
-rw-r--r--cliff.toml64
-rw-r--r--commitlintrc.yaml28
-rw-r--r--docs/Makefile19
-rw-r--r--docs/assets/hello-world-completion.pngbin0 -> 3076 bytes
-rw-r--r--docs/make.bat35
-rw-r--r--docs/requirements.txt220
-rw-r--r--docs/source/conf.py288
-rw-r--r--docs/source/index.rst51
-rw-r--r--docs/source/pages/getting_started.rst94
-rw-r--r--docs/source/pages/migrating-to-v1.rst241
-rw-r--r--docs/source/pages/reference.rst8
-rw-r--r--docs/source/pages/reference/clients.rst8
-rw-r--r--docs/source/pages/reference/protocol.rst11
-rw-r--r--docs/source/pages/reference/servers.rst11
-rw-r--r--docs/source/pages/reference/types.rst10
-rw-r--r--docs/source/pages/reference/workspace.rst9
-rw-r--r--docs/source/pages/testing.rst24
-rw-r--r--docs/source/pages/tutorial.rst207
-rw-r--r--docs/source/pages/user-guide.rst536
-rw-r--r--examples/hello-world/README.md78
-rw-r--r--examples/hello-world/main.py28
-rw-r--r--examples/servers/.vscode/launch.json20
-rw-r--r--examples/servers/.vscode/settings.json21
-rw-r--r--examples/servers/code_actions.py74
-rw-r--r--examples/servers/inlay_hints.py116
-rw-r--r--examples/servers/json_server.py387
-rw-r--r--examples/servers/workspace/Untitled-1.ipynb44
-rw-r--r--examples/servers/workspace/sums.txt7
-rw-r--r--examples/servers/workspace/test.json3
-rw-r--r--examples/vscode-playground/.eslintrc.yml13
-rw-r--r--examples/vscode-playground/.gitignore6
-rw-r--r--examples/vscode-playground/.vscode/launch.json23
-rw-r--r--examples/vscode-playground/.vscode/tasks.json29
-rw-r--r--examples/vscode-playground/.vscodeignore10
-rw-r--r--examples/vscode-playground/LICENSE.txt201
-rw-r--r--examples/vscode-playground/README.md84
-rw-r--r--examples/vscode-playground/package-lock.json2709
-rw-r--r--examples/vscode-playground/package.json149
-rw-r--r--examples/vscode-playground/src/extension.ts401
-rw-r--r--examples/vscode-playground/tsconfig.json18
-rw-r--r--poetry.lock1265
-rw-r--r--pygls/__init__.py25
-rw-r--r--pygls/capabilities.py460
-rw-r--r--pygls/client.py176
-rw-r--r--pygls/constants.py26
-rw-r--r--pygls/exceptions.py215
-rw-r--r--pygls/feature_manager.py244
-rw-r--r--pygls/lsp/__init__.py139
-rw-r--r--pygls/lsp/client.py1961
-rw-r--r--pygls/progress.py79
-rw-r--r--pygls/protocol/__init__.py78
-rw-r--r--pygls/protocol/json_rpc.py560
-rw-r--r--pygls/protocol/language_server.py569
-rw-r--r--pygls/protocol/lsp_meta.py51
-rw-r--r--pygls/py.typed2
-rw-r--r--pygls/server.py616
-rw-r--r--pygls/uris.py184
-rw-r--r--pygls/workspace/__init__.py97
-rw-r--r--pygls/workspace/position_codec.py206
-rw-r--r--pygls/workspace/text_document.py238
-rw-r--r--pygls/workspace/workspace.py323
-rw-r--r--pyproject.toml114
-rw-r--r--scripts/check_client_is_uptodate.py20
-rw-r--r--scripts/generate_client.py207
-rw-r--r--scripts/generate_contributors_md.py48
-rw-r--r--tests/__init__.py28
-rw-r--r--tests/_init_server_stall_fix_hack.py33
-rw-r--r--tests/client.py180
-rw-r--r--tests/conftest.py122
-rw-r--r--tests/ls_setup.py169
-rw-r--r--tests/lsp/__init__.py0
-rw-r--r--tests/lsp/semantic_tokens/__init__.py0
-rw-r--r--tests/lsp/semantic_tokens/test_delta_missing_legend.py92
-rw-r--r--tests/lsp/semantic_tokens/test_delta_missing_legend_none.py48
-rw-r--r--tests/lsp/semantic_tokens/test_full_missing_legend.py46
-rw-r--r--tests/lsp/semantic_tokens/test_range.py103
-rw-r--r--tests/lsp/semantic_tokens/test_range_missing_legends.py47
-rw-r--r--tests/lsp/semantic_tokens/test_semantic_tokens_full.py93
-rw-r--r--tests/lsp/test_call_hierarchy.py192
-rw-r--r--tests/lsp/test_code_action.py60
-rw-r--r--tests/lsp/test_code_lens.py94
-rw-r--r--tests/lsp/test_color_presentation.py103
-rw-r--r--tests/lsp/test_completion.py48
-rw-r--r--tests/lsp/test_declaration.py161
-rw-r--r--tests/lsp/test_definition.py164
-rw-r--r--tests/lsp/test_diagnostics.py68
-rw-r--r--tests/lsp/test_document_color.py81
-rw-r--r--tests/lsp/test_document_highlight.py108
-rw-r--r--tests/lsp/test_document_link.py98
-rw-r--r--tests/lsp/test_document_symbol.py168
-rw-r--r--tests/lsp/test_errors.py135
-rw-r--r--tests/lsp/test_folding_range.py92
-rw-r--r--tests/lsp/test_formatting.py108
-rw-r--r--tests/lsp/test_hover.py149
-rw-r--r--tests/lsp/test_implementation.py164
-rw-r--r--tests/lsp/test_inlay_hints.py56
-rw-r--r--tests/lsp/test_inline_value.py60
-rw-r--r--tests/lsp/test_linked_editing_range.py103
-rw-r--r--tests/lsp/test_moniker.py94
-rw-r--r--tests/lsp/test_on_type_formatting.py122
-rw-r--r--tests/lsp/test_prepare_rename.py111
-rw-r--r--tests/lsp/test_progress.py225
-rw-r--r--tests/lsp/test_range_formatting.py116
-rw-r--r--tests/lsp/test_references.py103
-rw-r--r--tests/lsp/test_rename.py195
-rw-r--r--tests/lsp/test_selection_range.py110
-rw-r--r--tests/lsp/test_signature_help.py161
-rw-r--r--tests/lsp/test_type_definition.py163
-rw-r--r--tests/lsp/test_type_hierarchy.py127
-rw-r--r--tests/pyodide_testrunner/.gitignore1
-rw-r--r--tests/pyodide_testrunner/index.html56
-rw-r--r--tests/pyodide_testrunner/run.py131
-rw-r--r--tests/pyodide_testrunner/test-runner.js38
-rw-r--r--tests/servers/invalid_json.py27
-rw-r--r--tests/servers/large_response.py36
-rw-r--r--tests/test_client.py102
-rw-r--r--tests/test_document.py390
-rw-r--r--tests/test_feature_manager.py782
-rw-r--r--tests/test_language_server.py144
-rw-r--r--tests/test_protocol.py660
-rw-r--r--tests/test_server_connection.py135
-rw-r--r--tests/test_types.py86
-rw-r--r--tests/test_uris.py87
-rw-r--r--tests/test_workspace.py442
143 files changed, 23680 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..321d6a7
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,12 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 4
+insert_final_newline = true
+end_of_line = lf
+
+[*.{yml,yaml}]
+indent_style = space
+indent_size = 2
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 0000000..7197b47
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,29 @@
+# This file contains a list of commits that are not likely what you
+# are looking for in a blame, such as mass reformatting or renaming.
+# You can set this file as a default ignore file for blame by running
+# the following command.
+#
+# $ git config blame.ignoreRevsFile .git-blame-ignore-revs
+#
+# To temporarily not use this file add
+# --ignore-revs-file=""
+# to your blame command.
+#
+# The ignoreRevsFile can't be set globally due to blame failing if the file isn't present.
+# To not have to set the option in every repository it is needed in,
+# save the following script in your path with the name "git-bblame"
+# now you can run
+# $ git bblame $FILE
+# to use the .git-blame-ignore-revs file if it is present.
+#
+# #!/usr/bin/env bash
+# repo_root=$(git rev-parse --show-toplevel)
+# if [[ -e $repo_root/.git-blame-ignore-revs ]]; then
+# git blame --ignore-revs-file="$repo_root/.git-blame-ignore-revs" $@
+# else
+# git blame $@
+# fi
+
+
+# chore: introduce `black` formatting
+86b36e271ebde5ac4f30bf83c4e7ee42ba5af9ac
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..cdf5d8a
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,38 @@
+# These settings are for any web project
+
+# Handle line endings automatically for files detected as text
+# and leave all files detected as binary untouched.
+* text=auto
+
+# Force the following filetypes to have unix eols, so Windows does not break them
+*.* text eol=lf
+
+#
+## These files are binary and should be left untouched
+#
+
+# (binary is a macro for -text -diff)
+*.png binary
+*.jpg binary
+*.jpeg binary
+*.gif binary
+*.ico binary
+*.mov binary
+*.mp4 binary
+*.mp3 binary
+*.flv binary
+*.fla binary
+*.swf binary
+*.gz binary
+*.zip binary
+*.7z binary
+*.ttf binary
+*.eot binary
+*.woff binary
+*.pyc binary
+*.pdf binary
+*.ez binary
+*.bz2 binary
+*.swp binary
+*.whl binary
+*.docx binary
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..38d3068
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+# These are supported funding model platforms
+
+github: OpenLawLibrary
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..58449e8
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,153 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+
+defaults:
+ run:
+ shell: bash
+
+jobs:
+ pre_job:
+ runs-on: ubuntu-latest
+ outputs:
+ should_skip: ${{ steps.skip_check.outputs.should_skip }}
+ steps:
+ - id: skip_check
+ uses: fkirc/skip-duplicate-actions@v5
+ with:
+ concurrent_skipping: 'outdated_runs'
+ cancel_others: 'true'
+ skip_after_successful_duplicate: 'false'
+
+ test:
+ needs: pre_job
+ if: needs.pre_job.outputs.should_skip != 'true'
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest]
+ python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v3
+ - name: Use Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v4
+ id: setup-python
+ with:
+ python-version: ${{ matrix.python-version }}
+ allow-prereleases: true
+ - name: Install Poetry
+ uses: snok/install-poetry@v1
+ with:
+ version: '1.5.1'
+ virtualenvs-in-project: true
+ - name: Load cached venv
+ id: cached-poetry-dependencies
+ uses: actions/cache@v3
+ with:
+ path: .venv
+ key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
+ - name: Install dependencies
+ if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
+ run: poetry install --all-extras
+ - name: Run tests
+ run: |
+ source $VENV # Only needed because of Github Action caching
+ poe test
+
+ test-pyodide:
+ needs: pre_job
+ if: needs.pre_job.outputs.should_skip != 'true'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Use Python "3.10"
+ uses: actions/setup-python@v4
+ with:
+ python-version: "3.10"
+ - name: Install Poetry
+ uses: snok/install-poetry@v1
+ with:
+ virtualenvs-in-project: true
+ - name: Install Dependencies
+ if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
+ run: |
+ poetry install --with pyodide
+ - name: Run Testsuite
+ uses: nick-fields/retry@v2
+ with:
+ timeout_minutes: 10
+ max_attempts: 6
+ command: |
+ source $VENV
+ poe test-pyodide || true
+
+ lint:
+ needs: pre_job
+ if: needs.pre_job.outputs.should_skip != 'true'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - name: Use Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: "3.x"
+ - name: Install Poetry
+ uses: snok/install-poetry@v1
+ with:
+ virtualenvs-in-project: true
+ - name: Load cached venv
+ id: cached-poetry-dependencies
+ uses: actions/cache@v3
+ with:
+ path: .venv
+ key: venv-${{ hashFiles('**/poetry.lock') }}
+ - name: Install dependencies
+ if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
+ run: poetry install --all-extras --with dev
+ - name: Run lints
+ run: |
+ source $VENV
+ poe lint
+
+ build:
+ needs: pre_job
+ if: needs.pre_job.outputs.should_skip != 'true'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - name: Use Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: "3.x"
+ - name: Install Poetry
+ uses: snok/install-poetry@v1
+ with:
+ virtualenvs-in-project: true
+ - name: Load cached venv
+ id: cached-poetry-dependencies
+ uses: actions/cache@v3
+ with:
+ path: .venv
+ key: venv-${{ hashFiles('**/poetry.lock') }}
+ - name: Install dependencies
+ if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
+ run: poetry install --all-extras
+ - name: Build packages (sdist and wheel)
+ run: |
+ git describe --tags --abbrev=0
+ poetry build
+ - name: Upload builds
+ uses: actions/upload-artifact@v3
+ with:
+ name: build-artifacts
+ path: "dist/*"
diff --git a/.github/workflows/json-extension.yml b/.github/workflows/json-extension.yml
new file mode 100644
index 0000000..dd3d626
--- /dev/null
+++ b/.github/workflows/json-extension.yml
@@ -0,0 +1,74 @@
+name: vscode-playground
+
+on:
+ - push
+ - pull_request
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ defaults:
+ run:
+ shell: bash
+ working-directory: examples/vscode-playground
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - uses: actions/setup-python@v2
+ with:
+ python-version: "3.x"
+
+ - uses: actions/setup-node@v2
+ with:
+ node-version: "16"
+
+ - uses: actions/cache@v2
+ with:
+ path: ~/.npm
+ key: ${{ runner.os }}-node-${{ hashFiles('examples/vscode-playground/package-lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-node-
+
+ - name: Install dependencies
+ run: |
+ npm i
+ npm i vsce
+
+ - name: Lint
+ run: npx eslint src/*.ts
+
+ - name: Compile
+ run: npm run compile
+
+ - name: Replace package.json version
+ run: |
+ replace_packagejson_version() {
+ version_line=$(grep -o '"version".*' $1)
+ version=$(python -m json.tool package.json | awk -F'"' '/version/{print $4}')
+ build_version=$version+$2
+ build_version_line=${version_line/$version/$build_version}
+ sed -i "s|$version_line|$build_version_line|g" $1
+
+ cat $1
+ }
+
+ replace_packagejson_version package.json $GITHUB_RUN_ID
+
+ - name: Build VSIX
+ run: npx vsce package
+
+ - name: Validate VSIX
+ run: |
+ npx vsce ls | grep package.json
+ npx vsce ls | grep out/extension.js
+
+ - name: Upload VSIX
+ uses: actions/upload-artifact@v2
+ with:
+ name: vscode-playground-vsix
+ # The path must be rooted from the directory GitHub Actions starts
+ # from, not the working-directory.
+ path: examples/vscode-playground/*.vsix
+ if-no-files-found: error
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..2c8bbad
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,53 @@
+name: Release Pygls to PyPI
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ relase:
+ name: "🚀 Release 🚢"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ ssh-key: ${{secrets.CI_RELEASE_DEPLOY_KEY}}
+ fetch-depth: 0
+ - name: Use Python "3.10"
+ uses: actions/setup-python@v4
+ with:
+ python-version: "3.10"
+ - name: Install Poetry
+ uses: snok/install-poetry@v1
+ - name: Generate the latest changelog
+ uses: orhun/git-cliff-action@v2
+ id: git-cliff
+ with:
+ config: cliff.toml
+ args: --verbose --latest
+ env:
+ OUTPUT: git-cliff-changes.tmp.md
+ - name: Update the changelog
+ run: |
+ git checkout main
+ cat git-cliff-changes.tmp.md | sed -i "3r /dev/stdin" CHANGELOG.md
+ git config --global user.name 'Github Action'
+ git config --global user.email 'github.action@users.noreply.github.com'
+ git add CHANGELOG.md
+ git commit -m "chore: update CHANGELOG.md"
+ git push
+ - name: Update CONTRIBUTORS.md
+ run: |
+ git checkout main
+ poetry install
+ poetry run poe generate_contributors_md
+ if [[ $(git diff --stat CONTRIBUTORS.md) != '' ]]; then
+ git add CONTRIBUTORS.md
+ git commit -m "chore: update CONTRIBUTORS.md"
+ git push
+ fi
+ - name: Release
+ run: |
+ poetry build
+ poetry publish --username "__token__" --password ${{ secrets.PYPI_API_TOKEN }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a0505c4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,120 @@
+.idea
+.dir-locals.el
+
+*.vsix
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+cov.pth
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# OS files
+.DS_Store
+
+# mypy
+.mypy_cache
+.dmypy.json
+
+/pyodide_testrunner
+
+# VS Code settings
+.vscode/
+!examples/servers/.vscode
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..766f7fe
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,25 @@
+# .readthedocs.yaml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Set the OS, Python version and other tools you might need
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3.11"
+
+# Build documentation in the "docs/" directory with Sphinx
+sphinx:
+ configuration: docs/source/conf.py
+
+# Optional but recommended, declare the Python requirements required
+# to build your documentation
+# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
+python:
+ install:
+ - requirements: docs/requirements.txt
+ - method: pip
+ path: .
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..ae79ada
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,628 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+## [1.2.1] - 2023-11-30
+More details: https://github.com/openlawlibrary/pygls/releases/tag/v1.2.1
+
+### Bug Fixes
+
+- Handle ResponseErrors correctly
+
+### Miscellaneous Tasks
+
+- Update CHANGELOG.md
+- Clean CHANGELOG
+
+### Build
+
+- V1.2.1
+
+## [1.2.0] - 2023-11-18
+More details: https://github.com/openlawlibrary/pygls/releases/tag/v1.2.0
+
+### Bug Fixes
+
+- Remove dependency on typeguard
+- Linting and formatting issues
+- Simplify option validation check
+- Index error on empty workspace
+
+### Features
+
+- Allow user to override Python interpreter
+
+### Miscellaneous Tasks
+
+- Update CHANGELOG.md
+- Update CONTRIBUTORS.md
+- Update `poetry.lock` after removing typeguard
+- Add example configuration
+- Pin lsprotocol to 2023.0.0
+
+### Refactor
+
+- Move workspace/ into servers/ dir
+
+## [1.1.2] - 2023-10-28
+More details: https://github.com/openlawlibrary/pygls/releases/tag/v1.1.2
+
+### Documentation
+
+- Correct doc comment for PositionCodec.client_num_units
+
+### Miscellaneous Tasks
+
+- Update CHANGELOG.md
+- Update CONTRIBUTORS.md
+- Split protocol.py into own folder/files
+
+### Build
+
+- Bump urllib3 from 2.0.6 to 2.0.7
+- Allow installation with typeguard 4.x
+- V1.1.2
+
+## [1.1.1] - 2023-10-06
+More details: https://github.com/openlawlibrary/pygls/releases/tag/v1.1.1
+
+### Bug Fixes
+
+- Prevent AttributeError root_path when no workspace
+
+### CI
+
+- Fix release process
+
+### Miscellaneous Tasks
+
+- Manual changes for v1.1.0 release
+- Explicit exports from pygls.workspace
+
+### Build
+
+- Bump urllib3 from 2.0.5 to 2.0.6
+- V1.1.1
+
+## [1.1.0] - 2023-10-02
+More details: https://github.com/openlawlibrary/pygls/releases/tag/v1.1.0
+
+### Bug Fixes
+
+- Fix broken link and outdated comment
+
+- Correctly cast from UTF16 positions
+- Ensure server commands can be executed
+- Mypy lints
+- Error code of JsonRpcInternalError
+- Only show code action when there's no sum
+- Don't include trailing whitespace in code action title
+- 'bool' object has no attribute 'resolve_provider'
+- Computation of formatting and diagnostic provider capabilities
+
+### CI
+
+- Migrate to Poetry and modernise
+- Linter for conventional commits
+- Autogenerate changelog with `git-cliff`
+- Automate CONTRIBUTORS.md
+- Retry Pyodide tests
+- Test against Python 3.12
+- Use `matrix.python-version` in cache key
+- Update json-extension pipeline
+- Pin poetry to 1.5.1
+- Do not install chromium/chromedriver
+- Enable coverage reporting
+- Run all lints even when some fail
+- Increase Pyodide CI retries to 6
+
+### Documentation
+
+- Use autodoc to document client methods
+- Update docstrings
+- Change specification for commit messages
+- Typo in vscode-playground README.md
+- Add api docs for servers, protocol and workspace
+- Align docstring formatting
+- Handle methods starting with `$/`
+- Update links and code snippets
+- Rename advanced usage to user guide
+- Instructions for using plain text files with vscode-playground
+
+### Features
+
+- Add document diagnostic capability
+- Add workspace symbol resolve
+- Add workspace diagnostic support
+- Adds inline value support
+- Adds type hierarchy request support
+- Add `await` syntax support for sending edit request to client
+- Allow servers to provide `NotebookDocumentSyncOptions`
+- Initial support for notebook document syncronisation
+- Add notebook support to example `inlay_hints.py` server
+- Accept `PositionEncoding` client capability
+- Support UTF32 ans UTF8 position encoding
+
+### Miscellaneous Tasks
+
+- Update autogenerated Pygls client
+- Introduce `black` formatting
+- Add `.git-blame-ignore-revs` file
+- Delete fountain-vscode-extension
+- Update README.md
+- Bump lsprotocol version
+- Fix deprecation warning, set chrome path
+- Disable body-max-line-length check
+- Add .readthedocs.yaml
+- Strict types in uris.py and workspace.py
+- Move workspace/doc/position into own files
+- Fix mypy types
+- Maintain `Workspace` backwards compat
+- Fix use of deprecated methods in tests/test_language_server.py
+
+### Refactor
+
+- Move example json-server to `examples/servers`
+- Rename `json-vscode-extension/` -> `vscode-playground`
+- Simplify end-to-end test client fixture definition
+- Rename `Client` -> `JsonRPCClient`
+- Rename `LanguageClient` -> `BaseLanguageClient`
+- Rename `<verb>_document` to `<verb>_text_document`
+- Expose workspace via a property
+- Server `Position` class
+- Rename server Position to PositionCodec, instantiate it in Workspace
+- Reference types via `types` module
+- Make `default` argument mandatory, add type annotations
+
+### Testing
+
+- Test that the client provided token is used
+- Remove a useless sleep
+- Test cases of server initiated progress
+- Base Pyodide wheel deps off poetry.lock
+
+### Build
+
+- Bump semver in /examples/fountain-vscode-extension
+- Bump semver in /examples/json-vscode-extension
+- Bump word-wrap in /examples/json-vscode-extension
+- Lock min Python version to 3.7.9
+- Cache specific Python minor version
+- Bump lsprotocol to 2023.0.0b1
+- Release v1.1.0
+
+### Json-extension
+
+- Support cancellation in progress example
+
+### Progress
+
+- Support work done progress cancellation from client
+
+### Server
+
+- Add a type annotation to help completions in editor
+
+### Extra Notes
+#### Added
+
+- Add `LanguageClient` with LSP methods autogenerated from type annotations in `lsprotocol` ([#328])
+- Add base JSON-RPC `Client` with support for running servers in a subprocess and communicating over stdio. ([#328])
+- Support work done progress cancel ([#253])
+- Add support for `textDocument/inlayHint` and `inlayHint/resolve` requests ([#342])
+
+#### Changed
+#### Fixed
+
+- `pygls` no longer overrides the event loop for the current thread when given an explicit loop to use. ([#334])
+- Fixed `MethodTypeNotRegisteredError` when registering a `TEXT_DOCUMENT_DID_SAVE` feature with options. ([#338])
+- Fixed detection of `LanguageServer` type annotations when using string-based annotations. ([#352])
+
+[#328]: https://github.com/openlawlibrary/pygls/issues/328
+[#334]: https://github.com/openlawlibrary/pygls/issues/334
+[#338]: https://github.com/openlawlibrary/pygls/discussions/338
+[#253]: https://github.com/openlawlibrary/pygls/pull/253
+[#342]: https://github.com/openlawlibrary/pygls/pull/342
+[#304]: https://github.com/openlawlibrary/pygls/issues/304
+
+# Pre Automation Changelog
+
+## [1.0.2] - May 15th, 2023
+### Changed
+- Update typeguard to 3.x ([#327])
+
+[#327]: https://github.com/openlawlibrary/pygls/issues/327
+
+
+### Fixed
+- Data files are no longer placed inside the wrong `site-packages` folder when installing `pygls` ([#232])
+[#232]: https://github.com/openlawlibrary/pygls/issues/232
+
+
+## [1.0.1] - February 16th, 2023
+### Fixed
+
+ - Fix progress example in json extension. ([#230])
+ - Fix `AttributeErrors` in `get_configuration_async`, `get_configuration_callback`, `get_configuration_threaded` commands in json extension. ([#307])
+ - Fix type annotations for `get_configuration_async` and `get_configuration` methods on `LanguageServer` and `LanguageServerProtocol` objects ([#307])
+ - Provide `version` param for publishing diagnostics ([#303])
+ - Relaxed the Python version upper bound to `<4` ([#318])
+
+[#230]: https://github.com/openlawlibrary/pygls/issues/230
+[#303]: https://github.com/openlawlibrary/pygls/issues/303
+[#307]: https://github.com/openlawlibrary/pygls/issues/307
+[#318]: https://github.com/openlawlibrary/pygls/issues/318
+
+## [1.0.0] - 2/12/2022
+### Changed
+BREAKING CHANGE: Replaced `pydantic` with [`lsprotocol`](https://github.com/microsoft/lsprotocol)
+
+## [0.13.1] - 1/12/2022
+### Changed
+Docs now state that the v1 alpha branch is the recommended way to start new projects
+### Fixed
+Support `CodeActionKind.SourceFixAll`
+
+## [0.13.0] - 2/11/2022
+### Added
+- Add `name` and `version` arguments to the constructor of `LanguageServer` ([#274])
+### Changed
+- Default behaviour change: uncaught errors are now sent as `showMessage` errors to client.
+ Overrideable in `LanguageServer.report_server_error()`: https://github.com/openlawlibrary/pygls/pull/282
+### Fixed
+- `_data_recevied()` JSONRPC message parsing errors now caught
+- Fix "Task attached to a different loop" error in `Server.start_ws` ([#268])
+
+[#274]: https://github.com/openlawlibrary/pygls/issues/274
+[#268]: https://github.com/openlawlibrary/pygls/issues/268
+
+## [0.12.4] - 24/10/2022
+### Fixed
+- Remove upper bound on Pydantic when Python is <3.11
+
+## [0.12.3] - 24/10/2022
+### Fixed
+- Require Pydantic 1.10.2 when Python is 3.11
+
+## [0.12.2] - 26/09/2022
+### Fixed
+- Relaxed the Python version upper bound to `<4` ([#266])
+
+[#266]: https://github.com/openlawlibrary/pygls/pulls/266
+
+## [0.12.1] - 01/08/2022
+### Changed
+- `Document` objects now expose a text document's `language_id`
+- Various Pyodide improvements
+- Improved tests for more reliable CI
+
+## [0.12] - 04/07/2022
+
+### Added
+
+- Allow custom word matching for `Document.word_at_point`
+
+### Changed
+
+- Upgraded Python support to 3.10, dropping support for 3.6
+- Dependency updates, notably Pydantic 1.9 and Websockets 10
+
+### Fixed
+
+## [0.11.3] - 11/06/2021
+
+### Added
+
+### Changed
+
+- Update json-example to include an example semantic tokens method ([#204])
+
+### Fixed
+
+- Fix example extension client not detecting debug mode appropriately ([#193])
+- Fix how the `semantic_tokens_provider` field of `ServerCapabilities` is computed ([#213])
+
+[#193]: https://github.com/openlawlibrary/pygls/issues/193
+[#204]: https://github.com/openlawlibrary/pygls/issues/204
+[#213]: https://github.com/openlawlibrary/pygls/pulls/213
+
+## [0.11.2] - 07/23/2021
+
+### Added
+
+### Changed
+
+### Fixed
+
+- Fix feature manager ([#203])
+- Use `127.0.0.1` for tests and examples to avoid Docker issues ([#165])
+
+[#203]: https://github.com/openlawlibrary/pygls/issues/203
+[#165]: https://github.com/openlawlibrary/pygls/issues/165
+
+## [0.11.1] - 06/21/2021
+
+### Added
+
+### Changed
+
+- Remove defaults from all optional fields on protocol-defined types ([#198])
+
+### Fixed
+
+[#198]: https://github.com/openlawlibrary/pygls/pull/198
+
+## [0.11.0] - 06/18/2021
+
+### Added
+
+- Testing against Python 3.9 ([#186])
+- Websocket server implementation `start_websocket` for LSP ([#129])
+
+### Changed
+
+### Fixed
+
+[#186]: https://github.com/openlawlibrary/pygls/pull/186
+[#129]: https://github.com/openlawlibrary/pygls/pull/129
+
+## [0.10.3] - 05/05/2021
+
+### Added
+
+### Changed
+
+- Move from Azure Pipelines to Github Actions ([#182] & [#183])
+- Update json-example ([#175])
+- Relax text_doc type to VersionedTextDocumentIdentifier ([#174])
+
+### Fixed
+
+- Handle `BrokenPipeError` on shutdown ([#181])
+- Exit when no more data available ([#178])
+- Adding kind field to resource file operation types ([#177])
+- Don't install the tests to site-packages ([#169])
+- Don't serialize unwanted `"null"` values in server capabilities ([#166])
+
+[#183]: https://github.com/openlawlibrary/pygls/pull/183
+[#182]: https://github.com/openlawlibrary/pygls/pull/182
+[#181]: https://github.com/openlawlibrary/pygls/pull/181
+[#178]: https://github.com/openlawlibrary/pygls/pull/178
+[#177]: https://github.com/openlawlibrary/pygls/pull/177
+[#175]: https://github.com/openlawlibrary/pygls/pull/175
+[#174]: https://github.com/openlawlibrary/pygls/pull/174
+[#169]: https://github.com/openlawlibrary/pygls/pull/169
+[#166]: https://github.com/openlawlibrary/pygls/pull/166
+
+## [0.10.2] - 03/25/2021
+
+### Added
+
+### Changed
+
+- Handle lost connection; Remove psutil ([#163])
+
+### Fixed
+
+- Fix `pydantic` Unions type conversion ([#160])
+- Fix change_notifications type (pydantic bug) ([#158])
+
+[#163]: https://github.com/openlawlibrary/pygls/pull/163
+[#160]: https://github.com/openlawlibrary/pygls/pull/160
+[#158]: https://github.com/openlawlibrary/pygls/pull/158
+
+## [0.10.1] - 03/17/2021
+
+### Fixed
+
+- Remove "query" from FoldingRangeParams ([#153])
+
+[#153]: https://github.com/openlawlibrary/pygls/pull/153
+
+## [0.10.0] - 03/16/2021
+
+### Added
+
+- New LSP types and methods ([#139])
+- `pydantic` and `typeguard` deps for type-checking ([#139])
+- Runtime type matching and deserialization ([#139])
+
+### Changed
+
+- New LSP types and methods ([#139])
+- Updated docs ([#139])
+
+### Fixed
+
+- Periodically check client pid and exit server ([#149])
+- Fix server handling of client errors ([#141])
+
+[#149]: https://github.com/openlawlibrary/pygls/pull/149
+[#141]: https://github.com/openlawlibrary/pygls/pull/141
+[#139]: https://github.com/openlawlibrary/pygls/pull/139
+
+## [0.9.1] - 09/29/2020
+
+### Added
+
+- Functions to convert positions from and to utf-16 code units ([#117])
+- Type definitions for `ClientInfo` and `HoverParams` ([#125])
+
+### Changed
+
+- Exit server normally when `ctrl+c` is pressed in command shell.
+- Mark deprecated `rangeLength` optional in `TextDocumentContentChangeEvent` ([#123])
+- Optimize json-rpc message serialization ([#120])
+- Fix `__init__()` constructors in several interface types ([#125])
+- Fix valueSet type in `SymbolKindAbstract` ([#125])
+
+### Fixed
+
+- `coroutine` deprecation warning - use async def instead ([#136])
+
+[#125]: https://github.com/openlawlibrary/pygls/pull/125
+[#123]: https://github.com/openlawlibrary/pygls/pull/123
+[#120]: https://github.com/openlawlibrary/pygls/pull/120
+[#117]: https://github.com/openlawlibrary/pygls/pull/117
+[#136]: https://github.com/openlawlibrary/pygls/pull/136
+
+## [0.9.0] - 04/20/2020
+
+### Changed
+
+- Fixed missing `Undo` member from `FailureHandlingKind` in types ([#98])
+- Fixed `@command`, `@feature` and `@thread` decorators to retain type of wrapped functions ([#89])
+
+### Added
+
+- _Azure Pipelines_ build script ([#100] and [#103])
+- Run tests and linters on multiple python versions with _tox_ ([#100])
+- Use python enums in types module ([#92])
+- Add comparisons and repr support to Range and Location types ([#90])
+
+### Removed
+
+- _appveyor_ build script ([#103])
+
+[#103]: https://github.com/openlawlibrary/pygls/pull/103
+[#100]: https://github.com/openlawlibrary/pygls/pull/100
+[#98]: https://github.com/openlawlibrary/pygls/pull/98
+[#92]: https://github.com/openlawlibrary/pygls/pull/92
+[#90]: https://github.com/openlawlibrary/pygls/pull/90
+[#89]: https://github.com/openlawlibrary/pygls/pull/89
+
+## [0.8.1] - 09/05/2019
+
+### Changed
+
+- Fix parsing of partial messages and those with Content-Length keyword ([#80])
+- Fix Full SyncKind for servers accepting Incremental SyncKind ([#78])
+
+[#80]: https://github.com/openlawlibrary/pygls/pull/80
+[#78]: https://github.com/openlawlibrary/pygls/pull/78
+
+## [0.8.0] - 05/13/2019
+
+### Added
+
+- Add new types and features from LSP v3.14.0 ([#67])
+- Add API to dynamically register/unregister client capability ([#67])
+- Full text document synchronization support ([#65])
+- Add more tests for `deserialize_message` function ([#61])
+
+### Changed
+
+- Response object should contain result OR error field ([#64])
+- Fix handling parameters whose names are reserved by Python ([#56])
+
+[#67]: https://github.com/openlawlibrary/pygls/pull/67
+[#65]: https://github.com/openlawlibrary/pygls/pull/65
+[#64]: https://github.com/openlawlibrary/pygls/pull/64
+[#61]: https://github.com/openlawlibrary/pygls/pull/61
+[#56]: https://github.com/openlawlibrary/pygls/pull/56
+
+## [0.7.4] - 03/21/2019
+
+### Added
+
+- Add Pull Request template ([#54])
+
+### Changed
+
+- Update dependencies ([#53])
+- Fix initialization failure when no workspace is open ([#51])
+
+[#54]: https://github.com/openlawlibrary/pygls/pull/54
+[#53]: https://github.com/openlawlibrary/pygls/pull/53
+[#51]: https://github.com/openlawlibrary/pygls/pull/51
+
+## [0.7.3] - 01/30/2019
+
+### Added
+
+- Add _flake8_ and _bandit_ checks to _appveyor_ script
+
+### Changed
+
+- Start using [Keep a Changelog][keepachangelog] format.
+- Fix and refactor _initialize_ LSP method and add more tests
+- Fix _python 3.5_ compatibility
+- Use _python 3.5_ in _appveyor_ script
+
+## 0.7.2 - 12/28/2018
+
+- Fix README to use absolute paths for GitHub urls (needed for PyPi)
+
+## 0.7.1 - 12/28/2018
+
+- Add `publish_diagnostics` to LanguageServer
+- Fix validation function in json example
+- Correct advanced usage doc page
+- "pygls" -> _pygls_ everywhere in the docs
+
+## 0.7.0 - 12/21/2018
+
+- Open source _pygls_
+
+## 0.6.0
+
+- Modules/functions/methods reorganization
+- Add more features/commands to json-extension example
+- Add unit tests to json-extension example
+- Update `appveyor.yml`
+- Small bug fixes
+
+## 0.5.0
+
+- Return awaitable Future object from get_configuration
+- Add / Remove Workspace folders bugfix
+- Attach loop to child watcher for UNIX systems
+
+## 0.4.0
+
+- Gracefully shutdown and exit server process
+- Disallow requests after shutdown request is received
+- Added more types for type hints
+- Improved example
+
+## 0.3.0
+
+- Async functions (coroutines) support
+- Mark function to execute it in a thread pool
+- Added _lsp_ types
+- New example
+- Fixed `appveyor.yml`
+
+## 0.2.0
+
+- Added classes for `textDocument/completion` method response
+
+## 0.1.0
+
+- Initial Version
+
+[keepachangelog]: https://keepachangelog.com/en/1.0.0/
+[semver]: https://semver.org/spec/v2.0.0.html
+
+[Unreleased]: https://github.com/openlawlibrary/pygls/compare/v1.0.0...HEAD
+[1.0.0]: https://github.com/openlawlibrary/pygls/compare/v0.13.1...v1.0.0
+[0.13.1]: https://github.com/openlawlibrary/pygls/compare/v0.13.0...v0.13.1
+[0.13.0]: https://github.com/openlawlibrary/pygls/compare/v0.12.3...v0.13.0
+[0.12.4]: https://github.com/openlawlibrary/pygls/compare/v0.12.3...v0.12.4
+[0.12.3]: https://github.com/openlawlibrary/pygls/compare/v0.12.2...v0.12.3
+[0.12.2]: https://github.com/openlawlibrary/pygls/compare/v0.12.1...v0.12.2
+[0.12.1]: https://github.com/openlawlibrary/pygls/compare/v0.12...v0.12.1
+[0.12]: https://github.com/openlawlibrary/pygls/compare/v0.11.3...v0.12
+[0.11.3]: https://github.com/openlawlibrary/pygls/compare/v0.11.2...v0.11.3
+[0.11.2]: https://github.com/openlawlibrary/pygls/compare/v0.11.1...v0.11.2
+[0.11.1]: https://github.com/openlawlibrary/pygls/compare/v0.11.0...v0.11.1
+[0.11.0]: https://github.com/openlawlibrary/pygls/compare/v0.10.3...v0.11.0
+[0.10.3]: https://github.com/openlawlibrary/pygls/compare/v0.10.2...v0.10.3
+[0.10.2]: https://github.com/openlawlibrary/pygls/compare/v0.10.1...v0.10.2
+[0.10.1]: https://github.com/openlawlibrary/pygls/compare/v0.10.0...v0.10.1
+[0.10.0]: https://github.com/openlawlibrary/pygls/compare/v0.9.1...v0.10.0
+[0.9.1]: https://github.com/openlawlibrary/pygls/compare/v0.9.0...v0.9.1
+[0.9.0]: https://github.com/openlawlibrary/pygls/compare/v0.8.1...v0.9.0
+[0.8.1]: https://github.com/openlawlibrary/pygls/compare/v0.8.0...v0.8.1
+[0.8.0]: https://github.com/openlawlibrary/pygls/compare/v0.7.4...v0.8.0
+[0.7.4]: https://github.com/openlawlibrary/pygls/compare/v0.7.3...v0.7.4
+[0.7.3]: https://github.com/openlawlibrary/pygls/compare/v0.7.2...v0.7.3
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..e651720
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,46 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others’ private information, such as a physical or electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at <opensource@openlawlib.org>. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://contributor-covenant.org/version/1/4][version]
+
+[homepage]: https://contributor-covenant.org
+[version]: https://contributor-covenant.org/version/1/4/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..9a97646
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,68 @@
+# Contributing to _pygls_
+
+Welcome, and thank you for your interest in contributing to _pygls_!
+
+There are many ways in which you can contribute, beyond writing code. The goal of this document is to provide a high-level overview of how you can get involved.
+
+## Reporting Issues
+
+Have you identified a reproducible problem in _pygls_? Have a feature request? We want to hear about it! Here's how you can make reporting your issue as effective as possible.
+
+### Look For an Existing Issue
+
+Before you create a new issue, please do a search in [open issues](https://github.com/openlawlibrary/pygls/issues) to see if the issue or feature request has already been filed.
+
+Be sure to scan through the [most popular](https://github.com/openlawlibrary/pygls/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request+sort%3Areactions-%2B1-desc) feature requests.
+
+If you find your issue already exists, make relevant comments and add your [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments). Use a reaction in place of a "+1" comment:
+
+* 👍 - upvote
+* 👎 - downvote
+
+If you cannot find an existing issue that describes your bug or feature, create a new issue using the guidelines below.
+
+### Writing Good Bug Reports and Feature Requests
+
+File a single issue per problem and feature request. Do not enumerate multiple bugs or feature requests in the same issue.
+
+Do not add your issue as a comment to an existing issue unless it's for the identical input. Many issues look similar, but have different causes.
+
+The more information you can provide, the more likely someone will be successful at reproducing the issue and finding a fix.
+
+Please include the following with each issue:
+
+* Reproducible steps (1... 2... 3...) that cause the issue
+
+* What you expected to see, versus what you actually saw
+
+* Images, animations, or a link to a video showing the issue occurring, if appropriate
+
+* A code snippet that demonstrates the issue or a link to a code repository the developers can easily pull down to recreate the issue locally
+
+ * **Note:** Because the developers need to copy and paste the code snippet, including a code snippet as a media file (i.e. .gif) is not sufficient.
+
+* If using VS Code, errors from the Dev Tools Console (open from the menu: Help > Toggle Developer Tools)
+
+### Final Checklist
+
+Please remember to do the following:
+
+* [ ] Search the issue repository to ensure your report is a new issue
+
+* [ ] Simplify your code around the issue to better isolate the problem
+
+* [ ] If you are committing a PR, please update the [changelog] and [contributors] documents, as appropriate. For the [changelog], only _notable_ changes should be added following the _[Keep a Changelog][keepachangelog]_ format.
+
+Don't feel bad if the developers can't reproduce the issue right away. They will simply ask for more information!
+
+## Thank You
+
+Your contributions to open source, large or small, make great projects like this possible. Thank you for taking the time to contribute.
+
+## Attribution
+
+This _Contributing to pygls_ document is adapted from VS Code's _[Contributing to VS Code](https://github.com/Microsoft/vscode/blob/master/CONTRIBUTING.md)_.
+
+[changelog]: https://github.com/openlawlibrary/pygls/blob/master/CHANGELOG.md
+[contributors]: https://github.com/openlawlibrary/pygls/blob/master/CONTRIBUTORS.md
+[keepachangelog]: https://keepachangelog.com/en/1.0.0/
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
new file mode 100644
index 0000000..2641814
--- /dev/null
+++ b/CONTRIBUTORS.md
@@ -0,0 +1,31 @@
+# Contributors (contributions)
+* [alcarney](https://github.com/alcarney) (99)
+* [anu-ka](https://github.com/anu-ka) (1)
+* [augb](https://github.com/augb) (35)
+* [berquist](https://github.com/berquist) (1)
+* [brettcannon](https://github.com/brettcannon) (2)
+* [D4N](https://github.com/D4N) (1)
+* [danixeee](https://github.com/danixeee) (321)
+* [deathaxe](https://github.com/deathaxe) (24)
+* [dependabot[bot]](https://github.com/apps/dependabot) (8)
+* [dgreisen](https://github.com/dgreisen) (4)
+* [DillanCMills](https://github.com/DillanCMills) (1)
+* [dimbleby](https://github.com/dimbleby) (11)
+* [dinvlad](https://github.com/dinvlad) (9)
+* [eirikpre](https://github.com/eirikpre) (1)
+* [karthiknadig](https://github.com/karthiknadig) (10)
+* [KOLANICH](https://github.com/KOLANICH) (3)
+* [LaurenceWarne](https://github.com/LaurenceWarne) (2)
+* [MatejKastak](https://github.com/MatejKastak) (3)
+* [muffinmad](https://github.com/muffinmad) (2)
+* [nemethf](https://github.com/nemethf) (1)
+* [oliversen](https://github.com/oliversen) (2)
+* [otreblan](https://github.com/otreblan) (1)
+* [pappasam](https://github.com/pappasam) (7)
+* [perimosocordiae](https://github.com/perimosocordiae) (1)
+* [perrinjerome](https://github.com/perrinjerome) (25)
+* [renatav](https://github.com/renatav) (2)
+* [RossBencina](https://github.com/RossBencina) (5)
+* [tombh](https://github.com/tombh) (60)
+* [tsugumi-sys](https://github.com/tsugumi-sys) (1)
+* [zanieb](https://github.com/zanieb) (5)
diff --git a/HISTORY.md b/HISTORY.md
new file mode 100644
index 0000000..b5621a0
--- /dev/null
+++ b/HISTORY.md
@@ -0,0 +1,17 @@
+# History of [_pygls_][pygls]
+
+This is the story of [_pygls_][pygls]' inception as recounted by its original project creator, [@augb][augb].
+
+While working at [Open Law Library][openlaw] as a programmer, we created a VS Code extension originally written in TypeScript called _Codify_. _Codify_ processes legal XML into legal code. Since our codification process was written in Python we were faced with the choice of slower performance to roundtrip from TypeScript to Python and back, or duplicating the logic in TypeScript. Neither option was really good. I had the idea of using the [Language Server Protocol (LSP)][lsp] to communicate with a Python LSP server. Existing Python language servers were focused on Python the language. We needed a generic language server since we were dealing with XML. [David Greisen][dgreisen], agreed with this approach. Thus, [_pygls_][pygls] was born.
+
+I, [@augb][augb], was the project manager for the project. Daniel Elero ([@danixeee][danixeee]) did the coding. When I left Open Law Library, Daniel took over the project for a time.
+
+It was open sourced on December 21, 2018. The announcement on Hacker News is [here][announcement].
+
+[augb]: https://github.com/augb
+[announcement]: https://news.ycombinator.com/item?id=18735413
+[danixeee]: https://github.com/danixeee
+[dgreisen]: https://github.com/dgreisen
+[lsp]: https://microsoft.github.io/language-server-protocol/specification
+[openlaw]: https://openlawlib.org/
+[pygls]: https://github.com/openlawlibrary/pygls
diff --git a/Implementations.md b/Implementations.md
new file mode 100644
index 0000000..d4f3e34
--- /dev/null
+++ b/Implementations.md
@@ -0,0 +1,32 @@
+# Implementations Based on _pygls_
+
+| Name | Maintainer | Repository (optional) | Notes |
+|--------------------------|-----------------------------------------------------------------------|----------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|
+| _Anakin Language Server_ | [Andrii Kolomoiets](https://github.com/muffinmad) | [anakin-language-server](https://github.com/muffinmad/anakin-language-server) | Yet another Jedi Python Language Server |
+| _Cmake Language Server_ | [Regen100](https://github.com/regen100) | [cmake-language-server](https://github.com/regen100/cmake-language-server) | CMake LSP Implementation
+| _Codify Language Server_ | [Open Law Library](http://www.openlawlib.org/) | (closed source) | Used in our VS Code extension, _[Codify](https://marketplace.visualstudio.com/items?itemName=openlawlibrary.open-law-codify)_ |
+| _CrossHair Language Server_ | [Phillip Schanely](http://github.com/pschanely) | [CrossHair](https://github.com/pschanely/CrossHair) | Supports [this VS Code extension](https://marketplace.visualstudio.com/items?itemName=CrossHair.crosshair) for Python contract testing. |
+| _Esbonio Language Server_| [Alex Carney](https://github.com/alcarney) | [esbonio](https://github.com/swyddfa/esbonio) | Language server for [Sphinx](https://www.sphinx-doc.org/en/master/) documentation projects |
+| _Helios Language Server_ | [et9797](https://github.com/et9797) | [helios-language-server](https://github.com/et9797/helios-language-server) | Language Server for [Helios](https://github.com/Hyperion-BT/Helios) smart contract language |
+| _Hy Language Server_ | [Rintaro Okamura](https://github.com/rinx) | [hy-language-server](https://github.com/rinx/hy-language-server) | Hy Language Server wrapping [Jedhy](https://github.com/ekaschalk/jedhy) |
+| _Jedi Language Server_ | [Samuel Roeca](https://softwarejourneyman.com/pages/about.html#about) | [jedi-language-server](https://github.com/pappasam/jedi-language-server) | Python Language Server wrapping [Jedi](https://github.com/davidhalter/jedi) |
+| _Ruff Language Server_ | [Charlie Marsh](https://github.com/charliermarsh) | [ruff-lsp](https://github.com/charliermarsh/ruff-lsp) | Language Server for Python's [ruff](https://github.com/charliermarsh/ruff) |
+| _Stata Language Server_ | [Hai Bo](https://github.com/HankBO) | [stata-language-server](https://github.com/HankBO/stata-language-server) | Language Server for [Stata](https://www.stata.com/) |
+| _VSCode `autopep8` extension_ | [Microsoft](https://github.com/microsoft) | [vscode-autopep8](https://github.com/microsoft/vscode-autopep8) | VSCode extension for Python's [autopep8](https://github.com/hhatto/autopep8) |
+| _VSCode `black` extension_ | [Microsoft](https://github.com/microsoft) | [vscode-black-formatter](https://github.com/microsoft/vscode-black-formatter) | VSCode extension for Python's [black](https://github.com/psf/black) |
+| _VSCode `isort` extension_ | [Microsoft](https://github.com/microsoft) | [vscode-isort](https://github.com/microsoft/vscode-isort) | VSCode extension for Python's [isort](https://pycqa.github.io/isort) |
+| _VSCode `flake8` extension_ | [Microsoft](https://github.com/microsoft) | [vscode-flake8](https://github.com/microsoft/vscode-flake8) | VSCode extension for Python's [flake8](https://github.com/PyCQA/flake8) |
+| _VSCode `mypy` extension_ | [Microsoft](https://github.com/microsoft) | [vscode-mypy](https://github.com/microsoft/vscode-mypy) | VSCode extension for Python's [mypy](https://github.com/python/mypy) |
+| _VSCode `pylint` extension_ | [Microsoft](https://github.com/microsoft) | [vscode-pylint](https://github.com/microsoft/vscode-pylint) | VSCode extension for Python's [pylint](https://github.com/PyCQA/pylint) |
+| _VSCode `ufmt` extension_ | [Omnilib](https://github.com/omnilib) | [vscode-ufmt](https://github.com/omnilib/vscode-ufmt) | VSCode extension for Python's [ufmt](https://ufmt.omnilib.dev/en/stable/) |
+| _YARA Language Server_ | [Avast](https://github.com/avast) | [yls](https://github.com/avast/yls) | Language Server for [YARA](https://github.com/VirusTotal/yara) |
+| _zc.buildout Language Server_ | [Jérome Perrin](https://github.com/perrinjerome) | [zc.buildout.languageserver](https://github.com/perrinjerome/vscode-zc-buildout) | Language Server for [zc.buildout](http://docs.buildout.org/en/latest/) profiles. |
+
+## _(alphabetic by name, maintainer)_
+
+# Tools Based on _pygls_
+
+| Name | Maintainer | Repository (optional) | Notes |
+|--------------------------|-----------------------------------------------------------------------|----------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|
+| _lsp-devtools_ | [Alex Carney](https://github.com/alcarney) | [lsp-devtools](https://github.com/swyddfa/lsp-devtools) | An experiment in building web browser inspired developer tooling for language servers |
+| _pytest-lsp_ | [Alex Carney](https://github.com/alcarney) | [lsp-devtools](https://github.com/swyddfa/lsp-devtools) | pytest plugin for testing language servers |
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..80f617c
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright (c) Open Law Library. All rights reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..33db775
--- /dev/null
+++ b/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,14 @@
+## Description (e.g. "Related to ...", etc.)
+
+_Please replace this description with a concise description of this Pull Request._
+
+## Code review checklist (for code reviewer to complete)
+
+- [ ] Pull request represents a single change (i.e. not fixing disparate/unrelated things in a single PR)
+- [ ] Title summarizes what is changing
+- [ ] Commit messages are meaningful (see [this][commit messages] for details)
+- [ ] Tests have been included and/or updated, as appropriate
+- [ ] Docstrings have been included and/or updated, as appropriate
+- [ ] Standalone docs have been updated accordingly
+
+[commit messages]: https://conventionalcommits.org/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..085efa8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,78 @@
+[![PyPI Version](https://img.shields.io/pypi/v/pygls.svg)](https://pypi.org/project/pygls/) ![!pyversions](https://img.shields.io/pypi/pyversions/pygls.svg) ![license](https://img.shields.io/pypi/l/pygls.svg) [![Documentation Status](https://img.shields.io/badge/docs-latest-green.svg)](https://pygls.readthedocs.io/en/latest/)
+
+# pygls: The Generic Language Server Framework
+
+_pygls_ (pronounced like "pie glass") is a pythonic generic implementation of the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/specification) for use as a foundation for writing your own [Language Servers](https://langserver.org/) in just a few lines of code.
+
+## Quickstart
+```python
+from pygls.server import LanguageServer
+from lsprotocol.types import (
+ TEXT_DOCUMENT_COMPLETION,
+ CompletionItem,
+ CompletionList,
+ CompletionParams,
+)
+
+server = LanguageServer("example-server", "v0.1")
+
+@server.feature(TEXT_DOCUMENT_COMPLETION)
+def completions(params: CompletionParams):
+ items = []
+ document = server.workspace.get_document(params.text_document.uri)
+ current_line = document.lines[params.position.line].strip()
+ if current_line.endswith("hello."):
+ items = [
+ CompletionItem(label="world"),
+ CompletionItem(label="friend"),
+ ]
+ return CompletionList(is_incomplete=False, items=items)
+
+server.start_io()
+```
+
+Which might look something like this when you trigger autocompletion in your editor:
+
+![completions](https://raw.githubusercontent.com/openlawlibrary/pygls/master/docs/assets/hello-world-completion.png)
+
+## Docs and Tutorial
+
+The full documentation and a tutorial are available at <https://pygls.readthedocs.io/en/latest/>.
+
+## Projects based on _pygls_
+
+We keep a table of all known _pygls_ [implementations](https://github.com/openlawlibrary/pygls/blob/master/Implementations.md). Please submit a Pull Request with your own or any that you find are missing.
+
+## Alternatives
+
+The main alternative to _pygls_ is Microsoft's [NodeJS-based Generic Language Server Framework](https://github.com/microsoft/vscode-languageserver-node). Being from Microsoft it is focussed on extending VSCode, although in theory it could be used to support any editor. So this is where pygls might be a better choice if you want to support more editors, as pygls is not focussed around VSCode.
+
+There are also other Language Servers with "general" in their descriptons, or at least intentions. They are however only general in the sense of having powerful _configuration_. They achieve generality in so much as configuration is able to, as opposed to what programming (in _pygls'_ case) can achieve.
+ * https://github.com/iamcco/diagnostic-languageserver
+ * https://github.com/mattn/efm-langserver
+ * https://github.com/jose-elias-alvarez/null-ls.nvim (Neovim only)
+
+## Tests
+All Pygls sub-tasks require the Poetry `poe` plugin: https://github.com/nat-n/poethepoet
+
+* `poetry install --all-extras`
+* `poetry run poe test`
+* `poetry run poe test-pyodide`
+
+
+## Contributing
+
+Your contributions to _pygls_ are most welcome ❤️ Please review the [Contributing](https://github.com/openlawlibrary/pygls/blob/master/CONTRIBUTING.md) and [Code of Conduct](https://github.com/openlawlibrary/pygls/blob/master/CODE_OF_CONDUCT.md) documents for how to get started.
+
+## Donating
+
+[Open Law Library](http://www.openlawlib.org/) is a 501(c)(3) tax exempt organization. Help us maintain our open source projects and open the law to all with [sponsorship](https://github.com/sponsors/openlawlibrary).
+
+### Supporters
+
+We would like to give special thanks to the following supporters:
+* [mpourmpoulis](https://github.com/mpourmpoulis)
+
+## License
+
+Apache-2.0
diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt
new file mode 100644
index 0000000..7f49a56
--- /dev/null
+++ b/ThirdPartyNotices.txt
@@ -0,0 +1,53 @@
+THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
+For Open Law Library pygls
+
+This project incorporates material from the project(s) listed below (collectively, “Third Party Code”). Open Law Library is not the original author of the Third Party Code. The original copyright notice and license under which Open Law Library received such Third Party Code are set out below. This Third Party Code is licensed to you under their original license terms set forth below. Open Law Library reserves all other rights not expressly granted, whether by implication, estoppel or otherwise.
+
+
+1) python-language-server
+(https://github.com/palantir/python-language-server)
+
+Copyright 2017 Palantir Technologies, Inc.
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+2) vscode-extension-samples
+(https://github.com/Microsoft/vscode-extension-samples)
+
+Copyright (c) Microsoft Corporation
+
+All rights reserved.
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
+modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+3) All third party packages in node_modules folders are licensed under the licenses specified in those packages.
diff --git a/cliff.toml b/cliff.toml
new file mode 100644
index 0000000..ab22e33
--- /dev/null
+++ b/cliff.toml
@@ -0,0 +1,64 @@
+[changelog]
+header = ""
+# template for the changelog body
+# https://tera.netlify.app/docs
+body = """
+{% if version %}\
+ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
+{% else %}\
+ ## [unreleased]
+{% endif %}\
+More details: https://github.com/openlawlibrary/pygls/releases/tag/{{version}}
+{% for group, commits in commits | group_by(attribute="group") %}
+ ### {{ group | upper_first }}
+ {% for commit in commits %}
+ - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
+ {% endfor %}
+{% endfor %}\n
+"""
+# remove the leading and trailing whitespace from the template
+trim = true
+# changelog footer
+footer = ""
+
+[git]
+# parse the commits based on https://www.conventionalcommits.org
+conventional_commits = true
+# filter out the commits that are not conventional
+filter_unconventional = false # TODO: Toggle after v1.0.3 as it introduces commit linting
+# process each line of a commit as an individual commit
+split_commits = false
+# regex for preprocessing the commit messages
+commit_preprocessors = [
+ { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/openlawlibrary/pygls/issues/${2}))"}, # replace issue numbers
+]
+# regex for parsing and grouping commits
+commit_parsers = [
+ { message = "^feat", group = "Features" },
+ { message = "^fix", group = "Bug Fixes" },
+ { message = "^doc", group = "Documentation" },
+ { message = "^perf", group = "Performance" },
+ { message = "^refactor", group = "Refactor" },
+ { message = "^style", group = "Styling" },
+ { message = "^test", group = "Testing" },
+ { message = "^ci", group = "CI" },
+ { message = "^chore\\(release\\): prepare for", skip = true },
+ { message = "^chore", group = "Miscellaneous Tasks" },
+ { body = ".*security", group = "Security" },
+]
+# protect breaking changes from being skipped due to matching a skipping commit_parser
+protect_breaking_commits = false
+# filter out the commits that are not matched by commit parsers
+filter_commits = false
+# glob pattern for matching git tags
+tag_pattern = "v[0-9]*"
+# regex for skipping tags
+skip_tags = "v0.1.0-beta.1"
+# regex for ignoring tags
+ignore_tags = ""
+# sort the tags topologically
+topo_order = false
+# sort the commits inside sections by oldest/newest order
+sort_commits = "oldest"
+# limit the number of commits included in the changelog.
+# limit_commits = 42
diff --git a/commitlintrc.yaml b/commitlintrc.yaml
new file mode 100644
index 0000000..9dd7a1e
--- /dev/null
+++ b/commitlintrc.yaml
@@ -0,0 +1,28 @@
+---
+# The rules below have been manually copied from @commitlint/config-conventional
+# and match the v1.0.0 specification:
+# https://www.conventionalcommits.org/en/v1.0.0/#specification
+#
+# You can remove them and uncomment the config below when the following issue is
+# fixed: https://github.com/conventional-changelog/commitlint/issues/613
+#
+# extends:
+# - '@commitlint/config-conventional'
+rules:
+ body-leading-blank: [1, always]
+ body-max-line-length: [2, always, Infinity]
+ footer-leading-blank: [1, always]
+ footer-max-line-length: [2, always, 100]
+ header-max-length: [2, always, 100]
+ subject-case:
+ - 2
+ - never
+ - [sentence-case, start-case, pascal-case, upper-case]
+ subject-empty: [2, never]
+ subject-full-stop: [2, never, "."]
+ type-case: [2, always, lower-case]
+ type-empty: [2, never]
+ type-enum:
+ - 2
+ - always
+ - [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test]
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..69fe55e
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,19 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+SOURCEDIR = source
+BUILDDIR = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file
diff --git a/docs/assets/hello-world-completion.png b/docs/assets/hello-world-completion.png
new file mode 100644
index 0000000..669b263
--- /dev/null
+++ b/docs/assets/hello-world-completion.png
Binary files differ
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000..4d9eb83
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
+
+:end
+popd
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..2cc6cd4
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,220 @@
+alabaster==0.7.13 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3 \
+ --hash=sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2
+attrs==23.1.0 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
+ --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
+babel==2.12.1 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610 \
+ --hash=sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455
+cattrs==23.1.2 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:b2bb14311ac17bed0d58785e5a60f022e5431aca3932e3fc5cc8ed8639de50a4 \
+ --hash=sha256:db1c821b8c537382b2c7c66678c3790091ca0275ac486c76f3c8f3920e83c657
+certifi==2023.7.22 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \
+ --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9
+charset-normalizer==3.2.0 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96 \
+ --hash=sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c \
+ --hash=sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710 \
+ --hash=sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706 \
+ --hash=sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020 \
+ --hash=sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252 \
+ --hash=sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad \
+ --hash=sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329 \
+ --hash=sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a \
+ --hash=sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f \
+ --hash=sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6 \
+ --hash=sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4 \
+ --hash=sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a \
+ --hash=sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46 \
+ --hash=sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2 \
+ --hash=sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23 \
+ --hash=sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace \
+ --hash=sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd \
+ --hash=sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982 \
+ --hash=sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10 \
+ --hash=sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2 \
+ --hash=sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea \
+ --hash=sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09 \
+ --hash=sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5 \
+ --hash=sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149 \
+ --hash=sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489 \
+ --hash=sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9 \
+ --hash=sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80 \
+ --hash=sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592 \
+ --hash=sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3 \
+ --hash=sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6 \
+ --hash=sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed \
+ --hash=sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c \
+ --hash=sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200 \
+ --hash=sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a \
+ --hash=sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e \
+ --hash=sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d \
+ --hash=sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6 \
+ --hash=sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623 \
+ --hash=sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669 \
+ --hash=sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3 \
+ --hash=sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa \
+ --hash=sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9 \
+ --hash=sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2 \
+ --hash=sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f \
+ --hash=sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1 \
+ --hash=sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4 \
+ --hash=sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a \
+ --hash=sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8 \
+ --hash=sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3 \
+ --hash=sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029 \
+ --hash=sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f \
+ --hash=sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959 \
+ --hash=sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22 \
+ --hash=sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7 \
+ --hash=sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952 \
+ --hash=sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346 \
+ --hash=sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e \
+ --hash=sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d \
+ --hash=sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299 \
+ --hash=sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd \
+ --hash=sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a \
+ --hash=sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3 \
+ --hash=sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037 \
+ --hash=sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94 \
+ --hash=sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c \
+ --hash=sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858 \
+ --hash=sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a \
+ --hash=sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449 \
+ --hash=sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c \
+ --hash=sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918 \
+ --hash=sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1 \
+ --hash=sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c \
+ --hash=sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac \
+ --hash=sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa
+colorama==0.4.6 ; python_full_version >= "3.7.9" and python_version < "4" and sys_platform == "win32" \
+ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
+ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
+docutils==0.18.1 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c \
+ --hash=sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06
+exceptiongroup==1.1.3 ; python_full_version >= "3.7.9" and python_version < "3.11" \
+ --hash=sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9 \
+ --hash=sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3
+idna==3.4 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
+ --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2
+imagesize==1.4.1 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b \
+ --hash=sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a
+importlib-metadata==6.7.0 ; python_full_version >= "3.7.9" and python_version < "3.10" \
+ --hash=sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4 \
+ --hash=sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5
+jinja2==3.1.2 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \
+ --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61
+lsprotocol==2023.0.0a3 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:2896c5a30c34846e3d5687e35715961f49bf7b92a36e4fb2b707ff65f19087f7 \
+ --hash=sha256:d704e4e00419f74bece9795de4b34d02aa555fc0131fec49f59ac9eb46816e51
+markupsafe==2.1.3 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \
+ --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \
+ --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \
+ --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \
+ --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \
+ --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \
+ --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \
+ --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \
+ --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \
+ --hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \
+ --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \
+ --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \
+ --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \
+ --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \
+ --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \
+ --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \
+ --hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \
+ --hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \
+ --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \
+ --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \
+ --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \
+ --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \
+ --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \
+ --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \
+ --hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \
+ --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \
+ --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \
+ --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \
+ --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \
+ --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \
+ --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \
+ --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \
+ --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \
+ --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \
+ --hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \
+ --hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \
+ --hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \
+ --hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \
+ --hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \
+ --hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \
+ --hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \
+ --hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \
+ --hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \
+ --hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \
+ --hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \
+ --hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \
+ --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \
+ --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \
+ --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \
+ --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2
+packaging==23.1 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \
+ --hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f
+pygments==2.16.1 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692 \
+ --hash=sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29
+pytz==2023.3 ; python_full_version >= "3.7.9" and python_version < "3.9" \
+ --hash=sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588 \
+ --hash=sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb
+requests==2.31.0 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
+ --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1
+snowballstemmer==2.2.0 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 \
+ --hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a
+sphinx-rtd-theme==1.3.0 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0 \
+ --hash=sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931
+sphinx==5.3.0 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d \
+ --hash=sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5
+sphinxcontrib-applehelp==1.0.2 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a \
+ --hash=sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58
+sphinxcontrib-devhelp==1.0.2 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e \
+ --hash=sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4
+sphinxcontrib-htmlhelp==2.0.0 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07 \
+ --hash=sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2
+sphinxcontrib-jquery==4.1 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a \
+ --hash=sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae
+sphinxcontrib-jsmath==1.0.1 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178 \
+ --hash=sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8
+sphinxcontrib-qthelp==1.0.3 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72 \
+ --hash=sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6
+sphinxcontrib-serializinghtml==1.1.5 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd \
+ --hash=sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952
+typeguard==3.0.2 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:bbe993854385284ab42fd5bd3bee6f6556577ce8b50696d6cb956d704f286c8e \
+ --hash=sha256:fee5297fdb28f8e9efcb8142b5ee219e02375509cd77ea9d270b5af826358d5a
+typing-extensions==4.7.1 ; python_full_version >= "3.7.9" and python_version < "3.11" \
+ --hash=sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36 \
+ --hash=sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2
+urllib3==2.0.4 ; python_full_version >= "3.7.9" and python_version < "4" \
+ --hash=sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11 \
+ --hash=sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4
+zipp==3.15.0 ; python_full_version >= "3.7.9" and python_version < "3.10" \
+ --hash=sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b \
+ --hash=sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556
diff --git a/docs/source/conf.py b/docs/source/conf.py
new file mode 100644
index 0000000..b3b3bfc
--- /dev/null
+++ b/docs/source/conf.py
@@ -0,0 +1,288 @@
+# -*- coding: utf-8 -*-
+#
+# Configuration file for the Sphinx documentation builder.
+#
+# This file does only contain a selection of the most common options. For a
+# full list see the documentation:
+# http://www.sphinx-doc.org/en/master/config
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+import importlib.metadata
+import re
+from docutils import nodes
+
+
+# -- Project information -----------------------------------------------------
+
+project = "pygls"
+copyright = "Open Law Library"
+author = "Open Law Library"
+
+# The short X.Y version
+version = importlib.metadata.version("pygls")
+# The full version, including alpha/beta/rc tags
+release = version
+
+title = "pygls Documentation"
+description = "a pythonic generic language server"
+
+
+# -- General configuration ---------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ "sphinx.ext.autodoc",
+ "sphinx.ext.intersphinx",
+ "sphinx.ext.napoleon",
+]
+
+autodoc_member_order = "groupwise"
+autodoc_typehints = "description"
+autodoc_typehints_description_target = "all"
+
+intersphinx_mapping = {
+ "python": ("https://docs.python.org/3/", None),
+}
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ["_templates"]
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = ".rst"
+
+# The master toctree document.
+master_doc = "index"
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = "en"
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = []
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = None
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = "sphinx_rtd_theme"
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+# html_static_path = ['_static']
+
+# Custom sidebar templates, must be a dictionary that maps document names
+# to template names.
+#
+# The default sidebars (for documents that don't match any pattern) are
+# defined by theme itself. Builtin themes are using these templates by
+# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
+# 'searchbox.html']``.
+#
+# html_sidebars = {}
+
+
+# -- Options for HTMLHelp output ---------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = "pyglsdoc"
+
+
+# -- Options for LaTeX output ------------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, "pygls.tex", title, author, "manual"),
+]
+
+
+# -- Options for manual page output ------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [(master_doc, "pygls", description, [author], 1)]
+
+
+# -- Options for Texinfo output ----------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (master_doc, "pygls", title, author, "pygls", description, "Miscellaneous"),
+]
+
+
+# -- Options for Epub output -------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = project
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#
+# epub_identifier = ''
+
+# A unique identification for the text.
+#
+# epub_uid = ''
+
+# A list of files that should not be packed into the epub file.
+epub_exclude_files = ["search.html"]
+
+
+def lsp_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
+ """Link to sections within the lsp specification."""
+
+ anchor = text.replace("$/", "").replace("/", "_")
+ ref = f"https://microsoft.github.io/language-server-protocol/specification.html#{anchor}"
+
+ node = nodes.reference(rawtext, text, refuri=ref, **options)
+ return [node], []
+
+
+CODE_FENCE_PATTERN = re.compile(r"```(\w+)?")
+LINK_PATTERN = re.compile(r"\{@link ([^}]+)\}")
+LITERAL_PATTERN = re.compile(r"(?<![`:])`([^`]+)`(?!_)")
+MD_LINK_PATTERN = re.compile(r"\[`?([^\]]+?)`?\]\(([^)]+)\)")
+SINCE_PATTERN = re.compile(r"@since ([\d\.]+)")
+
+
+def process_docstring(app, what, name, obj, options, lines):
+ """Fixup LSP docstrings so that they work with reStructuredText syntax
+
+ - Replaces ``@since <version>`` with ``**LSP v<version>**``
+
+ - Replaces ``{@link <item>}`` with ``:class:`~lsprotocol.types.<item>` ``
+
+ - Replaces markdown hyperlink with reStructuredText equivalent
+
+ - Replaces inline markdown code (single "`") with reStructuredText inline code
+ (double "`")
+
+ - Inserts the required newline before a bulleted list
+
+ - Replaces code fences with code blocks
+
+ - Fixes indentation
+ """
+
+ line_breaks = []
+ code_fences = []
+
+ for i, line in enumerate(lines):
+ if line.startswith("- "):
+ line_breaks.append(i)
+
+ # Does the line need dedenting?
+ if line.startswith(" " * 4) and not lines[i - 1].startswith(" "):
+ # Be sure to modify the original list *and* the line the rest of the
+ # loop will use.
+ line = lines[i][4:]
+ lines[i] = line
+
+ if (match := SINCE_PATTERN.search(line)) is not None:
+ start, end = match.span()
+ lines[i] = "".join([line[:start], f"**LSP v{match.group(1)}**", line[end:]])
+
+ if (match := LINK_PATTERN.search(line)) is not None:
+ start, end = match.span()
+ item = match.group(1)
+
+ lines[i] = "".join(
+ [line[:start], f":class:`~lsprotocol.types.{item}`", line[end:]]
+ )
+
+ if (match := MD_LINK_PATTERN.search(line)) is not None:
+ start, end = match.span()
+ text = match.group(1)
+ target = match.group(2)
+
+ line = "".join([line[:start], f"`{text} <{target}>`__", line[end:]])
+ lines[i] = line
+
+ if (match := LITERAL_PATTERN.search(line)) is not None:
+ start, end = match.span()
+ lines[i] = "".join([line[:start], f"`{match.group(0)}` ", line[end:]])
+
+ if (match := CODE_FENCE_PATTERN.match(line)) is not None:
+ open_ = len(code_fences) % 2 == 0
+ lang = match.group(1) or ""
+
+ if open_:
+ code_fences.append((i, lang))
+ line_breaks.extend([i, i + 1])
+ else:
+ code_fences.append(i)
+
+ # Rewrite fenced code blocks
+ open_ = -1
+ for fence in code_fences:
+ if isinstance(fence, tuple):
+ open_ = fence[0] + 1
+ lines[fence[0]] = f".. code-block:: {fence[1]}"
+ else:
+ # Indent content
+ for j in range(open_, fence):
+ lines[j] = f" {lines[j]}"
+
+ lines[fence] = ""
+
+ # Insert extra line breaks
+ for offset, line in enumerate(line_breaks):
+ lines.insert(line + offset, "")
+
+
+def setup(app):
+ app.add_role("lsp", lsp_role)
+ app.connect("autodoc-process-docstring", process_docstring)
diff --git a/docs/source/index.rst b/docs/source/index.rst
new file mode 100644
index 0000000..3fc264c
--- /dev/null
+++ b/docs/source/index.rst
@@ -0,0 +1,51 @@
+.. pygls documentation master file, created by
+ sphinx-quickstart on Sun Nov 25 16:16:27 2018.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+*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.
+
+Features
+--------
+
+- cross-platform support
+- TCP/IP and STDIO communication
+- runs in asyncio event loop
+- register LSP features and custom commands as:
+
+ - asynchronous functions (coroutines)
+ - synchronous functions
+ - functions that will be executed in separate thread
+
+- thread management
+- in-memory workspace with *full* and *incremental* document updates
+- type-checking
+- good test coverage
+
+Python Versions
+---------------
+
+*pygls* works with Python 3.8+.
+
+User Guide
+----------
+
+.. toctree::
+ :maxdepth: 2
+
+ pages/getting_started
+ pages/tutorial
+ pages/user-guide
+ pages/testing
+ pages/migrating-to-v1
+ pages/reference
+
+
+.. _Language Server Protocol: https://microsoft.github.io/language-server-protocol/specification
+.. _Language server: https://langserver.org/
+.. _pygls: https://github.com/openlawlibrary/pygls
diff --git a/docs/source/pages/getting_started.rst b/docs/source/pages/getting_started.rst
new file mode 100644
index 0000000..e73d20f
--- /dev/null
+++ b/docs/source/pages/getting_started.rst
@@ -0,0 +1,94 @@
+Getting Started
+===============
+
+This document explains how to install *pygls* and get started writing language
+servers that are based on it.
+
+.. note::
+
+ Before going any further, if you are not familiar with *language servers*
+ and *Language Server Protocol*, we recommend reading following articles:
+
+ - `Language Server Protocol Overview <https://microsoft.github.io/language-server-protocol/overview>`_
+ - `Language Server Protocol Specification <https://microsoft.github.io/language-server-protocol/specification>`_
+ - `Language Server Protocol SDKs <https://microsoft.github.io/language-server-protocol/implementors/sdks/>`_
+
+
+Installation
+------------
+
+To get the latest release from *PyPI*, simply run:
+
+.. code:: console
+
+ pip install pygls
+
+Alternatively, *pygls* source code can be downloaded from our `GitHub`_ page and installed with following command:
+
+.. code:: console
+
+ pip install git+https://github.com/openlawlibrary/pygls
+
+Quick Start
+-----------
+
+Spin the Server Up
+~~~~~~~~~~~~~~~~~~
+
+*pygls* is a language server that can be started without writing any additional
+code:
+
+.. code:: python
+
+ from pygls.server import LanguageServer
+
+ server = LanguageServer('example-server', 'v0.1')
+
+ server.start_tcp('127.0.0.1', 8080)
+
+After running the code above, server will start listening for incoming
+``Json RPC`` requests on ``http://127.0.0.1:8080``.
+
+Register Features and Commands
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+*pygls* comes with an API for registering additional features like
+``code completion``, ``find all references``, ``go to definition``, etc.
+
+.. code:: python
+
+ @server.feature(TEXT_DOCUMENT_COMPLETION, CompletionOptions(trigger_characters=[',']))
+ def completions(params: CompletionParams):
+ """Returns completion items."""
+ return CompletionList(
+ is_incomplete=False,
+ items=[
+ CompletionItem(label='Item1'),
+ CompletionItem(label='Item2'),
+ CompletionItem(label='Item3'),
+ ]
+ )
+
+… as well as custom commands:
+
+.. code:: python
+
+ @server.command('myVerySpecialCommandName')
+ def cmd_return_hello_world(ls, *args):
+ return 'Hello World!'
+
+See the :mod:`lsprotocol.types` module for the complete and canonical list of available features.
+
+Tutorial
+--------
+
+We recommend completing the :ref:`tutorial <tutorial>`, especially if you
+haven't worked with language servers before.
+
+User Guide
+----------
+
+To reveal the full potential of *pygls* (``thread management``, ``coroutines``,
+``multi-root workspace``, ``TCP/STDIO communication``, etc.) keep reading.
+
+.. _GitHub: https://github.com/openlawlibrary/pygls
diff --git a/docs/source/pages/migrating-to-v1.rst b/docs/source/pages/migrating-to-v1.rst
new file mode 100644
index 0000000..527a810
--- /dev/null
+++ b/docs/source/pages/migrating-to-v1.rst
@@ -0,0 +1,241 @@
+Migrating 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 "<stdin>", line 1, in <module>
+ | File "/.../python3.10/site-packages/cattrs/converters.py", li
+ ne 309, in structure
+ | return self._structure_func.dispatch(cl)(obj, cl)
+ | File "<cattrs generated structure __main__.ExampleConfig-2>", 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 "<cattrs generated structure __main__.ExampleConfig-2>", 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
diff --git a/docs/source/pages/reference.rst b/docs/source/pages/reference.rst
new file mode 100644
index 0000000..2b0aa56
--- /dev/null
+++ b/docs/source/pages/reference.rst
@@ -0,0 +1,8 @@
+API Reference
+=============
+
+.. toctree::
+ :glob:
+ :maxdepth: 2
+
+ reference/*
diff --git a/docs/source/pages/reference/clients.rst b/docs/source/pages/reference/clients.rst
new file mode 100644
index 0000000..b4c37ed
--- /dev/null
+++ b/docs/source/pages/reference/clients.rst
@@ -0,0 +1,8 @@
+Clients
+=======
+
+.. autoclass:: pygls.lsp.client.BaseLanguageClient
+ :members:
+
+.. autoclass:: pygls.client.JsonRPCClient
+ :members:
diff --git a/docs/source/pages/reference/protocol.rst b/docs/source/pages/reference/protocol.rst
new file mode 100644
index 0000000..e9bd2f7
--- /dev/null
+++ b/docs/source/pages/reference/protocol.rst
@@ -0,0 +1,11 @@
+Protocol
+========
+
+
+.. autoclass:: pygls.protocol.LanguageServerProtocol
+ :members:
+
+.. autoclass:: pygls.protocol.JsonRPCProtocol
+ :members:
+
+.. autofunction:: pygls.protocol.default_converter
diff --git a/docs/source/pages/reference/servers.rst b/docs/source/pages/reference/servers.rst
new file mode 100644
index 0000000..2a664ed
--- /dev/null
+++ b/docs/source/pages/reference/servers.rst
@@ -0,0 +1,11 @@
+Servers
+=======
+
+.. autoclass:: pygls.server.LanguageServer
+ :members:
+
+.. autoclass:: pygls.server.Server
+ :members:
+
+
+
diff --git a/docs/source/pages/reference/types.rst b/docs/source/pages/reference/types.rst
new file mode 100644
index 0000000..1cbcb1f
--- /dev/null
+++ b/docs/source/pages/reference/types.rst
@@ -0,0 +1,10 @@
+Types
+=====
+
+LSP type definitions in ``pygls`` are provided by the `lsprotocol <https://github.com/microsoft/lsprotocol>`__ library
+
+.. automodule:: lsprotocol.types
+ :members:
+ :undoc-members:
+
+
diff --git a/docs/source/pages/reference/workspace.rst b/docs/source/pages/reference/workspace.rst
new file mode 100644
index 0000000..1e392dc
--- /dev/null
+++ b/docs/source/pages/reference/workspace.rst
@@ -0,0 +1,9 @@
+Workspace
+=========
+
+.. autoclass:: pygls.workspace.TextDocument
+ :members:
+
+.. autoclass:: pygls.workspace.Workspace
+ :members:
+
diff --git a/docs/source/pages/testing.rst b/docs/source/pages/testing.rst
new file mode 100644
index 0000000..9910835
--- /dev/null
+++ b/docs/source/pages/testing.rst
@@ -0,0 +1,24 @@
+.. _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 <passing-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
diff --git a/docs/source/pages/tutorial.rst b/docs/source/pages/tutorial.rst
new file mode 100644
index 0000000..6db7f6c
--- /dev/null
+++ b/docs/source/pages/tutorial.rst
@@ -0,0 +1,207 @@
+.. _tutorial:
+
+Tutorial
+========
+
+In order to help you with using *pygls* in VSCode, we have created the `vscode-playground`_ extension.
+
+.. note::
+
+ This extension is meant to provide an environment in which you can easily experiment with a *pygls* powered language server.
+ It is not necessary in order to use *pygls* with other text editors.
+
+ If you decide you want to publish your language server on the VSCode marketplace this
+ `template extension <https://github.com/microsoft/vscode-python-tools-extension-template>`__
+ from Microsoft a useful starting point.
+
+Prerequisites
+-------------
+
+In order to setup and run the example VSCode extension, you need following software
+installed:
+
+* `Visual Studio Code <https://code.visualstudio.com/>`_ editor
+* `Python 3.8+ <https://www.python.org/downloads/>`_
+* `vscode-python <https://marketplace.visualstudio.com/items?itemName=ms-python.python>`_ extension
+* A clone of the `pygls <https://github.com/openlawlibrary/pygls>`_ repository
+
+.. note::
+ If you have created virtual environment, make sure that you have *pygls* installed
+ and `selected appropriate python interpreter <https://code.visualstudio.com/docs/python/environments>`_
+ for the *pygls* project.
+
+
+Running the Example
+-------------------
+
+For a step-by-step guide on how to setup and run the example follow `README`_.
+
+Hacking the Extension
+---------------------
+
+When you have successfully setup and run the extension, open `server.py`_ and
+go through the code.
+
+We have implemented following capabilities:
+
+- ``textDocument/completion`` feature
+- ``countDownBlocking`` command
+- ``countDownNonBlocking`` command
+- ``textDocument/didChange`` feature
+- ``textDocument/didClose`` feature
+- ``textDocument/didOpen`` feature
+- ``showConfigurationAsync`` command
+- ``showConfigurationCallback`` command
+- ``showConfigurationThread`` command
+
+When running the extension in *debug* mode, you can set breakpoints to see
+when each of above mentioned actions gets triggered.
+
+Visual Studio Code supports *Language Server Protocol*, which means, that every
+action on the client-side, will result in sending request or notification to
+the server via JSON RPC.
+
+Debug Code Completions
+~~~~~~~~~~~~~~~~~~~~~~
+
+Set a breakpoint inside ``completion`` function and go back to opened *json*
+file in your editor. Now press ``ctrl + space`` (``control + space`` on mac) to
+show completion list and you will hit the breakpoint. When you continue
+debugging, the completion list pop-up won't show up because it was closing when
+the editor lost focus.
+
+Similarly, you can debug any feature or command.
+
+Keep the breakpoint and continue to the next section.
+
+Blocking Command Test
+~~~~~~~~~~~~~~~~~~~~~
+
+In order to demonstrate you that blocking the language server will reject other
+requests, we have registered a custom command which counts down 10 seconds and
+sends notification messages to the client.
+
+1. Press **F1**, find and run ``Count down 10 seconds [Blocking]`` command.
+2. Try to show *code completions* while counter is still ticking.
+
+Language server is **blocked**, because ``time.sleep`` is a
+**blocking** operation. This is why you didn't hit the breakpoint this time.
+
+.. hint::
+
+ To make this command **non blocking**, add ``@json_server.thread()``
+ decorator, like in code below:
+
+ .. code-block:: python
+
+ @json_server.thread()
+ @json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_BLOCKING)
+ def count_down_10_seconds_blocking(ls, *args):
+ # Omitted
+
+ *pygls* uses a **thread pool** to execute functions that are marked with
+ a ``thread`` decorator.
+
+
+Non-Blocking Command Test
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Python 3.4 introduced *asyncio* module which allows us to use asynchronous
+functions (aka *coroutines*) and do `cooperative multitasking`_. Using the
+`await` keyword inside your coroutine will give back control to the
+scheduler and won't block the main thread.
+
+1. Press **F1** and run the ``Count down 10 seconds [Non Blocking]`` command.
+2. Try to show *code completions* while counter is still ticking.
+
+Bingo! We hit the breakpoint! What just happened?
+
+The language server was **not blocked** because we used ``asyncio.sleep`` this
+time. The language server was executing *just* in the *main* thread.
+
+
+Text Document Operations
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Opening and closing a JSON file will display appropriate notification message
+in the bottom right corner of the window and the file content will be
+validated. Validation will be performed on content changes, as well.
+
+Show Configuration Data
+~~~~~~~~~~~~~~~~~~~~~~~
+
+There are *three* ways for getting configuration section from the client
+settings.
+
+.. note::
+
+ *pygls*' built-in coroutines are suffixed with *async* word, which means that
+ you have to use the *await* keyword in order to get the result (instead of
+ *asyncio.Future* object).
+
+- **Get the configuration inside a coroutine**
+
+.. code-block:: python
+
+ config = await ls.get_configuration_async(ConfigurationParams([
+ ConfigurationItem('', JsonLanguageServer.CONFIGURATION_SECTION)
+ ]))
+
+- **Get the configuration inside a normal function**
+
+We already saw that we *don't* want to block the main thread. Sending the
+configuration request to the client will result with the response from it, but
+we don't know when. You have to pass *callback* function which will be
+triggered once response from the client is received.
+
+.. code-block:: python
+
+ def _config_callback(config):
+ try:
+ example_config = config[0].exampleConfiguration
+
+ ls.show_message(
+ f'jsonServer.exampleConfiguration value: {example_config}'
+ )
+
+ except Exception as e:
+ ls.show_message_log(f'Error ocurred: {e}')
+
+ ls.get_configuration(ConfigurationParams([
+ ConfigurationItem('', JsonLanguageServer.CONFIGURATION_SECTION)
+ ]), _config_callback)
+
+As you can see, the above code is hard to read.
+
+- **Get the configuration inside a threaded function**
+
+Blocking operations such as ``future.result(1)`` should not be used inside
+normal functions, but to increase the code readability, you can add the
+*thread* decorator to your function to use *pygls*' *thread pool*.
+
+.. code-block:: python
+
+ @json_server.thread()
+ @json_server.command(JsonLanguageServer.CMD_SHOW_CONFIGURATION_THREAD)
+ def show_configuration_thread(ls: JsonLanguageServer, *args):
+ """Gets exampleConfiguration from the client settings using a thread pool."""
+ try:
+ config = ls.get_configuration(ConfigurationParams([
+ ConfigurationItem('', JsonLanguageServer.CONFIGURATION_SECTION)
+ ])).result(2)
+
+ # ...
+
+This way you won't block the main thread. *pygls* will start a new thread when
+executing the function.
+
+Modify the Example
+~~~~~~~~~~~~~~~~~~
+
+We encourage you to continue to :ref:`user guide <user-guide>` and
+modify this example.
+
+.. _vscode-playground: https://github.com/openlawlibrary/pygls/blob/main/examples/vscode-playground
+.. _README: https://github.com/openlawlibrary/pygls/blob/main/examples/vscode-playground/README.md
+.. _server.py: https://github.com/openlawlibrary/pygls/blob/main/examples/servers/json_server.py
+.. _cooperative multitasking: https://en.wikipedia.org/wiki/Cooperative_multitasking
diff --git a/docs/source/pages/user-guide.rst b/docs/source/pages/user-guide.rst
new file mode 100644
index 0000000..c0ce43e
--- /dev/null
+++ b/docs/source/pages/user-guide.rst
@@ -0,0 +1,536 @@
+.. _user-guide:
+
+User Guide
+==========
+
+Language Server
+---------------
+
+The language server is responsible for managing the connection with the client as well as sending and receiving messages over
+the `Language Server Protocol <https://microsoft.github.io/language-server-protocol/>`__
+which is based on the `Json RPC protocol <https://www.jsonrpc.org/specification>`__.
+
+Connections
+~~~~~~~~~~~
+
+*pygls* supports :ref:`ls-tcp`, :ref:`ls-stdio` and :ref:`ls-websocket` connections.
+
+.. _ls-tcp:
+
+TCP
+^^^
+
+TCP connections are usually used while developing the language server.
+This way the server can be started in *debug* mode separately and wait
+for the client connection.
+
+.. note:: Server should be started **before** the client.
+
+The code snippet below shows how to start the server in *TCP* mode.
+
+.. code:: python
+
+ from pygls.server import LanguageServer
+
+ server = LanguageServer('example-server', 'v0.1')
+
+ server.start_tcp('127.0.0.1', 8080)
+
+.. _ls-stdio:
+
+STDIO
+^^^^^
+
+STDIO connections are useful when client is starting the server as a child
+process. This is the way to go in production.
+
+The code snippet below shows how to start the server in *STDIO* mode.
+
+.. code:: python
+
+ from pygls.server import LanguageServer
+
+ server = LanguageServer('example-server', 'v0.1')
+
+ server.start_io()
+
+.. _ls-websocket:
+
+WEBSOCKET
+^^^^^^^^^
+
+WEBSOCKET connections are used when you want to expose language server to
+browser based editors.
+
+The code snippet below shows how to start the server in *WEBSOCKET* mode.
+
+.. code:: python
+
+ from pygls.server import LanguageServer
+
+ server = LanguageServer('example-server', 'v0.1')
+
+ server.start_ws('0.0.0.0', 1234)
+
+Logging
+~~~~~~~
+
+Logs are useful for tracing client requests, finding out errors and
+measuring time needed to return results to the client.
+
+*pygls* uses built-in python *logging* module which has to be configured
+before server is started.
+
+Official documentation about logging in python can be found
+`here <https://docs.python.org/3/howto/logging-cookbook.html>`__. Below
+is the minimal setup to setup logging in *pygls*:
+
+.. code:: python
+
+ import logging
+
+ from pygls.server import LanguageServer
+
+ logging.basicConfig(filename='pygls.log', filemode='w', level=logging.DEBUG)
+
+ server = LanguageServer('example-server', 'v0.1')
+
+ server.start_io()
+
+Overriding ``LanguageServerProtocol``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you have a reason to override the existing ``LanguageServerProtocol`` class,
+you can do that by inheriting the class and passing it to the ``LanguageServer``
+constructor.
+
+Custom Error Reporting
+~~~~~~~~~~~~~~~~~~~~~~
+
+The default :class:`~pygls.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.server.LanguageServer.report_server_error` method like so:
+
+.. code:: python
+
+ class CustomLanguageServer(LanguageServer):
+ def report_server_error(self, error: Exception, source: Union[PyglsError, JsonRpcException]):
+ pass
+
+Handling Client Messages
+------------------------
+
+.. admonition:: Requests vs Notifications
+
+ Unlike a *request*, a *notification* message has no ``id`` field and the server *must not* reply to it.
+ This means that, even if you return a result inside a handler function for a notification, the result won't be passed to the client.
+
+ The ``Language Server Protocol``, unlike ``Json RPC``, allows bidirectional communication between the server and the client.
+
+For the majority of the time, a language server will be responding to requests and notifications sent from the client.
+*pygls* refers to the handlers for all of these messages as *features* with one exception.
+
+The Language Server protocol allows a server to define named methods that a client can invoke by sending a :lsp:`workspace/executeCommand` request.
+Unsurprisingly, *pygls* refers to these named methods a *commands*.
+
+*Built-In* Features
+~~~~~~~~~~~~~~~~~~~
+
+*pygls* comes with following predefined set of handlers for the following
+`Language Server Protocol <https://microsoft.github.io/language-server-protocol/>`__
+(LSP) features:
+
+.. note::
+
+ *Built-in* features in most cases should *not* be overridden.
+
+ If you need to do some additional processing of one of the messages listed below, register a feature with the same name and your handler will be called immediately after the corresponding built-in feature.
+
+**Lifecycle Messages**
+
+- The :lsp:`initialize` request is sent as a first request from client to the server to setup their communication.
+ *pygls* automatically computes registered LSP capabilities and sends them as part of the :class:`~lsprotocol.types.InitializeResult` response.
+
+- The :lsp:`shutdown` request is sent from the client to the server to ask the server to shutdown.
+
+- The :lsp:`exit` notification is sent from client to the server to ask the server to exit the process.
+ *pygls* automatically releases all resources and stops the process.
+
+**Text Document Synchronization**
+
+- The :lsp:`textDocument/didOpen` notification will tell *pygls* to create a document in the in-memory workspace which will exist as long as the document is opened in editor.
+
+- The :lsp:`textDocument/didChange` notification will tell *pygls* to update the document text.
+ *pygls* supports *full* and *incremental* document changes.
+
+- The :lsp:`textDocument/didClose` notification will tell *pygls* to remove a document from the in-memory workspace.
+
+**Notebook Document Synchronization**
+
+- The :lsp:`notebookDocument/didOpen` notification will tell *pygls* to create a notebook document in the in-memory workspace which will exist as long as the document is opened in editor.
+
+- The :lsp:`notebookDocument/didChange` notification will tell *pygls* to update the notebook document include its content, metadata, execution results and cell structure.
+
+- The :lsp:`notebookDocument/didClose` notification will tell *pygls* to remove the notebook from the in-memory workspace.
+
+**Miscellanous**
+
+- The :lsp:`workspace/didChangeWorkspaceFolders` notification will tell *pygls* to update in-memory workspace folders.
+
+- The :lsp:`workspace/executeCommand` request will tell *pygls* to execute a custom command.
+
+- The :lsp:`$/setTrace` notification tells *pygls* to update the server's :class:`TraceValue <lsprotocol.types.TraceValues>`.
+
+.. _ls-handlers:
+
+Registering Handlers
+~~~~~~~~~~~~~~~~~~~~
+
+.. seealso::
+
+ It's recommeded that you follow the :ref:`tutorial <tutorial>` before reading this section.
+
+- The :func:`~pygls.server.LanguageServer.feature` decorator is used to register a handler for a given LSP message.
+- The :func:`~pygls.server.LanguageServer.command` decorator is used to register a named command.
+
+The following applies to both feature and command handlers.
+
+Language servers using *pygls* run in an *asyncio event loop*.
+They *asynchronously* listen for incoming messages and, depending on the way handler is registered, apply different execution strategies to process the message.
+
+Depending on the use case, handlers can be registered in three different ways:
+
+- as an :ref:`async <ls-handler-async>` function
+- as a :ref:`synchronous <ls-handler-sync>` function
+- as a :ref:`threaded <ls-handler-thread>` function
+
+.. _ls-handler-async:
+
+*Asynchronous* Functions (*Coroutines*)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+*pygls* supports ``python 3.8+`` which has a keyword ``async`` to
+specify coroutines.
+
+The code snippet below shows how to register a command as a coroutine:
+
+.. code:: python
+
+ @json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_NON_BLOCKING)
+ 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 <https://docs.python.org/3.5/glossary.html#term-awaitable>`__
+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 <https://microsoft.github.io/language-server-protocol/specification#cancelRequest>`__).
+
+.. 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.
+
+.. _ls-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
+
+ @json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_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.
+
+.. _ls-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
+ @json_server.thread()
+ @json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_BLOCKING)
+ 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 <https://en.wikipedia.org/wiki/Global_interpreter_lock>`__
+before messing with threads.
+
+.. _passing-instance:
+
+Passing Language 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.
+
+There are two ways of doing this:
+
+- **ls** (**l**\anguage **s**\erver) naming convention
+
+Add **ls** as first parameter of a function and *pygls* will automatically pass
+the language server instance.
+
+.. code-block:: python
+
+ @json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_BLOCKING)
+ def count_down_10_seconds_blocking(ls, *args):
+ # Omitted
+
+
+- add **type** to first parameter
+
+Add the **LanguageServer** class or any class derived from it as a type to
+first parameter of a function
+
+.. code-block:: python
+
+ @json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_BLOCKING)
+ def count_down_10_seconds_blocking(ser: JsonLanguageServer, *args):
+ # Omitted
+
+
+Using outer ``json_server`` instance inside registered function will make
+writing unit :ref:`tests <testing>` more difficult.
+
+Communicating with the Client
+-----------------------------
+
+.. important::
+
+ Most of the messages listed here cannot be sent until the LSP session has been initialized.
+ See the section on the :lsp:`initiaiize` request in the specification for more details.
+
+In addition to responding to requests, there are a number of additional messages a server can send to the client.
+
+Configuration
+~~~~~~~~~~~~~
+
+The :lsp:`workspace/configuration` request is sent from the server to the client in order to fetch configuration settings from the client.
+Depending on how the handler is registered (see :ref:`here <ls-handlers>`) you can use the :meth:`~pygls.server.LanguageServer.get_configuration` or :meth:`~pygls.server.LanguageServer.get_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.get_configuration(
+ WorkspaceConfigurationParams(
+ items=[
+ ConfigurationItem(scope_uri='doc_uri_here', section='section')
+ ]
+ )
+ )
+
+- *synchronous* functions
+
+ .. code:: python
+
+ # callback is called when notification is received
+ def callback(config):
+ # Omitted
+
+ params = WorkspaceConfigurationParams(
+ items=[
+ ConfigurationItem(scope_uri='doc_uri_here', section='section')
+ ]
+ )
+ config = ls.get_configuration(params, callback)
+
+- *threaded* functions
+
+ .. code:: python
+
+ # .result() will block the thread
+ config = ls.get_configuration(
+ WorkspaceConfigurationParams(
+ items=[
+ ConfigurationItem(scope_uri='doc_uri_here', section='section')
+ ]
+ )
+ ).result()
+
+Publish 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
+
+ @json_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])
+
+Show Message
+~~~~~~~~~~~~
+
+: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
+
+ @json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_NON_BLOCKING)
+ 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
+
+ @json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_NON_BLOCKING)
+ 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)
+
+Workspace Edits
+~~~~~~~~~~~~~~~
+
+The :lsp:`workspace/applyEdit` request allows your language server to ask the client to modify particular documents in the client's workspace.
+
+.. code:: python
+
+ def apply_edit(self, edit: WorkspaceEdit, label: str = None) -> ApplyWorkspaceEditResponse:
+ # Omitted
+
+ def apply_edit_async(self, edit: WorkspaceEdit, label: str = None) -> ApplyWorkspaceEditResponse:
+ # Omitted
+
+Custom Notifications
+~~~~~~~~~~~~~~~~~~~~
+
+.. warning::
+
+ Custom notifications are not part of the LSP specification and dedicated support for your custom notification(s) will have to be added to each language client you intend to support.
+
+A custom notification can be sent to the client using the :meth:`~pygls.server.LanguageServer.send_notification` method
+
+.. code:: python
+
+ server.send_notification('myCustomNotification', 'test data')
+
+
+The Workspace
+-------------
+
+The :class:`~pygls.workspace.Workspace` is a python object that holds information about workspace folders, opened documents and is responsible for synchronising server side document state with that of the client.
+
+**Text Documents**
+
+The :class:`~pygls.workspace.TextDocument` class is how *pygls* represents a text document.
+Given a text document's uri the :meth:`~pygls.workspace.Workspace.get_text_document` method can be used to access the document itself:
+
+.. code:: python
+
+ @json_server.feature(TEXT_DOCUMENT_DID_OPEN)
+ async def did_open(ls, params: DidOpenTextDocumentParams):
+
+ # Get document from workspace
+ text_doc = ls.workspace.get_text_document(params.text_document.uri)
+
+**Notebook Documents**
+
+.. seealso::
+
+ See the section on :lsp:`notebookDocument/synchronization` in the specification for full details on how notebook documents are handled
+
+- 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.NotebookDocumentSyncOptionsNotebookSelectorType2(
+ cells=[
+ types.NotebookDocumentSyncOptionsNotebookSelectorType2CellsType(
+ 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)
diff --git a/examples/hello-world/README.md b/examples/hello-world/README.md
new file mode 100644
index 0000000..d95758a
--- /dev/null
+++ b/examples/hello-world/README.md
@@ -0,0 +1,78 @@
+# Hello World Pygls Language Server
+
+This is the bare-minimum, working example of a Pygls-based Language Server. It is the same as that shown in the main README, it autocompletes `hello.` with the options, "world" and "friend".
+
+You will only need to have installed Pygls on your system. Eg; `pip install pygls`. Normally you will want to formally define `pygls` as a dependency of your Language Server, with something like [venv](https://docs.python.org/3/library/venv.html), [Poetry](https://python-poetry.org/), etc.
+
+# Editor Configurations
+
+<details>
+<summary>Neovim Lua (vanilla Neovim without `lspconfig`)</summary>
+
+ Normally, once you have completed your own Language Server, you will want to submit it to the [LSP Config](https://github.com/neovim/nvim-lspconfig) repo, it is the defacto way to support Language Servers in the Neovim ecosystem. But before then you can just use something like this:
+
+ ```lua
+ vim.api.nvim_create_autocmd({ "BufEnter" }, {
+ -- NB: You must remember to manually put the file extension pattern matchers for each LSP filetype
+ pattern = { "*" },
+ callback = function()
+ vim.lsp.start({
+ name = "hello-world-pygls-example",
+ cmd = { "python path-to-hello-world-example/main.py" },
+ root_dir = vim.fs.dirname(vim.fs.find({ ".git" }, { upward = true })[1])
+ })
+ end,
+ })
+ ```
+</details>
+
+<details>
+<summary>Vim (`vim-lsp`)</summary>
+
+ ```vim
+ augroup HelloWorldPythonExample
+ au!
+ autocmd User lsp_setup call lsp#register_server({
+ \ 'name': 'hello-world-pygls-example',
+ \ 'cmd': {server_info->['python', 'path-to-hello-world-example/main.py']},
+ \ 'allowlist': ['*']
+ \ }})
+ augroup END
+ ```
+</details>
+
+<details>
+<summary>Emacs (`lsp-mode`)</summary>
+ Normally, once your Language Server is complete, you'll want to submit it to the [M-x Eglot](https://github.com/joaotavora/eglot) project, which will automatically set your server up. Until then, you can use:
+
+ ```
+ (make-lsp-client :new-connection
+ (lsp-stdio-connection
+ `(,(executable-find "python") "path-to-hello-world-example/main.py"))
+ :activation-fn (lsp-activate-on "*")
+ :server-id 'hello-world-pygls-example')))
+ ```
+</details>
+
+<details>
+<summary>Sublime</summary>
+
+
+ ```
+ {
+ "clients": {
+ "pygls-hello-world-example": {
+ "command": ["python", "path-to-hello-world-example/main.py"],
+ "enabled": true,
+ "selector": "source.python"
+ }
+ }
+ }
+ ```
+</details>
+
+<details>
+<summary>VSCode</summary>
+
+ VSCode is the most complex of the editors to setup. See the [json-vscode-extension](https://github.com/openlawlibrary/pygls/tree/master/examples/json-vscode-extension) for an idea of how to do it.
+</details>
diff --git a/examples/hello-world/main.py b/examples/hello-world/main.py
new file mode 100644
index 0000000..cb2c0e4
--- /dev/null
+++ b/examples/hello-world/main.py
@@ -0,0 +1,28 @@
+from pygls.server import LanguageServer
+from lsprotocol.types import (
+ TEXT_DOCUMENT_COMPLETION,
+ CompletionItem,
+ CompletionList,
+ CompletionParams,
+)
+
+server = LanguageServer("example-server", "v0.1")
+
+
+@server.feature(TEXT_DOCUMENT_COMPLETION)
+def completions(params: CompletionParams):
+ items = []
+ document = server.workspace.get_document(params.text_document.uri)
+ current_line = document.lines[params.position.line].strip()
+ if current_line.endswith("hello."):
+ items = [
+ CompletionItem(label="world"),
+ CompletionItem(label="friend"),
+ ]
+ return CompletionList(
+ is_incomplete=False,
+ items=items,
+ )
+
+
+server.start_io()
diff --git a/examples/servers/.vscode/launch.json b/examples/servers/.vscode/launch.json
new file mode 100644
index 0000000..f124186
--- /dev/null
+++ b/examples/servers/.vscode/launch.json
@@ -0,0 +1,20 @@
+{
+ "configurations": [
+ {
+ "name": "pygls: Debug Server",
+ "type": "python",
+ "request": "attach",
+ "connect": {
+ "host": "${config:pygls.server.debugHost}",
+ "port": "${config:pygls.server.debugPort}"
+ },
+ "pathMappings": [
+ {
+ "localRoot": "${workspaceFolder}",
+ "remoteRoot": "."
+ }
+ ],
+ "justMyCode": false
+ }
+ ]
+}
diff --git a/examples/servers/.vscode/settings.json b/examples/servers/.vscode/settings.json
new file mode 100644
index 0000000..c2b0d23
--- /dev/null
+++ b/examples/servers/.vscode/settings.json
@@ -0,0 +1,21 @@
+{
+ // Uncomment to override Python interpreter used.
+ // "pygls.server.pythonPath": "/path/to/python",
+ "pygls.server.debug": false,
+ // "pygls.server.debugHost": "localhost",
+ // "pygls.server.debugPort": 5678,
+ "pygls.server.launchScript": "json_server.py",
+ "pygls.trace.server": "off",
+ "pygls.client.documentSelector": [
+ {
+ "scheme": "file",
+ "language": "json"
+ }
+ // Uncomment to use code_actions or inlay_hints servers
+ // {
+ // "scheme": "file",
+ // "language": "plaintext"
+ // }
+ ],
+ // "pygls.jsonServer.exampleConfiguration": "some value here",
+}
diff --git a/examples/servers/code_actions.py b/examples/servers/code_actions.py
new file mode 100644
index 0000000..ec0382e
--- /dev/null
+++ b/examples/servers/code_actions.py
@@ -0,0 +1,74 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import re
+from pygls.server import LanguageServer
+from lsprotocol.types import (
+ TEXT_DOCUMENT_CODE_ACTION,
+ CodeAction,
+ CodeActionKind,
+ CodeActionOptions,
+ CodeActionParams,
+ Position,
+ Range,
+ TextEdit,
+ WorkspaceEdit,
+)
+
+
+ADDITION = re.compile(r"^\s*(\d+)\s*\+\s*(\d+)\s*=(?=\s*$)")
+server = LanguageServer("code-action-server", "v0.1")
+
+
+@server.feature(
+ TEXT_DOCUMENT_CODE_ACTION,
+ CodeActionOptions(code_action_kinds=[CodeActionKind.QuickFix]),
+)
+def code_actions(params: CodeActionParams):
+ items = []
+ document_uri = params.text_document.uri
+ document = server.workspace.get_document(document_uri)
+
+ start_line = params.range.start.line
+ end_line = params.range.end.line
+
+ lines = document.lines[start_line : end_line + 1]
+ for idx, line in enumerate(lines):
+ match = ADDITION.match(line)
+ if match is not None:
+ range_ = Range(
+ start=Position(line=start_line + idx, character=0),
+ end=Position(line=start_line + idx, character=len(line) - 1),
+ )
+
+ left = int(match.group(1))
+ right = int(match.group(2))
+ answer = left + right
+
+ text_edit = TextEdit(range=range_, new_text=f"{line.strip()} {answer}!")
+
+ action = CodeAction(
+ title=f"Evaluate '{match.group(0)}'",
+ kind=CodeActionKind.QuickFix,
+ edit=WorkspaceEdit(changes={document_uri: [text_edit]}),
+ )
+ items.append(action)
+
+ return items
+
+
+if __name__ == "__main__":
+ server.start_io()
diff --git a/examples/servers/inlay_hints.py b/examples/servers/inlay_hints.py
new file mode 100644
index 0000000..e9924dd
--- /dev/null
+++ b/examples/servers/inlay_hints.py
@@ -0,0 +1,116 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import re
+from typing import Optional
+
+from lsprotocol import types
+
+from pygls.server import LanguageServer
+
+NUMBER = re.compile(r"\d+")
+COMMENT = re.compile(r"^#$")
+
+
+server = LanguageServer(
+ name="inlay-hint-server",
+ version="v0.1",
+ notebook_document_sync=types.NotebookDocumentSyncOptions(
+ notebook_selector=[
+ types.NotebookDocumentSyncOptionsNotebookSelectorType2(
+ cells=[
+ types.NotebookDocumentSyncOptionsNotebookSelectorType2CellsType(
+ language="python"
+ )
+ ]
+ )
+ ]
+ ),
+)
+
+
+def parse_int(chars: str) -> Optional[int]:
+ try:
+ return int(chars)
+ except Exception:
+ return None
+
+
+@server.feature(types.TEXT_DOCUMENT_INLAY_HINT)
+def inlay_hints(params: types.InlayHintParams):
+ items = []
+ document_uri = params.text_document.uri
+ document = server.workspace.get_text_document(document_uri)
+
+ start_line = params.range.start.line
+ end_line = params.range.end.line
+
+ lines = document.lines[start_line : end_line + 1]
+ for lineno, line in enumerate(lines):
+ match = COMMENT.match(line)
+ if match is not None:
+ nb = server.workspace.get_notebook_document(cell_uri=document_uri)
+ if nb is not None:
+ idx = 0
+ for idx, cell in enumerate(nb.cells):
+ if cell.document == document_uri:
+ break
+
+ items.append(
+ types.InlayHint(
+ label=f"notebook: {nb.uri}, cell {idx+1}",
+ kind=types.InlayHintKind.Type,
+ padding_left=False,
+ padding_right=True,
+ position=types.Position(line=lineno, character=match.end()),
+ )
+ )
+
+ for match in NUMBER.finditer(line):
+ if not match:
+ continue
+
+ number = parse_int(match.group(0))
+ if number is None:
+ continue
+
+ binary_num = bin(number).split("b")[1]
+ items.append(
+ types.InlayHint(
+ label=f":{binary_num}",
+ kind=types.InlayHintKind.Type,
+ padding_left=False,
+ padding_right=True,
+ position=types.Position(line=lineno, character=match.end()),
+ )
+ )
+
+ return items
+
+
+@server.feature(types.INLAY_HINT_RESOLVE)
+def inlay_hint_resolve(hint: types.InlayHint):
+ try:
+ n = int(hint.label[1:], 2)
+ hint.tooltip = f"Binary representation of the number: {n}"
+ except Exception:
+ pass
+
+ return hint
+
+
+if __name__ == "__main__":
+ server.start_io()
diff --git a/examples/servers/json_server.py b/examples/servers/json_server.py
new file mode 100644
index 0000000..98905e4
--- /dev/null
+++ b/examples/servers/json_server.py
@@ -0,0 +1,387 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import argparse
+import asyncio
+import json
+import re
+import time
+import uuid
+from json import JSONDecodeError
+from typing import Optional
+
+from lsprotocol import types as lsp
+
+from pygls.server import LanguageServer
+
+COUNT_DOWN_START_IN_SECONDS = 10
+COUNT_DOWN_SLEEP_IN_SECONDS = 1
+
+
+class JsonLanguageServer(LanguageServer):
+ CMD_COUNT_DOWN_BLOCKING = "countDownBlocking"
+ CMD_COUNT_DOWN_NON_BLOCKING = "countDownNonBlocking"
+ CMD_PROGRESS = "progress"
+ CMD_REGISTER_COMPLETIONS = "registerCompletions"
+ CMD_SHOW_CONFIGURATION_ASYNC = "showConfigurationAsync"
+ CMD_SHOW_CONFIGURATION_CALLBACK = "showConfigurationCallback"
+ CMD_SHOW_CONFIGURATION_THREAD = "showConfigurationThread"
+ CMD_UNREGISTER_COMPLETIONS = "unregisterCompletions"
+
+ CONFIGURATION_SECTION = "pygls.jsonServer"
+
+ def __init__(self, *args):
+ super().__init__(*args)
+
+
+json_server = JsonLanguageServer("pygls-json-example", "v0.1")
+
+
+def _validate(ls, params):
+ ls.show_message_log("Validating json...")
+
+ text_doc = ls.workspace.get_document(params.text_document.uri)
+
+ source = text_doc.source
+ diagnostics = _validate_json(source) if source else []
+
+ ls.publish_diagnostics(text_doc.uri, diagnostics)
+
+
+def _validate_json(source):
+ """Validates json file."""
+ diagnostics = []
+
+ try:
+ json.loads(source)
+ except JSONDecodeError as err:
+ msg = err.msg
+ col = err.colno
+ line = err.lineno
+
+ d = lsp.Diagnostic(
+ range=lsp.Range(
+ start=lsp.Position(line=line - 1, character=col - 1),
+ end=lsp.Position(line=line - 1, character=col),
+ ),
+ message=msg,
+ source=type(json_server).__name__,
+ )
+
+ diagnostics.append(d)
+
+ return diagnostics
+
+
+@json_server.feature(
+ lsp.TEXT_DOCUMENT_DIAGNOSTIC,
+ lsp.DiagnosticOptions(
+ identifier="jsonServer",
+ inter_file_dependencies=True,
+ workspace_diagnostics=True,
+ ),
+)
+def text_document_diagnostic(
+ params: lsp.DocumentDiagnosticParams,
+) -> lsp.DocumentDiagnosticReport:
+ """Returns diagnostic report."""
+ document = json_server.workspace.get_document(params.text_document.uri)
+ return lsp.RelatedFullDocumentDiagnosticReport(
+ items=_validate_json(document.source),
+ kind=lsp.DocumentDiagnosticReportKind.Full,
+ )
+
+
+@json_server.feature(lsp.WORKSPACE_DIAGNOSTIC)
+def workspace_diagnostic(
+ params: lsp.WorkspaceDiagnosticParams,
+) -> lsp.WorkspaceDiagnosticReport:
+ """Returns diagnostic report."""
+ documents = json_server.workspace.text_documents.keys()
+
+ if len(documents) == 0:
+ items = []
+ else:
+ first = list(documents)[0]
+ document = json_server.workspace.get_document(first)
+ items = [
+ lsp.WorkspaceFullDocumentDiagnosticReport(
+ uri=document.uri,
+ version=document.version,
+ items=_validate_json(document.source),
+ kind=lsp.DocumentDiagnosticReportKind.Full,
+ )
+ ]
+
+ return lsp.WorkspaceDiagnosticReport(items=items)
+
+
+@json_server.feature(
+ lsp.TEXT_DOCUMENT_COMPLETION,
+ lsp.CompletionOptions(trigger_characters=[","], all_commit_characters=[":"]),
+)
+def completions(params: Optional[lsp.CompletionParams] = None) -> lsp.CompletionList:
+ """Returns completion items."""
+ return lsp.CompletionList(
+ is_incomplete=False,
+ items=[
+ lsp.CompletionItem(label='"'),
+ lsp.CompletionItem(label="["),
+ lsp.CompletionItem(label="]"),
+ lsp.CompletionItem(label="{"),
+ lsp.CompletionItem(label="}"),
+ ],
+ )
+
+
+@json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_BLOCKING)
+def count_down_10_seconds_blocking(ls, *args):
+ """Starts counting down and showing message synchronously.
+ It will `block` the main thread, which can be tested by trying to show
+ completion items.
+ """
+ for i in range(COUNT_DOWN_START_IN_SECONDS):
+ ls.show_message(f"Counting down... {COUNT_DOWN_START_IN_SECONDS - i}")
+ time.sleep(COUNT_DOWN_SLEEP_IN_SECONDS)
+
+
+@json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_NON_BLOCKING)
+async def count_down_10_seconds_non_blocking(ls, *args):
+ """Starts counting down and showing message asynchronously.
+ It won't `block` the main thread, which can be tested by trying to show
+ completion items.
+ """
+ for i in range(COUNT_DOWN_START_IN_SECONDS):
+ ls.show_message(f"Counting down... {COUNT_DOWN_START_IN_SECONDS - i}")
+ await asyncio.sleep(COUNT_DOWN_SLEEP_IN_SECONDS)
+
+
+@json_server.feature(lsp.TEXT_DOCUMENT_DID_CHANGE)
+def did_change(ls, params: lsp.DidChangeTextDocumentParams):
+ """Text document did change notification."""
+ _validate(ls, params)
+
+
+@json_server.feature(lsp.TEXT_DOCUMENT_DID_CLOSE)
+def did_close(server: JsonLanguageServer, params: lsp.DidCloseTextDocumentParams):
+ """Text document did close notification."""
+ server.show_message("Text Document Did Close")
+
+
+@json_server.feature(lsp.TEXT_DOCUMENT_DID_OPEN)
+async def did_open(ls, params: lsp.DidOpenTextDocumentParams):
+ """Text document did open notification."""
+ ls.show_message("Text Document Did Open")
+ _validate(ls, params)
+
+
+@json_server.feature(
+ lsp.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL,
+ lsp.SemanticTokensLegend(token_types=["operator"], token_modifiers=[]),
+)
+def semantic_tokens(ls: JsonLanguageServer, params: lsp.SemanticTokensParams):
+ """See https://microsoft.github.io/language-server-protocol/specification#textDocument_semanticTokens
+ for details on how semantic tokens are encoded."""
+
+ TOKENS = re.compile('".*"(?=:)')
+
+ uri = params.text_document.uri
+ doc = ls.workspace.get_document(uri)
+
+ last_line = 0
+ last_start = 0
+
+ data = []
+
+ for lineno, line in enumerate(doc.lines):
+ last_start = 0
+
+ for match in TOKENS.finditer(line):
+ start, end = match.span()
+ data += [(lineno - last_line), (start - last_start), (end - start), 0, 0]
+
+ last_line = lineno
+ last_start = start
+
+ return lsp.SemanticTokens(data=data)
+
+
+@json_server.feature(lsp.TEXT_DOCUMENT_INLINE_VALUE)
+def inline_value(params: lsp.InlineValueParams):
+ """Returns inline value."""
+ return [lsp.InlineValueText(range=params.range, text="Inline value")]
+
+
+@json_server.command(JsonLanguageServer.CMD_PROGRESS)
+async def progress(ls: JsonLanguageServer, *args):
+ """Create and start the progress on the client."""
+ token = str(uuid.uuid4())
+ # Create
+ await ls.progress.create_async(token)
+ # Begin
+ ls.progress.begin(
+ token,
+ lsp.WorkDoneProgressBegin(title="Indexing", percentage=0, cancellable=True),
+ )
+ # Report
+ for i in range(1, 10):
+ # Check for cancellation from client
+ if ls.progress.tokens[token].cancelled():
+ # ... and stop the computation if client cancelled
+ return
+ ls.progress.report(
+ token,
+ lsp.WorkDoneProgressReport(message=f"{i * 10}%", percentage=i * 10),
+ )
+ await asyncio.sleep(2)
+ # End
+ ls.progress.end(token, lsp.WorkDoneProgressEnd(message="Finished"))
+
+
+@json_server.command(JsonLanguageServer.CMD_REGISTER_COMPLETIONS)
+async def register_completions(ls: JsonLanguageServer, *args):
+ """Register completions method on the client."""
+ params = lsp.RegistrationParams(
+ registrations=[
+ lsp.Registration(
+ id=str(uuid.uuid4()),
+ method=lsp.TEXT_DOCUMENT_COMPLETION,
+ register_options={"triggerCharacters": "[':']"},
+ )
+ ]
+ )
+ response = await ls.register_capability_async(params)
+ if response is None:
+ ls.show_message("Successfully registered completions method")
+ else:
+ ls.show_message(
+ "Error happened during completions registration.", lsp.MessageType.Error
+ )
+
+
+@json_server.command(JsonLanguageServer.CMD_SHOW_CONFIGURATION_ASYNC)
+async def show_configuration_async(ls: JsonLanguageServer, *args):
+ """Gets exampleConfiguration from the client settings using coroutines."""
+ try:
+ config = await ls.get_configuration_async(
+ lsp.WorkspaceConfigurationParams(
+ items=[
+ lsp.ConfigurationItem(
+ scope_uri="", section=JsonLanguageServer.CONFIGURATION_SECTION
+ )
+ ]
+ )
+ )
+
+ example_config = config[0].get("exampleConfiguration")
+
+ ls.show_message(f"jsonServer.exampleConfiguration value: {example_config}")
+
+ except Exception as e:
+ ls.show_message_log(f"Error ocurred: {e}")
+
+
+@json_server.command(JsonLanguageServer.CMD_SHOW_CONFIGURATION_CALLBACK)
+def show_configuration_callback(ls: JsonLanguageServer, *args):
+ """Gets exampleConfiguration from the client settings using callback."""
+
+ def _config_callback(config):
+ try:
+ example_config = config[0].get("exampleConfiguration")
+
+ ls.show_message(f"jsonServer.exampleConfiguration value: {example_config}")
+
+ except Exception as e:
+ ls.show_message_log(f"Error ocurred: {e}")
+
+ ls.get_configuration(
+ lsp.WorkspaceConfigurationParams(
+ items=[
+ lsp.ConfigurationItem(
+ scope_uri="", section=JsonLanguageServer.CONFIGURATION_SECTION
+ )
+ ]
+ ),
+ _config_callback,
+ )
+
+
+@json_server.thread()
+@json_server.command(JsonLanguageServer.CMD_SHOW_CONFIGURATION_THREAD)
+def show_configuration_thread(ls: JsonLanguageServer, *args):
+ """Gets exampleConfiguration from the client settings using thread pool."""
+ try:
+ config = ls.get_configuration(
+ lsp.WorkspaceConfigurationParams(
+ items=[
+ lsp.ConfigurationItem(
+ scope_uri="", section=JsonLanguageServer.CONFIGURATION_SECTION
+ )
+ ]
+ )
+ ).result(2)
+
+ example_config = config[0].get("exampleConfiguration")
+
+ ls.show_message(f"jsonServer.exampleConfiguration value: {example_config}")
+
+ except Exception as e:
+ ls.show_message_log(f"Error ocurred: {e}")
+
+
+@json_server.command(JsonLanguageServer.CMD_UNREGISTER_COMPLETIONS)
+async def unregister_completions(ls: JsonLanguageServer, *args):
+ """Unregister completions method on the client."""
+ params = lsp.UnregistrationParams(
+ unregisterations=[
+ lsp.Unregistration(
+ id=str(uuid.uuid4()), method=lsp.TEXT_DOCUMENT_COMPLETION
+ )
+ ]
+ )
+ response = await ls.unregister_capability_async(params)
+ if response is None:
+ ls.show_message("Successfully unregistered completions method")
+ else:
+ ls.show_message(
+ "Error happened during completions unregistration.", lsp.MessageType.Error
+ )
+
+
+def add_arguments(parser):
+ parser.description = "simple json server example"
+
+ parser.add_argument("--tcp", action="store_true", help="Use TCP server")
+ parser.add_argument("--ws", action="store_true", help="Use WebSocket server")
+ parser.add_argument("--host", default="127.0.0.1", help="Bind to this address")
+ parser.add_argument("--port", type=int, default=2087, help="Bind to this port")
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ add_arguments(parser)
+ args = parser.parse_args()
+
+ if args.tcp:
+ json_server.start_tcp(args.host, args.port)
+ elif args.ws:
+ json_server.start_ws(args.host, args.port)
+ else:
+ json_server.start_io()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/servers/workspace/Untitled-1.ipynb b/examples/servers/workspace/Untitled-1.ipynb
new file mode 100644
index 0000000..d45e746
--- /dev/null
+++ b/examples/servers/workspace/Untitled-1.ipynb
@@ -0,0 +1,44 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "12\n",
+ "#"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "mykey": 3
+ },
+ "outputs": [],
+ "source": [
+ "#"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python",
+ "version": "3.11.4"
+ },
+ "orig_nbformat": 4
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/examples/servers/workspace/sums.txt b/examples/servers/workspace/sums.txt
new file mode 100644
index 0000000..fcbc410
--- /dev/null
+++ b/examples/servers/workspace/sums.txt
@@ -0,0 +1,7 @@
+1 + 1 =
+
+
+2 + 3 =
+
+
+6 + 6 =
diff --git a/examples/servers/workspace/test.json b/examples/servers/workspace/test.json
new file mode 100644
index 0000000..21da3b2
--- /dev/null
+++ b/examples/servers/workspace/test.json
@@ -0,0 +1,3 @@
+{
+ "key": "value"
+}
diff --git a/examples/vscode-playground/.eslintrc.yml b/examples/vscode-playground/.eslintrc.yml
new file mode 100644
index 0000000..154438e
--- /dev/null
+++ b/examples/vscode-playground/.eslintrc.yml
@@ -0,0 +1,13 @@
+env:
+ es2021: true
+ node: true
+extends:
+ - 'eslint:recommended'
+ - 'plugin:@typescript-eslint/recommended'
+parser: '@typescript-eslint/parser'
+parserOptions:
+ ecmaVersion: 12
+ sourceType: module
+plugins:
+ - '@typescript-eslint'
+rules: {}
diff --git a/examples/vscode-playground/.gitignore b/examples/vscode-playground/.gitignore
new file mode 100644
index 0000000..6b0c9b5
--- /dev/null
+++ b/examples/vscode-playground/.gitignore
@@ -0,0 +1,6 @@
+out
+node_modules
+client/server
+.vscode-test
+.vscode/settings.json
+env
diff --git a/examples/vscode-playground/.vscode/launch.json b/examples/vscode-playground/.vscode/launch.json
new file mode 100644
index 0000000..fe0ed4b
--- /dev/null
+++ b/examples/vscode-playground/.vscode/launch.json
@@ -0,0 +1,23 @@
+// A launch configuration that compiles the extension and then opens it inside a new window
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Launch Client",
+ "type": "extensionHost",
+ "request": "launch",
+ "runtimeExecutable": "${execPath}",
+ "args": [
+ "--extensionDevelopmentPath=${workspaceRoot}",
+ "--folder-uri=${workspaceRoot}/../servers",
+ ],
+ "outFiles": [
+ "${workspaceRoot}/out/**/*.js"
+ ],
+ "preLaunchTask": {
+ "type": "npm",
+ "script": "watch"
+ },
+ },
+ ],
+}
diff --git a/examples/vscode-playground/.vscode/tasks.json b/examples/vscode-playground/.vscode/tasks.json
new file mode 100644
index 0000000..6178c01
--- /dev/null
+++ b/examples/vscode-playground/.vscode/tasks.json
@@ -0,0 +1,29 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "type": "npm",
+ "script": "compile",
+ "group": "build",
+ "presentation": {
+ "panel": "dedicated",
+ "reveal": "never"
+ },
+ "problemMatcher": ["$tsc"]
+ },
+ {
+ "type": "npm",
+ "script": "watch",
+ "isBackground": true,
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ },
+ "presentation": {
+ "panel": "dedicated",
+ "reveal": "never"
+ },
+ "problemMatcher": ["$tsc-watch"]
+ }
+ ]
+}
diff --git a/examples/vscode-playground/.vscodeignore b/examples/vscode-playground/.vscodeignore
new file mode 100644
index 0000000..ba6eaa1
--- /dev/null
+++ b/examples/vscode-playground/.vscodeignore
@@ -0,0 +1,10 @@
+.vscode
+.gitignore
+client/out/*.map
+client/src/
+tsconfig.json
+tslint.json
+package.json
+package-lock.json
+
+.pytest_cache
diff --git a/examples/vscode-playground/LICENSE.txt b/examples/vscode-playground/LICENSE.txt
new file mode 100644
index 0000000..80f617c
--- /dev/null
+++ b/examples/vscode-playground/LICENSE.txt
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright (c) Open Law Library. All rights reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/examples/vscode-playground/README.md b/examples/vscode-playground/README.md
new file mode 100644
index 0000000..e73ed51
--- /dev/null
+++ b/examples/vscode-playground/README.md
@@ -0,0 +1,84 @@
+# Pygls Playground
+
+![Screenshot of the vscode-playground extension in action](https://user-images.githubusercontent.com/2675694/260591942-b7001a7b-3081-439d-b702-5f8a489856db.png)
+
+This VSCode extension aims to serve two purposes.
+
+- 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.
+
+For an example of a more complete VSCode client, including details on how to bundle your Python code with the VSCode extension itself you may also be interested in Microsoft's [template extension for Python tools](https://github.com/microsoft/vscode-python-tools-extension-template).
+
+## Setup
+
+### Install Server Dependencies
+
+Open a terminal in the repository's root directory
+
+1. Create a virtual environment
+ ```
+ python -m venv env
+ ```
+
+1. Install pygls
+ ```
+ python -m pip install -e .
+ ```
+
+### Install Client Dependencies
+
+Open terminal in the same directory as this file and execute following commands:
+
+1. Install node dependencies
+
+ ```
+ npm install
+ ```
+1. Compile the extension
+
+ ```
+ npm run compile
+ ```
+ Alternatively you can run `npm run watch` if you are going to be actively working on the extension itself.
+
+### Run Extension
+
+1. Open this directory in VS Code
+
+1. The playground relies on the [Python extension for VSCode](https://marketplace.visualstudio.com/items?itemName=ms-python.python) 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.
+
+1. Open the Run and Debug view (`ctrl + shift + D`)
+
+1. Select `Launch Client` and press `F5`, this will open a second VSCode window with the `vscode-playground` extension enabled.
+
+1. You will need to make sure that VSCode is using a virtual environment that contains an installation of `pygls`.
+ The `Python: Select Interpreter` command can be used to pick the correct one.
+
+ Alternatively, you can set the `pygls.server.pythonPath` option in the `.vscode/settings.json` file
+
+
+#### Selecting the document language
+
+The default settings for the `pygls-playground` VSCode extension are configured for the `json_server.py` example. In particular the server will only be used for `.json` files.
+
+The `code_actions.py` example is intended to be used with text files (e.g. the provided `sums.txt` file). To use the server with text files change the **Pygls > Client: Document Selector** setting to the following:
+
+```
+"pygls.client.documentSelector": [
+ {
+ "scheme": "file",
+ "language": "plaintext"
+ },
+],
+```
+
+You can find the full list of known language identifiers [here](https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers).
+
+#### Debugging the server
+
+To debug the language server set the `pygls.server.debug` option to `true`.
+The server should be restarted 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.
diff --git a/examples/vscode-playground/package-lock.json b/examples/vscode-playground/package-lock.json
new file mode 100644
index 0000000..a701722
--- /dev/null
+++ b/examples/vscode-playground/package-lock.json
@@ -0,0 +1,2709 @@
+{
+ "name": "pygls-playground",
+ "version": "1.0.2",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "pygls-playground",
+ "version": "1.0.2",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@vscode/python-extension": "^1.0.4",
+ "semver": "^7.5.4",
+ "vscode-languageclient": "^8.1.0"
+ },
+ "devDependencies": {
+ "@types/node": "^16.11.6",
+ "@types/semver": "^7.5.0",
+ "@types/vscode": "^1.78.0",
+ "@typescript-eslint/eslint-plugin": "^5.3.0",
+ "@typescript-eslint/parser": "^5.3.0",
+ "eslint": "^8.2.0",
+ "typescript": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=16.17.1",
+ "vscode": "^1.78.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.4.tgz",
+ "integrity": "sha512-h8Vx6MdxwWI2WM8/zREHMoqdgLNXEL4QX3MWSVMdyNJGvXVOs+6lp+m2hc3FnuMHDc4poxFNI20vCk0OmI4G0Q==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.0.0",
+ "globals": "^13.9.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.0.4",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/ignore": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.6.0.tgz",
+ "integrity": "sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==",
+ "dev": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^1.2.0",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "dev": true
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.9",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
+ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "16.11.6",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz",
+ "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==",
+ "dev": true
+ },
+ "node_modules/@types/semver": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz",
+ "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==",
+ "dev": true
+ },
+ "node_modules/@types/vscode": {
+ "version": "1.79.1",
+ "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.79.1.tgz",
+ "integrity": "sha512-Ikwc4YbHABzqthrWfeAvItaAIfX9mdjMWxqNgTpGjhgOu0TMRq9LzyZ2yBK0JhYqoSjEubEPawf6zJgnl6Egtw==",
+ "dev": true
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.3.0.tgz",
+ "integrity": "sha512-ARUEJHJrq85aaiCqez7SANeahDsJTD3AEua34EoQN9pHS6S5Bq9emcIaGGySt/4X2zSi+vF5hAH52sEen7IO7g==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/experimental-utils": "5.3.0",
+ "@typescript-eslint/scope-manager": "5.3.0",
+ "debug": "^4.3.2",
+ "functional-red-black-tree": "^1.0.1",
+ "ignore": "^5.1.8",
+ "regexpp": "^3.2.0",
+ "semver": "^7.3.5",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^5.0.0",
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/experimental-utils": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.3.0.tgz",
+ "integrity": "sha512-NFVxYTjKj69qB0FM+piah1x3G/63WB8vCBMnlnEHUsiLzXSTWb9FmFn36FD9Zb4APKBLY3xRArOGSMQkuzTF1w==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.9",
+ "@typescript-eslint/scope-manager": "5.3.0",
+ "@typescript-eslint/types": "5.3.0",
+ "@typescript-eslint/typescript-estree": "5.3.0",
+ "eslint-scope": "^5.1.1",
+ "eslint-utils": "^3.0.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "*"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.3.0.tgz",
+ "integrity": "sha512-rKu/yAReip7ovx8UwOAszJVO5MgBquo8WjIQcp1gx4pYQCwYzag+I5nVNHO4MqyMkAo0gWt2gWUi+36gWAVKcw==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "5.3.0",
+ "@typescript-eslint/types": "5.3.0",
+ "@typescript-eslint/typescript-estree": "5.3.0",
+ "debug": "^4.3.2"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.3.0.tgz",
+ "integrity": "sha512-22Uic9oRlTsPppy5Tcwfj+QET5RWEnZ5414Prby465XxQrQFZ6nnm5KnXgnsAJefG4hEgMnaxTB3kNEyjdjj6A==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.3.0",
+ "@typescript-eslint/visitor-keys": "5.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.3.0.tgz",
+ "integrity": "sha512-fce5pG41/w8O6ahQEhXmMV+xuh4+GayzqEogN24EK+vECA3I6pUwKuLi5QbXO721EMitpQne5VKXofPonYlAQg==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.3.0.tgz",
+ "integrity": "sha512-FJ0nqcaUOpn/6Z4Jwbtf+o0valjBLkqc3MWkMvrhA2TvzFXtcclIM8F4MBEmYa2kgcI8EZeSAzwoSrIC8JYkug==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.3.0",
+ "@typescript-eslint/visitor-keys": "5.3.0",
+ "debug": "^4.3.2",
+ "globby": "^11.0.4",
+ "is-glob": "^4.0.3",
+ "semver": "^7.3.5",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.3.0.tgz",
+ "integrity": "sha512-oVIAfIQuq0x2TFDNLVavUn548WL+7hdhxYn+9j3YdJJXB7mH9dAmZNJsPDa7Jc+B9WGqoiex7GUDbyMxV0a/aw==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.3.0",
+ "eslint-visitor-keys": "^3.0.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@vscode/python-extension": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@vscode/python-extension/-/python-extension-1.0.4.tgz",
+ "integrity": "sha512-+m9VOUqv5TXZD52Ad8FjGbYGch7VqLAIys3NyVhgU6eSxmXVcRgqeon5ee224tOkTGtRQHdH5kDCa1Va/6LwjQ==",
+ "engines": {
+ "node": ">=16.17.1",
+ "vscode": "^1.78.0"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.5.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz",
+ "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-colors": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
+ "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/enquirer": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
+ "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
+ "dev": true,
+ "dependencies": {
+ "ansi-colors": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.2.0.tgz",
+ "integrity": "sha512-erw7XmM+CLxTOickrimJ1SiF55jiNlVSp2qqm0NuBWPtHYQCegD5ZMaW0c3i5ytPqL+SSLaCxdvQXFPLJn+ABw==",
+ "dev": true,
+ "dependencies": {
+ "@eslint/eslintrc": "^1.0.4",
+ "@humanwhocodes/config-array": "^0.6.0",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "enquirer": "^2.3.5",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^6.0.0",
+ "eslint-utils": "^3.0.0",
+ "eslint-visitor-keys": "^3.0.0",
+ "espree": "^9.0.0",
+ "esquery": "^1.4.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "functional-red-black-tree": "^1.0.1",
+ "glob-parent": "^6.0.1",
+ "globals": "^13.6.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.0.4",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "progress": "^2.0.0",
+ "regexpp": "^3.2.0",
+ "semver": "^7.2.1",
+ "strip-ansi": "^6.0.1",
+ "strip-json-comments": "^3.1.0",
+ "text-table": "^0.2.0",
+ "v8-compile-cache": "^2.0.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/eslint-utils": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+ "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^2.0.0"
+ },
+ "engines": {
+ "node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ },
+ "peerDependencies": {
+ "eslint": ">=5"
+ }
+ },
+ "node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz",
+ "integrity": "sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/eslint/node_modules/eslint-scope": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-6.0.0.tgz",
+ "integrity": "sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/eslint/node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/eslint/node_modules/ignore": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.0.0.tgz",
+ "integrity": "sha512-r5EQJcYZ2oaGbeR0jR0fFVijGOcwai07/690YRXLINuhmVeRY4UKSAsQPe/0BNuDgwP7Ophoc1PRsr2E3tkbdQ==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.5.0",
+ "acorn-jsx": "^5.3.1",
+ "eslint-visitor-keys": "^3.0.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
+ "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esquery/node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esrecurse/node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "node_modules/fast-glob": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz",
+ "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+ "dev": true
+ },
+ "node_modules/fastq": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
+ "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz",
+ "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==",
+ "dev": true
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "node_modules/functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+ "dev": true
+ },
+ "node_modules/glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz",
+ "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globby": {
+ "version": "11.0.4",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz",
+ "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==",
+ "dev": true,
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.1.1",
+ "ignore": "^5.1.4",
+ "merge2": "^1.3.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz",
+ "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+ "dev": true
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
+ "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.1",
+ "picomatch": "^2.2.3"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+ "dev": true
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+ "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "dev": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
+ "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/regexpp": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
+ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+ "dev": true
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
+ },
+ "node_modules/tsutils": {
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
+ "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+ "dev": true,
+ "dependencies": {
+ "tslib": "^1.8.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ },
+ "peerDependencies": {
+ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz",
+ "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/v8-compile-cache": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
+ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
+ "dev": true
+ },
+ "node_modules/vscode-jsonrpc": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz",
+ "integrity": "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/vscode-languageclient": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.1.0.tgz",
+ "integrity": "sha512-GL4QdbYUF/XxQlAsvYWZRV3V34kOkpRlvV60/72ghHfsYFnS/v2MANZ9P6sHmxFcZKOse8O+L9G7Czg0NUWing==",
+ "dependencies": {
+ "minimatch": "^5.1.0",
+ "semver": "^7.3.7",
+ "vscode-languageserver-protocol": "3.17.3"
+ },
+ "engines": {
+ "vscode": "^1.67.0"
+ }
+ },
+ "node_modules/vscode-languageclient/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/vscode-languageclient/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/vscode-languageserver-protocol": {
+ "version": "3.17.3",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz",
+ "integrity": "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==",
+ "dependencies": {
+ "vscode-jsonrpc": "8.1.0",
+ "vscode-languageserver-types": "3.17.3"
+ }
+ },
+ "node_modules/vscode-languageserver-types": {
+ "version": "3.17.3",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz",
+ "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA=="
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
+ "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ }
+ },
+ "dependencies": {
+ "@eslint/eslintrc": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.4.tgz",
+ "integrity": "sha512-h8Vx6MdxwWI2WM8/zREHMoqdgLNXEL4QX3MWSVMdyNJGvXVOs+6lp+m2hc3FnuMHDc4poxFNI20vCk0OmI4G0Q==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.0.0",
+ "globals": "^13.9.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.0.4",
+ "strip-json-comments": "^3.1.1"
+ },
+ "dependencies": {
+ "ignore": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+ "dev": true
+ }
+ }
+ },
+ "@humanwhocodes/config-array": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.6.0.tgz",
+ "integrity": "sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==",
+ "dev": true,
+ "requires": {
+ "@humanwhocodes/object-schema": "^1.2.0",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.4"
+ }
+ },
+ "@humanwhocodes/object-schema": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "dev": true
+ },
+ "@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "requires": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ }
+ },
+ "@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true
+ },
+ "@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "requires": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ }
+ },
+ "@types/json-schema": {
+ "version": "7.0.9",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
+ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
+ "dev": true
+ },
+ "@types/node": {
+ "version": "16.11.6",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz",
+ "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==",
+ "dev": true
+ },
+ "@types/semver": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz",
+ "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==",
+ "dev": true
+ },
+ "@types/vscode": {
+ "version": "1.79.1",
+ "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.79.1.tgz",
+ "integrity": "sha512-Ikwc4YbHABzqthrWfeAvItaAIfX9mdjMWxqNgTpGjhgOu0TMRq9LzyZ2yBK0JhYqoSjEubEPawf6zJgnl6Egtw==",
+ "dev": true
+ },
+ "@typescript-eslint/eslint-plugin": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.3.0.tgz",
+ "integrity": "sha512-ARUEJHJrq85aaiCqez7SANeahDsJTD3AEua34EoQN9pHS6S5Bq9emcIaGGySt/4X2zSi+vF5hAH52sEen7IO7g==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/experimental-utils": "5.3.0",
+ "@typescript-eslint/scope-manager": "5.3.0",
+ "debug": "^4.3.2",
+ "functional-red-black-tree": "^1.0.1",
+ "ignore": "^5.1.8",
+ "regexpp": "^3.2.0",
+ "semver": "^7.3.5",
+ "tsutils": "^3.21.0"
+ }
+ },
+ "@typescript-eslint/experimental-utils": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.3.0.tgz",
+ "integrity": "sha512-NFVxYTjKj69qB0FM+piah1x3G/63WB8vCBMnlnEHUsiLzXSTWb9FmFn36FD9Zb4APKBLY3xRArOGSMQkuzTF1w==",
+ "dev": true,
+ "requires": {
+ "@types/json-schema": "^7.0.9",
+ "@typescript-eslint/scope-manager": "5.3.0",
+ "@typescript-eslint/types": "5.3.0",
+ "@typescript-eslint/typescript-estree": "5.3.0",
+ "eslint-scope": "^5.1.1",
+ "eslint-utils": "^3.0.0"
+ }
+ },
+ "@typescript-eslint/parser": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.3.0.tgz",
+ "integrity": "sha512-rKu/yAReip7ovx8UwOAszJVO5MgBquo8WjIQcp1gx4pYQCwYzag+I5nVNHO4MqyMkAo0gWt2gWUi+36gWAVKcw==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/scope-manager": "5.3.0",
+ "@typescript-eslint/types": "5.3.0",
+ "@typescript-eslint/typescript-estree": "5.3.0",
+ "debug": "^4.3.2"
+ }
+ },
+ "@typescript-eslint/scope-manager": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.3.0.tgz",
+ "integrity": "sha512-22Uic9oRlTsPppy5Tcwfj+QET5RWEnZ5414Prby465XxQrQFZ6nnm5KnXgnsAJefG4hEgMnaxTB3kNEyjdjj6A==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "5.3.0",
+ "@typescript-eslint/visitor-keys": "5.3.0"
+ }
+ },
+ "@typescript-eslint/types": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.3.0.tgz",
+ "integrity": "sha512-fce5pG41/w8O6ahQEhXmMV+xuh4+GayzqEogN24EK+vECA3I6pUwKuLi5QbXO721EMitpQne5VKXofPonYlAQg==",
+ "dev": true
+ },
+ "@typescript-eslint/typescript-estree": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.3.0.tgz",
+ "integrity": "sha512-FJ0nqcaUOpn/6Z4Jwbtf+o0valjBLkqc3MWkMvrhA2TvzFXtcclIM8F4MBEmYa2kgcI8EZeSAzwoSrIC8JYkug==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "5.3.0",
+ "@typescript-eslint/visitor-keys": "5.3.0",
+ "debug": "^4.3.2",
+ "globby": "^11.0.4",
+ "is-glob": "^4.0.3",
+ "semver": "^7.3.5",
+ "tsutils": "^3.21.0"
+ }
+ },
+ "@typescript-eslint/visitor-keys": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.3.0.tgz",
+ "integrity": "sha512-oVIAfIQuq0x2TFDNLVavUn548WL+7hdhxYn+9j3YdJJXB7mH9dAmZNJsPDa7Jc+B9WGqoiex7GUDbyMxV0a/aw==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "5.3.0",
+ "eslint-visitor-keys": "^3.0.0"
+ }
+ },
+ "@vscode/python-extension": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@vscode/python-extension/-/python-extension-1.0.4.tgz",
+ "integrity": "sha512-+m9VOUqv5TXZD52Ad8FjGbYGch7VqLAIys3NyVhgU6eSxmXVcRgqeon5ee224tOkTGtRQHdH5kDCa1Va/6LwjQ=="
+ },
+ "acorn": {
+ "version": "8.5.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz",
+ "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==",
+ "dev": true
+ },
+ "acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "requires": {}
+ },
+ "ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ansi-colors": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+ "dev": true
+ },
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true
+ },
+ "balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "requires": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ }
+ },
+ "debug": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
+ "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "requires": {
+ "path-type": "^4.0.0"
+ }
+ },
+ "doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ },
+ "enquirer": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
+ "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
+ "dev": true,
+ "requires": {
+ "ansi-colors": "^4.1.1"
+ }
+ },
+ "escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true
+ },
+ "eslint": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.2.0.tgz",
+ "integrity": "sha512-erw7XmM+CLxTOickrimJ1SiF55jiNlVSp2qqm0NuBWPtHYQCegD5ZMaW0c3i5ytPqL+SSLaCxdvQXFPLJn+ABw==",
+ "dev": true,
+ "requires": {
+ "@eslint/eslintrc": "^1.0.4",
+ "@humanwhocodes/config-array": "^0.6.0",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "enquirer": "^2.3.5",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^6.0.0",
+ "eslint-utils": "^3.0.0",
+ "eslint-visitor-keys": "^3.0.0",
+ "espree": "^9.0.0",
+ "esquery": "^1.4.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "functional-red-black-tree": "^1.0.1",
+ "glob-parent": "^6.0.1",
+ "globals": "^13.6.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.0.4",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "progress": "^2.0.0",
+ "regexpp": "^3.2.0",
+ "semver": "^7.2.1",
+ "strip-ansi": "^6.0.1",
+ "strip-json-comments": "^3.1.0",
+ "text-table": "^0.2.0",
+ "v8-compile-cache": "^2.0.3"
+ },
+ "dependencies": {
+ "eslint-scope": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-6.0.0.tgz",
+ "integrity": "sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ }
+ },
+ "estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true
+ },
+ "ignore": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ }
+ },
+ "eslint-utils": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+ "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^2.0.0"
+ },
+ "dependencies": {
+ "eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz",
+ "integrity": "sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q==",
+ "dev": true
+ },
+ "espree": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.0.0.tgz",
+ "integrity": "sha512-r5EQJcYZ2oaGbeR0jR0fFVijGOcwai07/690YRXLINuhmVeRY4UKSAsQPe/0BNuDgwP7Ophoc1PRsr2E3tkbdQ==",
+ "dev": true,
+ "requires": {
+ "acorn": "^8.5.0",
+ "acorn-jsx": "^5.3.1",
+ "eslint-visitor-keys": "^3.0.0"
+ }
+ },
+ "esquery": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
+ "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^5.1.0"
+ },
+ "dependencies": {
+ "estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true
+ }
+ }
+ },
+ "esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^5.2.0"
+ },
+ "dependencies": {
+ "estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true
+ }
+ }
+ },
+ "estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true
+ },
+ "esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true
+ },
+ "fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "fast-glob": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz",
+ "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==",
+ "dev": true,
+ "requires": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "dependencies": {
+ "glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ }
+ }
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+ "dev": true
+ },
+ "fastq": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
+ "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
+ "dev": true,
+ "requires": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "requires": {
+ "flat-cache": "^3.0.4"
+ }
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "flat-cache": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "dev": true,
+ "requires": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ }
+ },
+ "flatted": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz",
+ "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==",
+ "dev": true
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+ "dev": true
+ },
+ "glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.3"
+ }
+ },
+ "globals": {
+ "version": "13.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz",
+ "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==",
+ "dev": true,
+ "requires": {
+ "type-fest": "^0.20.2"
+ }
+ },
+ "globby": {
+ "version": "11.0.4",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz",
+ "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==",
+ "dev": true,
+ "requires": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.1.1",
+ "ignore": "^5.1.4",
+ "merge2": "^1.3.0",
+ "slash": "^3.0.0"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "ignore": {
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz",
+ "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==",
+ "dev": true
+ },
+ "import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "requires": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ }
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "requires": {
+ "argparse": "^2.0.1"
+ }
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+ "dev": true
+ },
+ "levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ }
+ },
+ "lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
+ "merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
+ "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
+ "dev": true,
+ "requires": {
+ "braces": "^3.0.1",
+ "picomatch": "^2.2.3"
+ }
+ },
+ "minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+ "dev": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "optionator": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+ "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "dev": true,
+ "requires": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ }
+ },
+ "parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "requires": {
+ "callsites": "^3.0.0"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true
+ },
+ "path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true
+ },
+ "picomatch": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
+ "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
+ "dev": true
+ },
+ "prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true
+ },
+ "progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true
+ },
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "dev": true
+ },
+ "queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true
+ },
+ "regexpp": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
+ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
+ "dev": true
+ },
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true
+ },
+ "reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "requires": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ },
+ "shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "requires": {
+ "shebang-regex": "^3.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true
+ },
+ "slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
+ "strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ },
+ "text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+ "dev": true
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ },
+ "tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
+ },
+ "tsutils": {
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
+ "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.8.1"
+ }
+ },
+ "type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "^1.2.1"
+ }
+ },
+ "type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true
+ },
+ "typescript": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz",
+ "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==",
+ "dev": true
+ },
+ "uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "v8-compile-cache": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
+ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
+ "dev": true
+ },
+ "vscode-jsonrpc": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz",
+ "integrity": "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw=="
+ },
+ "vscode-languageclient": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.1.0.tgz",
+ "integrity": "sha512-GL4QdbYUF/XxQlAsvYWZRV3V34kOkpRlvV60/72ghHfsYFnS/v2MANZ9P6sHmxFcZKOse8O+L9G7Czg0NUWing==",
+ "requires": {
+ "minimatch": "^5.1.0",
+ "semver": "^7.3.7",
+ "vscode-languageserver-protocol": "3.17.3"
+ },
+ "dependencies": {
+ "brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "requires": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "requires": {
+ "brace-expansion": "^2.0.1"
+ }
+ }
+ }
+ },
+ "vscode-languageserver-protocol": {
+ "version": "3.17.3",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz",
+ "integrity": "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==",
+ "requires": {
+ "vscode-jsonrpc": "8.1.0",
+ "vscode-languageserver-types": "3.17.3"
+ }
+ },
+ "vscode-languageserver-types": {
+ "version": "3.17.3",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz",
+ "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA=="
+ },
+ "which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "word-wrap": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
+ "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
+ "dev": true
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ }
+ }
+}
diff --git a/examples/vscode-playground/package.json b/examples/vscode-playground/package.json
new file mode 100644
index 0000000..9c6827d
--- /dev/null
+++ b/examples/vscode-playground/package.json
@@ -0,0 +1,149 @@
+{
+ "name": "pygls-playground",
+ "description": "Extension for experimenting with pygls powered language servers",
+ "author": "Open Law Library",
+ "repository": "https://github.com/openlawlibrary/pygls",
+ "license": "Apache-2.0",
+ "version": "1.0.2",
+ "publisher": "openlawlibrary",
+ "engines": {
+ "node": ">=16.17.1",
+ "vscode": "^1.78.0"
+ },
+ "extensionDependencies": [
+ "ms-python.python"
+ ],
+ "categories": [
+ "Programming Languages"
+ ],
+ "activationEvents": [
+ "onStartupFinished"
+ ],
+ "contributes": {
+ "commands": [
+ {
+ "command": "pygls.server.restart",
+ "title": "Restart Language Server",
+ "category": "pygls"
+ },
+ {
+ "command": "pygls.server.executeCommand",
+ "title": "Execute Command",
+ "category": "pygls"
+ }
+ ],
+ "configuration": [
+ {
+ "type": "object",
+ "title": "Json Server Configuration",
+ "properties": {
+ "pygls.jsonServer.exampleConfiguration": {
+ "scope": "resource",
+ "type": "string",
+ "default": "You can override this message"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "title": "Server Configuration",
+ "properties": {
+ "pygls.server.cwd": {
+ "scope": "resource",
+ "type": "string",
+ "description": "The working directory from which to launch the server.",
+ "markdownDescription": "The working directory from which to launch the server.\nIf blank, this will default to the `examples/servers` directory."
+ },
+ "pygls.server.debug": {
+ "scope": "resource",
+ "default": false,
+ "type": "boolean",
+ "description": "Enable debugging of the server process."
+ },
+ "pygls.server.debugHost": {
+ "scope": "resource",
+ "default": "localhost",
+ "type": "string",
+ "description": "The host on which the server process to debug is running."
+ },
+ "pygls.server.debugPort": {
+ "scope": "resource",
+ "default": 5678,
+ "type": "integer",
+ "description": "The port number on which the server process to debug is listening."
+ },
+ "pygls.server.launchScript": {
+ "scope": "resource",
+ "type": "string",
+ "default": "json_server.py",
+ "description": "The python script to run when launching the server.",
+ "markdownDescription": "The python script to run when launching the server.\n Relative to #pygls.server.cwd#"
+ },
+ "pygls.server.pythonPath": {
+ "scope": "resource",
+ "type": "string",
+ "default": "",
+ "description": "The python interpreter to use to run the server.\nBy default, this extension will attempt to use the Python interpreter configured via the Python extension, setting this setting will override this behavior."
+ },
+ "pygls.trace.server": {
+ "scope": "resource",
+ "type": "string",
+ "default": "off",
+ "enum": [
+ "off",
+ "messages",
+ "verbose"
+ ],
+ "description": "Controls if LSP messages send to/from the server should be logged.",
+ "enumDescriptions": [
+ "do not log any lsp messages",
+ "log all lsp messages sent to/from the server",
+ "log all lsp messages sent to/from the server, including their contents"
+ ]
+ }
+ }
+ },
+ {
+ "type": "object",
+ "title": "Client Configuration",
+ "properties": {
+ "pygls.client.documentSelector": {
+ "scope": "window",
+ "type": "array",
+ "items": {
+ "type": "object"
+ },
+ "default": [
+ {
+ "scheme": "file",
+ "language": "json"
+ }
+ ],
+ "description": "The client uses this to decide which documents the server is able to help with.",
+ "markdownDescription": "The client uses this to decide which documents the server is able to help with.\n See [DocumentSelector](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#documentFilter) in the LSP Specification for more details."
+ }
+ }
+ }
+ ]
+ },
+ "main": "./out/extension",
+ "scripts": {
+ "vscode:prepublish": "npm run compile",
+ "compile": "tsc -p .",
+ "watch": "tsc -p . -w"
+ },
+ "devDependencies": {
+ "@types/node": "^16.11.6",
+ "@types/semver": "^7.5.0",
+ "@types/vscode": "^1.78.0",
+ "@typescript-eslint/eslint-plugin": "^5.3.0",
+ "@typescript-eslint/parser": "^5.3.0",
+ "eslint": "^8.2.0",
+ "typescript": "^5.1.0"
+ },
+ "dependencies": {
+ "@vscode/python-extension": "^1.0.4",
+ "semver": "^7.5.4",
+ "vscode-languageclient": "^8.1.0"
+ }
+}
diff --git a/examples/vscode-playground/src/extension.ts b/examples/vscode-playground/src/extension.ts
new file mode 100644
index 0000000..8952f39
--- /dev/null
+++ b/examples/vscode-playground/src/extension.ts
@@ -0,0 +1,401 @@
+/* -------------------------------------------------------------------------
+ * Original work Copyright (c) Microsoft Corporation. All rights reserved.
+ * Original work licensed under the MIT License.
+ * See ThirdPartyNotices.txt in the project root for license information.
+ * All modifications Copyright (c) Open Law Library. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http: // www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ----------------------------------------------------------------------- */
+"use strict";
+
+import * as net from "net";
+import * as path from "path";
+import * as vscode from "vscode";
+import * as semver from "semver";
+
+import { PythonExtension } from "@vscode/python-extension";
+import { LanguageClient, LanguageClientOptions, ServerOptions, State, integer } from "vscode-languageclient/node";
+
+const MIN_PYTHON = semver.parse("3.7.9")
+
+// Some other nice to haves.
+// TODO: Check selected env satisfies pygls' requirements - if not offer to run the select env command.
+// TODO: TCP Transport
+// TODO: WS Transport
+// TODO: Web Extension support (requires WASM-WASI!)
+
+let client: LanguageClient;
+let clientStarting = false
+let python: PythonExtension;
+let logger: vscode.LogOutputChannel
+
+/**
+ * This is the main entry point.
+ * Called when vscode first activates the extension
+ */
+export async function activate(context: vscode.ExtensionContext) {
+ logger = vscode.window.createOutputChannel('pygls', { log: true })
+ logger.info("Extension activated.")
+
+ await getPythonExtension();
+ if (!python) {
+ return
+ }
+
+ // Restart language server command
+ context.subscriptions.push(
+ vscode.commands.registerCommand("pygls.server.restart", async () => {
+ logger.info('restarting server...')
+ await startLangServer()
+ })
+ )
+
+ // Execute command... command
+ context.subscriptions.push(
+ vscode.commands.registerCommand("pygls.server.executeCommand", async () => {
+ await executeServerCommand()
+ })
+ )
+
+ // Restart the language server if the user switches Python envs...
+ context.subscriptions.push(
+ python.environments.onDidChangeActiveEnvironmentPath(async () => {
+ logger.info('python env modified, restarting server...')
+ await startLangServer()
+ })
+ )
+
+ // ... or if they change a relevant config option
+ context.subscriptions.push(
+ vscode.workspace.onDidChangeConfiguration(async (event) => {
+ if (event.affectsConfiguration("pygls.server") || event.affectsConfiguration("pygls.client")) {
+ logger.info('config modified, restarting server...')
+ await startLangServer()
+ }
+ })
+ )
+
+ // Start the language server once the user opens the first text document...
+ context.subscriptions.push(
+ vscode.workspace.onDidOpenTextDocument(
+ async () => {
+ if (!client) {
+ await startLangServer()
+ }
+ }
+ )
+ )
+
+ // ...or notebook.
+ context.subscriptions.push(
+ vscode.workspace.onDidOpenNotebookDocument(
+ async () => {
+ if (!client) {
+ await startLangServer()
+ }
+ }
+ )
+ )
+
+ // Restart the server if the user modifies it.
+ context.subscriptions.push(
+ vscode.workspace.onDidSaveTextDocument(async (document: vscode.TextDocument) => {
+ const expectedUri = vscode.Uri.file(path.join(getCwd(), getServerPath()))
+
+ if (expectedUri.toString() === document.uri.toString()) {
+ logger.info('server modified, restarting...')
+ await startLangServer()
+ }
+ })
+ )
+}
+
+export function deactivate(): Thenable<void> {
+ return stopLangServer()
+}
+
+/**
+ * Start (or restart) the language server.
+ *
+ * @param command The executable to run
+ * @param args Arguments to pass to the executable
+ * @param cwd The working directory in which to run the executable
+ * @returns
+ */
+async function startLangServer() {
+
+ // Don't interfere if we are already in the process of launching the server.
+ if (clientStarting) {
+ return
+ }
+
+ clientStarting = true
+ if (client) {
+ await stopLangServer()
+ }
+ const config = vscode.workspace.getConfiguration("pygls.server")
+ const cwd = getCwd()
+ const serverPath = getServerPath()
+
+ logger.info(`cwd: '${cwd}'`)
+ logger.info(`server: '${serverPath}'`)
+
+ const resource = vscode.Uri.joinPath(vscode.Uri.file(cwd), serverPath)
+ const pythonCommand = await getPythonCommand(resource)
+ if (!pythonCommand) {
+ clientStarting = false
+ return
+ }
+
+ logger.debug(`python: ${pythonCommand.join(" ")}`)
+ const serverOptions: ServerOptions = {
+ command: pythonCommand[0],
+ args: [...pythonCommand.slice(1), serverPath],
+ options: { cwd },
+ };
+
+ client = new LanguageClient('pygls', serverOptions, getClientOptions());
+ const promises = [client.start()]
+
+ if (config.get<boolean>("debug")) {
+ promises.push(startDebugging())
+ }
+
+ const results = await Promise.allSettled(promises)
+ clientStarting = false
+
+ for (const result of results) {
+ if (result.status === "rejected") {
+ logger.error(`There was a error starting the server: ${result.reason}`)
+ }
+ }
+}
+
+async function stopLangServer(): Promise<void> {
+ if (!client) {
+ return
+ }
+
+ if (client.state === State.Running) {
+ await client.stop()
+ }
+
+ client.dispose()
+ client = undefined
+}
+
+function startDebugging(): Promise<void> {
+ if (!vscode.workspace.workspaceFolders) {
+ logger.error("Unable to start debugging, there is no workspace.")
+ return Promise.reject("Unable to start debugging, there is no workspace.")
+ }
+ // TODO: Is there a more reliable way to ensure the debug adapter is ready?
+ setTimeout(async () => {
+ await vscode.debug.startDebugging(vscode.workspace.workspaceFolders[0], "pygls: Debug Server")
+ }, 2000)
+}
+
+function getClientOptions(): LanguageClientOptions {
+ const config = vscode.workspace.getConfiguration('pygls.client')
+ const options = {
+ documentSelector: config.get<any>('documentSelector'),
+ outputChannel: logger,
+ connectionOptions: {
+ maxRestartCount: 0 // don't restart on server failure.
+ },
+ };
+ logger.info(`client options: ${JSON.stringify(options, undefined, 2)}`)
+ return options
+}
+
+function startLangServerTCP(addr: number): LanguageClient {
+ const serverOptions: ServerOptions = () => {
+ return new Promise((resolve /*, reject */) => {
+ const clientSocket = new net.Socket();
+ clientSocket.connect(addr, "127.0.0.1", () => {
+ resolve({
+ reader: clientSocket,
+ writer: clientSocket,
+ });
+ });
+ });
+ };
+
+ return new LanguageClient(
+ `tcp lang server (port ${addr})`,
+ serverOptions,
+ getClientOptions()
+ );
+}
+
+/**
+ * Execute a command provided by the language server.
+ */
+async function executeServerCommand() {
+ if (!client || client.state !== State.Running) {
+ await vscode.window.showErrorMessage("There is no language server running.")
+ return
+ }
+
+ const knownCommands = client.initializeResult.capabilities.executeCommandProvider?.commands
+ if (!knownCommands || knownCommands.length === 0) {
+ const info = client.initializeResult.serverInfo
+ const name = info?.name || "Server"
+ const version = info?.version || ""
+
+ await vscode.window.showInformationMessage(`${name} ${version} does not implement any commands.`)
+ return
+ }
+
+ const commandName = await vscode.window.showQuickPick(knownCommands, { canPickMany: false })
+ if (!commandName) {
+ return
+ }
+ logger.info(`executing command: '${commandName}'`)
+
+ const result = await vscode.commands.executeCommand(commandName /* if your command accepts arguments you can pass them here */)
+ logger.info(`${commandName} result: ${JSON.stringify(result, undefined, 2)}`)
+}
+
+/**
+ * If the user has explicitly provided a src directory use that.
+ * Otherwise, fallback to the examples/servers directory.
+ *
+ * @returns The working directory from which to launch the server
+ */
+function getCwd(): string {
+ const config = vscode.workspace.getConfiguration("pygls.server")
+ const cwd = config.get<string>('cwd')
+ if (cwd) {
+ return cwd
+ }
+
+ const serverDir = path.resolve(
+ path.join(__dirname, "..", "..", "servers")
+ )
+ return serverDir
+}
+
+/**
+ *
+ * @returns The python script that implements the server.
+ */
+function getServerPath(): string {
+ const config = vscode.workspace.getConfiguration("pygls.server")
+ const server = config.get<string>('launchScript')
+ return server
+}
+
+/**
+ * Return the python command to use when starting the server.
+ *
+ * If debugging is enabled, this will also included the arguments to required
+ * to wrap the server in a debug adapter.
+ *
+ * @returns The full python command needed in order to start the server.
+ */
+async function getPythonCommand(resource?: vscode.Uri): Promise<string[] | undefined> {
+ const config = vscode.workspace.getConfiguration("pygls.server", resource)
+ const pythonPath = await getPythonInterpreter(resource)
+ if (!pythonPath) {
+ return
+ }
+ const command = [pythonPath]
+ const enableDebugger = config.get<boolean>('debug')
+
+ if (!enableDebugger) {
+ return command
+ }
+
+ const debugHost = config.get<string>('debugHost')
+ const debugPort = config.get<integer>('debugPort')
+ try {
+ const debugArgs = await python.debug.getRemoteLauncherCommand(debugHost, debugPort, true)
+ // Debugpy recommends we disable frozen modules
+ command.push("-Xfrozen_modules=off", ...debugArgs)
+ } catch (err) {
+ logger.error(`Unable to get debugger command: ${err}`)
+ logger.error("Debugger will not be available.")
+ }
+
+ return command
+}
+
+/**
+ * Return the python interpreter to use when starting the server.
+ *
+ * This uses the official python extension to grab the user's currently
+ * configured environment.
+ *
+ * @returns The python interpreter to use to launch the server
+ */
+async function getPythonInterpreter(resource?: vscode.Uri): Promise<string | undefined> {
+ const config = vscode.workspace.getConfiguration("pygls.server", resource)
+ const pythonPath = config.get<string>('pythonPath')
+ if (pythonPath) {
+ logger.info(`Using user configured python environment: '${pythonPath}'`)
+ return pythonPath
+ }
+
+ if (!python) {
+ return
+ }
+
+ if (resource) {
+ logger.info(`Looking for environment in which to execute: '${resource.toString()}'`)
+ }
+ // Use whichever python interpreter the user has configured.
+ const activeEnvPath = python.environments.getActiveEnvironmentPath(resource)
+ logger.info(`Found environment: ${activeEnvPath.id}: ${activeEnvPath.path}`)
+
+ const activeEnv = await python.environments.resolveEnvironment(activeEnvPath)
+ if (!activeEnv) {
+ logger.error(`Unable to resolve envrionment: ${activeEnvPath}`)
+ return
+ }
+
+ const v = activeEnv.version
+ const pythonVersion = semver.parse(`${v.major}.${v.minor}.${v.micro}`)
+
+ // Check to see if the environment satisfies the min Python version.
+ if (semver.lt(pythonVersion, MIN_PYTHON)) {
+ const message = [
+ `Your currently configured environment provides Python v${pythonVersion} `,
+ `but pygls requires v${MIN_PYTHON}.\n\nPlease choose another environment.`
+ ].join('')
+
+ const response = await vscode.window.showErrorMessage(message, "Change Environment")
+ if (!response) {
+ return
+ } else {
+ await vscode.commands.executeCommand('python.setInterpreter')
+ return
+ }
+ }
+
+ const pythonUri = activeEnv.executable.uri
+ if (!pythonUri) {
+ logger.error(`URI of Python executable is undefined!`)
+ return
+ }
+
+ return pythonUri.fsPath
+}
+
+async function getPythonExtension() {
+ try {
+ python = await PythonExtension.api();
+ } catch (err) {
+ logger.error(`Unable to load python extension: ${err}`)
+ }
+}
diff --git a/examples/vscode-playground/tsconfig.json b/examples/vscode-playground/tsconfig.json
new file mode 100644
index 0000000..5d7a321
--- /dev/null
+++ b/examples/vscode-playground/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es2019",
+ "lib": [
+ "ES2019"
+ ],
+ "rootDir": "src",
+ "outDir": "out",
+ "sourceMap": true
+ },
+ "include": [
+ "src"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/poetry.lock b/poetry.lock
new file mode 100644
index 0000000..c66f71c
--- /dev/null
+++ b/poetry.lock
@@ -0,0 +1,1265 @@
+# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
+
+[[package]]
+name = "alabaster"
+version = "0.7.13"
+description = "A configurable sidebar-enabled Sphinx theme"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"},
+ {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"},
+]
+
+[[package]]
+name = "attrs"
+version = "23.2.0"
+description = "Classes Without Boilerplate"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"},
+ {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"},
+]
+
+[package.extras]
+cov = ["attrs[tests]", "coverage[toml] (>=5.3)"]
+dev = ["attrs[tests]", "pre-commit"]
+docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"]
+tests = ["attrs[tests-no-zope]", "zope-interface"]
+tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"]
+tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"]
+
+[[package]]
+name = "babel"
+version = "2.14.0"
+description = "Internationalization utilities"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"},
+ {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"},
+]
+
+[package.dependencies]
+pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""}
+
+[package.extras]
+dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
+
+[[package]]
+name = "black"
+version = "23.12.1"
+description = "The uncompromising code formatter."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"},
+ {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"},
+ {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"},
+ {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"},
+ {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"},
+ {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"},
+ {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"},
+ {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"},
+ {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"},
+ {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"},
+ {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"},
+ {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"},
+ {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"},
+ {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"},
+ {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"},
+ {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"},
+ {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"},
+ {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"},
+ {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"},
+ {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"},
+ {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"},
+ {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"},
+]
+
+[package.dependencies]
+click = ">=8.0.0"
+mypy-extensions = ">=0.4.3"
+packaging = ">=22.0"
+pathspec = ">=0.9.0"
+platformdirs = ">=2"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
+
+[package.extras]
+colorama = ["colorama (>=0.4.3)"]
+d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
+jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
+uvloop = ["uvloop (>=0.15.2)"]
+
+[[package]]
+name = "cattrs"
+version = "23.2.3"
+description = "Composable complex class support for attrs and dataclasses."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "cattrs-23.2.3-py3-none-any.whl", hash = "sha256:0341994d94971052e9ee70662542699a3162ea1e0c62f7ce1b4a57f563685108"},
+ {file = "cattrs-23.2.3.tar.gz", hash = "sha256:a934090d95abaa9e911dac357e3a8699e0b4b14f8529bcc7d2b1ad9d51672b9f"},
+]
+
+[package.dependencies]
+attrs = ">=23.1.0"
+exceptiongroup = {version = ">=1.1.1", markers = "python_version < \"3.11\""}
+typing-extensions = {version = ">=4.1.0,<4.6.3 || >4.6.3", markers = "python_version < \"3.11\""}
+
+[package.extras]
+bson = ["pymongo (>=4.4.0)"]
+cbor2 = ["cbor2 (>=5.4.6)"]
+msgpack = ["msgpack (>=1.0.5)"]
+orjson = ["orjson (>=3.9.2)"]
+pyyaml = ["pyyaml (>=6.0)"]
+tomlkit = ["tomlkit (>=0.11.8)"]
+ujson = ["ujson (>=5.7.0)"]
+
+[[package]]
+name = "certifi"
+version = "2023.11.17"
+description = "Python package for providing Mozilla's CA Bundle."
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"},
+ {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"},
+]
+
+[[package]]
+name = "cffi"
+version = "1.16.0"
+description = "Foreign Function Interface for Python calling C code."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"},
+ {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"},
+ {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"},
+ {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"},
+ {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"},
+ {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"},
+ {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"},
+ {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"},
+ {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"},
+ {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"},
+ {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"},
+ {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"},
+ {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"},
+ {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"},
+ {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"},
+]
+
+[package.dependencies]
+pycparser = "*"
+
+[[package]]
+name = "charset-normalizer"
+version = "3.3.2"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+optional = false
+python-versions = ">=3.7.0"
+files = [
+ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"},
+ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
+]
+
+[[package]]
+name = "click"
+version = "8.1.7"
+description = "Composable command line interface toolkit"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
+ {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "coverage"
+version = "7.4.0"
+description = "Code coverage measurement for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "coverage-7.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36b0ea8ab20d6a7564e89cb6135920bc9188fb5f1f7152e94e8300b7b189441a"},
+ {file = "coverage-7.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0676cd0ba581e514b7f726495ea75aba3eb20899d824636c6f59b0ed2f88c471"},
+ {file = "coverage-7.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ca5c71a5a1765a0f8f88022c52b6b8be740e512980362f7fdbb03725a0d6b9"},
+ {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7c97726520f784239f6c62506bc70e48d01ae71e9da128259d61ca5e9788516"},
+ {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:815ac2d0f3398a14286dc2cea223a6f338109f9ecf39a71160cd1628786bc6f5"},
+ {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:80b5ee39b7f0131ebec7968baa9b2309eddb35b8403d1869e08f024efd883566"},
+ {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5b2ccb7548a0b65974860a78c9ffe1173cfb5877460e5a229238d985565574ae"},
+ {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:995ea5c48c4ebfd898eacb098164b3cc826ba273b3049e4a889658548e321b43"},
+ {file = "coverage-7.4.0-cp310-cp310-win32.whl", hash = "sha256:79287fd95585ed36e83182794a57a46aeae0b64ca53929d1176db56aacc83451"},
+ {file = "coverage-7.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:5b14b4f8760006bfdb6e08667af7bc2d8d9bfdb648351915315ea17645347137"},
+ {file = "coverage-7.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04387a4a6ecb330c1878907ce0dc04078ea72a869263e53c72a1ba5bbdf380ca"},
+ {file = "coverage-7.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea81d8f9691bb53f4fb4db603203029643caffc82bf998ab5b59ca05560f4c06"},
+ {file = "coverage-7.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74775198b702868ec2d058cb92720a3c5a9177296f75bd97317c787daf711505"},
+ {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f03940f9973bfaee8cfba70ac991825611b9aac047e5c80d499a44079ec0bc"},
+ {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:485e9f897cf4856a65a57c7f6ea3dc0d4e6c076c87311d4bc003f82cfe199d25"},
+ {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6ae8c9d301207e6856865867d762a4b6fd379c714fcc0607a84b92ee63feff70"},
+ {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bf477c355274a72435ceb140dc42de0dc1e1e0bf6e97195be30487d8eaaf1a09"},
+ {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:83c2dda2666fe32332f8e87481eed056c8b4d163fe18ecc690b02802d36a4d26"},
+ {file = "coverage-7.4.0-cp311-cp311-win32.whl", hash = "sha256:697d1317e5290a313ef0d369650cfee1a114abb6021fa239ca12b4849ebbd614"},
+ {file = "coverage-7.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:26776ff6c711d9d835557ee453082025d871e30b3fd6c27fcef14733f67f0590"},
+ {file = "coverage-7.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:13eaf476ec3e883fe3e5fe3707caeb88268a06284484a3daf8250259ef1ba143"},
+ {file = "coverage-7.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846f52f46e212affb5bcf131c952fb4075b55aae6b61adc9856222df89cbe3e2"},
+ {file = "coverage-7.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f66da8695719ccf90e794ed567a1549bb2644a706b41e9f6eae6816b398c4a"},
+ {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:164fdcc3246c69a6526a59b744b62e303039a81e42cfbbdc171c91a8cc2f9446"},
+ {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:316543f71025a6565677d84bc4df2114e9b6a615aa39fb165d697dba06a54af9"},
+ {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bb1de682da0b824411e00a0d4da5a784ec6496b6850fdf8c865c1d68c0e318dd"},
+ {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0e8d06778e8fbffccfe96331a3946237f87b1e1d359d7fbe8b06b96c95a5407a"},
+ {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a56de34db7b7ff77056a37aedded01b2b98b508227d2d0979d373a9b5d353daa"},
+ {file = "coverage-7.4.0-cp312-cp312-win32.whl", hash = "sha256:51456e6fa099a8d9d91497202d9563a320513fcf59f33991b0661a4a6f2ad450"},
+ {file = "coverage-7.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd3c1e4cb2ff0083758f09be0f77402e1bdf704adb7f89108007300a6da587d0"},
+ {file = "coverage-7.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9d1bf53c4c8de58d22e0e956a79a5b37f754ed1ffdbf1a260d9dcfa2d8a325e"},
+ {file = "coverage-7.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:109f5985182b6b81fe33323ab4707011875198c41964f014579cf82cebf2bb85"},
+ {file = "coverage-7.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc9d4bc55de8003663ec94c2f215d12d42ceea128da8f0f4036235a119c88ac"},
+ {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc6d65b21c219ec2072c1293c505cf36e4e913a3f936d80028993dd73c7906b1"},
+ {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a10a4920def78bbfff4eff8a05c51be03e42f1c3735be42d851f199144897ba"},
+ {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b8e99f06160602bc64da35158bb76c73522a4010f0649be44a4e167ff8555952"},
+ {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7d360587e64d006402b7116623cebf9d48893329ef035278969fa3bbf75b697e"},
+ {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29f3abe810930311c0b5d1a7140f6395369c3db1be68345638c33eec07535105"},
+ {file = "coverage-7.4.0-cp38-cp38-win32.whl", hash = "sha256:5040148f4ec43644702e7b16ca864c5314ccb8ee0751ef617d49aa0e2d6bf4f2"},
+ {file = "coverage-7.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9864463c1c2f9cb3b5db2cf1ff475eed2f0b4285c2aaf4d357b69959941aa555"},
+ {file = "coverage-7.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:936d38794044b26c99d3dd004d8af0035ac535b92090f7f2bb5aa9c8e2f5cd42"},
+ {file = "coverage-7.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:799c8f873794a08cdf216aa5d0531c6a3747793b70c53f70e98259720a6fe2d7"},
+ {file = "coverage-7.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7defbb9737274023e2d7af02cac77043c86ce88a907c58f42b580a97d5bcca9"},
+ {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1526d265743fb49363974b7aa8d5899ff64ee07df47dd8d3e37dcc0818f09ed"},
+ {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf635a52fc1ea401baf88843ae8708591aa4adff875e5c23220de43b1ccf575c"},
+ {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:756ded44f47f330666843b5781be126ab57bb57c22adbb07d83f6b519783b870"},
+ {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0eb3c2f32dabe3a4aaf6441dde94f35687224dfd7eb2a7f47f3fd9428e421058"},
+ {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bfd5db349d15c08311702611f3dccbef4b4e2ec148fcc636cf8739519b4a5c0f"},
+ {file = "coverage-7.4.0-cp39-cp39-win32.whl", hash = "sha256:53d7d9158ee03956e0eadac38dfa1ec8068431ef8058fe6447043db1fb40d932"},
+ {file = "coverage-7.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfd2a8b6b0d8e66e944d47cdec2f47c48fef2ba2f2dff5a9a75757f64172857e"},
+ {file = "coverage-7.4.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:c530833afc4707fe48524a44844493f36d8727f04dcce91fb978c414a8556cc6"},
+ {file = "coverage-7.4.0.tar.gz", hash = "sha256:707c0f58cb1712b8809ece32b68996ee1e609f71bd14615bd8f87a1293cb610e"},
+]
+
+[package.dependencies]
+tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
+
+[package.extras]
+toml = ["tomli"]
+
+[[package]]
+name = "docutils"
+version = "0.20.1"
+description = "Docutils -- Python Documentation Utilities"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"},
+ {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"},
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.2.0"
+description = "Backport of PEP 654 (exception groups)"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"},
+ {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"},
+]
+
+[package.extras]
+test = ["pytest (>=6)"]
+
+[[package]]
+name = "h11"
+version = "0.14.0"
+description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
+ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
+]
+
+[[package]]
+name = "idna"
+version = "3.6"
+description = "Internationalized Domain Names in Applications (IDNA)"
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
+ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
+]
+
+[[package]]
+name = "imagesize"
+version = "1.4.1"
+description = "Getting image size from png/jpeg/jpeg2000/gif file"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"},
+ {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"},
+]
+
+[[package]]
+name = "importlib-metadata"
+version = "7.0.1"
+description = "Read metadata from Python packages"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"},
+ {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"},
+]
+
+[package.dependencies]
+zipp = ">=0.5"
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
+perf = ["ipython"]
+testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"]
+
+[[package]]
+name = "iniconfig"
+version = "2.0.0"
+description = "brain-dead simple config-ini parsing"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
+ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
+]
+
+[[package]]
+name = "jinja2"
+version = "3.1.3"
+description = "A very fast and expressive template engine."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
+ {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
+]
+
+[package.dependencies]
+MarkupSafe = ">=2.0"
+
+[package.extras]
+i18n = ["Babel (>=2.7)"]
+
+[[package]]
+name = "lsprotocol"
+version = "2023.0.1"
+description = "Python implementation of the Language Server Protocol."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "lsprotocol-2023.0.1-py3-none-any.whl", hash = "sha256:c75223c9e4af2f24272b14c6375787438279369236cd568f596d4951052a60f2"},
+ {file = "lsprotocol-2023.0.1.tar.gz", hash = "sha256:cc5c15130d2403c18b734304339e51242d3018a05c4f7d0f198ad6e0cd21861d"},
+]
+
+[package.dependencies]
+attrs = ">=21.3.0"
+cattrs = "!=23.2.1"
+
+[[package]]
+name = "markupsafe"
+version = "2.1.3"
+description = "Safely add untrusted strings to HTML/XML markup."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"},
+ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"},
+]
+
+[[package]]
+name = "mypy"
+version = "1.8.0"
+description = "Optional static typing for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"},
+ {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"},
+ {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"},
+ {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"},
+ {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"},
+ {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"},
+ {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"},
+ {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"},
+ {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"},
+ {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"},
+ {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"},
+ {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"},
+ {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"},
+ {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"},
+ {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"},
+ {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"},
+ {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"},
+ {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"},
+ {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"},
+ {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"},
+ {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"},
+ {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"},
+ {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"},
+ {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"},
+ {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"},
+ {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"},
+ {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"},
+]
+
+[package.dependencies]
+mypy-extensions = ">=1.0.0"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typing-extensions = ">=4.1.0"
+
+[package.extras]
+dmypy = ["psutil (>=4.0)"]
+install-types = ["pip"]
+mypyc = ["setuptools (>=50)"]
+reports = ["lxml"]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.0.0"
+description = "Type system extensions for programs checked with the mypy type checker."
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
+ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
+]
+
+[[package]]
+name = "outcome"
+version = "1.3.0.post0"
+description = "Capture the outcome of Python function calls."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"},
+ {file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"},
+]
+
+[package.dependencies]
+attrs = ">=19.2.0"
+
+[[package]]
+name = "packaging"
+version = "23.2"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
+ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
+]
+
+[[package]]
+name = "pastel"
+version = "0.2.1"
+description = "Bring colors to your terminal."
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+ {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"},
+ {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"},
+]
+
+[[package]]
+name = "pathspec"
+version = "0.12.1"
+description = "Utility library for gitignore style pattern matching of file paths."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
+ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.1.0"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"},
+ {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"},
+]
+
+[package.extras]
+docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
+
+[[package]]
+name = "pluggy"
+version = "1.3.0"
+description = "plugin and hook calling mechanisms for python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"},
+ {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"},
+]
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["pytest", "pytest-benchmark"]
+
+[[package]]
+name = "poethepoet"
+version = "0.24.4"
+description = "A task runner that works well with poetry."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "poethepoet-0.24.4-py3-none-any.whl", hash = "sha256:fb4ea35d7f40fe2081ea917d2e4102e2310fda2cde78974050ca83896e229075"},
+ {file = "poethepoet-0.24.4.tar.gz", hash = "sha256:ff4220843a87c888cbcb5312c8905214701d0af60ac7271795baa8369b428fef"},
+]
+
+[package.dependencies]
+pastel = ">=0.2.1,<0.3.0"
+tomli = ">=1.2.2"
+
+[package.extras]
+poetry-plugin = ["poetry (>=1.0,<2.0)"]
+
+[[package]]
+name = "pycparser"
+version = "2.21"
+description = "C parser in Python"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
+ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
+]
+
+[[package]]
+name = "pygments"
+version = "2.17.2"
+description = "Pygments is a syntax highlighting package written in Python."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"},
+ {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"},
+]
+
+[package.extras]
+plugins = ["importlib-metadata"]
+windows-terminal = ["colorama (>=0.4.6)"]
+
+[[package]]
+name = "pysocks"
+version = "1.7.1"
+description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information."
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+ {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"},
+ {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"},
+ {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"},
+]
+
+[[package]]
+name = "pytest"
+version = "7.4.4"
+description = "pytest: simple powerful testing with Python"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
+ {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=0.12,<2.0"
+tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
+
+[package.extras]
+testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
+
+[[package]]
+name = "pytest-asyncio"
+version = "0.23.3"
+description = "Pytest support for asyncio"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pytest-asyncio-0.23.3.tar.gz", hash = "sha256:af313ce900a62fbe2b1aed18e37ad757f1ef9940c6b6a88e2954de38d6b1fb9f"},
+ {file = "pytest_asyncio-0.23.3-py3-none-any.whl", hash = "sha256:37a9d912e8338ee7b4a3e917381d1c95bfc8682048cb0fbc35baba316ec1faba"},
+]
+
+[package.dependencies]
+pytest = ">=7.0.0"
+
+[package.extras]
+docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
+testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
+
+[[package]]
+name = "pytz"
+version = "2023.3.post1"
+description = "World timezone definitions, modern and historical"
+optional = false
+python-versions = "*"
+files = [
+ {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"},
+ {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"},
+]
+
+[[package]]
+name = "requests"
+version = "2.31.0"
+description = "Python HTTP for Humans."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
+ {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
+]
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = ">=2,<4"
+idna = ">=2.5,<4"
+urllib3 = ">=1.21.1,<3"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
+
+[[package]]
+name = "ruff"
+version = "0.1.13"
+description = "An extremely fast Python linter and code formatter, written in Rust."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "ruff-0.1.13-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e3fd36e0d48aeac672aa850045e784673449ce619afc12823ea7868fcc41d8ba"},
+ {file = "ruff-0.1.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9fb6b3b86450d4ec6a6732f9f60c4406061b6851c4b29f944f8c9d91c3611c7a"},
+ {file = "ruff-0.1.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b13ba5d7156daaf3fd08b6b993360a96060500aca7e307d95ecbc5bb47a69296"},
+ {file = "ruff-0.1.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9ebb40442f7b531e136d334ef0851412410061e65d61ca8ce90d894a094feb22"},
+ {file = "ruff-0.1.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226b517f42d59a543d6383cfe03cccf0091e3e0ed1b856c6824be03d2a75d3b6"},
+ {file = "ruff-0.1.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5f0312ba1061e9b8c724e9a702d3c8621e3c6e6c2c9bd862550ab2951ac75c16"},
+ {file = "ruff-0.1.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2f59bcf5217c661254bd6bc42d65a6fd1a8b80c48763cb5c2293295babd945dd"},
+ {file = "ruff-0.1.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6894b00495e00c27b6ba61af1fc666f17de6140345e5ef27dd6e08fb987259d"},
+ {file = "ruff-0.1.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1600942485c6e66119da294c6294856b5c86fd6df591ce293e4a4cc8e72989"},
+ {file = "ruff-0.1.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ee3febce7863e231a467f90e681d3d89210b900d49ce88723ce052c8761be8c7"},
+ {file = "ruff-0.1.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dcaab50e278ff497ee4d1fe69b29ca0a9a47cd954bb17963628fa417933c6eb1"},
+ {file = "ruff-0.1.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f57de973de4edef3ad3044d6a50c02ad9fc2dff0d88587f25f1a48e3f72edf5e"},
+ {file = "ruff-0.1.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7a36fa90eb12208272a858475ec43ac811ac37e91ef868759770b71bdabe27b6"},
+ {file = "ruff-0.1.13-py3-none-win32.whl", hash = "sha256:a623349a505ff768dad6bd57087e2461be8db58305ebd5577bd0e98631f9ae69"},
+ {file = "ruff-0.1.13-py3-none-win_amd64.whl", hash = "sha256:f988746e3c3982bea7f824c8fa318ce7f538c4dfefec99cd09c8770bd33e6539"},
+ {file = "ruff-0.1.13-py3-none-win_arm64.whl", hash = "sha256:6bbbc3042075871ec17f28864808540a26f0f79a4478c357d3e3d2284e832998"},
+ {file = "ruff-0.1.13.tar.gz", hash = "sha256:e261f1baed6291f434ffb1d5c6bd8051d1c2a26958072d38dfbec39b3dda7352"},
+]
+
+[[package]]
+name = "selenium"
+version = "4.16.0"
+description = ""
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "selenium-4.16.0-py3-none-any.whl", hash = "sha256:aec71f4e6ed6cb3ec25c9c1b5ed56ae31b6da0a7f17474c7566d303f84e6219f"},
+ {file = "selenium-4.16.0.tar.gz", hash = "sha256:b2e987a445306151f7be0e6dfe2aa72a479c2ac6a91b9d5ef2d6dd4e49ad0435"},
+]
+
+[package.dependencies]
+certifi = ">=2021.10.8"
+trio = ">=0.17,<1.0"
+trio-websocket = ">=0.9,<1.0"
+urllib3 = {version = ">=1.26,<3", extras = ["socks"]}
+
+[[package]]
+name = "sniffio"
+version = "1.3.0"
+description = "Sniff out which async library your code is running under"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
+ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
+]
+
+[[package]]
+name = "snowballstemmer"
+version = "2.2.0"
+description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
+optional = false
+python-versions = "*"
+files = [
+ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
+ {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
+]
+
+[[package]]
+name = "sortedcontainers"
+version = "2.4.0"
+description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
+optional = false
+python-versions = "*"
+files = [
+ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"},
+ {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
+]
+
+[[package]]
+name = "sphinx"
+version = "7.1.2"
+description = "Python documentation generator"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe"},
+ {file = "sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f"},
+]
+
+[package.dependencies]
+alabaster = ">=0.7,<0.8"
+babel = ">=2.9"
+colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
+docutils = ">=0.18.1,<0.21"
+imagesize = ">=1.3"
+importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""}
+Jinja2 = ">=3.0"
+packaging = ">=21.0"
+Pygments = ">=2.13"
+requests = ">=2.25.0"
+snowballstemmer = ">=2.0"
+sphinxcontrib-applehelp = "*"
+sphinxcontrib-devhelp = "*"
+sphinxcontrib-htmlhelp = ">=2.0.0"
+sphinxcontrib-jsmath = "*"
+sphinxcontrib-qthelp = "*"
+sphinxcontrib-serializinghtml = ">=1.1.5"
+
+[package.extras]
+docs = ["sphinxcontrib-websupport"]
+lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"]
+test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"]
+
+[[package]]
+name = "sphinx-rtd-theme"
+version = "2.0.0"
+description = "Read the Docs theme for Sphinx"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "sphinx_rtd_theme-2.0.0-py2.py3-none-any.whl", hash = "sha256:ec93d0856dc280cf3aee9a4c9807c60e027c7f7b461b77aeffed682e68f0e586"},
+ {file = "sphinx_rtd_theme-2.0.0.tar.gz", hash = "sha256:bd5d7b80622406762073a04ef8fadc5f9151261563d47027de09910ce03afe6b"},
+]
+
+[package.dependencies]
+docutils = "<0.21"
+sphinx = ">=5,<8"
+sphinxcontrib-jquery = ">=4,<5"
+
+[package.extras]
+dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"]
+
+[[package]]
+name = "sphinxcontrib-applehelp"
+version = "1.0.4"
+description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"},
+ {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"},
+]
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-devhelp"
+version = "1.0.2"
+description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"},
+ {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"},
+]
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-htmlhelp"
+version = "2.0.1"
+description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"},
+ {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"},
+]
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["html5lib", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-jquery"
+version = "4.1"
+description = "Extension to include jQuery on newer Sphinx releases"
+optional = false
+python-versions = ">=2.7"
+files = [
+ {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"},
+ {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"},
+]
+
+[package.dependencies]
+Sphinx = ">=1.8"
+
+[[package]]
+name = "sphinxcontrib-jsmath"
+version = "1.0.1"
+description = "A sphinx extension which renders display math in HTML via JavaScript"
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
+ {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
+]
+
+[package.extras]
+test = ["flake8", "mypy", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-qthelp"
+version = "1.0.3"
+description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"},
+ {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"},
+]
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-serializinghtml"
+version = "1.1.5"
+description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"},
+ {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"},
+]
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "tomli"
+version = "2.0.1"
+description = "A lil' TOML parser"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
+]
+
+[[package]]
+name = "trio"
+version = "0.24.0"
+description = "A friendly Python library for async concurrency and I/O"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "trio-0.24.0-py3-none-any.whl", hash = "sha256:c3bd3a4e3e3025cd9a2241eae75637c43fe0b9e88b4c97b9161a55b9e54cd72c"},
+ {file = "trio-0.24.0.tar.gz", hash = "sha256:ffa09a74a6bf81b84f8613909fb0beaee84757450183a7a2e0b47b455c0cac5d"},
+]
+
+[package.dependencies]
+attrs = ">=20.1.0"
+cffi = {version = ">=1.14", markers = "os_name == \"nt\" and implementation_name != \"pypy\""}
+exceptiongroup = {version = "*", markers = "python_version < \"3.11\""}
+idna = "*"
+outcome = "*"
+sniffio = ">=1.3.0"
+sortedcontainers = "*"
+
+[[package]]
+name = "trio-websocket"
+version = "0.11.1"
+description = "WebSocket library for Trio"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "trio-websocket-0.11.1.tar.gz", hash = "sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f"},
+ {file = "trio_websocket-0.11.1-py3-none-any.whl", hash = "sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638"},
+]
+
+[package.dependencies]
+exceptiongroup = {version = "*", markers = "python_version < \"3.11\""}
+trio = ">=0.11"
+wsproto = ">=0.14"
+
+[[package]]
+name = "typing-extensions"
+version = "4.9.0"
+description = "Backported and Experimental Type Hints for Python 3.8+"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
+ {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
+]
+
+[[package]]
+name = "urllib3"
+version = "2.1.0"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"},
+ {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"},
+]
+
+[package.dependencies]
+pysocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""}
+
+[package.extras]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
+socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
+zstd = ["zstandard (>=0.18.0)"]
+
+[[package]]
+name = "websockets"
+version = "12.0"
+description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
+optional = true
+python-versions = ">=3.8"
+files = [
+ {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"},
+ {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"},
+ {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"},
+ {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"},
+ {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"},
+ {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"},
+ {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"},
+ {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"},
+ {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"},
+ {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"},
+ {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"},
+ {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"},
+ {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"},
+ {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"},
+ {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"},
+ {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"},
+ {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"},
+ {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"},
+ {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"},
+ {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"},
+ {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"},
+ {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"},
+ {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"},
+ {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"},
+ {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"},
+ {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"},
+ {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"},
+ {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"},
+ {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"},
+ {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"},
+ {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"},
+ {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"},
+ {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"},
+ {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"},
+ {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"},
+ {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"},
+ {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"},
+ {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"},
+ {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"},
+ {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"},
+ {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"},
+ {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"},
+ {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"},
+ {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"},
+ {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"},
+ {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"},
+ {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"},
+ {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"},
+ {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"},
+ {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"},
+ {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"},
+ {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"},
+ {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"},
+ {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"},
+ {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"},
+ {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"},
+ {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"},
+]
+
+[[package]]
+name = "wsproto"
+version = "1.2.0"
+description = "WebSockets state-machine based protocol implementation"
+optional = false
+python-versions = ">=3.7.0"
+files = [
+ {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"},
+ {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"},
+]
+
+[package.dependencies]
+h11 = ">=0.9.0,<1"
+
+[[package]]
+name = "zipp"
+version = "3.17.0"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"},
+ {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"},
+]
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
+testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
+
+[extras]
+ws = ["websockets"]
+
+[metadata]
+lock-version = "2.0"
+python-versions = ">=3.8"
+content-hash = "2ec8c8dbebbb73092a1cfb6c37a4550714a46830aa0e3267ea3c1010a72a49e7"
diff --git a/pygls/__init__.py b/pygls/__init__.py
new file mode 100644
index 0000000..147cd9e
--- /dev/null
+++ b/pygls/__init__.py
@@ -0,0 +1,25 @@
+############################################################################
+# Original work Copyright 2018 Palantir Technologies, Inc. #
+# Original work licensed under the MIT License. #
+# See ThirdPartyNotices.txt in the project root for license information. #
+# All modifications Copyright (c) Open Law Library. All rights reserved. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import os
+import sys
+
+IS_WIN = os.name == "nt"
+IS_PYODIDE = "pyodide" in sys.modules
+
+pygls = "pygls"
diff --git a/pygls/capabilities.py b/pygls/capabilities.py
new file mode 100644
index 0000000..9db4744
--- /dev/null
+++ b/pygls/capabilities.py
@@ -0,0 +1,460 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from functools import reduce
+from typing import Any, Dict, List, Optional, Set, Union, TypeVar
+import logging
+
+from lsprotocol import types
+
+
+logger = logging.getLogger(__name__)
+T = TypeVar("T")
+
+
+def get_capability(
+ client_capabilities: types.ClientCapabilities, field: str, default: Any = None
+) -> Any:
+ """Check if ClientCapabilities has some nested value without raising
+ AttributeError.
+ e.g. get_capability('text_document.synchronization.will_save')
+ """
+ try:
+ value = reduce(getattr, field.split("."), client_capabilities)
+ except AttributeError:
+ return default
+
+ # If we reach the desired leaf value but it's None, return the default.
+ return default if value is None else value
+
+
+class ServerCapabilitiesBuilder:
+ """Create `ServerCapabilities` instance depending on builtin and user registered
+ features.
+ """
+
+ def __init__(
+ self,
+ client_capabilities: types.ClientCapabilities,
+ features: Set[str],
+ feature_options: Dict[str, Any],
+ commands: List[str],
+ text_document_sync_kind: types.TextDocumentSyncKind,
+ notebook_document_sync: Optional[types.NotebookDocumentSyncOptions] = None,
+ ):
+ self.client_capabilities = client_capabilities
+ self.features = features
+ self.feature_options = feature_options
+ self.commands = commands
+ self.text_document_sync_kind = text_document_sync_kind
+ self.notebook_document_sync = notebook_document_sync
+
+ self.server_cap = types.ServerCapabilities()
+
+ def _provider_options(self, feature: str, default: T) -> Optional[Union[T, Any]]:
+ if feature in self.features:
+ return self.feature_options.get(feature, default)
+ return None
+
+ def _with_text_document_sync(self):
+ open_close = (
+ types.TEXT_DOCUMENT_DID_OPEN in self.features
+ or types.TEXT_DOCUMENT_DID_CLOSE in self.features
+ )
+ will_save = (
+ get_capability(
+ self.client_capabilities, "text_document.synchronization.will_save"
+ )
+ and types.TEXT_DOCUMENT_WILL_SAVE in self.features
+ )
+ will_save_wait_until = (
+ get_capability(
+ self.client_capabilities,
+ "text_document.synchronization.will_save_wait_until",
+ )
+ and types.TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL in self.features
+ )
+ if types.TEXT_DOCUMENT_DID_SAVE in self.features:
+ save = self.feature_options.get(types.TEXT_DOCUMENT_DID_SAVE, True)
+ else:
+ save = False
+
+ self.server_cap.text_document_sync = types.TextDocumentSyncOptions(
+ open_close=open_close,
+ change=self.text_document_sync_kind,
+ will_save=will_save,
+ will_save_wait_until=will_save_wait_until,
+ save=save,
+ )
+
+ return self
+
+ def _with_notebook_document_sync(self):
+ if self.client_capabilities.notebook_document is None:
+ return self
+
+ self.server_cap.notebook_document_sync = self.notebook_document_sync
+ return self
+
+ def _with_completion(self):
+ value = self._provider_options(
+ types.TEXT_DOCUMENT_COMPLETION, default=types.CompletionOptions()
+ )
+ if value is not None:
+ self.server_cap.completion_provider = value
+ return self
+
+ def _with_hover(self):
+ value = self._provider_options(types.TEXT_DOCUMENT_HOVER, default=True)
+ if value is not None:
+ self.server_cap.hover_provider = value
+ return self
+
+ def _with_signature_help(self):
+ value = self._provider_options(
+ types.TEXT_DOCUMENT_SIGNATURE_HELP, default=types.SignatureHelpOptions()
+ )
+ if value is not None:
+ self.server_cap.signature_help_provider = value
+ return self
+
+ def _with_declaration(self):
+ value = self._provider_options(types.TEXT_DOCUMENT_DECLARATION, default=True)
+ if value is not None:
+ self.server_cap.declaration_provider = value
+ return self
+
+ def _with_definition(self):
+ value = self._provider_options(types.TEXT_DOCUMENT_DEFINITION, default=True)
+ if value is not None:
+ self.server_cap.definition_provider = value
+ return self
+
+ def _with_type_definition(self):
+ value = self._provider_options(
+ types.TEXT_DOCUMENT_TYPE_DEFINITION, default=types.TypeDefinitionOptions()
+ )
+ if value is not None:
+ self.server_cap.type_definition_provider = value
+ return self
+
+ def _with_inlay_hints(self):
+ value = self._provider_options(
+ types.TEXT_DOCUMENT_INLAY_HINT, default=types.InlayHintOptions()
+ )
+ if value is not None:
+ value.resolve_provider = types.INLAY_HINT_RESOLVE in self.features
+ self.server_cap.inlay_hint_provider = value
+ return self
+
+ def _with_implementation(self):
+ value = self._provider_options(
+ types.TEXT_DOCUMENT_IMPLEMENTATION, default=types.ImplementationOptions()
+ )
+ if value is not None:
+ self.server_cap.implementation_provider = value
+ return self
+
+ def _with_references(self):
+ value = self._provider_options(types.TEXT_DOCUMENT_REFERENCES, default=True)
+ if value is not None:
+ self.server_cap.references_provider = value
+ return self
+
+ def _with_document_highlight(self):
+ value = self._provider_options(
+ types.TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT, default=True
+ )
+ if value is not None:
+ self.server_cap.document_highlight_provider = value
+ return self
+
+ def _with_document_symbol(self):
+ value = self._provider_options(
+ types.TEXT_DOCUMENT_DOCUMENT_SYMBOL, default=True
+ )
+ if value is not None:
+ self.server_cap.document_symbol_provider = value
+ return self
+
+ def _with_code_action(self):
+ value = self._provider_options(types.TEXT_DOCUMENT_CODE_ACTION, default=True)
+ if value is not None:
+ self.server_cap.code_action_provider = value
+ return self
+
+ def _with_code_lens(self):
+ value = self._provider_options(
+ types.TEXT_DOCUMENT_CODE_LENS, default=types.CodeLensOptions()
+ )
+ if value is not None:
+ self.server_cap.code_lens_provider = value
+ return self
+
+ def _with_document_link(self):
+ value = self._provider_options(
+ types.TEXT_DOCUMENT_DOCUMENT_LINK, default=types.DocumentLinkOptions()
+ )
+ if value is not None:
+ self.server_cap.document_link_provider = value
+ return self
+
+ def _with_color(self):
+ value = self._provider_options(types.TEXT_DOCUMENT_DOCUMENT_COLOR, default=True)
+ if value is not None:
+ self.server_cap.color_provider = value
+ return self
+
+ def _with_document_formatting(self):
+ value = self._provider_options(types.TEXT_DOCUMENT_FORMATTING, default=True)
+ if value is not None:
+ self.server_cap.document_formatting_provider = value
+ return self
+
+ def _with_document_range_formatting(self):
+ value = self._provider_options(
+ types.TEXT_DOCUMENT_RANGE_FORMATTING, default=True
+ )
+ if value is not None:
+ self.server_cap.document_range_formatting_provider = value
+ return self
+
+ def _with_document_on_type_formatting(self):
+ value = self._provider_options(
+ types.TEXT_DOCUMENT_ON_TYPE_FORMATTING, default=None
+ )
+ if value is not None:
+ self.server_cap.document_on_type_formatting_provider = value
+ return self
+
+ def _with_rename(self):
+ value = self._provider_options(types.TEXT_DOCUMENT_RENAME, default=True)
+ if value is not None:
+ self.server_cap.rename_provider = value
+ return self
+
+ def _with_folding_range(self):
+ value = self._provider_options(types.TEXT_DOCUMENT_FOLDING_RANGE, default=True)
+ if value is not None:
+ self.server_cap.folding_range_provider = value
+ return self
+
+ def _with_execute_command(self):
+ self.server_cap.execute_command_provider = types.ExecuteCommandOptions(
+ commands=self.commands
+ )
+ return self
+
+ def _with_selection_range(self):
+ value = self._provider_options(
+ types.TEXT_DOCUMENT_SELECTION_RANGE, default=True
+ )
+ if value is not None:
+ self.server_cap.selection_range_provider = value
+ return self
+
+ def _with_call_hierarchy(self):
+ value = self._provider_options(
+ types.TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY, default=True
+ )
+ if value is not None:
+ self.server_cap.call_hierarchy_provider = value
+ return self
+
+ def _with_type_hierarchy(self):
+ value = self._provider_options(
+ types.TEXT_DOCUMENT_PREPARE_TYPE_HIERARCHY, default=True
+ )
+ if value is not None:
+ self.server_cap.type_hierarchy_provider = value
+ return self
+
+ def _with_semantic_tokens(self):
+ providers = [
+ types.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL,
+ types.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA,
+ types.TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE,
+ ]
+
+ value = None
+ for provider in providers:
+ value = self._provider_options(provider, default=None)
+ if value is not None:
+ break
+
+ if value is None:
+ return self
+
+ if isinstance(value, types.SemanticTokensRegistrationOptions):
+ self.server_cap.semantic_tokens_provider = value
+ return self
+
+ full_support: Union[bool, types.SemanticTokensOptionsFullType1] = (
+ types.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL in self.features
+ )
+
+ if types.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA in self.features:
+ full_support = types.SemanticTokensOptionsFullType1(delta=True)
+
+ options = types.SemanticTokensOptions(
+ legend=value,
+ full=full_support or None,
+ range=types.TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE in self.features or None,
+ )
+
+ if options.full or options.range:
+ self.server_cap.semantic_tokens_provider = options
+
+ return self
+
+ def _with_linked_editing_range(self):
+ value = self._provider_options(
+ types.TEXT_DOCUMENT_LINKED_EDITING_RANGE, default=True
+ )
+ if value is not None:
+ self.server_cap.linked_editing_range_provider = value
+ return self
+
+ def _with_moniker(self):
+ value = self._provider_options(types.TEXT_DOCUMENT_MONIKER, default=True)
+ if value is not None:
+ self.server_cap.moniker_provider = value
+ return self
+
+ def _with_workspace_symbol(self):
+ value = self._provider_options(
+ types.WORKSPACE_SYMBOL, default=types.WorkspaceSymbolOptions()
+ )
+ if value is not None:
+ value.resolve_provider = types.WORKSPACE_SYMBOL_RESOLVE in self.features
+ self.server_cap.workspace_symbol_provider = value
+ return self
+
+ def _with_workspace_capabilities(self):
+ # File operations
+ file_operations = types.FileOperationOptions()
+ operations = [
+ (types.WORKSPACE_WILL_CREATE_FILES, "will_create"),
+ (types.WORKSPACE_DID_CREATE_FILES, "did_create"),
+ (types.WORKSPACE_WILL_DELETE_FILES, "will_delete"),
+ (types.WORKSPACE_DID_DELETE_FILES, "did_delete"),
+ (types.WORKSPACE_WILL_RENAME_FILES, "will_rename"),
+ (types.WORKSPACE_DID_RENAME_FILES, "did_rename"),
+ ]
+
+ for method_name, capability_name in operations:
+ client_supports_method = get_capability(
+ self.client_capabilities, f"workspace.file_operations.{capability_name}"
+ )
+
+ if client_supports_method:
+ value = self._provider_options(method_name, default=None)
+ setattr(file_operations, capability_name, value)
+
+ self.server_cap.workspace = types.ServerCapabilitiesWorkspaceType(
+ workspace_folders=types.WorkspaceFoldersServerCapabilities(
+ supported=True,
+ change_notifications=True,
+ ),
+ file_operations=file_operations,
+ )
+ return self
+
+ def _with_diagnostic_provider(self):
+ value = self._provider_options(
+ types.TEXT_DOCUMENT_DIAGNOSTIC,
+ default=types.DiagnosticOptions(
+ inter_file_dependencies=False, workspace_diagnostics=False
+ ),
+ )
+ if value is not None:
+ value.workspace_diagnostics = types.WORKSPACE_DIAGNOSTIC in self.features
+ self.server_cap.diagnostic_provider = value
+ return self
+
+ def _with_inline_value_provider(self):
+ value = self._provider_options(types.TEXT_DOCUMENT_INLINE_VALUE, default=True)
+ if value is not None:
+ self.server_cap.inline_value_provider = value
+ return self
+
+ def _with_position_encodings(self):
+ self.server_cap.position_encoding = types.PositionEncodingKind.Utf16
+
+ general = self.client_capabilities.general
+ if general is None:
+ return self
+
+ encodings = general.position_encodings
+ if encodings is None:
+ return self
+
+ if types.PositionEncodingKind.Utf16 in encodings:
+ return self
+
+ if types.PositionEncodingKind.Utf32 in encodings:
+ self.server_cap.position_encoding = types.PositionEncodingKind.Utf32
+ return self
+
+ if types.PositionEncodingKind.Utf8 in encodings:
+ self.server_cap.position_encoding = types.PositionEncodingKind.Utf8
+ return self
+
+ logger.warning(f"Unknown `PositionEncoding`s: {encodings}")
+
+ return self
+
+ def _build(self):
+ return self.server_cap
+
+ def build(self):
+ return (
+ self._with_text_document_sync()
+ ._with_notebook_document_sync()
+ ._with_completion()
+ ._with_hover()
+ ._with_signature_help()
+ ._with_declaration()
+ ._with_definition()
+ ._with_type_definition()
+ ._with_inlay_hints()
+ ._with_implementation()
+ ._with_references()
+ ._with_document_highlight()
+ ._with_document_symbol()
+ ._with_code_action()
+ ._with_code_lens()
+ ._with_document_link()
+ ._with_color()
+ ._with_document_formatting()
+ ._with_document_range_formatting()
+ ._with_document_on_type_formatting()
+ ._with_rename()
+ ._with_folding_range()
+ ._with_execute_command()
+ ._with_selection_range()
+ ._with_call_hierarchy()
+ ._with_type_hierarchy()
+ ._with_semantic_tokens()
+ ._with_linked_editing_range()
+ ._with_moniker()
+ ._with_workspace_symbol()
+ ._with_workspace_capabilities()
+ ._with_diagnostic_provider()
+ ._with_inline_value_provider()
+ ._with_position_encodings()
+ ._build()
+ )
diff --git a/pygls/client.py b/pygls/client.py
new file mode 100644
index 0000000..577f05e
--- /dev/null
+++ b/pygls/client.py
@@ -0,0 +1,176 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import asyncio
+import logging
+import re
+from threading import Event
+from typing import Any
+from typing import Callable
+from typing import List
+from typing import Optional
+from typing import Type
+from typing import Union
+
+from cattrs import Converter
+
+from pygls.exceptions import PyglsError, JsonRpcException
+from pygls.protocol import JsonRPCProtocol, default_converter
+
+
+logger = logging.getLogger(__name__)
+
+
+async def aio_readline(stop_event, reader, message_handler):
+ CONTENT_LENGTH_PATTERN = re.compile(rb"^Content-Length: (\d+)\r\n$")
+
+ # Initialize message buffer
+ message = []
+ content_length = 0
+
+ while not stop_event.is_set():
+ # Read a header line
+ header = await reader.readline()
+ if not header:
+ break
+ message.append(header)
+
+ # Extract content length if possible
+ if not content_length:
+ match = CONTENT_LENGTH_PATTERN.fullmatch(header)
+ if match:
+ content_length = int(match.group(1))
+ logger.debug("Content length: %s", content_length)
+
+ # Check if all headers have been read (as indicated by an empty line \r\n)
+ if content_length and not header.strip():
+ # Read body
+ body = await reader.readexactly(content_length)
+ if not body:
+ break
+ message.append(body)
+
+ # Pass message to protocol
+ message_handler(b"".join(message))
+
+ # Reset the buffer
+ message = []
+ content_length = 0
+
+
+class JsonRPCClient:
+ """Base JSON-RPC client."""
+
+ def __init__(
+ self,
+ protocol_cls: Type[JsonRPCProtocol] = JsonRPCProtocol,
+ converter_factory: Callable[[], Converter] = default_converter,
+ ):
+ # Strictly speaking `JsonRPCProtocol` wants a `LanguageServer`, not a
+ # `JsonRPCClient`. However there similar enough for our purposes, which is
+ # that this client will mostly be used in testing contexts.
+ self.protocol = protocol_cls(self, converter_factory()) # type: ignore
+
+ self._server: Optional[asyncio.subprocess.Process] = None
+ self._stop_event = Event()
+ self._async_tasks: List[asyncio.Task] = []
+
+ @property
+ def stopped(self) -> bool:
+ """Return ``True`` if the client has been stopped."""
+ return self._stop_event.is_set()
+
+ def feature(
+ self,
+ feature_name: str,
+ options: Optional[Any] = None,
+ ):
+ """Decorator used to register LSP features.
+
+ Example
+ -------
+ ::
+
+ import logging
+ from pygls.client import JsonRPCClient
+
+ ls = JsonRPCClient()
+
+ @ls.feature('window/logMessage')
+ def completions(ls, params):
+ logging.info("%s", params.message)
+ """
+ return self.protocol.fm.feature(feature_name, options)
+
+ async def start_io(self, cmd: str, *args, **kwargs):
+ """Start the given server and communicate with it over stdio."""
+
+ logger.debug("Starting server process: %s", " ".join([cmd, *args]))
+ server = await asyncio.create_subprocess_exec(
+ cmd,
+ *args,
+ stdout=asyncio.subprocess.PIPE,
+ stdin=asyncio.subprocess.PIPE,
+ stderr=asyncio.subprocess.PIPE,
+ **kwargs,
+ )
+
+ self.protocol.connection_made(server.stdin) # type: ignore
+ connection = asyncio.create_task(
+ aio_readline(self._stop_event, server.stdout, self.protocol.data_received)
+ )
+ notify_exit = asyncio.create_task(self._server_exit())
+
+ self._server = server
+ self._async_tasks.extend([connection, notify_exit])
+
+ async def _server_exit(self):
+ if self._server is not None:
+ await self._server.wait()
+ logger.debug(
+ "Server process %s exited with return code: %s",
+ self._server.pid,
+ self._server.returncode,
+ )
+ await self.server_exit(self._server)
+ self._stop_event.set()
+
+ async def server_exit(self, server: asyncio.subprocess.Process):
+ """Called when the server process exits."""
+
+ def _report_server_error(
+ self, error: Exception, source: Union[PyglsError, JsonRpcException]
+ ):
+ try:
+ self.report_server_error(error, source)
+ except Exception:
+ logger.error("Unable to report error", exc_info=True)
+
+ def report_server_error(
+ self, error: Exception, source: Union[PyglsError, JsonRpcException]
+ ):
+ """Called when the server does something unexpected e.g. respond with malformed
+ JSON."""
+
+ async def stop(self):
+ self._stop_event.set()
+
+ if self._server is not None and self._server.returncode is None:
+ logger.debug("Terminating server process: %s", self._server.pid)
+ self._server.terminate()
+
+ if len(self._async_tasks) > 0:
+ await asyncio.gather(*self._async_tasks)
diff --git a/pygls/constants.py b/pygls/constants.py
new file mode 100644
index 0000000..ec2fa09
--- /dev/null
+++ b/pygls/constants.py
@@ -0,0 +1,26 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+# Dynamically assigned attributes
+ATTR_EXECUTE_IN_THREAD = "execute_in_thread"
+ATTR_COMMAND_TYPE = "command"
+ATTR_FEATURE_TYPE = "feature"
+ATTR_REGISTERED_NAME = "reg_name"
+ATTR_REGISTERED_TYPE = "reg_type"
+
+# Parameters
+PARAM_LS = "ls"
diff --git a/pygls/exceptions.py b/pygls/exceptions.py
new file mode 100644
index 0000000..5faf269
--- /dev/null
+++ b/pygls/exceptions.py
@@ -0,0 +1,215 @@
+############################################################################
+# Original work Copyright 2018 Palantir Technologies, Inc. #
+# Original work licensed under the MIT License. #
+# See ThirdPartyNotices.txt in the project root for license information. #
+# All modifications Copyright (c) Open Law Library. All rights reserved. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import traceback
+from typing import Set
+from typing import Type
+from lsprotocol.types import ResponseError
+
+
+class JsonRpcException(Exception):
+ """A class used as a base class for json rpc exceptions."""
+
+ def __init__(self, message=None, code=None, data=None):
+ message = message or getattr(self.__class__, "MESSAGE")
+ super().__init__(message)
+ self.message = message
+ self.code = code or getattr(self.__class__, "CODE")
+ self.data = data
+
+ def __eq__(self, other):
+ return (
+ isinstance(other, self.__class__)
+ and self.code == other.code
+ and self.message == other.message
+ )
+
+ def __hash__(self):
+ return hash((self.code, self.message))
+
+ @staticmethod
+ def from_error(error):
+ for exc_class in _EXCEPTIONS:
+ if exc_class.supports_code(error.code):
+ return exc_class(
+ code=error.code, message=error.message, data=error.data
+ )
+
+ return JsonRpcException(code=error.code, message=error.message, data=error.data)
+
+ @classmethod
+ def supports_code(cls, code):
+ # Defaults to UnknownErrorCode
+ return getattr(cls, "CODE", -32001) == code
+
+ def to_response_error(self) -> ResponseError:
+ return ResponseError(code=self.code, message=self.message, data=self.data)
+
+
+class JsonRpcInternalError(JsonRpcException):
+ CODE = -32603
+ MESSAGE = "Internal Error"
+
+ @classmethod
+ def of(cls, exc_info):
+ exc_type, exc_value, exc_tb = exc_info
+ return cls(
+ message="".join(
+ traceback.format_exception_only(exc_type, exc_value)
+ ).strip(),
+ data={"traceback": traceback.format_tb(exc_tb)},
+ )
+
+
+class JsonRpcInvalidParams(JsonRpcException):
+ CODE = -32602
+ MESSAGE = "Invalid Params"
+
+
+class JsonRpcInvalidRequest(JsonRpcException):
+ CODE = -32600
+ MESSAGE = "Invalid Request"
+
+
+class JsonRpcMethodNotFound(JsonRpcException):
+ CODE = -32601
+ MESSAGE = "Method Not Found"
+
+ @classmethod
+ def of(cls, method):
+ return cls(message=cls.MESSAGE + ": " + method)
+
+
+class JsonRpcParseError(JsonRpcException):
+ CODE = -32700
+ MESSAGE = "Parse Error"
+
+
+class JsonRpcRequestCancelled(JsonRpcException):
+ CODE = -32800
+ MESSAGE = "Request Cancelled"
+
+
+class JsonRpcContentModified(JsonRpcException):
+ CODE = -32801
+ MESSAGE = "Content Modified"
+
+
+class JsonRpcServerNotInitialized(JsonRpcException):
+ CODE = -32002
+ MESSAGE = "ServerNotInitialized"
+
+
+class JsonRpcUnknownErrorCode(JsonRpcException):
+ CODE = -32001
+ MESSAGE = "UnknownErrorCode"
+
+
+class JsonRpcReservedErrorRangeStart(JsonRpcException):
+ CODE = -32099
+ MESSAGE = "jsonrpcReservedErrorRangeStart"
+
+
+class JsonRpcReservedErrorRangeEnd(JsonRpcException):
+ CODE = -32000
+ MESSAGE = "jsonrpcReservedErrorRangeEnd"
+
+
+class LspReservedErrorRangeStart(JsonRpcException):
+ CODE = -32899
+ MESSAGE = "lspReservedErrorRangeStart"
+
+
+class LspReservedErrorRangeEnd(JsonRpcException):
+ CODE = -32800
+ MESSAGE = "lspReservedErrorRangeEnd"
+
+
+class JsonRpcServerError(JsonRpcException):
+ def __init__(self, message, code, data=None):
+ if not _is_server_error_code(code):
+ raise ValueError("Error code should be in range -32099 - -32000")
+ super().__init__(message=message, code=code, data=data)
+
+ @classmethod
+ def supports_code(cls, code):
+ return _is_server_error_code(code)
+
+
+def _is_server_error_code(code):
+ return -32099 <= code <= -32000
+
+
+_EXCEPTIONS: Set[Type[JsonRpcException]] = {
+ JsonRpcInternalError,
+ JsonRpcInvalidParams,
+ JsonRpcInvalidRequest,
+ JsonRpcMethodNotFound,
+ JsonRpcParseError,
+ JsonRpcRequestCancelled,
+ JsonRpcServerError,
+}
+
+
+class PyglsError(Exception):
+ pass
+
+
+class CommandAlreadyRegisteredError(PyglsError):
+ def __init__(self, command_name):
+ self.command_name = command_name
+
+ def __repr__(self):
+ return f'Command "{self.command_name}" is already registered.'
+
+
+class FeatureAlreadyRegisteredError(PyglsError):
+ def __init__(self, feature_name):
+ self.feature_name = feature_name
+
+ def __repr__(self):
+ return f'Feature "{self.feature_name}" is already registered.'
+
+
+class FeatureRequestError(PyglsError):
+ pass
+
+
+class FeatureNotificationError(PyglsError):
+ pass
+
+
+class MethodTypeNotRegisteredError(PyglsError):
+ def __init__(self, name):
+ self.name = name
+
+ def __repr__(self):
+ return f'"{self.name}" is not added to `pygls.lsp.LSP_METHODS_MAP`.'
+
+
+class ThreadDecoratorError(PyglsError):
+ pass
+
+
+class ValidationError(PyglsError):
+ def __init__(self, errors=None):
+ self.errors = errors or []
+
+ def __repr__(self):
+ opt_errs = "\n-".join([e for e in self.errors])
+ return f"Missing options: {opt_errs}"
diff --git a/pygls/feature_manager.py b/pygls/feature_manager.py
new file mode 100644
index 0000000..d00283a
--- /dev/null
+++ b/pygls/feature_manager.py
@@ -0,0 +1,244 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import asyncio
+import functools
+import inspect
+import itertools
+import logging
+from typing import Any, Callable, Dict, Optional, get_type_hints
+
+from pygls.constants import (
+ ATTR_COMMAND_TYPE,
+ ATTR_EXECUTE_IN_THREAD,
+ ATTR_FEATURE_TYPE,
+ ATTR_REGISTERED_NAME,
+ ATTR_REGISTERED_TYPE,
+ PARAM_LS,
+)
+from pygls.exceptions import (
+ CommandAlreadyRegisteredError,
+ FeatureAlreadyRegisteredError,
+ ThreadDecoratorError,
+ ValidationError,
+)
+from pygls.lsp import get_method_options_type, is_instance
+
+logger = logging.getLogger(__name__)
+
+
+def assign_help_attrs(f, reg_name, reg_type):
+ setattr(f, ATTR_REGISTERED_NAME, reg_name)
+ setattr(f, ATTR_REGISTERED_TYPE, reg_type)
+
+
+def assign_thread_attr(f):
+ setattr(f, ATTR_EXECUTE_IN_THREAD, True)
+
+
+def get_help_attrs(f):
+ return getattr(f, ATTR_REGISTERED_NAME, None), getattr(
+ f, ATTR_REGISTERED_TYPE, None
+ )
+
+
+def has_ls_param_or_annotation(f, annotation):
+ """Returns true if callable has first parameter named `ls` or type of
+ annotation"""
+ try:
+ sig = inspect.signature(f)
+ first_p = next(itertools.islice(sig.parameters.values(), 0, 1))
+ return first_p.name == PARAM_LS or get_type_hints(f)[first_p.name] == annotation
+ except Exception:
+ return False
+
+
+def is_thread_function(f):
+ return getattr(f, ATTR_EXECUTE_IN_THREAD, False)
+
+
+def wrap_with_server(f, server):
+ """Returns a new callable/coroutine with server as first argument."""
+ if not has_ls_param_or_annotation(f, type(server)):
+ return f
+
+ if asyncio.iscoroutinefunction(f):
+
+ async def wrapped(*args, **kwargs):
+ return await f(server, *args, **kwargs)
+
+ else:
+ wrapped = functools.partial(f, server)
+ if is_thread_function(f):
+ assign_thread_attr(wrapped)
+
+ return wrapped
+
+
+class FeatureManager:
+ """A class for managing server features.
+
+ Attributes:
+ _builtin_features(dict): Predefined set of lsp methods
+ _feature_options(dict): Registered feature's options
+ _features(dict): Registered features
+ _commands(dict): Registered commands
+ server(LanguageServer): Reference to the language server
+ If passed, server will be passed to registered
+ features/commands with first parameter:
+ 1. ls - parameter naming convention
+ 2. name: LanguageServer - add typings
+ """
+
+ def __init__(self, server=None, converter=None):
+ self._builtin_features = {}
+ self._feature_options = {}
+ self._features = {}
+ self._commands = {}
+ self.server = server
+ self.converter = converter
+
+ def add_builtin_feature(self, feature_name: str, func: Callable) -> None:
+ """Registers builtin (predefined) feature."""
+ self._builtin_features[feature_name] = func
+ logger.info("Registered builtin feature %s", feature_name)
+
+ @property
+ def builtin_features(self) -> Dict:
+ """Returns server builtin features."""
+ return self._builtin_features
+
+ def command(self, command_name: str) -> Callable:
+ """Decorator used to register custom commands.
+
+ Example:
+ @ls.command('myCustomCommand')
+ """
+
+ def decorator(f):
+ # Validate
+ if command_name is None or command_name.strip() == "":
+ logger.error("Missing command name.")
+ raise ValidationError("Command name is required.")
+
+ # Check if not already registered
+ if command_name in self._commands:
+ logger.error('Command "%s" is already registered.', command_name)
+ raise CommandAlreadyRegisteredError(command_name)
+
+ assign_help_attrs(f, command_name, ATTR_COMMAND_TYPE)
+
+ wrapped = wrap_with_server(f, self.server)
+ # Assign help attributes for thread decorator
+ assign_help_attrs(wrapped, command_name, ATTR_COMMAND_TYPE)
+
+ self._commands[command_name] = wrapped
+
+ logger.info('Command "%s" is successfully registered.', command_name)
+
+ return f
+
+ return decorator
+
+ @property
+ def commands(self) -> Dict:
+ """Returns registered custom commands."""
+ return self._commands
+
+ def feature(
+ self,
+ feature_name: str,
+ options: Optional[Any] = None,
+ ) -> Callable:
+ """Decorator used to register LSP features.
+
+ Example:
+ @ls.feature('textDocument/completion', CompletionItems(trigger_characters=['.']))
+ """
+
+ def decorator(f):
+ # Validate
+ if feature_name is None or feature_name.strip() == "":
+ logger.error("Missing feature name.")
+ raise ValidationError("Feature name is required.")
+
+ # Add feature if not exists
+ if feature_name in self._features:
+ logger.error('Feature "%s" is already registered.', feature_name)
+ raise FeatureAlreadyRegisteredError(feature_name)
+
+ assign_help_attrs(f, feature_name, ATTR_FEATURE_TYPE)
+
+ wrapped = wrap_with_server(f, self.server)
+ # Assign help attributes for thread decorator
+ assign_help_attrs(wrapped, feature_name, ATTR_FEATURE_TYPE)
+
+ self._features[feature_name] = wrapped
+
+ if options:
+ options_type = get_method_options_type(feature_name)
+ if options_type and not is_instance(
+ self.converter, options, options_type
+ ):
+ raise TypeError(
+ (
+ f'Options of method "{feature_name}"'
+ f" should be instance of type {options_type}"
+ )
+ )
+ self._feature_options[feature_name] = options
+
+ logger.info('Registered "%s" with options "%s"', feature_name, options)
+
+ return f
+
+ return decorator
+
+ @property
+ def feature_options(self) -> Dict:
+ """Returns feature options for registered features."""
+ return self._feature_options
+
+ @property
+ def features(self) -> Dict:
+ """Returns registered features"""
+ return self._features
+
+ def thread(self) -> Callable:
+ """Decorator that mark function to execute it in a thread."""
+
+ def decorator(f):
+ if asyncio.iscoroutinefunction(f):
+ raise ThreadDecoratorError(
+ f'Thread decorator cannot be used with async functions "{f.__name__}"'
+ )
+
+ # Allow any decorator order
+ try:
+ reg_name = getattr(f, ATTR_REGISTERED_NAME)
+ reg_type = getattr(f, ATTR_REGISTERED_TYPE)
+
+ if reg_type is ATTR_FEATURE_TYPE:
+ assign_thread_attr(self.features[reg_name])
+ elif reg_type is ATTR_COMMAND_TYPE:
+ assign_thread_attr(self.commands[reg_name])
+
+ except AttributeError:
+ assign_thread_attr(f)
+
+ return f
+
+ return decorator
diff --git a/pygls/lsp/__init__.py b/pygls/lsp/__init__.py
new file mode 100644
index 0000000..aa0725d
--- /dev/null
+++ b/pygls/lsp/__init__.py
@@ -0,0 +1,139 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import cattrs
+from typing import Any, Callable, List, Optional, Union
+
+from lsprotocol.types import (
+ ALL_TYPES_MAP,
+ METHOD_TO_TYPES,
+ TEXT_DOCUMENT_DID_SAVE,
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL,
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA,
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE,
+ WORKSPACE_DID_CREATE_FILES,
+ WORKSPACE_DID_DELETE_FILES,
+ WORKSPACE_DID_RENAME_FILES,
+ WORKSPACE_WILL_CREATE_FILES,
+ WORKSPACE_WILL_DELETE_FILES,
+ WORKSPACE_WILL_RENAME_FILES,
+ FileOperationRegistrationOptions,
+ SaveOptions,
+ SemanticTokensLegend,
+ SemanticTokensRegistrationOptions,
+ ShowDocumentResult,
+)
+
+from pygls.exceptions import MethodTypeNotRegisteredError
+
+ConfigCallbackType = Callable[[List[Any]], None]
+ShowDocumentCallbackType = Callable[[ShowDocumentResult], None]
+
+METHOD_TO_OPTIONS = {
+ TEXT_DOCUMENT_DID_SAVE: SaveOptions,
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL: Union[
+ SemanticTokensLegend, SemanticTokensRegistrationOptions
+ ],
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA: Union[
+ SemanticTokensLegend, SemanticTokensRegistrationOptions
+ ],
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE: Union[
+ SemanticTokensLegend, SemanticTokensRegistrationOptions
+ ],
+ WORKSPACE_DID_CREATE_FILES: FileOperationRegistrationOptions,
+ WORKSPACE_DID_DELETE_FILES: FileOperationRegistrationOptions,
+ WORKSPACE_DID_RENAME_FILES: FileOperationRegistrationOptions,
+ WORKSPACE_WILL_CREATE_FILES: FileOperationRegistrationOptions,
+ WORKSPACE_WILL_DELETE_FILES: FileOperationRegistrationOptions,
+ WORKSPACE_WILL_RENAME_FILES: FileOperationRegistrationOptions,
+}
+
+
+def get_method_registration_options_type(
+ method_name, lsp_methods_map=METHOD_TO_TYPES
+) -> Optional[Any]:
+ """The type corresponding with a method's options when dynamically registering
+ capability for it."""
+
+ try:
+ return lsp_methods_map[method_name][3]
+ except KeyError:
+ raise MethodTypeNotRegisteredError(method_name)
+
+
+def get_method_options_type(
+ method_name, lsp_options_map=METHOD_TO_OPTIONS, lsp_methods_map=METHOD_TO_TYPES
+) -> Optional[Any]:
+ """Return the type corresponding with a method's ``ServerCapabilities`` fields.
+
+ In the majority of cases this simply means returning the ``<MethodName>Options``
+ type, which we can easily derive from the method's
+ ``<MethodName>RegistrationOptions`` type.
+
+ However, where the options are more involved (such as semantic tokens) and
+ ``pygls`` does some extra work to help derive the options for the user the type
+ has to be provided via the ``lsp_options_map``
+
+ Arguments:
+ method_name:
+ The lsp method name to retrieve the options for
+
+ lsp_options_map:
+ The map used to override the default options type finding behavior
+
+ lsp_methods_map:
+ The standard map used to look up the various method types.
+ """
+
+ options_type = lsp_options_map.get(method_name, None)
+ if options_type is not None:
+ return options_type
+
+ registration_type = get_method_registration_options_type(
+ method_name, lsp_methods_map
+ )
+ if registration_type is None:
+ return None
+
+ type_name = registration_type.__name__.replace("Registration", "")
+ options_type = ALL_TYPES_MAP.get(type_name, None)
+
+ if options_type is None:
+ raise MethodTypeNotRegisteredError(method_name)
+
+ return options_type
+
+
+def get_method_params_type(method_name, lsp_methods_map=METHOD_TO_TYPES):
+ try:
+ return lsp_methods_map[method_name][2]
+ except KeyError:
+ raise MethodTypeNotRegisteredError(method_name)
+
+
+def get_method_return_type(method_name, lsp_methods_map=METHOD_TO_TYPES):
+ try:
+ return lsp_methods_map[method_name][1]
+ except KeyError:
+ raise MethodTypeNotRegisteredError(method_name)
+
+
+def is_instance(cv: cattrs.Converter, o, t):
+ try:
+ cv.unstructure(o, t)
+ return True
+ except TypeError:
+ return False
diff --git a/pygls/lsp/client.py b/pygls/lsp/client.py
new file mode 100644
index 0000000..c877fdb
--- /dev/null
+++ b/pygls/lsp/client.py
@@ -0,0 +1,1961 @@
+# GENERATED FROM scripts/gen-client.py -- DO NOT EDIT
+# flake8: noqa
+from concurrent.futures import Future
+from lsprotocol import types
+from pygls.client import JsonRPCClient
+from pygls.protocol import LanguageServerProtocol
+from pygls.protocol import default_converter
+from typing import Any
+from typing import Callable
+from typing import List
+from typing import Optional
+from typing import Union
+
+
+class BaseLanguageClient(JsonRPCClient):
+
+ def __init__(
+ self,
+ name: str,
+ version: str,
+ protocol_cls=LanguageServerProtocol,
+ converter_factory=default_converter,
+ **kwargs,
+ ):
+ self.name = name
+ self.version = version
+ super().__init__(protocol_cls, converter_factory, **kwargs)
+
+ def call_hierarchy_incoming_calls(
+ self,
+ params: types.CallHierarchyIncomingCallsParams,
+ callback: Optional[Callable[[Optional[List[types.CallHierarchyIncomingCall]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`callHierarchy/incomingCalls` request.
+
+ A request to resolve the incoming calls for a given `CallHierarchyItem`.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("callHierarchy/incomingCalls", params, callback)
+
+ async def call_hierarchy_incoming_calls_async(
+ self,
+ params: types.CallHierarchyIncomingCallsParams,
+ ) -> Optional[List[types.CallHierarchyIncomingCall]]:
+ """Make a :lsp:`callHierarchy/incomingCalls` request.
+
+ A request to resolve the incoming calls for a given `CallHierarchyItem`.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("callHierarchy/incomingCalls", params)
+
+ def call_hierarchy_outgoing_calls(
+ self,
+ params: types.CallHierarchyOutgoingCallsParams,
+ callback: Optional[Callable[[Optional[List[types.CallHierarchyOutgoingCall]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`callHierarchy/outgoingCalls` request.
+
+ A request to resolve the outgoing calls for a given `CallHierarchyItem`.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("callHierarchy/outgoingCalls", params, callback)
+
+ async def call_hierarchy_outgoing_calls_async(
+ self,
+ params: types.CallHierarchyOutgoingCallsParams,
+ ) -> Optional[List[types.CallHierarchyOutgoingCall]]:
+ """Make a :lsp:`callHierarchy/outgoingCalls` request.
+
+ A request to resolve the outgoing calls for a given `CallHierarchyItem`.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("callHierarchy/outgoingCalls", params)
+
+ def code_action_resolve(
+ self,
+ params: types.CodeAction,
+ callback: Optional[Callable[[types.CodeAction], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`codeAction/resolve` request.
+
+ Request to resolve additional information for a given code action.The request's
+ parameter is of type {@link CodeAction} the response
+ is of type {@link CodeAction} or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("codeAction/resolve", params, callback)
+
+ async def code_action_resolve_async(
+ self,
+ params: types.CodeAction,
+ ) -> types.CodeAction:
+ """Make a :lsp:`codeAction/resolve` request.
+
+ Request to resolve additional information for a given code action.The request's
+ parameter is of type {@link CodeAction} the response
+ is of type {@link CodeAction} or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("codeAction/resolve", params)
+
+ def code_lens_resolve(
+ self,
+ params: types.CodeLens,
+ callback: Optional[Callable[[types.CodeLens], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`codeLens/resolve` request.
+
+ A request to resolve a command for a given code lens.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("codeLens/resolve", params, callback)
+
+ async def code_lens_resolve_async(
+ self,
+ params: types.CodeLens,
+ ) -> types.CodeLens:
+ """Make a :lsp:`codeLens/resolve` request.
+
+ A request to resolve a command for a given code lens.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("codeLens/resolve", params)
+
+ def completion_item_resolve(
+ self,
+ params: types.CompletionItem,
+ callback: Optional[Callable[[types.CompletionItem], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`completionItem/resolve` request.
+
+ Request to resolve additional information for a given completion item.The request's
+ parameter is of type {@link CompletionItem} the response
+ is of type {@link CompletionItem} or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("completionItem/resolve", params, callback)
+
+ async def completion_item_resolve_async(
+ self,
+ params: types.CompletionItem,
+ ) -> types.CompletionItem:
+ """Make a :lsp:`completionItem/resolve` request.
+
+ Request to resolve additional information for a given completion item.The request's
+ parameter is of type {@link CompletionItem} the response
+ is of type {@link CompletionItem} or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("completionItem/resolve", params)
+
+ def document_link_resolve(
+ self,
+ params: types.DocumentLink,
+ callback: Optional[Callable[[types.DocumentLink], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`documentLink/resolve` request.
+
+ Request to resolve additional information for a given document link. The request's
+ parameter is of type {@link DocumentLink} the response
+ is of type {@link DocumentLink} or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("documentLink/resolve", params, callback)
+
+ async def document_link_resolve_async(
+ self,
+ params: types.DocumentLink,
+ ) -> types.DocumentLink:
+ """Make a :lsp:`documentLink/resolve` request.
+
+ Request to resolve additional information for a given document link. The request's
+ parameter is of type {@link DocumentLink} the response
+ is of type {@link DocumentLink} or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("documentLink/resolve", params)
+
+ def initialize(
+ self,
+ params: types.InitializeParams,
+ callback: Optional[Callable[[types.InitializeResult], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`initialize` request.
+
+ The initialize request is sent from the client to the server.
+ It is sent once as the request after starting up the server.
+ The requests parameter is of type {@link InitializeParams}
+ the response if of type {@link InitializeResult} of a Thenable that
+ resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("initialize", params, callback)
+
+ async def initialize_async(
+ self,
+ params: types.InitializeParams,
+ ) -> types.InitializeResult:
+ """Make a :lsp:`initialize` request.
+
+ The initialize request is sent from the client to the server.
+ It is sent once as the request after starting up the server.
+ The requests parameter is of type {@link InitializeParams}
+ the response if of type {@link InitializeResult} of a Thenable that
+ resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("initialize", params)
+
+ def inlay_hint_resolve(
+ self,
+ params: types.InlayHint,
+ callback: Optional[Callable[[types.InlayHint], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`inlayHint/resolve` request.
+
+ A request to resolve additional properties for an inlay hint.
+ The request's parameter is of type {@link InlayHint}, the response is
+ of type {@link InlayHint} or a Thenable that resolves to such.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("inlayHint/resolve", params, callback)
+
+ async def inlay_hint_resolve_async(
+ self,
+ params: types.InlayHint,
+ ) -> types.InlayHint:
+ """Make a :lsp:`inlayHint/resolve` request.
+
+ A request to resolve additional properties for an inlay hint.
+ The request's parameter is of type {@link InlayHint}, the response is
+ of type {@link InlayHint} or a Thenable that resolves to such.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("inlayHint/resolve", params)
+
+ def shutdown(
+ self,
+ params: None,
+ callback: Optional[Callable[[None], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`shutdown` request.
+
+ A shutdown request is sent from the client to the server.
+ It is sent once when the client decides to shutdown the
+ server. The only notification that is sent after a shutdown request
+ is the exit event.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("shutdown", params, callback)
+
+ async def shutdown_async(
+ self,
+ params: None,
+ ) -> None:
+ """Make a :lsp:`shutdown` request.
+
+ A shutdown request is sent from the client to the server.
+ It is sent once when the client decides to shutdown the
+ server. The only notification that is sent after a shutdown request
+ is the exit event.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("shutdown", params)
+
+ def text_document_code_action(
+ self,
+ params: types.CodeActionParams,
+ callback: Optional[Callable[[Optional[List[Union[types.Command, types.CodeAction]]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/codeAction` request.
+
+ A request to provide commands for the given text document and range.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/codeAction", params, callback)
+
+ async def text_document_code_action_async(
+ self,
+ params: types.CodeActionParams,
+ ) -> Optional[List[Union[types.Command, types.CodeAction]]]:
+ """Make a :lsp:`textDocument/codeAction` request.
+
+ A request to provide commands for the given text document and range.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/codeAction", params)
+
+ def text_document_code_lens(
+ self,
+ params: types.CodeLensParams,
+ callback: Optional[Callable[[Optional[List[types.CodeLens]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/codeLens` request.
+
+ A request to provide code lens for the given text document.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/codeLens", params, callback)
+
+ async def text_document_code_lens_async(
+ self,
+ params: types.CodeLensParams,
+ ) -> Optional[List[types.CodeLens]]:
+ """Make a :lsp:`textDocument/codeLens` request.
+
+ A request to provide code lens for the given text document.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/codeLens", params)
+
+ def text_document_color_presentation(
+ self,
+ params: types.ColorPresentationParams,
+ callback: Optional[Callable[[List[types.ColorPresentation]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/colorPresentation` request.
+
+ A request to list all presentation for a color. The request's
+ parameter is of type {@link ColorPresentationParams} the
+ response is of type {@link ColorInformation ColorInformation[]} or a Thenable
+ that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/colorPresentation", params, callback)
+
+ async def text_document_color_presentation_async(
+ self,
+ params: types.ColorPresentationParams,
+ ) -> List[types.ColorPresentation]:
+ """Make a :lsp:`textDocument/colorPresentation` request.
+
+ A request to list all presentation for a color. The request's
+ parameter is of type {@link ColorPresentationParams} the
+ response is of type {@link ColorInformation ColorInformation[]} or a Thenable
+ that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/colorPresentation", params)
+
+ def text_document_completion(
+ self,
+ params: types.CompletionParams,
+ callback: Optional[Callable[[Union[List[types.CompletionItem], types.CompletionList, None]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/completion` request.
+
+ Request to request completion at a given text document position. The request's
+ parameter is of type {@link TextDocumentPosition} the response
+ is of type {@link CompletionItem CompletionItem[]} or {@link CompletionList}
+ or a Thenable that resolves to such.
+
+ The request can delay the computation of the {@link CompletionItem.detail `detail`}
+ and {@link CompletionItem.documentation `documentation`} properties to the `completionItem/resolve`
+ request. However, properties that are needed for the initial sorting and filtering, like `sortText`,
+ `filterText`, `insertText`, and `textEdit`, must not be changed during resolve.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/completion", params, callback)
+
+ async def text_document_completion_async(
+ self,
+ params: types.CompletionParams,
+ ) -> Union[List[types.CompletionItem], types.CompletionList, None]:
+ """Make a :lsp:`textDocument/completion` request.
+
+ Request to request completion at a given text document position. The request's
+ parameter is of type {@link TextDocumentPosition} the response
+ is of type {@link CompletionItem CompletionItem[]} or {@link CompletionList}
+ or a Thenable that resolves to such.
+
+ The request can delay the computation of the {@link CompletionItem.detail `detail`}
+ and {@link CompletionItem.documentation `documentation`} properties to the `completionItem/resolve`
+ request. However, properties that are needed for the initial sorting and filtering, like `sortText`,
+ `filterText`, `insertText`, and `textEdit`, must not be changed during resolve.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/completion", params)
+
+ def text_document_declaration(
+ self,
+ params: types.DeclarationParams,
+ callback: Optional[Callable[[Union[types.Location, List[types.Location], List[types.LocationLink], None]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/declaration` request.
+
+ A request to resolve the type definition locations of a symbol at a given text
+ document position. The request's parameter is of type {@link TextDocumentPositionParams}
+ the response is of type {@link Declaration} or a typed array of {@link DeclarationLink}
+ or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/declaration", params, callback)
+
+ async def text_document_declaration_async(
+ self,
+ params: types.DeclarationParams,
+ ) -> Union[types.Location, List[types.Location], List[types.LocationLink], None]:
+ """Make a :lsp:`textDocument/declaration` request.
+
+ A request to resolve the type definition locations of a symbol at a given text
+ document position. The request's parameter is of type {@link TextDocumentPositionParams}
+ the response is of type {@link Declaration} or a typed array of {@link DeclarationLink}
+ or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/declaration", params)
+
+ def text_document_definition(
+ self,
+ params: types.DefinitionParams,
+ callback: Optional[Callable[[Union[types.Location, List[types.Location], List[types.LocationLink], None]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/definition` request.
+
+ A request to resolve the definition location of a symbol at a given text
+ document position. The request's parameter is of type {@link TextDocumentPosition}
+ the response is of either type {@link Definition} or a typed array of
+ {@link DefinitionLink} or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/definition", params, callback)
+
+ async def text_document_definition_async(
+ self,
+ params: types.DefinitionParams,
+ ) -> Union[types.Location, List[types.Location], List[types.LocationLink], None]:
+ """Make a :lsp:`textDocument/definition` request.
+
+ A request to resolve the definition location of a symbol at a given text
+ document position. The request's parameter is of type {@link TextDocumentPosition}
+ the response is of either type {@link Definition} or a typed array of
+ {@link DefinitionLink} or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/definition", params)
+
+ def text_document_diagnostic(
+ self,
+ params: types.DocumentDiagnosticParams,
+ callback: Optional[Callable[[Union[types.RelatedFullDocumentDiagnosticReport, types.RelatedUnchangedDocumentDiagnosticReport]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/diagnostic` request.
+
+ The document diagnostic request definition.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/diagnostic", params, callback)
+
+ async def text_document_diagnostic_async(
+ self,
+ params: types.DocumentDiagnosticParams,
+ ) -> Union[types.RelatedFullDocumentDiagnosticReport, types.RelatedUnchangedDocumentDiagnosticReport]:
+ """Make a :lsp:`textDocument/diagnostic` request.
+
+ The document diagnostic request definition.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/diagnostic", params)
+
+ def text_document_document_color(
+ self,
+ params: types.DocumentColorParams,
+ callback: Optional[Callable[[List[types.ColorInformation]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/documentColor` request.
+
+ A request to list all color symbols found in a given text document. The request's
+ parameter is of type {@link DocumentColorParams} the
+ response is of type {@link ColorInformation ColorInformation[]} or a Thenable
+ that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/documentColor", params, callback)
+
+ async def text_document_document_color_async(
+ self,
+ params: types.DocumentColorParams,
+ ) -> List[types.ColorInformation]:
+ """Make a :lsp:`textDocument/documentColor` request.
+
+ A request to list all color symbols found in a given text document. The request's
+ parameter is of type {@link DocumentColorParams} the
+ response is of type {@link ColorInformation ColorInformation[]} or a Thenable
+ that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/documentColor", params)
+
+ def text_document_document_highlight(
+ self,
+ params: types.DocumentHighlightParams,
+ callback: Optional[Callable[[Optional[List[types.DocumentHighlight]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/documentHighlight` request.
+
+ Request to resolve a {@link DocumentHighlight} for a given
+ text document position. The request's parameter is of type {@link TextDocumentPosition}
+ the request response is an array of type {@link DocumentHighlight}
+ or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/documentHighlight", params, callback)
+
+ async def text_document_document_highlight_async(
+ self,
+ params: types.DocumentHighlightParams,
+ ) -> Optional[List[types.DocumentHighlight]]:
+ """Make a :lsp:`textDocument/documentHighlight` request.
+
+ Request to resolve a {@link DocumentHighlight} for a given
+ text document position. The request's parameter is of type {@link TextDocumentPosition}
+ the request response is an array of type {@link DocumentHighlight}
+ or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/documentHighlight", params)
+
+ def text_document_document_link(
+ self,
+ params: types.DocumentLinkParams,
+ callback: Optional[Callable[[Optional[List[types.DocumentLink]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/documentLink` request.
+
+ A request to provide document links
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/documentLink", params, callback)
+
+ async def text_document_document_link_async(
+ self,
+ params: types.DocumentLinkParams,
+ ) -> Optional[List[types.DocumentLink]]:
+ """Make a :lsp:`textDocument/documentLink` request.
+
+ A request to provide document links
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/documentLink", params)
+
+ def text_document_document_symbol(
+ self,
+ params: types.DocumentSymbolParams,
+ callback: Optional[Callable[[Union[List[types.SymbolInformation], List[types.DocumentSymbol], None]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/documentSymbol` request.
+
+ A request to list all symbols found in a given text document. The request's
+ parameter is of type {@link TextDocumentIdentifier} the
+ response is of type {@link SymbolInformation SymbolInformation[]} or a Thenable
+ that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/documentSymbol", params, callback)
+
+ async def text_document_document_symbol_async(
+ self,
+ params: types.DocumentSymbolParams,
+ ) -> Union[List[types.SymbolInformation], List[types.DocumentSymbol], None]:
+ """Make a :lsp:`textDocument/documentSymbol` request.
+
+ A request to list all symbols found in a given text document. The request's
+ parameter is of type {@link TextDocumentIdentifier} the
+ response is of type {@link SymbolInformation SymbolInformation[]} or a Thenable
+ that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/documentSymbol", params)
+
+ def text_document_folding_range(
+ self,
+ params: types.FoldingRangeParams,
+ callback: Optional[Callable[[Optional[List[types.FoldingRange]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/foldingRange` request.
+
+ A request to provide folding ranges in a document. The request's
+ parameter is of type {@link FoldingRangeParams}, the
+ response is of type {@link FoldingRangeList} or a Thenable
+ that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/foldingRange", params, callback)
+
+ async def text_document_folding_range_async(
+ self,
+ params: types.FoldingRangeParams,
+ ) -> Optional[List[types.FoldingRange]]:
+ """Make a :lsp:`textDocument/foldingRange` request.
+
+ A request to provide folding ranges in a document. The request's
+ parameter is of type {@link FoldingRangeParams}, the
+ response is of type {@link FoldingRangeList} or a Thenable
+ that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/foldingRange", params)
+
+ def text_document_formatting(
+ self,
+ params: types.DocumentFormattingParams,
+ callback: Optional[Callable[[Optional[List[types.TextEdit]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/formatting` request.
+
+ A request to format a whole document.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/formatting", params, callback)
+
+ async def text_document_formatting_async(
+ self,
+ params: types.DocumentFormattingParams,
+ ) -> Optional[List[types.TextEdit]]:
+ """Make a :lsp:`textDocument/formatting` request.
+
+ A request to format a whole document.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/formatting", params)
+
+ def text_document_hover(
+ self,
+ params: types.HoverParams,
+ callback: Optional[Callable[[Optional[types.Hover]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/hover` request.
+
+ Request to request hover information at a given text document position. The request's
+ parameter is of type {@link TextDocumentPosition} the response is of
+ type {@link Hover} or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/hover", params, callback)
+
+ async def text_document_hover_async(
+ self,
+ params: types.HoverParams,
+ ) -> Optional[types.Hover]:
+ """Make a :lsp:`textDocument/hover` request.
+
+ Request to request hover information at a given text document position. The request's
+ parameter is of type {@link TextDocumentPosition} the response is of
+ type {@link Hover} or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/hover", params)
+
+ def text_document_implementation(
+ self,
+ params: types.ImplementationParams,
+ callback: Optional[Callable[[Union[types.Location, List[types.Location], List[types.LocationLink], None]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/implementation` request.
+
+ A request to resolve the implementation locations of a symbol at a given text
+ document position. The request's parameter is of type {@link TextDocumentPositionParams}
+ the response is of type {@link Definition} or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/implementation", params, callback)
+
+ async def text_document_implementation_async(
+ self,
+ params: types.ImplementationParams,
+ ) -> Union[types.Location, List[types.Location], List[types.LocationLink], None]:
+ """Make a :lsp:`textDocument/implementation` request.
+
+ A request to resolve the implementation locations of a symbol at a given text
+ document position. The request's parameter is of type {@link TextDocumentPositionParams}
+ the response is of type {@link Definition} or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/implementation", params)
+
+ def text_document_inlay_hint(
+ self,
+ params: types.InlayHintParams,
+ callback: Optional[Callable[[Optional[List[types.InlayHint]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/inlayHint` request.
+
+ A request to provide inlay hints in a document. The request's parameter is of
+ type {@link InlayHintsParams}, the response is of type
+ {@link InlayHint InlayHint[]} or a Thenable that resolves to such.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/inlayHint", params, callback)
+
+ async def text_document_inlay_hint_async(
+ self,
+ params: types.InlayHintParams,
+ ) -> Optional[List[types.InlayHint]]:
+ """Make a :lsp:`textDocument/inlayHint` request.
+
+ A request to provide inlay hints in a document. The request's parameter is of
+ type {@link InlayHintsParams}, the response is of type
+ {@link InlayHint InlayHint[]} or a Thenable that resolves to such.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/inlayHint", params)
+
+ def text_document_inline_completion(
+ self,
+ params: types.InlineCompletionParams,
+ callback: Optional[Callable[[Union[types.InlineCompletionList, List[types.InlineCompletionItem], None]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/inlineCompletion` request.
+
+ A request to provide inline completions in a document. The request's parameter is of
+ type {@link InlineCompletionParams}, the response is of type
+ {@link InlineCompletion InlineCompletion[]} or a Thenable that resolves to such.
+
+ @since 3.18.0
+ @proposed
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/inlineCompletion", params, callback)
+
+ async def text_document_inline_completion_async(
+ self,
+ params: types.InlineCompletionParams,
+ ) -> Union[types.InlineCompletionList, List[types.InlineCompletionItem], None]:
+ """Make a :lsp:`textDocument/inlineCompletion` request.
+
+ A request to provide inline completions in a document. The request's parameter is of
+ type {@link InlineCompletionParams}, the response is of type
+ {@link InlineCompletion InlineCompletion[]} or a Thenable that resolves to such.
+
+ @since 3.18.0
+ @proposed
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/inlineCompletion", params)
+
+ def text_document_inline_value(
+ self,
+ params: types.InlineValueParams,
+ callback: Optional[Callable[[Optional[List[Union[types.InlineValueText, types.InlineValueVariableLookup, types.InlineValueEvaluatableExpression]]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/inlineValue` request.
+
+ A request to provide inline values in a document. The request's parameter is of
+ type {@link InlineValueParams}, the response is of type
+ {@link InlineValue InlineValue[]} or a Thenable that resolves to such.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/inlineValue", params, callback)
+
+ async def text_document_inline_value_async(
+ self,
+ params: types.InlineValueParams,
+ ) -> Optional[List[Union[types.InlineValueText, types.InlineValueVariableLookup, types.InlineValueEvaluatableExpression]]]:
+ """Make a :lsp:`textDocument/inlineValue` request.
+
+ A request to provide inline values in a document. The request's parameter is of
+ type {@link InlineValueParams}, the response is of type
+ {@link InlineValue InlineValue[]} or a Thenable that resolves to such.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/inlineValue", params)
+
+ def text_document_linked_editing_range(
+ self,
+ params: types.LinkedEditingRangeParams,
+ callback: Optional[Callable[[Optional[types.LinkedEditingRanges]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/linkedEditingRange` request.
+
+ A request to provide ranges that can be edited together.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/linkedEditingRange", params, callback)
+
+ async def text_document_linked_editing_range_async(
+ self,
+ params: types.LinkedEditingRangeParams,
+ ) -> Optional[types.LinkedEditingRanges]:
+ """Make a :lsp:`textDocument/linkedEditingRange` request.
+
+ A request to provide ranges that can be edited together.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/linkedEditingRange", params)
+
+ def text_document_moniker(
+ self,
+ params: types.MonikerParams,
+ callback: Optional[Callable[[Optional[List[types.Moniker]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/moniker` request.
+
+ A request to get the moniker of a symbol at a given text document position.
+ The request parameter is of type {@link TextDocumentPositionParams}.
+ The response is of type {@link Moniker Moniker[]} or `null`.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/moniker", params, callback)
+
+ async def text_document_moniker_async(
+ self,
+ params: types.MonikerParams,
+ ) -> Optional[List[types.Moniker]]:
+ """Make a :lsp:`textDocument/moniker` request.
+
+ A request to get the moniker of a symbol at a given text document position.
+ The request parameter is of type {@link TextDocumentPositionParams}.
+ The response is of type {@link Moniker Moniker[]} or `null`.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/moniker", params)
+
+ def text_document_on_type_formatting(
+ self,
+ params: types.DocumentOnTypeFormattingParams,
+ callback: Optional[Callable[[Optional[List[types.TextEdit]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/onTypeFormatting` request.
+
+ A request to format a document on type.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/onTypeFormatting", params, callback)
+
+ async def text_document_on_type_formatting_async(
+ self,
+ params: types.DocumentOnTypeFormattingParams,
+ ) -> Optional[List[types.TextEdit]]:
+ """Make a :lsp:`textDocument/onTypeFormatting` request.
+
+ A request to format a document on type.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/onTypeFormatting", params)
+
+ def text_document_prepare_call_hierarchy(
+ self,
+ params: types.CallHierarchyPrepareParams,
+ callback: Optional[Callable[[Optional[List[types.CallHierarchyItem]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/prepareCallHierarchy` request.
+
+ A request to result a `CallHierarchyItem` in a document at a given position.
+ Can be used as an input to an incoming or outgoing call hierarchy.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/prepareCallHierarchy", params, callback)
+
+ async def text_document_prepare_call_hierarchy_async(
+ self,
+ params: types.CallHierarchyPrepareParams,
+ ) -> Optional[List[types.CallHierarchyItem]]:
+ """Make a :lsp:`textDocument/prepareCallHierarchy` request.
+
+ A request to result a `CallHierarchyItem` in a document at a given position.
+ Can be used as an input to an incoming or outgoing call hierarchy.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/prepareCallHierarchy", params)
+
+ def text_document_prepare_rename(
+ self,
+ params: types.PrepareRenameParams,
+ callback: Optional[Callable[[Union[types.Range, types.PrepareRenameResult_Type1, types.PrepareRenameResult_Type2, None]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/prepareRename` request.
+
+ A request to test and perform the setup necessary for a rename.
+
+ @since 3.16 - support for default behavior
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/prepareRename", params, callback)
+
+ async def text_document_prepare_rename_async(
+ self,
+ params: types.PrepareRenameParams,
+ ) -> Union[types.Range, types.PrepareRenameResult_Type1, types.PrepareRenameResult_Type2, None]:
+ """Make a :lsp:`textDocument/prepareRename` request.
+
+ A request to test and perform the setup necessary for a rename.
+
+ @since 3.16 - support for default behavior
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/prepareRename", params)
+
+ def text_document_prepare_type_hierarchy(
+ self,
+ params: types.TypeHierarchyPrepareParams,
+ callback: Optional[Callable[[Optional[List[types.TypeHierarchyItem]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/prepareTypeHierarchy` request.
+
+ A request to result a `TypeHierarchyItem` in a document at a given position.
+ Can be used as an input to a subtypes or supertypes type hierarchy.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/prepareTypeHierarchy", params, callback)
+
+ async def text_document_prepare_type_hierarchy_async(
+ self,
+ params: types.TypeHierarchyPrepareParams,
+ ) -> Optional[List[types.TypeHierarchyItem]]:
+ """Make a :lsp:`textDocument/prepareTypeHierarchy` request.
+
+ A request to result a `TypeHierarchyItem` in a document at a given position.
+ Can be used as an input to a subtypes or supertypes type hierarchy.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/prepareTypeHierarchy", params)
+
+ def text_document_ranges_formatting(
+ self,
+ params: types.DocumentRangesFormattingParams,
+ callback: Optional[Callable[[Optional[List[types.TextEdit]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/rangesFormatting` request.
+
+ A request to format ranges in a document.
+
+ @since 3.18.0
+ @proposed
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/rangesFormatting", params, callback)
+
+ async def text_document_ranges_formatting_async(
+ self,
+ params: types.DocumentRangesFormattingParams,
+ ) -> Optional[List[types.TextEdit]]:
+ """Make a :lsp:`textDocument/rangesFormatting` request.
+
+ A request to format ranges in a document.
+
+ @since 3.18.0
+ @proposed
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/rangesFormatting", params)
+
+ def text_document_range_formatting(
+ self,
+ params: types.DocumentRangeFormattingParams,
+ callback: Optional[Callable[[Optional[List[types.TextEdit]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/rangeFormatting` request.
+
+ A request to format a range in a document.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/rangeFormatting", params, callback)
+
+ async def text_document_range_formatting_async(
+ self,
+ params: types.DocumentRangeFormattingParams,
+ ) -> Optional[List[types.TextEdit]]:
+ """Make a :lsp:`textDocument/rangeFormatting` request.
+
+ A request to format a range in a document.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/rangeFormatting", params)
+
+ def text_document_references(
+ self,
+ params: types.ReferenceParams,
+ callback: Optional[Callable[[Optional[List[types.Location]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/references` request.
+
+ A request to resolve project-wide references for the symbol denoted
+ by the given text document position. The request's parameter is of
+ type {@link ReferenceParams} the response is of type
+ {@link Location Location[]} or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/references", params, callback)
+
+ async def text_document_references_async(
+ self,
+ params: types.ReferenceParams,
+ ) -> Optional[List[types.Location]]:
+ """Make a :lsp:`textDocument/references` request.
+
+ A request to resolve project-wide references for the symbol denoted
+ by the given text document position. The request's parameter is of
+ type {@link ReferenceParams} the response is of type
+ {@link Location Location[]} or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/references", params)
+
+ def text_document_rename(
+ self,
+ params: types.RenameParams,
+ callback: Optional[Callable[[Optional[types.WorkspaceEdit]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/rename` request.
+
+ A request to rename a symbol.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/rename", params, callback)
+
+ async def text_document_rename_async(
+ self,
+ params: types.RenameParams,
+ ) -> Optional[types.WorkspaceEdit]:
+ """Make a :lsp:`textDocument/rename` request.
+
+ A request to rename a symbol.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/rename", params)
+
+ def text_document_selection_range(
+ self,
+ params: types.SelectionRangeParams,
+ callback: Optional[Callable[[Optional[List[types.SelectionRange]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/selectionRange` request.
+
+ A request to provide selection ranges in a document. The request's
+ parameter is of type {@link SelectionRangeParams}, the
+ response is of type {@link SelectionRange SelectionRange[]} or a Thenable
+ that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/selectionRange", params, callback)
+
+ async def text_document_selection_range_async(
+ self,
+ params: types.SelectionRangeParams,
+ ) -> Optional[List[types.SelectionRange]]:
+ """Make a :lsp:`textDocument/selectionRange` request.
+
+ A request to provide selection ranges in a document. The request's
+ parameter is of type {@link SelectionRangeParams}, the
+ response is of type {@link SelectionRange SelectionRange[]} or a Thenable
+ that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/selectionRange", params)
+
+ def text_document_semantic_tokens_full(
+ self,
+ params: types.SemanticTokensParams,
+ callback: Optional[Callable[[Optional[types.SemanticTokens]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/semanticTokens/full` request.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/semanticTokens/full", params, callback)
+
+ async def text_document_semantic_tokens_full_async(
+ self,
+ params: types.SemanticTokensParams,
+ ) -> Optional[types.SemanticTokens]:
+ """Make a :lsp:`textDocument/semanticTokens/full` request.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/semanticTokens/full", params)
+
+ def text_document_semantic_tokens_full_delta(
+ self,
+ params: types.SemanticTokensDeltaParams,
+ callback: Optional[Callable[[Union[types.SemanticTokens, types.SemanticTokensDelta, None]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/semanticTokens/full/delta` request.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/semanticTokens/full/delta", params, callback)
+
+ async def text_document_semantic_tokens_full_delta_async(
+ self,
+ params: types.SemanticTokensDeltaParams,
+ ) -> Union[types.SemanticTokens, types.SemanticTokensDelta, None]:
+ """Make a :lsp:`textDocument/semanticTokens/full/delta` request.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/semanticTokens/full/delta", params)
+
+ def text_document_semantic_tokens_range(
+ self,
+ params: types.SemanticTokensRangeParams,
+ callback: Optional[Callable[[Optional[types.SemanticTokens]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/semanticTokens/range` request.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/semanticTokens/range", params, callback)
+
+ async def text_document_semantic_tokens_range_async(
+ self,
+ params: types.SemanticTokensRangeParams,
+ ) -> Optional[types.SemanticTokens]:
+ """Make a :lsp:`textDocument/semanticTokens/range` request.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/semanticTokens/range", params)
+
+ def text_document_signature_help(
+ self,
+ params: types.SignatureHelpParams,
+ callback: Optional[Callable[[Optional[types.SignatureHelp]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/signatureHelp` request.
+
+
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/signatureHelp", params, callback)
+
+ async def text_document_signature_help_async(
+ self,
+ params: types.SignatureHelpParams,
+ ) -> Optional[types.SignatureHelp]:
+ """Make a :lsp:`textDocument/signatureHelp` request.
+
+
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/signatureHelp", params)
+
+ def text_document_type_definition(
+ self,
+ params: types.TypeDefinitionParams,
+ callback: Optional[Callable[[Union[types.Location, List[types.Location], List[types.LocationLink], None]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/typeDefinition` request.
+
+ A request to resolve the type definition locations of a symbol at a given text
+ document position. The request's parameter is of type {@link TextDocumentPositionParams}
+ the response is of type {@link Definition} or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/typeDefinition", params, callback)
+
+ async def text_document_type_definition_async(
+ self,
+ params: types.TypeDefinitionParams,
+ ) -> Union[types.Location, List[types.Location], List[types.LocationLink], None]:
+ """Make a :lsp:`textDocument/typeDefinition` request.
+
+ A request to resolve the type definition locations of a symbol at a given text
+ document position. The request's parameter is of type {@link TextDocumentPositionParams}
+ the response is of type {@link Definition} or a Thenable that resolves to such.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/typeDefinition", params)
+
+ def text_document_will_save_wait_until(
+ self,
+ params: types.WillSaveTextDocumentParams,
+ callback: Optional[Callable[[Optional[List[types.TextEdit]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`textDocument/willSaveWaitUntil` request.
+
+ A document will save request is sent from the client to the server before
+ the document is actually saved. The request can return an array of TextEdits
+ which will be applied to the text document before it is saved. Please note that
+ clients might drop results if computing the text edits took too long or if a
+ server constantly fails on this request. This is done to keep the save fast and
+ reliable.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("textDocument/willSaveWaitUntil", params, callback)
+
+ async def text_document_will_save_wait_until_async(
+ self,
+ params: types.WillSaveTextDocumentParams,
+ ) -> Optional[List[types.TextEdit]]:
+ """Make a :lsp:`textDocument/willSaveWaitUntil` request.
+
+ A document will save request is sent from the client to the server before
+ the document is actually saved. The request can return an array of TextEdits
+ which will be applied to the text document before it is saved. Please note that
+ clients might drop results if computing the text edits took too long or if a
+ server constantly fails on this request. This is done to keep the save fast and
+ reliable.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("textDocument/willSaveWaitUntil", params)
+
+ def type_hierarchy_subtypes(
+ self,
+ params: types.TypeHierarchySubtypesParams,
+ callback: Optional[Callable[[Optional[List[types.TypeHierarchyItem]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`typeHierarchy/subtypes` request.
+
+ A request to resolve the subtypes for a given `TypeHierarchyItem`.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("typeHierarchy/subtypes", params, callback)
+
+ async def type_hierarchy_subtypes_async(
+ self,
+ params: types.TypeHierarchySubtypesParams,
+ ) -> Optional[List[types.TypeHierarchyItem]]:
+ """Make a :lsp:`typeHierarchy/subtypes` request.
+
+ A request to resolve the subtypes for a given `TypeHierarchyItem`.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("typeHierarchy/subtypes", params)
+
+ def type_hierarchy_supertypes(
+ self,
+ params: types.TypeHierarchySupertypesParams,
+ callback: Optional[Callable[[Optional[List[types.TypeHierarchyItem]]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`typeHierarchy/supertypes` request.
+
+ A request to resolve the supertypes for a given `TypeHierarchyItem`.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("typeHierarchy/supertypes", params, callback)
+
+ async def type_hierarchy_supertypes_async(
+ self,
+ params: types.TypeHierarchySupertypesParams,
+ ) -> Optional[List[types.TypeHierarchyItem]]:
+ """Make a :lsp:`typeHierarchy/supertypes` request.
+
+ A request to resolve the supertypes for a given `TypeHierarchyItem`.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("typeHierarchy/supertypes", params)
+
+ def workspace_diagnostic(
+ self,
+ params: types.WorkspaceDiagnosticParams,
+ callback: Optional[Callable[[types.WorkspaceDiagnosticReport], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`workspace/diagnostic` request.
+
+ The workspace diagnostic request definition.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("workspace/diagnostic", params, callback)
+
+ async def workspace_diagnostic_async(
+ self,
+ params: types.WorkspaceDiagnosticParams,
+ ) -> types.WorkspaceDiagnosticReport:
+ """Make a :lsp:`workspace/diagnostic` request.
+
+ The workspace diagnostic request definition.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("workspace/diagnostic", params)
+
+ def workspace_execute_command(
+ self,
+ params: types.ExecuteCommandParams,
+ callback: Optional[Callable[[Optional[Any]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`workspace/executeCommand` request.
+
+ A request send from the client to the server to execute a command. The request might return
+ a workspace edit which the client will apply to the workspace.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("workspace/executeCommand", params, callback)
+
+ async def workspace_execute_command_async(
+ self,
+ params: types.ExecuteCommandParams,
+ ) -> Optional[Any]:
+ """Make a :lsp:`workspace/executeCommand` request.
+
+ A request send from the client to the server to execute a command. The request might return
+ a workspace edit which the client will apply to the workspace.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("workspace/executeCommand", params)
+
+ def workspace_symbol(
+ self,
+ params: types.WorkspaceSymbolParams,
+ callback: Optional[Callable[[Union[List[types.SymbolInformation], List[types.WorkspaceSymbol], None]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`workspace/symbol` request.
+
+ A request to list project-wide symbols matching the query string given
+ by the {@link WorkspaceSymbolParams}. The response is
+ of type {@link SymbolInformation SymbolInformation[]} or a Thenable that
+ resolves to such.
+
+ @since 3.17.0 - support for WorkspaceSymbol in the returned data. Clients
+ need to advertise support for WorkspaceSymbols via the client capability
+ `workspace.symbol.resolveSupport`.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("workspace/symbol", params, callback)
+
+ async def workspace_symbol_async(
+ self,
+ params: types.WorkspaceSymbolParams,
+ ) -> Union[List[types.SymbolInformation], List[types.WorkspaceSymbol], None]:
+ """Make a :lsp:`workspace/symbol` request.
+
+ A request to list project-wide symbols matching the query string given
+ by the {@link WorkspaceSymbolParams}. The response is
+ of type {@link SymbolInformation SymbolInformation[]} or a Thenable that
+ resolves to such.
+
+ @since 3.17.0 - support for WorkspaceSymbol in the returned data. Clients
+ need to advertise support for WorkspaceSymbols via the client capability
+ `workspace.symbol.resolveSupport`.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("workspace/symbol", params)
+
+ def workspace_symbol_resolve(
+ self,
+ params: types.WorkspaceSymbol,
+ callback: Optional[Callable[[types.WorkspaceSymbol], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`workspaceSymbol/resolve` request.
+
+ A request to resolve the range inside the workspace
+ symbol's location.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("workspaceSymbol/resolve", params, callback)
+
+ async def workspace_symbol_resolve_async(
+ self,
+ params: types.WorkspaceSymbol,
+ ) -> types.WorkspaceSymbol:
+ """Make a :lsp:`workspaceSymbol/resolve` request.
+
+ A request to resolve the range inside the workspace
+ symbol's location.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("workspaceSymbol/resolve", params)
+
+ def workspace_will_create_files(
+ self,
+ params: types.CreateFilesParams,
+ callback: Optional[Callable[[Optional[types.WorkspaceEdit]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`workspace/willCreateFiles` request.
+
+ The will create files request is sent from the client to the server before files are actually
+ created as long as the creation is triggered from within the client.
+
+ The request can return a `WorkspaceEdit` which will be applied to workspace before the
+ files are created. Hence the `WorkspaceEdit` can not manipulate the content of the file
+ to be created.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("workspace/willCreateFiles", params, callback)
+
+ async def workspace_will_create_files_async(
+ self,
+ params: types.CreateFilesParams,
+ ) -> Optional[types.WorkspaceEdit]:
+ """Make a :lsp:`workspace/willCreateFiles` request.
+
+ The will create files request is sent from the client to the server before files are actually
+ created as long as the creation is triggered from within the client.
+
+ The request can return a `WorkspaceEdit` which will be applied to workspace before the
+ files are created. Hence the `WorkspaceEdit` can not manipulate the content of the file
+ to be created.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("workspace/willCreateFiles", params)
+
+ def workspace_will_delete_files(
+ self,
+ params: types.DeleteFilesParams,
+ callback: Optional[Callable[[Optional[types.WorkspaceEdit]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`workspace/willDeleteFiles` request.
+
+ The did delete files notification is sent from the client to the server when
+ files were deleted from within the client.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("workspace/willDeleteFiles", params, callback)
+
+ async def workspace_will_delete_files_async(
+ self,
+ params: types.DeleteFilesParams,
+ ) -> Optional[types.WorkspaceEdit]:
+ """Make a :lsp:`workspace/willDeleteFiles` request.
+
+ The did delete files notification is sent from the client to the server when
+ files were deleted from within the client.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("workspace/willDeleteFiles", params)
+
+ def workspace_will_rename_files(
+ self,
+ params: types.RenameFilesParams,
+ callback: Optional[Callable[[Optional[types.WorkspaceEdit]], None]] = None,
+ ) -> Future:
+ """Make a :lsp:`workspace/willRenameFiles` request.
+
+ The will rename files request is sent from the client to the server before files are actually
+ renamed as long as the rename is triggered from within the client.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return self.protocol.send_request("workspace/willRenameFiles", params, callback)
+
+ async def workspace_will_rename_files_async(
+ self,
+ params: types.RenameFilesParams,
+ ) -> Optional[types.WorkspaceEdit]:
+ """Make a :lsp:`workspace/willRenameFiles` request.
+
+ The will rename files request is sent from the client to the server before files are actually
+ renamed as long as the rename is triggered from within the client.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ return await self.protocol.send_request_async("workspace/willRenameFiles", params)
+
+ def cancel_request(self, params: types.CancelParams) -> None:
+ """Send a :lsp:`$/cancelRequest` notification.
+
+
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("$/cancelRequest", params)
+
+ def exit(self, params: None) -> None:
+ """Send a :lsp:`exit` notification.
+
+ The exit event is sent from the client to the server to
+ ask the server to exit its process.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("exit", params)
+
+ def initialized(self, params: types.InitializedParams) -> None:
+ """Send a :lsp:`initialized` notification.
+
+ The initialized notification is sent from the client to the
+ server after the client is fully initialized and the server
+ is allowed to send requests from the server to the client.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("initialized", params)
+
+ def notebook_document_did_change(self, params: types.DidChangeNotebookDocumentParams) -> None:
+ """Send a :lsp:`notebookDocument/didChange` notification.
+
+
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("notebookDocument/didChange", params)
+
+ def notebook_document_did_close(self, params: types.DidCloseNotebookDocumentParams) -> None:
+ """Send a :lsp:`notebookDocument/didClose` notification.
+
+ A notification sent when a notebook closes.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("notebookDocument/didClose", params)
+
+ def notebook_document_did_open(self, params: types.DidOpenNotebookDocumentParams) -> None:
+ """Send a :lsp:`notebookDocument/didOpen` notification.
+
+ A notification sent when a notebook opens.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("notebookDocument/didOpen", params)
+
+ def notebook_document_did_save(self, params: types.DidSaveNotebookDocumentParams) -> None:
+ """Send a :lsp:`notebookDocument/didSave` notification.
+
+ A notification sent when a notebook document is saved.
+
+ @since 3.17.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("notebookDocument/didSave", params)
+
+ def progress(self, params: types.ProgressParams) -> None:
+ """Send a :lsp:`$/progress` notification.
+
+
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("$/progress", params)
+
+ def set_trace(self, params: types.SetTraceParams) -> None:
+ """Send a :lsp:`$/setTrace` notification.
+
+
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("$/setTrace", params)
+
+ def text_document_did_change(self, params: types.DidChangeTextDocumentParams) -> None:
+ """Send a :lsp:`textDocument/didChange` notification.
+
+ The document change notification is sent from the client to the server to signal
+ changes to a text document.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("textDocument/didChange", params)
+
+ def text_document_did_close(self, params: types.DidCloseTextDocumentParams) -> None:
+ """Send a :lsp:`textDocument/didClose` notification.
+
+ The document close notification is sent from the client to the server when
+ the document got closed in the client. The document's truth now exists where
+ the document's uri points to (e.g. if the document's uri is a file uri the
+ truth now exists on disk). As with the open notification the close notification
+ is about managing the document's content. Receiving a close notification
+ doesn't mean that the document was open in an editor before. A close
+ notification requires a previous open notification to be sent.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("textDocument/didClose", params)
+
+ def text_document_did_open(self, params: types.DidOpenTextDocumentParams) -> None:
+ """Send a :lsp:`textDocument/didOpen` notification.
+
+ The document open notification is sent from the client to the server to signal
+ newly opened text documents. The document's truth is now managed by the client
+ and the server must not try to read the document's truth using the document's
+ uri. Open in this sense means it is managed by the client. It doesn't necessarily
+ mean that its content is presented in an editor. An open notification must not
+ be sent more than once without a corresponding close notification send before.
+ This means open and close notification must be balanced and the max open count
+ is one.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("textDocument/didOpen", params)
+
+ def text_document_did_save(self, params: types.DidSaveTextDocumentParams) -> None:
+ """Send a :lsp:`textDocument/didSave` notification.
+
+ The document save notification is sent from the client to the server when
+ the document got saved in the client.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("textDocument/didSave", params)
+
+ def text_document_will_save(self, params: types.WillSaveTextDocumentParams) -> None:
+ """Send a :lsp:`textDocument/willSave` notification.
+
+ A document will save notification is sent from the client to the server before
+ the document is actually saved.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("textDocument/willSave", params)
+
+ def window_work_done_progress_cancel(self, params: types.WorkDoneProgressCancelParams) -> None:
+ """Send a :lsp:`window/workDoneProgress/cancel` notification.
+
+ The `window/workDoneProgress/cancel` notification is sent from the client to the server to cancel a progress
+ initiated on the server side.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("window/workDoneProgress/cancel", params)
+
+ def workspace_did_change_configuration(self, params: types.DidChangeConfigurationParams) -> None:
+ """Send a :lsp:`workspace/didChangeConfiguration` notification.
+
+ The configuration change notification is sent from the client to the server
+ when the client's configuration has changed. The notification contains
+ the changed configuration as defined by the language client.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("workspace/didChangeConfiguration", params)
+
+ def workspace_did_change_watched_files(self, params: types.DidChangeWatchedFilesParams) -> None:
+ """Send a :lsp:`workspace/didChangeWatchedFiles` notification.
+
+ The watched files notification is sent from the client to the server when
+ the client detects changes to file watched by the language client.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("workspace/didChangeWatchedFiles", params)
+
+ def workspace_did_change_workspace_folders(self, params: types.DidChangeWorkspaceFoldersParams) -> None:
+ """Send a :lsp:`workspace/didChangeWorkspaceFolders` notification.
+
+ The `workspace/didChangeWorkspaceFolders` notification is sent from the client to the server when the workspace
+ folder configuration changes.
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("workspace/didChangeWorkspaceFolders", params)
+
+ def workspace_did_create_files(self, params: types.CreateFilesParams) -> None:
+ """Send a :lsp:`workspace/didCreateFiles` notification.
+
+ The did create files notification is sent from the client to the server when
+ files were created from within the client.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("workspace/didCreateFiles", params)
+
+ def workspace_did_delete_files(self, params: types.DeleteFilesParams) -> None:
+ """Send a :lsp:`workspace/didDeleteFiles` notification.
+
+ The will delete files request is sent from the client to the server before files are actually
+ deleted as long as the deletion is triggered from within the client.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("workspace/didDeleteFiles", params)
+
+ def workspace_did_rename_files(self, params: types.RenameFilesParams) -> None:
+ """Send a :lsp:`workspace/didRenameFiles` notification.
+
+ The did rename files notification is sent from the client to the server when
+ files were renamed from within the client.
+
+ @since 3.16.0
+ """
+ if self.stopped:
+ raise RuntimeError("Client has been stopped.")
+
+ self.protocol.notify("workspace/didRenameFiles", params)
diff --git a/pygls/progress.py b/pygls/progress.py
new file mode 100644
index 0000000..a2f0e5c
--- /dev/null
+++ b/pygls/progress.py
@@ -0,0 +1,79 @@
+import asyncio
+from concurrent.futures import Future
+from typing import Dict
+
+from lsprotocol.types import (
+ PROGRESS,
+ WINDOW_WORK_DONE_PROGRESS_CREATE,
+ ProgressParams,
+ ProgressToken,
+ WorkDoneProgressBegin,
+ WorkDoneProgressEnd,
+ WorkDoneProgressReport,
+ WorkDoneProgressCreateParams,
+)
+from pygls.protocol import LanguageServerProtocol
+
+
+class Progress:
+ """A class for working with client's progress bar.
+
+ Attributes:
+ _lsp(LanguageServerProtocol): Language server protocol instance
+ tokens(dict): Holds futures for work done progress tokens that are
+ already registered. These futures will be cancelled if the client
+ sends a cancel work done process notification.
+ """
+
+ def __init__(self, lsp: LanguageServerProtocol) -> None:
+ self._lsp = lsp
+
+ self.tokens: Dict[ProgressToken, Future] = {}
+
+ def _check_token_registered(self, token: ProgressToken) -> None:
+ if token in self.tokens:
+ raise Exception("Token is already registered!")
+
+ def _register_token(self, token: ProgressToken) -> None:
+ self.tokens[token] = Future()
+
+ def create(self, token: ProgressToken, callback=None) -> Future:
+ """Create a server initiated work done progress."""
+ self._check_token_registered(token)
+
+ def on_created(*args, **kwargs):
+ self._register_token(token)
+ if callback is not None:
+ callback(*args, **kwargs)
+
+ return self._lsp.send_request(
+ WINDOW_WORK_DONE_PROGRESS_CREATE,
+ WorkDoneProgressCreateParams(token=token),
+ on_created,
+ )
+
+ async def create_async(self, token: ProgressToken) -> asyncio.Future:
+ """Create a server initiated work done progress."""
+ self._check_token_registered(token)
+
+ result = await self._lsp.send_request_async(
+ WINDOW_WORK_DONE_PROGRESS_CREATE,
+ WorkDoneProgressCreateParams(token=token),
+ )
+ self._register_token(token)
+ return result
+
+ def begin(self, token: ProgressToken, value: WorkDoneProgressBegin) -> None:
+ """Notify beginning of work."""
+ # Register cancellation future for the case of client initiated progress
+ self.tokens.setdefault(token, Future())
+
+ return self._lsp.notify(PROGRESS, ProgressParams(token=token, value=value))
+
+ def report(self, token: ProgressToken, value: WorkDoneProgressReport) -> None:
+ """Notify progress of work."""
+ self._lsp.notify(PROGRESS, ProgressParams(token=token, value=value))
+
+ def end(self, token: ProgressToken, value: WorkDoneProgressEnd) -> None:
+ """Notify end of work."""
+ self._lsp.notify(PROGRESS, ProgressParams(token=token, value=value))
diff --git a/pygls/protocol/__init__.py b/pygls/protocol/__init__.py
new file mode 100644
index 0000000..1a30b48
--- /dev/null
+++ b/pygls/protocol/__init__.py
@@ -0,0 +1,78 @@
+import json
+from typing import Any
+
+from collections import namedtuple
+
+from lsprotocol import converters
+
+from pygls.protocol.json_rpc import (
+ JsonRPCNotification,
+ JsonRPCProtocol,
+ JsonRPCRequestMessage,
+ JsonRPCResponseMessage,
+)
+from pygls.protocol.language_server import LanguageServerProtocol, lsp_method
+from pygls.protocol.lsp_meta import LSPMeta, call_user_feature
+
+
+def _dict_to_object(d: Any):
+ """Create nested objects (namedtuple) from dict."""
+
+ if d is None:
+ return None
+
+ if not isinstance(d, dict):
+ return d
+
+ type_name = d.pop("type_name", "Object")
+ return json.loads(
+ json.dumps(d),
+ object_hook=lambda p: namedtuple(type_name, p.keys(), rename=True)(*p.values()),
+ )
+
+
+def _params_field_structure_hook(obj, cls):
+ if "params" in obj:
+ obj["params"] = _dict_to_object(obj["params"])
+
+ return cls(**obj)
+
+
+def _result_field_structure_hook(obj, cls):
+ if "result" in obj:
+ obj["result"] = _dict_to_object(obj["result"])
+
+ return cls(**obj)
+
+
+def default_converter():
+ """Default converter factory function."""
+
+ converter = converters.get_converter()
+ converter.register_structure_hook(
+ JsonRPCRequestMessage, _params_field_structure_hook
+ )
+
+ converter.register_structure_hook(
+ JsonRPCResponseMessage, _result_field_structure_hook
+ )
+
+ converter.register_structure_hook(JsonRPCNotification, _params_field_structure_hook)
+
+ return converter
+
+
+__all__ = (
+ "JsonRPCProtocol",
+ "LanguageServerProtocol",
+ "JsonRPCRequestMessage",
+ "JsonRPCResponseMessage",
+ "JsonRPCNotification",
+ "LSPMeta",
+ "call_user_feature",
+ "_dict_to_object",
+ "_params_field_structure_hook",
+ "_result_field_structure_hook",
+ "default_converter",
+ "lsp_method",
+)
diff --git a/pygls/protocol/json_rpc.py b/pygls/protocol/json_rpc.py
new file mode 100644
index 0000000..75a4b34
--- /dev/null
+++ b/pygls/protocol/json_rpc.py
@@ -0,0 +1,560 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from __future__ import annotations
+import asyncio
+import enum
+import json
+import logging
+import re
+import sys
+import uuid
+import traceback
+from concurrent.futures import Future
+from functools import partial
+from typing import (
+ Any,
+ Dict,
+ List,
+ Optional,
+ Type,
+ Union,
+ TYPE_CHECKING,
+)
+
+if TYPE_CHECKING:
+ from pygls.server import LanguageServer, WebSocketTransportAdapter
+
+
+import attrs
+from cattrs.errors import ClassValidationError
+
+from lsprotocol.types import (
+ CANCEL_REQUEST,
+ EXIT,
+ WORKSPACE_EXECUTE_COMMAND,
+ ResponseError,
+ ResponseErrorMessage,
+)
+
+from pygls.exceptions import (
+ JsonRpcException,
+ JsonRpcInternalError,
+ JsonRpcInvalidParams,
+ JsonRpcMethodNotFound,
+ JsonRpcRequestCancelled,
+ FeatureNotificationError,
+ FeatureRequestError,
+)
+from pygls.feature_manager import FeatureManager, is_thread_function
+
+logger = logging.getLogger(__name__)
+
+
+@attrs.define
+class JsonRPCNotification:
+ """A class that represents a generic json rpc notification message.
+ Used as a fallback for unknown types.
+ """
+
+ method: str
+ jsonrpc: str
+ params: Any
+
+
+@attrs.define
+class JsonRPCRequestMessage:
+ """A class that represents a generic json rpc request message.
+ Used as a fallback for unknown types.
+ """
+
+ id: Union[int, str]
+ method: str
+ jsonrpc: str
+ params: Any
+
+
+@attrs.define
+class JsonRPCResponseMessage:
+ """A class that represents a generic json rpc response message.
+ Used as a fallback for unknown types.
+ """
+
+ id: Union[int, str]
+ jsonrpc: str
+ result: Any
+
+
+class JsonRPCProtocol(asyncio.Protocol):
+ """Json RPC protocol implementation using on top of `asyncio.Protocol`.
+
+ Specification of the protocol can be found here:
+ https://www.jsonrpc.org/specification
+
+ This class provides bidirectional communication which is needed for LSP.
+ """
+
+ CHARSET = "utf-8"
+ CONTENT_TYPE = "application/vscode-jsonrpc"
+
+ MESSAGE_PATTERN = re.compile(
+ rb"^(?:[^\r\n]+\r\n)*"
+ + rb"Content-Length: (?P<length>\d+)\r\n"
+ + rb"(?:[^\r\n]+\r\n)*\r\n"
+ + rb"(?P<body>{.*)",
+ re.DOTALL,
+ )
+
+ VERSION = "2.0"
+
+ def __init__(self, server: LanguageServer, converter):
+ self._server = server
+ self._converter = converter
+
+ self._shutdown = False
+
+ # Book keeping for in-flight requests
+ self._request_futures: Dict[str, Future[Any]] = {}
+ self._result_types: Dict[str, Any] = {}
+
+ self.fm = FeatureManager(server, converter)
+ self.transport: Optional[
+ Union[asyncio.WriteTransport, WebSocketTransportAdapter]
+ ] = None
+ self._message_buf: List[bytes] = []
+
+ self._send_only_body = False
+
+ def __call__(self):
+ return self
+
+ def _execute_notification(self, handler, *params):
+ """Executes notification message handler."""
+ if asyncio.iscoroutinefunction(handler):
+ future = asyncio.ensure_future(handler(*params))
+ future.add_done_callback(self._execute_notification_callback)
+ else:
+ if is_thread_function(handler):
+ self._server.thread_pool.apply_async(handler, (*params,))
+ else:
+ handler(*params)
+
+ def _execute_notification_callback(self, future):
+ """Success callback used for coroutine notification message."""
+ if future.exception():
+ try:
+ raise future.exception()
+ except Exception:
+ error = JsonRpcInternalError.of(sys.exc_info())
+ logger.exception('Exception occurred in notification: "%s"', error)
+
+ # Revisit. Client does not support response with msg_id = None
+ # https://stackoverflow.com/questions/31091376/json-rpc-2-0-allow-notifications-to-have-an-error-response
+ # self._send_response(None, error=error)
+
+ def _execute_request(self, msg_id, handler, params):
+ """Executes request message handler."""
+
+ if asyncio.iscoroutinefunction(handler):
+ future = asyncio.ensure_future(handler(params))
+ self._request_futures[msg_id] = future
+ future.add_done_callback(partial(self._execute_request_callback, msg_id))
+ else:
+ # Can't be canceled
+ if is_thread_function(handler):
+ self._server.thread_pool.apply_async(
+ handler,
+ (params,),
+ callback=partial(
+ self._send_response,
+ msg_id,
+ ),
+ error_callback=partial(self._execute_request_err_callback, msg_id),
+ )
+ else:
+ self._send_response(msg_id, handler(params))
+
+ def _execute_request_callback(self, msg_id, future):
+ """Success callback used for coroutine request message."""
+ try:
+ if not future.cancelled():
+ self._send_response(msg_id, result=future.result())
+ else:
+ self._send_response(
+ msg_id,
+ error=JsonRpcRequestCancelled(
+ f'Request with id "{msg_id}" is canceled'
+ ).to_response_error(),
+ )
+ self._request_futures.pop(msg_id, None)
+ except Exception:
+ error = JsonRpcInternalError.of(sys.exc_info())
+ logger.exception('Exception occurred for message "%s": %s', msg_id, error)
+ self._send_response(msg_id, error=error.to_response_error())
+
+ def _execute_request_err_callback(self, msg_id, exc):
+ """Error callback used for coroutine request message."""
+ exc_info = (type(exc), exc, None)
+ error = JsonRpcInternalError.of(exc_info)
+ logger.exception('Exception occurred for message "%s": %s', msg_id, error)
+ self._send_response(msg_id, error=error.to_response_error())
+
+ def _get_handler(self, feature_name):
+ """Returns builtin or used defined feature by name if exists."""
+ try:
+ return self.fm.builtin_features[feature_name]
+ except KeyError:
+ try:
+ return self.fm.features[feature_name]
+ except KeyError:
+ raise JsonRpcMethodNotFound.of(feature_name)
+
+ def _handle_cancel_notification(self, msg_id):
+ """Handles a cancel notification from the client."""
+ future = self._request_futures.pop(msg_id, None)
+
+ if not future:
+ logger.warning('Cancel notification for unknown message id "%s"', msg_id)
+ return
+
+ # Will only work if the request hasn't started executing
+ if future.cancel():
+ logger.info('Cancelled request with id "%s"', msg_id)
+
+ def _handle_notification(self, method_name, params):
+ """Handles a notification from the client."""
+ if method_name == CANCEL_REQUEST:
+ self._handle_cancel_notification(params.id)
+ return
+
+ try:
+ handler = self._get_handler(method_name)
+ self._execute_notification(handler, params)
+ except (KeyError, JsonRpcMethodNotFound):
+ logger.warning('Ignoring notification for unknown method "%s"', method_name)
+ except Exception as error:
+ logger.exception(
+ 'Failed to handle notification "%s": %s',
+ method_name,
+ params,
+ exc_info=True,
+ )
+ self._server._report_server_error(error, FeatureNotificationError)
+
+ def _handle_request(self, msg_id, method_name, params):
+ """Handles a request from the client."""
+ try:
+ handler = self._get_handler(method_name)
+
+ # workspace/executeCommand is a special case
+ if method_name == WORKSPACE_EXECUTE_COMMAND:
+ handler(params, msg_id)
+ else:
+ self._execute_request(msg_id, handler, params)
+
+ except JsonRpcException as error:
+ logger.exception(
+ "Failed to handle request %s %s %s",
+ msg_id,
+ method_name,
+ params,
+ exc_info=True,
+ )
+ self._send_response(msg_id, None, error.to_response_error())
+ self._server._report_server_error(error, FeatureRequestError)
+ except Exception as error:
+ logger.exception(
+ "Failed to handle request %s %s %s",
+ msg_id,
+ method_name,
+ params,
+ exc_info=True,
+ )
+ err = JsonRpcInternalError.of(sys.exc_info()).to_response_error()
+ self._send_response(msg_id, None, err)
+ self._server._report_server_error(error, FeatureRequestError)
+
+ def _handle_response(self, msg_id, result=None, error=None):
+ """Handles a response from the client."""
+ future = self._request_futures.pop(msg_id, None)
+
+ if not future:
+ logger.warning('Received response to unknown message id "%s"', msg_id)
+ return
+
+ if error is not None:
+ logger.debug('Received error response to message "%s": %s', msg_id, error)
+ future.set_exception(JsonRpcException.from_error(error))
+ else:
+ logger.debug('Received result for message "%s": %s', msg_id, result)
+ future.set_result(result)
+
+ def _serialize_message(self, data):
+ """Function used to serialize data sent to the client."""
+
+ if hasattr(data, "__attrs_attrs__"):
+ return self._converter.unstructure(data)
+
+ if isinstance(data, enum.Enum):
+ return data.value
+
+ return data.__dict__
+
+ def _deserialize_message(self, data):
+ """Function used to deserialize data recevied from the client."""
+
+ if "jsonrpc" not in data:
+ return data
+
+ try:
+ if "id" in data:
+ if "error" in data:
+ return self._converter.structure(data, ResponseErrorMessage)
+ elif "method" in data:
+ request_type = (
+ self.get_message_type(data["method"]) or JsonRPCRequestMessage
+ )
+ return self._converter.structure(data, request_type)
+ else:
+ response_type = (
+ self._result_types.pop(data["id"]) or JsonRPCResponseMessage
+ )
+ return self._converter.structure(data, response_type)
+
+ else:
+ method = data.get("method", "")
+ notification_type = self.get_message_type(method) or JsonRPCNotification
+ return self._converter.structure(data, notification_type)
+
+ except ClassValidationError as exc:
+ logger.error("Unable to deserialize message\n%s", traceback.format_exc())
+ raise JsonRpcInvalidParams() from exc
+
+ except Exception as exc:
+ logger.error("Unable to deserialize message\n%s", traceback.format_exc())
+ raise JsonRpcInternalError() from exc
+
+ def _procedure_handler(self, message):
+ """Delegates message to handlers depending on message type."""
+
+ if message.jsonrpc != JsonRPCProtocol.VERSION:
+ logger.warning('Unknown message "%s"', message)
+ return
+
+ if self._shutdown and getattr(message, "method", "") != EXIT:
+ logger.warning("Server shutting down. No more requests!")
+ return
+
+ if hasattr(message, "method"):
+ if hasattr(message, "id"):
+ logger.debug("Request message received.")
+ self._handle_request(message.id, message.method, message.params)
+ else:
+ logger.debug("Notification message received.")
+ self._handle_notification(message.method, message.params)
+ else:
+ if hasattr(message, "error"):
+ logger.debug("Error message received.")
+ self._handle_response(message.id, None, message.error)
+ else:
+ logger.debug("Response message received.")
+ self._handle_response(message.id, message.result)
+
+ def _send_data(self, data):
+ """Sends data to the client."""
+ if not data:
+ return
+
+ if self.transport is None:
+ logger.error("Unable to send data, no available transport!")
+ return
+
+ try:
+ body = json.dumps(data, default=self._serialize_message)
+ logger.info("Sending data: %s", body)
+
+ if self._send_only_body:
+ # Mypy/Pyright seem to think `write()` wants `"bytes | bytearray | memoryview"`
+ # But runtime errors with anything but `str`.
+ self.transport.write(body) # type: ignore
+ return
+
+ header = (
+ f"Content-Length: {len(body)}\r\n"
+ f"Content-Type: {self.CONTENT_TYPE}; charset={self.CHARSET}\r\n\r\n"
+ ).encode(self.CHARSET)
+
+ self.transport.write(header + body.encode(self.CHARSET))
+ except Exception as error:
+ logger.exception("Error sending data", exc_info=True)
+ self._server._report_server_error(error, JsonRpcInternalError)
+
+ def _send_response(
+ self, msg_id, result=None, error: Union[ResponseError, None] = None
+ ):
+ """Sends a JSON RPC response to the client.
+
+ Args:
+ msg_id(str): Id from request
+ result(any): Result returned by handler
+ error(any): Error returned by handler
+ """
+
+ if error is not None:
+ response = ResponseErrorMessage(id=msg_id, error=error)
+
+ else:
+ response_type = self._result_types.pop(msg_id, JsonRPCResponseMessage)
+ response = response_type(
+ id=msg_id, result=result, jsonrpc=JsonRPCProtocol.VERSION
+ )
+
+ self._send_data(response)
+
+ def connection_lost(self, exc):
+ """Method from base class, called when connection is lost, in which case we
+ want to shutdown the server's process as well.
+ """
+ logger.error("Connection to the client is lost! Shutting down the server.")
+ sys.exit(1)
+
+ def connection_made( # type: ignore # see: https://github.com/python/typeshed/issues/3021
+ self,
+ transport: asyncio.Transport,
+ ):
+ """Method from base class, called when connection is established"""
+ self.transport = transport
+
+ def data_received(self, data: bytes):
+ try:
+ self._data_received(data)
+ except Exception as error:
+ logger.exception("Error receiving data", exc_info=True)
+ self._server._report_server_error(error, JsonRpcInternalError)
+
+ def _data_received(self, data: bytes):
+ """Method from base class, called when server receives the data"""
+ logger.debug("Received %r", data)
+
+ while len(data):
+ # Append the incoming chunk to the message buffer
+ self._message_buf.append(data)
+
+ # Look for the body of the message
+ message = b"".join(self._message_buf)
+ found = JsonRPCProtocol.MESSAGE_PATTERN.fullmatch(message)
+
+ body = found.group("body") if found else b""
+ length = int(found.group("length")) if found else 1
+
+ if len(body) < length:
+ # Message is incomplete; bail until more data arrives
+ return
+
+ # Message is complete;
+ # extract the body and any remaining data,
+ # and reset the buffer for the next message
+ body, data = body[:length], body[length:]
+ self._message_buf = []
+
+ # Parse the body
+ self._procedure_handler(
+ json.loads(
+ body.decode(self.CHARSET), object_hook=self._deserialize_message
+ )
+ )
+
+ def get_message_type(self, method: str) -> Optional[Type]:
+ """Return the type definition of the message associated with the given method."""
+ return None
+
+ def get_result_type(self, method: str) -> Optional[Type]:
+ """Return the type definition of the result associated with the given method."""
+ return None
+
+ def notify(self, method: str, params=None):
+ """Sends a JSON RPC notification to the client."""
+
+ logger.debug("Sending notification: '%s' %s", method, params)
+
+ notification_type = self.get_message_type(method) or JsonRPCNotification
+ notification = notification_type(
+ method=method, params=params, jsonrpc=JsonRPCProtocol.VERSION
+ )
+
+ self._send_data(notification)
+
+ def send_request(self, method, params=None, callback=None, msg_id=None):
+ """Sends a JSON RPC request to the client.
+
+ Args:
+ method(str): The method name of the message to send
+ params(any): The payload of the message
+
+ Returns:
+ Future that will be resolved once a response has been received
+ """
+
+ if msg_id is None:
+ msg_id = str(uuid.uuid4())
+
+ request_type = self.get_message_type(method) or JsonRPCRequestMessage
+ logger.debug('Sending request with id "%s": %s %s', msg_id, method, params)
+
+ request = request_type(
+ id=msg_id,
+ method=method,
+ params=params,
+ jsonrpc=JsonRPCProtocol.VERSION,
+ )
+
+ future = Future() # type: ignore[var-annotated]
+ # If callback function is given, call it when result is received
+ if callback:
+
+ def wrapper(future: Future):
+ result = future.result()
+ logger.info("Client response for %s received: %s", params, result)
+ callback(result)
+
+ future.add_done_callback(wrapper)
+
+ self._request_futures[msg_id] = future
+ self._result_types[msg_id] = self.get_result_type(method)
+
+ self._send_data(request)
+
+ return future
+
+ def send_request_async(self, method, params=None, msg_id=None):
+ """Calls `send_request` and wraps `concurrent.futures.Future` with
+ `asyncio.Future` so it can be used with `await` keyword.
+
+ Args:
+ method(str): The method name of the message to send
+ params(any): The payload of the message
+ msg_id(str|int): Optional, message id
+
+ Returns:
+ `asyncio.Future` that can be awaited
+ """
+ return asyncio.wrap_future(
+ self.send_request(method, params=params, msg_id=msg_id)
+ )
+
+ def thread(self):
+ """Decorator that mark function to execute it in a thread."""
+ return self.fm.thread()
diff --git a/pygls/protocol/language_server.py b/pygls/protocol/language_server.py
new file mode 100644
index 0000000..42b1319
--- /dev/null
+++ b/pygls/protocol/language_server.py
@@ -0,0 +1,569 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from __future__ import annotations
+import asyncio
+import json
+import logging
+import sys
+from concurrent.futures import Future
+from functools import lru_cache
+from itertools import zip_longest
+from typing import (
+ Callable,
+ List,
+ Optional,
+ Type,
+ TypeVar,
+ Union,
+)
+
+
+from pygls.capabilities import ServerCapabilitiesBuilder
+from pygls.lsp import ConfigCallbackType, ShowDocumentCallbackType
+from lsprotocol.types import (
+ CLIENT_REGISTER_CAPABILITY,
+ CLIENT_UNREGISTER_CAPABILITY,
+ EXIT,
+ INITIALIZE,
+ INITIALIZED,
+ METHOD_TO_TYPES,
+ NOTEBOOK_DOCUMENT_DID_CHANGE,
+ NOTEBOOK_DOCUMENT_DID_CLOSE,
+ NOTEBOOK_DOCUMENT_DID_OPEN,
+ LOG_TRACE,
+ SET_TRACE,
+ SHUTDOWN,
+ TEXT_DOCUMENT_DID_CHANGE,
+ TEXT_DOCUMENT_DID_CLOSE,
+ TEXT_DOCUMENT_DID_OPEN,
+ TEXT_DOCUMENT_PUBLISH_DIAGNOSTICS,
+ WINDOW_LOG_MESSAGE,
+ WINDOW_SHOW_DOCUMENT,
+ WINDOW_SHOW_MESSAGE,
+ WINDOW_WORK_DONE_PROGRESS_CANCEL,
+ WORKSPACE_APPLY_EDIT,
+ WORKSPACE_CONFIGURATION,
+ WORKSPACE_DID_CHANGE_WORKSPACE_FOLDERS,
+ WORKSPACE_EXECUTE_COMMAND,
+ WORKSPACE_SEMANTIC_TOKENS_REFRESH,
+)
+from lsprotocol.types import (
+ ApplyWorkspaceEditParams,
+ Diagnostic,
+ DidChangeNotebookDocumentParams,
+ DidChangeTextDocumentParams,
+ DidChangeWorkspaceFoldersParams,
+ DidCloseNotebookDocumentParams,
+ DidCloseTextDocumentParams,
+ DidOpenNotebookDocumentParams,
+ DidOpenTextDocumentParams,
+ ExecuteCommandParams,
+ InitializeParams,
+ InitializeResult,
+ LogMessageParams,
+ LogTraceParams,
+ MessageType,
+ PublishDiagnosticsParams,
+ RegistrationParams,
+ SetTraceParams,
+ ShowDocumentParams,
+ ShowMessageParams,
+ TraceValues,
+ UnregistrationParams,
+ WorkspaceApplyEditResponse,
+ WorkspaceEdit,
+ InitializeResultServerInfoType,
+ WorkspaceConfigurationParams,
+ WorkDoneProgressCancelParams,
+)
+from pygls.protocol.json_rpc import JsonRPCProtocol
+from pygls.protocol.lsp_meta import LSPMeta
+from pygls.uris import from_fs_path
+from pygls.workspace import Workspace
+
+
+F = TypeVar("F", bound=Callable)
+
+logger = logging.getLogger(__name__)
+
+
+def lsp_method(method_name: str) -> Callable[[F], F]:
+ def decorator(f: F) -> F:
+ f.method_name = method_name # type: ignore[attr-defined]
+ return f
+
+ return decorator
+
+
+class LanguageServerProtocol(JsonRPCProtocol, metaclass=LSPMeta):
+ """A class that represents language server protocol.
+
+ It contains implementations for generic LSP features.
+
+ Attributes:
+ workspace(Workspace): In memory workspace
+ """
+
+ def __init__(self, server, converter):
+ super().__init__(server, converter)
+
+ self._workspace: Optional[Workspace] = None
+ self.trace = None
+
+ from pygls.progress import Progress
+
+ self.progress = Progress(self)
+
+ self.server_info = InitializeResultServerInfoType(
+ name=server.name,
+ version=server.version,
+ )
+
+ self._register_builtin_features()
+
+ def _register_builtin_features(self):
+ """Registers generic LSP features from this class."""
+ for name in dir(self):
+ if name in {"workspace"}:
+ continue
+
+ attr = getattr(self, name)
+ if callable(attr) and hasattr(attr, "method_name"):
+ self.fm.add_builtin_feature(attr.method_name, attr)
+
+ @property
+ def workspace(self) -> Workspace:
+ if self._workspace is None:
+ raise RuntimeError(
+ "The workspace is not available - has the server been initialized?"
+ )
+
+ return self._workspace
+
+ @lru_cache()
+ def get_message_type(self, method: str) -> Optional[Type]:
+ """Return LSP type definitions, as provided by `lsprotocol`"""
+ return METHOD_TO_TYPES.get(method, (None,))[0]
+
+ @lru_cache()
+ def get_result_type(self, method: str) -> Optional[Type]:
+ return METHOD_TO_TYPES.get(method, (None, None))[1]
+
+ def apply_edit(
+ self, edit: WorkspaceEdit, label: Optional[str] = None
+ ) -> WorkspaceApplyEditResponse:
+ """Sends apply edit request to the client."""
+ return self.send_request(
+ WORKSPACE_APPLY_EDIT, ApplyWorkspaceEditParams(edit=edit, label=label)
+ )
+
+ def apply_edit_async(
+ self, edit: WorkspaceEdit, label: Optional[str] = None
+ ) -> WorkspaceApplyEditResponse:
+ """Sends apply edit request to the client. Should be called with `await`"""
+ return self.send_request_async(
+ WORKSPACE_APPLY_EDIT, ApplyWorkspaceEditParams(edit=edit, label=label)
+ )
+
+ @lsp_method(EXIT)
+ def lsp_exit(self, *args) -> None:
+ """Stops the server process."""
+ if self.transport is not None:
+ self.transport.close()
+
+ sys.exit(0 if self._shutdown else 1)
+
+ @lsp_method(INITIALIZE)
+ def lsp_initialize(self, params: InitializeParams) -> InitializeResult:
+ """Method that initializes language server.
+ It will compute and return server capabilities based on
+ registered features.
+ """
+ logger.info("Language server initialized %s", params)
+
+ self._server.process_id = params.process_id
+
+ text_document_sync_kind = self._server._text_document_sync_kind
+ notebook_document_sync = self._server._notebook_document_sync
+
+ # Initialize server capabilities
+ self.client_capabilities = params.capabilities
+ self.server_capabilities = ServerCapabilitiesBuilder(
+ self.client_capabilities,
+ set({**self.fm.features, **self.fm.builtin_features}.keys()),
+ self.fm.feature_options,
+ list(self.fm.commands.keys()),
+ text_document_sync_kind,
+ notebook_document_sync,
+ ).build()
+ logger.debug(
+ "Server capabilities: %s",
+ json.dumps(self.server_capabilities, default=self._serialize_message),
+ )
+
+ root_path = params.root_path
+ root_uri = params.root_uri
+ if root_path is not None and root_uri is None:
+ root_uri = from_fs_path(root_path)
+
+ # Initialize the workspace
+ workspace_folders = params.workspace_folders or []
+ self._workspace = Workspace(
+ root_uri,
+ text_document_sync_kind,
+ workspace_folders,
+ self.server_capabilities.position_encoding,
+ )
+
+ self.trace = TraceValues.Off
+
+ return InitializeResult(
+ capabilities=self.server_capabilities,
+ server_info=self.server_info,
+ )
+
+ @lsp_method(INITIALIZED)
+ def lsp_initialized(self, *args) -> None:
+ """Notification received when client and server are connected."""
+ pass
+
+ @lsp_method(SHUTDOWN)
+ def lsp_shutdown(self, *args) -> None:
+ """Request from client which asks server to shutdown."""
+ for future in self._request_futures.values():
+ future.cancel()
+
+ self._shutdown = True
+ return None
+
+ @lsp_method(TEXT_DOCUMENT_DID_CHANGE)
+ def lsp_text_document__did_change(
+ self, params: DidChangeTextDocumentParams
+ ) -> None:
+ """Updates document's content.
+ (Incremental(from server capabilities); not configurable for now)
+ """
+ for change in params.content_changes:
+ self.workspace.update_text_document(params.text_document, change)
+
+ @lsp_method(TEXT_DOCUMENT_DID_CLOSE)
+ def lsp_text_document__did_close(self, params: DidCloseTextDocumentParams) -> None:
+ """Removes document from workspace."""
+ self.workspace.remove_text_document(params.text_document.uri)
+
+ @lsp_method(TEXT_DOCUMENT_DID_OPEN)
+ def lsp_text_document__did_open(self, params: DidOpenTextDocumentParams) -> None:
+ """Puts document to the workspace."""
+ self.workspace.put_text_document(params.text_document)
+
+ @lsp_method(NOTEBOOK_DOCUMENT_DID_OPEN)
+ def lsp_notebook_document__did_open(
+ self, params: DidOpenNotebookDocumentParams
+ ) -> None:
+ """Put a notebook document into the workspace"""
+ self.workspace.put_notebook_document(params)
+
+ @lsp_method(NOTEBOOK_DOCUMENT_DID_CHANGE)
+ def lsp_notebook_document__did_change(
+ self, params: DidChangeNotebookDocumentParams
+ ) -> None:
+ """Update a notebook's contents"""
+ self.workspace.update_notebook_document(params)
+
+ @lsp_method(NOTEBOOK_DOCUMENT_DID_CLOSE)
+ def lsp_notebook_document__did_close(
+ self, params: DidCloseNotebookDocumentParams
+ ) -> None:
+ """Remove a notebook document from the workspace."""
+ self.workspace.remove_notebook_document(params)
+
+ @lsp_method(SET_TRACE)
+ def lsp_set_trace(self, params: SetTraceParams) -> None:
+ """Changes server trace value."""
+ self.trace = params.value
+
+ @lsp_method(WORKSPACE_DID_CHANGE_WORKSPACE_FOLDERS)
+ def lsp_workspace__did_change_workspace_folders(
+ self, params: DidChangeWorkspaceFoldersParams
+ ) -> None:
+ """Adds/Removes folders from the workspace."""
+ logger.info("Workspace folders changed: %s", params)
+
+ added_folders = params.event.added or []
+ removed_folders = params.event.removed or []
+
+ for f_add, f_remove in zip_longest(added_folders, removed_folders):
+ if f_add:
+ self.workspace.add_folder(f_add)
+ if f_remove:
+ self.workspace.remove_folder(f_remove.uri)
+
+ @lsp_method(WORKSPACE_EXECUTE_COMMAND)
+ def lsp_workspace__execute_command(
+ self, params: ExecuteCommandParams, msg_id: str
+ ) -> None:
+ """Executes commands with passed arguments and returns a value."""
+ cmd_handler = self.fm.commands[params.command]
+ self._execute_request(msg_id, cmd_handler, params.arguments)
+
+ @lsp_method(WINDOW_WORK_DONE_PROGRESS_CANCEL)
+ def lsp_work_done_progress_cancel(
+ self, params: WorkDoneProgressCancelParams
+ ) -> None:
+ """Received a progress cancellation from client."""
+ future = self.progress.tokens.get(params.token)
+ if future is None:
+ logger.warning(
+ "Ignoring work done progress cancel for unknown token %s", params.token
+ )
+ else:
+ future.cancel()
+
+ def get_configuration(
+ self,
+ params: WorkspaceConfigurationParams,
+ callback: Optional[ConfigCallbackType] = None,
+ ) -> Future:
+ """Sends configuration request to the client.
+
+ Args:
+ params(WorkspaceConfigurationParams): WorkspaceConfigurationParams from lsp specs
+ callback(callable): Callabe which will be called after
+ response from the client is received
+ Returns:
+ concurrent.futures.Future object that will be resolved once a
+ response has been received
+ """
+ return self.send_request(WORKSPACE_CONFIGURATION, params, callback)
+
+ def get_configuration_async(
+ self, params: WorkspaceConfigurationParams
+ ) -> asyncio.Future:
+ """Calls `get_configuration` method but designed to use with coroutines
+
+ Args:
+ params(WorkspaceConfigurationParams): WorkspaceConfigurationParams from lsp specs
+ Returns:
+ asyncio.Future that can be awaited
+ """
+ return asyncio.wrap_future(self.get_configuration(params))
+
+ def log_trace(self, message: str, verbose: Optional[str] = None) -> None:
+ """Sends trace notification to the client."""
+ if self.trace == TraceValues.Off:
+ return
+
+ params = LogTraceParams(message=message)
+ if verbose and self.trace == TraceValues.Verbose:
+ params.verbose = verbose
+
+ self.notify(LOG_TRACE, params)
+
+ def _publish_diagnostics_deprecator(
+ self,
+ params_or_uri: Union[str, PublishDiagnosticsParams],
+ diagnostics: Optional[List[Diagnostic]],
+ version: Optional[int],
+ **kwargs,
+ ) -> PublishDiagnosticsParams:
+ if isinstance(params_or_uri, str):
+ message = "DEPRECATION: "
+ "`publish_diagnostics("
+ "self, doc_uri: str, diagnostics: List[Diagnostic], version: Optional[int] = None)`"
+ "will be replaced with `publish_diagnostics(self, params: PublishDiagnosticsParams)`"
+ logging.warning(message)
+
+ params = self._construct_publish_diagnostic_type(
+ params_or_uri, diagnostics, version, **kwargs
+ )
+ else:
+ params = params_or_uri
+ return params
+
+ def _construct_publish_diagnostic_type(
+ self,
+ uri: str,
+ diagnostics: Optional[List[Diagnostic]],
+ version: Optional[int],
+ **kwargs,
+ ) -> PublishDiagnosticsParams:
+ if diagnostics is None:
+ diagnostics = []
+
+ args = {
+ **{"uri": uri, "diagnostics": diagnostics, "version": version},
+ **kwargs,
+ }
+
+ params = PublishDiagnosticsParams(**args) # type:ignore
+ return params
+
+ def publish_diagnostics(
+ self,
+ params_or_uri: Union[str, PublishDiagnosticsParams],
+ diagnostics: Optional[List[Diagnostic]] = None,
+ version: Optional[int] = None,
+ **kwargs,
+ ):
+ """Sends diagnostic notification to the client.
+
+ .. deprecated:: 1.0.1
+
+ Passing ``(uri, diagnostics, version)`` as arguments is deprecated.
+ Pass an instance of :class:`~lsprotocol.types.PublishDiagnosticParams`
+ instead.
+
+ Parameters
+ ----------
+ params_or_uri
+ The :class:`~lsprotocol.types.PublishDiagnosticParams` to send to the client.
+
+ diagnostics
+ *Deprecated*. The diagnostics to publish
+
+ version
+ *Deprecated*: The version number
+ """
+ params = self._publish_diagnostics_deprecator(
+ params_or_uri, diagnostics, version, **kwargs
+ )
+ self.notify(TEXT_DOCUMENT_PUBLISH_DIAGNOSTICS, params)
+
+ def register_capability(
+ self, params: RegistrationParams, callback: Optional[Callable[[], None]] = None
+ ) -> Future:
+ """Register a new capability on the client.
+
+ Args:
+ params(RegistrationParams): RegistrationParams from lsp specs
+ callback(callable): Callabe which will be called after
+ response from the client is received
+ Returns:
+ concurrent.futures.Future object that will be resolved once a
+ response has been received
+ """
+ return self.send_request(CLIENT_REGISTER_CAPABILITY, params, callback)
+
+ def register_capability_async(self, params: RegistrationParams) -> asyncio.Future:
+ """Register a new capability on the client.
+
+ Args:
+ params(RegistrationParams): RegistrationParams from lsp specs
+
+ Returns:
+ asyncio.Future object that will be resolved once a
+ response has been received
+ """
+ return asyncio.wrap_future(self.register_capability(params, None))
+
+ def semantic_tokens_refresh(
+ self, callback: Optional[Callable[[], None]] = None
+ ) -> Future:
+ """Requesting a refresh of all semantic tokens.
+
+ Args:
+ callback(callable): Callabe which will be called after
+ response from the client is received
+
+ Returns:
+ concurrent.futures.Future object that will be resolved once a
+ response has been received
+ """
+ return self.send_request(WORKSPACE_SEMANTIC_TOKENS_REFRESH, callback=callback)
+
+ def semantic_tokens_refresh_async(self) -> asyncio.Future:
+ """Requesting a refresh of all semantic tokens.
+
+ Returns:
+ asyncio.Future object that will be resolved once a
+ response has been received
+ """
+ return asyncio.wrap_future(self.semantic_tokens_refresh(None))
+
+ def show_document(
+ self,
+ params: ShowDocumentParams,
+ callback: Optional[ShowDocumentCallbackType] = None,
+ ) -> Future:
+ """Display a particular document in the user interface.
+
+ Args:
+ params(ShowDocumentParams): ShowDocumentParams from lsp specs
+ callback(callable): Callabe which will be called after
+ response from the client is received
+
+ Returns:
+ concurrent.futures.Future object that will be resolved once a
+ response has been received
+ """
+ return self.send_request(WINDOW_SHOW_DOCUMENT, params, callback)
+
+ def show_document_async(self, params: ShowDocumentParams) -> asyncio.Future:
+ """Display a particular document in the user interface.
+
+ Args:
+ params(ShowDocumentParams): ShowDocumentParams from lsp specs
+
+ Returns:
+ asyncio.Future object that will be resolved once a
+ response has been received
+ """
+ return asyncio.wrap_future(self.show_document(params, None))
+
+ def show_message(self, message, msg_type=MessageType.Info):
+ """Sends message to the client to display message."""
+ self.notify(
+ WINDOW_SHOW_MESSAGE, ShowMessageParams(type=msg_type, message=message)
+ )
+
+ def show_message_log(self, message, msg_type=MessageType.Log):
+ """Sends message to the client's output channel."""
+ self.notify(
+ WINDOW_LOG_MESSAGE, LogMessageParams(type=msg_type, message=message)
+ )
+
+ def unregister_capability(
+ self,
+ params: UnregistrationParams,
+ callback: Optional[Callable[[], None]] = None,
+ ) -> Future:
+ """Unregister a new capability on the client.
+
+ Args:
+ params(UnregistrationParams): UnregistrationParams from lsp specs
+ callback(callable): Callabe which will be called after
+ response from the client is received
+ Returns:
+ concurrent.futures.Future object that will be resolved once a
+ response has been received
+ """
+ return self.send_request(CLIENT_UNREGISTER_CAPABILITY, params, callback)
+
+ def unregister_capability_async(
+ self, params: UnregistrationParams
+ ) -> asyncio.Future:
+ """Unregister a new capability on the client.
+
+ Args:
+ params(UnregistrationParams): UnregistrationParams from lsp specs
+ callback(callable): Callabe which will be called after
+ response from the client is received
+ Returns:
+ asyncio.Future object that will be resolved once a
+ response has been received
+ """
+ return asyncio.wrap_future(self.unregister_capability(params, None))
diff --git a/pygls/protocol/lsp_meta.py b/pygls/protocol/lsp_meta.py
new file mode 100644
index 0000000..0dc52db
--- /dev/null
+++ b/pygls/protocol/lsp_meta.py
@@ -0,0 +1,51 @@
+import functools
+import logging
+from pygls.constants import ATTR_FEATURE_TYPE
+from pygls.feature_manager import assign_help_attrs
+
+
+logger = logging.getLogger(__name__)
+
+
+def call_user_feature(base_func, method_name):
+ """Wraps generic LSP features and calls user registered feature
+ immediately after it.
+ """
+
+ @functools.wraps(base_func)
+ def decorator(self, *args, **kwargs):
+ ret_val = base_func(self, *args, **kwargs)
+
+ try:
+ user_func = self.fm.features[method_name]
+ self._execute_notification(user_func, *args, **kwargs)
+ except KeyError:
+ pass
+ except Exception:
+ logger.exception(
+ 'Failed to handle user defined notification "%s": %s', method_name, args
+ )
+
+ return ret_val
+
+ return decorator
+
+
+class LSPMeta(type):
+ """Wraps LSP built-in features (`lsp_` naming convention).
+
+ Built-in features cannot be overridden but user defined features with
+ the same LSP name will be called after them.
+ """
+
+ def __new__(mcs, cls_name, cls_bases, cls):
+ for attr_name, attr_val in cls.items():
+ if callable(attr_val) and hasattr(attr_val, "method_name"):
+ method_name = attr_val.method_name
+ wrapped = call_user_feature(attr_val, method_name)
+ assign_help_attrs(wrapped, method_name, ATTR_FEATURE_TYPE)
+ cls[attr_name] = wrapped
+
+ logger.debug('Added decorator for lsp method: "%s"', attr_name)
+
+ return super().__new__(mcs, cls_name, cls_bases, cls)
diff --git a/pygls/py.typed b/pygls/py.typed
new file mode 100644
index 0000000..a9beb24
--- /dev/null
+++ b/pygls/py.typed
@@ -0,0 +1,2 @@
+# Marker file for PEP 561. The pygls package uses inline types.
+
diff --git a/pygls/server.py b/pygls/server.py
new file mode 100644
index 0000000..7717b84
--- /dev/null
+++ b/pygls/server.py
@@ -0,0 +1,616 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import asyncio
+import json
+import logging
+import re
+import sys
+from concurrent.futures import Future, ThreadPoolExecutor
+from threading import Event
+from typing import (
+ Any,
+ Callable,
+ List,
+ Optional,
+ TextIO,
+ Type,
+ TypeVar,
+ Union,
+)
+
+import cattrs
+from pygls import IS_PYODIDE
+from pygls.lsp import ConfigCallbackType, ShowDocumentCallbackType
+from pygls.exceptions import (
+ FeatureNotificationError,
+ JsonRpcInternalError,
+ PyglsError,
+ JsonRpcException,
+ FeatureRequestError,
+)
+from lsprotocol.types import (
+ ClientCapabilities,
+ Diagnostic,
+ MessageType,
+ NotebookDocumentSyncOptions,
+ RegistrationParams,
+ ServerCapabilities,
+ ShowDocumentParams,
+ TextDocumentSyncKind,
+ UnregistrationParams,
+ WorkspaceApplyEditResponse,
+ WorkspaceEdit,
+ WorkspaceConfigurationParams,
+)
+from pygls.progress import Progress
+from pygls.protocol import JsonRPCProtocol, LanguageServerProtocol, default_converter
+from pygls.workspace import Workspace
+
+if not IS_PYODIDE:
+ from multiprocessing.pool import ThreadPool
+
+
+logger = logging.getLogger(__name__)
+
+F = TypeVar("F", bound=Callable)
+
+ServerErrors = Union[
+ PyglsError,
+ JsonRpcException,
+ Type[JsonRpcInternalError],
+ Type[FeatureNotificationError],
+ Type[FeatureRequestError],
+]
+
+
+async def aio_readline(loop, executor, stop_event, rfile, proxy):
+ """Reads data from stdin in separate thread (asynchronously)."""
+
+ CONTENT_LENGTH_PATTERN = re.compile(rb"^Content-Length: (\d+)\r\n$")
+
+ # Initialize message buffer
+ message = []
+ content_length = 0
+
+ while not stop_event.is_set() and not rfile.closed:
+ # Read a header line
+ header = await loop.run_in_executor(executor, rfile.readline)
+ if not header:
+ break
+ message.append(header)
+
+ # Extract content length if possible
+ if not content_length:
+ match = CONTENT_LENGTH_PATTERN.fullmatch(header)
+ if match:
+ content_length = int(match.group(1))
+ logger.debug("Content length: %s", content_length)
+
+ # Check if all headers have been read (as indicated by an empty line \r\n)
+ if content_length and not header.strip():
+ # Read body
+ body = await loop.run_in_executor(executor, rfile.read, content_length)
+ if not body:
+ break
+ message.append(body)
+
+ # Pass message to language server protocol
+ proxy(b"".join(message))
+
+ # Reset the buffer
+ message = []
+ content_length = 0
+
+
+class StdOutTransportAdapter:
+ """Protocol adapter which overrides write method.
+
+ Write method sends data to stdout.
+ """
+
+ def __init__(self, rfile, wfile):
+ self.rfile = rfile
+ self.wfile = wfile
+
+ def close(self):
+ self.rfile.close()
+ self.wfile.close()
+
+ def write(self, data):
+ self.wfile.write(data)
+ self.wfile.flush()
+
+
+class PyodideTransportAdapter:
+ """Protocol adapter which overrides write method.
+
+ Write method sends data to stdout.
+ """
+
+ def __init__(self, wfile):
+ self.wfile = wfile
+
+ def close(self):
+ self.wfile.close()
+
+ def write(self, data):
+ self.wfile.write(data)
+ self.wfile.flush()
+
+
+class WebSocketTransportAdapter:
+ """Protocol adapter which calls write method.
+
+ Write method sends data via the WebSocket interface.
+ """
+
+ def __init__(self, ws, loop):
+ self._ws = ws
+ self._loop = loop
+
+ def close(self) -> None:
+ """Stop the WebSocket server."""
+ self._ws.close()
+
+ def write(self, data: Any) -> None:
+ """Create a task to write specified data into a WebSocket."""
+ asyncio.ensure_future(self._ws.send(data))
+
+
+class Server:
+ """Base server class
+
+ Parameters
+ ----------
+ protocol_cls
+ Protocol implementation that must be derive from :class:`~pygls.protocol.JsonRPCProtocol`
+
+ converter_factory
+ Factory function to use when constructing a cattrs converter.
+
+ loop
+ The asyncio event loop
+
+ max_workers
+ Maximum number of workers for `ThreadPool` and `ThreadPoolExecutor`
+
+ """
+
+ def __init__(
+ self,
+ protocol_cls: Type[JsonRPCProtocol],
+ converter_factory: Callable[[], cattrs.Converter],
+ loop: Optional[asyncio.AbstractEventLoop] = None,
+ max_workers: int = 2,
+ sync_kind: TextDocumentSyncKind = TextDocumentSyncKind.Incremental,
+ ):
+ if not issubclass(protocol_cls, asyncio.Protocol):
+ raise TypeError("Protocol class should be subclass of asyncio.Protocol")
+
+ self._max_workers = max_workers
+ self._server = None
+ self._stop_event: Optional[Event] = None
+ self._thread_pool: Optional[ThreadPool] = None
+ self._thread_pool_executor: Optional[ThreadPoolExecutor] = None
+
+ if sync_kind is not None:
+ self.text_document_sync_kind = sync_kind
+
+ if loop is None:
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+ self._owns_loop = True
+ else:
+ self._owns_loop = False
+
+ self.loop = loop
+
+ # TODO: Will move this to `LanguageServer` soon
+ self.lsp = protocol_cls(self, converter_factory()) # type: ignore
+
+ def shutdown(self):
+ """Shutdown server."""
+ logger.info("Shutting down the server")
+
+ if self._stop_event is not None:
+ self._stop_event.set()
+
+ if self._thread_pool:
+ self._thread_pool.terminate()
+ self._thread_pool.join()
+
+ if self._thread_pool_executor:
+ self._thread_pool_executor.shutdown()
+
+ if self._server:
+ self._server.close()
+ self.loop.run_until_complete(self._server.wait_closed())
+
+ if self._owns_loop and not self.loop.is_closed():
+ logger.info("Closing the event loop.")
+ self.loop.close()
+
+ def start_io(self, stdin: Optional[TextIO] = None, stdout: Optional[TextIO] = None):
+ """Starts IO server."""
+ logger.info("Starting IO server")
+
+ self._stop_event = Event()
+ transport = StdOutTransportAdapter(
+ stdin or sys.stdin.buffer, stdout or sys.stdout.buffer
+ )
+ self.lsp.connection_made(transport) # type: ignore[arg-type]
+
+ try:
+ self.loop.run_until_complete(
+ aio_readline(
+ self.loop,
+ self.thread_pool_executor,
+ self._stop_event,
+ stdin or sys.stdin.buffer,
+ self.lsp.data_received,
+ )
+ )
+ except BrokenPipeError:
+ logger.error("Connection to the client is lost! Shutting down the server.")
+ except (KeyboardInterrupt, SystemExit):
+ pass
+ finally:
+ self.shutdown()
+
+ def start_pyodide(self):
+ logger.info("Starting Pyodide server")
+
+ # Note: We don't actually start anything running as the main event
+ # loop will be handled by the web platform.
+ transport = PyodideTransportAdapter(sys.stdout)
+ self.lsp.connection_made(transport) # type: ignore[arg-type]
+ self.lsp._send_only_body = True # Don't send headers within the payload
+
+ def start_tcp(self, host: str, port: int) -> None:
+ """Starts TCP server."""
+ logger.info("Starting TCP server on %s:%s", host, port)
+
+ self._stop_event = Event()
+ self._server = self.loop.run_until_complete( # type: ignore[assignment]
+ self.loop.create_server(self.lsp, host, port)
+ )
+ try:
+ self.loop.run_forever()
+ except (KeyboardInterrupt, SystemExit):
+ pass
+ finally:
+ self.shutdown()
+
+ def start_ws(self, host: str, port: int) -> None:
+ """Starts WebSocket server."""
+ try:
+ from websockets.server import serve
+ except ImportError:
+ logger.error("Run `pip install pygls[ws]` to install `websockets`.")
+ sys.exit(1)
+
+ logger.info("Starting WebSocket server on {}:{}".format(host, port))
+
+ self._stop_event = Event()
+ self.lsp._send_only_body = True # Don't send headers within the payload
+
+ async def connection_made(websocket, _):
+ """Handle new connection wrapped in the WebSocket."""
+ self.lsp.transport = WebSocketTransportAdapter(websocket, self.loop)
+ async for message in websocket:
+ self.lsp._procedure_handler(
+ json.loads(message, object_hook=self.lsp._deserialize_message)
+ )
+
+ start_server = serve(connection_made, host, port, loop=self.loop)
+ self._server = start_server.ws_server # type: ignore[assignment]
+ self.loop.run_until_complete(start_server)
+
+ try:
+ self.loop.run_forever()
+ except (KeyboardInterrupt, SystemExit):
+ pass
+ finally:
+ self._stop_event.set()
+ self.shutdown()
+
+ if not IS_PYODIDE:
+
+ @property
+ def thread_pool(self) -> ThreadPool:
+ """Returns thread pool instance (lazy initialization)."""
+ if not self._thread_pool:
+ self._thread_pool = ThreadPool(processes=self._max_workers)
+
+ return self._thread_pool
+
+ @property
+ def thread_pool_executor(self) -> ThreadPoolExecutor:
+ """Returns thread pool instance (lazy initialization)."""
+ if not self._thread_pool_executor:
+ self._thread_pool_executor = ThreadPoolExecutor(
+ max_workers=self._max_workers
+ )
+
+ return self._thread_pool_executor
+
+
+class LanguageServer(Server):
+ """The default LanguageServer
+
+ This class can be extended and it can be passed as a first argument to
+ registered commands/features.
+
+ .. |ServerInfo| replace:: :class:`~lsprotocol.types.InitializeResultServerInfoType`
+
+ Parameters
+ ----------
+ name
+ Name of the server, used to populate |ServerInfo| which is sent to
+ the client during initialization
+
+ version
+ Version of the server, used to populate |ServerInfo| which is sent to
+ the client during initialization
+
+ protocol_cls
+ The :class:`~pygls.protocol.LanguageServerProtocol` class definition, or any
+ subclass of it.
+
+ max_workers
+ Maximum number of workers for ``ThreadPool`` and ``ThreadPoolExecutor``
+
+ text_document_sync_kind
+ Text document synchronization method
+
+ None
+ No synchronization
+
+ :attr:`~lsprotocol.types.TextDocumentSyncKind.Full`
+ Send entire document text with each update
+
+ :attr:`~lsprotocol.types.TextDocumentSyncKind.Incremental`
+ Send only the region of text that changed with each update
+
+ notebook_document_sync
+ Advertise :lsp:`NotebookDocument` support to the client.
+ """
+
+ lsp: LanguageServerProtocol
+
+ default_error_message = (
+ "Unexpected error in LSP server, see server's logs for details"
+ )
+ """
+ The default error message sent to the user's editor when this server encounters an uncaught
+ exception.
+ """
+
+ def __init__(
+ self,
+ name: str,
+ version: str,
+ loop=None,
+ protocol_cls: Type[LanguageServerProtocol] = LanguageServerProtocol,
+ converter_factory=default_converter,
+ text_document_sync_kind: TextDocumentSyncKind = TextDocumentSyncKind.Incremental,
+ notebook_document_sync: Optional[NotebookDocumentSyncOptions] = None,
+ max_workers: int = 2,
+ ):
+ if not issubclass(protocol_cls, LanguageServerProtocol):
+ raise TypeError(
+ "Protocol class should be subclass of LanguageServerProtocol"
+ )
+
+ self.name = name
+ self.version = version
+ self._text_document_sync_kind = text_document_sync_kind
+ self._notebook_document_sync = notebook_document_sync
+ self.process_id: Optional[Union[int, None]] = None
+ super().__init__(protocol_cls, converter_factory, loop, max_workers)
+
+ def apply_edit(
+ self, edit: WorkspaceEdit, label: Optional[str] = None
+ ) -> WorkspaceApplyEditResponse:
+ """Sends apply edit request to the client."""
+ return self.lsp.apply_edit(edit, label)
+
+ def apply_edit_async(
+ self, edit: WorkspaceEdit, label: Optional[str] = None
+ ) -> WorkspaceApplyEditResponse:
+ """Sends apply edit request to the client. Should be called with `await`"""
+ return self.lsp.apply_edit_async(edit, label)
+
+ def command(self, command_name: str) -> Callable[[F], F]:
+ """Decorator used to register custom commands.
+
+ Example
+ -------
+ ::
+
+ @ls.command('myCustomCommand')
+ def my_cmd(ls, a, b, c):
+ pass
+ """
+ return self.lsp.fm.command(command_name)
+
+ @property
+ def client_capabilities(self) -> ClientCapabilities:
+ """The client's capabilities."""
+ return self.lsp.client_capabilities
+
+ def feature(
+ self,
+ feature_name: str,
+ options: Optional[Any] = None,
+ ) -> Callable[[F], F]:
+ """Decorator used to register LSP features.
+
+ Example
+ -------
+ ::
+
+ @ls.feature('textDocument/completion', CompletionOptions(trigger_characters=['.']))
+ def completions(ls, params: CompletionParams):
+ return CompletionList(is_incomplete=False, items=[CompletionItem("Completion 1")])
+ """
+ return self.lsp.fm.feature(feature_name, options)
+
+ def get_configuration(
+ self,
+ params: WorkspaceConfigurationParams,
+ callback: Optional[ConfigCallbackType] = None,
+ ) -> Future:
+ """Gets the configuration settings from the client."""
+ return self.lsp.get_configuration(params, callback)
+
+ def get_configuration_async(
+ self, params: WorkspaceConfigurationParams
+ ) -> asyncio.Future:
+ """Gets the configuration settings from the client. Should be called with `await`"""
+ return self.lsp.get_configuration_async(params)
+
+ def log_trace(self, message: str, verbose: Optional[str] = None) -> None:
+ """Sends trace notification to the client."""
+ self.lsp.log_trace(message, verbose)
+
+ @property
+ def progress(self) -> Progress:
+ """Gets the object to manage client's progress bar."""
+ return self.lsp.progress
+
+ def publish_diagnostics(
+ self,
+ uri: str,
+ diagnostics: Optional[List[Diagnostic]] = None,
+ version: Optional[int] = None,
+ **kwargs
+ ):
+ """
+ Sends diagnostic notification to the client.
+ """
+ params = self.lsp._construct_publish_diagnostic_type(
+ uri, diagnostics, version, **kwargs
+ )
+ self.lsp.publish_diagnostics(params, **kwargs)
+
+ def register_capability(
+ self, params: RegistrationParams, callback: Optional[Callable[[], None]] = None
+ ) -> Future:
+ """Register a new capability on the client."""
+ return self.lsp.register_capability(params, callback)
+
+ def register_capability_async(self, params: RegistrationParams) -> asyncio.Future:
+ """Register a new capability on the client. Should be called with `await`"""
+ return self.lsp.register_capability_async(params)
+
+ def semantic_tokens_refresh(
+ self, callback: Optional[Callable[[], None]] = None
+ ) -> Future:
+ """Request a refresh of all semantic tokens."""
+ return self.lsp.semantic_tokens_refresh(callback)
+
+ def semantic_tokens_refresh_async(self) -> asyncio.Future:
+ """Request a refresh of all semantic tokens. Should be called with `await`"""
+ return self.lsp.semantic_tokens_refresh_async()
+
+ def send_notification(self, method: str, params: object = None) -> None:
+ """Sends notification to the client."""
+ self.lsp.notify(method, params)
+
+ @property
+ def server_capabilities(self) -> ServerCapabilities:
+ """Return server capabilities."""
+ return self.lsp.server_capabilities
+
+ def show_document(
+ self,
+ params: ShowDocumentParams,
+ callback: Optional[ShowDocumentCallbackType] = None,
+ ) -> Future:
+ """Display a particular document in the user interface."""
+ return self.lsp.show_document(params, callback)
+
+ def show_document_async(self, params: ShowDocumentParams) -> asyncio.Future:
+ """Display a particular document in the user interface. Should be called with `await`"""
+ return self.lsp.show_document_async(params)
+
+ def show_message(self, message, msg_type=MessageType.Info) -> None:
+ """Sends message to the client to display message."""
+ self.lsp.show_message(message, msg_type)
+
+ def show_message_log(self, message, msg_type=MessageType.Log) -> None:
+ """Sends message to the client's output channel."""
+ self.lsp.show_message_log(message, msg_type)
+
+ def _report_server_error(
+ self,
+ error: Exception,
+ source: ServerErrors,
+ ):
+ # Prevent recursive error reporting
+ try:
+ self.report_server_error(error, source)
+ except Exception:
+ logger.warning("Failed to report error to client")
+
+ def report_server_error(self, error: Exception, source: ServerErrors):
+ """
+ Sends error to the client for displaying.
+
+ By default this fucntion does not handle LSP request errors. This is because LSP requests
+ require direct responses and so already have a mechanism for including unexpected errors
+ in the response body.
+
+ All other errors are "out of band" in the sense that the client isn't explicitly waiting
+ for them. For example diagnostics are returned as notifications, not responses to requests,
+ and so can seemingly be sent at random. Also for example consider JSON RPC serialization
+ and deserialization, if a payload cannot be parsed then the whole request/response cycle
+ cannot be completed and so one of these "out of band" error messages is sent.
+
+ These "out of band" error messages are not a requirement of the LSP spec. Pygls simply
+ offers this behaviour as a recommended default. It is perfectly reasonble to override this
+ default.
+ """
+
+ if source == FeatureRequestError:
+ return
+
+ self.show_message(self.default_error_message, msg_type=MessageType.Error)
+
+ def thread(self) -> Callable[[F], F]:
+ """Decorator that mark function to execute it in a thread."""
+ return self.lsp.thread()
+
+ def unregister_capability(
+ self,
+ params: UnregistrationParams,
+ callback: Optional[Callable[[], None]] = None,
+ ) -> Future:
+ """Unregister a new capability on the client."""
+ return self.lsp.unregister_capability(params, callback)
+
+ def unregister_capability_async(
+ self, params: UnregistrationParams
+ ) -> asyncio.Future:
+ """Unregister a new capability on the client. Should be called with `await`"""
+ return self.lsp.unregister_capability_async(params)
+
+ @property
+ def workspace(self) -> Workspace:
+ """Returns in-memory workspace."""
+ return self.lsp.workspace
diff --git a/pygls/uris.py b/pygls/uris.py
new file mode 100644
index 0000000..8c40f70
--- /dev/null
+++ b/pygls/uris.py
@@ -0,0 +1,184 @@
+############################################################################
+# Original work Copyright 2017 Palantir Technologies, Inc. #
+# Original work licensed under the MIT License. #
+# See ThirdPartyNotices.txt in the project root for license information. #
+# All modifications Copyright (c) Open Law Library. All rights reserved. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+"""
+A collection of URI utilities with logic built on the VSCode URI library.
+
+https://github.com/Microsoft/vscode-uri/blob/e59cab84f5df6265aed18ae5f43552d3eef13bb9/lib/index.ts
+"""
+from typing import Optional, Tuple
+
+import re
+from urllib import parse
+
+from pygls import IS_WIN
+
+RE_DRIVE_LETTER_PATH = re.compile(r"^\/[a-zA-Z]:")
+
+URLParts = Tuple[str, str, str, str, str, str]
+
+
+def _normalize_win_path(path: str):
+ netloc = ""
+
+ # normalize to fwd-slashes on windows,
+ # on other systems bwd-slashes are valid
+ # filename character, eg /f\oo/ba\r.txt
+ if IS_WIN:
+ path = path.replace("\\", "/")
+
+ # check for authority as used in UNC shares
+ # or use the path as given
+ if path[:2] == "//":
+ idx = path.index("/", 2)
+ if idx == -1:
+ netloc = path[2:]
+ else:
+ netloc = path[2:idx]
+ path = path[idx:]
+
+ # Ensure that path starts with a slash
+ # or that it is at least a slash
+ if not path.startswith("/"):
+ path = "/" + path
+
+ # Normalize drive paths to lower case
+ if RE_DRIVE_LETTER_PATH.match(path):
+ path = path[0] + path[1].lower() + path[2:]
+
+ return path, netloc
+
+
+def from_fs_path(path: str):
+ """Returns a URI for the given filesystem path."""
+ try:
+ scheme = "file"
+ params, query, fragment = "", "", ""
+ path, netloc = _normalize_win_path(path)
+ return urlunparse((scheme, netloc, path, params, query, fragment))
+ except (AttributeError, TypeError):
+ return None
+
+
+def to_fs_path(uri: str):
+ """
+ Returns the filesystem path of the given URI.
+
+ Will handle UNC paths and normalize windows drive letters to lower-case.
+ Also uses the platform specific path separator. Will *not* validate the
+ path for invalid characters and semantics.
+ Will *not* look at the scheme of this URI.
+ """
+ try:
+ # scheme://netloc/path;parameters?query#fragment
+ scheme, netloc, path, _, _, _ = urlparse(uri)
+
+ if netloc and path and scheme == "file":
+ # unc path: file://shares/c$/far/boo
+ value = f"//{netloc}{path}"
+
+ elif RE_DRIVE_LETTER_PATH.match(path):
+ # windows drive letter: file:///C:/far/boo
+ value = path[1].lower() + path[2:]
+
+ else:
+ # Other path
+ value = path
+
+ if IS_WIN:
+ value = value.replace("/", "\\")
+
+ return value
+ except TypeError:
+ return None
+
+
+def uri_scheme(uri: str):
+ try:
+ return urlparse(uri)[0]
+ except (TypeError, IndexError):
+ return None
+
+
+# TODO: Use `URLParts` type
+def uri_with(
+ uri: str,
+ scheme: Optional[str] = None,
+ netloc: Optional[str] = None,
+ path: Optional[str] = None,
+ params: Optional[str] = None,
+ query: Optional[str] = None,
+ fragment: Optional[str] = None,
+):
+ """
+ Return a URI with the given part(s) replaced.
+ Parts are decoded / encoded.
+ """
+ old_scheme, old_netloc, old_path, old_params, old_query, old_fragment = urlparse(
+ uri
+ )
+
+ if path is None:
+ raise Exception("`path` must not be None")
+
+ path, _ = _normalize_win_path(path)
+ return urlunparse(
+ (
+ scheme or old_scheme,
+ netloc or old_netloc,
+ path or old_path,
+ params or old_params,
+ query or old_query,
+ fragment or old_fragment,
+ )
+ )
+
+
+def urlparse(uri: str):
+ """Parse and decode the parts of a URI."""
+ scheme, netloc, path, params, query, fragment = parse.urlparse(uri)
+ return (
+ parse.unquote(scheme),
+ parse.unquote(netloc),
+ parse.unquote(path),
+ parse.unquote(params),
+ parse.unquote(query),
+ parse.unquote(fragment),
+ )
+
+
+def urlunparse(parts: URLParts) -> str:
+ """Unparse and encode parts of a URI."""
+ scheme, netloc, path, params, query, fragment = parts
+
+ # Avoid encoding the windows drive letter colon
+ if RE_DRIVE_LETTER_PATH.match(path):
+ quoted_path = path[:3] + parse.quote(path[3:])
+ else:
+ quoted_path = parse.quote(path)
+
+ return parse.urlunparse(
+ (
+ parse.quote(scheme),
+ parse.quote(netloc),
+ quoted_path,
+ parse.quote(params),
+ parse.quote(query),
+ parse.quote(fragment),
+ )
+ )
diff --git a/pygls/workspace/__init__.py b/pygls/workspace/__init__.py
new file mode 100644
index 0000000..53e9b1f
--- /dev/null
+++ b/pygls/workspace/__init__.py
@@ -0,0 +1,97 @@
+from typing import List
+import warnings
+
+from lsprotocol import types
+
+from .workspace import Workspace
+from .text_document import TextDocument
+from .position_codec import PositionCodec
+
+# For backwards compatibility
+Document = TextDocument
+
+
+def utf16_unit_offset(chars: str):
+ warnings.warn(
+ "'utf16_unit_offset' has been deprecated, instead use "
+ "'PositionCodec.utf16_unit_offset' via 'workspace.position_codec' "
+ "or 'text_document.position_codec'",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ _codec = PositionCodec()
+ return _codec.utf16_unit_offset(chars)
+
+
+def utf16_num_units(chars: str):
+ warnings.warn(
+ "'utf16_num_units' has been deprecated, instead use "
+ "'PositionCodec.client_num_units' via 'workspace.position_codec' "
+ "or 'text_document.position_codec'",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ _codec = PositionCodec()
+ return _codec.client_num_units(chars)
+
+
+def position_from_utf16(lines: List[str], position: types.Position):
+ warnings.warn(
+ "'position_from_utf16' has been deprecated, instead use "
+ "'PositionCodec.position_from_client_units' via "
+ "'workspace.position_codec' or 'text_document.position_codec'",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ _codec = PositionCodec()
+ return _codec.position_from_client_units(lines, position)
+
+
+def position_to_utf16(lines: List[str], position: types.Position):
+ warnings.warn(
+ "'position_to_utf16' has been deprecated, instead use "
+ "'PositionCodec.position_to_client_units' via "
+ "'workspace.position_codec' or 'text_document.position_codec'",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ _codec = PositionCodec()
+ return _codec.position_to_client_units(lines, position)
+
+
+def range_from_utf16(lines: List[str], range: types.Range):
+ warnings.warn(
+ "'range_from_utf16' has been deprecated, instead use "
+ "'PositionCodec.range_from_client_units' via "
+ "'workspace.position_codec' or 'text_document.position_codec'",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ _codec = PositionCodec()
+ return _codec.range_from_client_units(lines, range)
+
+
+def range_to_utf16(lines: List[str], range: types.Range):
+ warnings.warn(
+ "'range_to_utf16' has been deprecated, instead use "
+ "'PositionCodec.range_to_client_units' via 'workspace.position_codec' "
+ "or 'text_document.position_codec'",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ _codec = PositionCodec()
+ return _codec.range_to_client_units(lines, range)
+
+
+__all__ = (
+ "Workspace",
+ "TextDocument",
+ "PositionCodec",
+ "Document",
+ "utf16_unit_offset",
+ "utf16_num_units",
+ "position_from_utf16",
+ "position_to_utf16",
+ "range_from_utf16",
+ "range_to_utf16",
+)
diff --git a/pygls/workspace/position_codec.py b/pygls/workspace/position_codec.py
new file mode 100644
index 0000000..b182c62
--- /dev/null
+++ b/pygls/workspace/position_codec.py
@@ -0,0 +1,206 @@
+############################################################################
+# Original work Copyright 2017 Palantir Technologies, Inc. #
+# Original work licensed under the MIT License. #
+# See ThirdPartyNotices.txt in the project root for license information. #
+# All modifications Copyright (c) Open Law Library. All rights reserved. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import logging
+from typing import List, Optional, Union
+
+from lsprotocol import types
+
+
+log = logging.getLogger(__name__)
+
+
+class PositionCodec:
+ def __init__(
+ self,
+ encoding: Optional[
+ Union[types.PositionEncodingKind, str]
+ ] = types.PositionEncodingKind.Utf16,
+ ):
+ self.encoding = encoding
+
+ @classmethod
+ def is_char_beyond_multilingual_plane(cls, char: str) -> bool:
+ return ord(char) > 0xFFFF
+
+ def utf16_unit_offset(self, chars: str):
+ """
+ Calculate the number of characters which need two utf-16 code units.
+
+ Arguments:
+ chars (str): The string to count occurrences of utf-16 code units for.
+ """
+ return sum(self.is_char_beyond_multilingual_plane(ch) for ch in chars)
+
+ def client_num_units(self, chars: str):
+ """
+ Calculate the length of `str` in client-supported UTF-[32|16|8] code units.
+
+ Arguments:
+ chars (str): The string to return the length in UTF-[32|16|8] code units for.
+ """
+ utf32_units = len(chars)
+ if self.encoding == types.PositionEncodingKind.Utf32:
+ return utf32_units
+
+ if self.encoding == types.PositionEncodingKind.Utf8:
+ return utf32_units + (self.utf16_unit_offset(chars) * 2)
+
+ return utf32_units + self.utf16_unit_offset(chars)
+
+ def position_from_client_units(
+ self, lines: List[str], position: types.Position
+ ) -> types.Position:
+ """
+ Convert the position.character from UTF-[32|16|8] code units to UTF-32.
+
+ A python application can't use the character member of `Position`
+ directly. As per specification it is represented as a zero-based line and
+ character offset based on posible a UTF-[32|16|8] string representation.
+
+ All characters whose code point exceeds the Basic Multilingual Plane are
+ represented by 2 UTF-16 or 4 UTF-8 code units.
+
+ The offset of the closing quotation mark in x="😋" is
+ - 7 in UTF-8 representation
+ - 5 in UTF-16 representation
+ - 4 in UTF-32 representation
+
+ see: https://github.com/microsoft/language-server-protocol/issues/376
+
+ Arguments:
+ lines (list):
+ The content of the document which the position refers to.
+ position (Position):
+ The line and character offset in UTF-[32|16|8] code units.
+
+ Returns:
+ The position with `character` being converted to UTF-32 code units.
+ """
+ if len(lines) == 0:
+ return types.Position(0, 0)
+ if position.line >= len(lines):
+ return types.Position(len(lines) - 1, self.client_num_units(lines[-1]))
+
+ _line = lines[position.line]
+ _line = _line.replace("\r\n", "\n") # TODO: it's a bit of a hack
+ _client_len = self.client_num_units(_line)
+ _utf32_len = len(_line)
+
+ if _client_len == 0:
+ return types.Position(position.line, 0)
+
+ _client_end_of_line = self.client_num_units(_line)
+ if position.character > _client_end_of_line:
+ position.character = _client_end_of_line - 1
+
+ _client_index = 0
+ utf32_index = 0
+ while True:
+ _is_searching_queried_position = _client_index < position.character
+ _is_before_end_of_line = utf32_index < _utf32_len
+ _is_searching_for_position = (
+ _is_searching_queried_position and _is_before_end_of_line
+ )
+ if not _is_searching_for_position:
+ break
+
+ _current_char = _line[utf32_index]
+ _is_double_width = PositionCodec.is_char_beyond_multilingual_plane(
+ _current_char
+ )
+ if _is_double_width:
+ if self.encoding == types.PositionEncodingKind.Utf32:
+ _client_index += 1
+ if self.encoding == types.PositionEncodingKind.Utf8:
+ _client_index += 4
+ _client_index += 2
+ else:
+ _client_index += 1
+ utf32_index += 1
+
+ position = types.Position(line=position.line, character=utf32_index)
+ return position
+
+ def position_to_client_units(
+ self, lines: List[str], position: types.Position
+ ) -> types.Position:
+ """
+ Convert the position.character from its internal UTF-32 representation
+ to client-supported UTF-[32|16|8] code units.
+
+ Arguments:
+ lines (list):
+ The content of the document which the position refers to.
+ position (Position):
+ The line and character offset in UTF-32 code units.
+
+ Returns:
+ The position with `character` being converted to UTF-[32|16|8] code units.
+ """
+ try:
+ character = self.client_num_units(
+ lines[position.line][: position.character]
+ )
+ return types.Position(
+ line=position.line,
+ character=character,
+ )
+ except IndexError:
+ return types.Position(line=len(lines), character=0)
+
+ def range_from_client_units(
+ self, lines: List[str], range: types.Range
+ ) -> types.Range:
+ """
+ Convert range.[start|end].character from UTF-[32|16|8] code units to UTF-32.
+
+ Arguments:
+ lines (list):
+ The content of the document which the range refers to.
+ range (Range):
+ The line and character offset in UTF-[32|16|8] code units.
+
+ Returns:
+ The range with `character` offsets being converted to UTF-32 code units.
+ """
+ range_new = types.Range(
+ start=self.position_from_client_units(lines, range.start),
+ end=self.position_from_client_units(lines, range.end),
+ )
+ return range_new
+
+ def range_to_client_units(
+ self, lines: List[str], range: types.Range
+ ) -> types.Range:
+ """
+ Convert range.[start|end].character from UTF-32 to UTF-[32|16|8] code units.
+
+ Arguments:
+ lines (list):
+ The content of the document which the range refers to.
+ range (Range):
+ The line and character offset in code units.
+
+ Returns:
+ The range with `character` offsets being converted to UTF-[32|16|8] code units.
+ """
+ return types.Range(
+ start=self.position_to_client_units(lines, range.start),
+ end=self.position_to_client_units(lines, range.end),
+ )
diff --git a/pygls/workspace/text_document.py b/pygls/workspace/text_document.py
new file mode 100644
index 0000000..d62c6aa
--- /dev/null
+++ b/pygls/workspace/text_document.py
@@ -0,0 +1,238 @@
+############################################################################
+# Original work Copyright 2017 Palantir Technologies, Inc. #
+# Original work licensed under the MIT License. #
+# See ThirdPartyNotices.txt in the project root for license information. #
+# All modifications Copyright (c) Open Law Library. All rights reserved. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import io
+import logging
+import os
+import re
+from typing import List, Optional, Pattern
+
+from lsprotocol import types
+
+from pygls.uris import to_fs_path
+from .position_codec import PositionCodec
+
+# TODO: this is not the best e.g. we capture numbers
+RE_END_WORD = re.compile("^[A-Za-z_0-9]*")
+RE_START_WORD = re.compile("[A-Za-z_0-9]*$")
+
+logger = logging.getLogger(__name__)
+
+
+class TextDocument(object):
+ def __init__(
+ self,
+ uri: str,
+ source: Optional[str] = None,
+ version: Optional[int] = None,
+ language_id: Optional[str] = None,
+ local: bool = True,
+ sync_kind: types.TextDocumentSyncKind = types.TextDocumentSyncKind.Incremental,
+ position_codec: Optional[PositionCodec] = None,
+ ):
+ self.uri = uri
+ self.version = version
+ path = to_fs_path(uri)
+ if path is None:
+ raise Exception("`path` cannot be None")
+ self.path = path
+ self.language_id = language_id
+ self.filename: Optional[str] = os.path.basename(self.path)
+
+ self._local = local
+ self._source = source
+
+ self._is_sync_kind_full = sync_kind == types.TextDocumentSyncKind.Full
+ self._is_sync_kind_incremental = (
+ sync_kind == types.TextDocumentSyncKind.Incremental
+ )
+ self._is_sync_kind_none = sync_kind == types.TextDocumentSyncKind.None_
+
+ self._position_codec = position_codec if position_codec else PositionCodec()
+
+ def __str__(self):
+ return str(self.uri)
+
+ @property
+ def position_codec(self) -> PositionCodec:
+ return self._position_codec
+
+ def _apply_incremental_change(
+ self, change: types.TextDocumentContentChangeEvent_Type1
+ ) -> None:
+ """Apply an ``Incremental`` text change to the document"""
+ lines = self.lines
+ text = change.text
+ change_range = change.range
+
+ range = self._position_codec.range_from_client_units(lines, change_range)
+ start_line = range.start.line
+ start_col = range.start.character
+ end_line = range.end.line
+ end_col = range.end.character
+
+ # Check for an edit occurring at the very end of the file
+ if start_line == len(lines):
+ self._source = self.source + text
+ return
+
+ new = io.StringIO()
+
+ # Iterate over the existing document until we hit the edit range,
+ # at which point we write the new text, then loop until we hit
+ # the end of the range and continue writing.
+ for i, line in enumerate(lines):
+ if i < start_line:
+ new.write(line)
+ continue
+
+ if i > end_line:
+ new.write(line)
+ continue
+
+ if i == start_line:
+ new.write(line[:start_col])
+ new.write(text)
+
+ if i == end_line:
+ new.write(line[end_col:])
+
+ self._source = new.getvalue()
+
+ def _apply_full_change(self, change: types.TextDocumentContentChangeEvent) -> None:
+ """Apply a ``Full`` text change to the document."""
+ self._source = change.text
+
+ def _apply_none_change(self, _: types.TextDocumentContentChangeEvent) -> None:
+ """Apply a ``None`` text change to the document
+
+ Currently does nothing, provided for consistency.
+ """
+ pass
+
+ def apply_change(self, change: types.TextDocumentContentChangeEvent) -> None:
+ """Apply a text change to a document, considering TextDocumentSyncKind
+
+ Performs either
+ :attr:`~lsprotocol.types.TextDocumentSyncKind.Incremental`,
+ :attr:`~lsprotocol.types.TextDocumentSyncKind.Full`, or no synchronization
+ based on both the client request and server capabilities.
+
+ .. admonition:: ``Incremental`` versus ``Full`` synchronization
+
+ Even if a server accepts ``Incremantal`` SyncKinds, clients may request
+ a ``Full`` SyncKind. In LSP 3.x, clients make this request by omitting
+ both Range and RangeLength from their request. Consequently, the
+ attributes "range" and "rangeLength" will be missing from ``Full``
+ content update client requests in the pygls Python library.
+
+ """
+ if isinstance(change, types.TextDocumentContentChangeEvent_Type1):
+ if self._is_sync_kind_incremental:
+ self._apply_incremental_change(change)
+ return
+ # Log an error, but still perform full update to preserve existing
+ # assumptions in test_document/test_document_full_edit. Test breaks
+ # otherwise, and fixing the tests would require a broader fix to
+ # protocol.py.
+ logger.error(
+ "Unsupported client-provided TextDocumentContentChangeEvent. "
+ "Please update / submit a Pull Request to your LSP client."
+ )
+
+ if self._is_sync_kind_none:
+ self._apply_none_change(change)
+ else:
+ self._apply_full_change(change)
+
+ @property
+ def lines(self) -> List[str]:
+ return self.source.splitlines(True)
+
+ def offset_at_position(self, client_position: types.Position) -> int:
+ """Return the character offset pointed at by the given client_position."""
+ lines = self.lines
+ server_position = self._position_codec.position_from_client_units(
+ lines, client_position
+ )
+ row, col = server_position.line, server_position.character
+ return col + sum(
+ self._position_codec.client_num_units(line) for line in lines[:row]
+ )
+
+ @property
+ def source(self) -> str:
+ if self._source is None:
+ with io.open(self.path, "r", encoding="utf-8") as f:
+ return f.read()
+ return self._source
+
+ def word_at_position(
+ self,
+ client_position: types.Position,
+ re_start_word: Pattern[str] = RE_START_WORD,
+ re_end_word: Pattern[str] = RE_END_WORD,
+ ) -> str:
+ """Return the word at position.
+
+ The word is constructed in two halves, the first half is found by taking
+ the first match of ``re_start_word`` on the line up until
+ ``position.character``.
+
+ The second half is found by taking ``position.character`` up until the
+ last match of ``re_end_word`` on the line.
+
+ :func:`python:re.findall` is used to find the matches.
+
+ Parameters
+ ----------
+ position
+ The line and character offset.
+
+ re_start_word
+ The regular expression for extracting the word backward from
+ position. The default pattern is ``[A-Za-z_0-9]*$``.
+
+ re_end_word
+ The regular expression for extracting the word forward from
+ position. The default pattern is ``^[A-Za-z_0-9]*``.
+
+ Returns
+ -------
+ str
+ The word (obtained by concatenating the two matches) at position.
+ """
+ lines = self.lines
+ if client_position.line >= len(lines):
+ return ""
+
+ server_position = self._position_codec.position_from_client_units(
+ lines, client_position
+ )
+ row, col = server_position.line, server_position.character
+ line = lines[row]
+ # Split word in two
+ start = line[:col]
+ end = line[col:]
+
+ # Take end of start and start of end to find word
+ # These are guaranteed to match, even if they match the empty string
+ m_start = re_start_word.findall(start)
+ m_end = re_end_word.findall(end)
+
+ return m_start[0] + m_end[-1]
diff --git a/pygls/workspace/workspace.py b/pygls/workspace/workspace.py
new file mode 100644
index 0000000..405a798
--- /dev/null
+++ b/pygls/workspace/workspace.py
@@ -0,0 +1,323 @@
+############################################################################
+# Original work Copyright 2017 Palantir Technologies, Inc. #
+# Original work licensed under the MIT License. #
+# See ThirdPartyNotices.txt in the project root for license information. #
+# All modifications Copyright (c) Open Law Library. All rights reserved. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import copy
+import logging
+import os
+import warnings
+from typing import Dict, List, Optional, Union
+
+from lsprotocol import types
+from lsprotocol.types import (
+ PositionEncodingKind,
+ TextDocumentSyncKind,
+ WorkspaceFolder,
+)
+from pygls.uris import to_fs_path, uri_scheme
+from pygls.workspace.text_document import TextDocument
+from pygls.workspace.position_codec import PositionCodec
+
+logger = logging.getLogger(__name__)
+
+
+class Workspace(object):
+ def __init__(
+ self,
+ root_uri: Optional[str],
+ sync_kind: TextDocumentSyncKind = TextDocumentSyncKind.Incremental,
+ workspace_folders: Optional[List[WorkspaceFolder]] = None,
+ position_encoding: Optional[
+ Union[PositionEncodingKind, str]
+ ] = PositionEncodingKind.Utf16,
+ ):
+ self._root_uri = root_uri
+ if self._root_uri is not None:
+ self._root_uri_scheme = uri_scheme(self._root_uri)
+ root_path = to_fs_path(self._root_uri)
+ if root_path is None:
+ raise Exception("Couldn't get `root_path` from `root_uri`")
+ self._root_path = root_path
+ else:
+ self._root_path = None
+ self._sync_kind = sync_kind
+ self._text_documents: Dict[str, TextDocument] = {}
+ self._notebook_documents: Dict[str, types.NotebookDocument] = {}
+
+ # Used to lookup notebooks which contain a given cell.
+ self._cell_in_notebook: Dict[str, str] = {}
+ self._folders: Dict[str, WorkspaceFolder] = {}
+ self._docs: Dict[str, TextDocument] = {}
+ self._position_encoding = position_encoding
+ self._position_codec = PositionCodec(encoding=position_encoding)
+
+ if workspace_folders is not None:
+ for folder in workspace_folders:
+ self.add_folder(folder)
+
+ @property
+ def position_encoding(self) -> Optional[Union[PositionEncodingKind, str]]:
+ return self._position_encoding
+
+ @property
+ def position_codec(self) -> PositionCodec:
+ return self._position_codec
+
+ def _create_text_document(
+ self,
+ doc_uri: str,
+ source: Optional[str] = None,
+ version: Optional[int] = None,
+ language_id: Optional[str] = None,
+ ) -> TextDocument:
+ return TextDocument(
+ doc_uri,
+ source=source,
+ version=version,
+ language_id=language_id,
+ sync_kind=self._sync_kind,
+ position_codec=self._position_codec,
+ )
+
+ def add_folder(self, folder: WorkspaceFolder):
+ self._folders[folder.uri] = folder
+
+ @property
+ def documents(self):
+ warnings.warn(
+ "'workspace.documents' has been deprecated, use "
+ "'workspace.text_documents' instead",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.text_documents
+
+ @property
+ def notebook_documents(self):
+ return self._notebook_documents
+
+ @property
+ def text_documents(self):
+ return self._text_documents
+
+ @property
+ def folders(self):
+ return self._folders
+
+ def get_notebook_document(
+ self, *, notebook_uri: Optional[str] = None, cell_uri: Optional[str] = None
+ ) -> Optional[types.NotebookDocument]:
+ """Return the notebook corresponding with the given uri.
+
+ If both ``notebook_uri`` and ``cell_uri`` are given, ``notebook_uri`` takes
+ precedence.
+
+ Parameters
+ ----------
+ notebook_uri
+ If given, return the notebook document with the given uri.
+
+ cell_uri
+ If given, return the notebook document which contains a cell with the
+ given uri
+
+ Returns
+ -------
+ Optional[NotebookDocument]
+ The requested notebook document if found, ``None`` otherwise.
+ """
+ if notebook_uri is not None:
+ return self._notebook_documents.get(notebook_uri)
+
+ if cell_uri is not None:
+ notebook_uri = self._cell_in_notebook.get(cell_uri)
+ if notebook_uri is None:
+ return None
+
+ return self._notebook_documents.get(notebook_uri)
+
+ return None
+
+ def get_text_document(self, doc_uri: str) -> TextDocument:
+ """
+ Return a managed document if-present,
+ else create one pointing at disk.
+
+ See https://github.com/Microsoft/language-server-protocol/issues/177
+ """
+ return self._text_documents.get(doc_uri) or self._create_text_document(doc_uri)
+
+ def is_local(self):
+ return (
+ self._root_uri_scheme == "" or self._root_uri_scheme == "file"
+ ) and os.path.exists(self._root_path)
+
+ def put_notebook_document(self, params: types.DidOpenNotebookDocumentParams):
+ notebook = params.notebook_document
+
+ # Create a fresh instance to ensure our copy cannot be accidentally modified.
+ self._notebook_documents[notebook.uri] = copy.deepcopy(notebook)
+
+ for cell_document in params.cell_text_documents:
+ self.put_text_document(cell_document, notebook_uri=notebook.uri)
+
+ def put_text_document(
+ self,
+ text_document: types.TextDocumentItem,
+ notebook_uri: Optional[str] = None,
+ ):
+ """Add a text document to the workspace.
+
+ Parameters
+ ----------
+ text_document
+ The text document to add
+
+ notebook_uri
+ If set, indicates that this text document represents a cell in a notebook
+ document
+ """
+ doc_uri = text_document.uri
+
+ self._text_documents[doc_uri] = self._create_text_document(
+ doc_uri,
+ source=text_document.text,
+ version=text_document.version,
+ language_id=text_document.language_id,
+ )
+
+ if notebook_uri:
+ self._cell_in_notebook[doc_uri] = notebook_uri
+
+ def remove_notebook_document(self, params: types.DidCloseNotebookDocumentParams):
+ notebook_uri = params.notebook_document.uri
+ self._notebook_documents.pop(notebook_uri, None)
+
+ for cell_document in params.cell_text_documents:
+ self.remove_text_document(cell_document.uri)
+
+ def remove_text_document(self, doc_uri: str):
+ self._text_documents.pop(doc_uri, None)
+ self._cell_in_notebook.pop(doc_uri, None)
+
+ def remove_folder(self, folder_uri: str):
+ self._folders.pop(folder_uri, None)
+ try:
+ del self._folders[folder_uri]
+ except KeyError:
+ pass
+
+ @property
+ def root_path(self):
+ return self._root_path
+
+ @property
+ def root_uri(self):
+ return self._root_uri
+
+ def update_notebook_document(self, params: types.DidChangeNotebookDocumentParams):
+ uri = params.notebook_document.uri
+ notebook = self._notebook_documents[uri]
+ notebook.version = params.notebook_document.version
+
+ if params.change.metadata:
+ notebook.metadata = params.change.metadata
+
+ cell_changes = params.change.cells
+ if cell_changes is None:
+ return
+
+ # Process changes to any cell metadata.
+ nb_cells = {cell.document: cell for cell in notebook.cells}
+ for new_data in cell_changes.data or []:
+ nb_cell = nb_cells.get(new_data.document)
+ if nb_cell is None:
+ logger.warning(
+ "Ignoring metadata for '%s': not in notebook.", new_data.document
+ )
+ continue
+
+ nb_cell.kind = new_data.kind
+ nb_cell.metadata = new_data.metadata
+ nb_cell.execution_summary = new_data.execution_summary
+
+ # Process changes to the notebook's structure
+ structure = cell_changes.structure
+ if structure:
+ cells = notebook.cells
+ new_cells = structure.array.cells or []
+
+ # Re-order the cells
+ before = cells[: structure.array.start]
+ after = cells[(structure.array.start + structure.array.delete_count) :]
+ notebook.cells = [*before, *new_cells, *after]
+
+ for new_cell in structure.did_open or []:
+ self.put_text_document(new_cell, notebook_uri=uri)
+
+ for removed_cell in structure.did_close or []:
+ self.remove_text_document(removed_cell.uri)
+
+ # Process changes to the text content of existing cells.
+ for text in cell_changes.text_content or []:
+ for change in text.changes:
+ self.update_text_document(text.document, change)
+
+ def update_text_document(
+ self,
+ text_doc: types.VersionedTextDocumentIdentifier,
+ change: types.TextDocumentContentChangeEvent,
+ ):
+ doc_uri = text_doc.uri
+ self._text_documents[doc_uri].apply_change(change)
+ self._text_documents[doc_uri].version = text_doc.version
+
+ def get_document(self, *args, **kwargs):
+ warnings.warn(
+ "'workspace.get_document' has been deprecated, use "
+ "'workspace.get_text_document' instead",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.get_text_document(*args, **kwargs)
+
+ def remove_document(self, *args, **kwargs):
+ warnings.warn(
+ "'workspace.remove_document' has been deprecated, use "
+ "'workspace.remove_text_document' instead",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.remove_text_document(*args, **kwargs)
+
+ def put_document(self, *args, **kwargs):
+ warnings.warn(
+ "'workspace.put_document' has been deprecated, use "
+ "'workspace.put_text_document' instead",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.put_text_document(*args, **kwargs)
+
+ def update_document(self, *args, **kwargs):
+ warnings.warn(
+ "'workspace.update_document' has been deprecated, use "
+ "'workspace.update_text_document' instead",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.update_text_document(*args, **kwargs)
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..930ea96
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,114 @@
+[tool.poetry]
+name = "pygls"
+version = "1.3.0"
+description = "A pythonic generic language server (pronounced like 'pie glass')"
+authors = ["Open Law Library <info@openlawlib.org>"]
+maintainers = [
+ "Tom BH <tom@tombh.co.uk>",
+ "Alex Carney <alcarneyme@gmail.com>",
+]
+repository = "https://github.com/openlawlibrary/pygls"
+documentation = "https://pygls.readthedocs.io/en/latest"
+license = "Apache 2.0"
+readme = "README.md"
+
+# You may want to use the Poetry "Up" plugin to automatically update all dependencies to
+# their latest major versions. But bear in mind that this is a library, so the non-development
+# dependency versions will be forced on downstream users. Therefore the very latest versions
+# may be too restrictive.
+# See https://github.com/MousaZeidBaker/poetry-plugin-up
+[tool.poetry.dependencies]
+python = ">=3.8"
+cattrs = ">=23.1.2"
+lsprotocol = "2023.0.1"
+websockets = { version = ">=11.0.3", optional = true }
+
+[tool.poetry.extras]
+ws = ["websockets"]
+
+[tool.poetry.group.dev.dependencies]
+# Replaces (amongst many other things) flake8 and bandit
+ruff = ">=0.1.6"
+# TODO `poethepoet>=0.20` needs python 3.8
+poethepoet = ">=0.24.4"
+mypy = ">=1.7.1"
+# TODO `black>=23` needs python 3.8
+black = ">=23.11.0"
+
+[tool.poetry.group.test.dependencies]
+# Note: `coverage` requires that your Python was built with system `sqlite` development files
+coverage = { version = ">=7.3.2", extras = ["toml"] }
+pytest = ">=7.4.3"
+pytest-asyncio = ">=0.21.0"
+
+[tool.poetry.group.docs.dependencies]
+# TODO `sphinx>=7.26` needs python 3.9
+sphinx = ">=7.1.2"
+sphinx-rtd-theme = ">=1.3.0"
+
+[tool.poetry.group.pyodide.dependencies]
+selenium = "^4.15.2"
+
+[tool.pytest.ini_options]
+asyncio_mode = "auto"
+
+[tool.poe.tasks]
+test-pyodide = "python tests/pyodide_testrunner/run.py"
+ruff = "ruff check ."
+mypy = "mypy -p pygls"
+check_generated_client = "python scripts/check_client_is_uptodate.py"
+check_commit_style = "npx commitlint --from origin/main --to HEAD --verbose --config commitlintrc.yaml"
+generate_client = "python scripts/generate_client.py --output pygls/lsp/client.py"
+generate_contributors_md = "python scripts/generate_contributors_md.py"
+black_check = "black --check ."
+poetry_lock_check = "poetry check"
+
+[tool.poe.tasks.test]
+env = { COVERAGE_PROCESS_START = "pyproject.toml" }
+sequence = [
+ { cmd = "python -c 'import pathlib,sys; pathlib.Path(f\"{sys.path[-1]}/cov.pth\").write_text(\"import coverage; coverage.process_startup()\");'"},
+ { cmd = "coverage erase" },
+ { cmd = "coverage run -m pytest" },
+ { cmd = "coverage combine" },
+ { cmd = "coverage report" },
+]
+ignore_fail = "return_non_zero"
+
+[tool.poe.tasks.lint]
+sequence = [
+ "ruff",
+ "mypy",
+ "check_generated_client",
+ "check_commit_style",
+ "black_check",
+ "poetry_lock_check"
+]
+ignore_fail = "return_non_zero"
+
+[tool.pyright]
+strict = ["pygls"]
+
+[tool.ruff]
+# Sometimes Black can't reduce line length without breaking more imortant rules.
+# So allow Ruff to be more lenient.
+line-length = 120
+
+[tool.black]
+line-length = 88
+extend-exclude = "pygls/lsp/client.py"
+
+[tool.coverage.run]
+parallel = true
+source_pkgs = ["pygls"]
+
+[tool.coverage.report]
+show_missing = true
+skip_covered = true
+sort = "Cover"
+
+[tool.mypy]
+check_untyped_defs = true
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
diff --git a/scripts/check_client_is_uptodate.py b/scripts/check_client_is_uptodate.py
new file mode 100644
index 0000000..1c5b1be
--- /dev/null
+++ b/scripts/check_client_is_uptodate.py
@@ -0,0 +1,20 @@
+import sys
+import subprocess
+
+AUTOGENERATED_CLIENT_FILE = "pygls/lsp/client.py"
+
+subprocess.run(["poe", "generate_client"])
+
+result = subprocess.run(
+ ["git", "diff", "--exit-code", AUTOGENERATED_CLIENT_FILE], stdout=subprocess.DEVNULL
+)
+
+if result.returncode == 0:
+ print("✅ Pygls client is up to date")
+else:
+ print(
+ "🔴 Pygls client not uptodate\n"
+ "1. Generate with: `poetry run poe generate_client`\n"
+ "2. Commit"
+ )
+ sys.exit(result.returncode)
diff --git a/scripts/generate_client.py b/scripts/generate_client.py
new file mode 100644
index 0000000..6aab8f2
--- /dev/null
+++ b/scripts/generate_client.py
@@ -0,0 +1,207 @@
+"""Script to automatically generate a lanaguge client from `lsprotocol` type definitons
+"""
+import argparse
+import inspect
+import pathlib
+import re
+import sys
+import textwrap
+from typing import Optional
+from typing import Set
+from typing import Tuple
+from typing import Type
+
+from lsprotocol._hooks import _resolve_forward_references
+from lsprotocol.types import METHOD_TO_TYPES
+from lsprotocol.types import message_direction
+
+cli = argparse.ArgumentParser(
+ description="generate language client from lsprotocol types."
+)
+cli.add_argument("-o", "--output", default=None)
+
+
+def write_imports(imports: Set[Tuple[str, str]]) -> str:
+ lines = []
+
+ for import_ in sorted(list(imports), key=lambda i: (i[0], i[1])):
+ if isinstance(import_, tuple):
+ mod, name = import_
+ lines.append(f"from {mod} import {name}")
+ continue
+
+ lines.append(f"import {import_}")
+
+ return "\n".join(lines)
+
+
+def to_snake_case(string: str) -> str:
+ return "".join(f"_{c.lower()}" if c.isupper() else c for c in string)
+
+
+def write_notification(
+ method: str,
+ request: Type,
+ params: Optional[Type],
+ imports: Set[Tuple[str, str]],
+) -> str:
+ python_name = to_snake_case(method).replace("/", "_").replace("$_", "")
+
+ if params is None:
+ param_name = "None"
+ param_mod = ""
+ else:
+ param_mod, param_name = params.__module__, params.__name__
+ param_mod = param_mod.replace("lsprotocol.types", "types") + "."
+
+ return "\n".join(
+ [
+ f"def {python_name}(self, params: {param_mod}{param_name}) -> None:",
+ f' """Send a :lsp:`{method}` notification.',
+ "",
+ textwrap.indent(inspect.getdoc(request) or "", " "),
+ ' """',
+ " if self.stopped:",
+ ' raise RuntimeError("Client has been stopped.")',
+ "",
+ f' self.protocol.notify("{method}", params)',
+ "",
+ ]
+ )
+
+
+def get_response_type(response: Type, imports: Set[Tuple[str, str]]) -> str:
+ # Find the response type.
+ result_field = [f for f in response.__attrs_attrs__ if f.name == "result"][0]
+ result = re.sub(r"<class '([\w.]+)'>", r"\1", str(result_field.type))
+ result = re.sub(r"ForwardRef\('([\w.]+)'\)", r"lsprotocol.types.\1", result)
+ result = result.replace("NoneType", "None")
+
+ # Replace any typing imports with their short name.
+ for match in re.finditer(r"typing.([\w]+)", result):
+ imports.add(("typing", match.group(1)))
+
+ result = result.replace("lsprotocol.types.", "types.")
+ result = result.replace("typing.", "")
+
+ return result
+
+
+def write_method(
+ method: str,
+ request: Type,
+ params: Optional[Type],
+ response: Type,
+ imports: Set[Tuple[str, str]],
+) -> str:
+ python_name = to_snake_case(method).replace("/", "_").replace("$_", "")
+
+ if params is None:
+ param_name = "None"
+ param_mod = ""
+ else:
+ param_mod, param_name = params.__module__, params.__name__
+ param_mod = param_mod.replace("lsprotocol.types", "types") + "."
+
+ result_type = get_response_type(response, imports)
+
+ return "\n".join(
+ [
+ f"def {python_name}(",
+ " self,",
+ f" params: {param_mod}{param_name},",
+ f" callback: Optional[Callable[[{result_type}], None]] = None,",
+ ") -> Future:",
+ f' """Make a :lsp:`{method}` request.',
+ "",
+ textwrap.indent(inspect.getdoc(request) or "", " "),
+ ' """',
+ " if self.stopped:",
+ ' raise RuntimeError("Client has been stopped.")',
+ "",
+ f' return self.protocol.send_request("{method}", params, callback)',
+ "",
+ f"async def {python_name}_async(",
+ " self,",
+ f" params: {param_mod}{param_name},",
+ f") -> {result_type}:",
+ f' """Make a :lsp:`{method}` request.',
+ "",
+ textwrap.indent(inspect.getdoc(request) or "", " "),
+ ' """',
+ " if self.stopped:",
+ ' raise RuntimeError("Client has been stopped.")',
+ "",
+ f' return await self.protocol.send_request_async("{method}", params)',
+ "",
+ ]
+ )
+
+
+def generate_client() -> str:
+ methods = []
+ imports = {
+ ("concurrent.futures", "Future"),
+ ("lsprotocol", "types"),
+ ("pygls.protocol", "LanguageServerProtocol"),
+ ("pygls.protocol", "default_converter"),
+ ("pygls.client", "JsonRPCClient"),
+ ("typing", "Callable"),
+ ("typing", "Optional"),
+ }
+
+ for method_name, types in METHOD_TO_TYPES.items():
+ # Skip any requests that come from the server.
+ if message_direction(method_name) == "serverToClient":
+ continue
+
+ request, response, params, _ = types
+
+ if response is None:
+ method = write_notification(method_name, request, params, imports)
+ else:
+ method = write_method(method_name, request, params, response, imports)
+
+ methods.append(textwrap.indent(method, " "))
+
+ code = [
+ "# GENERATED FROM scripts/gen-client.py -- DO NOT EDIT",
+ "# flake8: noqa",
+ write_imports(imports),
+ "",
+ "",
+ "class BaseLanguageClient(JsonRPCClient):",
+ "",
+ " def __init__(",
+ " self,",
+ " name: str,",
+ " version: str,",
+ " protocol_cls=LanguageServerProtocol,",
+ " converter_factory=default_converter,",
+ " **kwargs,",
+ " ):",
+ " self.name = name",
+ " self.version = version",
+ " super().__init__(protocol_cls, converter_factory, **kwargs)",
+ "",
+ *methods,
+ ]
+ return "\n".join(code)
+
+
+def main():
+ args = cli.parse_args()
+
+ # Make sure all the type annotations in lsprotocol are resolved correctly.
+ _resolve_forward_references()
+ client = generate_client()
+
+ if args.output is None:
+ sys.stdout.write(client)
+ else:
+ output = pathlib.Path(args.output)
+ output.write_text(client)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/generate_contributors_md.py b/scripts/generate_contributors_md.py
new file mode 100644
index 0000000..655181a
--- /dev/null
+++ b/scripts/generate_contributors_md.py
@@ -0,0 +1,48 @@
+"""
+Example JSON object:
+{
+ "login": "danixeee",
+ "id": 16227576,
+ "node_id": "MDQ6VXNlcjE2MjI3NTc2",
+ "avatar_url": "https://avatars.githubusercontent.com/u/16227576?v=4",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/danixeee",
+ "html_url": "https://github.com/danixeee",
+ "followers_url": "https://api.github.com/users/danixeee/followers",
+ "following_url": "https://api.github.com/users/danixeee/following{/other_user}",
+ "gists_url": "https://api.github.com/users/danixeee/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/danixeee/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/danixeee/subscriptions",
+ "organizations_url": "https://api.github.com/users/danixeee/orgs",
+ "repos_url": "https://api.github.com/users/danixeee/repos",
+ "events_url": "https://api.github.com/users/danixeee/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/danixeee/received_events",
+ "type": "User",
+ "site_admin": false,
+ "contributions": 321
+}
+"""
+
+import requests
+
+PYGLS_CONTRIBUTORS_JSON_URL = (
+ "https://api.github.com/repos/openlawlibrary/pygls/contributors"
+)
+CONTRIBUTORS_FILE = "CONTRIBUTORS.md"
+
+response = requests.get(PYGLS_CONTRIBUTORS_JSON_URL)
+contributors = sorted(response.json(), key=lambda d: d["login"].lower())
+
+contents = "# Contributors (contributions)\n"
+
+for contributor in contributors:
+ name = contributor["login"]
+ contributions = contributor["contributions"]
+ url = contributor["html_url"]
+ contents += f"* [{name}]({url}) ({contributions})\n"
+
+file = open(CONTRIBUTORS_FILE, "w")
+n = file.write(contents)
+file.close()
+
+print("✅ CONTRIBUTORS.md updated")
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..ea4bd6c
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,28 @@
+############################################################################
+# Original work Copyright 2017 Palantir Technologies, Inc. #
+# Original work licensed under the MIT License. #
+# See ThirdPartyNotices.txt in the project root for license information. #
+# All modifications Copyright (c) Open Law Library. All rights reserved. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import pytest
+
+from pygls import IS_WIN
+
+unix_only = pytest.mark.skipif(IS_WIN, reason="Unix only")
+windows_only = pytest.mark.skipif(not IS_WIN, reason="Windows only")
+
+CMD_ASYNC = "cmd_async"
+CMD_SYNC = "cmd_sync"
+CMD_THREAD = "cmd_thread"
diff --git a/tests/_init_server_stall_fix_hack.py b/tests/_init_server_stall_fix_hack.py
new file mode 100644
index 0000000..04895b0
--- /dev/null
+++ b/tests/_init_server_stall_fix_hack.py
@@ -0,0 +1,33 @@
+"""
+It would be great to find the real underlying issue here, but without these
+retries we get annoying flakey test errors. So it's preferable to hack this
+fix to actually guarantee it doesn't generate false negatives in the test
+suite.
+"""
+import os
+import concurrent
+
+RETRIES = 3
+
+
+def retry_stalled_init_fix_hack():
+ if "DISABLE_TIMEOUT" in os.environ:
+ return lambda f: f
+
+ def decorator(func):
+ def newfn(*args, **kwargs):
+ attempt = 0
+ while attempt < RETRIES:
+ try:
+ return func(*args, **kwargs)
+ except concurrent.futures._base.TimeoutError:
+ print(
+ "\n\nRetrying timeouted test server init "
+ "%d of %d\n" % (attempt, RETRIES)
+ )
+ attempt += 1
+ return func(*args, **kwargs)
+
+ return newfn
+
+ return decorator
diff --git a/tests/client.py b/tests/client.py
new file mode 100644
index 0000000..9be8752
--- /dev/null
+++ b/tests/client.py
@@ -0,0 +1,180 @@
+############################################################################
+# Original work Copyright 2017 Palantir Technologies, Inc. #
+# Original work licensed under the MIT License. #
+# See ThirdPartyNotices.txt in the project root for license information. #
+# All modifications Copyright (c) Open Law Library. All rights reserved. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import asyncio
+import logging
+import pathlib
+import sys
+from concurrent.futures import Future
+from typing import Dict
+from typing import List
+from typing import Type
+
+import pytest
+import pytest_asyncio
+from lsprotocol import types
+
+from pygls import IS_PYODIDE
+from pygls import uris
+from pygls.exceptions import JsonRpcMethodNotFound
+from pygls.lsp.client import BaseLanguageClient
+from pygls.protocol import LanguageServerProtocol
+from pygls.protocol import default_converter
+
+logger = logging.getLogger(__name__)
+
+
+class LanguageClientProtocol(LanguageServerProtocol):
+ """An extended protocol class with extra methods that are useful for testing."""
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ self._notification_futures = {}
+
+ def _handle_notification(self, method_name, params):
+ if method_name == types.CANCEL_REQUEST:
+ self._handle_cancel_notification(params.id)
+ return
+
+ future = self._notification_futures.pop(method_name, None)
+ if future:
+ future.set_result(params)
+
+ try:
+ handler = self._get_handler(method_name)
+ self._execute_notification(handler, params)
+ except (KeyError, JsonRpcMethodNotFound):
+ logger.warning("Ignoring notification for unknown method '%s'", method_name)
+ except Exception:
+ logger.exception(
+ "Failed to handle notification '%s': %s", method_name, params
+ )
+
+ def wait_for_notification(self, method: str, callback=None):
+ future: Future = Future()
+ if callback:
+
+ def wrapper(future: Future):
+ result = future.result()
+ callback(result)
+
+ future.add_done_callback(wrapper)
+
+ self._notification_futures[method] = future
+ return future
+
+ def wait_for_notification_async(self, method: str):
+ future = self.wait_for_notification(method)
+ return asyncio.wrap_future(future)
+
+
+class LanguageClient(BaseLanguageClient):
+ """Language client used to drive test cases."""
+
+ def __init__(
+ self,
+ protocol_cls: Type[LanguageClientProtocol] = LanguageClientProtocol,
+ *args,
+ **kwargs,
+ ):
+ super().__init__(
+ "pygls-test-client", "v1", protocol_cls=protocol_cls, *args, **kwargs
+ )
+
+ self.diagnostics: Dict[str, List[types.Diagnostic]] = {}
+ """Used to hold any recieved diagnostics."""
+
+ self.messages: List[types.ShowMessageParams] = []
+ """Holds any received ``window/showMessage`` requests."""
+
+ self.log_messages: List[types.LogMessageParams] = []
+ """Holds any received ``window/logMessage`` requests."""
+
+ async def wait_for_notification(self, method: str):
+ """Block until a notification with the given method is received.
+
+ Parameters
+ ----------
+ method
+ The notification method to wait for, e.g. ``textDocument/publishDiagnostics``
+ """
+ return await self.protocol.wait_for_notification_async(method)
+
+
+def make_test_lsp_client() -> LanguageClient:
+ """Construct a new test client instance with the handlers needed to capture
+ additional responses from the server."""
+
+ client = LanguageClient(converter_factory=default_converter)
+
+ @client.feature(types.TEXT_DOCUMENT_PUBLISH_DIAGNOSTICS)
+ def publish_diagnostics(
+ client: LanguageClient, params: types.PublishDiagnosticsParams
+ ):
+ client.diagnostics[params.uri] = params.diagnostics
+
+ @client.feature(types.WINDOW_LOG_MESSAGE)
+ def log_message(client: LanguageClient, params: types.LogMessageParams):
+ client.log_messages.append(params)
+
+ levels = ["ERROR: ", "WARNING: ", "INFO: ", "LOG: "]
+ log_level = levels[params.type.value - 1]
+
+ print(log_level, params.message)
+
+ @client.feature(types.WINDOW_SHOW_MESSAGE)
+ def show_message(client: LanguageClient, params):
+ client.messages.append(params)
+
+ return client
+
+
+def create_client_for_server(server_name: str):
+ """Automate the process of creating a language client connected to the given server
+ and tearing it down again.
+ """
+
+ @pytest_asyncio.fixture
+ async def fixture_func():
+ if IS_PYODIDE:
+ pytest.skip("not available in pyodide")
+
+ client = make_test_lsp_client()
+ server_dir = pathlib.Path(__file__, "..", "..", "examples", "servers").resolve()
+ root_dir = pathlib.Path(__file__, "..", "..", "examples", "workspace").resolve()
+
+ await client.start_io(sys.executable, str(server_dir / server_name))
+
+ # Initialize the server
+ response = await client.initialize_async(
+ types.InitializeParams(
+ capabilities=types.ClientCapabilities(),
+ root_uri=uris.from_fs_path(root_dir),
+ )
+ )
+ assert response is not None
+
+ yield client, response
+
+ await client.shutdown_async(None)
+ client.exit(None)
+
+ await client.stop()
+
+ return fixture_func
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..5dd2ecb
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,122 @@
+############################################################################
+# Original work Copyright 2017 Palantir Technologies, Inc. #
+# Original work licensed under the MIT License. #
+# See ThirdPartyNotices.txt in the project root for license information. #
+# All modifications Copyright (c) Open Law Library. All rights reserved. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import asyncio
+import pathlib
+
+import pytest
+from lsprotocol import types, converters
+
+from pygls import uris, IS_PYODIDE
+from pygls.feature_manager import FeatureManager
+from pygls.workspace import Workspace
+
+from .ls_setup import (
+ NativeClientServer,
+ PyodideClientServer,
+ setup_ls_features,
+)
+
+from .client import create_client_for_server
+
+DOC = """document
+for
+testing
+with "😋" unicode.
+"""
+DOC_URI = uris.from_fs_path(__file__) or ""
+
+
+ClientServer = NativeClientServer
+if IS_PYODIDE:
+ ClientServer = PyodideClientServer
+
+
+@pytest.fixture(autouse=False)
+def client_server(request):
+ if hasattr(request, "param"):
+ ConfiguredClientServer = request.param
+ client_server = ConfiguredClientServer()
+ else:
+ client_server = ClientServer()
+ setup_ls_features(client_server.server)
+
+ client_server.start()
+ client, server = client_server
+
+ yield client, server
+
+ client_server.stop()
+
+
+@pytest.fixture(scope="session")
+def uri_for():
+ """Returns the uri corresponsing to a file in the example workspace."""
+ base_dir = pathlib.Path(
+ __file__, "..", "..", "examples", "servers", "workspace"
+ ).resolve()
+
+ def fn(*args):
+ fpath = pathlib.Path(base_dir, *args)
+ return uris.from_fs_path(str(fpath))
+
+ return fn
+
+
+@pytest.fixture()
+def event_loop():
+ """Redefine `pytest-asyncio's default event_loop fixture to match the scope
+ of our client fixture."""
+
+ policy = asyncio.get_event_loop_policy()
+
+ loop = policy.new_event_loop()
+ yield loop
+
+ try:
+ # Not implemented on pyodide
+ loop.close()
+ except NotImplementedError:
+ pass
+
+
+@pytest.fixture(scope="session")
+def server_dir():
+ """Returns the directory where all the example language servers live"""
+ path = pathlib.Path(__file__) / ".." / ".." / "examples" / "servers"
+ return path.resolve()
+
+
+code_action_client = create_client_for_server("code_actions.py")
+inlay_hints_client = create_client_for_server("inlay_hints.py")
+json_server_client = create_client_for_server("json_server.py")
+
+
+@pytest.fixture
+def feature_manager():
+ """Return a feature manager"""
+ return FeatureManager(None, converters.get_converter())
+
+
+@pytest.fixture
+def workspace(tmpdir):
+ """Return a workspace."""
+ return Workspace(
+ uris.from_fs_path(str(tmpdir)),
+ sync_kind=types.TextDocumentSyncKind.Incremental,
+ )
diff --git a/tests/ls_setup.py b/tests/ls_setup.py
new file mode 100644
index 0000000..e52c06c
--- /dev/null
+++ b/tests/ls_setup.py
@@ -0,0 +1,169 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import asyncio
+import json
+import os
+import threading
+
+import pytest
+from lsprotocol.types import (
+ EXIT,
+ INITIALIZE,
+ SHUTDOWN,
+ ClientCapabilities,
+ InitializeParams,
+)
+from pygls.server import LanguageServer
+
+
+from . import CMD_ASYNC, CMD_SYNC, CMD_THREAD
+from ._init_server_stall_fix_hack import retry_stalled_init_fix_hack
+
+
+CALL_TIMEOUT = 3
+
+
+def setup_ls_features(server):
+ # Commands
+ @server.command(CMD_ASYNC)
+ async def cmd_test3(ls, *args): # pylint: disable=unused-variable
+ return True, threading.get_ident()
+
+ @server.thread()
+ @server.command(CMD_THREAD)
+ def cmd_test1(ls, *args): # pylint: disable=unused-variable
+ return True, threading.get_ident()
+
+ @server.command(CMD_SYNC)
+ def cmd_test2(ls, *args): # pylint: disable=unused-variable
+ return True, threading.get_ident()
+
+
+class PyodideTestTransportAdapter:
+ """Transort adapter that's only useful for tests in a pyodide environment."""
+
+ def __init__(self, dest: LanguageServer):
+ self.dest = dest
+
+ def close(self):
+ ...
+
+ def write(self, data):
+ object_hook = self.dest.lsp._deserialize_message
+ self.dest.lsp._procedure_handler(json.loads(data, object_hook=object_hook))
+
+
+class PyodideClientServer:
+ """Implementation of the `client_server` fixture for use in a pyodide
+ environment."""
+
+ def __init__(self, LS=LanguageServer):
+ self.server = LS("pygls-server", "v1")
+ self.client = LS("pygls-client", "v1")
+
+ self.server.lsp.connection_made(PyodideTestTransportAdapter(self.client))
+ self.server.lsp._send_only_body = True
+
+ self.client.lsp.connection_made(PyodideTestTransportAdapter(self.server))
+ self.client.lsp._send_only_body = True
+
+ def start(self):
+ self.initialize()
+
+ def stop(self):
+ ...
+
+ @classmethod
+ def decorate(cls):
+ return pytest.mark.parametrize("client_server", [cls], indirect=True)
+
+ def initialize(self):
+ response = self.client.lsp.send_request(
+ INITIALIZE,
+ InitializeParams(
+ process_id=12345, root_uri="file://", capabilities=ClientCapabilities()
+ ),
+ ).result(timeout=CALL_TIMEOUT)
+
+ assert response.capabilities is not None
+
+ def __iter__(self):
+ yield self.client
+ yield self.server
+
+
+class NativeClientServer:
+ def __init__(self, LS=LanguageServer):
+ # Client to Server pipe
+ csr, csw = os.pipe()
+ # Server to client pipe
+ scr, scw = os.pipe()
+
+ # Setup Server
+ self.server = LS("server", "v1")
+ self.server_thread = threading.Thread(
+ name="Server Thread",
+ target=self.server.start_io,
+ args=(os.fdopen(csr, "rb"), os.fdopen(scw, "wb")),
+ )
+ self.server_thread.daemon = True
+
+ # Setup client
+ self.client = LS("client", "v1", asyncio.new_event_loop())
+ self.client_thread = threading.Thread(
+ name="Client Thread",
+ target=self.client.start_io,
+ args=(os.fdopen(scr, "rb"), os.fdopen(csw, "wb")),
+ )
+ self.client_thread.daemon = True
+
+ @classmethod
+ def decorate(cls):
+ return pytest.mark.parametrize("client_server", [cls], indirect=True)
+
+ def start(self):
+ self.server_thread.start()
+ self.server.thread_id = self.server_thread.ident
+ self.client_thread.start()
+ self.initialize()
+
+ def stop(self):
+ shutdown_response = self.client.lsp.send_request(SHUTDOWN).result()
+ assert shutdown_response is None
+ self.client.lsp.notify(EXIT)
+ self.server_thread.join()
+ self.client._stop_event.set()
+ try:
+ self.client.loop._signal_handlers.clear() # HACK ?
+ except AttributeError:
+ pass
+ self.client_thread.join()
+
+ @retry_stalled_init_fix_hack()
+ def initialize(self):
+ timeout = None if "DISABLE_TIMEOUT" in os.environ else 1
+ response = self.client.lsp.send_request(
+ INITIALIZE,
+ InitializeParams(
+ process_id=12345, root_uri="file://", capabilities=ClientCapabilities()
+ ),
+ ).result(timeout=timeout)
+ assert response.capabilities is not None
+
+ def __iter__(self):
+ yield self.client
+ yield self.server
diff --git a/tests/lsp/__init__.py b/tests/lsp/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/lsp/__init__.py
diff --git a/tests/lsp/semantic_tokens/__init__.py b/tests/lsp/semantic_tokens/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/lsp/semantic_tokens/__init__.py
diff --git a/tests/lsp/semantic_tokens/test_delta_missing_legend.py b/tests/lsp/semantic_tokens/test_delta_missing_legend.py
new file mode 100644
index 0000000..a3069da
--- /dev/null
+++ b/tests/lsp/semantic_tokens/test_delta_missing_legend.py
@@ -0,0 +1,92 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from typing import Optional, Union
+
+from lsprotocol.types import (
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA,
+)
+from lsprotocol.types import (
+ SemanticTokens,
+ SemanticTokensDeltaParams,
+ SemanticTokensLegend,
+ SemanticTokensPartialResult,
+ SemanticTokensOptionsFullType1,
+ TextDocumentIdentifier,
+)
+
+from ...conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA,
+ SemanticTokensLegend(
+ token_types=["keyword", "operator"], token_modifiers=["readonly"]
+ ),
+ )
+ def f(
+ params: SemanticTokensDeltaParams,
+ ) -> Union[SemanticTokensPartialResult, Optional[SemanticTokens]]:
+ if params.text_document.uri == "file://return.tokens":
+ return SemanticTokens(data=[0, 0, 3, 0, 0])
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ provider = capabilities.semantic_tokens_provider
+ assert provider.full == SemanticTokensOptionsFullType1(delta=True)
+ assert provider.legend.token_types == [
+ "keyword",
+ "operator",
+ ]
+ assert provider.legend.token_modifiers == ["readonly"]
+
+
+@ConfiguredLS.decorate()
+def test_semantic_tokens_full_delta_return_tokens(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA,
+ SemanticTokensDeltaParams(
+ text_document=TextDocumentIdentifier(uri="file://return.tokens"),
+ previous_result_id="id",
+ ),
+ ).result()
+
+ assert response
+
+ assert response.data == [0, 0, 3, 0, 0]
+
+
+@ConfiguredLS.decorate()
+def test_semantic_tokens_full_delta_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA,
+ SemanticTokensDeltaParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ previous_result_id="id",
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/semantic_tokens/test_delta_missing_legend_none.py b/tests/lsp/semantic_tokens/test_delta_missing_legend_none.py
new file mode 100644
index 0000000..6f4fa17
--- /dev/null
+++ b/tests/lsp/semantic_tokens/test_delta_missing_legend_none.py
@@ -0,0 +1,48 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from typing import Optional, Union
+
+from lsprotocol.types import (
+ SemanticTokens,
+ SemanticTokensDeltaParams,
+ SemanticTokensPartialResult,
+)
+from lsprotocol.types import (
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA,
+)
+
+from ...conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA)
+ def f(
+ params: SemanticTokensDeltaParams,
+ ) -> Union[SemanticTokensPartialResult, Optional[SemanticTokens]]:
+ return SemanticTokens(data=[0, 0, 3, 0, 0])
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.semantic_tokens_provider is None
+ assert capabilities.semantic_tokens_provider is None
diff --git a/tests/lsp/semantic_tokens/test_full_missing_legend.py b/tests/lsp/semantic_tokens/test_full_missing_legend.py
new file mode 100644
index 0000000..e18dbde
--- /dev/null
+++ b/tests/lsp/semantic_tokens/test_full_missing_legend.py
@@ -0,0 +1,46 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from typing import Optional, Union
+
+from lsprotocol.types import (
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL,
+)
+from lsprotocol.types import (
+ SemanticTokens,
+ SemanticTokensPartialResult,
+ SemanticTokensParams,
+)
+
+from ...conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL)
+ def f(
+ params: SemanticTokensParams,
+ ) -> Union[SemanticTokensPartialResult, Optional[SemanticTokens]]:
+ return SemanticTokens(data=[0, 0, 3, 0, 0])
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+ assert capabilities.semantic_tokens_provider is None
diff --git a/tests/lsp/semantic_tokens/test_range.py b/tests/lsp/semantic_tokens/test_range.py
new file mode 100644
index 0000000..a65504b
--- /dev/null
+++ b/tests/lsp/semantic_tokens/test_range.py
@@ -0,0 +1,103 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from typing import Optional, Union
+
+from lsprotocol.types import (
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE,
+)
+from lsprotocol.types import (
+ Position,
+ Range,
+ SemanticTokens,
+ SemanticTokensLegend,
+ SemanticTokensPartialResult,
+ SemanticTokensRangeParams,
+ TextDocumentIdentifier,
+)
+
+from ...conftest import ClientServer
+
+SemanticTokenReturnType = Optional[
+ Union[SemanticTokensPartialResult, Optional[SemanticTokens]]
+]
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE,
+ SemanticTokensLegend(
+ token_types=["keyword", "operator"], token_modifiers=["readonly"]
+ ),
+ )
+ def f(
+ params: SemanticTokensRangeParams,
+ ) -> SemanticTokenReturnType:
+ if params.text_document.uri == "file://return.tokens":
+ return SemanticTokens(data=[0, 0, 3, 0, 0])
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ provider = capabilities.semantic_tokens_provider
+ assert provider.range
+ assert provider.legend.token_types == [
+ "keyword",
+ "operator",
+ ]
+ assert provider.legend.token_modifiers == ["readonly"]
+
+
+@ConfiguredLS.decorate()
+def test_semantic_tokens_range_return_tokens(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE,
+ SemanticTokensRangeParams(
+ text_document=TextDocumentIdentifier(uri="file://return.tokens"),
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=10, character=80),
+ ),
+ ),
+ ).result()
+
+ assert response
+
+ assert response.data == [0, 0, 3, 0, 0]
+
+
+@ConfiguredLS.decorate()
+def test_semantic_tokens_range_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE,
+ SemanticTokensRangeParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=10, character=80),
+ ),
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/semantic_tokens/test_range_missing_legends.py b/tests/lsp/semantic_tokens/test_range_missing_legends.py
new file mode 100644
index 0000000..69780ef
--- /dev/null
+++ b/tests/lsp/semantic_tokens/test_range_missing_legends.py
@@ -0,0 +1,47 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from typing import Optional, Union
+
+from lsprotocol.types import (
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE,
+)
+from lsprotocol.types import (
+ SemanticTokens,
+ SemanticTokensParams,
+ SemanticTokensPartialResult,
+)
+
+from ...conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE)
+ def f(
+ params: SemanticTokensParams,
+ ) -> Union[SemanticTokensPartialResult, Optional[SemanticTokens]]:
+ return SemanticTokens(data=[0, 0, 3, 0, 0])
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.semantic_tokens_provider is None
diff --git a/tests/lsp/semantic_tokens/test_semantic_tokens_full.py b/tests/lsp/semantic_tokens/test_semantic_tokens_full.py
new file mode 100644
index 0000000..dba9fa6
--- /dev/null
+++ b/tests/lsp/semantic_tokens/test_semantic_tokens_full.py
@@ -0,0 +1,93 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from typing import Optional, Union
+
+from lsprotocol.types import (
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL,
+)
+from lsprotocol.types import (
+ SemanticTokens,
+ SemanticTokensLegend,
+ SemanticTokensParams,
+ SemanticTokensPartialResult,
+ TextDocumentIdentifier,
+)
+
+from ...conftest import ClientServer
+
+SemanticTokenReturnType = Optional[
+ Union[SemanticTokensPartialResult, Optional[SemanticTokens]]
+]
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL,
+ SemanticTokensLegend(
+ token_types=["keyword", "operator"], token_modifiers=["readonly"]
+ ),
+ )
+ def f(
+ params: SemanticTokensParams,
+ ) -> SemanticTokenReturnType:
+ if params.text_document.uri == "file://return.tokens":
+ return SemanticTokens(data=[0, 0, 3, 0, 0])
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ provider = capabilities.semantic_tokens_provider
+ assert provider.full
+ assert provider.legend.token_types == [
+ "keyword",
+ "operator",
+ ]
+ assert provider.legend.token_modifiers == ["readonly"]
+
+
+@ConfiguredLS.decorate()
+def test_semantic_tokens_full_return_tokens(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL,
+ SemanticTokensParams(
+ text_document=TextDocumentIdentifier(uri="file://return.tokens")
+ ),
+ ).result()
+
+ assert response
+
+ assert response.data == [0, 0, 3, 0, 0]
+
+
+@ConfiguredLS.decorate()
+def test_semantic_tokens_full_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL,
+ SemanticTokensParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none")
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_call_hierarchy.py b/tests/lsp/test_call_hierarchy.py
new file mode 100644
index 0000000..410a982
--- /dev/null
+++ b/tests/lsp/test_call_hierarchy.py
@@ -0,0 +1,192 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from typing import List, Optional
+
+from lsprotocol.types import (
+ CALL_HIERARCHY_INCOMING_CALLS,
+ CALL_HIERARCHY_OUTGOING_CALLS,
+ TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY,
+)
+from lsprotocol.types import (
+ CallHierarchyIncomingCall,
+ CallHierarchyIncomingCallsParams,
+ CallHierarchyItem,
+ CallHierarchyOptions,
+ CallHierarchyOutgoingCall,
+ CallHierarchyOutgoingCallsParams,
+ CallHierarchyPrepareParams,
+ Position,
+ Range,
+ SymbolKind,
+ SymbolTag,
+ TextDocumentIdentifier,
+)
+
+from ..conftest import ClientServer
+
+CALL_HIERARCHY_ITEM = CallHierarchyItem(
+ name="test_name",
+ kind=SymbolKind.File,
+ uri="test_uri",
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ selection_range=Range(
+ start=Position(line=1, character=1),
+ end=Position(line=2, character=2),
+ ),
+ tags=[SymbolTag.Deprecated],
+ detail="test_detail",
+ data="test_data",
+)
+
+
+def check_call_hierarchy_item_response(item):
+ assert item.name == "test_name"
+ assert item.kind == SymbolKind.File
+ assert item.uri == "test_uri"
+ assert item.range.start.line == 0
+ assert item.range.start.character == 0
+ assert item.range.end.line == 1
+ assert item.range.end.character == 1
+ assert item.selection_range.start.line == 1
+ assert item.selection_range.start.character == 1
+ assert item.selection_range.end.line == 2
+ assert item.selection_range.end.character == 2
+ assert len(item.tags) == 1
+ assert item.tags[0] == SymbolTag.Deprecated
+ assert item.detail == "test_detail"
+ assert item.data == "test_data"
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY,
+ CallHierarchyOptions(),
+ )
+ def f1(params: CallHierarchyPrepareParams) -> Optional[List[CallHierarchyItem]]:
+ if params.text_document.uri == "file://return.list":
+ return [CALL_HIERARCHY_ITEM]
+ else:
+ return None
+
+ @self.server.feature(CALL_HIERARCHY_INCOMING_CALLS)
+ def f2(
+ params: CallHierarchyIncomingCallsParams,
+ ) -> Optional[List[CallHierarchyIncomingCall]]:
+ return [
+ CallHierarchyIncomingCall(
+ from_=params.item,
+ from_ranges=[
+ Range(
+ start=Position(line=2, character=2),
+ end=Position(line=3, character=3),
+ ),
+ ],
+ ),
+ ]
+
+ @self.server.feature(CALL_HIERARCHY_OUTGOING_CALLS)
+ def f3(
+ params: CallHierarchyOutgoingCallsParams,
+ ) -> Optional[List[CallHierarchyOutgoingCall]]:
+ return [
+ CallHierarchyOutgoingCall(
+ to=params.item,
+ from_ranges=[
+ Range(
+ start=Position(line=3, character=3),
+ end=Position(line=4, character=4),
+ ),
+ ],
+ ),
+ ]
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+ assert capabilities.call_hierarchy_provider
+
+
+@ConfiguredLS.decorate()
+def test_call_hierarchy_prepare_return_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY,
+ CallHierarchyPrepareParams(
+ text_document=TextDocumentIdentifier(uri="file://return.list"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ check_call_hierarchy_item_response(response[0])
+
+
+@ConfiguredLS.decorate()
+def test_call_hierarchy_prepare_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY,
+ CallHierarchyPrepareParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response is None
+
+
+@ConfiguredLS.decorate()
+def test_call_hierarchy_incoming_calls_return_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ CALL_HIERARCHY_INCOMING_CALLS,
+ CallHierarchyIncomingCallsParams(item=CALL_HIERARCHY_ITEM),
+ ).result()
+
+ item = response[0]
+
+ check_call_hierarchy_item_response(item.from_)
+
+ assert item.from_ranges[0].start.line == 2
+ assert item.from_ranges[0].start.character == 2
+ assert item.from_ranges[0].end.line == 3
+ assert item.from_ranges[0].end.character == 3
+
+
+@ConfiguredLS.decorate()
+def test_call_hierarchy_outgoing_calls_return_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ CALL_HIERARCHY_OUTGOING_CALLS,
+ CallHierarchyOutgoingCallsParams(item=CALL_HIERARCHY_ITEM),
+ ).result()
+
+ item = response[0]
+
+ check_call_hierarchy_item_response(item.to)
+
+ assert item.from_ranges[0].start.line == 3
+ assert item.from_ranges[0].start.character == 3
+ assert item.from_ranges[0].end.line == 4
+ assert item.from_ranges[0].end.character == 4
diff --git a/tests/lsp/test_code_action.py b/tests/lsp/test_code_action.py
new file mode 100644
index 0000000..c10dcd5
--- /dev/null
+++ b/tests/lsp/test_code_action.py
@@ -0,0 +1,60 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from typing import Tuple
+
+from lsprotocol import types
+
+from ..client import LanguageClient
+
+
+async def test_code_actions(
+ code_action_client: Tuple[LanguageClient, types.InitializeResult], uri_for
+):
+ """Ensure that the example code action server is working as expected."""
+ client, initialize_result = code_action_client
+
+ code_action_options = initialize_result.capabilities.code_action_provider
+ assert code_action_options.code_action_kinds == [types.CodeActionKind.QuickFix]
+
+ test_uri = uri_for("sums.txt")
+ assert test_uri is not None
+
+ response = await client.text_document_code_action_async(
+ types.CodeActionParams(
+ text_document=types.TextDocumentIdentifier(uri=test_uri),
+ range=types.Range(
+ start=types.Position(line=0, character=0),
+ end=types.Position(line=1, character=0),
+ ),
+ context=types.CodeActionContext(diagnostics=[]),
+ )
+ )
+
+ assert len(response) == 1
+ code_action = response[0]
+
+ assert code_action.title == "Evaluate '1 + 1 ='"
+ assert code_action.kind == types.CodeActionKind.QuickFix
+
+ fix = code_action.edit.changes[test_uri][0]
+ expected_range = types.Range(
+ start=types.Position(line=0, character=0),
+ end=types.Position(line=0, character=7),
+ )
+
+ assert fix.range == expected_range
+ assert fix.new_text == "1 + 1 = 2!"
diff --git a/tests/lsp/test_code_lens.py b/tests/lsp/test_code_lens.py
new file mode 100644
index 0000000..7024110
--- /dev/null
+++ b/tests/lsp/test_code_lens.py
@@ -0,0 +1,94 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from typing import List, Optional
+
+from lsprotocol.types import TEXT_DOCUMENT_CODE_LENS
+from lsprotocol.types import (
+ CodeLens,
+ CodeLensOptions,
+ CodeLensParams,
+ Command,
+ Position,
+ Range,
+ TextDocumentIdentifier,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_CODE_LENS,
+ CodeLensOptions(resolve_provider=False),
+ )
+ def f(params: CodeLensParams) -> Optional[List[CodeLens]]:
+ if params.text_document.uri == "file://return.list":
+ return [
+ CodeLens(
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ command=Command(
+ title="cmd1",
+ command="cmd1",
+ ),
+ data="some data",
+ ),
+ ]
+ else:
+ return None
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.code_lens_provider
+ assert not capabilities.code_lens_provider.resolve_provider
+
+
+@ConfiguredLS.decorate()
+def test_code_lens_return_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_CODE_LENS,
+ CodeLensParams(text_document=TextDocumentIdentifier(uri="file://return.list")),
+ ).result()
+
+ assert response[0].data == "some data"
+ assert response[0].range.start.line == 0
+ assert response[0].range.start.character == 0
+ assert response[0].range.end.line == 1
+ assert response[0].range.end.character == 1
+ assert response[0].command.title == "cmd1"
+ assert response[0].command.command == "cmd1"
+
+
+@ConfiguredLS.decorate()
+def test_code_lens_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_CODE_LENS,
+ CodeLensParams(text_document=TextDocumentIdentifier(uri="file://return.none")),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_color_presentation.py b/tests/lsp/test_color_presentation.py
new file mode 100644
index 0000000..6748e66
--- /dev/null
+++ b/tests/lsp/test_color_presentation.py
@@ -0,0 +1,103 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import List
+
+from lsprotocol.types import TEXT_DOCUMENT_COLOR_PRESENTATION
+from lsprotocol.types import (
+ Color,
+ ColorPresentation,
+ ColorPresentationParams,
+ Position,
+ Range,
+ TextDocumentIdentifier,
+ TextEdit,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(TEXT_DOCUMENT_COLOR_PRESENTATION)
+ def f(params: ColorPresentationParams) -> List[ColorPresentation]:
+ return [
+ ColorPresentation(
+ label="label1",
+ text_edit=TextEdit(
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ new_text="te",
+ ),
+ additional_text_edits=[
+ TextEdit(
+ range=Range(
+ start=Position(line=1, character=1),
+ end=Position(line=2, character=2),
+ ),
+ new_text="ate1",
+ ),
+ TextEdit(
+ range=Range(
+ start=Position(line=2, character=2),
+ end=Position(line=3, character=3),
+ ),
+ new_text="ate2",
+ ),
+ ],
+ )
+ ]
+
+
+@ConfiguredLS.decorate()
+def test_color_presentation(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_COLOR_PRESENTATION,
+ ColorPresentationParams(
+ text_document=TextDocumentIdentifier(uri="file://return.list"),
+ color=Color(red=0.6, green=0.2, blue=0.3, alpha=0.5),
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=3, character=3),
+ ),
+ ),
+ ).result()
+
+ assert response[0].label == "label1"
+ assert response[0].text_edit.new_text == "te"
+
+ assert response[0].text_edit.range.start.line == 0
+ assert response[0].text_edit.range.start.character == 0
+ assert response[0].text_edit.range.end.line == 1
+ assert response[0].text_edit.range.end.character == 1
+
+ range = response[0].additional_text_edits[0].range
+ assert range.start.line == 1
+ assert range.start.character == 1
+ assert range.end.line == 2
+ assert range.end.character == 2
+
+ range = response[0].additional_text_edits[1].range
+ assert range.start.line == 2
+ assert range.start.character == 2
+ assert range.end.line == 3
+ assert range.end.character == 3
diff --git a/tests/lsp/test_completion.py b/tests/lsp/test_completion.py
new file mode 100644
index 0000000..dcb8124
--- /dev/null
+++ b/tests/lsp/test_completion.py
@@ -0,0 +1,48 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from typing import Tuple
+
+from lsprotocol import types
+
+
+from ..client import LanguageClient
+
+
+async def test_completion(
+ json_server_client: Tuple[LanguageClient, types.InitializeResult],
+ uri_for,
+):
+ """Ensure that the completion methods are working as expected."""
+ client, initialize_result = json_server_client
+
+ completion_provider = initialize_result.capabilities.completion_provider
+ assert completion_provider
+ assert completion_provider.trigger_characters == [","]
+ assert completion_provider.all_commit_characters == [":"]
+
+ test_uri = uri_for("example.json")
+ assert test_uri is not None
+
+ response = await client.text_document_completion_async(
+ types.CompletionParams(
+ text_document=types.TextDocumentIdentifier(uri=test_uri),
+ position=types.Position(line=0, character=0),
+ )
+ )
+
+ labels = {i.label for i in response.items}
+ assert labels == set(['"', "[", "]", "{", "}"])
diff --git a/tests/lsp/test_declaration.py b/tests/lsp/test_declaration.py
new file mode 100644
index 0000000..221982d
--- /dev/null
+++ b/tests/lsp/test_declaration.py
@@ -0,0 +1,161 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import List, Optional, Union
+
+from lsprotocol.types import TEXT_DOCUMENT_DECLARATION
+from lsprotocol.types import (
+ DeclarationOptions,
+ DeclarationParams,
+ Location,
+ LocationLink,
+ Position,
+ Range,
+ TextDocumentIdentifier,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(TEXT_DOCUMENT_DECLARATION, DeclarationOptions())
+ def f(
+ params: DeclarationParams,
+ ) -> Optional[Union[Location, List[Location], List[LocationLink]]]:
+ location = Location(
+ uri="uri",
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ )
+
+ location_link = LocationLink(
+ target_uri="uri",
+ target_range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ target_selection_range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=2, character=2),
+ ),
+ origin_selection_range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=3, character=3),
+ ),
+ )
+
+ return { # type: ignore
+ "file://return.location": location,
+ "file://return.location_list": [location],
+ "file://return.location_link_list": [location_link],
+ }.get(params.text_document.uri, None)
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.declaration_provider
+
+
+@ConfiguredLS.decorate()
+def test_declaration_return_location(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_DECLARATION,
+ DeclarationParams(
+ text_document=TextDocumentIdentifier(uri="file://return.location"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response.uri == "uri"
+
+ assert response.range.start.line == 0
+ assert response.range.start.character == 0
+ assert response.range.end.line == 1
+ assert response.range.end.character == 1
+
+
+@ConfiguredLS.decorate()
+def test_declaration_return_location_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_DECLARATION,
+ DeclarationParams(
+ text_document=TextDocumentIdentifier(uri="file://return.location_list"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response[0].uri == "uri"
+
+ assert response[0].range.start.line == 0
+ assert response[0].range.start.character == 0
+ assert response[0].range.end.line == 1
+ assert response[0].range.end.character == 1
+
+
+@ConfiguredLS.decorate()
+def test_declaration_return_location_link_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_DECLARATION,
+ DeclarationParams(
+ text_document=TextDocumentIdentifier(
+ uri="file://return.location_link_list"
+ ),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response[0].target_uri == "uri"
+
+ assert response[0].target_range.start.line == 0
+ assert response[0].target_range.start.character == 0
+ assert response[0].target_range.end.line == 1
+ assert response[0].target_range.end.character == 1
+
+ assert response[0].target_selection_range.start.line == 0
+ assert response[0].target_selection_range.start.character == 0
+ assert response[0].target_selection_range.end.line == 2
+ assert response[0].target_selection_range.end.character == 2
+
+ assert response[0].origin_selection_range.start.line == 0
+ assert response[0].origin_selection_range.start.character == 0
+ assert response[0].origin_selection_range.end.line == 3
+ assert response[0].origin_selection_range.end.character == 3
+
+
+@ConfiguredLS.decorate()
+def test_declaration_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_DECLARATION,
+ DeclarationParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_definition.py b/tests/lsp/test_definition.py
new file mode 100644
index 0000000..3ed2f96
--- /dev/null
+++ b/tests/lsp/test_definition.py
@@ -0,0 +1,164 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import List, Optional, Union
+
+from lsprotocol.types import TEXT_DOCUMENT_DEFINITION
+from lsprotocol.types import (
+ DefinitionOptions,
+ DefinitionParams,
+ Location,
+ LocationLink,
+ Position,
+ Range,
+ TextDocumentIdentifier,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_DEFINITION,
+ DefinitionOptions(),
+ )
+ def f(
+ params: DefinitionParams,
+ ) -> Optional[Union[Location, List[Location], List[LocationLink]]]:
+ location = Location(
+ uri="uri",
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ )
+
+ location_link = LocationLink(
+ target_uri="uri",
+ target_range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ target_selection_range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=2, character=2),
+ ),
+ origin_selection_range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=3, character=3),
+ ),
+ )
+
+ return { # type: ignore
+ "file://return.location": location,
+ "file://return.location_list": [location],
+ "file://return.location_link_list": [location_link],
+ }.get(params.text_document.uri, None)
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.definition_provider is not None
+
+
+@ConfiguredLS.decorate()
+def test_definition_return_location(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_DEFINITION,
+ DefinitionParams(
+ text_document=TextDocumentIdentifier(uri="file://return.location"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response.uri == "uri"
+
+ assert response.range.start.line == 0
+ assert response.range.start.character == 0
+ assert response.range.end.line == 1
+ assert response.range.end.character == 1
+
+
+@ConfiguredLS.decorate()
+def test_definition_return_location_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_DEFINITION,
+ DefinitionParams(
+ text_document=TextDocumentIdentifier(uri="file://return.location_list"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response[0].uri == "uri"
+
+ assert response[0].range.start.line == 0
+ assert response[0].range.start.character == 0
+ assert response[0].range.end.line == 1
+ assert response[0].range.end.character == 1
+
+
+@ConfiguredLS.decorate()
+def test_definition_return_location_link_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_DEFINITION,
+ DefinitionParams(
+ text_document=TextDocumentIdentifier(
+ uri="file://return.location_link_list"
+ ),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response[0].target_uri == "uri"
+
+ assert response[0].target_range.start.line == 0
+ assert response[0].target_range.start.character == 0
+ assert response[0].target_range.end.line == 1
+ assert response[0].target_range.end.character == 1
+
+ assert response[0].target_selection_range.start.line == 0
+ assert response[0].target_selection_range.start.character == 0
+ assert response[0].target_selection_range.end.line == 2
+ assert response[0].target_selection_range.end.character == 2
+
+ assert response[0].origin_selection_range.start.line == 0
+ assert response[0].origin_selection_range.start.character == 0
+ assert response[0].origin_selection_range.end.line == 3
+ assert response[0].origin_selection_range.end.character == 3
+
+
+@ConfiguredLS.decorate()
+def test_definition_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_DEFINITION,
+ DefinitionParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_diagnostics.py b/tests/lsp/test_diagnostics.py
new file mode 100644
index 0000000..c420942
--- /dev/null
+++ b/tests/lsp/test_diagnostics.py
@@ -0,0 +1,68 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import json
+from typing import Tuple
+
+from lsprotocol import types
+
+
+from ..client import LanguageClient
+
+
+async def test_diagnostics(
+ json_server_client: Tuple[LanguageClient, types.InitializeResult],
+ uri_for,
+):
+ """Ensure that diagnostics are working as expected."""
+ client, _ = json_server_client
+
+ test_uri = uri_for("example.json")
+ assert test_uri is not None
+
+ # Get the expected error message
+ document_content = "text"
+ try:
+ json.loads(document_content)
+ except json.JSONDecodeError as err:
+ expected_message = err.msg
+
+ client.text_document_did_open(
+ types.DidOpenTextDocumentParams(
+ text_document=types.TextDocumentItem(
+ uri=test_uri, language_id="json", version=1, text=document_content
+ )
+ )
+ )
+
+ await client.wait_for_notification(types.TEXT_DOCUMENT_PUBLISH_DIAGNOSTICS)
+
+ diagnostics = client.diagnostics[test_uri]
+ assert diagnostics[0].message == expected_message
+
+ result = await client.text_document_diagnostic_async(
+ types.DocumentDiagnosticParams(
+ text_document=types.TextDocumentIdentifier(test_uri)
+ )
+ )
+ diagnostics = result.items
+ assert diagnostics[0].message == expected_message
+
+ workspace_result = await client.workspace_diagnostic_async(
+ types.WorkspaceDiagnosticParams(previous_result_ids=[])
+ )
+ diagnostics = workspace_result.items[0].items
+ assert diagnostics[0].message == expected_message
diff --git a/tests/lsp/test_document_color.py b/tests/lsp/test_document_color.py
new file mode 100644
index 0000000..460a60b
--- /dev/null
+++ b/tests/lsp/test_document_color.py
@@ -0,0 +1,81 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import List
+
+from lsprotocol.types import TEXT_DOCUMENT_DOCUMENT_COLOR
+from lsprotocol.types import (
+ Color,
+ ColorInformation,
+ DocumentColorOptions,
+ DocumentColorParams,
+ Position,
+ Range,
+ TextDocumentIdentifier,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_DOCUMENT_COLOR,
+ DocumentColorOptions(),
+ )
+ def f(params: DocumentColorParams) -> List[ColorInformation]:
+ return [
+ ColorInformation(
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ color=Color(red=0.5, green=0.5, blue=0.5, alpha=0.5),
+ )
+ ]
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.color_provider
+
+
+@ConfiguredLS.decorate()
+def test_document_color(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_DOCUMENT_COLOR,
+ DocumentColorParams(
+ text_document=TextDocumentIdentifier(uri="file://return.list")
+ ),
+ ).result()
+
+ assert response
+ assert response[0].color.red == 0.5
+ assert response[0].color.green == 0.5
+ assert response[0].color.blue == 0.5
+ assert response[0].color.alpha == 0.5
+
+ assert response[0].range.start.line == 0
+ assert response[0].range.start.character == 0
+ assert response[0].range.end.line == 1
+ assert response[0].range.end.character == 1
diff --git a/tests/lsp/test_document_highlight.py b/tests/lsp/test_document_highlight.py
new file mode 100644
index 0000000..e4afc5f
--- /dev/null
+++ b/tests/lsp/test_document_highlight.py
@@ -0,0 +1,108 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import List, Optional
+
+from lsprotocol.types import TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT
+from lsprotocol.types import (
+ DocumentHighlight,
+ DocumentHighlightKind,
+ DocumentHighlightOptions,
+ DocumentHighlightParams,
+ Position,
+ Range,
+ TextDocumentIdentifier,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT,
+ DocumentHighlightOptions(),
+ )
+ def f(params: DocumentHighlightParams) -> Optional[List[DocumentHighlight]]:
+ if params.text_document.uri == "file://return.list":
+ return [
+ DocumentHighlight(
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ ),
+ DocumentHighlight(
+ range=Range(
+ start=Position(line=1, character=1),
+ end=Position(line=2, character=2),
+ ),
+ kind=DocumentHighlightKind.Write,
+ ),
+ ]
+ else:
+ return None
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.document_highlight_provider
+
+
+@ConfiguredLS.decorate()
+def test_document_highlight_return_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT,
+ DocumentHighlightParams(
+ text_document=TextDocumentIdentifier(uri="file://return.list"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response
+
+ assert response[0].range.start.line == 0
+ assert response[0].range.start.character == 0
+ assert response[0].range.end.line == 1
+ assert response[0].range.end.character == 1
+ assert response[0].kind is None
+
+ assert response[1].range.start.line == 1
+ assert response[1].range.start.character == 1
+ assert response[1].range.end.line == 2
+ assert response[1].range.end.character == 2
+ assert response[1].kind == DocumentHighlightKind.Write
+
+
+@ConfiguredLS.decorate()
+def test_document_highlight_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT,
+ DocumentHighlightParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_document_link.py b/tests/lsp/test_document_link.py
new file mode 100644
index 0000000..0602773
--- /dev/null
+++ b/tests/lsp/test_document_link.py
@@ -0,0 +1,98 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import List, Optional
+
+from lsprotocol.types import TEXT_DOCUMENT_DOCUMENT_LINK
+from lsprotocol.types import (
+ DocumentLink,
+ DocumentLinkOptions,
+ DocumentLinkParams,
+ Position,
+ Range,
+ TextDocumentIdentifier,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_DOCUMENT_LINK,
+ DocumentLinkOptions(resolve_provider=True),
+ )
+ def f(params: DocumentLinkParams) -> Optional[List[DocumentLink]]:
+ if params.text_document.uri == "file://return.list":
+ return [
+ DocumentLink(
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ target="target",
+ tooltip="tooltip",
+ data="data",
+ ),
+ ]
+ else:
+ return None
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.document_link_provider
+ assert capabilities.document_link_provider.resolve_provider
+
+
+@ConfiguredLS.decorate()
+def test_document_link_return_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_DOCUMENT_LINK,
+ DocumentLinkParams(
+ text_document=TextDocumentIdentifier(uri="file://return.list"),
+ ),
+ ).result()
+
+ assert response
+
+ assert response[0].range.start.line == 0
+ assert response[0].range.start.character == 0
+ assert response[0].range.end.line == 1
+ assert response[0].range.end.character == 1
+ assert response[0].target == "target"
+ assert response[0].tooltip == "tooltip"
+ assert response[0].data == "data"
+
+
+@ConfiguredLS.decorate()
+def test_document_link_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_DOCUMENT_LINK,
+ DocumentLinkParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_document_symbol.py b/tests/lsp/test_document_symbol.py
new file mode 100644
index 0000000..251c8fb
--- /dev/null
+++ b/tests/lsp/test_document_symbol.py
@@ -0,0 +1,168 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import List, Union
+
+from lsprotocol.types import TEXT_DOCUMENT_DOCUMENT_SYMBOL
+from lsprotocol.types import (
+ DocumentSymbol,
+ DocumentSymbolOptions,
+ DocumentSymbolParams,
+ Location,
+ Position,
+ Range,
+ SymbolInformation,
+ SymbolKind,
+ TextDocumentIdentifier,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_DOCUMENT_SYMBOL,
+ DocumentSymbolOptions(),
+ )
+ def f(
+ params: DocumentSymbolParams,
+ ) -> Union[List[SymbolInformation], List[DocumentSymbol]]:
+ symbol_info = SymbolInformation(
+ name="symbol",
+ kind=SymbolKind.Namespace,
+ location=Location(
+ uri="uri",
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ ),
+ container_name="container",
+ deprecated=False,
+ )
+
+ document_symbol_inner = DocumentSymbol(
+ name="inner_symbol",
+ kind=SymbolKind.Number,
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ selection_range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ )
+
+ document_symbol = DocumentSymbol(
+ name="symbol",
+ kind=SymbolKind.Object,
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=10, character=10),
+ ),
+ selection_range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=10, character=10),
+ ),
+ detail="detail",
+ children=[document_symbol_inner],
+ deprecated=True,
+ )
+
+ return { # type: ignore
+ "file://return.symbol_information_list": [symbol_info],
+ "file://return.document_symbol_list": [document_symbol],
+ }.get(params.text_document.uri, None)
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.document_symbol_provider
+
+
+@ConfiguredLS.decorate()
+def test_document_symbol_return_symbol_information_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_DOCUMENT_SYMBOL,
+ DocumentSymbolParams(
+ text_document=TextDocumentIdentifier(
+ uri="file://return.symbol_information_list"
+ ),
+ ),
+ ).result()
+
+ assert response
+
+ assert response[0].name == "symbol"
+ assert response[0].kind == SymbolKind.Namespace
+ assert response[0].location.uri == "uri"
+ assert response[0].location.range.start.line == 0
+ assert response[0].location.range.start.character == 0
+ assert response[0].location.range.end.line == 1
+ assert response[0].location.range.end.character == 1
+ assert response[0].container_name == "container"
+ assert not response[0].deprecated
+
+
+@ConfiguredLS.decorate()
+def test_document_symbol_return_document_symbol_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_DOCUMENT_SYMBOL,
+ DocumentSymbolParams(
+ text_document=TextDocumentIdentifier(
+ uri="file://return.document_symbol_list"
+ ),
+ ),
+ ).result()
+
+ assert response
+
+ assert response[0].name == "symbol"
+ assert response[0].kind == SymbolKind.Object
+ assert response[0].range.start.line == 0
+ assert response[0].range.start.character == 0
+ assert response[0].range.end.line == 10
+ assert response[0].range.end.character == 10
+ assert response[0].selection_range.start.line == 0
+ assert response[0].selection_range.start.character == 0
+ assert response[0].selection_range.end.line == 10
+ assert response[0].selection_range.end.character == 10
+ assert response[0].detail == "detail"
+ assert response[0].deprecated
+
+ assert response[0].children[0].name == "inner_symbol"
+ assert response[0].children[0].kind == SymbolKind.Number
+ assert response[0].children[0].range.start.line == 0
+ assert response[0].children[0].range.start.character == 0
+ assert response[0].children[0].range.end.line == 1
+ assert response[0].children[0].range.end.character == 1
+ range = response[0].children[0].selection_range
+ assert range.start.line == 0
+ assert range.start.character == 0
+ assert range.end.line == 1
+ assert range.end.character == 1
+
+ assert response[0].children[0].children is None
diff --git a/tests/lsp/test_errors.py b/tests/lsp/test_errors.py
new file mode 100644
index 0000000..1fbc05b
--- /dev/null
+++ b/tests/lsp/test_errors.py
@@ -0,0 +1,135 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import Any, List, Union
+import time
+
+import pytest
+
+from pygls.exceptions import JsonRpcInternalError, PyglsError, JsonRpcException
+from lsprotocol.types import WINDOW_SHOW_MESSAGE, MessageType
+from pygls.server import LanguageServer
+
+from ..conftest import ClientServer
+
+ERROR_TRIGGER = "test/triggerError"
+ERROR_MESSAGE = "Testing errors"
+
+
+class CustomLanguageServerSafe(LanguageServer):
+ def report_server_error(
+ self, error: Exception, source: Union[PyglsError, JsonRpcException]
+ ):
+ pass
+
+
+class CustomLanguageServerPotentialRecursion(LanguageServer):
+ def report_server_error(
+ self, error: Exception, source: Union[PyglsError, JsonRpcException]
+ ):
+ raise Exception()
+
+
+class CustomLanguageServerSendAll(LanguageServer):
+ def report_server_error(
+ self, error: Exception, source: Union[PyglsError, JsonRpcException]
+ ):
+ self.show_message(self.default_error_message, msg_type=MessageType.Error)
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self, LS=LanguageServer):
+ super().__init__(LS)
+ self.init()
+
+ def init(self):
+ self.client.messages: List[str] = []
+
+ @self.server.feature(ERROR_TRIGGER)
+ def f1(params: Any):
+ raise Exception(ERROR_MESSAGE)
+
+ @self.client.feature(WINDOW_SHOW_MESSAGE)
+ def f2(params: Any):
+ self.client.messages.append(params.message)
+
+
+class CustomConfiguredLSSafe(ConfiguredLS):
+ def __init__(self):
+ super().__init__(CustomLanguageServerSafe)
+
+
+class CustomConfiguredLSPotentialRecusrion(ConfiguredLS):
+ def __init__(self):
+ super().__init__(CustomLanguageServerPotentialRecursion)
+
+
+class CustomConfiguredLSSendAll(ConfiguredLS):
+ def __init__(self):
+ super().__init__(CustomLanguageServerSendAll)
+
+
+@ConfiguredLS.decorate()
+def test_request_error_reporting_default(client_server):
+ client, _ = client_server
+ assert len(client.messages) == 0
+
+ with pytest.raises(JsonRpcInternalError, match=ERROR_MESSAGE):
+ client.lsp.send_request(ERROR_TRIGGER).result()
+
+ time.sleep(0.1)
+ assert len(client.messages) == 0
+
+
+@CustomConfiguredLSSendAll.decorate()
+def test_request_error_reporting_override(client_server):
+ client, _ = client_server
+ assert len(client.messages) == 0
+
+ with pytest.raises(JsonRpcInternalError, match=ERROR_MESSAGE):
+ client.lsp.send_request(ERROR_TRIGGER).result()
+
+ time.sleep(0.1)
+ assert len(client.messages) == 1
+
+
+@ConfiguredLS.decorate()
+def test_notification_error_reporting(client_server):
+ client, _ = client_server
+ client.lsp.notify(ERROR_TRIGGER)
+ time.sleep(0.1)
+
+ assert len(client.messages) == 1
+ assert client.messages[0] == LanguageServer.default_error_message
+
+
+@CustomConfiguredLSSafe.decorate()
+def test_overriding_error_reporting(client_server):
+ client, _ = client_server
+ client.lsp.notify(ERROR_TRIGGER)
+ time.sleep(0.1)
+
+ assert len(client.messages) == 0
+
+
+@CustomConfiguredLSPotentialRecusrion.decorate()
+def test_overriding_error_reporting_with_potential_recursion(client_server):
+ client, _ = client_server
+ client.lsp.notify(ERROR_TRIGGER)
+ time.sleep(0.1)
+
+ assert len(client.messages) == 0
diff --git a/tests/lsp/test_folding_range.py b/tests/lsp/test_folding_range.py
new file mode 100644
index 0000000..8f9c749
--- /dev/null
+++ b/tests/lsp/test_folding_range.py
@@ -0,0 +1,92 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import List, Optional
+
+from lsprotocol.types import TEXT_DOCUMENT_FOLDING_RANGE
+from lsprotocol.types import (
+ FoldingRange,
+ FoldingRangeKind,
+ FoldingRangeOptions,
+ FoldingRangeParams,
+ TextDocumentIdentifier,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_FOLDING_RANGE,
+ FoldingRangeOptions(),
+ )
+ def f(params: FoldingRangeParams) -> Optional[List[FoldingRange]]:
+ if params.text_document.uri == "file://return.list":
+ return [
+ FoldingRange(
+ start_line=0,
+ end_line=0,
+ start_character=1,
+ end_character=1,
+ kind=FoldingRangeKind.Comment,
+ ),
+ ]
+ else:
+ return None
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.folding_range_provider
+
+
+@ConfiguredLS.decorate()
+def test_folding_range_return_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_FOLDING_RANGE,
+ FoldingRangeParams(
+ text_document=TextDocumentIdentifier(uri="file://return.list"),
+ ),
+ ).result()
+
+ assert response
+
+ assert response[0].start_line == 0
+ assert response[0].end_line == 0
+ assert response[0].start_character == 1
+ assert response[0].end_character == 1
+ assert response[0].kind == FoldingRangeKind.Comment
+
+
+@ConfiguredLS.decorate()
+def test_folding_range_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_FOLDING_RANGE,
+ FoldingRangeParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_formatting.py b/tests/lsp/test_formatting.py
new file mode 100644
index 0000000..0c3fbd6
--- /dev/null
+++ b/tests/lsp/test_formatting.py
@@ -0,0 +1,108 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import List, Optional
+
+from lsprotocol.types import TEXT_DOCUMENT_FORMATTING
+from lsprotocol.types import (
+ DocumentFormattingOptions,
+ DocumentFormattingParams,
+ FormattingOptions,
+ Position,
+ Range,
+ TextDocumentIdentifier,
+ TextEdit,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_FORMATTING,
+ DocumentFormattingOptions(),
+ )
+ def f(params: DocumentFormattingParams) -> Optional[List[TextEdit]]:
+ if params.text_document.uri == "file://return.list":
+ return [
+ TextEdit(
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ new_text="text",
+ )
+ ]
+ else:
+ return None
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.document_formatting_provider
+
+
+@ConfiguredLS.decorate()
+def test_document_formatting_return_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_FORMATTING,
+ DocumentFormattingParams(
+ text_document=TextDocumentIdentifier(uri="file://return.list"),
+ options=FormattingOptions(
+ tab_size=2,
+ insert_spaces=True,
+ trim_trailing_whitespace=True,
+ insert_final_newline=True,
+ trim_final_newlines=True,
+ ),
+ ),
+ ).result()
+
+ assert response
+
+ assert response[0].new_text == "text"
+ assert response[0].range.start.line == 0
+ assert response[0].range.start.character == 0
+ assert response[0].range.end.line == 1
+ assert response[0].range.end.character == 1
+
+
+@ConfiguredLS.decorate()
+def test_document_formatting_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_FORMATTING,
+ DocumentFormattingParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ options=FormattingOptions(
+ tab_size=2,
+ insert_spaces=True,
+ trim_trailing_whitespace=True,
+ insert_final_newline=True,
+ trim_final_newlines=True,
+ ),
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_hover.py b/tests/lsp/test_hover.py
new file mode 100644
index 0000000..9007c78
--- /dev/null
+++ b/tests/lsp/test_hover.py
@@ -0,0 +1,149 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import Optional
+
+from lsprotocol.types import TEXT_DOCUMENT_HOVER
+from lsprotocol.types import (
+ Hover,
+ HoverOptions,
+ HoverParams,
+ MarkedString_Type1,
+ MarkupContent,
+ MarkupKind,
+ Position,
+ Range,
+ TextDocumentIdentifier,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_HOVER,
+ HoverOptions(),
+ )
+ def f(params: HoverParams) -> Optional[Hover]:
+ range = Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ )
+
+ return {
+ "file://return.marked_string": Hover(
+ range=range,
+ contents=MarkedString_Type1(
+ language="language",
+ value="value",
+ ),
+ ),
+ "file://return.marked_string_list": Hover(
+ range=range,
+ contents=[
+ MarkedString_Type1(
+ language="language",
+ value="value",
+ ),
+ "str type",
+ ],
+ ),
+ "file://return.markup_content": Hover(
+ range=range,
+ contents=MarkupContent(kind=MarkupKind.Markdown, value="value"),
+ ),
+ }.get(params.text_document.uri, None)
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.hover_provider
+
+
+@ConfiguredLS.decorate()
+def test_hover_return_marked_string(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_HOVER,
+ HoverParams(
+ text_document=TextDocumentIdentifier(uri="file://return.marked_string"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response
+
+ assert response.contents.language == "language"
+ assert response.contents.value == "value"
+
+ assert response.range.start.line == 0
+ assert response.range.start.character == 0
+ assert response.range.end.line == 1
+ assert response.range.end.character == 1
+
+
+@ConfiguredLS.decorate()
+def test_hover_return_marked_string_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_HOVER,
+ HoverParams(
+ text_document=TextDocumentIdentifier(
+ uri="file://return.marked_string_list"
+ ),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response
+
+ assert response.contents[0].language == "language"
+ assert response.contents[0].value == "value"
+ assert response.contents[1] == "str type"
+
+ assert response.range.start.line == 0
+ assert response.range.start.character == 0
+ assert response.range.end.line == 1
+ assert response.range.end.character == 1
+
+
+@ConfiguredLS.decorate()
+def test_hover_return_markup_content(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_HOVER,
+ HoverParams(
+ text_document=TextDocumentIdentifier(uri="file://return.markup_content"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response
+
+ assert response.contents.kind == MarkupKind.Markdown
+ assert response.contents.value == "value"
+
+ assert response.range.start.line == 0
+ assert response.range.start.character == 0
+ assert response.range.end.line == 1
+ assert response.range.end.character == 1
diff --git a/tests/lsp/test_implementation.py b/tests/lsp/test_implementation.py
new file mode 100644
index 0000000..4fea3a9
--- /dev/null
+++ b/tests/lsp/test_implementation.py
@@ -0,0 +1,164 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import List, Optional, Union
+
+from lsprotocol.types import TEXT_DOCUMENT_IMPLEMENTATION
+from lsprotocol.types import (
+ ImplementationOptions,
+ ImplementationParams,
+ Location,
+ LocationLink,
+ Position,
+ Range,
+ TextDocumentIdentifier,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_IMPLEMENTATION,
+ ImplementationOptions(),
+ )
+ def f(
+ params: ImplementationParams,
+ ) -> Optional[Union[Location, List[Location], List[LocationLink]]]:
+ location = Location(
+ uri="uri",
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ )
+
+ location_link = LocationLink(
+ target_uri="uri",
+ target_range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ target_selection_range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=2, character=2),
+ ),
+ origin_selection_range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=3, character=3),
+ ),
+ )
+
+ return { # type: ignore
+ "file://return.location": location,
+ "file://return.location_list": [location],
+ "file://return.location_link_list": [location_link],
+ }.get(params.text_document.uri, None)
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.implementation_provider
+
+
+@ConfiguredLS.decorate()
+def test_type_definition_return_location(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_IMPLEMENTATION,
+ ImplementationParams(
+ text_document=TextDocumentIdentifier(uri="file://return.location"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response.uri == "uri"
+
+ assert response.range.start.line == 0
+ assert response.range.start.character == 0
+ assert response.range.end.line == 1
+ assert response.range.end.character == 1
+
+
+@ConfiguredLS.decorate()
+def test_type_definition_return_location_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_IMPLEMENTATION,
+ ImplementationParams(
+ text_document=TextDocumentIdentifier(uri="file://return.location_list"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response[0].uri == "uri"
+
+ assert response[0].range.start.line == 0
+ assert response[0].range.start.character == 0
+ assert response[0].range.end.line == 1
+ assert response[0].range.end.character == 1
+
+
+@ConfiguredLS.decorate()
+def test_type_definition_return_location_link_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_IMPLEMENTATION,
+ ImplementationParams(
+ text_document=TextDocumentIdentifier(
+ uri="file://return.location_link_list"
+ ),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response[0].target_uri == "uri"
+
+ assert response[0].target_range.start.line == 0
+ assert response[0].target_range.start.character == 0
+ assert response[0].target_range.end.line == 1
+ assert response[0].target_range.end.character == 1
+
+ assert response[0].target_selection_range.start.line == 0
+ assert response[0].target_selection_range.start.character == 0
+ assert response[0].target_selection_range.end.line == 2
+ assert response[0].target_selection_range.end.character == 2
+
+ assert response[0].origin_selection_range.start.line == 0
+ assert response[0].origin_selection_range.start.character == 0
+ assert response[0].origin_selection_range.end.line == 3
+ assert response[0].origin_selection_range.end.character == 3
+
+
+@ConfiguredLS.decorate()
+def test_type_definition_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_IMPLEMENTATION,
+ ImplementationParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_inlay_hints.py b/tests/lsp/test_inlay_hints.py
new file mode 100644
index 0000000..1146e1b
--- /dev/null
+++ b/tests/lsp/test_inlay_hints.py
@@ -0,0 +1,56 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from typing import Tuple
+
+from lsprotocol import types
+
+from ..client import LanguageClient
+
+
+async def test_code_actions(
+ inlay_hints_client: Tuple[LanguageClient, types.InitializeResult], uri_for
+):
+ """Ensure that the example code action server is working as expected."""
+ client, initialize_result = inlay_hints_client
+
+ inlay_hint_provider = initialize_result.capabilities.inlay_hint_provider
+ assert inlay_hint_provider.resolve_provider is True
+
+ test_uri = uri_for("sums.txt")
+ assert test_uri is not None
+
+ response = await client.text_document_inlay_hint_async(
+ types.InlayHintParams(
+ text_document=types.TextDocumentIdentifier(uri=test_uri),
+ range=types.Range(
+ start=types.Position(line=3, character=0),
+ end=types.Position(line=4, character=0),
+ ),
+ )
+ )
+
+ assert len(response) == 2
+ two, three = response[0], response[1]
+
+ assert two.label == ":10"
+ assert two.tooltip is None
+
+ assert three.label == ":11"
+ assert three.tooltip is None
+
+ resolved = await client.inlay_hint_resolve_async(three)
+ assert resolved.tooltip == "Binary representation of the number: 3"
diff --git a/tests/lsp/test_inline_value.py b/tests/lsp/test_inline_value.py
new file mode 100644
index 0000000..68d682d
--- /dev/null
+++ b/tests/lsp/test_inline_value.py
@@ -0,0 +1,60 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from typing import Tuple
+
+from lsprotocol import types
+
+
+from ..client import LanguageClient
+
+
+async def test_inline_value(
+ json_server_client: Tuple[LanguageClient, types.InitializeResult],
+ uri_for,
+):
+ """Ensure that inline values are working as expected."""
+ client, _ = json_server_client
+
+ test_uri = uri_for("example.json")
+ assert test_uri is not None
+
+ document_content = '{\n"foo": "bar"\n}'
+ client.text_document_did_open(
+ types.DidOpenTextDocumentParams(
+ text_document=types.TextDocumentItem(
+ uri=test_uri, language_id="json", version=1, text=document_content
+ )
+ )
+ )
+
+ result = await client.text_document_inline_value_async(
+ types.InlineValueParams(
+ text_document=types.TextDocumentIdentifier(test_uri),
+ range=types.Range(
+ start=types.Position(line=1, character=0),
+ end=types.Position(line=1, character=6),
+ ),
+ context=types.InlineValueContext(
+ frame_id=1,
+ stopped_location=types.Range(
+ start=types.Position(line=1, character=0),
+ end=types.Position(line=1, character=6),
+ ),
+ ),
+ )
+ )
+ assert result[0].text == "Inline value"
diff --git a/tests/lsp/test_linked_editing_range.py b/tests/lsp/test_linked_editing_range.py
new file mode 100644
index 0000000..2650b7e
--- /dev/null
+++ b/tests/lsp/test_linked_editing_range.py
@@ -0,0 +1,103 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import Optional
+
+from lsprotocol.types import TEXT_DOCUMENT_LINKED_EDITING_RANGE
+from lsprotocol.types import (
+ LinkedEditingRangeOptions,
+ LinkedEditingRangeParams,
+ LinkedEditingRanges,
+ Position,
+ Range,
+ TextDocumentIdentifier,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_LINKED_EDITING_RANGE,
+ LinkedEditingRangeOptions(),
+ )
+ def f(params: LinkedEditingRangeParams) -> Optional[LinkedEditingRanges]:
+ if params.text_document.uri == "file://return.ranges":
+ return LinkedEditingRanges(
+ ranges=[
+ Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ Range(
+ start=Position(line=1, character=1),
+ end=Position(line=2, character=2),
+ ),
+ ],
+ word_pattern="pattern",
+ )
+ else:
+ return None
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.linked_editing_range_provider
+
+
+@ConfiguredLS.decorate()
+def test_linked_editing_ranges_return_ranges(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_LINKED_EDITING_RANGE,
+ LinkedEditingRangeParams(
+ text_document=TextDocumentIdentifier(uri="file://return.ranges"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response
+
+ assert response.ranges[0].start.line == 0
+ assert response.ranges[0].start.character == 0
+ assert response.ranges[0].end.line == 1
+ assert response.ranges[0].end.character == 1
+ assert response.ranges[1].start.line == 1
+ assert response.ranges[1].start.character == 1
+ assert response.ranges[1].end.line == 2
+ assert response.ranges[1].end.character == 2
+ assert response.word_pattern == "pattern"
+
+
+@ConfiguredLS.decorate()
+def test_linked_editing_ranges_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_LINKED_EDITING_RANGE,
+ LinkedEditingRangeParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_moniker.py b/tests/lsp/test_moniker.py
new file mode 100644
index 0000000..09b962d
--- /dev/null
+++ b/tests/lsp/test_moniker.py
@@ -0,0 +1,94 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import List, Optional
+
+from lsprotocol.types import TEXT_DOCUMENT_MONIKER
+from lsprotocol.types import (
+ Moniker,
+ MonikerKind,
+ MonikerOptions,
+ MonikerParams,
+ Position,
+ TextDocumentIdentifier,
+ UniquenessLevel,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_MONIKER,
+ MonikerOptions(),
+ )
+ def f(params: MonikerParams) -> Optional[List[Moniker]]:
+ if params.text_document.uri == "file://return.list":
+ return [
+ Moniker(
+ scheme="test_scheme",
+ identifier="test_identifier",
+ unique=UniquenessLevel.Global,
+ kind=MonikerKind.Local,
+ ),
+ ]
+ else:
+ return None
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.moniker_provider
+
+
+@ConfiguredLS.decorate()
+def test_moniker_return_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_MONIKER,
+ MonikerParams(
+ text_document=TextDocumentIdentifier(uri="file://return.list"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response
+
+ assert response[0].scheme == "test_scheme"
+ assert response[0].identifier == "test_identifier"
+ assert response[0].unique == UniquenessLevel.Global
+ assert response[0].kind == MonikerKind.Local
+
+
+@ConfiguredLS.decorate()
+def test_references_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_MONIKER,
+ MonikerParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_on_type_formatting.py b/tests/lsp/test_on_type_formatting.py
new file mode 100644
index 0000000..2e7adc9
--- /dev/null
+++ b/tests/lsp/test_on_type_formatting.py
@@ -0,0 +1,122 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import List, Optional
+
+from lsprotocol.types import TEXT_DOCUMENT_ON_TYPE_FORMATTING
+from lsprotocol.types import (
+ DocumentOnTypeFormattingOptions,
+ DocumentOnTypeFormattingParams,
+ FormattingOptions,
+ Position,
+ Range,
+ TextDocumentIdentifier,
+ TextEdit,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_ON_TYPE_FORMATTING,
+ DocumentOnTypeFormattingOptions(
+ first_trigger_character=":",
+ more_trigger_character=[",", "."],
+ ),
+ )
+ def f(params: DocumentOnTypeFormattingParams) -> Optional[List[TextEdit]]:
+ if params.text_document.uri == "file://return.list":
+ return [
+ TextEdit(
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ new_text="text",
+ )
+ ]
+ else:
+ return None
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.document_on_type_formatting_provider
+ assert (
+ capabilities.document_on_type_formatting_provider.first_trigger_character == ":"
+ )
+ assert capabilities.document_on_type_formatting_provider.more_trigger_character == [
+ ",",
+ ".",
+ ]
+
+
+@ConfiguredLS.decorate()
+def test_on_type_formatting_return_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_ON_TYPE_FORMATTING,
+ DocumentOnTypeFormattingParams(
+ text_document=TextDocumentIdentifier(uri="file://return.list"),
+ position=Position(line=0, character=0),
+ ch=":",
+ options=FormattingOptions(
+ tab_size=2,
+ insert_spaces=True,
+ trim_trailing_whitespace=True,
+ insert_final_newline=True,
+ trim_final_newlines=True,
+ ),
+ ),
+ ).result()
+
+ assert response
+
+ assert response[0].new_text == "text"
+ assert response[0].range.start.line == 0
+ assert response[0].range.start.character == 0
+ assert response[0].range.end.line == 1
+ assert response[0].range.end.character == 1
+
+
+@ConfiguredLS.decorate()
+def test_on_type_formatting_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_ON_TYPE_FORMATTING,
+ DocumentOnTypeFormattingParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ position=Position(line=0, character=0),
+ ch=":",
+ options=FormattingOptions(
+ tab_size=2,
+ insert_spaces=True,
+ trim_trailing_whitespace=True,
+ insert_final_newline=True,
+ trim_final_newlines=True,
+ ),
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_prepare_rename.py b/tests/lsp/test_prepare_rename.py
new file mode 100644
index 0000000..39d0712
--- /dev/null
+++ b/tests/lsp/test_prepare_rename.py
@@ -0,0 +1,111 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import Optional, Union
+
+from lsprotocol.types import TEXT_DOCUMENT_PREPARE_RENAME
+from lsprotocol.types import (
+ Position,
+ PrepareRenameResult,
+ PrepareRenameResult_Type1,
+ PrepareRenameParams,
+ Range,
+ TextDocumentIdentifier,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(TEXT_DOCUMENT_PREPARE_RENAME)
+ def f(
+ params: PrepareRenameParams,
+ ) -> Optional[Union[Range, PrepareRenameResult]]:
+ return { # type: ignore
+ "file://return.range": Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ "file://return.prepare_rename": PrepareRenameResult_Type1(
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ placeholder="placeholder",
+ ),
+ }.get(params.text_document.uri, None)
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ pass
+
+
+@ConfiguredLS.decorate()
+def test_prepare_rename_return_range(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_PREPARE_RENAME,
+ PrepareRenameParams(
+ text_document=TextDocumentIdentifier(uri="file://return.range"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response
+
+ assert response.start.line == 0
+ assert response.start.character == 0
+ assert response.end.line == 1
+ assert response.end.character == 1
+
+
+@ConfiguredLS.decorate()
+def test_prepare_rename_return_prepare_rename(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_PREPARE_RENAME,
+ PrepareRenameParams(
+ text_document=TextDocumentIdentifier(uri="file://return.prepare_rename"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response
+
+ assert response.range.start.line == 0
+ assert response.range.start.character == 0
+ assert response.range.end.line == 1
+ assert response.range.end.character == 1
+ assert response.placeholder == "placeholder"
+
+
+@ConfiguredLS.decorate()
+def test_prepare_rename_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_PREPARE_RENAME,
+ PrepareRenameParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_progress.py b/tests/lsp/test_progress.py
new file mode 100644
index 0000000..4965772
--- /dev/null
+++ b/tests/lsp/test_progress.py
@@ -0,0 +1,225 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+import asyncio
+from typing import List, Optional
+
+import pytest
+from lsprotocol.types import (
+ TEXT_DOCUMENT_CODE_LENS,
+ WINDOW_WORK_DONE_PROGRESS_CANCEL,
+ WINDOW_WORK_DONE_PROGRESS_CREATE,
+ PROGRESS,
+)
+from lsprotocol.types import (
+ CodeLens,
+ CodeLensParams,
+ CodeLensOptions,
+ ProgressParams,
+ TextDocumentIdentifier,
+ WorkDoneProgressBegin,
+ WorkDoneProgressEnd,
+ WorkDoneProgressReport,
+ WorkDoneProgressCancelParams,
+ WorkDoneProgressCreateParams,
+)
+from ..conftest import ClientServer
+from pygls import IS_PYODIDE
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+ self.client.notifications: List[ProgressParams] = []
+ self.client.method_calls: List[WorkDoneProgressCreateParams] = []
+
+ @self.server.feature(
+ TEXT_DOCUMENT_CODE_LENS,
+ CodeLensOptions(resolve_provider=False, work_done_progress=True),
+ )
+ async def f1(params: CodeLensParams) -> Optional[List[CodeLens]]:
+ if "client_initiated_token" in params.text_document.uri:
+ token = params.work_done_token
+ else:
+ assert "server_initiated_token" in params.text_document.uri
+ token = params.text_document.uri[len("file://") :]
+ if "async" in params.text_document.uri:
+ await self.server.progress.create_async(token)
+ else:
+ f = self.server.progress.create(token)
+ await asyncio.sleep(0.1)
+ f.result()
+
+ assert token
+ self.server.lsp.progress.begin(
+ token,
+ WorkDoneProgressBegin(kind="begin", title="starting", percentage=0),
+ )
+ await asyncio.sleep(0.1)
+ if self.server.lsp.progress.tokens[token].cancelled():
+ self.server.lsp.progress.end(
+ token, WorkDoneProgressEnd(kind="end", message="cancelled")
+ )
+ else:
+ self.server.lsp.progress.report(
+ token,
+ WorkDoneProgressReport(
+ kind="report", message="doing", percentage=50
+ ),
+ )
+ self.server.lsp.progress.end(
+ token, WorkDoneProgressEnd(kind="end", message="done")
+ )
+ return None
+
+ @self.client.feature(PROGRESS)
+ def f2(params):
+ self.client.notifications.append(params)
+ if params.value["kind"] == "begin" and "cancel" in params.token:
+ # client cancels the progress token
+ self.client.lsp.notify(
+ WINDOW_WORK_DONE_PROGRESS_CANCEL,
+ WorkDoneProgressCancelParams(token=params.token),
+ )
+
+ @self.client.feature(WINDOW_WORK_DONE_PROGRESS_CREATE)
+ def f3(params: WorkDoneProgressCreateParams):
+ self.client.method_calls.append(params)
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ provider = capabilities.code_lens_provider
+ assert provider
+ assert provider.work_done_progress
+
+
+@pytest.mark.skipif(IS_PYODIDE, reason="threads are not available in pyodide.")
+@ConfiguredLS.decorate()
+async def test_progress_notifications(client_server):
+ client, _ = client_server
+ client.lsp.send_request(
+ TEXT_DOCUMENT_CODE_LENS,
+ CodeLensParams(
+ text_document=TextDocumentIdentifier(uri="file://client_initiated_token"),
+ work_done_token="token",
+ ),
+ ).result()
+
+ assert [notif.value for notif in client.notifications] == [
+ {
+ "kind": "begin",
+ "title": "starting",
+ "percentage": 0,
+ },
+ {
+ "kind": "report",
+ "message": "doing",
+ "percentage": 50,
+ },
+ {"kind": "end", "message": "done"},
+ ]
+ assert {notif.token for notif in client.notifications} == {"token"}
+
+
+@pytest.mark.skipif(IS_PYODIDE, reason="threads are not available in pyodide.")
+@pytest.mark.parametrize("registration", ("sync", "async"))
+@ConfiguredLS.decorate()
+async def test_server_initiated_progress_notifications(client_server, registration):
+ client, _ = client_server
+ client.lsp.send_request(
+ TEXT_DOCUMENT_CODE_LENS,
+ CodeLensParams(
+ text_document=TextDocumentIdentifier(
+ uri=f"file://server_initiated_token_{registration}"
+ ),
+ work_done_token="token",
+ ),
+ ).result()
+
+ assert [notif.value for notif in client.notifications] == [
+ {
+ "kind": "begin",
+ "title": "starting",
+ "percentage": 0,
+ },
+ {
+ "kind": "report",
+ "message": "doing",
+ "percentage": 50,
+ },
+ {"kind": "end", "message": "done"},
+ ]
+ assert {notif.token for notif in client.notifications} == {
+ f"server_initiated_token_{registration}"
+ }
+ assert [mc.token for mc in client.method_calls] == [
+ f"server_initiated_token_{registration}"
+ ]
+
+
+@pytest.mark.skipif(IS_PYODIDE, reason="threads are not available in pyodide.")
+@ConfiguredLS.decorate()
+def test_progress_cancel_notifications(client_server):
+ client, _ = client_server
+ client.lsp.send_request(
+ TEXT_DOCUMENT_CODE_LENS,
+ CodeLensParams(
+ text_document=TextDocumentIdentifier(uri="file://client_initiated_token"),
+ work_done_token="token_with_cancellation",
+ ),
+ ).result()
+ assert [notif.value for notif in client.notifications] == [
+ {
+ "kind": "begin",
+ "title": "starting",
+ "percentage": 0,
+ },
+ {"kind": "end", "message": "cancelled"},
+ ]
+ assert {notif.token for notif in client.notifications} == {
+ "token_with_cancellation"
+ }
+
+
+@pytest.mark.skipif(IS_PYODIDE, reason="threads are not available in pyodide.")
+@pytest.mark.parametrize("registration", ("sync", "async"))
+@ConfiguredLS.decorate()
+def test_server_initiated_progress_progress_cancel_notifications(
+ client_server, registration
+):
+ client, _ = client_server
+ client.lsp.send_request(
+ TEXT_DOCUMENT_CODE_LENS,
+ CodeLensParams(
+ text_document=TextDocumentIdentifier(
+ uri=f"file://server_initiated_token_{registration}_with_cancellation"
+ ),
+ ),
+ ).result()
+
+ assert [notif.value for notif in client.notifications] == [
+ {
+ "kind": "begin",
+ "title": "starting",
+ "percentage": 0,
+ },
+ {"kind": "end", "message": "cancelled"},
+ ]
diff --git a/tests/lsp/test_range_formatting.py b/tests/lsp/test_range_formatting.py
new file mode 100644
index 0000000..e7a118c
--- /dev/null
+++ b/tests/lsp/test_range_formatting.py
@@ -0,0 +1,116 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import List, Optional
+
+from lsprotocol.types import TEXT_DOCUMENT_RANGE_FORMATTING
+from lsprotocol.types import (
+ DocumentRangeFormattingOptions,
+ DocumentRangeFormattingParams,
+ FormattingOptions,
+ Position,
+ Range,
+ TextDocumentIdentifier,
+ TextEdit,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_RANGE_FORMATTING,
+ DocumentRangeFormattingOptions(),
+ )
+ def f(params: DocumentRangeFormattingParams) -> Optional[List[TextEdit]]:
+ if params.text_document.uri == "file://return.list":
+ return [
+ TextEdit(
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ new_text="text",
+ )
+ ]
+ else:
+ return None
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.document_range_formatting_provider
+
+
+@ConfiguredLS.decorate()
+def test_range_formatting_return_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_RANGE_FORMATTING,
+ DocumentRangeFormattingParams(
+ text_document=TextDocumentIdentifier(uri="file://return.list"),
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ options=FormattingOptions(
+ tab_size=2,
+ insert_spaces=True,
+ trim_trailing_whitespace=True,
+ insert_final_newline=True,
+ trim_final_newlines=True,
+ ),
+ ),
+ ).result()
+
+ assert response
+
+ assert response[0].new_text == "text"
+ assert response[0].range.start.line == 0
+ assert response[0].range.start.character == 0
+ assert response[0].range.end.line == 1
+ assert response[0].range.end.character == 1
+
+
+@ConfiguredLS.decorate()
+def test_range_formatting_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_RANGE_FORMATTING,
+ DocumentRangeFormattingParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ options=FormattingOptions(
+ tab_size=2,
+ insert_spaces=True,
+ trim_trailing_whitespace=True,
+ insert_final_newline=True,
+ trim_final_newlines=True,
+ ),
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_references.py b/tests/lsp/test_references.py
new file mode 100644
index 0000000..5867e35
--- /dev/null
+++ b/tests/lsp/test_references.py
@@ -0,0 +1,103 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import List, Optional
+
+from lsprotocol.types import TEXT_DOCUMENT_REFERENCES
+from lsprotocol.types import (
+ Location,
+ Position,
+ Range,
+ ReferenceContext,
+ ReferenceOptions,
+ ReferenceParams,
+ TextDocumentIdentifier,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_REFERENCES,
+ ReferenceOptions(),
+ )
+ def f(params: ReferenceParams) -> Optional[List[Location]]:
+ if params.text_document.uri == "file://return.list":
+ return [
+ Location(
+ uri="uri",
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ ),
+ ]
+ else:
+ return None
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.references_provider
+
+
+@ConfiguredLS.decorate()
+def test_references_return_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_REFERENCES,
+ ReferenceParams(
+ text_document=TextDocumentIdentifier(uri="file://return.list"),
+ position=Position(line=0, character=0),
+ context=ReferenceContext(
+ include_declaration=True,
+ ),
+ ),
+ ).result()
+
+ assert response
+
+ assert response[0].uri == "uri"
+
+ assert response[0].range.start.line == 0
+ assert response[0].range.start.character == 0
+ assert response[0].range.end.line == 1
+ assert response[0].range.end.character == 1
+
+
+@ConfiguredLS.decorate()
+def test_references_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_REFERENCES,
+ ReferenceParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ position=Position(line=0, character=0),
+ context=ReferenceContext(
+ include_declaration=True,
+ ),
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_rename.py b/tests/lsp/test_rename.py
new file mode 100644
index 0000000..48cface
--- /dev/null
+++ b/tests/lsp/test_rename.py
@@ -0,0 +1,195 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import Optional
+
+from lsprotocol.types import TEXT_DOCUMENT_RENAME
+from lsprotocol.types import (
+ CreateFile,
+ CreateFileOptions,
+ DeleteFile,
+ DeleteFileOptions,
+ OptionalVersionedTextDocumentIdentifier,
+ Position,
+ Range,
+ RenameFile,
+ RenameFileOptions,
+ RenameOptions,
+ RenameParams,
+ ResourceOperationKind,
+ TextDocumentEdit,
+ TextDocumentIdentifier,
+ TextEdit,
+ WorkspaceEdit,
+)
+
+from ..conftest import ClientServer
+
+workspace_edit = {
+ "changes": {
+ "uri1": [
+ TextEdit(
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ new_text="text1",
+ ),
+ TextEdit(
+ range=Range(
+ start=Position(line=1, character=1),
+ end=Position(line=2, character=2),
+ ),
+ new_text="text2",
+ ),
+ ],
+ },
+ "document_changes": [
+ TextDocumentEdit(
+ text_document=OptionalVersionedTextDocumentIdentifier(
+ uri="uri",
+ version=3,
+ ),
+ edits=[
+ TextEdit(
+ range=Range(
+ start=Position(line=2, character=2),
+ end=Position(line=3, character=3),
+ ),
+ new_text="text3",
+ ),
+ ],
+ ),
+ CreateFile(
+ kind=ResourceOperationKind.Create.value,
+ uri="create file",
+ options=CreateFileOptions(
+ overwrite=True,
+ ignore_if_exists=True,
+ ),
+ ),
+ RenameFile(
+ kind=ResourceOperationKind.Rename.value,
+ old_uri="rename old uri",
+ new_uri="rename new uri",
+ options=RenameFileOptions(
+ overwrite=True,
+ ignore_if_exists=True,
+ ),
+ ),
+ DeleteFile(
+ kind=ResourceOperationKind.Delete.value,
+ uri="delete file",
+ options=DeleteFileOptions(
+ recursive=True,
+ ignore_if_not_exists=True,
+ ),
+ ),
+ ],
+}
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_RENAME,
+ RenameOptions(prepare_provider=True),
+ )
+ def f(params: RenameParams) -> Optional[WorkspaceEdit]:
+ if params.text_document.uri == "file://return.workspace_edit":
+ return WorkspaceEdit(**workspace_edit)
+ else:
+ return None
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.rename_provider
+ assert capabilities.rename_provider.prepare_provider
+
+
+@ConfiguredLS.decorate()
+def test_rename_return_workspace_edit(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_RENAME,
+ RenameParams(
+ text_document=TextDocumentIdentifier(uri="file://return.workspace_edit"),
+ position=Position(line=0, character=0),
+ new_name="new name",
+ ),
+ ).result()
+
+ assert response
+
+ changes = response.changes["uri1"]
+ assert changes[0].new_text == "text1"
+ assert changes[0].range.start.line == 0
+ assert changes[0].range.start.character == 0
+ assert changes[0].range.end.line == 1
+ assert changes[0].range.end.character == 1
+
+ assert changes[1].new_text == "text2"
+ assert changes[1].range.start.line == 1
+ assert changes[1].range.start.character == 1
+ assert changes[1].range.end.line == 2
+ assert changes[1].range.end.character == 2
+
+ changes = response.document_changes
+ assert changes[0].text_document.uri == "uri"
+ assert changes[0].text_document.version == 3
+ assert changes[0].edits[0].new_text == "text3"
+ assert changes[0].edits[0].range.start.line == 2
+ assert changes[0].edits[0].range.start.character == 2
+ assert changes[0].edits[0].range.end.line == 3
+ assert changes[0].edits[0].range.end.character == 3
+
+ assert changes[1].kind == ResourceOperationKind.Create.value
+ assert changes[1].uri == "create file"
+ assert changes[1].options.ignore_if_exists
+ assert changes[1].options.overwrite
+
+ assert changes[2].kind == ResourceOperationKind.Rename.value
+ assert changes[2].new_uri == "rename new uri"
+ assert changes[2].old_uri == "rename old uri"
+ assert changes[2].options.ignore_if_exists
+ assert changes[2].options.overwrite
+
+ assert changes[3].kind == ResourceOperationKind.Delete.value
+ assert changes[3].uri == "delete file"
+ assert changes[3].options.ignore_if_not_exists
+ assert changes[3].options.recursive
+
+
+@ConfiguredLS.decorate()
+def test_rename_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_RENAME,
+ RenameParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ position=Position(line=0, character=0),
+ new_name="new name",
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_selection_range.py b/tests/lsp/test_selection_range.py
new file mode 100644
index 0000000..5f669a2
--- /dev/null
+++ b/tests/lsp/test_selection_range.py
@@ -0,0 +1,110 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from typing import List, Optional
+
+from lsprotocol.types import TEXT_DOCUMENT_SELECTION_RANGE
+from lsprotocol.types import (
+ Position,
+ Range,
+ SelectionRange,
+ SelectionRangeOptions,
+ SelectionRangeParams,
+ TextDocumentIdentifier,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_SELECTION_RANGE,
+ SelectionRangeOptions(),
+ )
+ def f(params: SelectionRangeParams) -> Optional[List[SelectionRange]]:
+ if params.text_document.uri == "file://return.list":
+ root = SelectionRange(
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=10, character=10),
+ ),
+ )
+
+ inner_range = SelectionRange(
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ parent=root,
+ )
+
+ return [root, inner_range]
+ else:
+ return None
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.selection_range_provider
+
+
+@ConfiguredLS.decorate()
+def test_selection_range_return_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_SELECTION_RANGE,
+ SelectionRangeParams(
+ # query="query",
+ text_document=TextDocumentIdentifier(uri="file://return.list"),
+ positions=[Position(line=0, character=0)],
+ ),
+ ).result()
+
+ assert response
+
+ root = response[0]
+ assert root.range.start.line == 0
+ assert root.range.start.character == 0
+ assert root.range.end.line == 10
+ assert root.range.end.character == 10
+ assert root.parent is None
+
+ assert response[1].range.start.line == 0
+ assert response[1].range.start.character == 0
+ assert response[1].range.end.line == 1
+ assert response[1].range.end.character == 1
+ assert response[1].parent == root
+
+
+@ConfiguredLS.decorate()
+def test_selection_range_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_SELECTION_RANGE,
+ SelectionRangeParams(
+ # query="query",
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ positions=[Position(line=0, character=0)],
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_signature_help.py b/tests/lsp/test_signature_help.py
new file mode 100644
index 0000000..e318120
--- /dev/null
+++ b/tests/lsp/test_signature_help.py
@@ -0,0 +1,161 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+
+from typing import Optional
+
+import pytest
+
+from lsprotocol.types import TEXT_DOCUMENT_SIGNATURE_HELP
+from lsprotocol.types import (
+ ParameterInformation,
+ Position,
+ SignatureHelp,
+ SignatureHelpContext,
+ SignatureHelpOptions,
+ SignatureHelpParams,
+ SignatureHelpTriggerKind,
+ SignatureInformation,
+ TextDocumentIdentifier,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_SIGNATURE_HELP,
+ SignatureHelpOptions(
+ trigger_characters=["a", "b"],
+ retrigger_characters=["c", "d"],
+ ),
+ )
+ def f(params: SignatureHelpParams) -> Optional[SignatureHelp]:
+ if params.text_document.uri == "file://return.signature_help":
+ return SignatureHelp(
+ signatures=[
+ SignatureInformation(
+ label="label",
+ documentation="documentation",
+ parameters=[
+ ParameterInformation(
+ label=(0, 0),
+ documentation="documentation",
+ ),
+ ],
+ ),
+ ],
+ active_signature=0,
+ active_parameter=0,
+ )
+ else:
+ return None
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ provider = capabilities.signature_help_provider
+ assert provider
+ assert provider.trigger_characters == ["a", "b"]
+ assert provider.retrigger_characters == ["c", "d"]
+
+
+@ConfiguredLS.decorate()
+@pytest.mark.skip
+def test_signature_help_return_signature_help(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_SIGNATURE_HELP,
+ SignatureHelpParams(
+ text_document=TextDocumentIdentifier(uri="file://return.signature_help"),
+ position=Position(line=0, character=0),
+ context=SignatureHelpContext(
+ trigger_kind=SignatureHelpTriggerKind.TriggerCharacter,
+ is_retrigger=True,
+ trigger_character="a",
+ active_signature_help=SignatureHelp(
+ signatures=[
+ SignatureInformation(
+ label="label",
+ documentation="documentation",
+ parameters=[
+ ParameterInformation(
+ label=(0, 0),
+ documentation="documentation",
+ ),
+ ],
+ ),
+ ],
+ active_signature=0,
+ active_parameter=0,
+ ),
+ ),
+ ),
+ ).result()
+
+ assert response
+
+ assert response["activeParameter"] == 0
+ assert response["activeSignature"] == 0
+
+ assert response["signatures"][0]["label"] == "label"
+ assert response["signatures"][0]["documentation"] == "documentation"
+ assert response["signatures"][0]["parameters"][0]["label"] == [0, 0]
+ assert (
+ response["signatures"][0]["parameters"][0]["documentation"] == "documentation"
+ )
+
+
+@ConfiguredLS.decorate()
+@pytest.mark.skip
+def test_signature_help_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_SIGNATURE_HELP,
+ SignatureHelpParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ position=Position(line=0, character=0),
+ context=SignatureHelpContext(
+ trigger_kind=SignatureHelpTriggerKind.TriggerCharacter,
+ is_retrigger=True,
+ trigger_character="a",
+ active_signature_help=SignatureHelp(
+ signatures=[
+ SignatureInformation(
+ label="label",
+ documentation="documentation",
+ parameters=[
+ ParameterInformation(
+ label=(0, 0),
+ documentation="documentation",
+ ),
+ ],
+ ),
+ ],
+ active_signature=0,
+ active_parameter=0,
+ ),
+ ),
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_type_definition.py b/tests/lsp/test_type_definition.py
new file mode 100644
index 0000000..b6d3eff
--- /dev/null
+++ b/tests/lsp/test_type_definition.py
@@ -0,0 +1,163 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from typing import List, Optional, Union
+
+from lsprotocol.types import TEXT_DOCUMENT_TYPE_DEFINITION
+from lsprotocol.types import (
+ Location,
+ LocationLink,
+ Position,
+ Range,
+ TextDocumentIdentifier,
+ TypeDefinitionOptions,
+ TypeDefinitionParams,
+)
+
+from ..conftest import ClientServer
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(
+ TEXT_DOCUMENT_TYPE_DEFINITION,
+ TypeDefinitionOptions(),
+ )
+ def f(
+ params: TypeDefinitionParams,
+ ) -> Optional[Union[Location, List[Location], List[LocationLink]]]:
+ location = Location(
+ uri="uri",
+ range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ )
+
+ location_link = LocationLink(
+ target_uri="uri",
+ target_range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=1, character=1),
+ ),
+ target_selection_range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=2, character=2),
+ ),
+ origin_selection_range=Range(
+ start=Position(line=0, character=0),
+ end=Position(line=3, character=3),
+ ),
+ )
+
+ return { # type: ignore
+ "file://return.location": location,
+ "file://return.location_list": [location],
+ "file://return.location_link_list": [location_link],
+ }.get(params.text_document.uri, None)
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+
+ assert capabilities.type_definition_provider
+
+
+@ConfiguredLS.decorate()
+def test_type_definition_return_location(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_TYPE_DEFINITION,
+ TypeDefinitionParams(
+ text_document=TextDocumentIdentifier(uri="file://return.location"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response.uri == "uri"
+
+ assert response.range.start.line == 0
+ assert response.range.start.character == 0
+ assert response.range.end.line == 1
+ assert response.range.end.character == 1
+
+
+@ConfiguredLS.decorate()
+def test_type_definition_return_location_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_TYPE_DEFINITION,
+ TypeDefinitionParams(
+ text_document=TextDocumentIdentifier(uri="file://return.location_list"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response[0].uri == "uri"
+
+ assert response[0].range.start.line == 0
+ assert response[0].range.start.character == 0
+ assert response[0].range.end.line == 1
+ assert response[0].range.end.character == 1
+
+
+@ConfiguredLS.decorate()
+def test_type_definition_return_location_link_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_TYPE_DEFINITION,
+ TypeDefinitionParams(
+ text_document=TextDocumentIdentifier(
+ uri="file://return.location_link_list"
+ ),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response[0].target_uri == "uri"
+
+ assert response[0].target_range.start.line == 0
+ assert response[0].target_range.start.character == 0
+ assert response[0].target_range.end.line == 1
+ assert response[0].target_range.end.character == 1
+
+ assert response[0].target_selection_range.start.line == 0
+ assert response[0].target_selection_range.start.character == 0
+ assert response[0].target_selection_range.end.line == 2
+ assert response[0].target_selection_range.end.character == 2
+
+ assert response[0].origin_selection_range.start.line == 0
+ assert response[0].origin_selection_range.start.character == 0
+ assert response[0].origin_selection_range.end.line == 3
+ assert response[0].origin_selection_range.end.character == 3
+
+
+@ConfiguredLS.decorate()
+def test_type_definition_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ TEXT_DOCUMENT_TYPE_DEFINITION,
+ TypeDefinitionParams(
+ text_document=TextDocumentIdentifier(uri="file://return.none"),
+ position=Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response is None
diff --git a/tests/lsp/test_type_hierarchy.py b/tests/lsp/test_type_hierarchy.py
new file mode 100644
index 0000000..e186c7f
--- /dev/null
+++ b/tests/lsp/test_type_hierarchy.py
@@ -0,0 +1,127 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from typing import List, Optional
+
+from lsprotocol import types as lsp
+
+from ..conftest import ClientServer
+
+
+TYPE_HIERARCHY_ITEM = lsp.TypeHierarchyItem(
+ name="test_name",
+ kind=lsp.SymbolKind.Class,
+ uri="test_uri",
+ range=lsp.Range(
+ start=lsp.Position(line=0, character=0),
+ end=lsp.Position(line=0, character=6),
+ ),
+ selection_range=lsp.Range(
+ start=lsp.Position(line=0, character=0),
+ end=lsp.Position(line=0, character=6),
+ ),
+)
+
+
+def check_type_hierarchy_item_response(item):
+ assert item.name == TYPE_HIERARCHY_ITEM.name
+ assert item.kind == TYPE_HIERARCHY_ITEM.kind
+ assert item.uri == TYPE_HIERARCHY_ITEM.uri
+ assert item.range == TYPE_HIERARCHY_ITEM.range
+ assert item.selection_range == TYPE_HIERARCHY_ITEM.selection_range
+
+
+class ConfiguredLS(ClientServer):
+ def __init__(self):
+ super().__init__()
+
+ @self.server.feature(lsp.TEXT_DOCUMENT_PREPARE_TYPE_HIERARCHY)
+ def f1(
+ params: lsp.TypeHierarchyPrepareParams,
+ ) -> Optional[List[lsp.TypeHierarchyItem]]:
+ if params.text_document.uri == "file://return.list":
+ return [TYPE_HIERARCHY_ITEM]
+ else:
+ return None
+
+ @self.server.feature(lsp.TYPE_HIERARCHY_SUPERTYPES)
+ def f2(
+ params: lsp.TypeHierarchySupertypesParams,
+ ) -> Optional[List[lsp.TypeHierarchyItem]]:
+ return [TYPE_HIERARCHY_ITEM]
+
+ @self.server.feature(lsp.TYPE_HIERARCHY_SUBTYPES)
+ def f3(
+ params: lsp.TypeHierarchySubtypesParams,
+ ) -> Optional[List[lsp.TypeHierarchyItem]]:
+ return [TYPE_HIERARCHY_ITEM]
+
+
+@ConfiguredLS.decorate()
+def test_capabilities(client_server):
+ _, server = client_server
+ capabilities = server.server_capabilities
+ assert capabilities.type_hierarchy_provider
+
+
+@ConfiguredLS.decorate()
+def test_type_hierarchy_prepare_return_list(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ lsp.TEXT_DOCUMENT_PREPARE_TYPE_HIERARCHY,
+ lsp.TypeHierarchyPrepareParams(
+ text_document=lsp.TextDocumentIdentifier(uri="file://return.list"),
+ position=lsp.Position(line=0, character=0),
+ ),
+ ).result()
+
+ check_type_hierarchy_item_response(response[0])
+
+
+@ConfiguredLS.decorate()
+def test_type_hierarchy_prepare_return_none(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ lsp.TEXT_DOCUMENT_PREPARE_TYPE_HIERARCHY,
+ lsp.TypeHierarchyPrepareParams(
+ text_document=lsp.TextDocumentIdentifier(uri="file://return.none"),
+ position=lsp.Position(line=0, character=0),
+ ),
+ ).result()
+
+ assert response is None
+
+
+@ConfiguredLS.decorate()
+def test_type_hierarchy_supertypes(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ lsp.TYPE_HIERARCHY_SUPERTYPES,
+ lsp.TypeHierarchySupertypesParams(item=TYPE_HIERARCHY_ITEM),
+ ).result()
+
+ check_type_hierarchy_item_response(response[0])
+
+
+@ConfiguredLS.decorate()
+def test_type_hierarchy_subtypes(client_server):
+ client, _ = client_server
+ response = client.lsp.send_request(
+ lsp.TYPE_HIERARCHY_SUBTYPES,
+ lsp.TypeHierarchySubtypesParams(item=TYPE_HIERARCHY_ITEM),
+ ).result()
+
+ check_type_hierarchy_item_response(response[0])
diff --git a/tests/pyodide_testrunner/.gitignore b/tests/pyodide_testrunner/.gitignore
new file mode 100644
index 0000000..704d307
--- /dev/null
+++ b/tests/pyodide_testrunner/.gitignore
@@ -0,0 +1 @@
+*.whl
diff --git a/tests/pyodide_testrunner/index.html b/tests/pyodide_testrunner/index.html
new file mode 100644
index 0000000..b3e1b8b
--- /dev/null
+++ b/tests/pyodide_testrunner/index.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+ <meta charset="UTF-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+ <title>Pygls Testsuite</title>
+
+ <style>
+ @media (prefers-color-scheme: dark) {
+ * {
+ background-color: #222;
+ color: white;
+ }
+
+ }
+ </style>
+</head>
+
+<body>
+ <div>
+ <pre id="console"></pre>
+ </div>
+ <button id="exit-code" disabled></button>
+ <script>
+ let log = document.getElementById("console")
+ let exitCode = document.getElementById("exit-code")
+
+ function print(event) {
+ log.innerText += event.data
+ }
+
+ // Use a web worker to prevent freezing the UI
+ function runTests(whl) {
+ let worker = new Worker(`test-runner.js?whl=${whl}`)
+ worker.addEventListener('message', (event) => {
+
+ if (event.data.exitCode !== undefined) {
+ exitCode.innerText = event.data.exitCode
+ exitCode.disabled = false
+ return
+ }
+
+ print(event)
+ })
+ }
+
+ let queryParams = new URLSearchParams(window.location.search)
+ runTests(queryParams.get('whl'))
+
+ </script>
+</body>
+
+</html>
diff --git a/tests/pyodide_testrunner/run.py b/tests/pyodide_testrunner/run.py
new file mode 100644
index 0000000..7b56374
--- /dev/null
+++ b/tests/pyodide_testrunner/run.py
@@ -0,0 +1,131 @@
+import os
+import pathlib
+import shutil
+import subprocess
+import sys
+import tempfile
+
+from functools import partial
+from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
+from multiprocessing import Process, Queue
+
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.common.exceptions import WebDriverException
+from selenium.webdriver.support import expected_conditions as EC
+
+
+# Path to the root of the repo.
+REPO = pathlib.Path(__file__).parent.parent.parent
+BROWSERS = {
+ "chrome": (webdriver.Chrome, webdriver.ChromeOptions),
+ "firefox": (webdriver.Firefox, webdriver.FirefoxOptions),
+}
+
+
+def build_wheel() -> str:
+ """Build a wheel package of ``pygls`` and its testsuite.
+
+ In order to test pygls under pyodide, we need to load the code for both pygls and its
+ testsuite. This is done by building a wheel.
+
+ To avoid messing with the repo this is all done under a temp directory.
+ """
+
+ with tempfile.TemporaryDirectory() as tmpdir:
+ # Copy all required files.
+ dest = pathlib.Path(tmpdir)
+
+ # So that we don't have to fuss with packaging, copy the test suite into `pygls`
+ # as a sub module.
+ directories = [("pygls", "pygls"), ("tests", "pygls/tests")]
+
+ for src, target in directories:
+ shutil.copytree(REPO / src, dest / target)
+
+ files = ["pyproject.toml", "poetry.lock", "README.md", "ThirdPartyNotices.txt"]
+
+ for src in files:
+ shutil.copy(REPO / src, dest)
+
+ # Convert the lock file to requirements.txt.
+ # Ensures reproducible behavour for testing.
+ subprocess.run(
+ [
+ "poetry",
+ "export",
+ "-f",
+ "requirements.txt",
+ "--output",
+ "requirements.txt",
+ ],
+ cwd=dest,
+ )
+ subprocess.run(
+ ["poetry", "run", "pip", "install", "-r", "requirements.txt"], cwd=dest
+ )
+ # Build the wheel
+ subprocess.run(["poetry", "build", "--format", "wheel"], cwd=dest)
+ whl = list((dest / "dist").glob("*.whl"))[0]
+ shutil.copy(whl, REPO / "tests/pyodide_testrunner")
+
+ return whl.name
+
+
+def spawn_http_server(q: Queue, directory: str):
+ """A http server is needed to serve the files to the browser."""
+
+ handler_class = partial(SimpleHTTPRequestHandler, directory=directory)
+ server = ThreadingHTTPServer(("localhost", 0), handler_class)
+ q.put(server.server_port)
+
+ server.serve_forever()
+
+
+def main():
+ exit_code = 1
+ whl = build_wheel()
+
+ q = Queue()
+ server_process = Process(
+ target=spawn_http_server,
+ args=(q, REPO / "tests/pyodide_testrunner"),
+ daemon=True,
+ )
+ server_process.start()
+ port = q.get()
+
+ print("Running tests...")
+ try:
+ driver_cls, options_cls = BROWSERS[os.environ.get("BROWSER", "chrome")]
+
+ options = options_cls()
+ if "CI" in os.environ:
+ options.binary_location = "/usr/bin/google-chrome"
+ options.add_argument("--headless")
+
+ driver = driver_cls(options=options)
+ driver.get(f"http://localhost:{port}?whl={whl}")
+
+ wait = WebDriverWait(driver, 120)
+ try:
+ button = wait.until(EC.element_to_be_clickable((By.ID, "exit-code")))
+ exit_code = int(button.text)
+ except WebDriverException as e:
+ print(f"Error while running test: {e!r}")
+ exit_code = 1
+
+ console = driver.find_element(By.ID, "console")
+ print(console.text)
+ finally:
+ if hasattr(server_process, "kill"):
+ server_process.kill()
+ else:
+ server_process.terminate()
+
+ return exit_code
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/tests/pyodide_testrunner/test-runner.js b/tests/pyodide_testrunner/test-runner.js
new file mode 100644
index 0000000..dbbc01f
--- /dev/null
+++ b/tests/pyodide_testrunner/test-runner.js
@@ -0,0 +1,38 @@
+importScripts("https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js")
+
+// Used to redirect pyodide's stdout to the webpage.
+function patchedStdout(...args) {
+ postMessage(args[0])
+}
+
+async function runTests(whl) {
+ console.log("Loading pyodide")
+ let pyodide = await loadPyodide({
+ indexURL: "https://cdn.jsdelivr.net/pyodide/v0.21.3/full/"
+ })
+
+ console.log("Installing dependencies")
+ await pyodide.loadPackage("micropip")
+ await pyodide.runPythonAsync(`
+ import sys
+ import micropip
+
+ await micropip.install('pytest')
+ await micropip.install('pytest-asyncio')
+ await micropip.install('${whl}')
+ `)
+
+ console.log('Running testsuite')
+
+ // Patch stdout to redirect the output.
+ pyodide.globals.get('sys').stdout.write = patchedStdout
+ await pyodide.runPythonAsync(`
+ import pytest
+ exit_code = pytest.main(['--color', 'no', '--pyargs', 'pygls.tests'])
+ `)
+
+ postMessage({ exitCode: pyodide.globals.get('exit_code') })
+}
+
+let queryParams = new URLSearchParams(self.location.search)
+runTests(queryParams.get('whl'))
diff --git a/tests/servers/invalid_json.py b/tests/servers/invalid_json.py
new file mode 100644
index 0000000..5b40d7a
--- /dev/null
+++ b/tests/servers/invalid_json.py
@@ -0,0 +1,27 @@
+"""This server does nothing but print invalid JSON."""
+import asyncio
+import threading
+import sys
+from concurrent.futures import ThreadPoolExecutor
+
+from pygls.server import aio_readline
+
+
+def handler(data):
+ content = 'Content-Length: 5\r\n\r\n{"ll}'.encode("utf8")
+ sys.stdout.buffer.write(content)
+ sys.stdout.flush()
+
+
+async def main():
+ await aio_readline(
+ asyncio.get_running_loop(),
+ ThreadPoolExecutor(),
+ threading.Event(),
+ sys.stdin.buffer,
+ handler,
+ )
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/tests/servers/large_response.py b/tests/servers/large_response.py
new file mode 100644
index 0000000..fd85b62
--- /dev/null
+++ b/tests/servers/large_response.py
@@ -0,0 +1,36 @@
+"""This server returns a particuarly large response."""
+import asyncio
+import threading
+import sys
+from concurrent.futures import ThreadPoolExecutor
+
+from pygls.server import aio_readline
+
+
+def handler(data):
+ payload = dict(
+ jsonrpc="2.0",
+ id=1,
+ result=dict(
+ numbers=list(range(100_000)),
+ ),
+ )
+ content = str(payload).replace("'", '"')
+ message = f"Content-Length: {len(content)}\r\n\r\n{content}".encode("utf8")
+
+ sys.stdout.buffer.write(message)
+ sys.stdout.flush()
+
+
+async def main():
+ await aio_readline(
+ asyncio.get_running_loop(),
+ ThreadPoolExecutor(),
+ threading.Event(),
+ sys.stdin.buffer,
+ handler,
+ )
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/tests/test_client.py b/tests/test_client.py
new file mode 100644
index 0000000..cacd3e0
--- /dev/null
+++ b/tests/test_client.py
@@ -0,0 +1,102 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import asyncio
+import pathlib
+import sys
+from typing import Union
+
+import pytest
+from pygls import IS_PYODIDE
+
+from pygls.client import JsonRPCClient
+from pygls.exceptions import JsonRpcException, PyglsError
+
+
+SERVERS = pathlib.Path(__file__).parent / "servers"
+
+
+@pytest.mark.asyncio
+@pytest.mark.skipif(IS_PYODIDE, reason="Subprocesses are not available on pyodide.")
+async def test_client_detect_server_exit():
+ """Ensure that the client detects when the server process exits."""
+
+ class TestClient(JsonRPCClient):
+ server_exit_called = False
+
+ async def server_exit(self, server: asyncio.subprocess.Process):
+ self.server_exit_called = True
+ assert server.returncode == 0
+
+ client = TestClient()
+ await client.start_io(sys.executable, "-c", "print('Hello, World!')")
+ await asyncio.sleep(1)
+ await client.stop()
+
+ message = "Expected the `server_exit` method to have been called."
+ assert client.server_exit_called, message
+
+
+@pytest.mark.asyncio
+@pytest.mark.skipif(IS_PYODIDE, reason="Subprocesses are not available on pyodide.")
+async def test_client_detect_invalid_json():
+ """Ensure that the client can detect the case where the server returns invalid
+ json."""
+
+ class TestClient(JsonRPCClient):
+ report_error_called = False
+ future = None
+
+ def report_server_error(
+ self, error: Exception, source: Union[PyglsError, JsonRpcException]
+ ):
+ self.report_error_called = True
+ self.future.cancel()
+
+ self._server.kill()
+ self._stop_event.set()
+
+ assert "Unterminated string" in str(error)
+
+ client = TestClient()
+ await client.start_io(sys.executable, str(SERVERS / "invalid_json.py"))
+
+ future = client.protocol.send_request_async("method/name", {})
+ client.future = future
+
+ try:
+ await future
+ except asyncio.CancelledError:
+ pass # Ignore the exception generated by cancelling the future
+ finally:
+ await client.stop()
+
+ assert_message = "Expected `report_server_error` to have been called"
+ assert client.report_error_called, assert_message
+
+
+@pytest.mark.asyncio
+@pytest.mark.skipif(IS_PYODIDE, reason="Subprocesses are not available on pyodide.")
+async def test_client_large_responses():
+ """Ensure that the client can correctly handle large responses from a server."""
+
+ client = JsonRPCClient()
+ await client.start_io(sys.executable, str(SERVERS / "large_response.py"))
+
+ result = await client.protocol.send_request_async("get/numbers", {}, msg_id=1)
+ assert len(result.numbers) == 100_000
+
+ await client.stop()
diff --git a/tests/test_document.py b/tests/test_document.py
new file mode 100644
index 0000000..f071a2f
--- /dev/null
+++ b/tests/test_document.py
@@ -0,0 +1,390 @@
+############################################################################
+# Original work Copyright 2018 Palantir Technologies, Inc. #
+# Original work licensed under the MIT License. #
+# See ThirdPartyNotices.txt in the project root for license information. #
+# All modifications Copyright (c) Open Law Library. All rights reserved. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import re
+
+from lsprotocol import types
+from pygls.workspace import TextDocument, PositionCodec
+from .conftest import DOC, DOC_URI
+
+
+def test_document_empty_edit():
+ doc = TextDocument("file:///uri", "")
+ change = types.TextDocumentContentChangeEvent_Type1(
+ range=types.Range(
+ start=types.Position(line=0, character=0),
+ end=types.Position(line=0, character=0),
+ ),
+ range_length=0,
+ text="f",
+ )
+ doc.apply_change(change)
+ assert doc.source == "f"
+
+
+def test_document_end_of_file_edit():
+ old = ["print 'a'\n", "print 'b'\n"]
+ doc = TextDocument("file:///uri", "".join(old))
+
+ change = types.TextDocumentContentChangeEvent_Type1(
+ range=types.Range(
+ start=types.Position(line=2, character=0),
+ end=types.Position(line=2, character=0),
+ ),
+ range_length=0,
+ text="o",
+ )
+ doc.apply_change(change)
+
+ assert doc.lines == [
+ "print 'a'\n",
+ "print 'b'\n",
+ "o",
+ ]
+
+
+def test_document_full_edit():
+ old = ["def hello(a, b):\n", " print a\n", " print b\n"]
+ doc = TextDocument(
+ "file:///uri", "".join(old), sync_kind=types.TextDocumentSyncKind.Full
+ )
+ change = types.TextDocumentContentChangeEvent_Type1(
+ range=types.Range(
+ start=types.Position(line=1, character=4),
+ end=types.Position(line=2, character=11),
+ ),
+ range_length=0,
+ text="print a, b",
+ )
+ doc.apply_change(change)
+
+ assert doc.lines == ["print a, b"]
+
+ doc = TextDocument(
+ "file:///uri", "".join(old), sync_kind=types.TextDocumentSyncKind.Full
+ )
+ change = types.TextDocumentContentChangeEvent_Type1(
+ range=types.Range(
+ start=types.Position(line=0, character=0),
+ end=types.Position(line=0, character=0),
+ ),
+ text="print a, b",
+ )
+ doc.apply_change(change)
+
+ assert doc.lines == ["print a, b"]
+
+
+def test_document_line_edit():
+ doc = TextDocument("file:///uri", "itshelloworld")
+ change = types.TextDocumentContentChangeEvent_Type1(
+ range=types.Range(
+ start=types.Position(line=0, character=3),
+ end=types.Position(line=0, character=8),
+ ),
+ range_length=0,
+ text="goodbye",
+ )
+ doc.apply_change(change)
+ assert doc.source == "itsgoodbyeworld"
+
+
+def test_document_lines():
+ doc = TextDocument(DOC_URI, DOC)
+ assert len(doc.lines) == 4
+ assert doc.lines[0] == "document\n"
+
+
+def test_document_multiline_edit():
+ old = ["def hello(a, b):\n", " print a\n", " print b\n"]
+ doc = TextDocument(
+ "file:///uri", "".join(old), sync_kind=types.TextDocumentSyncKind.Incremental
+ )
+ change = types.TextDocumentContentChangeEvent_Type1(
+ range=types.Range(
+ start=types.Position(line=1, character=4),
+ end=types.Position(line=2, character=11),
+ ),
+ range_length=0,
+ text="print a, b",
+ )
+ doc.apply_change(change)
+
+ assert doc.lines == ["def hello(a, b):\n", " print a, b\n"]
+
+ doc = TextDocument(
+ "file:///uri", "".join(old), sync_kind=types.TextDocumentSyncKind.Incremental
+ )
+ change = types.TextDocumentContentChangeEvent_Type1(
+ range=types.Range(
+ start=types.Position(line=1, character=4),
+ end=types.Position(line=2, character=11),
+ ),
+ text="print a, b",
+ )
+ doc.apply_change(change)
+
+ assert doc.lines == ["def hello(a, b):\n", " print a, b\n"]
+
+
+def test_document_no_edit():
+ old = ["def hello(a, b):\n", " print a\n", " print b\n"]
+ doc = TextDocument(
+ "file:///uri", "".join(old), sync_kind=types.TextDocumentSyncKind.None_
+ )
+ change = types.TextDocumentContentChangeEvent_Type1(
+ range=types.Range(
+ start=types.Position(line=1, character=4),
+ end=types.Position(line=2, character=11),
+ ),
+ range_length=0,
+ text="print a, b",
+ )
+ doc.apply_change(change)
+
+ assert doc.lines == old
+
+
+def test_document_props():
+ doc = TextDocument(DOC_URI, DOC)
+
+ assert doc.uri == DOC_URI
+ assert doc.source == DOC
+
+
+def test_document_source_unicode():
+ document_mem = TextDocument(DOC_URI, "my source")
+ document_disk = TextDocument(DOC_URI)
+ assert isinstance(document_mem.source, type(document_disk.source))
+
+
+def test_position_from_utf16():
+ codec = PositionCodec(encoding=types.PositionEncodingKind.Utf16)
+ assert codec.position_from_client_units(
+ ['x="😋"'], types.Position(line=0, character=3)
+ ) == types.Position(line=0, character=3)
+ assert codec.position_from_client_units(
+ ['x="😋"'], types.Position(line=0, character=5)
+ ) == types.Position(line=0, character=4)
+
+
+def test_position_from_utf32():
+ codec = PositionCodec(encoding=types.PositionEncodingKind.Utf32)
+ assert codec.position_from_client_units(
+ ['x="😋"'], types.Position(line=0, character=3)
+ ) == types.Position(line=0, character=3)
+ assert codec.position_from_client_units(
+ ['x="😋"'], types.Position(line=0, character=4)
+ ) == types.Position(line=0, character=4)
+
+
+def test_position_from_utf8():
+ codec = PositionCodec(encoding=types.PositionEncodingKind.Utf8)
+ assert codec.position_from_client_units(
+ ['x="😋"'], types.Position(line=0, character=3)
+ ) == types.Position(line=0, character=3)
+ assert codec.position_from_client_units(
+ ['x="😋"'], types.Position(line=0, character=7)
+ ) == types.Position(line=0, character=4)
+
+
+def test_position_to_utf16():
+ codec = PositionCodec(encoding=types.PositionEncodingKind.Utf16)
+ assert codec.position_to_client_units(
+ ['x="😋"'], types.Position(line=0, character=3)
+ ) == types.Position(line=0, character=3)
+
+ assert codec.position_to_client_units(
+ ['x="😋"'], types.Position(line=0, character=4)
+ ) == types.Position(line=0, character=5)
+
+
+def test_position_to_utf32():
+ codec = PositionCodec(encoding=types.PositionEncodingKind.Utf32)
+ assert codec.position_to_client_units(
+ ['x="😋"'], types.Position(line=0, character=3)
+ ) == types.Position(line=0, character=3)
+
+ assert codec.position_to_client_units(
+ ['x="😋"'], types.Position(line=0, character=4)
+ ) == types.Position(line=0, character=4)
+
+
+def test_position_to_utf8():
+ codec = PositionCodec(encoding=types.PositionEncodingKind.Utf8)
+ assert codec.position_to_client_units(
+ ['x="😋"'], types.Position(line=0, character=3)
+ ) == types.Position(line=0, character=3)
+
+ assert codec.position_to_client_units(
+ ['x="😋"'], types.Position(line=0, character=4)
+ ) == types.Position(line=0, character=6)
+
+
+def test_range_from_utf16():
+ codec = PositionCodec(encoding=types.PositionEncodingKind.Utf16)
+ assert codec.range_from_client_units(
+ ['x="😋"'],
+ types.Range(
+ start=types.Position(line=0, character=3),
+ end=types.Position(line=0, character=5),
+ ),
+ ) == types.Range(
+ start=types.Position(line=0, character=3),
+ end=types.Position(line=0, character=4),
+ )
+
+ range = types.Range(
+ start=types.Position(line=0, character=3),
+ end=types.Position(line=0, character=5),
+ )
+ actual = codec.range_from_client_units(['x="😋😋"'], range)
+ expected = types.Range(
+ start=types.Position(line=0, character=3),
+ end=types.Position(line=0, character=4),
+ )
+ assert actual == expected
+
+
+def test_range_to_utf16():
+ codec = PositionCodec(encoding=types.PositionEncodingKind.Utf16)
+ assert codec.range_to_client_units(
+ ['x="😋"'],
+ types.Range(
+ start=types.Position(line=0, character=3),
+ end=types.Position(line=0, character=4),
+ ),
+ ) == types.Range(
+ start=types.Position(line=0, character=3),
+ end=types.Position(line=0, character=5),
+ )
+
+ range = types.Range(
+ start=types.Position(line=0, character=3),
+ end=types.Position(line=0, character=4),
+ )
+ actual = codec.range_to_client_units(['x="😋😋"'], range)
+ expected = types.Range(
+ start=types.Position(line=0, character=3),
+ end=types.Position(line=0, character=5),
+ )
+ assert actual == expected
+
+
+def test_offset_at_position_utf16():
+ doc = TextDocument(DOC_URI, DOC)
+ assert doc.offset_at_position(types.Position(line=0, character=8)) == 8
+ assert doc.offset_at_position(types.Position(line=1, character=5)) == 12
+ assert doc.offset_at_position(types.Position(line=2, character=0)) == 13
+ assert doc.offset_at_position(types.Position(line=2, character=4)) == 17
+ assert doc.offset_at_position(types.Position(line=3, character=6)) == 27
+ assert doc.offset_at_position(types.Position(line=3, character=7)) == 28
+ assert doc.offset_at_position(types.Position(line=3, character=8)) == 28
+ assert doc.offset_at_position(types.Position(line=4, character=0)) == 40
+ assert doc.offset_at_position(types.Position(line=5, character=0)) == 40
+
+
+def test_offset_at_position_utf32():
+ doc = TextDocument(
+ DOC_URI,
+ DOC,
+ position_codec=PositionCodec(encoding=types.PositionEncodingKind.Utf32),
+ )
+ assert doc.offset_at_position(types.Position(line=0, character=8)) == 8
+ assert doc.offset_at_position(types.Position(line=5, character=0)) == 39
+
+
+def test_offset_at_position_utf8():
+ doc = TextDocument(
+ DOC_URI,
+ DOC,
+ position_codec=PositionCodec(encoding=types.PositionEncodingKind.Utf8),
+ )
+ assert doc.offset_at_position(types.Position(line=0, character=8)) == 8
+ assert doc.offset_at_position(types.Position(line=5, character=0)) == 41
+
+
+def test_utf16_to_utf32_position_cast():
+ codec = PositionCodec(encoding=types.PositionEncodingKind.Utf16)
+ lines = ["", "😋😋", ""]
+ assert codec.position_from_client_units(
+ lines, types.Position(line=0, character=0)
+ ) == types.Position(line=0, character=0)
+ assert codec.position_from_client_units(
+ lines, types.Position(line=0, character=1)
+ ) == types.Position(line=0, character=0)
+ assert codec.position_from_client_units(
+ lines, types.Position(line=1, character=0)
+ ) == types.Position(line=1, character=0)
+ assert codec.position_from_client_units(
+ lines, types.Position(line=1, character=2)
+ ) == types.Position(line=1, character=1)
+ assert codec.position_from_client_units(
+ lines, types.Position(line=1, character=3)
+ ) == types.Position(line=1, character=2)
+ assert codec.position_from_client_units(
+ lines, types.Position(line=1, character=4)
+ ) == types.Position(line=1, character=2)
+ assert codec.position_from_client_units(
+ lines, types.Position(line=1, character=100)
+ ) == types.Position(line=1, character=2)
+ assert codec.position_from_client_units(
+ lines, types.Position(line=3, character=0)
+ ) == types.Position(line=2, character=0)
+ assert codec.position_from_client_units(
+ lines, types.Position(line=4, character=10)
+ ) == types.Position(line=2, character=0)
+
+
+def test_position_for_line_endings():
+ codec = PositionCodec(encoding=types.PositionEncodingKind.Utf16)
+ lines = ["x\r\n", "y\n"]
+ assert codec.position_from_client_units(
+ lines, types.Position(line=0, character=10)
+ ) == types.Position(line=0, character=1)
+ assert codec.position_from_client_units(
+ lines, types.Position(line=1, character=10)
+ ) == types.Position(line=1, character=1)
+
+
+def test_word_at_position():
+ """
+ Return word under the cursor (or last in line if past the end)
+ """
+ doc = TextDocument(DOC_URI, DOC)
+
+ assert doc.word_at_position(types.Position(line=0, character=8)) == "document"
+ assert doc.word_at_position(types.Position(line=0, character=1000)) == "document"
+ assert doc.word_at_position(types.Position(line=1, character=5)) == "for"
+ assert doc.word_at_position(types.Position(line=2, character=0)) == "testing"
+ assert doc.word_at_position(types.Position(line=3, character=10)) == "unicode"
+ assert doc.word_at_position(types.Position(line=4, character=0)) == ""
+ assert doc.word_at_position(types.Position(line=4, character=0)) == ""
+ re_start_word = re.compile(r"[A-Za-z_0-9.]*$")
+ re_end_word = re.compile(r"^[A-Za-z_0-9.]*")
+ assert (
+ doc.word_at_position(
+ types.Position(
+ line=3,
+ character=10,
+ ),
+ re_start_word=re_start_word,
+ re_end_word=re_end_word,
+ )
+ == "unicode."
+ )
diff --git a/tests/test_feature_manager.py b/tests/test_feature_manager.py
new file mode 100644
index 0000000..f69f12a
--- /dev/null
+++ b/tests/test_feature_manager.py
@@ -0,0 +1,782 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import asyncio
+from typing import Any
+
+import pytest
+from pygls.capabilities import ServerCapabilitiesBuilder
+from pygls.exceptions import (
+ CommandAlreadyRegisteredError,
+ FeatureAlreadyRegisteredError,
+ ValidationError,
+)
+from pygls.feature_manager import (
+ FeatureManager,
+ has_ls_param_or_annotation,
+ wrap_with_server,
+)
+from lsprotocol import types as lsp
+
+
+class Temp:
+ pass
+
+
+def test_has_ls_param_or_annotation():
+ def f1(ls, a, b, c):
+ pass
+
+ def f2(temp: Temp, a, b, c):
+ pass
+
+ def f3(temp: "Temp", a, b, c):
+ pass
+
+ assert has_ls_param_or_annotation(f1, None)
+ assert has_ls_param_or_annotation(f2, Temp)
+ assert has_ls_param_or_annotation(f3, Temp)
+
+
+def test_register_command_validation_error(feature_manager):
+ with pytest.raises(ValidationError):
+
+ @feature_manager.command(" \n\t")
+ def cmd1(): # pylint: disable=unused-variable
+ pass
+
+
+def test_register_commands(feature_manager):
+ cmd1_name = "cmd1"
+ cmd2_name = "cmd2"
+
+ @feature_manager.command(cmd1_name)
+ def cmd1():
+ pass
+
+ @feature_manager.command(cmd2_name)
+ def cmd2():
+ pass
+
+ reg_commands = feature_manager.commands.keys()
+
+ assert cmd1_name in reg_commands
+ assert cmd2_name in reg_commands
+
+ assert feature_manager.commands[cmd1_name] is cmd1
+ assert feature_manager.commands[cmd2_name] is cmd2
+
+
+def test_register_feature_with_valid_options(feature_manager):
+ options = lsp.CompletionOptions(trigger_characters=["!"])
+
+ @feature_manager.feature(lsp.TEXT_DOCUMENT_COMPLETION, options)
+ def completions():
+ pass
+
+ reg_features = feature_manager.features.keys()
+ reg_feature_options = feature_manager.feature_options.keys()
+
+ assert lsp.TEXT_DOCUMENT_COMPLETION in reg_features
+ assert lsp.TEXT_DOCUMENT_COMPLETION in reg_feature_options
+
+ assert feature_manager.features[lsp.TEXT_DOCUMENT_COMPLETION] is completions
+ assert feature_manager.feature_options[lsp.TEXT_DOCUMENT_COMPLETION] is options
+
+
+def test_register_feature_with_wrong_options(feature_manager):
+ class Options:
+ pass
+
+ with pytest.raises(
+ AttributeError,
+ match=("'Options' object has no attribute 'trigger_characters'"), # noqa
+ ):
+
+ @feature_manager.feature(lsp.TEXT_DOCUMENT_COMPLETION, Options())
+ def completions():
+ pass
+
+
+def test_register_features(feature_manager):
+ @feature_manager.feature(lsp.TEXT_DOCUMENT_COMPLETION)
+ def completions():
+ pass
+
+ @feature_manager.feature(lsp.TEXT_DOCUMENT_CODE_LENS)
+ def code_lens():
+ pass
+
+ reg_features = feature_manager.features.keys()
+
+ assert lsp.TEXT_DOCUMENT_COMPLETION in reg_features
+ assert lsp.TEXT_DOCUMENT_CODE_LENS in reg_features
+
+ assert feature_manager.features[lsp.TEXT_DOCUMENT_COMPLETION] is completions
+ assert feature_manager.features[lsp.TEXT_DOCUMENT_CODE_LENS] is code_lens
+
+
+def test_register_same_command_twice_error(feature_manager):
+ with pytest.raises(CommandAlreadyRegisteredError):
+
+ @feature_manager.command("cmd1")
+ def cmd1(): # pylint: disable=unused-variable
+ pass
+
+ @feature_manager.command("cmd1")
+ def cmd2(): # pylint: disable=unused-variable
+ pass
+
+
+def test_register_same_feature_twice_error(feature_manager):
+ with pytest.raises(FeatureAlreadyRegisteredError):
+
+ @feature_manager.feature(lsp.TEXT_DOCUMENT_CODE_ACTION)
+ def code_action1(): # pylint: disable=unused-variable
+ pass
+
+ @feature_manager.feature(lsp.TEXT_DOCUMENT_CODE_ACTION)
+ def code_action2(): # pylint: disable=unused-variable
+ pass
+
+
+def test_wrap_with_server_async():
+ class Server:
+ pass
+
+ async def f(ls):
+ assert isinstance(ls, Server)
+
+ wrapped = wrap_with_server(f, Server())
+ assert asyncio.iscoroutinefunction(wrapped)
+
+
+def test_wrap_with_server_sync():
+ class Server:
+ pass
+
+ def f(ls):
+ assert isinstance(ls, Server)
+
+ wrapped = wrap_with_server(f, Server())
+ wrapped()
+
+
+def test_wrap_with_server_thread():
+ class Server:
+ pass
+
+ def f(ls):
+ assert isinstance(ls, Server)
+
+ f.execute_in_thread = True
+
+ wrapped = wrap_with_server(f, Server())
+ assert wrapped.execute_in_thread is True
+
+
+def server_capabilities(**kwargs):
+ """Helper to reduce the amount of boilerplate required to specify the expected
+ server capabilities by filling in some fields - unless they are explicitly
+ overriden."""
+
+ if "text_document_sync" not in kwargs:
+ kwargs["text_document_sync"] = lsp.TextDocumentSyncOptions(
+ open_close=False,
+ save=False,
+ )
+
+ if "execute_command_provider" not in kwargs:
+ kwargs["execute_command_provider"] = lsp.ExecuteCommandOptions(commands=[])
+
+ if "workspace" not in kwargs:
+ kwargs["workspace"] = lsp.ServerCapabilitiesWorkspaceType(
+ workspace_folders=lsp.WorkspaceFoldersServerCapabilities(
+ supported=True, change_notifications=True
+ ),
+ file_operations=lsp.FileOperationOptions(),
+ )
+
+ if "position_encoding" not in kwargs:
+ kwargs["position_encoding"] = lsp.PositionEncodingKind.Utf16
+
+ return lsp.ServerCapabilities(**kwargs)
+
+
+@pytest.mark.parametrize(
+ "method, options, capabilities, expected",
+ [
+ (
+ lsp.INITIALIZE,
+ None,
+ lsp.ClientCapabilities(
+ general=lsp.GeneralClientCapabilities(
+ position_encodings=[lsp.PositionEncodingKind.Utf8]
+ )
+ ),
+ server_capabilities(position_encoding=lsp.PositionEncodingKind.Utf8),
+ ),
+ (
+ lsp.INITIALIZE,
+ None,
+ lsp.ClientCapabilities(
+ general=lsp.GeneralClientCapabilities(
+ position_encodings=[
+ lsp.PositionEncodingKind.Utf8,
+ lsp.PositionEncodingKind.Utf32,
+ ]
+ )
+ ),
+ server_capabilities(position_encoding=lsp.PositionEncodingKind.Utf32),
+ ),
+ (
+ lsp.TEXT_DOCUMENT_DID_SAVE,
+ lsp.SaveOptions(include_text=True),
+ lsp.ClientCapabilities(),
+ server_capabilities(
+ text_document_sync=lsp.TextDocumentSyncOptions(
+ open_close=False, save=lsp.SaveOptions(include_text=True)
+ )
+ ),
+ ),
+ (
+ lsp.TEXT_DOCUMENT_DID_SAVE,
+ None,
+ lsp.ClientCapabilities(),
+ server_capabilities(
+ text_document_sync=lsp.TextDocumentSyncOptions(
+ open_close=False, save=True
+ )
+ ),
+ ),
+ (
+ lsp.TEXT_DOCUMENT_WILL_SAVE,
+ None,
+ lsp.ClientCapabilities(),
+ server_capabilities(
+ text_document_sync=lsp.TextDocumentSyncOptions(
+ open_close=False, save=False
+ )
+ ),
+ ),
+ (
+ lsp.TEXT_DOCUMENT_WILL_SAVE,
+ None,
+ lsp.ClientCapabilities(
+ text_document=lsp.TextDocumentClientCapabilities(
+ synchronization=lsp.TextDocumentSyncClientCapabilities(
+ will_save=True
+ )
+ )
+ ),
+ server_capabilities(
+ text_document_sync=lsp.TextDocumentSyncOptions(
+ open_close=False, save=False, will_save=True
+ )
+ ),
+ ),
+ (
+ lsp.TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL,
+ None,
+ lsp.ClientCapabilities(
+ text_document=lsp.TextDocumentClientCapabilities(
+ synchronization=lsp.TextDocumentSyncClientCapabilities(
+ will_save_wait_until=True
+ )
+ )
+ ),
+ server_capabilities(
+ text_document_sync=lsp.TextDocumentSyncOptions(
+ open_close=False, save=False, will_save_wait_until=True
+ )
+ ),
+ ),
+ (
+ lsp.TEXT_DOCUMENT_DID_OPEN,
+ None,
+ lsp.ClientCapabilities(),
+ server_capabilities(
+ text_document_sync=lsp.TextDocumentSyncOptions(
+ open_close=True, save=False
+ )
+ ),
+ ),
+ (
+ lsp.TEXT_DOCUMENT_DID_CLOSE,
+ None,
+ lsp.ClientCapabilities(),
+ server_capabilities(
+ text_document_sync=lsp.TextDocumentSyncOptions(
+ open_close=True, save=False
+ )
+ ),
+ ),
+ (
+ lsp.TEXT_DOCUMENT_INLAY_HINT,
+ None,
+ lsp.ClientCapabilities(),
+ server_capabilities(
+ inlay_hint_provider=lsp.InlayHintOptions(resolve_provider=False),
+ ),
+ ),
+ (
+ lsp.WORKSPACE_WILL_CREATE_FILES,
+ lsp.FileOperationRegistrationOptions(
+ filters=[
+ lsp.FileOperationFilter(
+ pattern=lsp.FileOperationPattern(glob="**/*.py")
+ )
+ ]
+ ),
+ lsp.ClientCapabilities(),
+ server_capabilities(),
+ ),
+ (
+ lsp.WORKSPACE_WILL_CREATE_FILES,
+ lsp.FileOperationRegistrationOptions(
+ filters=[
+ lsp.FileOperationFilter(
+ pattern=lsp.FileOperationPattern(glob="**/*.py")
+ )
+ ]
+ ),
+ lsp.ClientCapabilities(
+ workspace=lsp.WorkspaceClientCapabilities(
+ file_operations=lsp.FileOperationClientCapabilities(
+ will_create=True
+ )
+ )
+ ),
+ server_capabilities(
+ workspace=lsp.ServerCapabilitiesWorkspaceType(
+ workspace_folders=lsp.WorkspaceFoldersServerCapabilities(
+ supported=True, change_notifications=True
+ ),
+ file_operations=lsp.FileOperationOptions(
+ will_create=lsp.FileOperationRegistrationOptions(
+ filters=[
+ lsp.FileOperationFilter(
+ pattern=lsp.FileOperationPattern(glob="**/*.py")
+ )
+ ]
+ )
+ ),
+ )
+ ),
+ ),
+ (
+ lsp.WORKSPACE_DID_CREATE_FILES,
+ lsp.FileOperationRegistrationOptions(
+ filters=[
+ lsp.FileOperationFilter(
+ pattern=lsp.FileOperationPattern(glob="**/*.py")
+ )
+ ]
+ ),
+ lsp.ClientCapabilities(),
+ server_capabilities(),
+ ),
+ (
+ lsp.WORKSPACE_DID_CREATE_FILES,
+ lsp.FileOperationRegistrationOptions(
+ filters=[
+ lsp.FileOperationFilter(
+ pattern=lsp.FileOperationPattern(glob="**/*.py")
+ )
+ ]
+ ),
+ lsp.ClientCapabilities(
+ workspace=lsp.WorkspaceClientCapabilities(
+ file_operations=lsp.FileOperationClientCapabilities(did_create=True)
+ )
+ ),
+ server_capabilities(
+ workspace=lsp.ServerCapabilitiesWorkspaceType(
+ workspace_folders=lsp.WorkspaceFoldersServerCapabilities(
+ supported=True, change_notifications=True
+ ),
+ file_operations=lsp.FileOperationOptions(
+ did_create=lsp.FileOperationRegistrationOptions(
+ filters=[
+ lsp.FileOperationFilter(
+ pattern=lsp.FileOperationPattern(glob="**/*.py")
+ )
+ ]
+ )
+ ),
+ )
+ ),
+ ),
+ (
+ lsp.WORKSPACE_WILL_DELETE_FILES,
+ lsp.FileOperationRegistrationOptions(
+ filters=[
+ lsp.FileOperationFilter(
+ pattern=lsp.FileOperationPattern(glob="**/*.py")
+ )
+ ]
+ ),
+ lsp.ClientCapabilities(),
+ server_capabilities(),
+ ),
+ (
+ lsp.WORKSPACE_WILL_DELETE_FILES,
+ lsp.FileOperationRegistrationOptions(
+ filters=[
+ lsp.FileOperationFilter(
+ pattern=lsp.FileOperationPattern(glob="**/*.py")
+ )
+ ]
+ ),
+ lsp.ClientCapabilities(
+ workspace=lsp.WorkspaceClientCapabilities(
+ file_operations=lsp.FileOperationClientCapabilities(
+ will_delete=True
+ )
+ )
+ ),
+ server_capabilities(
+ workspace=lsp.ServerCapabilitiesWorkspaceType(
+ workspace_folders=lsp.WorkspaceFoldersServerCapabilities(
+ supported=True, change_notifications=True
+ ),
+ file_operations=lsp.FileOperationOptions(
+ will_delete=lsp.FileOperationRegistrationOptions(
+ filters=[
+ lsp.FileOperationFilter(
+ pattern=lsp.FileOperationPattern(glob="**/*.py")
+ )
+ ]
+ )
+ ),
+ )
+ ),
+ ),
+ (
+ lsp.WORKSPACE_DID_DELETE_FILES,
+ lsp.FileOperationRegistrationOptions(
+ filters=[
+ lsp.FileOperationFilter(
+ pattern=lsp.FileOperationPattern(glob="**/*.py")
+ )
+ ]
+ ),
+ lsp.ClientCapabilities(),
+ server_capabilities(),
+ ),
+ (
+ lsp.WORKSPACE_DID_DELETE_FILES,
+ lsp.FileOperationRegistrationOptions(
+ filters=[
+ lsp.FileOperationFilter(
+ pattern=lsp.FileOperationPattern(glob="**/*.py")
+ )
+ ]
+ ),
+ lsp.ClientCapabilities(
+ workspace=lsp.WorkspaceClientCapabilities(
+ file_operations=lsp.FileOperationClientCapabilities(did_delete=True)
+ )
+ ),
+ server_capabilities(
+ workspace=lsp.ServerCapabilitiesWorkspaceType(
+ workspace_folders=lsp.WorkspaceFoldersServerCapabilities(
+ supported=True, change_notifications=True
+ ),
+ file_operations=lsp.FileOperationOptions(
+ did_delete=lsp.FileOperationRegistrationOptions(
+ filters=[
+ lsp.FileOperationFilter(
+ pattern=lsp.FileOperationPattern(glob="**/*.py")
+ )
+ ]
+ )
+ ),
+ )
+ ),
+ ),
+ (
+ lsp.WORKSPACE_WILL_RENAME_FILES,
+ lsp.FileOperationRegistrationOptions(
+ filters=[
+ lsp.FileOperationFilter(
+ pattern=lsp.FileOperationPattern(glob="**/*.py")
+ )
+ ]
+ ),
+ lsp.ClientCapabilities(),
+ server_capabilities(),
+ ),
+ (
+ lsp.WORKSPACE_WILL_RENAME_FILES,
+ lsp.FileOperationRegistrationOptions(
+ filters=[
+ lsp.FileOperationFilter(
+ pattern=lsp.FileOperationPattern(glob="**/*.py")
+ )
+ ]
+ ),
+ lsp.ClientCapabilities(
+ workspace=lsp.WorkspaceClientCapabilities(
+ file_operations=lsp.FileOperationClientCapabilities(
+ will_rename=True
+ )
+ )
+ ),
+ server_capabilities(
+ workspace=lsp.ServerCapabilitiesWorkspaceType(
+ workspace_folders=lsp.WorkspaceFoldersServerCapabilities(
+ supported=True, change_notifications=True
+ ),
+ file_operations=lsp.FileOperationOptions(
+ will_rename=lsp.FileOperationRegistrationOptions(
+ filters=[
+ lsp.FileOperationFilter(
+ pattern=lsp.FileOperationPattern(glob="**/*.py")
+ )
+ ]
+ )
+ ),
+ )
+ ),
+ ),
+ (
+ lsp.WORKSPACE_DID_RENAME_FILES,
+ lsp.FileOperationRegistrationOptions(
+ filters=[
+ lsp.FileOperationFilter(
+ pattern=lsp.FileOperationPattern(glob="**/*.py")
+ )
+ ]
+ ),
+ lsp.ClientCapabilities(),
+ server_capabilities(),
+ ),
+ (
+ lsp.WORKSPACE_DID_RENAME_FILES,
+ lsp.FileOperationRegistrationOptions(
+ filters=[
+ lsp.FileOperationFilter(
+ pattern=lsp.FileOperationPattern(glob="**/*.py")
+ )
+ ]
+ ),
+ lsp.ClientCapabilities(
+ workspace=lsp.WorkspaceClientCapabilities(
+ file_operations=lsp.FileOperationClientCapabilities(did_rename=True)
+ )
+ ),
+ server_capabilities(
+ workspace=lsp.ServerCapabilitiesWorkspaceType(
+ workspace_folders=lsp.WorkspaceFoldersServerCapabilities(
+ supported=True, change_notifications=True
+ ),
+ file_operations=lsp.FileOperationOptions(
+ did_rename=lsp.FileOperationRegistrationOptions(
+ filters=[
+ lsp.FileOperationFilter(
+ pattern=lsp.FileOperationPattern(glob="**/*.py")
+ )
+ ]
+ )
+ ),
+ )
+ ),
+ ),
+ (
+ lsp.WORKSPACE_SYMBOL,
+ None,
+ lsp.ClientCapabilities(),
+ server_capabilities(
+ workspace_symbol_provider=lsp.WorkspaceSymbolOptions(
+ resolve_provider=False,
+ ),
+ ),
+ ),
+ (
+ lsp.TEXT_DOCUMENT_DIAGNOSTIC,
+ None,
+ lsp.ClientCapabilities(),
+ server_capabilities(
+ diagnostic_provider=lsp.DiagnosticOptions(
+ inter_file_dependencies=False,
+ workspace_diagnostics=False,
+ ),
+ ),
+ ),
+ (
+ lsp.TEXT_DOCUMENT_DIAGNOSTIC,
+ lsp.DiagnosticOptions(
+ workspace_diagnostics=True,
+ inter_file_dependencies=True,
+ ),
+ lsp.ClientCapabilities(),
+ server_capabilities(
+ diagnostic_provider=lsp.DiagnosticOptions(
+ inter_file_dependencies=True,
+ workspace_diagnostics=False,
+ ),
+ ),
+ ),
+ (
+ lsp.TEXT_DOCUMENT_ON_TYPE_FORMATTING,
+ None,
+ lsp.ClientCapabilities(),
+ server_capabilities(
+ document_on_type_formatting_provider=None,
+ ),
+ ),
+ (
+ lsp.TEXT_DOCUMENT_ON_TYPE_FORMATTING,
+ lsp.DocumentOnTypeFormattingOptions(first_trigger_character=":"),
+ lsp.ClientCapabilities(),
+ server_capabilities(
+ document_on_type_formatting_provider=lsp.DocumentOnTypeFormattingOptions(
+ first_trigger_character=":",
+ ),
+ ),
+ ),
+ ],
+)
+def test_register_feature(
+ feature_manager: FeatureManager,
+ method: str,
+ options: Any,
+ capabilities: lsp.ClientCapabilities,
+ expected: lsp.ServerCapabilities,
+):
+ """Ensure that we can register features while specifying their associated
+ options and that `pygls` is able to correctly build the corresponding server
+ capabilities.
+
+ Parameters
+ ----------
+ feature_manager
+ The feature manager to use
+
+ method
+ The method to register the feature handler for.
+
+ options
+ The method options to use
+
+ capabilities
+ The client capabilities to use when building the server's capabilities.
+
+ expected
+ The expected server capabilties we are expecting to see.
+ """
+
+ @feature_manager.feature(method, options)
+ def _():
+ pass
+
+ actual = ServerCapabilitiesBuilder(
+ capabilities,
+ feature_manager.features.keys(),
+ feature_manager.feature_options,
+ [],
+ None,
+ None,
+ ).build()
+
+ assert expected == actual
+
+
+def test_register_inlay_hint_resolve(feature_manager: FeatureManager):
+ @feature_manager.feature(lsp.TEXT_DOCUMENT_INLAY_HINT)
+ def _():
+ pass
+
+ @feature_manager.feature(lsp.INLAY_HINT_RESOLVE)
+ def _():
+ pass
+
+ expected = server_capabilities(
+ inlay_hint_provider=lsp.InlayHintOptions(resolve_provider=True),
+ )
+
+ actual = ServerCapabilitiesBuilder(
+ lsp.ClientCapabilities(),
+ feature_manager.features.keys(),
+ feature_manager.feature_options,
+ [],
+ None,
+ None,
+ ).build()
+
+ assert expected == actual
+
+
+def test_register_workspace_symbol_resolve(feature_manager: FeatureManager):
+ @feature_manager.feature(lsp.WORKSPACE_SYMBOL)
+ def _():
+ pass
+
+ @feature_manager.feature(lsp.WORKSPACE_SYMBOL_RESOLVE)
+ def _():
+ pass
+
+ expected = server_capabilities(
+ workspace_symbol_provider=lsp.WorkspaceSymbolOptions(resolve_provider=True),
+ )
+
+ actual = ServerCapabilitiesBuilder(
+ lsp.ClientCapabilities(),
+ feature_manager.features.keys(),
+ feature_manager.feature_options,
+ [],
+ None,
+ None,
+ ).build()
+
+ assert expected == actual
+
+
+def test_register_workspace_diagnostics(feature_manager: FeatureManager):
+ @feature_manager.feature(
+ lsp.TEXT_DOCUMENT_DIAGNOSTIC,
+ lsp.DiagnosticOptions(
+ identifier="example",
+ inter_file_dependencies=False,
+ workspace_diagnostics=False,
+ ),
+ )
+ def _():
+ pass
+
+ @feature_manager.feature(lsp.WORKSPACE_DIAGNOSTIC)
+ def _():
+ pass
+
+ expected = server_capabilities(
+ diagnostic_provider=lsp.DiagnosticOptions(
+ identifier="example",
+ inter_file_dependencies=False,
+ workspace_diagnostics=True,
+ ),
+ )
+
+ actual = ServerCapabilitiesBuilder(
+ lsp.ClientCapabilities(),
+ feature_manager.features.keys(),
+ feature_manager.feature_options,
+ [],
+ None,
+ None,
+ ).build()
+
+ assert expected == actual
diff --git a/tests/test_language_server.py b/tests/test_language_server.py
new file mode 100644
index 0000000..5271ff5
--- /dev/null
+++ b/tests/test_language_server.py
@@ -0,0 +1,144 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import pathlib
+from time import sleep
+
+import pytest
+
+from pygls import IS_PYODIDE
+from lsprotocol.types import (
+ INITIALIZE,
+ TEXT_DOCUMENT_DID_OPEN,
+ WORKSPACE_EXECUTE_COMMAND,
+)
+from lsprotocol.types import (
+ ClientCapabilities,
+ DidOpenTextDocumentParams,
+ ExecuteCommandParams,
+ InitializeParams,
+ TextDocumentItem,
+)
+from pygls.protocol import LanguageServerProtocol
+from pygls.server import LanguageServer
+from . import CMD_ASYNC, CMD_SYNC, CMD_THREAD
+
+
+def _initialize_server(server):
+ server.lsp.lsp_initialize(
+ InitializeParams(
+ process_id=1234,
+ root_uri=pathlib.Path(__file__).parent.as_uri(),
+ capabilities=ClientCapabilities(),
+ )
+ )
+
+
+def test_bf_initialize(client_server):
+ client, server = client_server
+ root_uri = pathlib.Path(__file__).parent.as_uri()
+ process_id = 1234
+
+ response = client.lsp.send_request(
+ INITIALIZE,
+ InitializeParams(
+ process_id=process_id,
+ root_uri=root_uri,
+ capabilities=ClientCapabilities(),
+ ),
+ ).result()
+
+ assert server.process_id == process_id
+ assert server.workspace.root_uri == root_uri
+ assert response.capabilities is not None
+
+
+def test_bf_text_document_did_open(client_server):
+ client, server = client_server
+
+ _initialize_server(server)
+
+ client.lsp.notify(
+ TEXT_DOCUMENT_DID_OPEN,
+ DidOpenTextDocumentParams(
+ text_document=TextDocumentItem(
+ uri=__file__, language_id="python", version=1, text="test"
+ )
+ ),
+ )
+
+ sleep(1)
+
+ assert len(server.lsp.workspace.text_documents) == 1
+
+ document = server.workspace.get_text_document(__file__)
+ assert document.uri == __file__
+ assert document.version == 1
+ assert document.source == "test"
+ assert document.language_id == "python"
+
+
+@pytest.mark.skipif(IS_PYODIDE, reason="threads are not available in pyodide.")
+def test_command_async(client_server):
+ client, server = client_server
+
+ is_called, thread_id = client.lsp.send_request(
+ WORKSPACE_EXECUTE_COMMAND, ExecuteCommandParams(command=CMD_ASYNC)
+ ).result()
+
+ assert is_called
+ assert thread_id == server.thread_id
+
+
+@pytest.mark.skipif(IS_PYODIDE, reason="threads are not available in pyodide.")
+def test_command_sync(client_server):
+ client, server = client_server
+
+ is_called, thread_id = client.lsp.send_request(
+ WORKSPACE_EXECUTE_COMMAND, ExecuteCommandParams(command=CMD_SYNC)
+ ).result()
+
+ assert is_called
+ assert thread_id == server.thread_id
+
+
+@pytest.mark.skipif(IS_PYODIDE, reason="threads are not available in pyodide.")
+def test_command_thread(client_server):
+ client, server = client_server
+
+ is_called, thread_id = client.lsp.send_request(
+ WORKSPACE_EXECUTE_COMMAND, ExecuteCommandParams(command=CMD_THREAD)
+ ).result()
+
+ assert is_called
+ assert thread_id != server.thread_id
+
+
+def test_allow_custom_protocol_derived_from_lsp():
+ class CustomProtocol(LanguageServerProtocol):
+ pass
+
+ server = LanguageServer("pygls-test", "v1", protocol_cls=CustomProtocol)
+
+ assert isinstance(server.lsp, CustomProtocol)
+
+
+def test_forbid_custom_protocol_not_derived_from_lsp():
+ class CustomProtocol:
+ pass
+
+ with pytest.raises(TypeError):
+ LanguageServer("pygls-test", "v1", protocol_cls=CustomProtocol)
diff --git a/tests/test_protocol.py b/tests/test_protocol.py
new file mode 100644
index 0000000..63b689f
--- /dev/null
+++ b/tests/test_protocol.py
@@ -0,0 +1,660 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved. #
+# See ThirdPartyNotices.txt in the project root for additional notices. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import io
+import json
+from concurrent.futures import Future
+from pathlib import Path
+from typing import Optional
+from unittest.mock import Mock
+
+import attrs
+import pytest
+
+from pygls.exceptions import JsonRpcException, JsonRpcInvalidParams
+from lsprotocol.types import (
+ PROGRESS,
+ TEXT_DOCUMENT_COMPLETION,
+ ClientCapabilities,
+ CompletionItem,
+ CompletionItemKind,
+ CompletionParams,
+ InitializeParams,
+ InitializeResult,
+ ProgressParams,
+ Position,
+ ShutdownResponse,
+ TextDocumentCompletionResponse,
+ TextDocumentIdentifier,
+ WorkDoneProgressBegin,
+)
+from pygls.protocol import (
+ default_converter,
+ JsonRPCProtocol,
+ JsonRPCRequestMessage,
+ JsonRPCResponseMessage,
+ JsonRPCNotification,
+)
+
+EXAMPLE_NOTIFICATION = "example/notification"
+EXAMPLE_REQUEST = "example/request"
+
+
+@attrs.define
+class IntResult:
+ id: str
+ result: int
+ jsonrpc: str = attrs.field(default="2.0")
+
+
+@attrs.define
+class ExampleParams:
+ @attrs.define
+ class InnerType:
+ inner_field: str
+
+ field_a: str
+ field_b: Optional[InnerType] = None
+
+
+@attrs.define
+class ExampleNotification:
+ jsonrpc: str = attrs.field(default="2.0")
+ method: str = EXAMPLE_NOTIFICATION
+ params: ExampleParams = attrs.field(default=None)
+
+
+@attrs.define
+class ExampleRequest:
+ id: str
+ jsonrpc: str = attrs.field(default="2.0")
+ method: str = EXAMPLE_REQUEST
+ params: ExampleParams = attrs.field(default=None)
+
+
+EXAMPLE_LSP_METHODS_MAP = {
+ EXAMPLE_NOTIFICATION: (ExampleNotification, None, ExampleParams, None),
+ EXAMPLE_REQUEST: (ExampleRequest, None, ExampleParams, None),
+}
+
+
+class ExampleProtocol(JsonRPCProtocol):
+ def get_message_type(self, method: str):
+ return EXAMPLE_LSP_METHODS_MAP.get(method, (None,))[0]
+
+
+@pytest.fixture()
+def protocol():
+ return ExampleProtocol(None, default_converter())
+
+
+def test_deserialize_notification_message_valid_params(protocol):
+ params = f"""
+ {{
+ "jsonrpc": "2.0",
+ "method": "{EXAMPLE_NOTIFICATION}",
+ "params": {{
+ "fieldA": "test_a",
+ "fieldB": {{
+ "innerField": "test_inner"
+ }}
+ }}
+ }}
+ """
+
+ result = json.loads(params, object_hook=protocol._deserialize_message)
+
+ assert isinstance(
+ result, ExampleNotification
+ ), f"Expected FeatureRequest instance, got {result}"
+ assert result.jsonrpc == "2.0"
+ assert result.method == EXAMPLE_NOTIFICATION
+
+ assert isinstance(result.params, ExampleParams)
+ assert result.params.field_a == "test_a"
+
+ assert isinstance(result.params.field_b, ExampleParams.InnerType)
+ assert result.params.field_b.inner_field == "test_inner"
+
+
+def test_deserialize_notification_message_unknown_type(protocol):
+ params = """
+ {
+ "jsonrpc": "2.0",
+ "method": "random",
+ "params": {
+ "field_a": "test_a",
+ "field_b": {
+ "inner_field": "test_inner"
+ }
+ }
+ }
+ """
+
+ result = json.loads(params, object_hook=protocol._deserialize_message)
+
+ assert isinstance(result, JsonRPCNotification)
+ assert result.jsonrpc == "2.0"
+ assert result.method == "random"
+
+ assert result.params.field_a == "test_a"
+ assert result.params.field_b.inner_field == "test_inner"
+
+
+def test_deserialize_notification_message_bad_params_should_raise_error(protocol):
+ params = f"""
+ {{
+ "jsonrpc": "2.0",
+ "method": "{EXAMPLE_NOTIFICATION}",
+ "params": {{
+ "field_a": "test_a",
+ "field_b": {{
+ "wrong_field_name": "test_inner"
+ }}
+ }}
+ }}
+ """
+
+ with pytest.raises(JsonRpcInvalidParams):
+ json.loads(params, object_hook=protocol._deserialize_message)
+
+
+def test_deserialize_response_message_custom_converter():
+ params = """
+ {
+ "jsonrpc": "2.0",
+ "id": "id",
+ "result": "1"
+ }
+ """
+
+ # Just for fun, let's create a converter that reverses all the keys in a dict.
+ #
+ @attrs.define
+ class egasseM:
+ cprnosj: str
+ di: str
+ tluser: str
+
+ def structure_hook(obj, cls):
+ params = {k[::-1]: v for k, v in obj.items()}
+ return cls(**params)
+
+ def custom_converter():
+ converter = default_converter()
+ converter.register_structure_hook(egasseM, structure_hook)
+ return converter
+
+ protocol = JsonRPCProtocol(None, custom_converter())
+ protocol._result_types["id"] = egasseM
+ result = json.loads(params, object_hook=protocol._deserialize_message)
+
+ assert isinstance(result, egasseM)
+ assert result.cprnosj == "2.0"
+ assert result.di == "id"
+ assert result.tluser == "1"
+
+
+@pytest.mark.parametrize(
+ "method, params, expected",
+ [
+ (
+ # Known notification type.
+ PROGRESS,
+ ProgressParams(
+ token="id1",
+ value=WorkDoneProgressBegin(
+ title="Begin progress",
+ percentage=0,
+ ),
+ ),
+ {
+ "jsonrpc": "2.0",
+ "method": "$/progress",
+ "params": {
+ "token": "id1",
+ "value": {
+ "kind": "begin",
+ "percentage": 0,
+ "title": "Begin progress",
+ },
+ },
+ },
+ ),
+ (
+ # Custom notification type.
+ EXAMPLE_NOTIFICATION,
+ ExampleParams(
+ field_a="field one",
+ field_b=ExampleParams.InnerType(inner_field="field two"),
+ ),
+ {
+ "jsonrpc": "2.0",
+ "method": EXAMPLE_NOTIFICATION,
+ "params": {
+ "fieldA": "field one",
+ "fieldB": {
+ "innerField": "field two",
+ },
+ },
+ },
+ ),
+ (
+ # Custom notification with dict params.
+ EXAMPLE_NOTIFICATION,
+ {"fieldA": "field one", "fieldB": {"innerField": "field two"}},
+ {
+ "jsonrpc": "2.0",
+ "method": EXAMPLE_NOTIFICATION,
+ "params": {
+ "fieldA": "field one",
+ "fieldB": {
+ "innerField": "field two",
+ },
+ },
+ },
+ ),
+ ],
+)
+def test_serialize_notification_message(method, params, expected):
+ """
+ Ensure that we can serialize notification messages, retaining all
+ expected fields.
+ """
+
+ buffer = io.StringIO()
+
+ protocol = JsonRPCProtocol(None, default_converter())
+ protocol._send_only_body = True
+ protocol.connection_made(buffer)
+
+ protocol.notify(method, params=params)
+ actual = json.loads(buffer.getvalue())
+
+ assert actual == expected
+
+
+def test_deserialize_response_message(protocol):
+ params = """
+ {
+ "jsonrpc": "2.0",
+ "id": "id",
+ "result": "1"
+ }
+ """
+ protocol._result_types["id"] = IntResult
+ result = json.loads(params, object_hook=protocol._deserialize_message)
+
+ assert isinstance(result, IntResult)
+ assert result.jsonrpc == "2.0"
+ assert result.id == "id"
+ assert result.result == 1
+
+
+def test_deserialize_response_message_unknown_type(protocol):
+ params = """
+ {
+ "jsonrpc": "2.0",
+ "id": "id",
+ "result": {
+ "field_a": "test_a",
+ "field_b": {
+ "inner_field": "test_inner"
+ }
+ }
+ }
+ """
+ protocol._result_types["id"] = JsonRPCResponseMessage
+ result = json.loads(params, object_hook=protocol._deserialize_message)
+
+ assert isinstance(result, JsonRPCResponseMessage)
+ assert result.jsonrpc == "2.0"
+ assert result.id == "id"
+
+ assert result.result.field_a == "test_a"
+ assert result.result.field_b.inner_field == "test_inner"
+
+
+def test_deserialize_request_message_with_registered_type(protocol):
+ params = f"""
+ {{
+ "jsonrpc": "2.0",
+ "id": "id",
+ "method": "{EXAMPLE_REQUEST}",
+ "params": {{
+ "fieldA": "test_a",
+ "fieldB": {{
+ "innerField": "test_inner"
+ }}
+ }}
+ }}
+ """
+ result = json.loads(params, object_hook=protocol._deserialize_message)
+
+ assert isinstance(result, ExampleRequest)
+ assert result.jsonrpc == "2.0"
+ assert result.id == "id"
+ assert result.method == EXAMPLE_REQUEST
+
+ assert isinstance(result.params, ExampleParams)
+ assert result.params.field_a == "test_a"
+
+ assert isinstance(result.params.field_b, ExampleParams.InnerType)
+ assert result.params.field_b.inner_field == "test_inner"
+
+
+def test_deserialize_request_message_without_registered_type(protocol):
+ params = """
+ {
+ "jsonrpc": "2.0",
+ "id": "id",
+ "method": "random",
+ "params": {
+ "field_a": "test_a",
+ "field_b": {
+ "inner_field": "test_inner"
+ }
+ }
+ }
+ """
+ result = json.loads(params, object_hook=protocol._deserialize_message)
+
+ assert isinstance(result, JsonRPCRequestMessage)
+ assert result.jsonrpc == "2.0"
+ assert result.id == "id"
+ assert result.method == "random"
+
+ assert result.params.field_a == "test_a"
+ assert result.params.field_b.inner_field == "test_inner"
+
+
+@pytest.mark.parametrize(
+ "msg_type, result, expected",
+ [
+ (ShutdownResponse, None, {"jsonrpc": "2.0", "id": "1", "result": None}),
+ (
+ TextDocumentCompletionResponse,
+ [
+ CompletionItem(label="example-one"),
+ CompletionItem(
+ label="example-two",
+ kind=CompletionItemKind.Class,
+ preselect=False,
+ deprecated=True,
+ ),
+ ],
+ {
+ "jsonrpc": "2.0",
+ "id": "1",
+ "result": [
+ {"label": "example-one"},
+ {
+ "label": "example-two",
+ "kind": 7, # CompletionItemKind.Class
+ "preselect": False,
+ "deprecated": True,
+ },
+ ],
+ },
+ ),
+ ( # Unknown type with object params.
+ JsonRPCResponseMessage,
+ ExampleParams(
+ field_a="field one",
+ field_b=ExampleParams.InnerType(inner_field="field two"),
+ ),
+ {
+ "jsonrpc": "2.0",
+ "id": "1",
+ "result": {
+ "fieldA": "field one",
+ "fieldB": {"innerField": "field two"},
+ },
+ },
+ ),
+ ( # Unknown type with dict params.
+ JsonRPCResponseMessage,
+ {"fieldA": "field one", "fieldB": {"innerField": "field two"}},
+ {
+ "jsonrpc": "2.0",
+ "id": "1",
+ "result": {
+ "fieldA": "field one",
+ "fieldB": {"innerField": "field two"},
+ },
+ },
+ ),
+ ],
+)
+def test_serialize_response_message(msg_type, result, expected):
+ """
+ Ensure that we can serialize response messages, retaining all expected
+ fields.
+ """
+
+ buffer = io.StringIO()
+
+ protocol = JsonRPCProtocol(None, default_converter())
+ protocol._send_only_body = True
+ protocol.connection_made(buffer)
+
+ protocol._result_types["1"] = msg_type
+
+ protocol._send_response("1", result=result)
+ actual = json.loads(buffer.getvalue())
+
+ assert actual == expected
+
+
+@pytest.mark.parametrize(
+ "method, params, expected",
+ [
+ (
+ TEXT_DOCUMENT_COMPLETION,
+ CompletionParams(
+ text_document=TextDocumentIdentifier(uri="file:///file.txt"),
+ position=Position(line=1, character=0),
+ ),
+ {
+ "jsonrpc": "2.0",
+ "id": "1",
+ "method": TEXT_DOCUMENT_COMPLETION,
+ "params": {
+ "textDocument": {"uri": "file:///file.txt"},
+ "position": {"line": 1, "character": 0},
+ },
+ },
+ ),
+ ( # Unknown type with object params.
+ EXAMPLE_REQUEST,
+ ExampleParams(
+ field_a="field one",
+ field_b=ExampleParams.InnerType(inner_field="field two"),
+ ),
+ {
+ "jsonrpc": "2.0",
+ "id": "1",
+ "method": EXAMPLE_REQUEST,
+ "params": {
+ "fieldA": "field one",
+ "fieldB": {"innerField": "field two"},
+ },
+ },
+ ),
+ ( # Unknown type with dict params.
+ EXAMPLE_REQUEST,
+ {"fieldA": "field one", "fieldB": {"innerField": "field two"}},
+ {
+ "jsonrpc": "2.0",
+ "id": "1",
+ "method": EXAMPLE_REQUEST,
+ "params": {
+ "fieldA": "field one",
+ "fieldB": {"innerField": "field two"},
+ },
+ },
+ ),
+ ],
+)
+def test_serialize_request_message(method, params, expected):
+ """
+ Ensure that we can serialize request messages, retaining all expected
+ fields.
+ """
+
+ buffer = io.StringIO()
+
+ protocol = JsonRPCProtocol(None, default_converter())
+ protocol._send_only_body = True
+ protocol.connection_made(buffer)
+
+ protocol.send_request(method, params, callback=None, msg_id="1")
+ actual = json.loads(buffer.getvalue())
+
+ assert actual == expected
+
+
+def test_data_received_without_content_type(client_server):
+ _, server = client_server
+ body = json.dumps(
+ {
+ "jsonrpc": "2.0",
+ "method": "test",
+ "params": 1,
+ }
+ )
+ message = "\r\n".join(
+ (
+ "Content-Length: " + str(len(body)),
+ "",
+ body,
+ )
+ )
+ data = bytes(message, "utf-8")
+ server.lsp.data_received(data)
+
+
+def test_data_received_content_type_first_should_handle_message(client_server):
+ _, server = client_server
+ body = json.dumps(
+ {
+ "jsonrpc": "2.0",
+ "method": "test",
+ "params": 1,
+ }
+ )
+ message = "\r\n".join(
+ (
+ "Content-Type: application/vscode-jsonrpc; charset=utf-8",
+ "Content-Length: " + str(len(body)),
+ "",
+ body,
+ )
+ )
+ data = bytes(message, "utf-8")
+ server.lsp.data_received(data)
+
+
+def dummy_message(param=1):
+ body = json.dumps(
+ {
+ "jsonrpc": "2.0",
+ "method": "test",
+ "params": param,
+ }
+ )
+ message = "\r\n".join(
+ (
+ "Content-Length: " + str(len(body)),
+ "Content-Type: application/vscode-jsonrpc; charset=utf-8",
+ "",
+ body,
+ )
+ )
+ return bytes(message, "utf-8")
+
+
+def test_data_received_single_message_should_handle_message(client_server):
+ _, server = client_server
+ data = dummy_message()
+ server.lsp.data_received(data)
+
+
+def test_data_received_partial_message_should_handle_message(client_server):
+ _, server = client_server
+ data = dummy_message()
+ partial = len(data) - 5
+ server.lsp.data_received(data[:partial])
+ server.lsp.data_received(data[partial:])
+
+
+def test_data_received_multi_message_should_handle_messages(client_server):
+ _, server = client_server
+ messages = (dummy_message(i) for i in range(3))
+ data = b"".join(messages)
+ server.lsp.data_received(data)
+
+
+def test_data_received_error_should_raise_jsonrpc_error(client_server):
+ _, server = client_server
+ body = json.dumps(
+ {
+ "jsonrpc": "2.0",
+ "id": "err",
+ "error": {
+ "code": -1,
+ "message": "message for you sir",
+ },
+ }
+ )
+ message = "\r\n".join(
+ [
+ "Content-Length: " + str(len(body)),
+ "Content-Type: application/vscode-jsonrpc; charset=utf-8",
+ "",
+ body,
+ ]
+ ).encode("utf-8")
+ future = server.lsp._request_futures["err"] = Future()
+ server.lsp.data_received(message)
+ with pytest.raises(JsonRpcException, match="message for you sir"):
+ future.result()
+
+
+def test_initialize_should_return_server_capabilities(client_server):
+ _, server = client_server
+ params = InitializeParams(
+ process_id=1234,
+ root_uri=Path(__file__).parent.as_uri(),
+ capabilities=ClientCapabilities(),
+ )
+
+ server_capabilities = server.lsp.lsp_initialize(params)
+
+ assert isinstance(server_capabilities, InitializeResult)
+
+
+def test_ignore_unknown_notification(client_server):
+ _, server = client_server
+
+ fn = server.lsp._execute_notification
+ server.lsp._execute_notification = Mock()
+
+ server.lsp._handle_notification("random/notification", None)
+ assert not server.lsp._execute_notification.called
+
+ # Remove mock
+ server.lsp._execute_notification = fn
diff --git a/tests/test_server_connection.py b/tests/test_server_connection.py
new file mode 100644
index 0000000..1fda258
--- /dev/null
+++ b/tests/test_server_connection.py
@@ -0,0 +1,135 @@
+import asyncio
+import json
+import os
+from threading import Thread
+from unittest.mock import Mock
+
+import pytest
+
+from pygls import IS_PYODIDE
+from pygls.server import LanguageServer
+
+try:
+ import websockets
+
+ WEBSOCKETS_AVAILABLE = True
+except ImportError:
+ WEBSOCKETS_AVAILABLE = False
+
+
+@pytest.mark.asyncio
+@pytest.mark.skipif(IS_PYODIDE, reason="threads are not available in pyodide.")
+async def test_tcp_connection_lost():
+ loop = asyncio.new_event_loop()
+
+ server = LanguageServer("pygls-test", "v1", loop=loop)
+
+ server.lsp.connection_made = Mock()
+ server.lsp.connection_lost = Mock()
+
+ # Run the server over TCP in a separate thread
+ server_thread = Thread(
+ target=server.start_tcp,
+ args=(
+ "127.0.0.1",
+ 0,
+ ),
+ )
+ server_thread.daemon = True
+ server_thread.start()
+
+ # Wait for server to be ready
+ while server._server is None:
+ await asyncio.sleep(0.5)
+
+ # Simulate client's connection
+ port = server._server.sockets[0].getsockname()[1]
+ reader, writer = await asyncio.open_connection("127.0.0.1", port)
+ await asyncio.sleep(1)
+
+ assert server.lsp.connection_made.called
+
+ # Socket is closed (client's process is terminated)
+ writer.close()
+ await asyncio.sleep(1)
+
+ assert server.lsp.connection_lost.called
+
+
+@pytest.mark.asyncio
+@pytest.mark.skipif(IS_PYODIDE, reason="threads are not available in pyodide.")
+async def test_io_connection_lost():
+ # Client to Server pipe.
+ csr, csw = os.pipe()
+ # Server to client pipe.
+ scr, scw = os.pipe()
+
+ server = LanguageServer("pygls-test", "v1", loop=asyncio.new_event_loop())
+ server.lsp.connection_made = Mock()
+ server_thread = Thread(
+ target=server.start_io, args=(os.fdopen(csr, "rb"), os.fdopen(scw, "wb"))
+ )
+ server_thread.daemon = True
+ server_thread.start()
+
+ # Wait for server to be ready
+ while not server.lsp.connection_made.called:
+ await asyncio.sleep(0.5)
+
+ # Pipe is closed (client's process is terminated)
+ os.close(csw)
+ server_thread.join()
+
+
+@pytest.mark.asyncio
+@pytest.mark.skipif(
+ IS_PYODIDE or not WEBSOCKETS_AVAILABLE,
+ reason="threads are not available in pyodide",
+)
+async def test_ws_server():
+ """Smoke test to ensure we can send/receive messages over websockets"""
+
+ loop = asyncio.new_event_loop()
+ server = LanguageServer("pygls-test", "v1", loop=loop)
+
+ # Run the server over Websockets in a separate thread
+ server_thread = Thread(
+ target=server.start_ws,
+ args=(
+ "127.0.0.1",
+ 0,
+ ),
+ )
+ server_thread.daemon = True
+ server_thread.start()
+
+ # Wait for server to be ready
+ while server._server is None:
+ await asyncio.sleep(0.5)
+
+ port = server._server.sockets[0].getsockname()[1]
+ # Simulate client's connection
+ async with websockets.connect(f"ws://127.0.0.1:{port}") as connection:
+ # Send an 'initialize' request
+ msg = dict(
+ jsonrpc="2.0", id=1, method="initialize", params=dict(capabilities=dict())
+ )
+ await connection.send(json.dumps(msg))
+
+ response = await connection.recv()
+ assert "result" in response
+
+ # Shut the server down
+ msg = dict(
+ jsonrpc="2.0", id=2, method="shutdown", params=dict(capabilities=dict())
+ )
+ await connection.send(json.dumps(msg))
+
+ response = await connection.recv()
+ assert "result" in response
+
+ # Finally, tell it to exit
+ msg = dict(jsonrpc="2.0", id=2, method="exit", params=None)
+ await connection.send(json.dumps(msg))
+
+ server_thread.join()
diff --git a/tests/test_types.py b/tests/test_types.py
new file mode 100644
index 0000000..0958493
--- /dev/null
+++ b/tests/test_types.py
@@ -0,0 +1,86 @@
+############################################################################
+# Original work Copyright 2018 Palantir Technologies, Inc. #
+# Original work licensed under the MIT License. #
+# See ThirdPartyNotices.txt in the project root for license information. #
+# All modifications Copyright (c) Open Law Library. All rights reserved. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+from lsprotocol.types import Location, Position, Range
+
+
+def test_position():
+ assert Position(line=1, character=2) == Position(line=1, character=2)
+ assert Position(line=1, character=2) != Position(line=2, character=2)
+ assert Position(line=1, character=2) <= Position(line=2, character=2)
+ assert Position(line=2, character=2) >= Position(line=2, character=0)
+ assert Position(line=1, character=2) != "something else"
+ assert "1:2" == repr(Position(line=1, character=2))
+
+
+def test_range():
+ assert Range(
+ start=Position(line=1, character=2), end=Position(line=3, character=4)
+ ) == Range(start=Position(line=1, character=2), end=Position(line=3, character=4))
+ assert Range(
+ start=Position(line=0, character=2), end=Position(line=3, character=4)
+ ) != Range(start=Position(line=1, character=2), end=Position(line=3, character=4))
+ assert (
+ Range(start=Position(line=0, character=2), end=Position(line=3, character=4))
+ != "something else"
+ )
+ assert "1:2-3:4" == repr(
+ Range(start=Position(line=1, character=2), end=Position(line=3, character=4))
+ )
+
+
+def test_location():
+ assert Location(
+ uri="file:///document.txt",
+ range=Range(
+ start=Position(line=1, character=2), end=Position(line=3, character=4)
+ ),
+ ) == Location(
+ uri="file:///document.txt",
+ range=Range(
+ start=Position(line=1, character=2), end=Position(line=3, character=4)
+ ),
+ )
+ assert Location(
+ uri="file:///document.txt",
+ range=Range(
+ start=Position(line=1, character=2), end=Position(line=3, character=4)
+ ),
+ ) != Location(
+ uri="file:///another.txt",
+ range=Range(
+ start=Position(line=1, character=2), end=Position(line=3, character=4)
+ ),
+ )
+ assert (
+ Location(
+ uri="file:///document.txt",
+ range=Range(
+ start=Position(line=1, character=2), end=Position(line=3, character=4)
+ ),
+ )
+ != "something else"
+ )
+ assert "file:///document.txt:1:2-3:4" == repr(
+ Location(
+ uri="file:///document.txt",
+ range=Range(
+ start=Position(line=1, character=2), end=Position(line=3, character=4)
+ ),
+ )
+ )
diff --git a/tests/test_uris.py b/tests/test_uris.py
new file mode 100644
index 0000000..16c8bd4
--- /dev/null
+++ b/tests/test_uris.py
@@ -0,0 +1,87 @@
+############################################################################
+# Original work Copyright 2018 Palantir Technologies, Inc. #
+# Original work licensed under the MIT License. #
+# See ThirdPartyNotices.txt in the project root for license information. #
+# All modifications Copyright (c) Open Law Library. All rights reserved. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import pytest
+
+from pygls import uris
+from . import unix_only, windows_only
+
+
+@unix_only
+@pytest.mark.parametrize(
+ "path,uri",
+ [
+ ("/foo/bar", "file:///foo/bar"),
+ ("/foo/space ?bar", "file:///foo/space%20%3Fbar"),
+ ],
+)
+def test_from_fs_path(path, uri):
+ assert uris.from_fs_path(path) == uri
+
+
+@unix_only
+@pytest.mark.parametrize(
+ "uri,path",
+ [
+ ("file:///foo/bar#frag", "/foo/bar"),
+ ("file:/foo/bar#frag", "/foo/bar"),
+ ("file:/foo/space%20%3Fbar#frag", "/foo/space ?bar"),
+ ],
+)
+def test_to_fs_path(uri, path):
+ assert uris.to_fs_path(uri) == path
+
+
+@pytest.mark.parametrize(
+ "uri,kwargs,new_uri",
+ [
+ ("file:///foo/bar", {"path": "/baz/boo"}, "file:///baz/boo"),
+ (
+ "file:///D:/hello%20world.py",
+ {"path": "D:/hello universe.py"},
+ "file:///d:/hello%20universe.py",
+ ),
+ ],
+)
+def test_uri_with(uri, kwargs, new_uri):
+ assert uris.uri_with(uri, **kwargs) == new_uri
+
+
+@windows_only
+@pytest.mark.parametrize(
+ "path,uri",
+ [
+ ("c:\\far\\boo", "file:///c:/far/boo"),
+ ("C:\\far\\space ?boo", "file:///c:/far/space%20%3Fboo"),
+ ],
+)
+def test_win_from_fs_path(path, uri):
+ assert uris.from_fs_path(path) == uri
+
+
+@windows_only
+@pytest.mark.parametrize(
+ "uri,path",
+ [
+ ("file:///c:/far/boo", "c:\\far\\boo"),
+ ("file:///C:/far/boo", "c:\\far\\boo"),
+ ("file:///C:/far/space%20%3Fboo", "c:\\far\\space ?boo"),
+ ],
+)
+def test_win_to_fs_path(uri, path):
+ assert uris.to_fs_path(uri) == path
diff --git a/tests/test_workspace.py b/tests/test_workspace.py
new file mode 100644
index 0000000..52d50f3
--- /dev/null
+++ b/tests/test_workspace.py
@@ -0,0 +1,442 @@
+############################################################################
+# Original work Copyright 2017 Palantir Technologies, Inc. #
+# Original work licensed under the MIT License. #
+# See ThirdPartyNotices.txt in the project root for license information. #
+# All modifications Copyright (c) Open Law Library. All rights reserved. #
+# #
+# Licensed under the Apache License, Version 2.0 (the "License") #
+# you may not use this file except in compliance with the License. #
+# You may obtain a copy of the License at #
+# #
+# http: // www.apache.org/licenses/LICENSE-2.0 #
+# #
+# Unless required by applicable law or agreed to in writing, software #
+# distributed under the License is distributed on an "AS IS" BASIS, #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and #
+# limitations under the License. #
+############################################################################
+import os
+
+import pytest
+from lsprotocol import types
+
+from pygls import uris
+from pygls.workspace import Workspace
+
+DOC_URI = uris.from_fs_path(__file__)
+DOC_TEXT = """test"""
+DOC = types.TextDocumentItem(
+ uri=DOC_URI, language_id="plaintext", version=0, text=DOC_TEXT
+)
+NOTEBOOK = types.NotebookDocument(
+ uri="file:///path/to/notebook.ipynb",
+ notebook_type="jupyter-notebook",
+ version=0,
+ cells=[
+ types.NotebookCell(
+ kind=types.NotebookCellKind.Code,
+ document="nb-cell-scheme://path/to/notebook.ipynb#cv32321",
+ ),
+ types.NotebookCell(
+ kind=types.NotebookCellKind.Code,
+ document="nb-cell-scheme://path/to/notebook.ipynb#cp897h32",
+ ),
+ ],
+)
+NB_CELL_1 = types.TextDocumentItem(
+ uri="nb-cell-scheme://path/to/notebook.ipynb#cv32321",
+ language_id="python",
+ version=0,
+ text="# cell 1",
+)
+NB_CELL_2 = types.TextDocumentItem(
+ uri="nb-cell-scheme://path/to/notebook.ipynb#cp897h32",
+ language_id="python",
+ version=0,
+ text="# cell 2",
+)
+NB_CELL_3 = types.TextDocumentItem(
+ uri="nb-cell-scheme://path/to/notebook.ipynb#cq343eeds",
+ language_id="python",
+ version=0,
+ text="# cell 3",
+)
+
+
+def test_add_folder(workspace):
+ dir_uri = os.path.dirname(DOC_URI)
+ dir_name = "test"
+ workspace.add_folder(types.WorkspaceFolder(uri=dir_uri, name=dir_name))
+ assert workspace.folders[dir_uri].name == dir_name
+
+
+def test_get_notebook_document_by_uri(workspace):
+ """Ensure that we can get a notebook given its uri."""
+ params = types.DidOpenNotebookDocumentParams(
+ notebook_document=NOTEBOOK,
+ cell_text_documents=[
+ NB_CELL_1,
+ NB_CELL_2,
+ ],
+ )
+ workspace.put_notebook_document(params)
+
+ notebook = workspace.get_notebook_document(notebook_uri=NOTEBOOK.uri)
+ assert notebook == NOTEBOOK
+
+
+@pytest.mark.parametrize(
+ "cell,expected",
+ [
+ (NB_CELL_1, NOTEBOOK),
+ (NB_CELL_2, NOTEBOOK),
+ (NB_CELL_3, None),
+ (DOC, None),
+ ],
+)
+def test_get_notebook_document_by_cell_uri(workspace, cell, expected):
+ """Ensure that we can get a notebook given a uri of one of its cells"""
+ params = types.DidOpenNotebookDocumentParams(
+ notebook_document=NOTEBOOK,
+ cell_text_documents=[
+ NB_CELL_1,
+ NB_CELL_2,
+ ],
+ )
+ workspace.put_notebook_document(params)
+
+ notebook = workspace.get_notebook_document(cell_uri=cell.uri)
+ assert notebook == expected
+
+
+def test_get_text_document(workspace):
+ workspace.put_text_document(DOC)
+
+ assert workspace.get_text_document(DOC_URI).source == DOC_TEXT
+
+
+def test_get_missing_document(tmpdir, workspace):
+ doc_path = tmpdir.join("test_document.py")
+ doc_path.write(DOC_TEXT)
+ doc_uri = uris.from_fs_path(str(doc_path))
+ assert workspace.get_text_document(doc_uri).source == DOC_TEXT
+
+
+def test_put_notebook_document(workspace):
+ """Ensure that we can add notebook documents to the workspace correctly."""
+ params = types.DidOpenNotebookDocumentParams(
+ notebook_document=NOTEBOOK,
+ cell_text_documents=[
+ NB_CELL_1,
+ NB_CELL_2,
+ ],
+ )
+ workspace.put_notebook_document(params)
+
+ assert NOTEBOOK.uri in workspace._notebook_documents
+ assert NB_CELL_1.uri in workspace._text_documents
+ assert NB_CELL_2.uri in workspace._text_documents
+
+
+def test_put_text_document(workspace):
+ workspace.put_text_document(DOC)
+ assert DOC_URI in workspace._text_documents
+
+
+def test_remove_folder(workspace):
+ dir_uri = os.path.dirname(DOC_URI)
+ dir_name = "test"
+ workspace.add_folder(types.WorkspaceFolder(uri=dir_uri, name=dir_name))
+ workspace.remove_folder(dir_uri)
+
+ assert dir_uri not in workspace.folders
+
+
+def test_remove_notebook_document(workspace):
+ """Ensure that we can correctly remove a document from the workspace."""
+ params = types.DidOpenNotebookDocumentParams(
+ notebook_document=NOTEBOOK,
+ cell_text_documents=[
+ NB_CELL_1,
+ NB_CELL_2,
+ ],
+ )
+ workspace.put_notebook_document(params)
+
+ assert NOTEBOOK.uri in workspace._notebook_documents
+ assert NB_CELL_1.uri in workspace._text_documents
+ assert NB_CELL_2.uri in workspace._text_documents
+
+ params = types.DidCloseNotebookDocumentParams(
+ notebook_document=types.NotebookDocumentIdentifier(uri=NOTEBOOK.uri),
+ cell_text_documents=[
+ types.TextDocumentIdentifier(uri=NB_CELL_1.uri),
+ types.TextDocumentIdentifier(uri=NB_CELL_2.uri),
+ ],
+ )
+ workspace.remove_notebook_document(params)
+
+ assert NOTEBOOK.uri not in workspace._notebook_documents
+ assert NB_CELL_1.uri not in workspace._text_documents
+ assert NB_CELL_2.uri not in workspace._text_documents
+
+
+def test_remove_text_document(workspace):
+ workspace.put_text_document(DOC)
+ assert workspace.get_text_document(DOC_URI).source == DOC_TEXT
+ workspace.remove_text_document(DOC_URI)
+ assert workspace.get_text_document(DOC_URI)._source is None
+
+
+def test_update_notebook_metadata(workspace):
+ """Ensure we can update a notebook's metadata correctly."""
+ params = types.DidOpenNotebookDocumentParams(
+ notebook_document=NOTEBOOK,
+ cell_text_documents=[
+ NB_CELL_1,
+ NB_CELL_2,
+ ],
+ )
+ workspace.put_notebook_document(params)
+
+ notebook = workspace.get_notebook_document(notebook_uri=NOTEBOOK.uri)
+ assert notebook.version == 0
+ assert notebook.metadata is None
+
+ params = types.DidChangeNotebookDocumentParams(
+ notebook_document=types.VersionedNotebookDocumentIdentifier(
+ uri=NOTEBOOK.uri, version=31
+ ),
+ change=types.NotebookDocumentChangeEvent(
+ metadata={"custom": "metadata"},
+ ),
+ )
+ workspace.update_notebook_document(params)
+
+ notebook = workspace.get_notebook_document(notebook_uri=NOTEBOOK.uri)
+ assert notebook.version == 31
+ assert notebook.metadata == {"custom": "metadata"}
+
+
+def test_update_notebook_cell_data(workspace):
+ """Ensure we can update a notebook correctly when cell data changes."""
+ params = types.DidOpenNotebookDocumentParams(
+ notebook_document=NOTEBOOK,
+ cell_text_documents=[
+ NB_CELL_1,
+ NB_CELL_2,
+ ],
+ )
+ workspace.put_notebook_document(params)
+
+ notebook = workspace.get_notebook_document(notebook_uri=NOTEBOOK.uri)
+ assert notebook.version == 0
+
+ cell_1 = notebook.cells[0]
+ assert cell_1.metadata is None
+ assert cell_1.execution_summary is None
+
+ cell_2 = notebook.cells[1]
+ assert cell_2.metadata is None
+ assert cell_2.execution_summary is None
+
+ params = types.DidChangeNotebookDocumentParams(
+ notebook_document=types.VersionedNotebookDocumentIdentifier(
+ uri=NOTEBOOK.uri, version=31
+ ),
+ change=types.NotebookDocumentChangeEvent(
+ cells=types.NotebookDocumentChangeEventCellsType(
+ data=[
+ types.NotebookCell(
+ kind=types.NotebookCellKind.Code,
+ document=NB_CELL_1.uri,
+ metadata={"slideshow": {"slide_type": "skip"}},
+ execution_summary=types.ExecutionSummary(
+ execution_order=2, success=True
+ ),
+ ),
+ types.NotebookCell(
+ kind=types.NotebookCellKind.Code,
+ document=NB_CELL_2.uri,
+ metadata={"slideshow": {"slide_type": "note"}},
+ execution_summary=types.ExecutionSummary(
+ execution_order=3, success=False
+ ),
+ ),
+ ]
+ )
+ ),
+ )
+ workspace.update_notebook_document(params)
+
+ notebook = workspace.get_notebook_document(notebook_uri=NOTEBOOK.uri)
+ assert notebook.version == 31
+
+ cell_1 = notebook.cells[0]
+ assert cell_1.metadata == {"slideshow": {"slide_type": "skip"}}
+ assert cell_1.execution_summary == types.ExecutionSummary(
+ execution_order=2, success=True
+ )
+
+ cell_2 = notebook.cells[1]
+ assert cell_2.metadata == {"slideshow": {"slide_type": "note"}}
+ assert cell_2.execution_summary == types.ExecutionSummary(
+ execution_order=3, success=False
+ )
+
+
+def test_update_notebook_cell_content(workspace):
+ """Ensure we can update a notebook correctly when the cell contents change."""
+ params = types.DidOpenNotebookDocumentParams(
+ notebook_document=NOTEBOOK,
+ cell_text_documents=[
+ NB_CELL_1,
+ NB_CELL_2,
+ ],
+ )
+ workspace.put_notebook_document(params)
+
+ notebook = workspace.get_notebook_document(notebook_uri=NOTEBOOK.uri)
+ assert notebook.version == 0
+
+ cell_1 = workspace.get_text_document(NB_CELL_1.uri)
+ assert cell_1.version == 0
+ assert cell_1.source == "# cell 1"
+
+ cell_2 = workspace.get_text_document(NB_CELL_2.uri)
+ assert cell_2.version == 0
+ assert cell_2.source == "# cell 2"
+
+ params = types.DidChangeNotebookDocumentParams(
+ notebook_document=types.VersionedNotebookDocumentIdentifier(
+ uri=NOTEBOOK.uri, version=31
+ ),
+ change=types.NotebookDocumentChangeEvent(
+ cells=types.NotebookDocumentChangeEventCellsType(
+ text_content=[
+ types.NotebookDocumentChangeEventCellsTypeTextContentType(
+ document=types.VersionedTextDocumentIdentifier(
+ uri=NB_CELL_1.uri, version=13
+ ),
+ changes=[
+ types.TextDocumentContentChangeEvent_Type1(
+ text="new text",
+ range=types.Range(
+ start=types.Position(line=0, character=0),
+ end=types.Position(line=0, character=8),
+ ),
+ )
+ ],
+ ),
+ types.NotebookDocumentChangeEventCellsTypeTextContentType(
+ document=types.VersionedTextDocumentIdentifier(
+ uri=NB_CELL_2.uri, version=21
+ ),
+ changes=[
+ types.TextDocumentContentChangeEvent_Type1(
+ text="",
+ range=types.Range(
+ start=types.Position(line=0, character=0),
+ end=types.Position(line=0, character=8),
+ ),
+ ),
+ types.TextDocumentContentChangeEvent_Type1(
+ text="other text",
+ range=types.Range(
+ start=types.Position(line=0, character=0),
+ end=types.Position(line=0, character=0),
+ ),
+ ),
+ ],
+ ),
+ ]
+ )
+ ),
+ )
+ workspace.update_notebook_document(params)
+
+ notebook = workspace.get_notebook_document(notebook_uri=NOTEBOOK.uri)
+ assert notebook.version == 31
+
+ cell_1 = workspace.get_text_document(NB_CELL_1.uri)
+ assert cell_1.version == 13
+ assert cell_1.source == "new text"
+
+ cell_2 = workspace.get_text_document(NB_CELL_2.uri)
+ assert cell_2.version == 21
+ assert cell_2.source == "other text"
+
+
+def test_update_notebook_new_cells(workspace):
+ """Ensure that we can correctly add new cells to an existing notebook."""
+
+ params = types.DidOpenNotebookDocumentParams(
+ notebook_document=NOTEBOOK,
+ cell_text_documents=[
+ NB_CELL_1,
+ NB_CELL_2,
+ ],
+ )
+ workspace.put_notebook_document(params)
+
+ notebook = workspace.get_notebook_document(notebook_uri=NOTEBOOK.uri)
+ assert notebook.version == 0
+
+ cell_uris = [c.document for c in notebook.cells]
+ assert cell_uris == [NB_CELL_1.uri, NB_CELL_2.uri]
+
+ cell_1 = workspace.get_text_document(NB_CELL_1.uri)
+ assert cell_1.version == 0
+ assert cell_1.source == "# cell 1"
+
+ cell_2 = workspace.get_text_document(NB_CELL_2.uri)
+ assert cell_2.version == 0
+ assert cell_2.source == "# cell 2"
+
+ params = types.DidChangeNotebookDocumentParams(
+ notebook_document=types.VersionedNotebookDocumentIdentifier(
+ uri=NOTEBOOK.uri, version=31
+ ),
+ change=types.NotebookDocumentChangeEvent(
+ cells=types.NotebookDocumentChangeEventCellsType(
+ structure=types.NotebookDocumentChangeEventCellsTypeStructureType(
+ array=types.NotebookCellArrayChange(
+ start=1,
+ delete_count=0,
+ cells=[
+ types.NotebookCell(
+ kind=types.NotebookCellKind.Code, document=NB_CELL_3.uri
+ )
+ ],
+ ),
+ did_open=[NB_CELL_3],
+ )
+ )
+ ),
+ )
+ workspace.update_notebook_document(params)
+
+ notebook = workspace.get_notebook_document(cell_uri=NB_CELL_3.uri)
+ assert notebook.uri == NOTEBOOK.uri
+ assert NB_CELL_3.uri in workspace._text_documents
+
+ cell_uris = [c.document for c in notebook.cells]
+ assert cell_uris == [NB_CELL_1.uri, NB_CELL_3.uri, NB_CELL_2.uri]
+
+
+def test_workspace_folders():
+ wf1 = types.WorkspaceFolder(uri="/ws/f1", name="ws1")
+ wf2 = types.WorkspaceFolder(uri="/ws/f2", name="ws2")
+
+ workspace = Workspace("/ws", workspace_folders=[wf1, wf2])
+
+ assert workspace.folders["/ws/f1"] is wf1
+ assert workspace.folders["/ws/f2"] is wf2
+
+
+def test_null_workspace():
+ workspace = Workspace(None)
+
+ assert workspace.root_uri is None
+ assert workspace.root_path is None