chore(lsprotocol): migrate to 2025.0.0 and cleanup artifacts

The changes align the project with the 2025.0.0 lsprotocol
release, removing the old backport and updating type hints
in the protocol hooks to use Sequence where appropriate. The
dist-info and packaging metadata for older lsprotocol
versions are replaced with the new 2025.0.0 artifacts.

- Remove exceptiongroup backport used on Python <3.11
- Use Sequence instead of List in LS protocol hooks
- Replace old dist-info with 2025.0.0 metadata
This commit is contained in:
Christoph Brandau
2026-09-03 08:39:12 +02:00
parent a0a0d38fe5
commit 53ebc5d055
101 changed files with 13838 additions and 7624 deletions
+8 -18
View File
@@ -1,5 +1,6 @@
# file generated by setuptools-scm
# file generated by vcs-versioning
# don't change, don't track in version control
from __future__ import annotations
__all__ = [
"__version__",
@@ -10,25 +11,14 @@ __all__ = [
"commit_id",
]
TYPE_CHECKING = False
if TYPE_CHECKING:
from typing import Tuple
from typing import Union
VERSION_TUPLE = Tuple[Union[int, str], ...]
COMMIT_ID = Union[str, None]
else:
VERSION_TUPLE = object
COMMIT_ID = object
version: str
__version__: str
__version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE
commit_id: COMMIT_ID
__commit_id__: COMMIT_ID
__version_tuple__: tuple[int | str, ...]
version_tuple: tuple[int | str, ...]
commit_id: str | None
__commit_id__: str | None
__version__ = version = '0.8.0'
__version_tuple__ = version_tuple = (0, 8, 0)
__version__ = version = '0.9.0'
__version_tuple__ = version_tuple = (0, 9, 0)
__commit_id__ = commit_id = None
+10 -1
View File
@@ -573,7 +573,16 @@ def _package_ifneeded(args, parser):
def _proc(args, parser):
if len(args) != 3:
required_args = ["name", "args", "body"]
if len(args) < len(required_args):
missing_args = ", ".join(required_args[len(args) :])
raise CommandArgError(
"missing required"
f" argument{'s' if len(args) < len(required_args) - 1 else ''} for proc:"
f" {missing_args}"
)
if len(args) > len(required_args):
raise CommandArgError(f"wrong # of args to proc: got {len(args)}, expected 3")
# Parse args as list, then iterate over each item to parse arg specifier lists and
+102 -9
View File
@@ -2,7 +2,8 @@
from __future__ import annotations
from collections.abc import Callable
from collections.abc import Callable, Iterable
from difflib import get_close_matches
from typing import TYPE_CHECKING, Optional
from tclint.syntax_tree import ArgExpansion, BareWord, BracedWord, Node, QuotedWord
@@ -18,6 +19,33 @@ class CommandArgError(Exception):
pass
def get_suggestion(value: str | None, candidates: Iterable[str]) -> Optional[str]:
if value is None:
return None
unique_candidates = sorted({candidate for candidate in candidates if candidate})
if value in unique_candidates:
unique_candidates.remove(value)
if not unique_candidates:
return None
cutoff = 0.85 if len(value) <= 3 else 0.75
matches = get_close_matches(value, unique_candidates, n=1, cutoff=cutoff)
if not matches:
return None
return matches[0]
def _did_you_mean_suffix(value: str | None, candidates: Iterable[str]) -> str:
suggestion = get_suggestion(value, candidates)
if suggestion is None:
return ""
return f"; did you mean {suggestion}?"
def arg_count(args: list[Node], parser: Parser) -> tuple[int, bool]:
"""Returns the number of arguments in args, taking {*} into account.
@@ -83,6 +111,9 @@ def check_count(command, min=None, max=None):
def eval(args: list[Node], parser: Parser, command: str) -> list[Node]:
if len(args) == 1:
return [parser.parse_script(args[0])]
if len(args) > 1 and any(isinstance(arg, (QuotedWord, BracedWord)) for arg in args):
# Slightly odd restriction, but our syntax tree doesn't have a great way
# to handle this case. We require each command argument to correspond to
@@ -174,10 +205,19 @@ def check_arg_spec(
mapping = map_positionals(positionals, arg_spec["positionals"], command)
args = list(args)
for arg_i, map_to_spec in zip(positional_args, mapping):
arg = args[arg_i]
if _positional_has_type("script", arg_spec, map_to_spec):
args[arg_i] = parser.parse_script(args[arg_i])
args[arg_i] = parser.parse_script(arg)
elif _positional_has_type("expression", arg_spec, map_to_spec):
args[arg_i] = parser.parse_expression(args[arg_i])
args[arg_i] = parser.parse_expression(arg)
elif len(map_to_spec) == 1:
positional_spec = arg_spec["positionals"][map_to_spec[0]]
_validate_value(
arg,
positional_spec["value"],
f"{command} {positional_spec['name']}",
)
return args
@@ -201,12 +241,18 @@ def dispatch_subcommands(
if "" in spec:
return check_command(command, args, parser, spec[""])
valid_subcommands = [name for name in spec.keys() if name != ""]
if subcommand is not None:
msg = f"invalid subcommand for {command}: got {subcommand}"
suggestion = _did_you_mean_suffix(subcommand, valid_subcommands)
else:
msg = f"no subcommand provided for {command}"
suggestion = ""
raise CommandArgError(f"{msg}, expected one of {', '.join(spec.keys())}")
raise CommandArgError(
f"{msg}, expected one of {', '.join(valid_subcommands)}{suggestion}"
)
def map_switches(
@@ -250,17 +296,23 @@ def map_switches(
continue
if contents in switches:
if contents in mapped and not switches[contents]["repeated"]:
switch_spec = switches[contents]
if contents in mapped and not switch_spec["repeated"]:
raise CommandArgError(
f"duplicate argument for {command_name}: {contents}"
)
if switches[contents]["value"]:
if switch_spec["value"]:
arg_i += 1
if arg_i > len(args):
expected = _switch_value_description(switch_spec)
raise CommandArgError(
f"invalid arguments for {command_name}: expected value after"
f" {contents}"
f"invalid arguments for {command_name}: expected"
f" {expected} after {contents}"
)
_validate_value(
args[arg_i - 1], switch_spec["value"], f"{command_name} {contents}"
)
mapped.add(contents)
continue
@@ -281,11 +333,52 @@ def map_switches(
f" {', '.join(prefix_matches)}"
)
raise CommandArgError(f"unrecognized argument for {command_name}: {contents}")
raise CommandArgError(
f"unrecognized argument for {command_name}: {contents}"
f"{_did_you_mean_suffix(contents, switches.keys())}"
)
return mapped, positional_args
def _switch_value_description(switch_spec: dict) -> str:
metavar = switch_spec.get("metavar")
if metavar is not None:
return metavar
value_spec = switch_spec.get("value")
if value_spec is None:
return "value"
value_type = value_spec.get("type")
if value_type == "int":
return "int value"
return "value"
def _validate_value(arg: Node, value_spec: dict | None, context: str) -> None:
if value_spec is None:
return
contents = arg.contents
if contents is None:
return
value_type = value_spec.get("type")
if value_type in {"any", "variadic", "script", "expression"}:
return
if value_type == "int":
try:
int(contents, 0)
return
except ValueError as error:
raise CommandArgError(
f"invalid value for {context}: got {contents}, expected {value_type}"
) from error
def map_positionals(
args: list[Node], spec: list[dict], command_name: str
) -> list[list[int]]:
+11 -7
View File
@@ -2,6 +2,15 @@ from collections.abc import Callable
from voluptuous import Optional, Or, Schema, Self
_switch_value = Or({"type": "any"}, {"type": "int"}, None)
_positional_value = Or(
{"type": "any"},
{"type": "int"},
{"type": "variadic"},
{"type": "script"},
{"type": "expression"},
)
# Need to define this as a Schema with required=True to ensure that this requirement
# persists through the Or in the main schema definition.
_command_args = Schema(
@@ -10,19 +19,14 @@ _command_args = Schema(
{
"name": str,
"required": bool,
"value": Or(
{"type": "any"},
{"type": "variadic"},
{"type": "script"},
{"type": "expression"},
),
"value": _positional_value,
}
],
Optional("switches", default={}): {
Optional(str): {
"required": bool,
"repeated": bool,
"value": Or({"type": "any"}, None),
"value": _switch_value,
Optional("metavar"): str,
}
},
+64 -17
View File
@@ -69,13 +69,15 @@ class Formatter:
assert len(debug_char) == 1
if space is None:
space = self.opts.indent
elif isinstance(space, int):
space = space * " "
if self.opts.debug_whitespace:
# Enable this to return a string of debug_char.
return len(space) * debug_char
return space
def get_spaces_in_braces(self, space: tuple[int, int]):
spaces_in_braces = self.space("A", " ") if self.opts.spaces_in_braces else ""
spaces_in_braces = self.space("A", 1) if self.opts.spaces_in_braces else ""
if not self.opts.balanced_spaces_in_braces:
# No balancing.
return spaces_in_braces
@@ -88,7 +90,7 @@ class Formatter:
if space[0] != -1 and space[1] == -1:
# we've got empty braces. Keep "{}" and "{ }" as is, but normalize
# more than one space to a single space.
return min(space[0], 1) * self.space("B", " ")
return min(space[0], 1) * self.space("B", 1)
# Normalize more than one space to a single space.
before = min(space[0], 1)
@@ -101,7 +103,7 @@ class Formatter:
# Check that we have a balanced expression.
assert before == after
# Keep either "{1}" or "{ 1 }".
return before * self.space("C", " ")
return before * self.space("C", 1)
def _brace(self, lines: list[str], space: tuple[int, int]) -> list[str]:
"""Format content between braces.
@@ -307,6 +309,10 @@ class Formatter:
def format_script(self, script: Script, should_indent=True) -> list[str]:
lines = self.format_script_contents(script)
if not script.braced:
# Script came from a non-braced word argument (e.g. plugin called
# parse_script on a BareWord). Don't wrap in braces.
return lines
if script.pos[0] == script.end_pos[0]:
space_before = -1
space_after = -1
@@ -327,7 +333,7 @@ class Formatter:
and isinstance(script.children[0], Comment)
and script.pos[0] == script.children[0].pos[0]
):
open_brace += self.space("D", " ") + lines[0]
open_brace += self.space("D", 1) + lines[0]
lines = lines[1:]
if should_indent:
@@ -353,14 +359,26 @@ class Formatter:
child_lines = self.format(child)
if last_line == child.pos[0]:
formatted[-1] += self.space("F", " ")
if self.opts.emacs and child_lines[0][-1] == "\\":
base_indent = (len(formatted[-1])) * self.space("G", " ")
else:
base_indent = ""
formatted[-1] += self.space("F", 1)
base_indent = ""
if self.opts.emacs:
if child_lines[0][-1] == "\\":
base_indent = (len(formatted[-1])) * self.space("G", 1)
elif (isinstance(child, BracedExpression)) and child_lines[
-1
] == "}":
child_lines[1:-1] = self._indent(
child_lines[1:-1], self.space("V", len(formatted[-1]))
)
elif (isinstance(child, BracedExpression)) and child_lines[-1][
-1
] == "}":
child_lines[1:] = self._indent(
child_lines[1:], self.space("W", len(formatted[-1]))
)
formatted[-1] += child_lines[0]
else:
formatted[-1] += self.space("H", " ") + "\\"
formatted[-1] += self.space("H", 1) + "\\"
formatted.append(self.space("I") + child_lines[0])
hanging_indent = True
@@ -383,13 +401,19 @@ class Formatter:
formatted = []
contents = self.format_script_contents(command_sub)
if len(command_sub.children) > 1 and len(contents) > 1:
formatted.append("[")
formatted.extend(self._indent(contents, self.space("K")))
formatted.append("]")
if self.opts.emacs and command_sub.pos[0] == command_sub.children[0].pos[0]:
formatted = contents
formatted[0] = "[" + formatted[0]
formatted[-1] = formatted[-1] + "]"
formatted[1:] = self._indent(formatted[1:], self.space("U", 1))
else:
formatted.append("[")
formatted.extend(self._indent(contents, self.space("K")))
formatted.append("]")
else:
formatted.append("[" + contents[0])
if self.opts.emacs:
indent = self.space("L", " ")
indent = self.space("L", 1)
else:
indent = ""
formatted.extend(self._indent(contents[1:], indent))
@@ -460,7 +484,7 @@ class Formatter:
for child in list_node.children:
if last_line is not None:
if last_line == child.pos[0]:
contents[-1] += self.space("M", " ")
contents[-1] += self.space("M", 1)
else:
newlines = child.pos[0] - last_line
newlines = min(newlines, 3)
@@ -516,6 +540,29 @@ class Formatter:
space_after = expr.end_pos[1] - expr.children[-1].end_pos[1] - 1
return self._brace(formatted, (space_before, space_after))
if self.opts.emacs:
pre = []
post = []
indent_first = 0
indent_last = len(formatted)
if expr.pos[0] == expr.children[0].pos[0]:
formatted[0] = "{" + formatted[0]
indent_first = 1
else:
pre = ["{"]
if expr.end_pos[0] == expr.children[-1].end_pos[0]:
formatted[-1] = formatted[-1] + "}"
else:
post = ["}"]
formatted = (
pre
+ formatted[0:indent_first]
+ self._indent(formatted[indent_first:indent_last], self.space("X", 1))
+ formatted[indent_last:]
+ post
)
return formatted
return ["{"] + self._indent(formatted, self.space("P")) + ["}"]
def format_paren_expression(self, expr) -> list[str]:
@@ -556,7 +603,7 @@ class Formatter:
if last.end_pos[0] != next.pos[0]:
formatted.extend(lines)
else:
formatted[-1] += self.space("R", " ")
formatted[-1] += self.space("R", 1)
formatted[-1] += lines[0]
formatted.extend(lines[1:])
last = next
@@ -585,7 +632,7 @@ class Formatter:
formatted.extend(lines)
else:
if i > 0:
formatted[-1] += self.space("S", " ")
formatted[-1] += self.space("S", 1)
formatted[-1] += lines[0]
formatted.extend(lines[1:])
last = child
+3
View File
@@ -13,6 +13,9 @@ class SymbolTable:
def add_proc_definition(self, command: Command) -> None:
"""Add definition of procedure"""
# command holds the "proc" keyword, so the proc name is 1st argument
if len(command.args) == 0:
return
proc_name_node = command.args[0]
proc_name = proc_name_node.contents
if not proc_name:
+1 -1
View File
@@ -253,7 +253,7 @@ class Node:
class Script(Node):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# hack for spaces-in-braces check
# Used by formatter.
self.braced = False
def accept(self, visitor, recurse=False):