Skip to content

Commit f6bfe60

Browse files
authored
Merge branch 'develop' into fixInlineComments
2 parents 7aa2636 + c8e4e99 commit f6bfe60

28 files changed

+251
-82
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
strategy:
1818
fail-fast: false
1919
matrix:
20-
PYTHON_VERSION: ['3.8']
20+
PYTHON_VERSION: ['3.9']
2121
timeout-minutes: 10
2222
steps:
2323
- uses: actions/cache@v1

.github/workflows/static.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@ jobs:
3030
- uses: actions/checkout@v4
3131
- uses: actions/setup-python@v5
3232
with:
33-
# TODO: check with Python 3, but need to fix the
34-
# errors first
35-
python-version: '3.8'
33+
python-version: '3.9'
3634
architecture: 'x64'
3735
- run: python -m pip install --upgrade pip setuptools jsonschema
3836
# If we don't install pycodestyle, pylint will throw an unused-argument error in pylsp/plugins/pycodestyle_lint.py:72

.github/workflows/test-linux.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
strategy:
2525
fail-fast: false
2626
matrix:
27-
PYTHON_VERSION: ['3.10', '3.9', '3.8']
27+
PYTHON_VERSION: ['3.11', '3.10', '3.9']
2828
timeout-minutes: 10
2929
steps:
3030
- uses: actions/cache@v4

.github/workflows/test-mac.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
strategy:
2525
fail-fast: false
2626
matrix:
27-
PYTHON_VERSION: ['3.10', '3.9', '3.8']
27+
PYTHON_VERSION: ['3.11', '3.10', '3.9']
2828
timeout-minutes: 10
2929
steps:
3030
- uses: actions/cache@v4

.github/workflows/test-win.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
strategy:
2525
fail-fast: false
2626
matrix:
27-
PYTHON_VERSION: ['3.10', '3.9', '3.8']
27+
PYTHON_VERSION: ['3.11', '3.10', '3.9']
2828
timeout-minutes: 10
2929
steps:
3030
- uses: actions/cache@v4

CONFIGURATION.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,7 @@ This server can be configured using the `workspace/didChangeConfiguration` metho
7575
| `pylsp.plugins.yapf.enabled` | `boolean` | Enable or disable the plugin. | `true` |
7676
| `pylsp.rope.extensionModules` | `string` | Builtin and c-extension modules that are allowed to be imported and inspected by rope. | `null` |
7777
| `pylsp.rope.ropeFolder` | `array` of unique `string` items | The name of the folder in which rope stores project configurations and data. Pass `null` for not using such a folder at all. | `null` |
78+
| `pylsp.signature.formatter` | `string` (one of: `'black'`, `'ruff'`, `None`) | Formatter to use for reformatting signatures in docstrings. | `"black"` |
79+
| `pylsp.signature.line_length` | `number` | Maximum line length in signatures. | `88` |
7880

7981
This documentation was generated from `pylsp/config/schema.json`. Please do not edit this file directly.

pylsp/__main__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
start_ws_lang_server,
2121
)
2222

