90 Commits

Author SHA1 Message Date
46bc1aaa84 fix: add python 3.14 support, adopt pygls >=2.0.0 api 2026-03-15 08:20:41 +01:00
nikromen
3e5e86736c support python 3.13.x (#97)
Fixes `error="Failed to find a python3 installation in PATH that meets the required versions
(<3.13,>=3.8.0). Found version: 3.13.1."` traceback.
2025-01-01 14:39:31 +09:00
offa
806d44f424 Remove EOL Python 3.8 (#95)
Co-authored-by: Regen <regen100@users.noreply.github.com>
2024-10-13 23:26:48 +09:00
offa
3760f15580 Update GH Actions (#96) 2024-10-13 23:09:18 +09:00
offa
1c40387cac Add Python 3.13 support (#94) 2024-10-13 23:07:01 +09:00
atanda rasheed
890bb77f8a feat: handle workspace config event (#91)
* feat: handle workspace config event

* Fix linter errors

---------

Co-authored-by: Regen <regen100@users.noreply.github.com>
2024-04-20 21:43:26 +09:00
offa
9529df5d68 Add Python 3.12 build (#85)
* Add Python 3.12 build

* Update typing extensions

* Remove Python 3.7
2023-12-31 13:02:53 +09:00
Raoul Wols
8cc05adcf4 Reply with MarkupContent in completion documentation (#87) 2023-10-26 14:06:42 +09:00
Regen
68bbc8187b Update build backend (#84) 2023-10-13 19:48:45 +09:00
Regen
0916b888d8 Update pygls (#83) 2023-10-13 19:07:22 +09:00
Regen
cd7ba40b55 Revert "Fix release workflow (#72)" (#73) 2023-03-09 20:17:09 +09:00
Regen
7b68a4e0fe Fix release workflow (#72) 2023-03-09 19:27:39 +09:00
Regen
60c376a5fd Support Python 3.11 (#69) 2023-01-08 21:21:44 +09:00
Regen
a28d3803ad Update pygls (#68) 2023-01-08 21:06:39 +09:00
Regen
691beef1dc Update CI and CD (#67) 2023-01-08 17:26:32 +09:00
Regen
2a5983f9aa Use PDM (#66) 2023-01-08 16:39:41 +09:00
Regen
bff7990e7d Run CI on mater branch (#57) 2022-08-04 22:48:51 +09:00
Charlie Vieth
981150e308 poetry: update pygls to version 0.12 (#55)
Update pygls to version 0.12 to remove a conflict with
jedi-language-server which requires pygls "^0.12" and set the minimum
python version to py37 (as required by pygls 0.12).

This commit also bumps the cmake-language-server version to 0.1.36
2022-08-04 22:28:14 +09:00
Regen
6de2cc3867 Release v0.1.5 (#53) 2022-06-11 12:09:33 +09:00
Regen
1fefcb4cba Use cmakelang formatter (#52) 2022-05-01 23:43:22 +09:00
Regen
f0cfa8b13f Release v0.1.4 (#51) 2022-04-30 23:54:34 +09:00
Regen
722dae419e Remove cmake-format (#50) 2022-04-30 02:54:19 +09:00
Regen
6a3140f105 Fix CI (#49) 2022-04-30 02:28:35 +09:00
dev-bz
4dec2f5afa Update server.py (#48)
Fixed a spelling error
2022-04-18 12:20:58 +09:00
offa
9f7754f0e6 Add Python 3.10 CI build (#45) 2021-10-07 12:28:22 +09:00
Charlie Vieth
9630b96935 poetry: update pygls to version 0.11.2 (#42)
Update pygls to version 0.11.2 and change the version requirement to
'^0.11' this removes a conflict with jedi-language-server which requires
pygls>=0.11.1,<0.12.0.

This commit also bumps the cmake-language-server version to 0.1.3.
2021-10-03 15:13:39 +09:00
Regen
76e34ae628 Fix __version__ (#43) 2021-10-03 12:51:16 +09:00
Regen
cbb6bdd1ae Fix parse_modules (#44) 2021-10-03 12:46:02 +09:00
Regen
a5af5b505f Update pygls (#38) 2021-03-28 23:49:27 +09:00
Regen
4d120a6a98 Replace linter (#37) 2021-03-28 22:23:57 +09:00
KOLANICH
6e839f7675 Added .editorconfig. (#35)
Applies some elements of PEP8 during editing in the editors supporting it.
2021-01-23 16:07:22 +09:00
KOLANICH
cade1e2c45 Fixed build system requirements in pyproject.toml. (#34)
Poetry depends on lot of dependencies having nothing common with building packages. That's why poetry-core was created, which has much less dependencies and more focused on building wheels.
2021-01-23 16:04:06 +09:00
Regen
d16d3b24ef Merge pull request #31 from jargonzombies/doc-and-spelling
Some documentation and misspelling fixes
2020-11-08 16:48:02 +09:00
Jan-Grimo Sobez
4be7657edb Some documentation and misspelling fixes 2020-11-07 22:09:42 +01:00
Regen
040f0b9f0c Merge pull request #28 from regen100/add-doc
Add doc
2020-08-22 16:58:58 +09:00
Regen
ef2c31c6a3 Add doc 2020-08-22 15:15:45 +09:00
Regen
87879ee5df Merge pull request #27 from regen100/replace-linter
Replace yapf with black
2020-08-22 14:54:15 +09:00
Regen
01b1fac73e Replace yapf with black 2020-08-22 14:38:29 +09:00
Regen
5550cb259c Merge pull request #24 from r-burns/darwin
Fix test_read_cmake_files on macOS
2020-07-29 12:42:32 +09:00
Regen
466c5b7bcc Run CI on PR 2020-07-29 12:31:44 +09:00
Ryan Burns
0ec120f391 Fix test_read_cmake_files on macOS 2020-07-27 00:55:16 -07:00
Regen
5d916b6989 Merge pull request #19 from regen100/release-v0.1.2
Release v0.1.2
2020-06-07 22:01:20 +09:00
Regen
1c606ee8a8 Release v0.1.2 2020-06-07 21:50:21 +09:00
Regen
cdb62adce3 Merge pull request #18 from regen100/fix-ci
Fix CI
2020-06-07 20:17:27 +09:00
Regen
6ac3b1d17f Fix CI 2020-06-07 19:52:24 +09:00
Regen
ce2c3a21db Merge pull request #17 from regen100/update-doc
Update README
2020-06-06 15:23:44 +09:00
Regen
3697fae2d3 Update README 2020-06-06 14:52:03 +09:00
Regen
48d5980a36 Merge pull request #13 from regen100/fix-vim-lsp
Fix vim lsp
2020-04-29 23:45:46 +09:00
Regen
e07b3242c8 Add test 2020-04-29 22:57:43 +09:00
Regen
2d36887b26 Support no TriggerCharacter 2020-04-29 19:25:51 +09:00
Regen
67aced6544 Add encoding 2020-04-29 18:04:23 +09:00
Regen
3c171b9e25 Fix CompletionItem for vim-lsp 2020-04-29 17:56:09 +09:00
Regen
c8c284e061 Merge pull request #12 from regen100/fix-completion
Fix error without completion context
2020-04-29 16:09:21 +09:00
Regen
6bf08e0f14 Fix error without completion context 2020-04-29 15:57:25 +09:00
Regen
40d93525d9 Merge pull request #8 from regen100/add-version
Add --version to cmake-language-server
2020-03-07 00:26:36 +09:00
Regen
f8136d6dbc Disable patch status 2020-03-07 00:21:50 +09:00
Regen
5af6555d3c Add --version to cmake-language-server 2020-03-07 00:09:43 +09:00
Regen
310c449250 Merge branch 'release-v0.1.1' 2020-03-01 16:50:13 +09:00
Regen
bd4f357e59 Release v0.1.1 2020-03-01 16:42:53 +09:00
Regen
02ae7c4a7c Merge pull request #5 from regen100/format-stdin
Add stdin mode to cmake-format
2020-03-01 16:38:50 +09:00
Regen
6e03b145ba Add stdin mode to cmake-format 2020-03-01 16:26:59 +09:00
Regen
b3123db464 Merge branch 'add-codecovyml' 2020-03-01 16:26:07 +09:00
Regen
7d85c2c8dd Add codecov.yml 2020-03-01 16:16:21 +09:00
Regen
b08dc91e53 Merge branch 'add-test-format' 2020-02-29 19:53:28 +09:00
Regen
894b38d55d Add tests 2020-02-29 19:46:06 +09:00
Regen
cc9877adbe Refactor formatter 2020-02-29 19:45:36 +09:00
Regen
66af586b2a Merge branch 'add-server-test' 2020-01-04 02:17:26 +09:00
Regen
c870e3d512 Add server tests 2020-01-04 02:10:30 +09:00
Regen
9e6fc1a277 Merge branch 'add-include-completion' 2020-01-02 23:56:25 +09:00
Regen
79864d24ee Add module parser 2020-01-02 23:51:56 +09:00
Regen
6f93218462 Merge pull request #2 from otreblan/master
Aur badge
2019-12-24 22:31:59 +09:00
otreblan
600659fe81 Aur badge added 2019-12-22 14:02:35 -05:00
Regen
af59e9b3f6 Merge branch 'cov' 2019-12-23 02:52:44 +09:00
Regen
39aa03cd55 Clean cmake on CI 2019-12-23 02:48:01 +09:00
Regen
8d5c6b588c Add codecov badge 2019-12-23 02:43:14 +09:00
Regen
b2d8e66ef1 Integrate codecov 2019-12-23 02:42:07 +09:00
Regen
4624bdf4e8 WIP 2019-12-23 02:25:21 +09:00
Regen
6628dfe5d0 Merge branch 'fix-parse-commands' 2019-12-23 02:24:43 +09:00
Regen
e73b0bab0f Fix windows build 2019-12-23 02:20:41 +09:00
Regen
06f7a4669d Fix _parse_commands() 2019-12-23 01:04:16 +09:00
Regen
1a8267bb74 Add version option to cmake-format 2019-11-24 18:14:46 +09:00
Regen
9670ecfb59 Update README 2019-11-24 13:10:55 +09:00
Regen
3b8c225d06 Merge branch 'windows-ci' 2019-11-24 03:39:31 +09:00
Regen
ff727b7793 Add windows build 2019-11-24 03:01:06 +09:00
Regen
1ac3bf421d Remove cache 2019-11-23 17:40:05 +09:00
Regen
7919cf5025 Merge branch 'update-doc' 2019-11-23 15:08:59 +09:00
Regen
b4dd6c840d Add installation 2019-11-22 00:34:38 +09:00
Regen
0021c07b56 Add badges 2019-11-22 00:34:07 +09:00
Regen
5e2736a710 Merge branch 'fix-cache' 2019-11-22 00:10:47 +09:00
Regen
f89f0d6b56 Fix cache in CI 2019-11-17 00:48:32 +09:00
27 changed files with 1629 additions and 1351 deletions

12
.editorconfig Normal file
View File

@@ -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

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
poetry.lock linguist-generated=true

View File

@@ -1,26 +0,0 @@
name: Publish
on:
push:
tags:
- v*
jobs:
build-n-publish:
name: Build and publish
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@master
- name: Set up Python 3.7
uses: actions/setup-python@v1
with:
python-version: 3.7
- name: Install dependencies
run: |
python -m pip install poetry
- name: Build a binary wheel and a source tarball
run: |
poetry build
- name: Publish distribution to PyPI
run: |
poetry publish -u __token__ -p ${{ secrets.pypi_password }}

View File

@@ -1,35 +0,0 @@
name: Tests
on: [push]
jobs:
build:
env:
CMAKE_VERSION: 3.14.7
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-18.04]
python: [3.6, 3.7, 3.8]
steps:
- uses: actions/checkout@v1
- name: Cache .tox
uses: actions/cache@v1
with:
path: .tox
key: ${{ runner.OS }}-tox-${{ hashFiles('poetry.lock') }}
restore-keys: |
${{ runner.OS }}-tox-
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python }}
- name: Install dependencies
run: |
curl -sSL https://github.com/Kitware/CMake/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION-Linux-x86_64.tar.gz | tar xz
python -m pip install --upgrade setuptools pip wheel
python -m pip install poetry tox-gh-actions
- name: Test with tox
run: |
export PATH=$GITHUB_WORKSPACE/cmake-$CMAKE_VERSION-Linux-x86_64/bin:$PATH
tox

133
.gitignore vendored
View File

@@ -1,133 +1,18 @@
### https://raw.github.com/github/gitignore/cb0c6ef7ac68f2300409ee85501d9ad432cb4c7e/Python.gitignore
cmake_language_server/version.py
# Byte-compiled / optimized / DLL files
.venv/
pyrightconfig.json
/build/
/dist/
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-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/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# pyflow
.pdm-python
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# 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
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
.pdm-build/

View File

@@ -1,2 +0,0 @@
[style]
based_on_style = pep8

View File

@@ -1,5 +1,9 @@
# cmake-language-server
[![PyPI](https://img.shields.io/pypi/v/cmake-language-server)](https://pypi.org/project/cmake-language-server)
[![AUR version](https://img.shields.io/aur/version/cmake-language-server)](https://aur.archlinux.org/packages/cmake-language-server/)
[![GitHub Actions (Tests)](https://github.com/regen100/cmake-language-server/workflows/Tests/badge.svg)](https://github.com/regen100/cmake-language-server/actions)
[![codecov](https://codecov.io/gh/regen100/cmake-language-server/branch/master/graph/badge.svg)](https://codecov.io/gh/regen100/cmake-language-server)
[![GitHub](https://img.shields.io/github/license/regen100/cmake-language-server)](https://github.com/regen100/cmake-language-server/blob/master/LICENSE)
CMake LSP Implementation.
@@ -8,21 +12,26 @@ Alpha Stage, work in progress.
## Features
- [x] Builtin command completion
- [x] Documentation for commands and variables on hover
- [x] Formatting
- [x] Formatting (by [`cmake-format`](https://github.com/cheshirekow/cmake_format))
## Commands
- cmake-language-server: LSP server
- cmake-format: CLI frontend for formatting
- `cmake-language-server`: LSP server
## Installation
### Clients
```bash
$ pip install cmake-language-server
```
- Neovim ([neoclide/coc.nvim][coc.nvim])
### Tested Clients
- Neovim ([neoclide/coc.nvim][coc.nvim], [prabirshrestha/vim-lsp][vim-lsp])
#### Neovim
##### coc.nvim
```jsonc
"languageserver": {
"cmake": {
@@ -38,5 +47,29 @@ Alpha Stage, work in progress.
}
```
##### vim-lsp
```vim
if executable('cmake-language-server')
au User lsp_setup call lsp#register_server({
\ 'name': 'cmake',
\ 'cmd': {server_info->['cmake-language-server']},
\ 'root_uri': {server_info->lsp#utils#path_to_uri(lsp#utils#find_nearest_parent_file_directory(lsp#utils#get_buffer_path(), 'build/'))},
\ 'whitelist': ['cmake'],
\ 'initialization_options': {
\ 'buildDirectory': 'build',
\ }
\})
endif
```
### Configuration
* `buildDirectory`
This language server uses CMake's file API to get cached variables.
The API communicates using `<buildDirectory>/.cmake/api/`.
`buildDirectory` is relative path to the root uri of the workspace.
To configure the build tree, you need to run the cmake command such as `cmake .. -DFOO=bar`.
[coc.nvim]: https://github.com/neoclide/coc.nvim
[vim-lsp]: https://github.com/prabirshrestha/vim-lsp

View File

@@ -0,0 +1,3 @@
from .version import __version__
__all__ = ["__version__"]

View File

@@ -0,0 +1,365 @@
import json
import logging
import re
import subprocess
import tempfile
import uuid
from pathlib import Path
from typing import Dict, List, Optional, Pattern
logger = logging.getLogger(__name__)
def _tidy_doc(doc: str) -> str:
doc = doc.strip()
doc = re.sub(r":.+?:`(.+?)`", r"\1", doc)
doc = re.sub(r"``([^`]+)``", r"`\1`", doc)
doc = doc.replace("\n", " ")
doc = doc.replace(". ", ". ")
return doc
class API(object):
_cmake: str
_build: Path
_uuid: uuid.UUID
_builtin_commands: Dict[str, str]
_builtin_variables: Dict[str, str]
_builtin_variable_template: Dict[Pattern[str], str]
_builtin_modules: Dict[str, str]
_targets: List[str]
_cached_variables: Dict[str, str]
_generated_list_parsed: bool
def __init__(self, cmake: str, build: Path):
self._cmake = cmake
self._build = Path(build)
self._uuid = uuid.uuid4()
self._builtin_commands = {}
self._builtin_variables = {}
self._builtin_variable_template = {}
self._builtin_modules = {}
self._targets = []
self._cached_variables = {}
self._generated_list_parsed = False
def query(self) -> bool:
"""Use CMake's file API to get JSON information about the build tree
Generates a JSON request file for the current build tree and runs
CMake on the build tree. Deletes the request file immediately
after.
"""
if not self.cmake_cache.exists():
return False
self.query_json.parent.mkdir(parents=True, exist_ok=True)
with self.query_json.open("w") as fp:
fp.write(
"""\
{
"requests": [
{"kind": "codemodel", "version": 2},
{"kind": "cache", "version": 2},
{"kind": "cmakeFiles", "version": 1}
]
}"""
)
proc = subprocess.run(
[self._cmake, str(self._build)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
universal_newlines=True,
)
self.query_json.unlink()
self.query_json.parent.rmdir()
if proc.returncode != 0:
logging.error(f"cmake exited with {proc.returncode}: {proc.stderr}")
return False
return True
def read_reply(self) -> bool:
"""Reads the CMake file API reply file and updates internal state
Reads the result of the previous query file and updates
the targets, the cache entries and the cmake files.
"""
reply = self._build / ".cmake" / "api" / "v1" / "reply"
indices = sorted(reply.glob("index-*.json"))
if not indices:
logger.error("no reply")
return False
with indices[-1].open() as fp:
index = json.load(fp)
try:
responses = index["reply"][f"client-{self._uuid}"]["query.json"][
"responses"
]
except KeyError:
logger.error("no rensponse")
return False
for response in responses:
if response["kind"] == "codemodel":
self._read_codemodel(reply / response["jsonFile"])
elif response["kind"] == "cache":
self._read_cache(reply / response["jsonFile"])
elif response["kind"] == "cmakeFiles":
self._read_cmake_files(reply / response["jsonFile"])
return True
def _read_codemodel(self, codemodelpath: Path) -> None:
with (codemodelpath).open() as fp:
codemodel = json.load(fp)
config = codemodel["configurations"][0]
self._targets[:] = [x["name"] for x in config["targets"]]
def _read_cache(self, cachepath: Path) -> None:
with cachepath.open() as fp:
cache = json.load(fp)
self._cached_variables.clear()
for entry in cache["entries"]:
name = entry["name"]
value = self._truncate_variable(entry["value"])
properties = {x["name"]: x["value"] for x in entry["properties"]}
helpstring = properties.get("HELPSTRING", "")
doc = []
if helpstring:
doc.append(helpstring)
if value:
doc.append(f"`{value}`")
self._cached_variables[name] = "\n\n".join(doc)
def _read_cmake_files(self, jsonpath: Path) -> None:
"""inspect CMake list files that are used during build generation"""
if not self._builtin_variables or self._generated_list_parsed:
return
with jsonpath.open() as fp:
cmake_files = json.load(fp)
# Inspect generated list files: Get the values of variables in each script
with tempfile.TemporaryDirectory() as tmpdirname:
tmplist = Path(tmpdirname) / "dump.cmake"
with tmplist.open("w") as fp:
for listfile in cmake_files["inputs"]:
if not listfile.get("isGenerated", False):
continue
path = listfile["path"]
fp.write(f"include({path})\n")
fp.write(
"""
get_cmake_property(variables VARIABLES)
foreach (variable ${variables})
message("${variable}=${${variable}}")
endforeach()
"""
)
p = subprocess.run(
[self._cmake, "-P", str(tmplist)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=cmake_files["paths"]["source"],
encoding="utf-8",
universal_newlines=True,
)
if p.returncode != 0:
return
for line in p.stderr.split("\n"):
line = line.strip()
if not line:
continue
k, v = line.split("=", 1)
if k.startswith("CMAKE_ARG"):
continue
v = self._truncate_variable(v)
if k in self._builtin_variables:
self._builtin_variables[k] += f"\n\n`{v}`"
else:
for pattern, doc in self._builtin_variable_template.items():
if pattern.fullmatch(k):
self._builtin_variables[k] = f"{doc}\n\n`{v}`"
break
else:
# ignore variable with no document
pass
self._generated_list_parsed = True
@property
def query_json(self) -> Path:
return (
self._build
/ ".cmake"
/ "api"
/ "v1"
/ "query"
/ f"client-{self._uuid}"
/ "query.json"
)
@property
def cmake_cache(self) -> Path:
return self._build / "CMakeCache.txt"
def parse_doc(self) -> None:
self._parse_commands()
self._parse_variables()
self._parse_modules()
def _parse_commands(self) -> None:
"""Load docs for builtin cmake functions
Loads the documentation for builtin cmake functions from the result
of `$ cmake --help-commands`.
"""
p = subprocess.run(
[self._cmake, "--help-commands"],
stdout=subprocess.PIPE,
encoding="utf-8",
universal_newlines=True,
)
if p.returncode != 0:
return
matches = re.finditer(
r"""
(?P<command>.+)\n
-+\n+?
[\s\S]*?
(?P<signature>(?P=command)\s*\([^)]*\))
""",
p.stdout,
re.VERBOSE,
)
self._builtin_commands.clear()
for match in matches:
command = match.group("command")
signature = match.group("signature")
signature = re.sub(r"^ ", r"", signature, flags=re.MULTILINE)
self._builtin_commands[command] = "```cmake\n" + signature + "\n```"
def _parse_variables(self) -> None:
"""Load docs for builtin cmake variables
Loads the documentation for builtin cmake variables from
the result of `$ cmake --help-variables`.
"""
p = subprocess.run(
[self._cmake, "--help-variables"],
stdout=subprocess.PIPE,
encoding="utf-8",
universal_newlines=True,
)
if p.returncode != 0:
return
matches = re.finditer(
r"""
(?P<variable>.+)\n
-+\n\n
(?P<doc>[\s\S]+?)(?:\n\n|$)
""",
p.stdout,
re.VERBOSE,
)
self._builtin_variables.clear()
for match in matches:
variable = match.group("variable")
doc = _tidy_doc(match.group("doc"))
if variable == "CMAKE_MATCH_<n>":
for i in range(10):
self._builtin_variables[f"CMAKE_MATCH_{i}"] = doc
elif "<" in variable:
variable = re.sub(r"<[^>]+>", r"[^_]+", variable)
pattern = re.compile(variable)
self._builtin_variable_template[pattern] = doc
else:
self._builtin_variables[variable] = doc
def _parse_modules(self) -> None:
"""Loads docs for all modules in the cmake distribution
Loads the documentation for cmake modules included in the
distribution from the result of `$ cmake --help-modules`.
"""
p = subprocess.run(
[self._cmake, "--help-modules"],
stdout=subprocess.PIPE,
encoding="utf-8",
universal_newlines=True,
)
if p.returncode != 0:
return
matches = re.finditer(
r"""
(?P<module>.+)\n
-+\n+?
(?:(?P<header>\w[\w\s]+)\n\^+\n+?)?
(?P<doc>(?:.|\n)*?\n\n)
""",
p.stdout + "\n\n",
re.VERBOSE,
)
self._builtin_modules.clear()
for match in matches:
module = match.group("module")
header = match.group("header")
doc = _tidy_doc(match.group("doc"))
if header != "Overview":
doc = ""
self._builtin_modules[module] = doc
def get_command_doc(self, command: str) -> Optional[str]:
return self._builtin_commands.get(command)
def search_command(self, command: str) -> List[str]:
command = command.lower()
return [x for x in self._builtin_commands if x.startswith(command)]
def get_variable_doc(self, variable: str) -> Optional[str]:
doc = self._cached_variables.get(variable)
if doc:
return doc
return self._builtin_variables.get(variable)
def search_variable(self, variable: str) -> List[str]:
cached = frozenset(x for x in self._cached_variables if x.startswith(variable))
builtin = frozenset(
x for x in self._builtin_variables if x.startswith(variable)
)
return list(cached | builtin)
def get_module_doc(self, module: str, package: bool) -> Optional[str]:
if package:
return self._builtin_modules.get("Find" + module)
return self._builtin_modules.get(module)
def search_module(self, module: str, package: bool) -> List[str]:
if package:
module = "Find" + module
return [x[4:] for x in self._builtin_modules if x.startswith(module)]
return [
x
for x in self._builtin_modules
if x.startswith(module) and not x.startswith("Find")
]
def search_target(self, target: str) -> List[str]:
return [x for x in self._targets if x.startswith(target)]
def _truncate_variable(self, v: str) -> str:
width = 70
return v[:width] + (v[width:] and "...")

View File

@@ -0,0 +1,287 @@
import logging
import re
import shutil
import subprocess
from pathlib import Path
from typing import Any, Callable, List, Optional, Tuple
from lsprotocol.types import (
INITIALIZE,
INITIALIZED,
TEXT_DOCUMENT_COMPLETION,
TEXT_DOCUMENT_DID_SAVE,
TEXT_DOCUMENT_FORMATTING,
TEXT_DOCUMENT_HOVER,
WORKSPACE_DID_CHANGE_CONFIGURATION,
CompletionItem,
CompletionItemKind,
CompletionList,
CompletionOptions,
CompletionParams,
CompletionTriggerKind,
DidChangeConfigurationParams,
DocumentFormattingParams,
Hover,
InitializeParams,
MarkupContent,
MarkupKind,
Position,
Range,
SaveOptions,
TextDocumentPositionParams,
TextEdit,
)
from pygls.lsp.server import LanguageServer
from .api import API
logger = logging.getLogger(__name__)
class CMakeLanguageServer(LanguageServer):
_api: Optional[API]
def __init__(self, *args: Any) -> None:
super().__init__(*args)
self._api = None
@self.feature(INITIALIZE)
def initialize(params: InitializeParams) -> None:
opts = params.initialization_options or {}
cmake = opts.get("cmakeExecutable", "cmake")
builddir = opts.get("buildDirectory", "")
logging.info(f"cmakeExecutable={cmake}, buildDirectory={builddir}")
self._api = API(cmake, Path(builddir))
self._api.parse_doc()
@self.feature(WORKSPACE_DID_CHANGE_CONFIGURATION)
def workspace_did_change_configuration(
params: DidChangeConfigurationParams,
) -> None:
settings = params.settings or {}
assert self._api is not None
if opts := settings.get("initialization_options"):
cmake = opts.get("cmakeExecutable", self._api._cmake)
builddir = opts.get("buildDirectory", self._api._build.as_posix())
logging.info(f"cmakeExecutable={cmake}, buildDirectory={builddir}")
api = API(cmake, Path(builddir))
api.parse_doc()
self._api = api
run_cmake()
trigger_characters = ["{", "("]
@self.feature(
TEXT_DOCUMENT_COMPLETION,
CompletionOptions(trigger_characters=trigger_characters),
)
def completions(params: CompletionParams) -> CompletionList:
assert self._api is not None
if (
params.context is not None
and params.context.trigger_kind
== CompletionTriggerKind.TriggerCharacter
):
token = ""
trigger = params.context.trigger_character
else:
line = self._cursor_line(params.text_document.uri, params.position)
idx = params.position.character - 1
if 0 <= idx < len(line) and line[idx] in trigger_characters:
token = ""
trigger = line[idx]
else:
word = self._cursor_word(
params.text_document.uri, params.position, False
)
token = "" if word is None else word[0]
trigger = None
items: List[CompletionItem] = []
if trigger is None:
commands = self._api.search_command(token)
items.extend(
CompletionItem(
label=x,
kind=CompletionItemKind.Function,
documentation=self._get_command_doc(x),
insert_text=x,
)
for x in commands
)
if trigger is None or trigger == "{":
variables = self._api.search_variable(token)
items.extend(
CompletionItem(
label=x,
kind=CompletionItemKind.Variable,
documentation=self._get_variable_doc(x),
insert_text=x,
)
for x in variables
)
if trigger is None:
targets = self._api.search_target(token)
items.extend(
CompletionItem(
label=x, kind=CompletionItemKind.Class, insert_text=x
)
for x in targets
)
if trigger == "(":
func = self._cursor_function(params.text_document.uri, params.position)
if func is not None:
func = func.lower()
if func == "include":
modules = self._api.search_module(token, False)
items.extend(
CompletionItem(
label=x,
kind=CompletionItemKind.Module,
documentation=self._get_module_doc(x, False),
insert_text=x,
)
for x in modules
)
elif func == "find_package":
modules = self._api.search_module(token, True)
items.extend(
CompletionItem(
label=x,
kind=CompletionItemKind.Module,
documentation=self._get_module_doc(x, True),
insert_text=x,
)
for x in modules
)
return CompletionList(is_incomplete=False, items=items)
if shutil.which("cmake-format") is not None:
@self.feature(TEXT_DOCUMENT_FORMATTING)
def formatting(
params: DocumentFormattingParams,
) -> Optional[List[TextEdit]]:
doc = self.workspace.get_text_document(params.text_document.uri)
content = doc.source
formatted = subprocess.check_output(
["cmake-format", "-"],
cwd=str(Path(doc.path).parent),
input=content,
universal_newlines=True,
)
lines = content.count("\n")
return [
TextEdit(
range=Range(
start=Position(line=0, character=0),
end=Position(line=lines + 1, character=0),
),
new_text=formatted,
)
]
@self.feature(TEXT_DOCUMENT_HOVER)
def hover(params: TextDocumentPositionParams) -> Optional[Hover]:
word = self._cursor_word(params.text_document.uri, params.position, True)
if not word:
return None
candidates: List[Callable[[str], Optional[MarkupContent]]] = [
lambda x: self._get_command_doc(x.lower()),
lambda x: self._get_variable_doc(x),
lambda x: self._get_module_doc(x, False),
lambda x: self._get_module_doc(x, True),
]
for c in candidates:
doc = c(word[0])
if doc is None:
continue
return Hover(contents=doc, range=word[1])
return None
@self.thread()
@self.feature(
TEXT_DOCUMENT_DID_SAVE,
SaveOptions(include_text=False),
)
@self.feature(INITIALIZED)
def run_cmake(*args: Any) -> None:
assert self._api is not None
if self._api.query():
self._api.read_reply()
def _cursor_function(self, uri: str, position: Position) -> Optional[str]:
doc = self.workspace.get_text_document(uri)
lines = doc.source.split("\n")[: position.line + 1]
lines[-1] = lines[-1][: position.character - 1].strip()
words = re.split(r"[\s\n()]+", "\n".join(lines))
return words[-1] if words else None
def _cursor_line(self, uri: str, position: Position) -> str:
doc = self.workspace.get_text_document(uri)
content = doc.source
line = content.split("\n")[position.line]
return str(line)
def _cursor_word(
self, uri: str, position: Position, include_all: bool = True
) -> Optional[Tuple[str, Range]]:
line = self._cursor_line(uri, position)
cursor = position.character
for m in re.finditer(r"\w+", line):
end = m.end() if include_all else cursor
if m.start() <= cursor <= m.end():
word = (
line[m.start() : end],
Range(
start=Position(line=position.line, character=m.start()),
end=Position(line=position.line, character=end),
),
)
return word
return None
def _get_command_doc(self, command: str) -> Optional[MarkupContent]:
assert self._api is not None
docs = self._api.get_command_doc(command)
return None if docs is None else MarkupContent(MarkupKind.Markdown, docs)
def _get_variable_doc(self, variable: str) -> Optional[MarkupContent]:
assert self._api is not None
docs = self._api.get_variable_doc(variable)
return None if docs is None else MarkupContent(MarkupKind.Markdown, docs)
def _get_module_doc(self, module: str, package: bool) -> Optional[MarkupContent]:
assert self._api is not None
docs = self._api.get_module_doc(module, package)
return None if docs is None else MarkupContent(MarkupKind.Markdown, docs)
def main() -> None:
from argparse import ArgumentParser
from . import __version__
parser = ArgumentParser(description="CMake Language Server")
parser.add_argument(
"--version", action="version", version=f"%(prog)s {__version__}"
)
parser.parse_args()
logging.basicConfig(level=logging.INFO)
logging.getLogger("pygls").setLevel(logging.WARNING)
CMakeLanguageServer("cmake-language-server", __version__).start_io()

7
codecov.yml Normal file
View File

@@ -0,0 +1,7 @@
coverage:
status:
project:
default:
threshold: 10%
patch:
default: off

View File

@@ -1,6 +0,0 @@
[mypy]
ignore_missing_imports = True
allow_redefinition = True
[mypy-re.Scanner]
ignore_errors = True

504
pdm.lock generated Normal file
View File

@@ -0,0 +1,504 @@
# This file is @generated by PDM.
# It is not intended for manual editing.
[metadata]
groups = ["default", "dev", "lint"]
strategy = ["cross_platform"]
lock_version = "4.5.0"
content_hash = "sha256:ef24aee91e46f0db41b85228c16cb2703f20aff76eb93225d2a982304959120e"
[[metadata.targets]]
requires_python = ">=3.10.0,<3.16"
[[package]]
name = "attrs"
version = "25.4.0"
requires_python = ">=3.9"
summary = "Classes Without Boilerplate"
files = [
{file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"},
{file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"},
]
[[package]]
name = "black"
version = "24.3.0"
requires_python = ">=3.8"
summary = "The uncompromising code formatter."
dependencies = [
"click>=8.0.0",
"mypy-extensions>=0.4.3",
"packaging>=22.0",
"pathspec>=0.9.0",
"platformdirs>=2",
"tomli>=1.1.0; python_version < \"3.11\"",
"typing-extensions>=4.0.1; python_version < \"3.11\"",
]
files = [
{file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"},
{file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"},
{file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"},
{file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"},
{file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"},
{file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"},
{file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"},
{file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"},
{file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"},
{file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"},
{file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"},
{file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"},
{file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"},
{file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"},
{file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"},
{file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"},
{file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"},
{file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"},
{file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"},
{file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"},
{file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"},
{file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"},
]
[[package]]
name = "cattrs"
version = "23.2.3"
requires_python = ">=3.8"
summary = "Composable complex class support for attrs and dataclasses."
dependencies = [
"attrs>=23.1.0",
"exceptiongroup>=1.1.1; python_version < \"3.11\"",
"typing-extensions!=4.6.3,>=4.1.0; python_version < \"3.11\"",
]
files = [
{file = "cattrs-23.2.3-py3-none-any.whl", hash = "sha256:0341994d94971052e9ee70662542699a3162ea1e0c62f7ce1b4a57f563685108"},
{file = "cattrs-23.2.3.tar.gz", hash = "sha256:a934090d95abaa9e911dac357e3a8699e0b4b14f8529bcc7d2b1ad9d51672b9f"},
]
[[package]]
name = "click"
version = "8.1.7"
requires_python = ">=3.7"
summary = "Composable command line interface toolkit"
dependencies = [
"colorama; platform_system == \"Windows\"",
]
files = [
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
]
[[package]]
name = "cmakelang"
version = "0.6.13"
summary = "Language tools for cmake (format, lint, etc)"
dependencies = [
"six>=1.13.0",
]
files = [
{file = "cmakelang-0.6.13-py3-none-any.whl", hash = "sha256:764b9467195c7c36453d60a829f30229720d26c7dffd41cb516b99bd9c7daf4e"},
{file = "cmakelang-0.6.13.tar.gz", hash = "sha256:03982e87b00654d024d73ef972d9d9bb0e5726cdb6b8a424a15661fb6278e67f"},
]
[[package]]
name = "colorama"
version = "0.4.6"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
summary = "Cross-platform colored terminal text."
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.4"
requires_python = ">=3.8"
summary = "Code coverage measurement for Python"
files = [
{file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"},
{file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"},
{file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"},
{file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"},
{file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"},
{file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"},
{file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"},
{file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"},
{file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"},
{file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"},
{file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"},
{file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"},
{file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"},
{file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"},
{file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"},
{file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"},
{file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"},
{file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"},
{file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"},
{file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"},
{file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"},
{file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"},
{file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"},
{file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"},
{file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"},
{file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"},
{file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"},
{file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"},
{file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"},
{file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"},
{file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"},
{file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"},
{file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"},
{file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"},
{file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"},
{file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"},
{file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"},
{file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"},
{file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"},
{file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"},
{file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"},
{file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"},
{file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"},
{file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"},
{file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"},
{file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"},
{file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"},
{file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"},
{file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"},
{file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"},
{file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"},
{file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"},
]
[[package]]
name = "coverage"
version = "7.4.4"
extras = ["toml"]
requires_python = ">=3.8"
summary = "Code coverage measurement for Python"
dependencies = [
"coverage==7.4.4",
"tomli; python_full_version <= \"3.11.0a6\"",
]
files = [
{file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"},
{file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"},
{file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"},
{file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"},
{file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"},
{file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"},
{file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"},
{file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"},
{file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"},
{file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"},
{file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"},
{file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"},
{file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"},
{file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"},
{file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"},
{file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"},
{file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"},
{file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"},
{file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"},
{file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"},
{file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"},
{file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"},
{file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"},
{file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"},
{file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"},
{file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"},
{file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"},
{file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"},
{file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"},
{file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"},
{file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"},
{file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"},
{file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"},
{file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"},
{file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"},
{file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"},
{file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"},
{file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"},
{file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"},
{file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"},
{file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"},
{file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"},
{file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"},
{file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"},
{file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"},
{file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"},
{file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"},
{file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"},
{file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"},
{file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"},
{file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"},
{file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"},
]
[[package]]
name = "exceptiongroup"
version = "1.2.0"
requires_python = ">=3.7"
summary = "Backport of PEP 654 (exception groups)"
files = [
{file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"},
{file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"},
]
[[package]]
name = "flake8"
version = "5.0.4"
requires_python = ">=3.6.1"
summary = "the modular source code checker: pep8 pyflakes and co"
dependencies = [
"mccabe<0.8.0,>=0.7.0",
"pycodestyle<2.10.0,>=2.9.0",
"pyflakes<2.6.0,>=2.5.0",
]
files = [
{file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"},
{file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"},
]
[[package]]
name = "iniconfig"
version = "2.0.0"
requires_python = ">=3.7"
summary = "brain-dead simple config-ini parsing"
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 = "isort"
version = "5.13.2"
requires_python = ">=3.8.0"
summary = "A Python utility / library to sort Python imports."
files = [
{file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
{file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
]
[[package]]
name = "lsprotocol"
version = "2025.0.0"
requires_python = ">=3.8"
summary = "Python types for Language Server Protocol."
dependencies = [
"attrs>=21.3.0",
"cattrs!=23.2.1",
]
files = [
{file = "lsprotocol-2025.0.0-py3-none-any.whl", hash = "sha256:f9d78f25221f2a60eaa4a96d3b4ffae011b107537facee61d3da3313880995c7"},
{file = "lsprotocol-2025.0.0.tar.gz", hash = "sha256:e879da2b9301e82cfc3e60d805630487ac2f7ab17492f4f5ba5aaba94fe56c29"},
]
[[package]]
name = "mccabe"
version = "0.7.0"
requires_python = ">=3.6"
summary = "McCabe checker, plugin for flake8"
files = [
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
]
[[package]]
name = "mypy"
version = "1.9.0"
requires_python = ">=3.8"
summary = "Optional static typing for Python"
dependencies = [
"mypy-extensions>=1.0.0",
"tomli>=1.1.0; python_version < \"3.11\"",
"typing-extensions>=4.1.0",
]
files = [
{file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"},
{file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"},
{file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"},
{file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"},
{file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"},
{file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"},
{file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"},
{file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"},
{file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"},
{file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"},
{file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"},
{file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"},
{file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"},
{file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"},
{file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"},
{file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"},
{file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"},
{file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"},
{file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"},
{file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"},
{file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"},
{file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"},
{file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"},
{file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"},
{file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"},
{file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"},
{file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"},
]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
requires_python = ">=3.5"
summary = "Type system extensions for programs checked with the mypy type checker."
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 = "packaging"
version = "24.0"
requires_python = ">=3.7"
summary = "Core utilities for Python packages"
files = [
{file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
{file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
]
[[package]]
name = "pathspec"
version = "0.12.1"
requires_python = ">=3.8"
summary = "Utility library for gitignore style pattern matching of file paths."
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.2.0"
requires_python = ">=3.8"
summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
files = [
{file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"},
{file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"},
]
[[package]]
name = "pluggy"
version = "1.4.0"
requires_python = ">=3.8"
summary = "plugin and hook calling mechanisms for python"
files = [
{file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
{file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
]
[[package]]
name = "pycodestyle"
version = "2.9.1"
requires_python = ">=3.6"
summary = "Python style guide checker"
files = [
{file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"},
{file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"},
]
[[package]]
name = "pyflakes"
version = "2.5.0"
requires_python = ">=3.6"
summary = "passive checker of Python programs"
files = [
{file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"},
{file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"},
]
[[package]]
name = "pygls"
version = "2.0.1"
requires_python = ">=3.9"
summary = "A pythonic generic language server (pronounced like 'pie glass')"
dependencies = [
"attrs>=24.3.0",
"cattrs>=23.1.2",
"lsprotocol==2025.0.0",
]
files = [
{file = "pygls-2.0.1-py3-none-any.whl", hash = "sha256:d29748042cea5bedc98285eb3e2c0c60bf3fc73786319519001bf72bbe8f36cc"},
{file = "pygls-2.0.1.tar.gz", hash = "sha256:2f774a669fbe2ece977d302786f01f9b0c5df7d0204ea0fa371ecb08288d6b86"},
]
[[package]]
name = "pytest"
version = "8.1.1"
requires_python = ">=3.8"
summary = "pytest: simple powerful testing with Python"
dependencies = [
"colorama; sys_platform == \"win32\"",
"exceptiongroup>=1.0.0rc8; python_version < \"3.11\"",
"iniconfig",
"packaging",
"pluggy<2.0,>=1.4",
"tomli>=1; python_version < \"3.11\"",
]
files = [
{file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"},
{file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"},
]
[[package]]
name = "pytest-cov"
version = "5.0.0"
requires_python = ">=3.8"
summary = "Pytest plugin for measuring coverage."
dependencies = [
"coverage[toml]>=5.2.1",
"pytest>=4.6",
]
files = [
{file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"},
{file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"},
]
[[package]]
name = "pytest-datadir"
version = "1.5.0"
requires_python = ">=3.8"
summary = "pytest plugin for test data directories and files"
dependencies = [
"pytest>=5.0",
]
files = [
{file = "pytest-datadir-1.5.0.tar.gz", hash = "sha256:1617ed92f9afda0c877e4eac91904b5f779d24ba8f5e438752e3ae39d8d2ee3f"},
{file = "pytest_datadir-1.5.0-py3-none-any.whl", hash = "sha256:34adf361bcc7b37961bbc1dfa8d25a4829e778bab461703c38a5c50ca9c36dc8"},
]
[[package]]
name = "six"
version = "1.16.0"
requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
summary = "Python 2 and 3 compatibility utilities"
files = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
[[package]]
name = "tomli"
version = "2.0.1"
requires_python = ">=3.7"
summary = "A lil' TOML parser"
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 = "typing-extensions"
version = "4.10.0"
requires_python = ">=3.8"
summary = "Backported and Experimental Type Hints for Python 3.8+"
files = [
{file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"},
{file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"},
]

346
poetry.lock generated
View File

@@ -1,346 +0,0 @@
[[package]]
category = "dev"
description = "Atomic file writes."
name = "atomicwrites"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.3.0"
[[package]]
category = "dev"
description = "Classes Without Boilerplate"
name = "attrs"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "19.3.0"
[[package]]
category = "dev"
description = "Cross-platform colored terminal text."
marker = "sys_platform == \"win32\""
name = "colorama"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.4.1"
[[package]]
category = "dev"
description = "Discover and load entry points from installed packages."
name = "entrypoints"
optional = false
python-versions = ">=2.7"
version = "0.3"
[[package]]
category = "dev"
description = "A platform independent file lock."
name = "filelock"
optional = false
python-versions = "*"
version = "3.0.12"
[[package]]
category = "dev"
description = "the modular source code checker: pep8, pyflakes and co"
name = "flake8"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "3.7.8"
[package.dependencies]
entrypoints = ">=0.3.0,<0.4.0"
mccabe = ">=0.6.0,<0.7.0"
pycodestyle = ">=2.5.0,<2.6.0"
pyflakes = ">=2.1.0,<2.2.0"
[[package]]
category = "dev"
description = "Read metadata from Python packages"
marker = "python_version < \"3.8\""
name = "importlib-metadata"
optional = false
python-versions = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3"
version = "0.23"
[package.dependencies]
zipp = ">=0.5"
[[package]]
category = "dev"
description = "A Python utility / library to sort Python imports."
name = "isort"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "4.3.21"
[[package]]
category = "dev"
description = "McCabe checker, plugin for flake8"
name = "mccabe"
optional = false
python-versions = "*"
version = "0.6.1"
[[package]]
category = "dev"
description = "More routines for operating on iterables, beyond itertools"
name = "more-itertools"
optional = false
python-versions = ">=3.4"
version = "7.2.0"
[[package]]
category = "dev"
description = "Optional static typing for Python"
name = "mypy"
optional = false
python-versions = ">=3.5"
version = "0.740"
[package.dependencies]
mypy-extensions = ">=0.4.0,<0.5.0"
typed-ast = ">=1.4.0,<1.5.0"
typing-extensions = ">=3.7.4"
[[package]]
category = "dev"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
name = "mypy-extensions"
optional = false
python-versions = "*"
version = "0.4.3"
[[package]]
category = "dev"
description = "Core utilities for Python packages"
name = "packaging"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "19.2"
[package.dependencies]
pyparsing = ">=2.0.2"
six = "*"
[[package]]
category = "dev"
description = "plugin and hook calling mechanisms for python"
name = "pluggy"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.13.0"
[package.dependencies]
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12"
[[package]]
category = "dev"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
name = "py"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.8.0"
[[package]]
category = "dev"
description = "Python style guide checker"
name = "pycodestyle"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.5.0"
[[package]]
category = "dev"
description = "passive checker of Python programs"
name = "pyflakes"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.1.1"
[[package]]
category = "main"
description = "a pythonic generic language server (pronounced like \"pie glass\")."
name = "pygls"
optional = false
python-versions = "*"
version = "0.8.1"
[[package]]
category = "main"
description = "Python parsing module"
name = "pyparsing"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
version = "2.4.2"
[[package]]
category = "dev"
description = "pytest: simple powerful testing with Python"
name = "pytest"
optional = false
python-versions = ">=3.5"
version = "5.2.1"
[package.dependencies]
atomicwrites = ">=1.0"
attrs = ">=17.4.0"
colorama = "*"
more-itertools = ">=4.0.0"
packaging = "*"
pluggy = ">=0.12,<1.0"
py = ">=1.5.0"
wcwidth = "*"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12"
[[package]]
category = "dev"
description = "pytest plugin for test data directories and files"
name = "pytest-datadir"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.3.1"
[package.dependencies]
pytest = ">=2.7.0"
[[package]]
category = "dev"
description = "Python 2 and 3 compatibility utilities"
name = "six"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*"
version = "1.12.0"
[[package]]
category = "dev"
description = "Python Library for Tom's Obvious, Minimal Language"
name = "toml"
optional = false
python-versions = "*"
version = "0.10.0"
[[package]]
category = "dev"
description = "tox is a generic virtualenv management and test command line tool"
name = "tox"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "3.14.0"
[package.dependencies]
filelock = ">=3.0.0,<4"
packaging = ">=14"
pluggy = ">=0.12.0,<1"
py = ">=1.4.17,<2"
six = ">=1.0.0,<2"
toml = ">=0.9.4"
virtualenv = ">=14.0.0"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12,<1"
[[package]]
category = "dev"
description = "a fork of Python 2 and 3 ast modules with type comment support"
name = "typed-ast"
optional = false
python-versions = "*"
version = "1.4.0"
[[package]]
category = "dev"
description = "Type Hints for Python"
name = "typing"
optional = false
python-versions = "*"
version = "3.7.4.1"
[[package]]
category = "dev"
description = "Backported and Experimental Type Hints for Python 3.5+"
name = "typing-extensions"
optional = false
python-versions = "*"
version = "3.7.4"
[package.dependencies]
typing = ">=3.7.4"
[[package]]
category = "dev"
description = "Virtual Python Environment builder"
name = "virtualenv"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
version = "16.7.7"
[[package]]
category = "dev"
description = "Measures number of Terminal column cells of wide-character codes"
name = "wcwidth"
optional = false
python-versions = "*"
version = "0.1.7"
[[package]]
category = "dev"
description = "A formatter for Python code."
name = "yapf"
optional = false
python-versions = "*"
version = "0.28.0"
[[package]]
category = "dev"
description = "Backport of pathlib-compatible object wrapper for zip files"
marker = "python_version < \"3.8\""
name = "zipp"
optional = false
python-versions = ">=2.7"
version = "0.6.0"
[package.dependencies]
more-itertools = "*"
[metadata]
content-hash = "2fa2f64a1c51f6312594f611baa29f434f3a8ede16543988ac0d76a6de587c7f"
python-versions = "^3.6"
[metadata.hashes]
atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"]
attrs = ["08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", "f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"]
colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"]
entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"]
filelock = ["18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", "929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"]
flake8 = ["19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548", "8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696"]
importlib-metadata = ["aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", "d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"]
isort = ["54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", "6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"]
mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"]
more-itertools = ["409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"]
mypy = ["1521c186a3d200c399bd5573c828ea2db1362af7209b2adb1bb8532cea2fb36f", "31a046ab040a84a0fc38bc93694876398e62bc9f35eca8ccbf6418b7297f4c00", "3b1a411909c84b2ae9b8283b58b48541654b918e8513c20a400bb946aa9111ae", "48c8bc99380575deb39f5d3400ebb6a8a1cb5cc669bbba4d3bb30f904e0a0e7d", "540c9caa57a22d0d5d3c69047cc9dd0094d49782603eb03069821b41f9e970e9", "672e418425d957e276c291930a3921b4a6413204f53fe7c37cad7bc57b9a3391", "6ed3b9b3fdc7193ea7aca6f3c20549b377a56f28769783a8f27191903a54170f", "9371290aa2cad5ad133e4cdc43892778efd13293406f7340b9ffe99d5ec7c1d9", "ace6ac1d0f87d4072f05b5468a084a45b4eda970e4d26704f201e06d47ab2990", "b428f883d2b3fe1d052c630642cc6afddd07d5cd7873da948644508be3b9d4a7", "d5bf0e6ec8ba346a2cf35cb55bf4adfddbc6b6576fcc9e10863daa523e418dbb", "d7574e283f83c08501607586b3167728c58e8442947e027d2d4c7dcd6d82f453", "dc889c84241a857c263a2b1cd1121507db7d5b5f5e87e77147097230f374d10b", "f4748697b349f373002656bf32fede706a0e713d67bfdcf04edf39b1f61d46eb"]
mypy-extensions = ["090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", "2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"]
packaging = ["28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", "d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"]
pluggy = ["0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", "fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34"]
py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"]
pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", "e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"]
pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"]
pygls = ["3ee878a828b7bc0873a2ea44208d6846a91aa7dbbbdc052e7fe8cc689f6644fa", "780fd0c5ae95ad02ecaf70b071e43ff8ced8384b7d6bed19311a7b431d26fb88"]
pyparsing = ["6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", "d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"]
pytest = ["7e4800063ccfc306a53c461442526c5571e1462f61583506ce97e4da6a1d88c8", "ca563435f4941d0cb34767301c27bc65c510cb82e90b9ecf9cb52dc2c63caaa0"]
pytest-datadir = ["1847ed0efe0bc54cac40ab3fba6d651c2f03d18dd01f2a582979604d32e7621e", "d3af1e738df87515ee509d6135780f25a15959766d9c2b2dbe02bf4fb979cb18"]
six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"]
toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"]
tox = ["0bc216b6a2e6afe764476b4a07edf2c1dab99ed82bb146a1130b2e828f5bff5e", "c4f6b319c20ba4913dbfe71ebfd14ff95d1853c4231493608182f66e566ecfe1"]
typed-ast = ["1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", "18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", "262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", "2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", "354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", "48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", "4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", "630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", "66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", "71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", "7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", "838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", "95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", "bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", "cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", "d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", "d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", "d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", "fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", "ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"]
typing = ["91dfe6f3f706ee8cc32d38edbbf304e9b7583fb37108fef38229617f8b3eba23", "c8cabb5ab8945cd2f54917be357d134db9cc1eb039e59d1606dc1e60cb1d9d36", "f38d83c5a7a7086543a0f649564d661859c5146a85775ab90c0d2f93ffaa9714"]
typing-extensions = ["2ed632b30bb54fc3941c382decfd0ee4148f5c591651c9272473fea2c6397d95", "b1edbbf0652660e32ae780ac9433f4231e7339c7f9a8057d0f042fcbcea49b87", "d8179012ec2c620d3791ca6fe2bf7979d979acdbef1fca0bc56b37411db682ed"]
virtualenv = ["11cb4608930d5fd3afb545ecf8db83fa50e1f96fc4fca80c94b07d2c83146589", "d257bb3773e48cac60e475a19b608996c73f4d333b3ba2e4e57d5ac6134e0136"]
wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"]
yapf = ["02ace10a00fa2e36c7ebd1df2ead91dbfbd7989686dc4ccbdc549e95d19f5780", "6f94b6a176a7c114cfa6bad86d40f259bbe0f10cf2fa7f2f4b3596fc5802a41b"]
zipp = ["3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", "f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"]

View File

@@ -1,40 +1,61 @@
[tool.poetry]
[project]
name = "cmake-language-server"
version = "0.1.0"
dynamic = ["version"]
description = "CMake LSP Implementation"
license = "MIT"
authors = ["regen"]
authors = [
{name = "Regen"},
]
dependencies = [
"pygls>=2.0.0",
]
requires-python = ">=3.10.0,<3.16"
readme = "README.md"
repository = "https://github.com/regen100/cmake-language-server"
license = {text = "MIT"}
keywords = ["cmake", "completion", "vim", "lsp"]
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Console",
"Development Status :: 3 - Alpha", "Environment :: Console",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Topic :: Software Development",
"Topic :: Text Editors :: Integrated Development Environments (IDE)",
"Topic :: Utilities"
"Topic :: Text Editors :: Integrated Development Environments (IDE)", "Topic :: Utilities",
]
[tool.poetry.dependencies]
python = "^3.6"
pygls = "^0.8.1"
pyparsing = "^2.4"
[project.urls]
repository = "https://github.com/regen100/cmake-language-server"
[tool.poetry.dev-dependencies]
flake8 = "^3.7"
mypy = "^0.740.0"
pytest = "^5.2"
yapf = "^0.28.0"
pytest-datadir = "^1.3"
tox = "^3.14"
isort = "^4.3"
[tool.poetry.scripts]
cmake-format = "cmake_language_server.formatter:main"
[project.scripts]
cmake-language-server = "cmake_language_server.server:main"
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
requires = ["pdm-backend"]
build-backend = "pdm.backend"
[tool.pdm.version]
source = "scm"
write_to = "cmake_language_server/version.py"
write_template = "__version__ = \"{}\"\n"
[tool.pdm.dev-dependencies]
dev = [
"pytest>=7.2.0",
"pytest-datadir>=1.4.1",
"pytest-cov>=4.0.0",
"cmakelang>=0.6.13",
]
lint = [
"mypy>=0.991",
"flake8>=5.0.4",
"black>=22.12.0",
"isort>=5.11.4",
]
[tool.pdm.scripts]
test = "pytest --cov-report=term --cov-report=xml --cov=cmake_language_server -sv tests"
[tool.pdm.scripts.lint]
shell = """
isort --check cmake_language_server tests
black --check cmake_language_server tests
flake8 cmake_language_server tests
mypy cmake_language_server tests
"""

12
setup.cfg Normal file
View File

@@ -0,0 +1,12 @@
[flake8]
max-line-length = 88
extend-ignore = E203
[isort]
profile = black
line_length = 88
[mypy]
strict = true
ignore_missing_imports = true
show_error_codes = true

View File

@@ -1 +0,0 @@
__version__ = '0.1.0'

View File

@@ -1,257 +0,0 @@
import json
import logging
import re
import subprocess
import tempfile
import uuid
from pathlib import Path
from typing import Dict, List, Optional, Pattern
logger = logging.getLogger(__name__)
class API(object):
_cmake: str
_build: Path
_uuid: uuid.UUID
_builtin_commands: Dict[str, str]
_builtin_variables: Dict[str, str]
_builtin_variable_template: Dict[Pattern, str]
_targets: List[str]
_cached_variables: Dict[str, str]
_generated_list_parsed: bool
def __init__(self, cmake: str, build: Path):
self._cmake = cmake
self._build = Path(build)
self._uuid = uuid.uuid4()
self._builtin_commands = {}
self._builtin_variables = {}
self._builtin_variable_template = {}
self._targets = []
self._cached_variables = {}
self._generated_list_parsed = False
def query(self) -> bool:
if not self.cmake_cache.exists():
return False
self.query_json.parent.mkdir(parents=True, exist_ok=True)
with self.query_json.open('w') as fp:
fp.write('''\
{
"requests": [
{"kind": "codemodel", "version": 2},
{"kind": "cache", "version": 2},
{"kind": "cmakeFiles", "version": 1}
]
}''')
proc = subprocess.run([self._cmake, self._build],
universal_newlines=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
self.query_json.unlink()
self.query_json.parent.rmdir()
if proc.returncode != 0:
logging.error(
f'cmake exited with {proc.returncode}: {proc.stderr}')
return False
return True
def read_reply(self) -> bool:
reply = self._build / '.cmake' / 'api' / 'v1' / 'reply'
indices = sorted(reply.glob('index-*.json'))
if not indices:
logger.error('no reply')
return False
with indices[-1].open() as fp:
index = json.load(fp)
try:
responses = index['reply'][f'client-{self._uuid}']['query.json'][
'responses']
except KeyError:
logger.error('no rensponse')
return False
for response in responses:
if response['kind'] == 'codemodel':
self._read_codemodel(reply / response['jsonFile'])
elif response['kind'] == 'cache':
self._read_cache(reply / response['jsonFile'])
elif response['kind'] == 'cmakeFiles':
self._read_cmake_files(reply / response['jsonFile'])
return True
def _read_codemodel(self, codemodelpath: Path):
with (codemodelpath).open() as fp:
codemodel = json.load(fp)
config = codemodel['configurations'][0]
self._targets[:] = [x['name'] for x in config['targets']]
def _read_cache(self, cachepath: Path):
with cachepath.open() as fp:
cache = json.load(fp)
self._cached_variables.clear()
for entry in cache['entries']:
name = entry['name']
value = self._truncate_variable(entry['value'])
properties = {x['name']: x['value'] for x in entry['properties']}
helpstring = properties.get('HELPSTRING', '')
doc = []
if helpstring:
doc.append(helpstring)
if value:
doc.append(f'`{value}`')
self._cached_variables[name] = '\n\n'.join(doc)
def _read_cmake_files(self, jsonpath: Path):
'''inspect generated list files'''
if not self._builtin_variables or self._generated_list_parsed:
return
with jsonpath.open() as fp:
cmake_files = json.load(fp)
# inspect generated list files
with tempfile.TemporaryDirectory() as tmpdirname:
tmplist = Path(tmpdirname) / 'dump.cmake'
with tmplist.open('w') as fp:
for listfile in cmake_files['inputs']:
if not listfile.get('isGenerated', False):
continue
path = listfile['path']
fp.write(f'include({path})\n')
fp.write('''
get_cmake_property(variables VARIABLES)
foreach (variable ${variables})
message("${variable}=${${variable}}")
endforeach()
''')
p = subprocess.run([self._cmake, '-P', tmplist],
cwd=cmake_files['paths']['source'],
universal_newlines=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
if p.returncode != 0:
return
for line in p.stderr.split('\n'):
line = line.strip()
if not line:
continue
k, v = line.split('=', 1)
if k.startswith('CMAKE_ARG'):
continue
v = self._truncate_variable(v)
if k in self._builtin_variables:
self._builtin_variables[k] += f'\n\n`{v}`'
else:
for pattern, doc in self._builtin_variable_template.items(
):
if pattern.fullmatch(k):
self._builtin_variables[k] = f'{doc}\n\n`{v}`'
break
else:
# ignore variable with no document
pass
self._generated_list_parsed = True
@property
def query_json(self) -> Path:
return (self._build / '.cmake' / 'api' / 'v1' / 'query' /
f'client-{self._uuid}' / 'query.json')
@property
def cmake_cache(self) -> Path:
return self._build / 'CMakeCache.txt'
def parse_doc(self) -> None:
self._parse_commands()
self._parse_variables()
def _parse_commands(self) -> None:
p = subprocess.run([self._cmake, '--help-commands'],
stdout=subprocess.PIPE,
universal_newlines=True)
if p.returncode != 0:
return
matches = re.finditer(
r'''
(?P<command>.+)\n
-+\n\n
[\s\S]*?
(?P<signature>\ (?P=command)\s*\([^)]*\))
''', p.stdout, re.VERBOSE)
self._builtin_commands.clear()
for match in matches:
command = match.group('command')
signature = match.group('signature')
signature = re.sub(r'^ ', r'', signature, flags=re.MULTILINE)
self._builtin_commands[
command] = '```cmake\n' + signature + '\n```'
def _parse_variables(self) -> None:
p = subprocess.run([self._cmake, '--help-variables'],
stdout=subprocess.PIPE,
universal_newlines=True)
if p.returncode != 0:
return
matches = re.finditer(
r'''
(?P<variable>.+)\n
-+\n\n
(?P<doc>[\s\S]+?)(?:\n\n|$)
''', p.stdout, re.VERBOSE)
self._builtin_variables.clear()
for match in matches:
variable = match.group('variable')
doc = match.group('doc')
doc = re.sub(r':.+:`(.+)`', r'\1', doc)
doc = re.sub(r'``(.+)``', r'`\1`', doc)
doc = doc.replace('\n', ' ')
doc = doc.replace('. ', '. ')
if variable == 'CMAKE_MATCH_<n>':
for i in range(10):
self._builtin_variables[f'CMAKE_MATCH_{i}'] = doc
elif '<' in variable:
variable = re.sub(r'<[^>]+>', r'[^_]+', variable)
pattern = re.compile(variable)
self._builtin_variable_template[pattern] = doc
else:
self._builtin_variables[variable] = doc
def get_command_doc(self, command: str) -> Optional[str]:
return self._builtin_commands.get(command)
def search_command(self, command: str) -> List[str]:
command = command.lower()
return [x for x in self._builtin_commands if x.startswith(command)]
def get_variable_doc(self, variable: str) -> Optional[str]:
doc = self._cached_variables.get(variable)
if doc:
return doc
return self._builtin_variables.get(variable)
def search_variable(self, variable: str) -> List[str]:
cached = frozenset(x for x in self._cached_variables
if x.startswith(variable))
builtin = frozenset(x for x in self._builtin_variables
if x.startswith(variable))
return list(cached | builtin)
def search_target(self, target: str) -> List[str]:
return [x for x in self._targets if x.startswith(target)]
def _truncate_variable(self, v: str) -> str:
width = 70
return v[:width] + (v[width:] and '...')

View File

@@ -1,123 +0,0 @@
from typing import List
from .parser import TokenList
class Formatter(object):
indnt: str
lower_identifier: bool
def __init__(self, indent=' ', lower_identifier=True):
self.indent = indent
self.lower_identifier = lower_identifier
def format(self, tokens: TokenList) -> str:
cmds: List[str] = ['']
indnet_level = 0
for token in tokens:
if isinstance(token, tuple):
raw_identifier = token[0]
identifier = raw_identifier.lower()
if identifier in ('elseif', 'else', 'endif', 'endforeach',
'endwhile', 'endmacro', 'endfunction'):
if indnet_level > 0:
indnet_level -= 1
cmds[-1] = self.indent * indnet_level
cmds[-1] += (identifier
if self.lower_identifier else raw_identifier)
args = self._format_args(token[1])
if len(args) < 2:
cmds[-1] += '(' + ''.join(args) + ')'
else:
cmds[-1] += '(\n'
for arg in args:
cmds[-1] += self.indent * (indnet_level +
1) + arg + '\n'
cmds[-1] += self.indent * indnet_level + ')'
if identifier in ('if', 'elseif', 'else', 'foreach', 'while',
'macro', 'function'):
indnet_level += 1
elif token == '\n':
cmds.append('')
elif token[0] == '#':
if cmds[-1]:
cmds[-1] += token
else:
cmds[-1] = self.indent * indnet_level + token
elif cmds[-1]:
cmds[-1] += token
cmds = self._strip_line(cmds)
return '\n'.join(cmds) + '\n'
def _format_args(self, args: List[str]) -> List[str]:
lines = ['']
for i in range(len(args)):
arg = args[i]
if arg[0] == '#':
lines[-1] += arg
elif arg[0] == '\n':
lines.append('')
elif arg.isspace():
if lines[-1]:
if i + 1 < len(args) and args[i + 1][0] == '#':
lines[-1] += arg
else:
lines[-1] += ' '
else:
lines[-1] += arg
return self._strip_line(lines)
def _strip_line(self, lines: List[str]) -> List[str]:
'''Delete empty lines at the start/end of the input'''
ret: List[str] = []
for line in lines:
line = line.rstrip()
if line != '' or len(ret) > 0:
ret.append(line)
while ret and ret[-1] == '':
del ret[-1]
return ret
def main(args: List[str] = None):
from pathlib import Path
from argparse import ArgumentParser
from .parser import ListParser
from difflib import unified_diff
parser = ArgumentParser()
parser.add_argument('lists', type=Path, nargs='*', help='CMake list files')
parser.add_argument('-i',
'--inplace',
action='store_true',
help='inplace edit')
parser.add_argument('-d', '--diff', action='store_true', help='show diff')
args = parser.parse_args(args)
list_parser = ListParser()
formatter = Formatter()
for listpath in args.lists:
with listpath.open() as fp:
content = fp.read()
tokens, remain = list_parser.parse(content)
formatted = content if remain else formatter.format(tokens)
if args.inplace:
if not remain:
with listpath.open('w') as fp:
fp.write(formatted)
else:
if args.diff:
diff = unified_diff(content.splitlines(True),
formatted.splitlines(True), str(listpath),
str(listpath), '(before formatting)',
'(after formatting)')
diffstr = ''.join(diff)
if diffstr:
print(diffstr, end='')
else:
print(formatted, end='')

View File

@@ -1,63 +0,0 @@
from typing import List, Tuple, Union
import pyparsing as pp
CommandTokenType = Tuple[str, List[str]]
TokenType = Union[str, CommandTokenType]
TokenList = List[TokenType]
class ListParser(object):
_parser: pp.ParserElement
def __init__(self):
newline = '\n'
space_plus = pp.Regex('[ \t]+')
space_star = pp.Optional(space_plus)
quoted_element = pp.Regex(r'[^\\"]|\\[^A-Za-z0-9]|\\[trn]')
quoted_argument = pp.Combine('"' + pp.ZeroOrMore(quoted_element) + '"')
bracket_content = pp.Forward()
def action_bracket_open(tokens: pp.ParseResults):
nonlocal bracket_content
marker = ']' + '=' * (len(tokens[0]) - 2) + ']'
bracket_content <<= pp.SkipTo(marker, include=True)
bracket_open = pp.Regex(r'\[=*\[').setParseAction(action_bracket_open)
bracket_argument = pp.Combine(bracket_open + bracket_content)
unquoted_element = pp.Regex(r'[^\s()#"\\]|\\[^A-Za-z0-9]|\\[trn]')
unquoted_argument = pp.Combine(pp.OneOrMore(unquoted_element))
argument = bracket_argument | quoted_argument | unquoted_argument
line_comment = pp.Combine('#' + ~bracket_open +
pp.SkipTo(pp.LineEnd()))
bracket_comment = pp.Combine('#' + bracket_argument)
line_ending = (space_star +
pp.ZeroOrMore(bracket_comment + space_star) +
pp.Optional(line_comment) + (newline | pp.lineEnd))
identifier = pp.Word(pp.alphas + '_', pp.alphanums + '_')
arguments = pp.Forward()
arguments << pp.ZeroOrMore(argument | line_ending | space_plus
| '(' + arguments + ')').leaveWhitespace()
arguments = pp.Group(arguments)
PAREN_L, PAREN_R = map(pp.Suppress, '()')
command_invocation = (
identifier + space_star.suppress() + PAREN_L + arguments +
PAREN_R).setParseAction(lambda t: (t[0], t[1].asList()))
file_element = (space_star + command_invocation + line_ending
| line_ending).leaveWhitespace()
file = pp.ZeroOrMore(file_element)
self._parser = file
def parse(self, liststr: str) -> Tuple[TokenList, str]:
for t, s, e in self._parser.scanString(liststr, maxMatches=1):
if s == 0:
return t.asList(), liststr[e:]
return [], liststr

View File

@@ -1,138 +0,0 @@
import logging
import re
from pathlib import Path
from typing import List, Optional, Tuple
from pygls.features import (COMPLETION, FORMATTING, HOVER, INITIALIZE,
INITIALIZED, TEXT_DOCUMENT_DID_SAVE)
from pygls.server import LanguageServer
from pygls.types import (CompletionItem, CompletionItemKind, CompletionList,
CompletionParams, CompletionTriggerKind,
DocumentFormattingParams, Hover, InitializeParams,
MarkupContent, MarkupKind, Position, Range,
TextDocumentPositionParams, TextEdit)
from .api import API
from .formatter import Formatter
from .parser import ListParser
logger = logging.getLogger(__name__)
class CMakeLanguageServer(LanguageServer):
_parser: ListParser
_api: API
def __init__(self):
super().__init__()
self._parser = ListParser()
self._api = None
@self.feature(INITIALIZE)
def initialize(params: InitializeParams):
opts = params.initializationOptions
cmake = getattr(opts, 'cmakeExecutable', 'cmake')
builddir = getattr(opts, 'buildDirectory', '')
logging.info(f'cmakeExecutable={cmake}, buildDirectory={builddir}')
self._api = API(cmake, Path(builddir))
self._api.parse_doc()
@self.feature(COMPLETION, trigger_characters=['{'])
def completions(params: CompletionParams):
if (params.context.triggerKind ==
CompletionTriggerKind.TriggerCharacter):
token = ''
trigger = params.context.triggerCharacter
else:
ret = self.cursor_word(params.textDocument.uri,
params.position, False)
if not ret:
return None
token = ret[0]
trigger = None
items: List[CompletionItem] = []
if trigger != '{':
commands = self._api.search_command(token)
items.extend(
CompletionItem(x,
CompletionItemKind.Function,
documentation=self._api.get_command_doc(x))
for x in commands)
variables = self._api.search_variable(token)
items.extend(
CompletionItem(x,
CompletionItemKind.Variable,
documentation=self._api.get_variable_doc(x))
for x in variables)
if trigger != '{':
targets = self._api.search_target(token)
items.extend(
CompletionItem(x, CompletionItemKind.Class)
for x in targets)
return CompletionList(False, items)
@self.feature(FORMATTING)
def formatting(params: DocumentFormattingParams):
doc = self.workspace.get_document(params.textDocument.uri)
content = doc.source
tokens, remain = self._parser.parse(content)
if remain:
self.show_message('CMake parser failed')
return None
formatted = Formatter().format(tokens)
lines = content.count('\n')
return [
TextEdit(Range(Position(0, 0), Position(lines + 1, 0)),
formatted)
]
@self.feature(HOVER)
def hover(params: TextDocumentPositionParams):
ret = self.cursor_word(params.textDocument.uri, params.position)
if not ret:
return None
doc = self._api.get_command_doc(ret[0].lower())
if not doc:
doc = self._api.get_variable_doc(ret[0])
if not doc:
return None
return Hover(MarkupContent(MarkupKind.Markdown, doc), ret[1])
@self.thread()
@self.feature(TEXT_DOCUMENT_DID_SAVE, includeText=False)
@self.feature(INITIALIZED)
def run_cmake(*args):
if self._api.query():
self._api.read_reply()
def cursor_word(self,
uri: str,
position: Position,
include_all: bool = True) -> Optional[Tuple[str, Range]]:
doc = self.workspace.get_document(uri)
content = doc.source
line = content.split('\n')[position.line]
cursor = position.character
for m in re.finditer(r'\w+', line):
if m.start() <= cursor <= m.end():
end = m.end() if include_all else cursor
return (line[m.start():end],
Range(Position(position.line, m.start()),
Position(position.line, end)))
return None
def main():
logging.basicConfig(level=logging.INFO)
logging.getLogger('pygls').setLevel(logging.WARNING)
CMakeLanguageServer().start_io()

View File

@@ -1,16 +1,60 @@
import logging
import os
from pathlib import Path
from subprocess import PIPE, run
from threading import Thread
from typing import Iterable, Tuple
import pytest
from lsprotocol.types import EXIT, SHUTDOWN
from pygls.lsp.server import LanguageServer
from cmake_language_server.server import CMakeLanguageServer
@pytest.fixture()
def cmake_build(shared_datadir):
from subprocess import run, PIPE
source = shared_datadir / 'cmake'
build = source / 'build'
def cmake_build(shared_datadir: Path) -> Iterable[Path]:
source = shared_datadir / "cmake"
build = source / "build"
build.mkdir()
run(['cmake', source],
check=True,
p = run(
["cmake", str(source)],
cwd=build,
stdout=PIPE,
stderr=PIPE,
universal_newlines=True)
universal_newlines=True,
)
if p.returncode != 0:
logging.error("stdout:\n" + p.stdout)
logging.error("stderr:\n" + p.stderr)
raise RuntimeError("CMake failed")
yield build
@pytest.fixture()
def client_server() -> Iterable[Tuple[LanguageServer, CMakeLanguageServer]]:
c2s_r, c2s_w = os.pipe()
s2c_r, s2c_w = os.pipe()
def start(ls: LanguageServer, fdr: int, fdw: int) -> None:
# start_io type hints seem to be wrong?
ls.start_io(os.fdopen(fdr, "rb"), os.fdopen(fdw, "wb")) # type:ignore[arg-type]
server = CMakeLanguageServer("server", "v1")
server_thread = Thread(target=start, args=(server, c2s_r, s2c_w))
server_thread.start()
client = LanguageServer("client", "v1")
client_thread = Thread(target=start, args=(client, s2c_r, c2s_w))
client_thread.start()
yield client, server
# fix bug on python 3.7
if hasattr(client.loop, "_signal_handlers"):
client.loop._signal_handlers.clear()
client.protocol.send_request(SHUTDOWN) # type:ignore[no-untyped-call]
client.protocol.notify(EXIT)
client_thread.join()
server_thread.join()

View File

@@ -1,75 +1,122 @@
import subprocess
from pathlib import Path
from cmake_language_server.api import API
def test_query_with_cache(cmake_build):
api = API('cmake', cmake_build)
def test_query_with_cache(cmake_build: Path) -> None:
api = API("cmake", cmake_build)
assert api.query()
query = cmake_build / '.cmake' / 'api' / 'v1' / 'query'
query = cmake_build / ".cmake" / "api" / "v1" / "query"
assert query.exists()
reply = cmake_build / '.cmake' / 'api' / 'v1' / 'reply'
reply = cmake_build / ".cmake" / "api" / "v1" / "reply"
assert reply.exists()
def test_query_without_cache(cmake_build):
api = API('cmake', cmake_build)
(cmake_build / 'CMakeCache.txt').unlink()
def test_query_without_cache(cmake_build: Path) -> None:
api = API("cmake", cmake_build)
(cmake_build / "CMakeCache.txt").unlink()
assert not api.query()
def test_read_variable(cmake_build):
api = API('cmake', cmake_build)
def test_read_variable(cmake_build: Path) -> None:
api = API("cmake", cmake_build)
assert api.query()
assert api.read_reply()
assert api.get_variable_doc('testproject_BINARY_DIR')
assert api.get_variable_doc("testproject_BINARY_DIR")
def test_read_cmake_files(cmake_build):
api = API('cmake', cmake_build)
def test_read_cmake_files(cmake_build: Path) -> None:
api = API("cmake", cmake_build)
api.parse_doc()
assert api.query()
api.read_reply()
assert 'GNU' in api.get_variable_doc('CMAKE_CXX_COMPILER_ID')
import platform
system = platform.system()
cxx = api.get_variable_doc("CMAKE_CXX_COMPILER_ID")
assert cxx is not None
if system == "Linux":
assert "GNU" in cxx
elif system == "Windows":
assert "MSVC" in cxx
elif system == "Darwin":
assert "Clang" in cxx
else:
raise RuntimeError("Unexpected system")
def test_parse_commands(cmake_build):
api = API('cmake', cmake_build)
def test_parse_commands(cmake_build: Path) -> None:
api = API("cmake", cmake_build)
api.parse_doc()
p = subprocess.run(['cmake', '--help-command-list'],
universal_newlines=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
commands = p.stdout.strip().split('\n')
p = subprocess.run(
["cmake", "--help-command-list"],
universal_newlines=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
commands = p.stdout.strip().split("\n")
for command in commands:
assert api.get_command_doc(command) is not None, f'{command} not found'
assert api.get_command_doc(command) is not None, f"{command} not found"
assert 'break()' in api.get_command_doc('break')
assert api.get_command_doc('not_existing_command') is None
break_doc = api.get_command_doc("break")
assert break_doc is not None and "break()" in break_doc
assert api.get_command_doc("not_existing_command") is None
def test_parse_variables(cmake_build):
api = API('cmake', cmake_build)
def test_parse_variables(cmake_build: Path) -> None:
api = API("cmake", cmake_build)
api.parse_doc()
p = subprocess.run(['cmake', '--help-variable-list'],
universal_newlines=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
variables = p.stdout.strip().split('\n')
p = subprocess.run(
["cmake", "--help-variable-list"],
universal_newlines=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
variables = p.stdout.strip().split("\n")
for variable in variables:
if '<' in variable:
if "<" in variable:
continue
assert api.get_variable_doc(
variable) is not None, f'{variable} not found'
assert api.get_variable_doc(variable) is not None, f"{variable} not found"
assert api.get_variable_doc('BUILD_SHARED_LIBS') is not None
assert api.get_variable_doc('not_existing_variable') is None
assert api.get_variable_doc("BUILD_SHARED_LIBS") is not None
assert api.get_variable_doc("not_existing_variable") is None
def test_parse_modules(cmake_build: Path) -> None:
api = API("cmake", cmake_build)
api.parse_doc()
p = subprocess.run(
["cmake", "--help-module-list"],
universal_newlines=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
modules = p.stdout.strip().split("\n")
for module in modules:
if module.startswith("Find"):
assert (
api.get_module_doc(module[4:], True) is not None
), f"{module} not found"
else:
assert api.get_module_doc(module, False) is not None, f"{module} not found"
assert api.get_module_doc("GoogleTest", False) is not None
assert api.get_module_doc("GoogleTest", True) is None
assert api.search_module("GoogleTest", False) == ["GoogleTest"]
assert api.search_module("GoogleTest", True) == []
assert api.get_module_doc("Boost", False) is None
assert api.get_module_doc("Boost", True) is not None
assert api.search_module("Boost", False) == []
assert api.search_module("Boost", True) == ["Boost"]

View File

@@ -1,74 +0,0 @@
from cmake_language_server.formatter import Formatter
from cmake_language_server.parser import ListParser
def make_formatter_test(liststr: str, expect: str):
def test():
tokens, remain = ListParser().parse(liststr)
actual = Formatter().format(tokens)
assert actual == expect
return test
test_command = make_formatter_test('a()', 'a()\n')
test_command_tolower = make_formatter_test('A()', 'a()\n')
test_remove_space = make_formatter_test('''
#a
b ( c ) # d
''', '''\
#a
b(c) # d
''')
test_indent_if = make_formatter_test(
'''
if()
a() # a
else()
# b
b()
endif()
''', '''\
if()
a() # a
else()
# b
b()
endif()
''')
test_indent_if_nested = make_formatter_test(
'''
if()
if()
a()
b()
endif()
endif()
''', '''\
if()
if()
a()
b()
endif()
endif()
''')
test_argument = make_formatter_test('a( b c d)', 'a(b c d)\n')
test_argument_multiline = make_formatter_test(
'''
if()
a(b c
d # e
f
# g
) # h
endif()
''', '''\
if()
a(
b c
d # e
f
# g
) # h
endif()
''')

View File

@@ -1,64 +0,0 @@
from typing import List
from cmake_language_server.parser import ListParser, TokenType
def make_parser_test(liststr: str,
expect_token: List[TokenType],
expect_remain: str = ''):
def test():
actual_token, actual_remain = ListParser().parse(liststr)
assert actual_token == expect_token
assert actual_remain == expect_remain
return test
test_command_no_args = make_parser_test('a()', [('a', [])])
test_command_space = make_parser_test(' a ()', [' ', ('a', [])])
test_command_arg = make_parser_test('a(b)', [('a', ['b'])])
test_command_arg_space = make_parser_test('a ( b )', [('a', ['b'])])
test_command_arg_escape = make_parser_test(r'a(\n\")', [('a', [r'\n\"'])])
test_command_arg_paren = make_parser_test('a((b))', [('a', ['(', 'b', ')'])])
test_command_arg_paren_paren = make_parser_test(
'a(((b)))', [('a', ['(', '(', 'b', ')', ')'])])
test_command_arg_quote = make_parser_test(r'a("b\"")', [('a', [r'"b\""'])])
test_command_arg_quote_cont = make_parser_test('a("\\\n")',
[('a', ['"\\\n"'])])
test_command_arg_quo_multiline = make_parser_test('''a("b
c
")''', [('a', ['"b\nc\n"'])])
test_command_arg_bracket_0 = make_parser_test('a([[b]])', [('a', ['[[b]]'])])
test_command_arg_bracket_1 = make_parser_test('a([=[b]=])',
[('a', ['[=[b]=]'])])
test_command_arg_space = make_parser_test('a ( b )', [('a', [' ', 'b', ' '])])
test_command_arg_multi = make_parser_test('a(b c)', [('a', ['b', ' ', 'c'])])
test_command_multielement = make_parser_test('''a(
b
c # c
)''', [('a', ['\n', ' ', 'b', '\n', ' ', 'c', ' ', '# c', '\n'])])
test_line_comment = make_parser_test('a() # b # c',
[('a', []), ' ', '# b # c'])
test_bracket_comment = make_parser_test('#[[a]]#[[b]]', ['#[[a]]', '#[[b]]'])
test_bracket_comment_nested = make_parser_test('#[=[[[a]]]=]',
['#[=[[[a]]]=]'])
test_bracket_comment_multiline = make_parser_test('#[[\na\nb\nc\n]]',
['#[[\na\nb\nc\n]]'])
test_if_block = make_parser_test('''if()
a()
else()
b()
endif()''', [('if', []), '\n', ' ', ('a', []), '\n', ('else', []), '\n', ' ',
('b', []), '\n', ('endif', [])])
test_comment_multi_linecomment = make_parser_test(
'''a()# a
b() # b
c() # c''', [('a', []), '# a', '\n', ('b', []), ' ', '# b', '\n',
('c', []), ' ', '# c'])
test_incomplete_id = make_parser_test('a', [], 'a')
test_incomplete_command = make_parser_test('a(', [], 'a(')
test_incomplete_id_after_command = make_parser_test('a()\nb',
[('a', []), '\n'], 'b')
test_incomplete_command_after_command = make_parser_test(
'a()\nb(c', [('a', []), '\n'], 'b(c')

196
tests/test_server.py Normal file
View File

@@ -0,0 +1,196 @@
import time
from concurrent import futures
from pathlib import Path
from typing import Optional, Tuple
import pytest
from lsprotocol.types import (
INITIALIZE,
TEXT_DOCUMENT_COMPLETION,
TEXT_DOCUMENT_DID_OPEN,
TEXT_DOCUMENT_FORMATTING,
TEXT_DOCUMENT_HOVER,
WORKSPACE_DID_CHANGE_CONFIGURATION,
ClientCapabilities,
CompletionContext,
CompletionList,
CompletionParams,
CompletionTriggerKind,
DidChangeConfigurationParams,
DidOpenTextDocumentParams,
DocumentFormattingParams,
FormattingOptions,
HoverParams,
InitializeParams,
MarkupContent,
Position,
TextDocumentIdentifier,
TextDocumentItem,
)
from pygls.lsp.server import LanguageServer
from cmake_language_server.server import CMakeLanguageServer
CALL_TIMEOUT = 2
def _init(client: LanguageServer, root: Path) -> None:
retry = 3
while retry > 0:
try:
client.protocol.send_request( # type:ignore[no-untyped-call]
INITIALIZE,
InitializeParams(
process_id=1234,
root_uri=root.as_uri(),
capabilities=ClientCapabilities(),
),
).result(timeout=CALL_TIMEOUT)
except futures.TimeoutError:
retry -= 1
else:
break
def _open(client: LanguageServer, path: Path, text: Optional[str] = None) -> None:
if text is None:
text = path.read_text()
client.protocol.notify(
TEXT_DOCUMENT_DID_OPEN,
DidOpenTextDocumentParams(
text_document=TextDocumentItem(
uri=path.as_uri(), language_id="cmake", version=1, text=text
)
),
)
def _test_completion(
client_server: Tuple[LanguageServer, CMakeLanguageServer],
datadir: Path,
content: str,
context: Optional[CompletionContext],
) -> CompletionList:
client, server = client_server
_init(client, datadir)
path = datadir / "CMakeLists.txt"
_open(client, path, content)
params = CompletionParams(
text_document=TextDocumentIdentifier(uri=path.as_uri()),
position=Position(line=0, character=len(content)),
context=context,
)
ret = client.protocol.send_request( # type:ignore[no-untyped-call]
TEXT_DOCUMENT_COMPLETION, params
).result(timeout=CALL_TIMEOUT)
assert isinstance(ret, CompletionList)
return ret
def test_initialize(
client_server: Tuple[LanguageServer, CMakeLanguageServer], datadir: Path
) -> None:
client, server = client_server
assert server._api is None
_init(client, datadir)
assert server._api is not None
def test_workspace_did_change_configuration(
client_server: Tuple[LanguageServer, CMakeLanguageServer], datadir: Path
) -> None:
client, server = client_server
_init(client, datadir)
old_api = server._api
client.protocol.notify(
WORKSPACE_DID_CHANGE_CONFIGURATION,
DidChangeConfigurationParams(
settings={"initialization_options": {"buildDirectory": "c_build"}}
),
)
start = time.monotonic()
while server._api is old_api and (time.monotonic() - start) < CALL_TIMEOUT:
time.sleep(0.1)
assert server._api is not None
assert server._api._build.as_posix() == "c_build"
@pytest.mark.parametrize(
"context", [CompletionContext(trigger_kind=CompletionTriggerKind.Invoked), None]
)
def test_completions(
context: Optional[CompletionContext],
client_server: Tuple[LanguageServer, CMakeLanguageServer],
datadir: Path,
) -> None:
response = _test_completion(client_server, datadir, "projec", context)
item = next(filter(lambda x: x.label == "project", response.items), None)
assert item is not None
assert isinstance(item.documentation, MarkupContent)
assert "<PROJECT-NAME>" in item.documentation.value
@pytest.mark.parametrize(
"text, item",
[("find_package(", "Boost"), ("include(", "GoogleTest"), ("${", "PROJECT_VERSION")],
)
def test_completions_triggercharacter(
text: str,
item: str,
client_server: Tuple[LanguageServer, CMakeLanguageServer],
datadir: Path,
) -> None:
response = _test_completion(
client_server,
datadir,
text,
CompletionContext(
trigger_kind=CompletionTriggerKind.TriggerCharacter,
trigger_character=text[-1],
),
)
assert item in [x.label for x in response.items]
response_nocontext = _test_completion(client_server, datadir, text, None)
assert response == response_nocontext
def test_formatting(
client_server: Tuple[LanguageServer, CMakeLanguageServer], datadir: Path
) -> None:
client, server = client_server
_init(client, datadir)
path = datadir / "CMakeLists.txt"
_open(client, path, "a ( b c ) ")
response = client.protocol.send_request( # type:ignore[no-untyped-call]
TEXT_DOCUMENT_FORMATTING,
DocumentFormattingParams(
text_document=TextDocumentIdentifier(uri=path.as_uri()),
options=FormattingOptions(tab_size=2, insert_spaces=True),
),
).result(timeout=CALL_TIMEOUT)
assert response[0].new_text == "a(b c)\n"
def test_hover(
client_server: Tuple[LanguageServer, CMakeLanguageServer], datadir: Path
) -> None:
client, server = client_server
_init(client, datadir)
path = datadir / "CMakeLists.txt"
_open(client, path, "project()")
response = client.protocol.send_request( # type:ignore[no-untyped-call]
TEXT_DOCUMENT_HOVER,
HoverParams(
text_document=TextDocumentIdentifier(uri=path.as_uri()),
position=Position(line=0, character=0),
),
).result(timeout=CALL_TIMEOUT)
assert "<PROJECT-NAME>" in response.contents.value

34
tox.ini
View File

@@ -1,27 +1,23 @@
[tox]
env_list = py{39,310,311,312,313,314}
isolated_build = True
skipsdist = True
envlist = py36, py37, py38, lint
passenv = *
setenv =
PDM_IGNORE_SAVED_PYTHON="1"
[gh-actions]
python =
3.6: py36
3.7: py37, lint
3.8: py38
3.9: py39
3.10: py310
3.11: py311
3.12: py312
3.13: py313
3.14: py314
[testenv]
whitelist_externals = poetry
skip_install = true
commands_pre =
poetry install
allowlist_externals =
pdm
commands =
poetry run pytest -sv tests
[testenv:lint]
whitelist_externals = poetry
skip_install = true
commands =
poetry run isort -c -rc src tests
poetry run yapf -d -r src tests
poetry run flake8
poetry run mypy src tests
pdm install --dev -G :all
pdm run lint
pdm run test