Skip to content

Commit 833a32c

Browse files
observerwCopilot
andauthored
feat: implement Inlay Hint capabilities (#12)
* feat: implement textDocument/inlayHint and workspace/inlayHint/refresh capabilities * Update src/lsp_client/capability/request/inlay_hint.py Co-authored-by: Copilot <[email protected]> * Update src/lsp_client/capability/request/inlay_hint.py Co-authored-by: Copilot <[email protected]> * fix: update type --------- Co-authored-by: Copilot <[email protected]>
1 parent 5cb6d5f commit 833a32c

File tree

4 files changed

+222
-0
lines changed

4 files changed

+222
-0
lines changed

src/lsp_client/capability/request/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .document_symbol import WithRequestDocumentSymbol
1010
from .hover import WithRequestHover
1111
from .implementation import WithRequestImplementation
12+
from .inlay_hint import WithRequestInlayHint
1213
from .inline_value import WithRequestInlineValue
1314
from .pull_diagnostic import WithRequestPullDiagnostic
1415
from .reference import WithRequestReferences
@@ -25,6 +26,7 @@
2526
WithRequestDocumentSymbol,
2627
WithRequestHover,
2728
WithRequestImplementation,
29+
WithRequestInlayHint,
2830
WithRequestInlineValue,
2931
WithRequestPullDiagnostic,
3032
WithRequestReferences,
@@ -42,6 +44,7 @@
4244
"WithRequestDocumentSymbol",
4345
"WithRequestHover",
4446
"WithRequestImplementation",
47+
"WithRequestInlayHint",
4548
"WithRequestInlineValue",
4649
"WithRequestPullDiagnostic",
4750
"WithRequestReferences",
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Sequence
4+
from typing import Protocol, override, runtime_checkable
5+
6+
import asyncer
7+
8+
from lsp_client.jsonrpc.id import jsonrpc_uuid
9+
from lsp_client.protocol import (
10+
CapabilityClientProtocol,
11+
TextDocumentCapabilityProtocol,
12+
)
13+
from lsp_client.utils.types import AnyPath, Range, lsp_type
14+
15+
16+
@runtime_checkable
17+
class WithRequestInlayHint(
18+
TextDocumentCapabilityProtocol,
19+
CapabilityClientProtocol,
20+
Protocol,
21+
):
22+
"""
23+
`textDocument/inlayHint` - https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_inlayHint
24+
"""
25+
26+
@override
27+
@classmethod
28+
def methods(cls) -> Sequence[str]:
29+
return (
30+
lsp_type.TEXT_DOCUMENT_INLAY_HINT,
31+
lsp_type.INLAY_HINT_RESOLVE,
32+
)
33+
34+
@override
35+
@classmethod
36+
def register_text_document_capability(
37+
cls, cap: lsp_type.TextDocumentClientCapabilities
38+
) -> None:
39+
cap.inlay_hint = lsp_type.InlayHintClientCapabilities(
40+
dynamic_registration=True,
41+
resolve_support=lsp_type.ClientInlayHintResolveOptions(
42+
properties=[
43+
"tooltip",
44+
"location",
45+
"label.tooltip",
46+
"label.location",
47+
"textEdits",
48+
]
49+
),
50+
)
51+
52+
@override
53+
@classmethod
54+
def check_server_capability(
55+
cls,
56+
cap: lsp_type.ServerCapabilities,
57+
info: lsp_type.ServerInfo | None,
58+
) -> None:
59+
super().check_server_capability(cap, info)
60+
assert cap.inlay_hint_provider
61+
62+
def get_inlay_hint_label(
63+
self, hint: lsp_type.InlayHint | lsp_type.InlayHintLabelPart
64+
) -> str:
65+
"""Extract the text label from an InlayHint or InlayHintLabelPart."""
66+
match hint:
67+
case lsp_type.InlayHintLabelPart(value=value):
68+
return value
69+
case lsp_type.InlayHint(label=str() as label):
70+
return label
71+
case lsp_type.InlayHint(label=parts):
72+
return "".join(part.value for part in parts)
73+
case _:
74+
raise TypeError(f"Unexpected type for inlay hint label: {type(hint)}")
75+
76+
async def request_inlay_hint(
77+
self,
78+
file_path: AnyPath,
79+
range: Range,
80+
*,
81+
resolve: bool = False,
82+
) -> Sequence[lsp_type.InlayHint] | None:
83+
"""
84+
Request inlay hints for a given file and range.
85+
86+
This sends a `textDocument/inlayHint` request for the specified document range.
87+
If ``resolve`` is True, each returned inlay hint is further resolved using
88+
:meth:`request_inlay_hint_resolve` to populate optional properties such as
89+
tooltip, locations, and text edits.
90+
91+
:param file_path: Path to the file for which inlay hints are requested.
92+
:param range: LSP range within the document to compute inlay hints for.
93+
:param resolve: Whether to resolve each returned inlay hint for additional
94+
details supported by the server.
95+
:return: A sequence of :class:`lsp_type.InlayHint` instances if the server
96+
returns hints, or ``None`` if no hints are provided.
97+
"""
98+
hints = await self.file_request(
99+
lsp_type.InlayHintRequest(
100+
id=jsonrpc_uuid(),
101+
params=lsp_type.InlayHintParams(
102+
text_document=lsp_type.TextDocumentIdentifier(
103+
uri=self.as_uri(file_path)
104+
),
105+
range=range,
106+
),
107+
),
108+
schema=lsp_type.InlayHintResponse,
109+
file_paths=[file_path],
110+
)
111+
112+
if resolve and hints:
113+
async with asyncer.create_task_group() as tg:
114+
tasks = [
115+
tg.soonify(self.request_inlay_hint_resolve)(hint) for hint in hints
116+
]
117+
return [task.value for task in tasks]
118+
119+
return hints
120+
121+
async def request_inlay_hint_resolve(
122+
self,
123+
hint: lsp_type.InlayHint,
124+
) -> lsp_type.InlayHint:
125+
"""
126+
Resolve additional details for a previously returned inlay hint.
127+
128+
This sends an LSP ``inlayHint/resolve`` request to the server for the
129+
given ``hint``. Servers may initially return inlay hints with only a
130+
subset of properties populated and require a subsequent resolve
131+
request to fill in optional fields such as tooltips, locations, or
132+
text edits.
133+
134+
:param hint: An :class:`lsp_type.InlayHint` instance obtained from a
135+
prior ``textDocument/inlayHint`` request that should be fully
136+
resolved by the server.
137+
:return: A new :class:`lsp_type.InlayHint` containing any additional
138+
data supplied by the server.
139+
"""
140+
return await self.request(
141+
lsp_type.InlayHintResolveRequest(
142+
id=jsonrpc_uuid(),
143+
params=hint,
144+
),
145+
schema=lsp_type.InlayHintResolveResponse,
146+
)

src/lsp_client/capability/server_request/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,23 @@
22

33
from typing import Final
44

5+
from .inlay_hint_refresh import WithRespondInlayHintRefresh
56
from .show_document_request import WithRespondShowDocumentRequest
67
from .show_message_request import WithRespondShowMessageRequest
78
from .workspace_configuration import WithRespondConfigurationRequest
89
from .workspace_folders import WithRespondWorkspaceFoldersRequest
910

1011
capabilities: Final = (
1112
WithRespondConfigurationRequest,
13+
WithRespondInlayHintRefresh,
1214
WithRespondShowDocumentRequest,
1315
WithRespondShowMessageRequest,
1416
WithRespondWorkspaceFoldersRequest,
1517
)
1618

1719
__all__ = [
1820
"WithRespondConfigurationRequest",
21+
"WithRespondInlayHintRefresh",
1922
"WithRespondShowDocumentRequest",
2023
"WithRespondShowMessageRequest",
2124
"WithRespondWorkspaceFoldersRequest",
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Sequence
4+
from typing import Protocol, override, runtime_checkable
5+
6+
from lsp_client.protocol import (
7+
CapabilityClientProtocol,
8+
ServerRequestHook,
9+
ServerRequestHookProtocol,
10+
ServerRequestHookRegistry,
11+
WorkspaceCapabilityProtocol,
12+
)
13+
from lsp_client.utils.types import lsp_type
14+
15+
16+
@runtime_checkable
17+
class WithRespondInlayHintRefresh(
18+
WorkspaceCapabilityProtocol,
19+
ServerRequestHookProtocol,
20+
CapabilityClientProtocol,
21+
Protocol,
22+
):
23+
"""
24+
`workspace/inlayHint/refresh` - https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_inlayHint_refresh
25+
"""
26+
27+
@override
28+
@classmethod
29+
def methods(cls) -> Sequence[str]:
30+
return (lsp_type.WORKSPACE_INLAY_HINT_REFRESH,)
31+
32+
@override
33+
@classmethod
34+
def register_workspace_capability(
35+
cls, cap: lsp_type.WorkspaceClientCapabilities
36+
) -> None:
37+
super().register_workspace_capability(cap)
38+
cap.inlay_hint = lsp_type.InlayHintWorkspaceClientCapabilities(
39+
refresh_support=True
40+
)
41+
42+
@override
43+
@classmethod
44+
def check_server_capability(
45+
cls,
46+
cap: lsp_type.ServerCapabilities,
47+
info: lsp_type.ServerInfo | None,
48+
) -> None:
49+
super().check_server_capability(cap, info)
50+
51+
async def respond_inlay_hint_refresh(
52+
self, req: lsp_type.InlayHintRefreshRequest
53+
) -> lsp_type.InlayHintRefreshResponse:
54+
return lsp_type.InlayHintRefreshResponse(
55+
id=req.id,
56+
result=None,
57+
)
58+
59+
@override
60+
def register_server_request_hooks(
61+
self, registry: ServerRequestHookRegistry
62+
) -> None:
63+
super().register_server_request_hooks(registry)
64+
registry.register(
65+
lsp_type.WORKSPACE_INLAY_HINT_REFRESH,
66+
ServerRequestHook(
67+
cls=lsp_type.InlayHintRefreshRequest,
68+
execute=self.respond_inlay_hint_refresh,
69+
),
70+
)

0 commit comments

Comments
 (0)