Skip to content

Add client side configuration and cache configuration per file #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 28, 2022
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
.mypy_cache
*.egg-info
*.pyc
*.orig
.pytest_cache
.ropeproject
build
Expand Down
31 changes: 27 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,31 @@ To avoid unexpected results you should make sure `yapf` and `autopep8` are not i
- The code will only be formatted if it is syntactically valid Python.
- Text selections are treated as if they were a separate Python file.
Unfortunately this means you can't format an indented block of code.
- `python-lsp-black` will use your project's [pyproject.toml](https://github.com/psf/black#pyprojecttoml) if it has one.
- `python-lsp-black` only officially supports the latest stable version of [black](https://github.com/psf/black). An effort is made to keep backwards-compatibility but older black versions will not be actively tested.
- `python-lsp-black` will use your project's
[pyproject.toml](https://github.com/psf/black#pyprojecttoml) if it has one.
- `python-lsp-black` only officially supports the latest stable version of
[black](https://github.com/psf/black). An effort is made to keep backwards-compatibility
but older black versions will not be actively tested.
- The plugin can cache the black configuration that applies to each Python file, this
improves performance of the plugin. When configuration caching is enabled any changes to
black's configuration will need the LSP server to be restarted. Configuration caching
can be disabled with the `cache_config` option, see *Configuration* below.

# Configuration

The plugin follows [python-lsp-server's
configuration](https://github.com/python-lsp/python-lsp-server/#configuration). These are
the valid configuration keys:

- `pylsp.plugins.black.enabled`: boolean to enable/disable the plugin.
- `pylsp.plugins.black.cache_config`: a boolean to enable black configuration caching (see
*Usage*). `false` by default.
- `pylsp.plugins.black.line_length`: an integer that maps to [black's
`max-line-length`](https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#line-length)
setting. Defaults to 88 (same as black's default). This can also be set through black's
configuration files, which should be preferred for multi-user projects.
- `pylsp.plugins.black.preview`: a boolean to enable or disable [black's `--preview`
setting](https://black.readthedocs.io/en/stable/the_black_code_style/future_style.html#preview-style).

# Development

Expand All @@ -35,8 +58,8 @@ pip install -e .[dev]
```

This project uses [pre-commit](https://pre-commit.com/) hooks to control code quality,
install them to run them when creating a git commit, thus avoiding seeing errors when you
create a pull request:
install them to run automatically when creating a git commit, thus avoiding seeing errors
when you create a pull request:

```shell
pre-commit install
Expand Down
47 changes: 39 additions & 8 deletions pylsp_black/plugin.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import logging
import os
from functools import lru_cache
from pathlib import Path
from typing import Dict, Optional

import black
import toml
from pylsp import hookimpl
from pylsp._utils import get_eol_chars
from pylsp.config.config import Config

logger = logging.getLogger(__name__)

Expand All @@ -27,19 +29,34 @@


@hookimpl(tryfirst=True)
def pylsp_format_document(document):
return format_document(document)
def pylsp_format_document(config, document):
return format_document(config, document)


@hookimpl(tryfirst=True)
def pylsp_format_range(document, range):
def pylsp_format_range(config, document, range):
range["start"]["character"] = 0
range["end"]["line"] += 1
range["end"]["character"] = 0
return format_document(document, range)
return format_document(config, document, range)


@hookimpl
def pylsp_settings():
"""Configuration options that can be set on the client."""
return {
"plugins": {
"black": {
"enabled": True,
"line_length": 88,
"preview": False,
"cache_config": False,
}
}
}


def format_document(document, range=None):
def format_document(client_config, document, range=None):
if range:
start = range["start"]["line"]
end = range["end"]["line"]
Expand All @@ -51,7 +68,7 @@ def format_document(document, range=None):
"end": {"line": len(document.lines), "character": 0},
}

config = load_config(document.path)
config = load_config(document.path, client_config)

try:
formatted_text = format_text(text=text, config=config)
Expand Down Expand Up @@ -103,13 +120,17 @@ def format_text(*, text, config):
raise black.NothingChanged from e


def load_config(filename: str) -> Dict:
@lru_cache(100)
def _load_config(filename: str, client_config: Config) -> Dict:
settings = client_config.plugin_settings("black")

defaults = {
"line_length": 88,
"line_length": settings.get("line_length", 88),
"fast": False,
"pyi": filename.endswith(".pyi"),
"skip_string_normalization": False,
"target_version": set(),
"preview": settings.get("preview", False),
}

root = black.find_project_root((filename,))
Expand Down Expand Up @@ -168,3 +189,13 @@ def load_config(filename: str) -> Dict:
logger.info("Using config from %s: %r", pyproject_filename, config)

return config


def load_config(filename: str, client_config: Config) -> Dict:
settings = client_config.plugin_settings("black")

# Use the original, not cached function to load settings if requested
if not settings.get("cache_config", False):
return _load_config.__wrapped__(filename, client_config)

return _load_config(filename, client_config)
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ classifiers =

[options]
packages = find:
install_requires = python-lsp-server>=1.4.0; black>=19.3b0; toml
install_requires = python-lsp-server>=1.4.0; black>=22.1.0; toml
python_requires = >= 3.7

[options.entry_points]
Expand Down
4 changes: 4 additions & 0 deletions tests/fixtures/formatted-line-length.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def foo(
aaaaa, bbbbb, ccccc, ddddd, eeeee, fffff, ggggg, hhhhh, iiiii, jjjjj, kkkkk
):
return aaaaa # noqa
3 changes: 3 additions & 0 deletions tests/fixtures/unformatted-line-length.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

def foo(aaaaa, bbbbb, ccccc, ddddd, eeeee, fffff, ggggg, hhhhh, iiiii, jjjjj, kkkkk):
return aaaaa # noqa
Loading