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
}

pylsp/plugins/_resolvers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def resolve(self, completion):
8888
def format_label(completion, sig):
8989
if sig and completion.type in ("function", "method"):
9090
params = ", ".join(param.name for param in sig[0].params)
91-
label = "{}({})".format(completion.name, params)
91+
label = f"{completion.name}({params})"
9292
return label
9393
return completion.name
9494

@@ -115,7 +115,7 @@ def format_snippet(completion, sig):
115115
snippet_completion["insertTextFormat"] = lsp.InsertTextFormat.Snippet
116116
snippet = completion.name + "("
117117
for i, param in enumerate(positional_args):
118-
snippet += "${%s:%s}" % (i + 1, param.name)
118+
snippet += "${{{}:{}}}".format(i + 1, param.name)
119119
if i < len(positional_args) - 1:
120120
snippet += ", "
121121
snippet += ")$0"

pylsp/plugins/_rope_task_handle.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from __future__ import annotations
22

33
import logging
4-
from typing import Callable, ContextManager, List, Optional, Sequence
4+
from collections.abc import Sequence
5+
from typing import Callable, ContextManager
56

67
from rope.base.taskhandle import BaseJobSet, BaseTaskHandle
78

@@ -19,13 +20,13 @@ class PylspJobSet(BaseJobSet):
1920
_report_iter: ContextManager
2021
job_name: str = ""
2122

22-
def __init__(self, count: Optional[int], report_iter: ContextManager) -> None:
23+
def __init__(self, count: int | None, report_iter: ContextManager) -> None:
2324
if count is not None:
2425
self.count = count
2526
self._reporter = report_iter.__enter__()
2627
self._report_iter = report_iter
2728

28-
def started_job(self, name: Optional[str]) -> None:
29+
def started_job(self, name: str | None) -> None:
2930
if name:
3031
self.job_name = name
3132

@@ -42,7 +43,7 @@ def finished_job(self) -> None:
4243
def check_status(self) -> None:
4344
pass
4445

45-
def get_percent_done(self) -> Optional[float]:
46+
def get_percent_done(self) -> float | None:
4647
if self.count == 0:
4748
return 0
4849
return (self.done / self.count) * 100
@@ -66,8 +67,8 @@ def _report(self) -> None:
6667

6768
class PylspTaskHandle(BaseTaskHandle):
6869
name: str
69-
observers: List
70-
job_sets: List[PylspJobSet]
70+
observers: list
71+
job_sets: list[PylspJobSet]
7172
stopped: bool
7273
workspace: Workspace
7374
_report: Callable[[str, str], None]
@@ -77,7 +78,7 @@ def __init__(self, workspace: Workspace) -> None:
7778
self.job_sets = []
7879
self.observers = []
7980

80-
def create_jobset(self, name="JobSet", count: Optional[int] = None):
81+
def create_jobset(self, name="JobSet", count: int | None = None):
8182
report_iter = self.workspace.report_progress(
8283
name, None, None, skip_token_initialization=True
8384
)
@@ -89,7 +90,7 @@ def create_jobset(self, name="JobSet", count: Optional[int] = None):
8990
def stop(self) -> None:
9091
pass
9192

92-
def current_jobset(self) -> Optional[BaseJobSet]:
93+
def current_jobset(self) -> BaseJobSet | None:
9394
pass
9495

9596
def add_observer(self) -> None:

pylsp/plugins/definition.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
import logging
6-
from typing import TYPE_CHECKING, Any, Dict, List
6+
from typing import TYPE_CHECKING, Any
77

88
import jedi
99

@@ -23,7 +23,7 @@
2323

2424

2525
def _resolve_definition(
26-
maybe_defn: Name, script: Script, settings: Dict[str, Any]
26+
maybe_defn: Name, script: Script, settings: dict[str, Any]
2727
) -> Name:
2828
for _ in range(MAX_JEDI_GOTO_HOPS):
2929
if maybe_defn.is_definition() or maybe_defn.module_path != script.path:
@@ -43,8 +43,8 @@ def _resolve_definition(
4343

4444
@hookimpl
4545
def pylsp_definitions(
46-
config: Config, document: Document, position: Dict[str, int]
47-
) -> List[Dict[str, Any]]:
46+
config: Config, document: Document, position: dict[str, int]
47+
) -> list[dict[str, Any]]:
4848
settings = config.plugin_settings("jedi_definition")
4949
code_position = _utils.position_to_jedi_linecolumn(document, position)
5050
script = document.jedi_script(use_document_path=True)

pylsp/plugins/flake8_lint.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def run_flake8(flake8_executable, args, document, source):
135135
cmd = [flake8_executable]
136136
cmd.extend(args)
137137
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, **popen_kwargs)
138-
except IOError:
138+
except OSError:
139139
log.debug(
140140
"Can't execute %s. Trying with '%s -m flake8'",
141141
flake8_executable,
@@ -165,9 +165,9 @@ def build_args(options):
165165
arg = "--{}={}".format(arg_name, ",".join(arg_val))
166166
elif isinstance(arg_val, bool):
167167
if arg_val:
168-
arg = "--{}".format(arg_name)
168+
arg = f"--{arg_name}"
169169
else:
170-
arg = "--{}={}".format(arg_name, arg_val)
170+
arg = f"--{arg_name}={arg_val}"
171171
args.append(arg)
172172
return args
173173

0 commit comments

Comments
 (0)