23-
LOG_FORMAT = "%(asctime)s {0} - %(levelname)s - %(name)s - %(message)s".format(
23+
LOG_FORMAT = "%(asctime)s {} - %(levelname)s - %(name)s - %(message)s".format(
2424
time.localtime().tm_zone
2525
)
2626

@@ -98,7 +98,7 @@ def _configure_logger(verbose=0, log_config=None, log_file=None) -> None:
9898
root_logger = logging.root
9999

100100
if log_config:
101-
with open(log_config, "r", encoding="utf-8") as f:
101+
with open(log_config, encoding="utf-8") as f:
102102
logging.config.dictConfig(json.load(f))
103103
else:
104104
formatter = logging.Formatter(LOG_FORMAT)

pylsp/_utils.py

Lines changed: 99 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
import os
88
import pathlib
99
import re
10+
import subprocess
11+
import sys
1012
import threading
1113
import time
12-
from typing import List, Optional
14+
from typing import Optional
1315

1416
import docstring_to_markdown
1517
import jedi
@@ -57,7 +59,7 @@ def run():
5759

5860

5961
def throttle(seconds=1):
60-
"""Throttles calls to a function evey `seconds` seconds."""
62+
"""Throttles calls to a function every `seconds` seconds."""
6163

6264
def decorator(func):
6365
@functools.wraps(func)
@@ -78,7 +80,7 @@ def find_parents(root, path, names):
7880
7981
Args:
8082
path (str): The file path to start searching up from.
81-
names (List[str]): The file/directory names to look for.
83+
names (list[str]): The file/directory names to look for.
8284
root (str): The directory at which to stop recursing upwards.
8385
8486
Note:
@@ -198,7 +200,7 @@ def wrap_signature(signature):
198200
SERVER_SUPPORTED_MARKUP_KINDS = {"markdown", "plaintext"}
199201

200202

201-
def choose_markup_kind(client_supported_markup_kinds: List[str]):
203+
def choose_markup_kind(client_supported_markup_kinds: list[str]):
202204
"""Choose a markup kind supported by both client and the server.
203205
204206
This gives priority to the markup kinds provided earlier on the client preference list.
@@ -209,8 +211,96 @@ def choose_markup_kind(client_supported_markup_kinds: List[str]):
209211
return "markdown"
210212

211213

214+
class Formatter:
215+
command: list[str]
216+
217+
@property
218+
def is_installed(self) -> bool:
219+
"""Returns whether formatter is available"""
220+
if not hasattr(self, "_is_installed"):
221+
self._is_installed = self._is_available_via_cli()
222+
return self._is_installed
223+
224+
def format(self, code: str, line_length: int) -> str:
225+
"""Formats code"""
226+
return subprocess.check_output(
227+
[
228+
sys.executable,
229+
"-m",
230+
*self.command,
231+
"--line-length",
232+
str(line_length),
233+
"-",
234+
],
235+
input=code,
236+
text=True,
237+
).strip()
238+
239+
def _is_available_via_cli(self) -> bool:
240+
try:
241+
subprocess.check_output(
242+
[
243+
sys.executable,
244+
"-m",
245+
*self.command,
246+
"--help",
247+
],
248+
)
249+
return True
250+
except subprocess.CalledProcessError:
251+
return False
252+
253+
254+
class RuffFormatter(Formatter):
255+
command = ["ruff", "format"]
256+
257+
258+
class BlackFormatter(Formatter):
259+
command = ["black"]
260+
261+
262+
formatters = {"ruff": RuffFormatter(), "black": BlackFormatter()}
263+
264+
265+
def format_signature(signature: str, config: dict, signature_formatter: str) -> str:
266+
"""Formats signature using ruff or black if either is available."""
267+
as_func = f"def {signature.strip()}:\n pass"
268+
line_length = config.get("line_length", 88)
269+
formatter = formatters[signature_formatter]
270+
if formatter.is_installed:
271+
try:
272+
return (
273+
formatter.format(as_func, line_length=line_length)
274+
.removeprefix("def ")
275+
.removesuffix(":\n pass")
276+
)
277+
except subprocess.CalledProcessError as e:
278+
log.warning("Signature formatter failed %s", e)
279+
else:
280+
log.warning(
281+
"Formatter %s was requested but it does not appear to be installed",
282+
signature_formatter,
283+
)
284+
return signature
285+
286+
287+
def convert_signatures_to_markdown(signatures: list[str], config: dict) -> str:
288+
signature_formatter = config.get("formatter", "black")
289+
if signature_formatter:
290+
signatures = [
291+
format_signature(
292+
signature, signature_formatter=signature_formatter, config=config
293+
)
294+
for signature in signatures
295+
]
296+
return wrap_signature("\n".join(signatures))
297+
298+
212299
def format_docstring(
213-
contents: str, markup_kind: str, signatures: Optional[List[str]] = None
300+
contents: str,
301+
markup_kind: str,
302+
signatures: Optional[list[str]] = None,
303+
signature_config: Optional[dict] = None,
214304
):
215305
"""Transform the provided docstring into a MarkupContent object.
216306
@@ -232,7 +322,10 @@ def format_docstring(
232322
value = escape_markdown(contents)
233323

234324
if signatures:
235-
value = wrap_signature("\n".join(signatures)) + "\n\n" + value
325+
wrapped_signatures = convert_signatures_to_markdown(
326+
signatures, config=signature_config or {}
327+
)
328+
value = wrapped_signatures + "\n\n" + value
236329

237330
return {"kind": "markdown", "value": value}
238331
value = contents

pylsp/config/config.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33

44
import logging
55
import sys
6+
from collections.abc import Mapping, Sequence
67
from functools import lru_cache
7-
from typing import List, Mapping, Sequence, Union
8+
from typing import Union
89

910
import pluggy
1011
from pluggy._hooks import HookImpl
@@ -32,7 +33,7 @@ def _hookexec(
3233
methods: Sequence[HookImpl],
3334
kwargs: Mapping[str, object],
3435
firstresult: bool,
35-
) -> Union[object, List[object]]:
36+
) -> Union[object, list[object]]:
3637
# called from all hookcaller instances.
3738
# enable_tracing will set its own wrapping function at self._inner_hookexec
3839
try:

pylsp/config/schema.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,24 @@
511511
},
512512
"uniqueItems": true,
513513
"description": "The name of the folder in which rope stores project configurations and data. Pass `null` for not using such a folder at all."
514+
},
515+
"pylsp.signature.formatter": {
516+
"type": [
517+
"string",
518+
"null"
519+
],
520+
"enum": [
521+
"black",
522+
"ruff",
523+
null
524+
],
525+
"default": "black",
526+
"description": "Formatter to use for reformatting signatures in docstrings."
527+
},
528+
"pylsp.signature.line_length": {
529+
"type": "number",
530+
"default": 88,
531+
"description": "Maximum line length in signatures."
514532
}
515533
}
516534
}

0 commit comments

Comments
 (0)