diff --git a/packages/debugger/src/robotcode/debugger/protocol.py b/packages/debugger/src/robotcode/debugger/protocol.py index f7f85b10..f43c0604 100644 --- a/packages/debugger/src/robotcode/debugger/protocol.py +++ b/packages/debugger/src/robotcode/debugger/protocol.py @@ -1,7 +1,6 @@ from __future__ import annotations import asyncio -import concurrent.futures import inspect import json import threading @@ -21,6 +20,7 @@ Union, ) +from robotcode.core.concurrent import FutureEx from robotcode.core.utils.dataclasses import as_dict, as_json, from_dict from robotcode.core.utils.inspect import ensure_coroutine from robotcode.core.utils.logging import LoggingDescriptor @@ -293,12 +293,8 @@ def send_response( ) @_logger.call - def send_request( - self, - request: Request, - return_type: Optional[Type[TResult]] = None, - ) -> concurrent.futures.Future[TResult]: - result: concurrent.futures.Future[TResult] = concurrent.futures.Future() + def send_request(self, request: Request, return_type: Optional[Type[TResult]] = None) -> FutureEx[TResult]: + result: FutureEx[TResult] = FutureEx() with self._sended_request_lock: self._sended_request[request.seq] = SendedRequestEntry(result, return_type) @@ -309,9 +305,7 @@ def send_request( @_logger.call def send_request_async( - self, - request: Request, - return_type: Optional[Type[TResult]] = None, + self, request: Request, return_type: Optional[Type[TResult]] = None ) -> asyncio.Future[TResult]: return asyncio.wrap_future(self.send_request(request, return_type)) diff --git a/packages/jsonrpc2/src/robotcode/jsonrpc2/protocol.py b/packages/jsonrpc2/src/robotcode/jsonrpc2/protocol.py index 19b642be..b4153ff6 100644 --- a/packages/jsonrpc2/src/robotcode/jsonrpc2/protocol.py +++ b/packages/jsonrpc2/src/robotcode/jsonrpc2/protocol.py @@ -1,7 +1,6 @@ from __future__ import annotations import asyncio -import concurrent.futures import functools import inspect import json @@ -38,7 +37,7 @@ create_sub_task, run_coroutine_in_thread, ) -from robotcode.core.concurrent import is_threaded_callable, run_in_thread +from robotcode.core.concurrent import FutureEx, is_threaded_callable, run_in_thread from robotcode.core.utils.dataclasses import as_json, from_dict from robotcode.core.utils.inspect import ensure_coroutine, iter_methods from robotcode.core.utils.logging import LoggingDescriptor @@ -344,7 +343,7 @@ def get_param_type(self, name: str) -> Optional[Type[Any]]: class SendedRequestEntry: - def __init__(self, future: concurrent.futures.Future[Any], result_type: Optional[Type[Any]]) -> None: + def __init__(self, future: FutureEx[Any], result_type: Optional[Type[Any]]) -> None: self.future = future self.result_type = result_type @@ -563,8 +562,8 @@ def send_request( method: str, params: Optional[Any] = None, return_type_or_converter: Optional[Type[_TResult]] = None, - ) -> concurrent.futures.Future[_TResult]: - result: concurrent.futures.Future[_TResult] = concurrent.futures.Future() + ) -> FutureEx[_TResult]: + result: FutureEx[_TResult] = FutureEx() with self._sended_request_lock: self._sended_request_count += 1 diff --git a/packages/language_server/src/robotcode/language_server/common/parts/code_lens.py b/packages/language_server/src/robotcode/language_server/common/parts/code_lens.py index 071ba919..1895bf3d 100644 --- a/packages/language_server/src/robotcode/language_server/common/parts/code_lens.py +++ b/packages/language_server/src/robotcode/language_server/common/parts/code_lens.py @@ -1,11 +1,8 @@ -from __future__ import annotations - -import asyncio -from asyncio import CancelledError +from concurrent.futures import CancelledError from typing import TYPE_CHECKING, Any, Final, List, Optional -from robotcode.core.async_tools import async_tasking_event, create_sub_task -from robotcode.core.concurrent import threaded +from robotcode.core.concurrent import FutureEx, check_current_thread_canceled, threaded +from robotcode.core.event import event from robotcode.core.lsp.types import ( CodeLens, CodeLensOptions, @@ -27,16 +24,17 @@ class CodeLensProtocolPart(LanguageServerProtocolPart): _logger: Final = LoggingDescriptor() - def __init__(self, parent: LanguageServerProtocol) -> None: + def __init__(self, parent: "LanguageServerProtocol") -> None: super().__init__(parent) - self.refresh_task: Optional[asyncio.Task[Any]] = None + self.refresh_task: Optional[FutureEx[Any]] = None + self._refresh_timeout = 5 - @async_tasking_event - async def collect(sender, document: TextDocument) -> Optional[List[CodeLens]]: # NOSONAR + @event + def collect(sender, document: TextDocument) -> Optional[List[CodeLens]]: # NOSONAR ... - @async_tasking_event - async def resolve(sender, code_lens: CodeLens) -> Optional[CodeLens]: # NOSONAR + @event + def resolve(sender, code_lens: CodeLens) -> Optional[CodeLens]: # NOSONAR ... def extend_capabilities(self, capabilities: ServerCapabilities) -> None: @@ -45,7 +43,7 @@ def extend_capabilities(self, capabilities: ServerCapabilities) -> None: @rpc_method(name="textDocument/codeLens", param_type=CodeLensParams) @threaded - async def _text_document_code_lens( + def _text_document_code_lens( self, text_document: TextDocumentIdentifier, *args: Any, **kwargs: Any ) -> Optional[List[CodeLens]]: results: List[CodeLens] = [] @@ -53,7 +51,9 @@ async def _text_document_code_lens( if document is None: return None - for result in await self.collect(self, document, callback_filter=language_id_filter(document)): + for result in self.collect(self, document, callback_filter=language_id_filter(document)): + check_current_thread_canceled() + if isinstance(result, BaseException): if not isinstance(result, CancelledError): self._logger.exception(result, exc_info=result) @@ -64,17 +64,19 @@ async def _text_document_code_lens( if not results: return None - for result in results: - result.range = document.range_to_utf16(result.range) + for r in results: + r.range = document.range_to_utf16(r.range) return results @rpc_method(name="codeLens/resolve", param_type=CodeLens) @threaded - async def _code_lens_resolve(self, params: CodeLens, *args: Any, **kwargs: Any) -> CodeLens: + def _code_lens_resolve(self, params: CodeLens, *args: Any, **kwargs: Any) -> CodeLens: results: List[CodeLens] = [] - for result in await self.resolve(self, params): + for result in self.resolve(self, params): + check_current_thread_canceled() + if isinstance(result, BaseException): if not isinstance(result, CancelledError): self._logger.exception(result, exc_info=result) @@ -88,19 +90,7 @@ async def _code_lens_resolve(self, params: CodeLens, *args: Any, **kwargs: Any) return params - async def __do_refresh(self, now: bool = False) -> None: - if not now: - await asyncio.sleep(1) - - await self.__refresh() - - async def refresh(self, now: bool = False) -> None: - if self.refresh_task is not None and not self.refresh_task.done(): - self.refresh_task.get_loop().call_soon_threadsafe(self.refresh_task.cancel) - - self.refresh_task = create_sub_task(self.__do_refresh(now), loop=self.parent.diagnostics.diagnostics_loop) - - async def __refresh(self) -> None: + def refresh(self) -> None: if not ( self.parent.client_capabilities is not None and self.parent.client_capabilities.workspace is not None @@ -109,4 +99,4 @@ async def __refresh(self) -> None: ): return - await self.parent.send_request_async("workspace/codeLens/refresh") + self.parent.send_request("workspace/codeLens/refresh").result(self._refresh_timeout) diff --git a/packages/language_server/src/robotcode/language_server/common/parts/diagnostics.py b/packages/language_server/src/robotcode/language_server/common/parts/diagnostics.py index 9054338e..0109ac1c 100644 --- a/packages/language_server/src/robotcode/language_server/common/parts/diagnostics.py +++ b/packages/language_server/src/robotcode/language_server/common/parts/diagnostics.py @@ -18,6 +18,7 @@ create_sub_task, ) from robotcode.core.concurrent import threaded +from robotcode.core.event import event from robotcode.core.lsp.types import ( Diagnostic, DiagnosticOptions, @@ -241,8 +242,8 @@ async def collect(sender, document: TextDocument) -> Optional[DiagnosticsResult] async def load_workspace_documents(sender) -> Optional[List[WorkspaceDocumentsResult]]: # NOSONAR ... - @async_tasking_event - async def on_workspace_loaded(sender) -> None: # NOSONAR + @event + def on_workspace_loaded(sender) -> None: # NOSONAR ... @async_event @@ -262,7 +263,7 @@ async def ensure_workspace_loaded(self) -> None: finally: self._workspace_loaded = True self.workspace_loaded_event.set() - await self.on_workspace_loaded(self) + self.on_workspace_loaded(self) await self.force_refresh_all() async def force_refresh_all(self, refresh: bool = True) -> None: diff --git a/packages/language_server/src/robotcode/language_server/robotframework/parts/codelens.py b/packages/language_server/src/robotcode/language_server/robotframework/parts/code_lens.py similarity index 83% rename from packages/language_server/src/robotcode/language_server/robotframework/parts/codelens.py rename to packages/language_server/src/robotcode/language_server/robotframework/parts/code_lens.py index f4c018a8..7e2a3ee3 100644 --- a/packages/language_server/src/robotcode/language_server/robotframework/parts/codelens.py +++ b/packages/language_server/src/robotcode/language_server/robotframework/parts/code_lens.py @@ -1,9 +1,7 @@ -from __future__ import annotations - import ast from typing import TYPE_CHECKING, Any, List, Optional, Set, Tuple, cast -from robotcode.core.async_tools import create_sub_task +from robotcode.core.concurrent import run_in_thread from robotcode.core.lsp.types import CodeLens, Command from robotcode.core.utils.logging import LoggingDescriptor from robotcode.robot.diagnostics.library_doc import KeywordDoc @@ -17,64 +15,13 @@ from .protocol_part import RobotLanguageServerProtocolPart if TYPE_CHECKING: - from ..protocol import ( - RobotLanguageServerProtocol, - ) - - -class _Visitor(Visitor): - def __init__(self, parent: RobotCodeLensProtocolPart, document: TextDocument) -> None: - super().__init__() - self.parent = parent - self.document = document - - self.result: List[CodeLens] = [] - - def visit(self, node: ast.AST) -> None: - super().visit(node) - - @classmethod - def find_from( - cls, model: ast.AST, parent: RobotCodeLensProtocolPart, document: TextDocument - ) -> Optional[List[CodeLens]]: - finder = cls(parent, document) - - finder.visit(model) - - return finder.result if finder.result else None - - def visit_Section(self, node: ast.AST) -> None: # noqa: N802 - from robot.parsing.model.blocks import KeywordSection - - if isinstance(node, KeywordSection): - self.generic_visit(node) - - def visit_KeywordName(self, node: ast.AST) -> None: # noqa: N802 - from robot.parsing.lexer.tokens import Token as RobotToken - from robot.parsing.model.statements import KeywordName - - kw_node = cast(KeywordName, node) - name_token = cast(RobotToken, kw_node.get_token(RobotToken.KEYWORD_NAME)) - if not name_token: - return - - self.result.append( - CodeLens( - range_from_token(name_token), - command=None, - data={ - "uri": str(self.document.uri), - "name": name_token.value, - "line": name_token.lineno, - }, - ) - ) + from ..protocol import RobotLanguageServerProtocol class RobotCodeLensProtocolPart(RobotLanguageServerProtocolPart, ModelHelperMixin): _logger = LoggingDescriptor() - def __init__(self, parent: RobotLanguageServerProtocol) -> None: + def __init__(self, parent: "RobotLanguageServerProtocol") -> None: super().__init__(parent) parent.code_lens.collect.add(self.collect) @@ -86,18 +33,18 @@ def __init__(self, parent: RobotLanguageServerProtocol) -> None: parent.robot_references.cache_cleared.add(self.codelens_refresh) @language_id("robotframework") - async def codelens_refresh(self, sender: Any) -> None: # NOSONAR - await self.parent.code_lens.refresh() + def codelens_refresh(self, sender: Any) -> None: # NOSONAR + self.parent.code_lens.refresh() @language_id("robotframework") - async def collect(self, sender: Any, document: TextDocument) -> Optional[List[CodeLens]]: - if not (await self.parent.workspace.get_configuration_async(AnalysisConfig, document.uri)).references_code_lens: + def collect(self, sender: Any, document: TextDocument) -> Optional[List[CodeLens]]: + if not (self.parent.workspace.get_configuration(AnalysisConfig, document.uri)).references_code_lens: return None return _Visitor.find_from(self.parent.documents_cache.get_model(document), self, document) @language_id("robotframework") - async def resolve(self, sender: Any, code_lens: CodeLens) -> Optional[CodeLens]: + def resolve(self, sender: Any, code_lens: CodeLens) -> Optional[CodeLens]: if code_lens.data is None: return code_lens @@ -105,7 +52,7 @@ async def resolve(self, sender: Any, code_lens: CodeLens) -> Optional[CodeLens]: if document is None: return None - if not (await self.parent.workspace.get_configuration_async(AnalysisConfig, document.uri)).references_code_lens: + if not (self.parent.workspace.get_configuration(AnalysisConfig, document.uri)).references_code_lens: return None namespace = self.parent.documents_cache.get_namespace(document) @@ -126,7 +73,7 @@ async def resolve(self, sender: Any, code_lens: CodeLens) -> Optional[CodeLens]: [str(document.uri), code_lens.range.start, []], ) - async def find_refs() -> None: + def find_refs() -> None: if document is None or kw_doc is None: return # type: ignore[unreachable] @@ -134,15 +81,15 @@ async def find_refs() -> None: document, kw_doc, include_declaration=False ) - await self.parent.code_lens.refresh() + self.parent.code_lens.refresh() key = (document, kw_doc) if key not in self._running_task: - task = create_sub_task(find_refs(), loop=self.parent.diagnostics.diagnostics_loop) + task = run_in_thread(find_refs) def done(task: Any) -> None: if key in self._running_task: - self._running_task.remove(key) + self._running_task.discard(key) task.add_done_callback(done) @@ -170,3 +117,52 @@ def done(task: Any) -> None: ) return code_lens + + +class _Visitor(Visitor): + def __init__(self, parent: RobotCodeLensProtocolPart, document: TextDocument) -> None: + super().__init__() + self.parent = parent + self.document = document + + self.result: List[CodeLens] = [] + + def visit(self, node: ast.AST) -> None: + super().visit(node) + + @classmethod + def find_from( + cls, model: ast.AST, parent: RobotCodeLensProtocolPart, document: TextDocument + ) -> Optional[List[CodeLens]]: + finder = cls(parent, document) + + finder.visit(model) + + return finder.result if finder.result else None + + def visit_Section(self, node: ast.AST) -> None: # noqa: N802 + from robot.parsing.model.blocks import KeywordSection + + if isinstance(node, KeywordSection): + self.generic_visit(node) + + def visit_KeywordName(self, node: ast.AST) -> None: # noqa: N802 + from robot.parsing.lexer.tokens import Token as RobotToken + from robot.parsing.model.statements import KeywordName + + kw_node = cast(KeywordName, node) + name_token = cast(RobotToken, kw_node.get_token(RobotToken.KEYWORD_NAME)) + if not name_token: + return + + self.result.append( + CodeLens( + range_from_token(name_token), + command=None, + data={ + "uri": str(self.document.uri), + "name": name_token.value, + "line": name_token.lineno, + }, + ) + ) diff --git a/packages/language_server/src/robotcode/language_server/robotframework/parts/references.py b/packages/language_server/src/robotcode/language_server/robotframework/parts/references.py index 28179cf5..08bb83a7 100644 --- a/packages/language_server/src/robotcode/language_server/robotframework/parts/references.py +++ b/packages/language_server/src/robotcode/language_server/robotframework/parts/references.py @@ -13,8 +13,9 @@ ) from robot.parsing.model.statements import Statement -from robotcode.core.async_tools import async_event, create_sub_task +from robotcode.core.async_tools import create_sub_task from robotcode.core.concurrent import threaded +from robotcode.core.event import event from robotcode.core.lsp.types import FileEvent, Location, Position, Range, ReferenceContext, WatchKind from robotcode.core.uri import Uri from robotcode.core.utils.caching import SimpleLRUCache @@ -60,8 +61,8 @@ def __init__(self, parent: RobotLanguageServerProtocol) -> None: parent.references.collect.add(self.collect) parent.documents.did_change.add(self.document_did_change) - @async_event - async def cache_cleared(sender) -> None: # NOSONAR + @event + def cache_cleared(sender) -> None: # NOSONAR ... def server_initialized(self, sender: Any) -> None: @@ -83,7 +84,7 @@ async def clear_cache(self) -> None: self._keyword_reference_cache.clear() self._variable_reference_cache.clear() - await self.cache_cleared(self) + self.cache_cleared(self) def _find_method(self, cls: Type[Any]) -> Optional[_ReferencesMethod]: if cls is ast.AST: diff --git a/packages/language_server/src/robotcode/language_server/robotframework/protocol.py b/packages/language_server/src/robotcode/language_server/robotframework/protocol.py index 8d7ca70f..5f56d422 100644 --- a/packages/language_server/src/robotcode/language_server/robotframework/protocol.py +++ b/packages/language_server/src/robotcode/language_server/robotframework/protocol.py @@ -18,7 +18,7 @@ from .parts.code_action_documentation import RobotCodeActionDocumentationProtocolPart from .parts.code_action_quick_fixes import RobotCodeActionQuickFixesProtocolPart from .parts.code_action_refactor import RobotCodeActionRefactorProtocolPart -from .parts.codelens import RobotCodeLensProtocolPart +from .parts.code_lens import RobotCodeLensProtocolPart from .parts.completion import RobotCompletionProtocolPart from .parts.debugging_utils import RobotDebuggingUtilsProtocolPart from .parts.diagnostics import RobotDiagnosticsProtocolPart diff --git a/tests/robotcode/language_server/robotframework/parts/data/.vscode/settings.json b/tests/robotcode/language_server/robotframework/parts/data/.vscode/settings.json index 0b2cd98b..7f1181c2 100644 --- a/tests/robotcode/language_server/robotframework/parts/data/.vscode/settings.json +++ b/tests/robotcode/language_server/robotframework/parts/data/.vscode/settings.json @@ -26,8 +26,9 @@ // "robotcode.debug.outputMessages": true //"python.analysis.diagnosticMode": "workspace" //"robotcode.robot.paths": ["./tests", "./tests1"] - "robotcode.analysis.diagnosticMode": "workspace", - "robotcode.analysis.progressMode": "detailed", + "robotcode.analysis.referencesCodeLens": true, + "robotcode.analysis.diagnosticMode": "openFilesOnly", + "robotcode.analysis.progressMode": "off", "robotcode.analysis.cache.saveLocation": "workspaceFolder", "robotcode.analysis.cache.ignoredLibraries": [ //"robot.libraries.Remote",