Skip to content

Commit

Permalink
refactor: move some diagnostics code from langserver package to robot…
Browse files Browse the repository at this point in the history
… package
  • Loading branch information
d-biehl committed Dec 28, 2023
1 parent 3798c5e commit 4b3e65c
Show file tree
Hide file tree
Showing 24 changed files with 192 additions and 182 deletions.
12 changes: 8 additions & 4 deletions packages/core/src/robotcode/core/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
Optional,
Type,
TypeVar,
Union,
cast,
)

Expand Down Expand Up @@ -74,12 +75,15 @@ def _notify(
*__args: _TParams.args,
callback_filter: Optional[Callable[[Callable[..., Any]], bool]] = None,
**__kwargs: _TParams.kwargs,
) -> Iterator[_TResult]:
) -> Iterator[Union[_TResult, BaseException]]:
for method in filter(
lambda x: callback_filter(x) if callback_filter is not None else True,
set(self),
):
yield method(*__args, **__kwargs)
try:
yield method(*__args, **__kwargs)
except BaseException as e:
yield e


class EventIterator(EventResultIteratorBase[_TParams, _TResult]):
Expand All @@ -88,7 +92,7 @@ def __call__(
*__args: _TParams.args,
callback_filter: Optional[Callable[[Callable[..., Any]], bool]] = None,
**__kwargs: _TParams.kwargs,
) -> Iterator[_TResult]:
) -> Iterator[Union[_TResult, BaseException]]:
return self._notify(*__args, callback_filter=callback_filter, **__kwargs)


Expand All @@ -98,7 +102,7 @@ def __call__(
*__args: _TParams.args,
callback_filter: Optional[Callable[[Callable[..., Any]], bool]] = None,
**__kwargs: _TParams.kwargs,
) -> List[_TResult]:
) -> List[Union[_TResult, BaseException]]:
return list(self._notify(*__args, callback_filter=callback_filter, **__kwargs))


Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/robotcode/core/utils/threading.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import inspect
from typing import Any, Callable, TypeVar

_F = TypeVar("_F", bound=Callable[..., Any])

__THREADED_MARKER = "__threaded__"


def threaded(enabled: bool = True) -> Callable[[_F], _F]:
def decorator(func: _F) -> _F:
setattr(func, "__threaded__", enabled)
setattr(func, __THREADED_MARKER, enabled)
return func

return decorator


def is_threaded_callable(func: Callable[..., Any]) -> bool:
return getattr(func, "__threaded__", False)
return getattr(func, __THREADED_MARKER, False) or inspect.ismethod(func) and getattr(func, __THREADED_MARKER, False)
124 changes: 65 additions & 59 deletions packages/jsonrpc2/src/robotcode/jsonrpc2/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import json
import re
import threading
import time
import weakref
from abc import ABC, abstractmethod
from collections import OrderedDict
Expand Down Expand Up @@ -443,6 +444,7 @@ def __init__(self) -> None:
self._sended_request_count = 0
self._received_request: OrderedDict[Union[str, int, None], ReceivedRequestEntry] = OrderedDict()
self._received_request_lock = threading.RLock()
self._signature_cache: Dict[Callable[..., Any], inspect.Signature] = {}

