Skip to content

Commit 8923892

Browse files
committed
Start implementing support for "showDocumentation" code action
1 parent 3b5361a commit 8923892

File tree

13 files changed

+876
-474
lines changed

13 files changed

+876
-474
lines changed

robotcode/debugger/protocol.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ def done(f: asyncio.Future[Any]) -> None:
151151
self._logger.exception(ex, exc_info=ex)
152152

153153
for m in iterator:
154-
task = asyncio.create_task(self.handle_message(m))
154+
task = asyncio.create_task(self.handle_message(m), name="handle_message")
155155
task.add_done_callback(done)
156156

157157
@_logger.call
@@ -234,11 +234,13 @@ def handle_request(self, message: Request) -> None:
234234

235235
with self._received_request_lock:
236236
if e is None or not callable(e.method):
237-
result = asyncio.create_task(self.handle_unknown_command(message))
237+
result = asyncio.create_task(self.handle_unknown_command(message), name="handle_unknown_command")
238238
else:
239239
params = self._convert_params(e.method, e.param_type, message.arguments)
240240

241-
result = asyncio.create_task(ensure_coroutine(e.method)(*params[0], **params[1]))
241+
result = asyncio.create_task(
242+
ensure_coroutine(e.method)(*params[0], **params[1]), name=e.method.__name__
243+
)
242244

243245
self._received_request[message.seq] = result
244246

robotcode/jsonrpc2/protocol.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,7 @@ async def handle_request(self, message: JsonRPCRequest) -> None:
695695
ensure_coroutine(e.method)(*params[0], **params[1]),
696696
name=message.method,
697697
)
698+
698699
with self._received_request_lock:
699700
self._received_request[message.id] = ReceivedRequestEntry(task, message, e.cancelable)
700701

robotcode/language_server/common/decorators.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ def decorator(func: _F) -> _F:
2626
return decorator
2727

2828

29+
@runtime_checkable
30+
class HasTriggerCharacters(Protocol):
31+
__trigger_characters__: List[str]
32+
33+
2934
@runtime_checkable
3035
class HasRetriggerCharacters(Protocol):
3136
__retrigger_characters__: str
@@ -39,11 +44,6 @@ def decorator(func: _F) -> _F:
3944
return decorator
4045

4146

42-
@runtime_checkable
43-
class HasTriggerCharacters(Protocol):
44-
__trigger_characters__: List[str]
45-
46-
4747
def all_commit_characters(characters: List[str]) -> Callable[[_F], _F]:
4848
def decorator(func: _F) -> _F:
4949
setattr(func, "__all_commit_characters__", characters)
@@ -57,6 +57,19 @@ class HasAllCommitCharacters(Protocol):
5757
__all_commit_characters__: List[str]
5858

5959

