feat(inlay-hints): add configurable parameter name hints
Adds configurable inlay hints for TCL procedures and merges signatures from built-ins and workspace files. The feature supports parameterNames and suppressWhenArgumentMatchesName and respects an optional range filter and current-file priority. - Introduces built-in and custom inlay hint builders - Honors inlayHints parameterNames and suppression options - Adds tests validating hints, ranges, and priority rules
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
## Unreleased
|
||||
|
||||
- Prevent truncated TCL inlay hints and add configurable parameter hint modes
|
||||
- Add inlay hints for built-in NX procedures, variadic arguments, and visible ranges
|
||||
- Add inlay hint documentation and navigation to custom procedure definitions
|
||||
- Add signature help for custom TCL procedures and built-in NX/MOM procedures
|
||||
- Clean stale TCL indexes on close, delete, and rename operations
|
||||
- Make background parsing and index updates thread-safe
|
||||
|
||||
@@ -31,6 +31,12 @@ The extension can be configured through VS Code settings:
|
||||
- `nx-post-support.interpreter` - Specify custom Python interpreter path for the language server
|
||||
- `nx-post-support.formatter` - Enable/disable the TCL formatter (default: false)
|
||||
- `nx-post-support.inlayHint` - Enable/disable inlay Hints (default: true)
|
||||
- `nx-post-support.inlayHints.parameterNames` - Show parameter names for `all`, only `literals`, or `none` (default: `all`)
|
||||
- `nx-post-support.inlayHints.suppressWhenArgumentMatchesName` - Hide redundant hints such as `value:` before `$value` (default: true)
|
||||
|
||||
TCL files default to unlimited inlay hint length so that VS Code does not
|
||||
truncate later parameter names on a line. An explicit user setting for
|
||||
`editor.inlayHints.maximumLength` still takes precedence.
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -18,6 +18,12 @@ export interface ISettings {
|
||||
interpreter: string[]
|
||||
importStrategy: string
|
||||
showNotifications: string
|
||||
formatter: boolean
|
||||
inlayHint: boolean
|
||||
inlayHints: {
|
||||
parameterNames: "all" | "literals" | "none"
|
||||
suppressWhenArgumentMatchesName: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export function getExtensionSettings(
|
||||
@@ -80,7 +86,13 @@ export async function getWorkspaceSettings(
|
||||
importStrategy: config.get<string>(`importStrategy`) ?? "useBundled",
|
||||
showNotifications: config.get<string>(`showNotifications`) ?? "off",
|
||||
formatter: config.get<boolean>(`formatter`) ?? true,
|
||||
inlayHint: config.get<boolean>(`inlayHint`) ?? true
|
||||
inlayHint: config.get<boolean>(`inlayHint`) ?? true,
|
||||
inlayHints: {
|
||||
parameterNames:
|
||||
config.get<"all" | "literals" | "none">(`inlayHints.parameterNames`) ?? "all",
|
||||
suppressWhenArgumentMatchesName:
|
||||
config.get<boolean>(`inlayHints.suppressWhenArgumentMatchesName`) ?? true
|
||||
}
|
||||
}
|
||||
return workspaceSetting
|
||||
}
|
||||
@@ -113,7 +125,13 @@ export async function getGlobalSettings(
|
||||
importStrategy: getGlobalValue<string>(config, "importStrategy", "useBundled"),
|
||||
showNotifications: getGlobalValue<string>(config, "showNotifications", "off"),
|
||||
formatter: config.get<boolean>(`formatter`) ?? true,
|
||||
inlayHint: config.get<boolean>(`inlayHint`) ?? true
|
||||
inlayHint: config.get<boolean>(`inlayHint`) ?? true,
|
||||
inlayHints: {
|
||||
parameterNames:
|
||||
config.get<"all" | "literals" | "none">(`inlayHints.parameterNames`) ?? "all",
|
||||
suppressWhenArgumentMatchesName:
|
||||
config.get<boolean>(`inlayHints.suppressWhenArgumentMatchesName`) ?? true
|
||||
}
|
||||
}
|
||||
return setting
|
||||
}
|
||||
@@ -129,7 +147,9 @@ export function checkIfConfigurationChanged(
|
||||
`${namespace}.importStrategy`,
|
||||
`${namespace}.showNotifications`,
|
||||
`${namespace}.formatter`,
|
||||
`${namespace}.inlayHint`
|
||||
`${namespace}.inlayHint`,
|
||||
`${namespace}.inlayHints.parameterNames`,
|
||||
`${namespace}.inlayHints.suppressWhenArgumentMatchesName`
|
||||
]
|
||||
const changed = settings.map((s) => e.affectsConfiguration(s))
|
||||
return changed.includes(true)
|
||||
|
||||
@@ -96,6 +96,26 @@
|
||||
"default": true,
|
||||
"description": "Use the Inlay Hints in from `NX Postprocessor Support`"
|
||||
},
|
||||
"nx-post-support.inlayHints.parameterNames": {
|
||||
"type": "string",
|
||||
"default": "all",
|
||||
"enum": [
|
||||
"all",
|
||||
"literals",
|
||||
"none"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Show parameter name hints for all arguments.",
|
||||
"Show parameter name hints only for literal arguments.",
|
||||
"Do not show parameter name hints."
|
||||
],
|
||||
"description": "Controls which TCL procedure arguments receive parameter name hints."
|
||||
},
|
||||
"nx-post-support.inlayHints.suppressWhenArgumentMatchesName": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Hide a parameter hint when a variable argument already has the same name, for example `output` in `my_proc $output`."
|
||||
},
|
||||
"nx-post-support.importStrategy": {
|
||||
"default": "useBundled",
|
||||
"description": "Defines where `NX Postprocessor Support` is imported from.",
|
||||
@@ -120,6 +140,11 @@
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"configurationDefaults": {
|
||||
"[tcl]": {
|
||||
"editor.inlayHints.maximumLength": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -44,7 +44,11 @@ from pygls import uris, workspace
|
||||
from common.load_data import standard_items
|
||||
from tools.folding_ranges import build_folding_ranges
|
||||
from tools.semantic_tokens import _Highlighter, TOKEN_TYPES, TokenModifier
|
||||
from tools.inlay_hint import InlayHintGenerator
|
||||
from tools.inlay_hint import (
|
||||
InlayHintGenerator,
|
||||
build_builtin_inlay_signatures,
|
||||
build_custom_inlay_signatures,
|
||||
)
|
||||
from tools.navigation import (
|
||||
SymbolIdentity,
|
||||
definition_identities,
|
||||
@@ -333,20 +337,43 @@ def document_symbols(params: lsp.DocumentSymbolParams):
|
||||
|
||||
@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_INLAY_HINT)
|
||||
def inlay_hints(params: lsp.InlayHintParams):
|
||||
if not GLOBAL_SETTINGS.get("inlayHint", False):
|
||||
return []
|
||||
document = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
|
||||
settings = _get_settings_by_document(document)
|
||||
if not settings.get("inlayHint", False):
|
||||
return []
|
||||
|
||||
inlay_settings = settings.get("inlayHints", {})
|
||||
parameter_names = inlay_settings.get("parameterNames", "all")
|
||||
if parameter_names == "none":
|
||||
return []
|
||||
|
||||
# Reuse cached AST
|
||||
tree = LSP_SERVER.get_tree(document)
|
||||
|
||||
# Merge proc signatures across files and traverse once
|
||||
merged_signatures = {}
|
||||
_, proc_signatures, _ = LSP_SERVER.index_snapshot()
|
||||
for sigs in proc_signatures.values():
|
||||
merged_signatures.update(sigs)
|
||||
# Built-in NX procedures are the fallback. Workspace procedures replace them,
|
||||
# and a declaration in the current file wins over duplicate workspace names.
|
||||
signatures = build_builtin_inlay_signatures(
|
||||
standard_items.json_data.get("MOM_procs", [])
|
||||
)
|
||||
_, proc_signatures, proc_docs = LSP_SERVER.index_snapshot()
|
||||
signatures.update(
|
||||
build_custom_inlay_signatures(
|
||||
proc_signatures,
|
||||
proc_docs,
|
||||
LSP_SERVER.navigation_snapshot(),
|
||||
document.path,
|
||||
)
|
||||
)
|
||||
|
||||
generator = InlayHintGenerator(merged_signatures)
|
||||
generator = InlayHintGenerator(
|
||||
document.source,
|
||||
signatures,
|
||||
requested_range=params.range,
|
||||
parameter_names=parameter_names,
|
||||
suppress_when_argument_matches_name=inlay_settings.get(
|
||||
"suppressWhenArgumentMatchesName", True
|
||||
),
|
||||
)
|
||||
tree.accept(generator, recurse=True)
|
||||
return generator.hints
|
||||
|
||||
@@ -769,6 +796,13 @@ def _get_global_defaults():
|
||||
"showNotifications": GLOBAL_SETTINGS.get("showNotifications", "off"),
|
||||
"formatter": GLOBAL_SETTINGS.get("formatter", True),
|
||||
"inlayHint": GLOBAL_SETTINGS.get("inlayHint", True),
|
||||
"inlayHints": GLOBAL_SETTINGS.get(
|
||||
"inlayHints",
|
||||
{
|
||||
"parameterNames": "all",
|
||||
"suppressWhenArgumentMatchesName": True,
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
||||
+212
-12
@@ -1,29 +1,229 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
import lsprotocol.types as lsp
|
||||
from tclint.syntax_tree import Visitor, Command
|
||||
from tclint.syntax_tree import Command, VarSub, Visitor
|
||||
|
||||
from tools.navigation import FileSymbolIndex
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class InlayHintParameter:
|
||||
name: str
|
||||
documentation: str | None = None
|
||||
variadic: bool = False
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class InlayHintSignature:
|
||||
parameters: tuple[InlayHintParameter, ...]
|
||||
display_label: str
|
||||
documentation: str | None = None
|
||||
location: lsp.Location | None = None
|
||||
|
||||
|
||||
def _normalized_path(path: str) -> str:
|
||||
return os.path.normcase(os.path.abspath(path))
|
||||
|
||||
|
||||
def _definition_location(
|
||||
index: FileSymbolIndex | None, proc_name: str
|
||||
) -> lsp.Location | None:
|
||||
if index is None:
|
||||
return None
|
||||
|
||||
basename = proc_name.removeprefix("::").rsplit("::", 1)[-1]
|
||||
for occurrence in reversed(index.occurrences):
|
||||
if (
|
||||
occurrence.is_definition
|
||||
and occurrence.identity.kind == "proc"
|
||||
and occurrence.placeholder == basename
|
||||
):
|
||||
return lsp.Location(uri=index.uri, range=occurrence.range)
|
||||
return None
|
||||
|
||||
|
||||
def build_custom_inlay_signatures(
|
||||
signatures_by_path: dict[str, dict[str, list[str]]],
|
||||
docs_by_path: dict[str, dict[str, str]],
|
||||
indexes_by_path: dict[str, FileSymbolIndex],
|
||||
current_path: str,
|
||||
) -> dict[str, InlayHintSignature]:
|
||||
"""Merge workspace signatures deterministically, preferring the current file."""
|
||||
current_normalized = _normalized_path(current_path)
|
||||
paths = sorted(
|
||||
signatures_by_path, key=lambda path: _normalized_path(path).casefold()
|
||||
)
|
||||
paths.sort(key=lambda path: _normalized_path(path) == current_normalized)
|
||||
|
||||
result: dict[str, InlayHintSignature] = {}
|
||||
for path in paths:
|
||||
docs = docs_by_path.get(path, {})
|
||||
index = indexes_by_path.get(path)
|
||||
for proc_name, parameter_names in signatures_by_path[path].items():
|
||||
parameters = tuple(
|
||||
InlayHintParameter(
|
||||
name=parameter_name,
|
||||
variadic=(
|
||||
parameter_name == "args"
|
||||
and parameter_index == len(parameter_names) - 1
|
||||
),
|
||||
)
|
||||
for parameter_index, parameter_name in enumerate(parameter_names)
|
||||
)
|
||||
result[proc_name] = InlayHintSignature(
|
||||
parameters=parameters,
|
||||
display_label=" ".join([proc_name, *parameter_names]),
|
||||
documentation=docs.get(proc_name),
|
||||
location=_definition_location(index, proc_name),
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def _is_builtin_variadic(item: dict[str, Any], parameter_name: str, index: int) -> bool:
|
||||
parameters = item.get("parameters", [])
|
||||
if index != len(parameters) - 1:
|
||||
return False
|
||||
if "..." in parameter_name or "…" in parameter_name:
|
||||
return True
|
||||
|
||||
format_label = item.get("format", "")
|
||||
return (
|
||||
f"<{parameter_name}>+" in format_label or f"[{parameter_name}]+" in format_label
|
||||
)
|
||||
|
||||
|
||||
def _builtin_parameter_label(parameter_name: str) -> str:
|
||||
if "..." not in parameter_name and "…" not in parameter_name:
|
||||
return parameter_name.strip("<>[]")
|
||||
|
||||
first_name = parameter_name.split()[0].strip("<>[]")
|
||||
return re.sub(r"(?:_?1)$", "", first_name) or first_name
|
||||
|
||||
|
||||
def build_builtin_inlay_signatures(
|
||||
items: list[dict[str, Any]],
|
||||
) -> dict[str, InlayHintSignature]:
|
||||
result = {}
|
||||
for item in items:
|
||||
proc_name = item.get("label")
|
||||
if not proc_name:
|
||||
continue
|
||||
parameters = tuple(
|
||||
InlayHintParameter(
|
||||
name=_builtin_parameter_label(parameter.get("name", "")),
|
||||
documentation=parameter.get("desc") or None,
|
||||
variadic=_is_builtin_variadic(
|
||||
item, parameter.get("name", ""), parameter_index
|
||||
),
|
||||
)
|
||||
for parameter_index, parameter in enumerate(item.get("parameters", []))
|
||||
if parameter.get("name")
|
||||
)
|
||||
result[proc_name] = InlayHintSignature(
|
||||
parameters=parameters,
|
||||
display_label=item.get("format") or proc_name,
|
||||
documentation=item.get("description") or None,
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def _position_in_range(
|
||||
position: lsp.Position, requested_range: lsp.Range | None
|
||||
) -> bool:
|
||||
if requested_range is None:
|
||||
return True
|
||||
value = (position.line, position.character)
|
||||
start = (requested_range.start.line, requested_range.start.character)
|
||||
end = (requested_range.end.line, requested_range.end.character)
|
||||
return start <= value < end
|
||||
|
||||
|
||||
class InlayHintGenerator(Visitor):
|
||||
def __init__(self, proc_signatures):
|
||||
def __init__(
|
||||
self,
|
||||
source: str,
|
||||
proc_signatures: dict[str, InlayHintSignature],
|
||||
*,
|
||||
requested_range: lsp.Range | None = None,
|
||||
parameter_names: str = "all",
|
||||
suppress_when_argument_matches_name: bool = True,
|
||||
):
|
||||
self.source_lines = source.splitlines()
|
||||
self.proc_signatures = proc_signatures
|
||||
self.hints = []
|
||||
self.requested_range = requested_range
|
||||
self.parameter_names = parameter_names
|
||||
self.suppress_when_argument_matches_name = suppress_when_argument_matches_name
|
||||
self.hints: list[lsp.InlayHint] = []
|
||||
|
||||
def _position(self, line: int, column: int) -> lsp.Position:
|
||||
line_index = line - 1
|
||||
character_index = column - 1
|
||||
if 0 <= line_index < len(self.source_lines):
|
||||
prefix = self.source_lines[line_index][:character_index]
|
||||
character_index = len(prefix.encode("utf-16-le")) // 2
|
||||
return lsp.Position(line=line_index, character=character_index)
|
||||
|
||||
@staticmethod
|
||||
def _parameter_for_argument(
|
||||
signature: InlayHintSignature, argument_index: int
|
||||
) -> InlayHintParameter | None:
|
||||
if argument_index < len(signature.parameters):
|
||||
return signature.parameters[argument_index]
|
||||
if signature.parameters and signature.parameters[-1].variadic:
|
||||
return signature.parameters[-1]
|
||||
return None
|
||||
|
||||
def _should_show(self, argument, parameter: InlayHintParameter) -> bool:
|
||||
if self.parameter_names == "none":
|
||||
return False
|
||||
if self.parameter_names == "literals" and isinstance(argument, VarSub):
|
||||
return False
|
||||
if not self.suppress_when_argument_matches_name or not isinstance(
|
||||
argument, VarSub
|
||||
):
|
||||
return True
|
||||
return getattr(argument, "value", None) != parameter.name
|
||||
|
||||
@staticmethod
|
||||
def _tooltip(signature: InlayHintSignature) -> lsp.MarkupContent:
|
||||
value = f"`{signature.display_label}`"
|
||||
if signature.documentation:
|
||||
value += f"\n\n{signature.documentation}"
|
||||
return lsp.MarkupContent(kind=lsp.MarkupKind.Markdown, value=value)
|
||||
|
||||
def visit_command(self, command: Command):
|
||||
name = getattr(command.routine, "contents", None)
|
||||
if name not in self.proc_signatures:
|
||||
signature = self.proc_signatures.get(name)
|
||||
if signature is None or self.parameter_names == "none":
|
||||
return
|
||||
|
||||
param_names = self.proc_signatures[name]
|
||||
for idx, arg in enumerate(command.args):
|
||||
if idx >= len(param_names):
|
||||
for argument_index, argument in enumerate(command.args):
|
||||
parameter = self._parameter_for_argument(signature, argument_index)
|
||||
if parameter is None:
|
||||
break
|
||||
param_name = param_names[idx]
|
||||
if not argument.pos or not self._should_show(argument, parameter):
|
||||
continue
|
||||
|
||||
if arg.pos:
|
||||
line, col = arg.pos
|
||||
line, column = argument.pos
|
||||
position = self._position(line, column)
|
||||
if not _position_in_range(position, self.requested_range):
|
||||
continue
|
||||
|
||||
label = lsp.InlayHintLabelPart(
|
||||
value=f"{parameter.name}:",
|
||||
tooltip=parameter.documentation,
|
||||
location=signature.location,
|
||||
)
|
||||
self.hints.append(
|
||||
lsp.InlayHint(
|
||||
position=lsp.Position(line=line - 1, character=col - 1),
|
||||
label=f"{param_name}:",
|
||||
position=position,
|
||||
label=[label],
|
||||
kind=lsp.InlayHintKind.Parameter,
|
||||
tooltip=self._tooltip(signature),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
THIS_DIR = Path(__file__).parent
|
||||
SRC_DIR = THIS_DIR.parent.parent / "src"
|
||||
if str(SRC_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(SRC_DIR))
|
||||
|
||||
import lsprotocol.types as lsp # type: ignore
|
||||
|
||||
from tools.inlay_hint import (
|
||||
InlayHintGenerator,
|
||||
InlayHintParameter,
|
||||
InlayHintSignature,
|
||||
build_builtin_inlay_signatures,
|
||||
build_custom_inlay_signatures,
|
||||
)
|
||||
from tools.navigation import build_file_symbol_index
|
||||
from tools.parser import CustomParser
|
||||
|
||||
|
||||
def _signature(*names: str, variadic: bool = False) -> InlayHintSignature:
|
||||
parameters = tuple(
|
||||
InlayHintParameter(
|
||||
name=name,
|
||||
variadic=variadic and index == len(names) - 1,
|
||||
)
|
||||
for index, name in enumerate(names)
|
||||
)
|
||||
return InlayHintSignature(
|
||||
parameters=parameters,
|
||||
display_label=" ".join(["test_proc", *names]),
|
||||
)
|
||||
|
||||
|
||||
def _generate(
|
||||
source: str,
|
||||
signatures: dict[str, InlayHintSignature],
|
||||
**options,
|
||||
) -> list[lsp.InlayHint]:
|
||||
tree = CustomParser().parse(source)
|
||||
generator = InlayHintGenerator(source, signatures, **options)
|
||||
tree.accept(generator, recurse=True)
|
||||
return generator.hints
|
||||
|
||||
|
||||
def _labels(hints: list[lsp.InlayHint]) -> list[str]:
|
||||
labels = []
|
||||
for hint in hints:
|
||||
if isinstance(hint.label, str):
|
||||
labels.append(hint.label)
|
||||
else:
|
||||
labels.append("".join(part.value for part in hint.label))
|
||||
return labels
|
||||
|
||||
|
||||
def test_many_parameters_are_returned_without_server_side_truncation():
|
||||
names = tuple(f"parameter_{index}" for index in range(10))
|
||||
source = "test_proc " + " ".join(str(index) for index in range(10))
|
||||
|
||||
hints = _generate(source, {"test_proc": _signature(*names)})
|
||||
|
||||
assert _labels(hints) == [f"{name}:" for name in names]
|
||||
assert all("…" not in label and "..." not in label for label in _labels(hints))
|
||||
|
||||
|
||||
def test_only_hints_inside_requested_range_are_returned():
|
||||
source = "test_proc first\nset spacer 1\ntest_proc second"
|
||||
requested_range = lsp.Range(
|
||||
start=lsp.Position(line=2, character=0),
|
||||
end=lsp.Position(line=3, character=0),
|
||||
)
|
||||
|
||||
hints = _generate(
|
||||
source,
|
||||
{"test_proc": _signature("value")},
|
||||
requested_range=requested_range,
|
||||
)
|
||||
|
||||
assert len(hints) == 1
|
||||
assert hints[0].position.line == 2
|
||||
|
||||
|
||||
def test_matching_variable_name_can_be_suppressed():
|
||||
source = "test_proc $value $other"
|
||||
signature = _signature("value", "result")
|
||||
|
||||
suppressed = _generate(source, {"test_proc": signature})
|
||||
visible = _generate(
|
||||
source,
|
||||
{"test_proc": signature},
|
||||
suppress_when_argument_matches_name=False,
|
||||
)
|
||||
|
||||
assert _labels(suppressed) == ["result:"]
|
||||
assert _labels(visible) == ["value:", "result:"]
|
||||
|
||||
|
||||
def test_literal_mode_hides_variable_argument_hints():
|
||||
source = 'test_proc $value "literal"'
|
||||
|
||||
hints = _generate(
|
||||
source,
|
||||
{"test_proc": _signature("first", "second")},
|
||||
parameter_names="literals",
|
||||
)
|
||||
|
||||
assert _labels(hints) == ["second:"]
|
||||
|
||||
|
||||
def test_variadic_parameter_labels_every_remaining_argument():
|
||||
hints = _generate(
|
||||
"test_proc first second third fourth",
|
||||
{"test_proc": _signature("required", "args", variadic=True)},
|
||||
)
|
||||
|
||||
assert _labels(hints) == ["required:", "args:", "args:", "args:"]
|
||||
|
||||
|
||||
def test_builtin_signature_has_variadic_hints_and_parameter_documentation():
|
||||
signatures = build_builtin_inlay_signatures(
|
||||
[
|
||||
{
|
||||
"label": "MOM_force",
|
||||
"description": "Controls address output.",
|
||||
"format": "MOM_force <mode> <address_1 ... address_n>",
|
||||
"parameters": [
|
||||
{"name": "mode", "desc": "Output mode."},
|
||||
{
|
||||
"name": "address_1 ... address_n",
|
||||
"desc": "Output addresses.",
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
hints = _generate("MOM_force Always X Y", signatures)
|
||||
|
||||
assert _labels(hints) == ["mode:", "address:", "address:"]
|
||||
assert hints[1].label[0].tooltip == "Output addresses." # type: ignore[index]
|
||||
assert hints[0].tooltip.value.endswith("Controls address output.") # type: ignore[union-attr]
|
||||
|
||||
|
||||
def test_current_file_signature_and_definition_location_take_priority(tmp_path: Path):
|
||||
other_path = tmp_path / "other.tcl"
|
||||
current_path = tmp_path / "current.tcl"
|
||||
other_source = "proc shared {from_other} { return $from_other }"
|
||||
current_source = "proc shared {from_current args} { return $from_current }"
|
||||
parser = CustomParser()
|
||||
other_tree = parser.parse(other_source)
|
||||
current_tree = parser.parse(current_source)
|
||||
indexes = {
|
||||
str(other_path): build_file_symbol_index(
|
||||
str(other_path), other_path.as_uri(), other_tree
|
||||
),
|
||||
str(current_path): build_file_symbol_index(
|
||||
str(current_path), current_path.as_uri(), current_tree
|
||||
),
|
||||
}
|
||||
|
||||
signatures = build_custom_inlay_signatures(
|
||||
{
|
||||
str(current_path): {"shared": ["from_current", "args"]},
|
||||
str(other_path): {"shared": ["from_other"]},
|
||||
},
|
||||
{
|
||||
str(current_path): {"shared": "Current documentation."},
|
||||
str(other_path): {"shared": "Other documentation."},
|
||||
},
|
||||
indexes,
|
||||
str(current_path),
|
||||
)
|
||||
|
||||
signature = signatures["shared"]
|
||||
assert [parameter.name for parameter in signature.parameters] == [
|
||||
"from_current",
|
||||
"args",
|
||||
]
|
||||
assert signature.parameters[-1].variadic
|
||||
assert signature.documentation == "Current documentation."
|
||||
assert signature.location is not None
|
||||
assert signature.location.uri == current_path.as_uri()
|
||||
|
||||
|
||||
def test_positions_use_lsp_utf16_offsets():
|
||||
source = 'test_proc "😀" second'
|
||||
|
||||
hints = _generate(
|
||||
source,
|
||||
{"test_proc": _signature("first", "second")},
|
||||
)
|
||||
|
||||
assert hints[1].position.character == source.index("second") + 1
|
||||
Reference in New Issue
Block a user