Skip to content

Commit 57551f5

Browse files
committed
Optimize collecting model errors, fixes #42
1 parent a3e0938 commit 57551f5

File tree

7 files changed

+128
-33
lines changed

7 files changed

+128
-33
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ All notable changes to the "robotcode" extension will be documented in this file
44

55
## [Unreleased]
66

7-
- none so far
7+
### added
8+
9+
- Optimize collecting model errors
10+
- also fixes [#42](https://github.com/d-biehl/robotcode/issues/42)
811

912
## 0.8.0
1013

robotcode/language_server/robotframework/parts/completion.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,10 @@ async def collect(
108108
if namespace is None:
109109
return None
110110

111+
model = await self.parent.documents_cache.get_model(document, False)
112+
111113
return await CompletionCollector(
112-
self.parent, document, namespace, await self.get_section_style(document)
114+
self.parent, document, model, namespace, await self.get_section_style(document)
113115
).collect(
114116
position,
115117
context,
@@ -126,9 +128,10 @@ async def resolve(self, sender: Any, completion_item: CompletionItem) -> Complet
126128
document = await self.parent.documents.get(document_uri)
127129
if document is not None:
128130
namespace = await self.parent.documents_cache.get_namespace(document)
131+
model = await self.parent.documents_cache.get_model(document, False)
129132
if namespace is not None:
130133
return await CompletionCollector(
131-
self.parent, document, namespace, await self.get_section_style(document)
134+
self.parent, document, model, namespace, await self.get_section_style(document)
132135
).resolve(completion_item)
133136

134137
return completion_item
@@ -221,11 +224,17 @@ class CompletionCollector(ModelHelperMixin):
221224
_logger = LoggingDescriptor()
222225

223226
def __init__(
224-
self, parent: RobotLanguageServerProtocol, document: TextDocument, namespace: Namespace, section_style: str
227+
self,
228+
parent: RobotLanguageServerProtocol,
229+
document: TextDocument,
230+
model: ast.AST,
231+
namespace: Namespace,
232+
section_style: str,
225233
) -> None:
226234
self.parent = parent
227235
self.section_style = section_style
228236
self.document = document
237+
self.model = model
229238
self.namespace = namespace
230239

231240
async def _find_methods(self, cls: Type[Any]) -> AsyncGenerator[_CompleteMethod, None]:
@@ -248,7 +257,7 @@ async def collect(
248257
self, position: Position, context: Optional[CompletionContext]
249258
) -> Union[List[CompletionItem], CompletionList, None]:
250259

251-
result_nodes = await get_nodes_at_position(self.namespace.model, position)
260+
result_nodes = await get_nodes_at_position(self.model, position)
252261

253262
result_nodes.reverse()
254263

robotcode/language_server/robotframework/parts/diagnostics.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from ...common.parts.diagnostics import DiagnosticsResult
1212
from ...common.text_document import TextDocument
1313
from ..diagnostics.analyzer import Analyzer
14-
from ..utils.ast import Token, range_from_node, range_from_token
14+
from ..utils.ast import HeaderAndBodyBlock, Token, range_from_node, range_from_token
1515

1616
if TYPE_CHECKING:
1717
from ..protocol import RobotLanguageServerProtocol
@@ -28,7 +28,7 @@ def __init__(self, parent: RobotLanguageServerProtocol) -> None:
2828
self.source_name = "robotcode.diagnostics"
2929

3030
parent.diagnostics.collect.add(self.collect_token_errors)
31-
parent.diagnostics.collect.add(self.collect_walk_model_errors)
31+
parent.diagnostics.collect.add(self.collect_model_errors)
3232

3333
parent.diagnostics.collect.add(self.collect_namespace_diagnostics)
3434

@@ -72,9 +72,21 @@ async def collect_namespace_diagnostics(self, sender: Any, document: TextDocumen
7272
],
7373
)
7474

75-
def _create_error_from_node(self, node: ast.AST, msg: str, source: Optional[str] = None) -> Diagnostic:
75+
def _create_error_from_node(
76+
self, node: ast.AST, msg: str, source: Optional[str] = None, only_start: bool = True
77+
) -> Diagnostic:
78+
from robot.parsing.model.statements import Statement
79+
80+
if isinstance(node, HeaderAndBodyBlock):
81+
if node.header is not None:
82+
node = node.header
83+
elif node.body:
84+
stmt = next((n for n in node.body if isinstance(n, Statement)), None)
85+
if stmt is not None:
86+
node = stmt
87+
7688
return Diagnostic(
77-
range=range_from_node(node, True),
89+
range=range_from_node(node, True, only_start),
7890
message=msg,
7991
severity=DiagnosticSeverity.ERROR,
8092
source=source if source is not None else self.source_name,
@@ -158,13 +170,13 @@ async def collect_token_errors(self, sender: Any, document: TextDocument) -> Dia
158170
@language_id("robotframework")
159171
@threaded()
160172
@_logger.call
161-
async def collect_walk_model_errors(self, sender: Any, document: TextDocument) -> DiagnosticsResult:
173+
async def collect_model_errors(self, sender: Any, document: TextDocument) -> DiagnosticsResult:
162174

163175
from ..utils.ast import HasError, HasErrors
164176
from ..utils.async_ast import iter_nodes
165177

166178
try:
167-
model = await self.parent.documents_cache.get_model(document)
179+
model = await self.parent.documents_cache.get_model(document, True)
168180

169181
result: List[Diagnostic] = []
170182
async for node in iter_nodes(model):
@@ -177,13 +189,13 @@ async def collect_walk_model_errors(self, sender: Any, document: TextDocument) -
177189
if not await Analyzer.should_ignore(document, range_from_node(node)):
178190
result.append(self._create_error_from_node(node, e))
179191

180-
return DiagnosticsResult(self.collect_walk_model_errors, result)
192+
return DiagnosticsResult(self.collect_model_errors, result)
181193

182194
except (asyncio.CancelledError, SystemExit, KeyboardInterrupt):
183195
raise
184196
except BaseException as e:
185197
return DiagnosticsResult(
186-
self.collect_walk_model_errors,
198+
self.collect_model_errors,
187199
[
188200
Diagnostic(
189201
range=Range(

robotcode/language_server/robotframework/parts/documents_cache.py

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,22 @@ async def __get_document_type(self, document: TextDocument) -> DocumentType:
6363
else:
6464
return DocumentType.UNKNOWN
6565

66-
async def get_tokens(self, document: TextDocument) -> List[Token]:
66+
async def get_tokens(self, document: TextDocument, data_only: bool = False) -> List[Token]:
67+
if data_only:
68+
return await document.get_cache(self.__get_tokens_data_only)
6769
return await document.get_cache(self.__get_tokens)
6870

71+
async def __get_tokens_data_only(self, document: TextDocument) -> List[Token]:
72+
document_type = await self.get_document_type(document)
73+
if document_type == DocumentType.INIT:
74+
return await self.get_init_tokens(document, True)
75+
elif document_type == DocumentType.GENERAL:
76+
return await self.get_general_tokens(document, True)
77+
elif document_type == DocumentType.RESOURCE:
78+
return await self.get_resource_tokens(document, True)
79+
else:
80+
raise UnknownFileTypeError(str(document.uri))
81+
6982
async def __get_tokens(self, document: TextDocument) -> List[Token]:
7083
document_type = await self.get_document_type(document)
7184
if document_type == DocumentType.INIT:
@@ -77,9 +90,20 @@ async def __get_tokens(self, document: TextDocument) -> List[Token]:
7790
else:
7891
raise UnknownFileTypeError(str(document.uri))
7992

80-
async def get_general_tokens(self, document: TextDocument) -> List[Token]:
93+
async def get_general_tokens(self, document: TextDocument, data_only: bool = False) -> List[Token]:
94+
if data_only:
95+
return await document.get_cache(self.__get_general_tokens_data_only)
8196
return await document.get_cache(self.__get_general_tokens)
8297

98+
async def __get_general_tokens_data_only(self, document: TextDocument) -> List[Token]:
99+
import robot.api
100+
101+
def get(text: str) -> List[Token]:
102+
with io.StringIO(text) as content:
103+
return [e for e in robot.api.get_tokens(content, True) if check_canceled_sync()]
104+
105+
return await self.__get_tokens_internal(document, get)
106+
83107
async def __get_general_tokens(self, document: TextDocument) -> List[Token]:
84108
import robot.api
85109

@@ -97,9 +121,21 @@ async def __get_tokens_internal(
97121

98122
return get(await document.text())
99123

100-
async def get_resource_tokens(self, document: TextDocument) -> List[Token]:
124+
async def get_resource_tokens(self, document: TextDocument, data_only: bool = False) -> List[Token]:
125+
if data_only:
126+
return await document.get_cache(self.__get_resource_tokens_data_only)
127+
101128
return await document.get_cache(self.__get_resource_tokens)
102129

130+
async def __get_resource_tokens_data_only(self, document: TextDocument) -> List[Token]:
131+
import robot.api
132+
133+
def get(text: str) -> List[Token]:
134+
with io.StringIO(text) as content:
135+
return [e for e in robot.api.get_resource_tokens(content, True) if check_canceled_sync()]
136+
137+
return await self.__get_tokens_internal(document, get)
138+
103139
async def __get_resource_tokens(self, document: TextDocument) -> List[Token]:
104140
import robot.api
105141

@@ -109,9 +145,20 @@ def get(text: str) -> List[Token]:
109145

110146
return await self.__get_tokens_internal(document, get)
111147

112-
async def get_init_tokens(self, document: TextDocument) -> List[Token]:
148+
async def get_init_tokens(self, document: TextDocument, data_only: bool = False) -> List[Token]:
149+
if data_only:
150+
return await document.get_cache(self.__get_init_tokens_data_only)
113151
return await document.get_cache(self.__get_init_tokens)
114152

153+
async def __get_init_tokens_data_only(self, document: TextDocument) -> List[Token]:
154+
import robot.api
155+
156+
def get(text: str) -> List[Token]:
157+
with io.StringIO(text) as content:
158+
return [e for e in robot.api.get_init_tokens(content, True) if check_canceled_sync()]
159+
160+
return await self.__get_tokens_internal(document, get)
161+
115162
async def __get_init_tokens(self, document: TextDocument) -> List[Token]:
116163
import robot.api
117164

@@ -121,15 +168,15 @@ def get(text: str) -> List[Token]:
121168

122169
return await self.__get_tokens_internal(document, get)
123170

124-
async def get_model(self, document: TextDocument) -> ast.AST:
171+
async def get_model(self, document: TextDocument, data_only: bool = True) -> ast.AST:
125172
document_type = await self.get_document_type(document)
126173

127174
if document_type == DocumentType.INIT:
128-
return await self.get_init_model(document)
175+
return await self.get_init_model(document, data_only)
129176
if document_type == DocumentType.GENERAL:
130-
return await self.get_general_model(document)
177+
return await self.get_general_model(document, data_only)
131178
if document_type == DocumentType.RESOURCE:
132-
return await self.get_resource_model(document)
179+
return await self.get_resource_model(document, data_only)
133180
else:
134181
raise UnknownFileTypeError(f"Unknown file type '{document.uri}'.")
135182

@@ -150,21 +197,37 @@ def get_tokens(_source: str, _data_only: bool = False) -> Generator[Token, None,
150197

151198
return cast(ast.AST, model)
152199

153-
async def get_general_model(self, document: TextDocument) -> ast.AST:
200+
async def get_general_model(self, document: TextDocument, data_only: bool = True) -> ast.AST:
201+
if data_only:
202+
return await document.get_cache(self.__get_general_model_data_only)
154203
return await document.get_cache(self.__get_general_model)
155204

205+
async def __get_general_model_data_only(self, document: TextDocument) -> ast.AST:
206+
return self.__get_model(document, await self.get_general_tokens(document, True), DocumentType.GENERAL)
207+
156208
async def __get_general_model(self, document: TextDocument) -> ast.AST:
157209
return self.__get_model(document, await self.get_general_tokens(document), DocumentType.GENERAL)
158210

159-
async def get_resource_model(self, document: TextDocument) -> ast.AST:
211+
async def get_resource_model(self, document: TextDocument, data_only: bool = True) -> ast.AST:
212+
if data_only:
213+
return await document.get_cache(self.__get_resource_model_data_only)
214+
160215
return await document.get_cache(self.__get_resource_model)
161216

217+
async def __get_resource_model_data_only(self, document: TextDocument) -> ast.AST:
218+
return self.__get_model(document, await self.get_resource_tokens(document, True), DocumentType.RESOURCE)
219+
162220
async def __get_resource_model(self, document: TextDocument) -> ast.AST:
163221
return self.__get_model(document, await self.get_resource_tokens(document), DocumentType.RESOURCE)
164222

165-
async def get_init_model(self, document: TextDocument) -> ast.AST:
223+
async def get_init_model(self, document: TextDocument, data_only: bool = True) -> ast.AST:
224+
if data_only:
225+
return await document.get_cache(self.__get_init_model_data_only)
166226
return await document.get_cache(self.__get_init_model)
167227

228+
async def __get_init_model_data_only(self, document: TextDocument) -> ast.AST:
229+
return self.__get_model(document, await self.get_init_tokens(document, True), DocumentType.INIT)
230+
168231
async def __get_init_model(self, document: TextDocument) -> ast.AST:
169232
return self.__get_model(document, await self.get_init_tokens(document), DocumentType.INIT)
170233

robotcode/language_server/robotframework/parts/semantic_tokens.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -726,11 +726,7 @@ async def get_tokens() -> AsyncGenerator[Tuple[Token, ast.AST], None]:
726726
lambda t: range is None or token_in_range(t[0], range),
727727
async_dropwhile(
728728
lambda t: range is not None and not token_in_range(t[0], range),
729-
(
730-
(t, n)
731-
async for t, n in get_tokens()
732-
if t.type not in [RobotToken.SEPARATOR, RobotToken.EOL, RobotToken.EOS]
733-
),
729+
((t, n) async for t, n in get_tokens()),
734730
),
735731
):
736732
async for token in self.generate_sem_tokens(
@@ -770,7 +766,7 @@ async def collect_threading(
770766
self, document: TextDocument, range: Optional[Range]
771767
) -> Union[SemanticTokens, SemanticTokensPartialResult, None]:
772768

773-
model = await self.parent.documents_cache.get_model(document)
769+
model = await self.parent.documents_cache.get_model(document, True)
774770
namespace = await self.parent.documents_cache.get_namespace(document)
775771

776772
builtin_library_doc = next(

robotcode/language_server/robotframework/parts/signature_help.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ async def collect(
7979
self, sender: Any, document: TextDocument, position: Position, context: Optional[SignatureHelpContext] = None
8080
) -> Optional[SignatureHelp]:
8181

82-
result_node = await get_node_at_position(await self.parent.documents_cache.get_model(document), position)
82+
result_node = await get_node_at_position(await self.parent.documents_cache.get_model(document, False), position)
8383
if result_node is None:
8484
return None
8585

robotcode/language_server/robotframework/utils/ast.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ def end_col_offset(self) -> int:
9595
...
9696

9797

98+
@runtime_checkable
99+
class HeaderAndBodyBlock(Protocol):
100+
header: Any
101+
body: List[Any]
102+
103+
98104
def range_from_token(token: Token) -> Range:
99105
return Range(
100106
start=Position(line=token.lineno - 1, character=token.col_offset),
@@ -105,12 +111,18 @@ def range_from_token(token: Token) -> Range:
105111
)
106112

107113

108-
def range_from_node(node: ast.AST, skip_non_data: bool = False) -> Range:
114+
def range_from_node(node: ast.AST, skip_non_data: bool = False, only_start: bool = False) -> Range:
109115
from robot.parsing.lexer import Token as RobotToken
110116

111117
if skip_non_data and isinstance(node, HasTokens) and node.tokens:
112118
start_token = next((v for v in node.tokens if v.type not in RobotToken.NON_DATA_TOKENS), None)
113-
end_token = next((v for v in reversed(node.tokens) if v.type not in RobotToken.NON_DATA_TOKENS), None)
119+
120+
if only_start and start_token is not None:
121+
end_tokens = tuple(t for t in node.tokens if start_token.lineno == t.lineno)
122+
else:
123+
end_tokens = node.tokens
124+
125+
end_token = next((v for v in reversed(end_tokens) if v.type not in RobotToken.NON_DATA_TOKENS), None)
114126
if start_token is not None and end_token is not None:
115127
return Range(start=range_from_token(start_token).start, end=range_from_token(end_token).end)
116128

0 commit comments

Comments
 (0)