60+
def code_action_kinds(characters: List[str]) -> Callable[[_F], _F]:
61+
def decorator(func: _F) -> _F:
62+
setattr(func, "__code_action_kinds__", characters)
63+
return func
64+
65+
return decorator
66+
67+
68+
@runtime_checkable
69+
class HasCodeActionKinds(Protocol):
70+
__code_action_kinds__: List[str]
71+
72+
6073
def language_id_filter(language_id_or_document: Union[str, TextDocument]) -> Callable[[Any], bool]:
6174
def filter(c: Any) -> bool:
6275
return not isinstance(c, HasLanguageId) or c.__language_id__ == (

robotcode/language_server/common/lsp_types.py

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -397,21 +397,19 @@ class DocumentSymbolClientCapabilities(Model):
397397
label_support: Optional[bool] = None
398398

399399

400-
CodeActionKind = str
401-
402-
403-
class CodeActionKinds(Enum):
400+
class CodeActionKinds:
404401
EMPTY = ""
405402
QUICKFIX = "quickfix"
406403
REFACTOR = "refactor"
407-
REFACTOREXTRACT = "refactor.extract"
408-
REFACTORINLINE = "refactor.inline"
409-
REFACTORREWRITE = "refactor.rewrite"
404+
REFACTOR_EXTRACT = "refactor.extract"
405+
REFACTOR_INLINE = "refactor.inline"
406+
REFACTOR_REWRITE = "refactor.rewrite"
410407
SOURCE = "source"
411-
SOURCEORGANIZEIMPORTS = "source.organizeImports"
408+
SOURCE_ORGANIZE_IMPORTS = "source.organizeImports"
409+
SOURCE_FIX_ALL = "source.fixAll"
412410

413-
def __repr__(self) -> str: # pragma: no cover
414-
return super().__str__()
411+
412+
CodeActionKind = str
415413

416414

417415
@dataclass(repr=False)
@@ -1099,6 +1097,17 @@ class DiagnosticRegistrationOptions(TextDocumentRegistrationOptions, StaticRegis
10991097
pass
11001098

11011099

1100+
@dataclass(repr=False)
1101+
class _CodeActionOptions(Model):
1102+
code_action_kinds: Optional[List[CodeActionKind]] = None
1103+
resolve_provider: Optional[bool] = None
1104+
1105+
1106+
@dataclass(repr=False)
1107+
class CodeActionOptions(WorkDoneProgressOptions, _CodeActionOptions):
1108+
pass
1109+
1110+
11021111
@dataclass(repr=False)
11031112
class ServerCapabilities(Model):
11041113
position_encoding: Optional[PositionEncodingKind] = None
@@ -1112,7 +1121,7 @@ class ServerCapabilities(Model):
11121121
references_provider: Union[bool, ReferenceOptions, None] = None
11131122
document_highlight_provider: Union[bool, DocumentHighlightOptions, None] = None
11141123
document_symbol_provider: Union[bool, DocumentSymbolOptions, None] = None
1115-
# TODO code_action_provider: Union[bool, CodeActionOptions] = None
1124+
code_action_provider: Union[bool, CodeActionOptions, None] = None
11161125
code_lens_provider: Optional[CodeLensOptions] = None
11171126
# TODO document_link_provider: Optional[DocumentLinkOptions] = None
11181127
# TODO color_provider: Union[bool, DocumentColorOptions, DocumentColorRegistrationOptions, None] = None
@@ -2318,3 +2327,44 @@ class WorkspaceDiagnosticReportPartialResult(Model):
23182327
@dataclass(repr=False)
23192328
class WorkspaceDiagnosticReport(Model):
23202329
items: List[WorkspaceDocumentDiagnosticReport]
2330+
2331+
2332+
@dataclass(repr=False)
2333+
class _CodeActionParams(Model):
2334+
text_document: TextDocumentIdentifier
2335+
range: Range
2336+
context: CodeActionContext
2337+
2338+
2339+
@dataclass(repr=False)
2340+
class CodeActionParams(WorkDoneProgressParams, PartialResultParams, _CodeActionParams):
2341+
pass
2342+
2343+
2344+
class CodeActionTriggerKind(IntEnum):
2345+
INVOKED = 1
2346+
AUTOMATIC = 2
2347+
2348+
2349+
@dataclass(repr=False)
2350+
class CodeActionContext(Model):
2351+
diagnostics: List[Diagnostic]
2352+
only: Optional[List[CodeActionKind]] = None
2353+
trigger_kind: Optional[CodeActionTriggerKind] = None
2354+
2355+
2356+
@dataclass(repr=False)
2357+
class CodeActionDisabled:
2358+
reason: str
2359+
2360+
2361+
@dataclass(repr=False)
2362+
class CodeAction:
2363+
title: str
2364+
kind: Optional[CodeActionKind] = None
2365+
diagnostics: Optional[List[Diagnostic]] = None
2366+
is_preferred: Optional[bool] = None
2367+
disabled: Optional[CodeActionDisabled] = None
2368+
edit: Optional[WorkspaceEdit] = None
2369+
command: Optional[Command] = None
2370+
data: Any = None
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
from __future__ import annotations
2+
3+
from asyncio import CancelledError
4+
from itertools import chain
5+
from typing import TYPE_CHECKING, Any, List, Optional, Union, cast
6+
7+
from ....jsonrpc2.protocol import rpc_method
8+
from ....utils.async_tools import async_tasking_event, threaded
9+
from ....utils.logging import LoggingDescriptor
10+
from ..decorators import HasCodeActionKinds, language_id_filter
11+
from ..has_extend_capabilities import HasExtendCapabilities
12+
from ..lsp_types import (
13+
CodeAction,
14+
CodeActionContext,
15+
CodeActionOptions,
16+
CodeActionParams,
17+
Command,
18+
Range,
19+
ServerCapabilities,
20+
TextDocumentIdentifier,
21+
)
22+
from ..text_document import TextDocument
23+
24+
if TYPE_CHECKING:
25+
from ..protocol import LanguageServerProtocol
26+
27+
from .protocol_part import LanguageServerProtocolPart
28+
29+
30+
class CodeActionProtocolPart(LanguageServerProtocolPart, HasExtendCapabilities):
31+
32+
_logger = LoggingDescriptor()
33+
34+
def __init__(self, parent: LanguageServerProtocol) -> None:
35+
super().__init__(parent)
36+
37+
@async_tasking_event
38+
async def collect(
39+
sender, document: TextDocument, range: Range, context: CodeActionContext # NOSONAR
40+
) -> Optional[List[Union[Command, CodeAction]]]:
41+
...
42+
43+
@async_tasking_event
44+
async def resolve(sender, code_action: CodeAction) -> CodeAction: # NOSONAR
45+
...
46+
47+
def extend_capabilities(self, capabilities: ServerCapabilities) -> None:
48+
if len(self.collect):
49+
code_action_kinds = [
50+
k
51+
for k in chain(
52+
*[
53+
cast(HasCodeActionKinds, e).__code_action_kinds__
54+
for e in self.collect
55+
if isinstance(e, HasCodeActionKinds)
56+
]
57+
)
58+
]
59+
60+
capabilities.code_action_provider = CodeActionOptions(
61+
code_action_kinds=code_action_kinds if code_action_kinds else None,
62+
resolve_provider=len(self.resolve) > 0,
63+
)
64+
65+
@rpc_method(name="textDocument/codeAction", param_type=CodeActionParams)
66+
@threaded()
67+
async def _text_document_code_action(
68+
self,
69+
text_document: TextDocumentIdentifier,
70+
range: Range,
71+
context: CodeActionContext,
72+
*args: Any,
73+
**kwargs: Any,
74+
) -> Optional[List[Union[Command, CodeAction]]]:
75+
76+
results: List[Union[Command, CodeAction]] = []
77+
78+
document = await self.parent.documents.get(text_document.uri)
79+
if document is None:
80+
return None
81+
82+
for result in await self.collect(
83+
self,
84+
document,
85+
range,
86+
context,
87+
callback_filter=language_id_filter(document),
88+
):
89+
if isinstance(result, BaseException):
90+
if not isinstance(result, CancelledError):
91+
self._logger.exception(result, exc_info=result)
92+
else:
93+
if result is not None:
94+
results.extend(result)
95+
96+
if len(results) > 0:
97+
return results
98+
99+
return None
100+
101+
@rpc_method(name="textDocument/codeAction/resolve", param_type=CodeAction)
102+
@threaded()
103+
async def _text_document_code_action_resolve(
104+
self,
105+
params: CodeAction,
106+
*args: Any,
107+
**kwargs: Any,
108+
) -> CodeAction:
109+
110+
results: List[CodeAction] = []
111+
112+
for result in await self.resolve(self, params):
113+
if isinstance(result, BaseException):
114+
if not isinstance(result, CancelledError):
115+
self._logger.exception(result, exc_info=result)
116+
else:
117+
if result is not None:
118+
results.append(result)
119+
120+
if len(results) > 0:
121+
if len(results) > 1:
122+
self._logger.warning("More then one resolve result. Use the last one.")
123+
124+
return results[-1]
125+
126+
return params

0 commit comments

Comments
 (0)