Skip to content

Commit eceb2e5

Browse files
committed
some refactoring to speedup loading and parsing documents
1 parent 5511a45 commit eceb2e5

File tree

8 files changed

+127
-63
lines changed

8 files changed

+127
-63
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ All notable changes to the "robotcode" extension will be documented in this file
44

55
## [Unreleased]
66

7+
### added
8+
9+
- some refactoring to speedup loading and parsing documents
10+
- semantic tokens now highlight builtin keywords
11+
712
## 0.3.2
813

914
### added

robotcode/language_server/common/parts/documents.py

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import gc
4+
import re
45
from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Mapping, Optional
56

67
from ....jsonrpc2.protocol import JsonRPCException, rpc_method
@@ -103,45 +104,55 @@ def __hash__(self) -> int:
103104

104105
def _create_document(
105106
self,
106-
text_document_item: Optional[TextDocumentItem] = None,
107-
*,
108-
document_uri: Optional[DocumentUri] = None,
107+
document_uri: DocumentUri,
108+
text: str,
109109
language_id: Optional[str] = None,
110110
version: Optional[int] = None,
111-
text: Optional[str] = None,
112111
) -> TextDocument:
113112
return TextDocument(
114-
text_document_item=text_document_item,
115113
document_uri=document_uri,
116114
language_id=language_id,
117-
version=version,
118115
text=text,
116+
version=version,
119117
)
120118

121119
def append_document(
122120
self,
123121
document_uri: DocumentUri,
124122
language_id: str,
125-
text: Optional[str],
123+
text: str,
126124
version: Optional[int] = None,
127125
) -> TextDocument:
128-
document = self._create_document(document_uri=document_uri, language_id=language_id, version=version, text=text)
126+
document = self._create_document(document_uri=document_uri, language_id=language_id, text=text, version=version)
129127

130128
self._documents[document_uri] = document
131129
return document
132130

131+
__NORMALIZE_LINE_ENDINGS = re.compile(r"(\r?\n)")
132+
133+
@classmethod
134+
def _normalize_line_endings(cls, text: str) -> str:
135+
return cls.__NORMALIZE_LINE_ENDINGS.sub("\n", text)
136+
133137
@rpc_method(name="textDocument/didOpen", param_type=DidOpenTextDocumentParams)
134138
@_logger.call
135139
async def _text_document_did_open(self, text_document: TextDocumentItem, *args: Any, **kwargs: Any) -> None:
136140
uri = str(Uri(text_document.uri).normalized())
137141
document = self.get(uri, None)
138142

143+
text_changed = True
144+
normalized_text = self._normalize_line_endings(text_document.text)
145+
139146
if document is None:
140-
document = self._create_document(text_document)
147+
document = self._create_document(
148+
text_document.uri, normalized_text, text_document.language_id, text_document.version
149+
)
141150

142151
self._documents[uri] = document
143152
else:
144-
await document.apply_full_change(text_document.version, text_document.text)
153+
text_changed = document.text != normalized_text
154+
if text_changed:
155+
await document.apply_full_change(text_document.version, normalized_text)
145156

146157
document.opened_in_editor = True
147158
document.references.add(self)
@@ -152,6 +163,13 @@ async def _text_document_did_open(self, text_document: TextDocumentItem, *args:
152163
callback_filter=lambda c: not isinstance(c, HasLanguageId) or c.__language_id__ == document.language_id,
153164
)
154165

