Skip to content

Commit

Permalink
fix(langserver): preventing extensive calls to 'workspace/configurati…
Browse files Browse the repository at this point in the history
…on' through caching
  • Loading branch information
d-biehl committed Jan 4, 2024
1 parent 3f3944f commit 77db502
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -1,29 +1,24 @@
from __future__ import annotations

import asyncio
import threading
import uuid
import weakref
from concurrent.futures import Future
from dataclasses import dataclass
from typing import (
TYPE_CHECKING,
Any,
Callable,
ClassVar,
Coroutine,
Dict,
Final,
List,
Mapping,
NamedTuple,
Optional,
Protocol,
Tuple,
Type,
TypeVar,
Union,
cast,
runtime_checkable,
)

from robotcode.core.concurrent import threaded
Expand Down Expand Up @@ -125,6 +120,9 @@ def __init__(self, name: str, uri: Uri, document_uri: DocumentUri) -> None:
self.document_uri = document_uri


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


def config_section(name: str) -> Callable[[_F], _F]:
def decorator(func: _F) -> _F:
setattr(func, "__config_section__", name)
Expand All @@ -133,26 +131,20 @@ def decorator(func: _F) -> _F:
return decorator


@runtime_checkable
class HasConfigSection(Protocol):
__config_section__: str


@dataclass
# @dataclass
class ConfigBase(CamelSnakeMixin):
pass
__config_section__: ClassVar[str]


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


class Workspace(LanguageServerProtocolPart):
_logger: Final = LoggingDescriptor()