@staticmethod
def _generate_json_rpc_messages_from_dict(
Expand Down Expand Up @@ -640,9 +642,8 @@ async def handle_error(self, message: JsonRPCError) -> None:
if not entry.future.done():
entry.future.set_exception(e)

@staticmethod
def _convert_params(
callable: Callable[..., Any], params_type: Optional[Type[Any]], params: Any
self, callable: Callable[..., Any], params_type: Optional[Type[Any]], params: Any
) -> Tuple[List[Any], Dict[str, Any]]:
if params is None:
return [], {}
Expand All @@ -655,7 +656,12 @@ def _convert_params(
# try to convert the dict to correct type
converted_params = from_dict(params, params_type)

signature = inspect.signature(callable)
# get the signature of the callable
if callable in self._signature_cache:
signature = self._signature_cache[callable]
else:
signature = inspect.signature(callable)
self._signature_cache[callable] = signature

has_var_kw = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in signature.parameters.values())

Expand Down Expand Up @@ -705,62 +711,66 @@ def _convert_params(
return args, kw_args

async def handle_request(self, message: JsonRPCRequest) -> None:
e = self.registry.get_entry(message.method)
start = time.monotonic_ns()
try:
e = self.registry.get_entry(message.method)

if e is None or not callable(e.method):
self.send_error(
JsonRPCErrors.METHOD_NOT_FOUND,
f"Unknown method: {message.method}",
id=message.id,
)
return
if e is None or not callable(e.method):
self.send_error(
JsonRPCErrors.METHOD_NOT_FOUND,
f"Unknown method: {message.method}",
id=message.id,
)
return

params = self._convert_params(e.method, e.param_type, message.params)
params = self._convert_params(e.method, e.param_type, message.params)

if not e.is_coroutine:
self.send_response(message.id, e.method(*params[0], **params[1]))
else:
if is_threaded_callable(e.method) or inspect.ismethod(e.method) and is_threaded_callable(e.method.__func__):
task = run_coroutine_in_thread(
ensure_coroutine(cast(Callable[..., Any], e.method)), *params[0], **params[1]
)
if not e.is_coroutine:
self.send_response(message.id, e.method(*params[0], **params[1]))
else:
task = create_sub_task(
ensure_coroutine(e.method)(*params[0], **params[1]),
name=message.method,
)
if is_threaded_callable(e.method):
task = run_coroutine_in_thread(
ensure_coroutine(cast(Callable[..., Any], e.method)), *params[0], **params[1]
)
else:
task = create_sub_task(
ensure_coroutine(e.method)(*params[0], **params[1]),
name=message.method,
)

with self._received_request_lock:
self._received_request[message.id] = ReceivedRequestEntry(task, message, e.cancelable)

def done(t: asyncio.Future[Any]) -> None:
try:
if not t.cancelled():
ex = t.exception()
if ex is not None:
self.__logger.exception(ex, exc_info=ex)
raise JsonRPCErrorException(
JsonRPCErrors.INTERNAL_ERROR, f"{type(ex).__name__}: {ex}"
) from ex

self.send_response(message.id, t.result())
except asyncio.CancelledError:
self.__logger.debug(lambda: f"request message {message!r} canceled")
self.send_error(JsonRPCErrors.REQUEST_CANCELLED, "Request canceled.", id=message.id)
except (SystemExit, KeyboardInterrupt):
raise
except JsonRPCErrorException as e:
self.send_error(e.code, e.message or f"{type(e).__name__}: {e}", id=message.id, data=e.data)
except BaseException as e:
self.__logger.exception(e)
self.send_error(JsonRPCErrors.INTERNAL_ERROR, f"{type(e).__name__}: {e}", id=message.id)
finally:
with self._received_request_lock:
self._received_request.pop(message.id, None)

task.add_done_callback(done)

with self._received_request_lock:
self._received_request[message.id] = ReceivedRequestEntry(task, message, e.cancelable)

def done(t: asyncio.Future[Any]) -> None:
try:
if not t.cancelled():
ex = t.exception()
if ex is not None:
self.__logger.exception(ex, exc_info=ex)
raise JsonRPCErrorException(
JsonRPCErrors.INTERNAL_ERROR, f"{type(ex).__name__}: {ex}"
) from ex

self.send_response(message.id, t.result())
except asyncio.CancelledError:
self.__logger.debug(lambda: f"request message {message!r} canceled")
self.send_error(JsonRPCErrors.REQUEST_CANCELLED, "Request canceled.", id=message.id)
except (SystemExit, KeyboardInterrupt):
raise
except JsonRPCErrorException as e:
self.send_error(e.code, e.message or f"{type(e).__name__}: {e}", id=message.id, data=e.data)
except BaseException as e:
self.__logger.exception(e)
self.send_error(JsonRPCErrors.INTERNAL_ERROR, f"{type(e).__name__}: {e}", id=message.id)
finally:
with self._received_request_lock:
self._received_request.pop(message.id, None)

task.add_done_callback(done)

await task
await task
finally:
self.__logger.debug(lambda: f"request message {message!r} done in {time.monotonic_ns() - start}ns")

def cancel_request(self, id: Union[int, str, None]) -> None:
with self._received_request_lock:
Expand Down Expand Up @@ -788,11 +798,7 @@ async def handle_notification(self, message: JsonRPCNotification) -> None:
if not e.is_coroutine:
e.method(*params[0], **params[1])
else:
if (
is_threaded_callable(e.method)
or inspect.ismethod(e.method)
and is_threaded_callable(e.method.__func__)
):
if is_threaded_callable(e.method):
task = run_coroutine_in_thread(
ensure_coroutine(cast(Callable[..., Any], e.method)), *params[0], **params[1]
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ def read_document_text(self, uri: Uri, language_id: Union[str, Callable[[Any], b
for e in self.on_read_document_text(
self, uri, callback_filter=language_id_filter(language_id) if isinstance(language_id, str) else language_id
):
if isinstance(e, BaseException):
raise RuntimeError(f"Can't read document text from {uri}: {e}") from e

if e is not None:
return self._normalize_line_endings(e)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
from __future__ import annotations

from asyncio import CancelledError
from typing import TYPE_CHECKING, Any, Final, List, Optional

from robotcode.core.async_tools import async_tasking_event
from robotcode.core.event import event
from robotcode.core.lsp.types import (
Hover,
HoverOptions,
Expand Down Expand Up @@ -31,11 +28,11 @@
class HoverProtocolPart(LanguageServerProtocolPart, HasExtendCapabilities):
_logger: Final = LoggingDescriptor()

def __init__(self, parent: LanguageServerProtocol) -> None:
def __init__(self, parent: "LanguageServerProtocol") -> None:
super().__init__(parent)

@async_tasking_event
async def collect(sender, document: TextDocument, position: Position) -> Optional[Hover]: # NOSONAR
@event
def collect(sender, document: TextDocument, position: Position) -> Optional[Hover]:
...

def extend_capabilities(self, capabilities: ServerCapabilities) -> None:
Expand All @@ -44,7 +41,7 @@ def extend_capabilities(self, capabilities: ServerCapabilities) -> None:

@rpc_method(name="textDocument/hover", param_type=HoverParams)
@threaded()
async def _text_document_hover(
def _text_document_hover(
self,
text_document: TextDocumentIdentifier,
position: Position,
Expand All @@ -57,15 +54,14 @@ async def _text_document_hover(
if document is None:
return None

for result in await self.collect(
for result in self.collect(
self,
document,
document.position_from_utf16(position),
callback_filter=language_id_filter(document),
):
if isinstance(result, BaseException):
if not isinstance(result, CancelledError):
self._logger.exception(result, exc_info=result)
self._logger.exception(result, exc_info=result)
else:
if result is not None:
results.append(result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@
Range,
)
from robotcode.core.uri import Uri
from robotcode.robot.diagnostics.entities import (
ArgumentDefinition,
CommandLineVariableDefinition,
EnvironmentVariableDefinition,
LibraryEntry,
LocalVariableDefinition,
VariableDefinition,
VariableDefinitionType,
VariableNotFoundDefinition,
)
from robotcode.robot.diagnostics.library_doc import KeywordDoc, is_embedded_keyword
from robotcode.robot.utils import get_robot_version
from robotcode.robot.utils.ast import (
is_not_variable_token,
Expand All @@ -48,18 +59,7 @@
)
from robotcode.robot.utils.visitor import Visitor

from .entities import (
ArgumentDefinition,
CommandLineVariableDefinition,
EnvironmentVariableDefinition,
LibraryEntry,
LocalVariableDefinition,
VariableDefinition,
VariableDefinitionType,
VariableNotFoundDefinition,
)
from .errors import DIAGNOSTICS_SOURCE_NAME, Error
from .library_doc import KeywordDoc, is_embedded_keyword
from .model_helper import ModelHelperMixin
from .namespace import (
KeywordFinder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,8 @@
from robotcode.language_server.common.parts.workspace import FileWatcherEntry, Workspace
from robotcode.language_server.common.text_document import TextDocument
from robotcode.language_server.robotframework.configuration import CacheSaveLocation, RobotCodeConfig
from robotcode.robot.utils import get_robot_version, get_robot_version_str
from robotcode.robot.utils.robot_path import find_file_ex

from ...__version__ import __version__
from .entities import CommandLineVariableDefinition, VariableDefinition
from .library_doc import (
from robotcode.robot.diagnostics.entities import CommandLineVariableDefinition, VariableDefinition
from robotcode.robot.diagnostics.library_doc import (
ROBOT_LIBRARY_PACKAGE,
CompleteResult,
LibraryDoc,
Expand All @@ -64,6 +60,10 @@
resolve_args,
resolve_variable,
)
from robotcode.robot.utils import get_robot_version, get_robot_version_str
from robotcode.robot.utils.robot_path import find_file_ex

from ...__version__ import __version__

if TYPE_CHECKING:
from robotcode.language_server.robotframework.protocol import RobotLanguageServerProtocol
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@
from robot.variables.finders import NOT_FOUND, NumberFinder
from robot.variables.search import contains_variable, search_variable
from robotcode.core.lsp.types import Position
from robotcode.robot.diagnostics.entities import (
LibraryEntry,
VariableDefinition,
VariableNotFoundDefinition,
)
from robotcode.robot.diagnostics.library_doc import (
ArgumentInfo,
KeywordArgumentKind,
KeywordDoc,
LibraryDoc,
)
from robotcode.robot.utils import get_robot_version
from robotcode.robot.utils.ast import (
iter_over_keyword_names_and_owners,
Expand All @@ -32,17 +43,6 @@
whitespace_from_begin_of_token,
)

from .entities import (
LibraryEntry,
VariableDefinition,
VariableNotFoundDefinition,
)
from .library_doc import (
ArgumentInfo,
KeywordArgumentKind,
KeywordDoc,
LibraryDoc,
)
from .namespace import (
DEFAULT_BDD_PREFIXES,
Namespace,
Expand Down
Loading

0 comments on commit 4b3e65c

Please sign in to comment.