166+
if text_changed:
167+
await self.did_change(
168+
self,
169+
document,
170+
callback_filter=lambda c: not isinstance(c, HasLanguageId) or c.__language_id__ == document.language_id,
171+
)
172+
155173
@rpc_method(name="textDocument/didClose", param_type=DidCloseTextDocumentParams)
156174
@_logger.call
157175
async def _text_document_did_close(self, text_document: TextDocumentIdentifier, *args: Any, **kwargs: Any) -> None:
@@ -195,8 +213,20 @@ async def _text_document_did_save(
195213
document = self._documents.get(str(Uri(text_document.uri).normalized()), None)
196214
self._logger.warning(lambda: f"Document {text_document.uri} is not opened.", condition=lambda: document is None)
197215

198-
if document is not None and text is not None:
199-
await document.apply_full_change(None, text)
216+
if document is not None:
217+
if text is not None:
218+
normalized_text = self._normalize_line_endings(text)
219+
220+
text_changed = document.text != normalized_text
221+
if text_changed:
222+
await document.apply_full_change(None, text)
223+
224+
await self.did_change(
225+
self,
226+
document,
227+
callback_filter=lambda c: not isinstance(c, HasLanguageId)
228+
or c.__language_id__ == document.language_id,
229+
)
200230

201231
await self.did_save(
202232
self,
@@ -238,12 +268,14 @@ async def _text_document_did_change(
238268
elif sync_kind == TextDocumentSyncKind.FULL and isinstance(
239269
content_change, TextDocumentContentTextChangeEvent
240270
):
241-
await document.apply_full_change(text_document.version, content_change.text)
271+
await document.apply_full_change(
272+
text_document.version, self._normalize_line_endings(content_change.text)
273+
)
242274
elif sync_kind == TextDocumentSyncKind.INCREMENTAL and isinstance(
243275
content_change, TextDocumentContentRangeChangeEvent
244276
):
245277
await document.apply_incremental_change(
246-
text_document.version, content_change.range, content_change.text
278+
text_document.version, content_change.range, self._normalize_line_endings(content_change.text)
247279
)
248280
else:
249281
raise LanguageServerDocumentException(

robotcode/language_server/common/text_document.py

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from typing import Any, Awaitable, Callable, Dict, List, Optional, TypeVar, Union, cast
99

1010
from ...utils.uri import Uri
11-
from .lsp_types import DocumentUri, Position, Range, TextDocumentItem
11+
from .lsp_types import DocumentUri, Position, Range
1212

1313

1414
def _utf16_unit_offset(chars: str) -> int:
@@ -47,37 +47,25 @@ def __init__(self, data: Any = None) -> None:
4747
class TextDocument:
4848
def __init__(
4949
self,
50-
text_document_item: Optional[TextDocumentItem] = None,
51-
*,
52-
document_uri: Optional[DocumentUri] = None,
50+
document_uri: DocumentUri,
51+
text: str,
5352
language_id: Optional[str] = None,
5453
version: Optional[int] = None,
55-
text: Optional[str] = None,
5654
) -> None:
5755
super().__init__()
5856

5957
self._lock = asyncio.Lock()
6058

6159
self._references: weakref.WeakSet[Any] = weakref.WeakSet()
6260

63-
self.document_uri = (
64-
text_document_item.uri
65-
if text_document_item is not None
66-
else document_uri
67-
if document_uri is not None
68-
else ""
69-
)
61+
self.document_uri = document_uri
62+
7063
self.uri = Uri(self.document_uri).normalized()
7164

72-
self.language_id = (
73-
text_document_item.language_id
74-
if text_document_item is not None
75-
else language_id
76-
if language_id is not None
77-
else ""
78-
)
79-
self.version = text_document_item.version if text_document_item is not None else version
80-
self._text = text_document_item.text if text_document_item is not None else text if text is not None else ""
65+
self.language_id = language_id
66+
67+
self.version = version
68+
self._text = text
8169

8270
self._lines: Optional[List[str]] = None
8371

robotcode/language_server/robotframework/diagnostics/imports_manager.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,10 +380,7 @@ def __init__(
380380
self._resources: OrderedDict[_ResourcesEntryKey, _ResourcesEntry] = OrderedDict()
381381
self.file_watchers: List[FileWatcherEntry] = []
382382
self._loop = asyncio.get_event_loop()
383-
self.parent_protocol.documents.did_open.add(self.resource_document_changed)
384383
self.parent_protocol.documents.did_change.add(self.resource_document_changed)
385-
self.parent_protocol.documents.did_close.add(self.resource_document_changed)
386-
self.parent_protocol.documents.did_save.add(self.resource_document_changed)
387384

388385
@async_tasking_event
389386
async def libraries_changed(sender, libraries: List[LibraryDoc]) -> None:

robotcode/language_server/robotframework/diagnostics/library_doc.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,13 +274,16 @@ class KeywordDoc(Model):
274274
is_error_handler: bool = False
275275
error_handler_message: Optional[str] = None
276276
is_initializer: bool = False
277+
is_registered_run_keyword: bool = False
278+
args_to_process: Optional[int] = None
279+
deprecated: bool = False
277280

278281
def __str__(self) -> str:
279282
return f"{self.name}({', '.join(str(arg) for arg in self.args)})"
280283

281284
@property
282285
def is_deprecated(self) -> bool:
283-
return DEPRECATED_PATTERN.match(self.doc) is not None
286+
return self.deprecated or DEPRECATED_PATTERN.match(self.doc) is not None
284287

285288
@property
286289
def deprecated_message(self) -> str:
@@ -946,6 +949,7 @@ def get_library_doc(
946949
from robot.output import LOGGER
947950
from robot.output.loggerhelper import AbstractLogger
948951
from robot.running.outputcapture import OutputCapturer
952+
from robot.running.runkwregister import RUN_KW_REGISTER
949953
from robot.running.testlibraries import _get_lib_class
950954
from robot.utils import Importer
951955

@@ -1128,6 +1132,9 @@ def get_test_library(
11281132
doc_format=str(lib.doc_format) or DEFAULT_DOC_FORMAT,
11291133
is_error_handler=kw[1].is_error_handler,
11301134
error_handler_message=kw[1].error_handler_message,
1135+
is_registered_run_keyword=RUN_KW_REGISTER.is_run_keyword(libdoc.name, kw[0].name),
1136+
args_to_process=RUN_KW_REGISTER.get_args_to_process(libdoc.name, kw[0].name),
1137+
deprecated=kw[0].deprecated,
11311138
)
11321139
for kw in [
11331140
(KeywordDocBuilder().build_keyword(k), k)

robotcode/language_server/robotframework/diagnostics/namespace.py

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -922,12 +922,13 @@ def __init__(
922922
self._imports: Optional[List[Import]] = None
923923
self._own_variables: Optional[List[VariableDefinition]] = None
924924
self._diagnostics: List[Diagnostic] = []
925-
926925
self._keywords: Optional[List[KeywordDoc]] = None
927926

928927
# TODO: how to get the search order from model
929928
self.search_order: Tuple[str, ...] = ()
930929

930+
self._finder: Optional[KeywordFinder] = None
931+
931932
@property
932933
def document(self) -> Optional[TextDocument]:
933934
return self._document() if self._document is not None else None
@@ -938,7 +939,7 @@ async def libraries_changed(self, sender: Any, libraries: List[LibraryDoc]) -> N
938939
if any(e for e in self._libraries.values() if e.library_doc == p):
939940
if self.document is not None:
940941
self.document.set_data(Namespace.DataEntry, None)
941-
self.invalidated_callback(self)
942+
await self.invalidate()
942943
break
943944

944945
@_logger.call
@@ -947,9 +948,27 @@ async def resources_changed(self, sender: Any, resources: List[LibraryDoc]) -> N
947948
if any(e for e in self._resources.values() if e.library_doc.source == p.source):
948949
if self.document is not None:
949950
self.document.set_data(Namespace.DataEntry, None)
950-
self.invalidated_callback(self)
951+
await self.invalidate()
951952
break
952953

954+
async def invalidate(self) -> None:
955+
async with self._initialize_lock, self._library_doc_lock, self._analyze_lock:
956+
self._initialized = False
957+
958+
self._libraries = OrderedDict()
959+
self._libraries_matchers = None
960+
self._resources = OrderedDict()
961+
self._resources_matchers = None
962+
self._variables = OrderedDict()
963+
self._imports = None
964+
self._own_variables = None
965+
self._keywords = None
966+
self._library_doc = None
967+
self._analyzed = False
968+
self._diagnostics = []
969+
970+
self.invalidated_callback(self)
971+
953972
@_logger.call
954973
async def get_diagnostisc(self) -> List[Diagnostic]:
955974
await self.ensure_initialized()
@@ -1338,7 +1357,7 @@ async def _import(value: Import) -> Optional[LibraryEntry]:
13381357
)
13391358

13401359
else:
1341-
if entry.name == BUILTIN_LIBRARY_NAME and entry.alias is None:
1360+
if top_level and entry.name == BUILTIN_LIBRARY_NAME and entry.alias is None:
13421361
self._diagnostics.append(
13431362
Diagnostic(
13441363
range=entry.import_range,
@@ -1502,9 +1521,18 @@ async def _analyze(self) -> None:
15021521

15031522
@_logger.call
15041523
async def find_keyword(self, name: Optional[str]) -> Optional[KeywordDoc]:
1524+
if self._finder is None:
1525+
await self.ensure_initialized()
1526+
1527+
self._finder = await self.create_finder()
1528+
1529+
return await self._finder.find_keyword(name)
1530+
1531+
@_logger.call
1532+
async def create_finder(self) -> KeywordFinder:
15051533
await self.ensure_initialized()
15061534

1507-
return await KeywordFinder(self).find_keyword(name)
1535+
return KeywordFinder(self)
15081536

15091537

15101538
class DiagnosticsEntry(NamedTuple):
@@ -1523,6 +1551,7 @@ def __init__(self, namespace: Namespace) -> None:
15231551
self.namespace = namespace
15241552
self.diagnostics: List[DiagnosticsEntry] = []
15251553
self.self_library_doc: Optional[LibraryDoc] = None
1554+
self._cache: Dict[Optional[str], Tuple[Optional[KeywordDoc], List[DiagnosticsEntry]]] = {}
15261555

15271556
def reset_diagnostics(self) -> None:
15281557
self.diagnostics = []
@@ -1531,15 +1560,22 @@ async def find_keyword(self, name: Optional[str]) -> Optional[KeywordDoc]:
15311560
try:
15321561
self.reset_diagnostics()
15331562

1534-
result = await self._find_keyword(name)
1535-
if result is None:
1536-
self.diagnostics.append(
1537-
DiagnosticsEntry(
1538-
f"No keyword with name {repr(name)} found.", DiagnosticSeverity.ERROR, "KeywordError"
1563+
cached = self._cache.get(name, None)
1564+
1565+
if cached is not None:
1566+
self.diagnostics = cached[1]
1567+
return cached[0]
1568+
else:
1569+
result = await self._find_keyword(name)
1570+
if result is None:
1571+
self.diagnostics.append(
1572+
DiagnosticsEntry(
1573+
f"No keyword with name {repr(name)} found.", DiagnosticSeverity.ERROR, "KeywordError"
1574+
)
15391575
)
1540-
)
1576+
self._cache[name] = (result, self.diagnostics)
15411577

1542-
return result
1578+
return result
15431579
except CancelSearchError:
15441580
return None
15451581

0 commit comments

Comments
 (0)