Initial commit
This commit is contained in:
133
.gitignore
vendored
Normal file
133
.gitignore
vendored
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
### https://raw.github.com/github/gitignore/cb0c6ef7ac68f2300409ee85501d9ad432cb4c7e/Python.gitignore
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
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
|
||||||
|
__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/
|
||||||
|
|
||||||
|
|
||||||
2
.style.yapf
Normal file
2
.style.yapf
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[style]
|
||||||
|
based_on_style = pep8
|
||||||
36
README.md
Normal file
36
README.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# cmake-language-server
|
||||||
|
|
||||||
|
CMake LSP Implementation.
|
||||||
|
|
||||||
|
Alpha Stage, work in progress.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- [x] Builtin command completion
|
||||||
|
- [x] Documentation for commands and variables on hover
|
||||||
|
- [x] Formatting
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Clients
|
||||||
|
|
||||||
|
- Neovim ([neoclide/coc.nvim][coc.nvim])
|
||||||
|
|
||||||
|
#### Neovim
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
"languageserver": {
|
||||||
|
"cmake": {
|
||||||
|
"command": "cmake-language-server",
|
||||||
|
"filetypes": ["cmake"],
|
||||||
|
"rootPatterns": [
|
||||||
|
"build/"
|
||||||
|
],
|
||||||
|
"initializationOptions": {
|
||||||
|
"buildDirectory": "build"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
[coc.nvim]: https://github.com/neoclide/coc.nvim
|
||||||
6
mypy.ini
Normal file
6
mypy.ini
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[mypy]
|
||||||
|
ignore_missing_imports = True
|
||||||
|
allow_redefinition = True
|
||||||
|
|
||||||
|
[mypy-re.Scanner]
|
||||||
|
ignore_errors = True
|
||||||
346
poetry.lock
generated
Normal file
346
poetry.lock
generated
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
[[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"]
|
||||||
38
pyproject.toml
Normal file
38
pyproject.toml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "cmake-language-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "CMake LSP Implementation"
|
||||||
|
authors = ["regen"]
|
||||||
|
license = "MIT"
|
||||||
|
classifiers = [
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
keywords = ["cmake", "completion", "vim"]
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.6"
|
||||||
|
pygls = "^0.8.1"
|
||||||
|
pyparsing = "^2.4"
|
||||||
|
|
||||||
|
[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"
|
||||||
|
cmake-language-server = "cmake_language_server.server:main"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry>=0.12"]
|
||||||
|
build-backend = "poetry.masonry.api"
|
||||||
1
src/cmake_language_server/__init__.py
Normal file
1
src/cmake_language_server/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
__version__ = '0.1.0'
|
||||||
255
src/cmake_language_server/api.py
Normal file
255
src/cmake_language_server/api.py
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
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,
|
||||||
|
capture_output=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:
|
||||||
|
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,
|
||||||
|
capture_output=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()
|
||||||
|
|
||||||
|
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 '...')
|
||||||
116
src/cmake_language_server/formatter.py
Normal file
116
src/cmake_language_server/formatter.py
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
parser = ArgumentParser()
|
||||||
|
parser.add_argument('lists', type=Path, nargs='*', help='CMake list files')
|
||||||
|
parser.add_argument('-i',
|
||||||
|
'--inplace',
|
||||||
|
action='store_true',
|
||||||
|
help='inplace edit')
|
||||||
|
|
||||||
|
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)
|
||||||
|
if remain:
|
||||||
|
if args.inplace:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
print(content, end='')
|
||||||
|
else:
|
||||||
|
formated = formatter.format(tokens)
|
||||||
|
if args.inplace:
|
||||||
|
with listpath.open('w') as fp:
|
||||||
|
fp.write(formated)
|
||||||
|
else:
|
||||||
|
print(formated, end='')
|
||||||
63
src/cmake_language_server/parser.py
Normal file
63
src/cmake_language_server/parser.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
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
|
||||||
138
src/cmake_language_server/server.py
Normal file
138
src/cmake_language_server/server.py
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
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()
|
||||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
18
tests/conftest.py
Normal file
18
tests/conftest.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def cmake_build(shared_datadir):
|
||||||
|
from subprocess import run
|
||||||
|
source = shared_datadir / 'cmake'
|
||||||
|
build = source / 'build'
|
||||||
|
build.mkdir()
|
||||||
|
p = run(['cmake', '-S', source, '-B', build],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
universal_newlines=True)
|
||||||
|
logging.debug(p.stdout)
|
||||||
|
logging.debug(p.stderr)
|
||||||
|
yield build
|
||||||
7
tests/data/cmake/CMakeLists.txt
Normal file
7
tests/data/cmake/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
|
project(testproject CXX)
|
||||||
|
|
||||||
|
add_executable(test_app main.cpp)
|
||||||
|
|
||||||
|
add_library(test_lib lib.cpp)
|
||||||
0
tests/data/cmake/lib.cpp
Normal file
0
tests/data/cmake/lib.cpp
Normal file
1
tests/data/cmake/main.cpp
Normal file
1
tests/data/cmake/main.cpp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
int main(int argc, char *argv[]) { return 0; }
|
||||||
73
tests/test_api.py
Normal file
73
tests/test_api.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import subprocess
|
||||||
|
|
||||||
|
from cmake_language_server.api import API
|
||||||
|
|
||||||
|
|
||||||
|
def test_query_with_cache(cmake_build):
|
||||||
|
api = API('cmake', cmake_build)
|
||||||
|
assert api.query()
|
||||||
|
|
||||||
|
query = cmake_build / '.cmake' / 'api' / 'v1' / 'query'
|
||||||
|
assert query.exists()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
assert not api.query()
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_variable(cmake_build):
|
||||||
|
api = API('cmake', cmake_build)
|
||||||
|
assert api.query()
|
||||||
|
assert api.read_reply()
|
||||||
|
|
||||||
|
assert api.get_variable_doc('testproject_BINARY_DIR')
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_cmake_files(cmake_build):
|
||||||
|
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')
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_commands(cmake_build):
|
||||||
|
api = API('cmake', cmake_build)
|
||||||
|
api.parse_doc()
|
||||||
|
|
||||||
|
p = subprocess.run(['cmake', '--help-command-list'],
|
||||||
|
capture_output=True,
|
||||||
|
universal_newlines=True)
|
||||||
|
commands = p.stdout.strip().split('\n')
|
||||||
|
|
||||||
|
for command in commands:
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_variables(cmake_build):
|
||||||
|
api = API('cmake', cmake_build)
|
||||||
|
api.parse_doc()
|
||||||
|
|
||||||
|
p = subprocess.run(['cmake', '--help-variable-list'],
|
||||||
|
capture_output=True,
|
||||||
|
universal_newlines=True)
|
||||||
|
variables = p.stdout.strip().split('\n')
|
||||||
|
|
||||||
|
for variable in variables:
|
||||||
|
if '<' in variable:
|
||||||
|
continue
|
||||||
|
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
|
||||||
74
tests/test_fomatter.py
Normal file
74
tests/test_fomatter.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
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()
|
||||||
|
''')
|
||||||
64
tests/test_parser.py
Normal file
64
tests/test_parser.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
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')
|
||||||
21
tox.ini
Normal file
21
tox.ini
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[tox]
|
||||||
|
isolated_build = True
|
||||||
|
skipsdist = True
|
||||||
|
envlist = py36, py37, py38, lint
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
whitelist_externals = poetry
|
||||||
|
skip_install = true
|
||||||
|
commands_pre =
|
||||||
|
poetry install
|
||||||
|
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
|
||||||
Reference in New Issue
Block a user