def __init__(
self,
parent: LanguageServerProtocol,
parent: "LanguageServerProtocol",
root_uri: Optional[str],
root_path: Optional[str],
workspace_folders: Optional[List[TypesWorkspaceFolder]] = None,
Expand All @@ -174,6 +166,7 @@ def __init__(

self.parent.on_shutdown.add(self.server_shutdown)
self.parent.on_initialize.add(self.server_initialize)
self._settings_cache: Dict[Tuple[Optional[WorkspaceFolder], str], ConfigBase] = {}

def server_initialize(self, sender: Any, initialization_options: Optional[Any] = None) -> None:
if (
Expand Down Expand Up @@ -260,6 +253,7 @@ def did_change_configuration(sender, settings: Dict[str, Any]) -> None: # NOSON
@threaded
def _workspace_did_change_configuration(self, settings: Dict[str, Any], *args: Any, **kwargs: Any) -> None:
self.settings = settings
self._settings_cache.clear()
self.did_change_configuration(self, settings)

@event
Expand Down Expand Up @@ -333,14 +327,6 @@ def _workspace_will_delete_files(self, files: List[FileDelete], *args: Any, **kw
def _workspace_did_delete_files(self, files: List[FileDelete], *args: Any, **kwargs: Any) -> None:
self.did_delete_files(self, [f.uri for f in files])

def get_configuration_async(
self,
section: Type[_TConfig],
scope_uri: Union[str, Uri, None] = None,
request: bool = True,
) -> asyncio.Future[_TConfig]:
return asyncio.wrap_future(self.get_configuration_future(section, scope_uri, request))

def get_configuration(
self,
section: Type[_TConfig],
Expand All @@ -357,6 +343,12 @@ def get_configuration_future(
) -> Future[_TConfig]:
result_future: Future[_TConfig] = Future()

scope = self.get_workspace_folder(scope_uri) if scope_uri is not None else None

if (scope, section.__config_section__) in self._settings_cache:
result_future.set_result(cast(_TConfig, self._settings_cache[(scope, section.__config_section__)]))
return result_future

def _get_configuration_done(f: Future[Optional[Any]]) -> None:
try:
if result_future.cancelled():
Expand All @@ -371,12 +363,14 @@ def _get_configuration_done(f: Future[Optional[Any]]) -> None:
return

result = f.result()
result_future.set_result(from_dict(result[0] if result else {}, section))
r = from_dict(result[0] if result else {}, section)
self._settings_cache[(scope, section.__config_section__)] = r
result_future.set_result(r)
except Exception as e:
result_future.set_exception(e)

self.get_configuration_raw(
section=cast(HasConfigSection, section).__config_section__,
section=section.__config_section__,
scope_uri=scope_uri,
request=request,
).add_done_callback(_get_configuration_done)
Expand Down Expand Up @@ -453,6 +447,10 @@ def _workspace_did_change_workspace_folders(
for r in to_remove:
self._workspace_folders.remove(r)

settings_to_remove = [k for k in self._settings_cache.keys() if k[0] == r]
for k in settings_to_remove:
self._settings_cache.pop(k, None)

for a in event.added:
self._workspace_folders.append(WorkspaceFolder(a.name, Uri(a.uri), a.uri))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,28 +207,35 @@ def _find_variable_references(
def find_variable_references_in_file(
self, doc: TextDocument, variable: VariableDefinition, include_declaration: bool = True
) -> List[Location]:
namespace = self.parent.documents_cache.get_namespace(doc)
try:
namespace = self.parent.documents_cache.get_namespace(doc)

if (
variable.source
and variable.source != str(doc.uri.to_path())
and not any(e for e in (namespace.get_resources()).values() if e.library_doc.source == variable.source)
and not any(
e for e in namespace.get_imported_variables().values() if e.library_doc.source == variable.source
)
and not any(e for e in namespace.get_command_line_variables() if e.source == variable.source)
):
return []
if (
variable.source
and variable.source != str(doc.uri.to_path())
and not any(e for e in (namespace.get_resources()).values() if e.library_doc.source == variable.source)
and not any(
e for e in namespace.get_imported_variables().values() if e.library_doc.source == variable.source
)
and not any(e for e in namespace.get_command_line_variables() if e.source == variable.source)
):
return []

result = set()
if include_declaration and variable.source:
result.add(Location(str(Uri.from_path(variable.source)), variable.name_range))
result = set()
if include_declaration and variable.source:
result.add(Location(str(Uri.from_path(variable.source)), variable.name_range))

refs = namespace.get_variable_references()
if variable in refs:
result |= refs[variable]
refs = namespace.get_variable_references()
if variable in refs:
result |= refs[variable]

return list(result)
return list(result)
except (SystemExit, KeyboardInterrupt, CancelledError):
raise
except BaseException as e:
self._logger.exception(e)

return []

@_logger.call
def find_keyword_references_in_file(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import dataclasses
import shutil
from pathlib import Path
from typing import AsyncIterator, Iterator, cast
from typing import AsyncIterator, Iterator

import pytest
import pytest_asyncio
Expand All @@ -18,7 +18,6 @@
)
from robotcode.core.utils.dataclasses import as_dict
from robotcode.language_server.common.parts.diagnostics import DiagnosticsMode
from robotcode.language_server.common.parts.workspace import HasConfigSection
from robotcode.language_server.common.text_document import TextDocument
from robotcode.language_server.robotframework.configuration import AnalysisConfig, RobotCodeConfig, RobotConfig
from robotcode.language_server.robotframework.protocol import (
Expand Down Expand Up @@ -73,7 +72,7 @@ async def protocol(request: pytest.FixtureRequest) -> AsyncIterator[RobotLanguag
)

protocol.workspace.settings = {
cast(HasConfigSection, RobotCodeConfig).__config_section__: as_dict(
RobotCodeConfig.__config_section__: as_dict(
RobotCodeConfig(
robot=RobotConfig(
python_path=["./lib", "./resources"],
Expand Down
2 changes: 2 additions & 0 deletions vscode-client/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ export async function activateAsync(context: vscode.ExtensionContext): Promise<v
"robotcode.analysis",
"robotcode.workspace",
"robotcode.documentationServer",
"robotcode.completion",
"robotcode.inlayHints",
]) {
for (const ws of vscode.workspace.workspaceFolders ?? []) {
if (languageClientManger.clients.has(ws.uri.toString()))
Expand Down

0 comments on commit 77db502

Please sign in to comment.