Skip to content

Commit 0cf89f0

Browse files
committed
cache specific namespace data to speedup reanalyzing of documents, some refactoring
1 parent 0a08964 commit 0cf89f0

File tree

6 files changed

+79
-50
lines changed

6 files changed

+79
-50
lines changed

log.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ keys: console
1414
format: %(name)s:%(levelname)s: %(message)s
1515

1616
[formatter_colored_simple]
17-
class=coloredlogs.ColoredFormatter
17+
class: coloredlogs.ColoredFormatter
1818
#format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
1919
format: %(name)s:%(levelname)s: %(message)s
2020

robotcode/language_server/robotframework/diagnostics/imports_manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -368,11 +368,11 @@ def __init__(
368368
self.parent_protocol.documents.did_save.add(self.resource_document_changed)
369369

370370
@async_tasking_event
371-
async def libraries_changed(sender, params: List[LibraryDoc]) -> None:
371+
async def libraries_changed(sender, libraries: List[LibraryDoc]) -> None:
372372
...
373373

374374
@async_tasking_event
375-
async def resources_changed(sender, params: List[LibraryDoc]) -> None:
375+
async def resources_changed(sender, resources: List[LibraryDoc]) -> None:
376376
...
377377

378378
async def resource_document_changed(self, sender: Any, document: TextDocument) -> None:
@@ -636,7 +636,7 @@ async def find_file(self, name: str, base_dir: str, file_type: str = "Resource")
636636

637637
@_logger.call
638638
async def _get_entry_for_resource_import(self, name: str, base_dir: str, sentinel: Any = None) -> _ResourcesEntry:
639-
source = await self.find_file(name, base_dir, "Resource")
639+
source = await self.find_file(name, base_dir)
640640

641641
async def _get_document() -> TextDocument:
642642
self._logger.debug(lambda: f"Load resource {name} from source {source}")

robotcode/language_server/robotframework/diagnostics/namespace.py

Lines changed: 61 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
from ...common.text_document import TextDocument
3838
from ..utils.ast import (
3939
Token,
40-
is_non_variable_token,
40+
is_not_variable_token,
4141
range_from_node,
4242
range_from_token,
4343
range_from_token_or_node,
@@ -599,14 +599,14 @@ async def _analyse_run_keyword(
599599
if keyword_doc is None or not keyword_doc.is_any_run_keyword():
600600
return argument_tokens
601601

602-
if keyword_doc.is_run_keyword() and len(argument_tokens) > 0 and is_non_variable_token(argument_tokens[0]):
602+
if keyword_doc.is_run_keyword() and len(argument_tokens) > 0 and is_not_variable_token(argument_tokens[0]):
603603
await self._analyze_keyword_call(argument_tokens[0].value, node, argument_tokens[0], argument_tokens[1:])
604604

605605
return argument_tokens[1:]
606606
elif (
607607
keyword_doc.is_run_keyword_with_condition()
608608
and len(argument_tokens) > 1
609-
and is_non_variable_token(argument_tokens[1])
609+
and is_not_variable_token(argument_tokens[1])
610610
):
611611
await self._analyze_keyword_call(argument_tokens[1].value, node, argument_tokens[1], argument_tokens[2:])
612612
return argument_tokens[2:]
@@ -627,7 +627,7 @@ async def _analyse_run_keyword(
627627
)
628628
continue
629629

630-
if not is_non_variable_token(t):
630+
if not is_not_variable_token(t):
631631
continue
632632

633633
and_token = next((e for e in argument_tokens if e.value == "AND"), None)
@@ -640,7 +640,7 @@ async def _analyse_run_keyword(
640640

641641
return []
642642

643-
elif keyword_doc.is_run_keyword_if() and len(argument_tokens) > 1 and is_non_variable_token(argument_tokens[1]):
643+
elif keyword_doc.is_run_keyword_if() and len(argument_tokens) > 1 and is_not_variable_token(argument_tokens[1]):
644644

645645
def skip_args() -> None:
646646
nonlocal argument_tokens
@@ -714,7 +714,7 @@ async def visit_Fixture(self, node: ast.AST) -> None: # noqa: N802
714714

715715
# TODO: calculate possible variables in NAME
716716

717-
if keyword_token is not None and is_non_variable_token(keyword_token):
717+
if keyword_token is not None and is_not_variable_token(keyword_token):
718718
await self._analyze_keyword_call(
719719
value.name, value, keyword_token, [cast(Token, e) for e in value.get_tokens(RobotToken.ARGUMENT)]
720720
)
@@ -730,7 +730,7 @@ async def visit_TestTemplate(self, node: ast.AST) -> None: # noqa: N802
730730

731731
# TODO: calculate possible variables in NAME
732732

733-
if keyword_token is not None and is_non_variable_token(keyword_token):
733+
if keyword_token is not None and is_not_variable_token(keyword_token):
734734
await self._analyze_keyword_call(value.value, value, keyword_token, [])
735735

736736
await self.generic_visit(node)
@@ -744,7 +744,7 @@ async def visit_Template(self, node: ast.AST) -> None: # noqa: N802
744744

745745
# TODO: calculate possible variables in NAME
746746

747-
if keyword_token is not None and is_non_variable_token(keyword_token):
747+
if keyword_token is not None and is_not_variable_token(keyword_token):
748748
await self._analyze_keyword_call(value.value, value, keyword_token, [])
749749

750750
await self.generic_visit(node)
@@ -908,6 +908,7 @@ def __init__(
908908
self._analyzed = False
909909
self._analyze_lock = asyncio.Lock()
910910
self._library_doc: Optional[LibraryDoc] = None
911+
self._library_doc_lock = asyncio.Lock()
911912
self._imports: Optional[List[Import]] = None
912913
self._own_variables: Optional[List[VariableDefinition]] = None
913914
self._diagnostics: List[Diagnostic] = []
@@ -921,15 +922,14 @@ def __init__(
921922
def document(self) -> Optional[TextDocument]:
922923
return self._document() if self._document is not None else None
923924

924-
async def libraries_changed(self, sender: Any, params: List[LibraryDoc]) -> None:
925-
926-
for p in params:
925+
async def libraries_changed(self, sender: Any, libraries: List[LibraryDoc]) -> None:
926+
for p in libraries:
927927
if any(e for e in self._libraries.values() if e.library_doc == p):
928928
self.invalidated_callback(self)
929929
break
930930

931-
async def resources_changed(self, sender: Any, params: List[LibraryDoc]) -> None:
932-
for p in params:
931+
async def resources_changed(self, sender: Any, resources: List[LibraryDoc]) -> None:
932+
for p in resources:
933933
if any(e for e in self._resources.values() if e.library_doc.source == p.source):
934934
self.invalidated_callback(self)
935935
break
@@ -972,34 +972,43 @@ async def get_library_doc(self) -> LibraryDoc:
972972
from ..parts.documents_cache import DocumentType
973973

974974
if self._library_doc is None:
975-
976-
model_type = ""
977-
978-
if hasattr(self.model, "model_type"):
979-
t = getattr(self.model, "model_type")
980-
981-
if t == DocumentType.RESOURCE:
982-
model_type = "RESOURCE"
983-
elif t == DocumentType.GENERAL:
984-
model_type = "TESTCASE"
985-
elif t == DocumentType.INIT:
986-
model_type = "INIT"
987-
988-
self._library_doc = await self.imports_manager.get_libdoc_from_model(
989-
self.model, self.source, model_type=model_type
990-
)
975+
async with self._library_doc_lock:
976+
if self._library_doc is None:
977+
model_type = ""
978+
979+
if hasattr(self.model, "model_type"):
980+
t = getattr(self.model, "model_type")
981+
982+
if t == DocumentType.RESOURCE:
983+
model_type = "RESOURCE"
984+
elif t == DocumentType.GENERAL:
985+
model_type = "TESTCASE"
986+
elif t == DocumentType.INIT:
987+
model_type = "INIT"
988+
989+
self._library_doc = await self.imports_manager.get_libdoc_from_model(
990+
self.model, self.source, model_type=model_type
991+
)
991992

992993
return self._library_doc
993994

995+
class DataEntry(NamedTuple):
996+
libraries: OrderedDict[str, LibraryEntry] = OrderedDict()
997+
resources: OrderedDict[str, ResourceEntry] = OrderedDict()
998+
variables: OrderedDict[str, VariablesEntry] = OrderedDict()
999+
diagnostics: List[Diagnostic] = []
1000+
9941001
@_logger.call
9951002
async def ensure_initialized(self) -> bool:
9961003
if not self._initialized:
9971004
async with self._initialize_lock:
9981005
if not self._initialized:
9991006
imports = await self.get_imports()
10001007

1008+
data_entry: Optional[Namespace.DataEntry] = None
10011009
if self.document is not None:
1002-
1010+
# check or save several data in documents data cache,
1011+
# if imports are different, then the data is invalid
10031012
old_imports: List[Import] = self.document.get_data(Namespace)
10041013
if old_imports is None:
10051014
self.document.set_data(Namespace, imports)
@@ -1012,9 +1021,28 @@ async def ensure_initialized(self) -> bool:
10121021
if e not in new_imports:
10131022
new_imports.append(e)
10141023
self.document.set_data(Namespace, new_imports)
1015-
1016-
await self._import_default_libraries()
1017-
await self._import_imports(imports, str(Path(self.source).parent), top_level=True)
1024+
else:
1025+
data_entry = self.document.get_data(Namespace.DataEntry)
1026+
1027+
if data_entry is not None:
1028+
self._libraries = data_entry.libraries.copy()
1029+
self._resources = data_entry.resources.copy()
1030+
self._variables = data_entry.variables.copy()
1031+
self._diagnostics = data_entry.diagnostics.copy()
1032+
else:
1033+
await self._import_default_libraries()
1034+
await self._import_imports(imports, str(Path(self.source).parent), top_level=True)
1035+
1036+
if self.document is not None:
1037+
self.document.set_data(
1038+
Namespace.DataEntry,
1039+
Namespace.DataEntry(
1040+
self._libraries.copy(),
1041+
self._resources.copy(),
1042+
self._variables.copy(),
1043+
self._diagnostics.copy(),
1044+
),
1045+
)
10181046

10191047
self._initialized = True
10201048

robotcode/language_server/robotframework/parts/model_helper.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from ...common.lsp_types import Position
66
from ..diagnostics.library_doc import KeywordDoc, KeywordError
77
from ..diagnostics.namespace import Namespace
8-
from ..utils.ast import Token, is_non_variable_token, range_from_token
8+
from ..utils.ast import Token, is_not_variable_token, range_from_token
99

1010

1111
class ModelHelperMixin:
@@ -20,7 +20,7 @@ async def get_run_keyword_keyworddoc_and_token_from_position(
2020
if keyword_doc is None or not keyword_doc.is_any_run_keyword():
2121
return None, argument_tokens
2222

23-
if keyword_doc.is_run_keyword() and len(argument_tokens) > 0 and is_non_variable_token(argument_tokens[0]):
23+
if keyword_doc.is_run_keyword() and len(argument_tokens) > 0 and is_not_variable_token(argument_tokens[0]):
2424
result = await self.get_keyworddoc_and_token_from_position(
2525
argument_tokens[0].value, argument_tokens[0], argument_tokens[1:], namespace, position
2626
)
@@ -29,7 +29,7 @@ async def get_run_keyword_keyworddoc_and_token_from_position(
2929
elif (
3030
keyword_doc.is_run_keyword_with_condition()
3131
and len(argument_tokens) > 1
32-
and is_non_variable_token(argument_tokens[1])
32+
and is_not_variable_token(argument_tokens[1])
3333
):
3434
result = await self.get_keyworddoc_and_token_from_position(
3535
argument_tokens[1].value, argument_tokens[1], argument_tokens[2:], namespace, position
@@ -39,13 +39,12 @@ async def get_run_keyword_keyworddoc_and_token_from_position(
3939

4040
elif keyword_doc.is_run_keywords():
4141
while argument_tokens:
42-
# TODO: Parse "run keywords" with arguments using upper case AND
4342
t = argument_tokens[0]
4443
argument_tokens = argument_tokens[1:]
4544
if t.value == "AND":
4645
continue
4746

48-
if is_non_variable_token(t) and position.is_in_range(range_from_token(t)):
47+
if is_not_variable_token(t) and position.is_in_range(range_from_token(t)):
4948
result = await self.get_keyworddoc_and_token_from_position(t.value, t, [], namespace, position)
5049

5150
return result, argument_tokens
@@ -55,7 +54,7 @@ async def get_run_keyword_keyworddoc_and_token_from_position(
5554
argument_tokens = argument_tokens[argument_tokens.index(and_token) + 1 :]
5655

5756
return None, []
58-
elif keyword_doc.is_run_keyword_if() and len(argument_tokens) > 1 and is_non_variable_token(argument_tokens[1]):
57+
elif keyword_doc.is_run_keyword_if() and len(argument_tokens) > 1 and is_not_variable_token(argument_tokens[1]):
5958

6059
def skip_args() -> None:
6160
nonlocal argument_tokens

robotcode/language_server/robotframework/parts/references.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
range_from_token_or_node,
3636
tokenize_variables,
3737
)
38+
from ..utils.async_ast import iter_nodes
3839

3940
if TYPE_CHECKING:
4041
from ..protocol import RobotLanguageServerProtocol
@@ -280,7 +281,8 @@ def _yield_owner_and_kw_names(full_name: str) -> Iterator[Tuple[Optional[str], .
280281
for i in range(1, len(tokens)):
281282
yield ".".join(tokens[:i]), ".".join(tokens[i:])
282283

283-
async def _find_keyword_references_from_file(
284+
@_logger.call
285+
async def _find_keyword_references_in_file(
284286
self, kw_doc: KeywordDoc, lib_doc: Optional[LibraryDoc], file: Path, cancel_token: CancelationToken
285287
) -> List[Location]:
286288
from robot.parsing.lexer.tokens import Token as RobotToken
@@ -302,15 +304,15 @@ async def _find_keyword_references_from_file(
302304
):
303305
return []
304306

307+
libraries_matchers = await namespace.get_libraries_matchers()
308+
resources_matchers = await namespace.get_resources_matchers()
309+
305310
async def _run() -> List[Location]:
306311
kw_matcher = KeywordMatcher(kw_doc.name)
307312

308313
result: List[Location] = []
309314

310-
libraries_matchers = await namespace.get_libraries_matchers()
311-
resources_matchers = await namespace.get_resources_matchers()
312-
313-
for node in ast.walk(namespace.model):
315+
async for node in iter_nodes(namespace.model):
314316
cancel_token.throw_if_canceled()
315317

316318
kw: Optional[KeywordDoc] = None
@@ -388,7 +390,7 @@ async def _find_keyword_references(self, document: TextDocument, kw_doc: Keyword
388390
absolute=True,
389391
):
390392
futures.append(
391-
asyncio.create_task(self._find_keyword_references_from_file(kw_doc, lib_doc, f, cancel_token))
393+
asyncio.create_task(self._find_keyword_references_in_file(kw_doc, lib_doc, f, cancel_token))
392394
)
393395

394396
for e in await asyncio.gather(*futures, return_exceptions=True):

robotcode/language_server/robotframework/utils/ast.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def range_from_token_or_node(node: ast.AST, token: Optional[Token]) -> Range:
131131
return Range.zero()
132132

133133

134-
def is_non_variable_token(token: Token) -> bool:
134+
def is_not_variable_token(token: Token) -> bool:
135135
from robot.errors import VariableError
136136

137137
try:

0 commit comments

Comments
 (0)