Skip to content

Commit 81b8626

Browse files
committed
implement document highlight for variables and keywords
1 parent bdac340 commit 81b8626

File tree

9 files changed

+347
-39
lines changed

9 files changed

+347
-39
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ All notable changes to the "robotcode" extension will be documented in this file
77
### added
88

99
- implement find references for libraries, resources, variables import
10+
- implement document highlight for variables and keywords
1011

1112
## 0.4.2
1213

robotcode/language_server/common/lsp_types.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,16 @@ class ReferenceRegistrationOptions(TextDocumentRegistrationOptions, ReferenceOpt
931931
pass
932932

933933

934+
@dataclass(repr=False)
935+
class DocumentHighlightOptions(WorkDoneProgressOptions):
936+
pass
937+
938+
939+
@dataclass(repr=False)
940+
class DocumentHighlightRegistrationOptions(TextDocumentRegistrationOptions, DocumentHighlightOptions):
941+
pass
942+
943+
934944
@dataclass(repr=False)
935945
class ServerCapabilities(Model):
936946
text_document_sync: Union[TextDocumentSyncOptions, TextDocumentSyncKind, None] = None
@@ -941,7 +951,7 @@ class ServerCapabilities(Model):
941951
definition_provider: Union[bool, DefinitionOptions, None] = None
942952
implementation_provider: Union[bool, ImplementationOptions, ImplementationRegistrationOptions, None] = None
943953
references_provider: Union[bool, ReferenceOptions, None] = None
944-
# TODO document_highlight_provider: Union[bool, DocumentHighlightOptions, None] = None
954+
document_highlight_provider: Union[bool, DocumentHighlightOptions, None] = None
945955
document_symbol_provider: Union[bool, DocumentSymbolOptions, None] = None
946956
# TODO code_action_provider: Union[bool, CodeActionOptions] = None
947957
code_lens_provider: Optional[CodeLensOptions] = None
@@ -1837,3 +1847,27 @@ class _ReferenceParams(Model):
18371847
@dataclass(repr=False)
18381848
class ReferenceParams(WorkDoneProgressParams, PartialResultParams, TextDocumentPositionParams, _ReferenceParams):
18391849
pass
1850+
1851+
1852+
@dataclass(repr=False)
1853+
class _DocumentHighlightParams(Model):
1854+
pass
1855+
1856+
1857+
@dataclass(repr=False)
1858+
class DocumentHighlightParams(
1859+
WorkDoneProgressParams, PartialResultParams, TextDocumentPositionParams, _DocumentHighlightParams
1860+
):
1861+
pass
1862+
1863+
1864+
class DocumentHighlightKind(Enum):
1865+
TEXT = 1
1866+
READ = 2
1867+
WRITE = 3
1868+
1869+
1870+
@dataclass(repr=False)
1871+
class DocumentHighlight(Model):
1872+
range: Range
1873+
kind: Optional[DocumentHighlightKind] = None
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from __future__ import annotations
2+
3+
from asyncio import CancelledError
4+
from typing import TYPE_CHECKING, Any, List, Optional
5+
6+
from ....jsonrpc2.protocol import rpc_method
7+
from ....utils.async_tools import async_tasking_event
8+
from ....utils.logging import LoggingDescriptor
9+
from ..has_extend_capabilities import HasExtendCapabilities
10+
from ..language import language_id_filter
11+
from ..lsp_types import (
12+
DocumentHighlight,
13+
DocumentHighlightOptions,
14+
DocumentHighlightParams,
15+
Position,
16+
ServerCapabilities,
17+
TextDocumentIdentifier,
18+
)
19+
from ..text_document import TextDocument
20+
21+
if TYPE_CHECKING:
22+
from ..protocol import LanguageServerProtocol
23+
24+
from .protocol_part import LanguageServerProtocolPart
25+
26+
27+
class DocumentHighlightProtocolPart(LanguageServerProtocolPart, HasExtendCapabilities):
28+
29+
_logger = LoggingDescriptor()
30+
31+
def __init__(self, parent: LanguageServerProtocol) -> None:
32+
super().__init__(parent)
33+
34+
def extend_capabilities(self, capabilities: ServerCapabilities) -> None:
35+
if len(self.collect):
36+
capabilities.document_highlight_provider = DocumentHighlightOptions(work_done_progress=True)
37+
38+
@async_tasking_event
39+
async def collect(
40+
sender, document: TextDocument, position: Position # NOSONAR
41+
) -> Optional[List[DocumentHighlight]]:
42+
...
43+
44+
@rpc_method(name="textDocument/documentHighlight", param_type=DocumentHighlightParams)
45+
async def _text_document_document_highlight(
46+
self,
47+
text_document: TextDocumentIdentifier,
48+
position: Position,
49+
*args: Any,
50+
**kwargs: Any,
51+
) -> Optional[List[DocumentHighlight]]:
52+
53+
highlights: List[DocumentHighlight] = []
54+
55+
document = await self.parent.documents.get(text_document.uri)
56+
if document is None:
57+
return None
58+
59+
for result in await self.collect(self, document, position, callback_filter=language_id_filter(document)):
60+
if isinstance(result, BaseException):
61+
if not isinstance(result, CancelledError):
62+
self._logger.exception(result, exc_info=result)
63+
else:
64+
if result is not None:
65+
highlights.extend(result)
66+
67+
if len(highlights) == 0:
68+
return None
69+
70+
return highlights

robotcode/language_server/common/protocol.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from .parts.declaration import DeclarationProtocolPart
4242
from .parts.definition import DefinitionProtocolPart
4343
from .parts.diagnostics import DiagnosticsProtocolPart
44+
from .parts.document_highlight import DocumentHighlightProtocolPart
4445
from .parts.document_symbols import DocumentSymbolsProtocolPart
4546
from .parts.documents import TextDocumentProtocolPart
4647
from .parts.folding_range import FoldingRangeProtocolPart
@@ -79,6 +80,7 @@ class LanguageServerProtocol(JsonRPCProtocol):
7980
formatting = ProtocolPartDescriptor(FormattingProtocolPart)
8081
semantic_tokens = ProtocolPartDescriptor(SemanticTokensProtocolPart)
8182
references = ProtocolPartDescriptor(ReferencesProtocolPart)
83+
document_highlight = ProtocolPartDescriptor(DocumentHighlightProtocolPart)
8284

8385
name: Optional[str] = None
8486
version: Optional[str] = None

robotcode/language_server/robotframework/diagnostics/library_doc.py

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -165,20 +165,11 @@ class InvalidVariableError(Exception):
165165

166166

167167
class VariableMatcher:
168-
_match_extended = re.compile(
169-
r"""
170-
(.+?) # base name (group 1)
171-
([^\s\w].+) # extended part (group 2)
172-
""",
173-
re.UNICODE | re.VERBOSE,
174-
)
175-
176-
def __init__(self, name: str, extended: bool = False) -> None:
168+
def __init__(self, name: str) -> None:
177169
from robot.utils.normalizing import normalize
178170
from robot.variables.search import VariableSearcher
179171

180172
self.name = name
181-
self.extended = extended
182173

183174
searcher = VariableSearcher("$@&%", ignore_errors=True)
184175
match = searcher.search(name)
@@ -188,28 +179,20 @@ def __init__(self, name: str, extended: bool = False) -> None:
188179

189180
self.base = match.base
190181

191-
self.extended_base: Optional[str] = None
192-
self.normalize_extended: Optional[str] = None
193-
if extended:
194-
ext_match = self._match_extended.match(self.name[2:-1])
195-
if ext_match is not None:
196-
self.extended_base, _ = ext_match.groups()
197-
self.normalize_extended = str(normalize(self.extended_base, "_"))
198-
199182
self.normalized_name = str(normalize(self.base, "_"))
200183

201184
def __eq__(self, o: object) -> bool:
202185
from robot.utils.normalizing import normalize
203186
from robot.variables.search import VariableSearcher
204187

205188
if isinstance(o, VariableMatcher):
206-
return o.normalized_name == self.normalized_name or (o.extended and self.normalized_name == o.extended_base)
189+
return o.normalized_name == self.normalized_name
207190
elif isinstance(o, str):
208191
searcher = VariableSearcher("$@&%", ignore_errors=True)
209192
match = searcher.search(o)
210193
base = match.base
211194
normalized = str(normalize(base, "_"))
212-
return self.normalized_name == normalized or (self.extended and self.normalize_extended == normalized)
195+
return self.normalized_name == normalized
213196
else:
214197
return False
215198

robotcode/language_server/robotframework/diagnostics/namespace.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import ast
44
import asyncio
55
import itertools
6+
import re
67
import weakref
78
from collections import OrderedDict
89
from dataclasses import dataclass, field
@@ -588,14 +589,12 @@ def get_command_line_variables(self) -> List[VariableDefinition]:
588589
return self.imports_manager.get_command_line_variables()
589590

590591
@_logger.call
591-
async def get_variables(
592+
async def yield_variables(
592593
self, nodes: Optional[List[ast.AST]] = None, position: Optional[Position] = None
593-
) -> Dict[VariableMatcher, VariableDefinition]:
594+
) -> AsyncGenerator[Tuple[VariableMatcher, VariableDefinition], None]:
594595
from robot.parsing.model.blocks import Keyword, TestCase
595596

596-
await self.ensure_initialized()
597-
598-
result: Dict[VariableMatcher, VariableDefinition] = {}
597+
yielded: Dict[VariableMatcher, VariableDefinition] = {}
599598

600599
async for var in async_chain(
601600
*[
@@ -609,16 +608,46 @@ async def get_variables(
609608
*(e.variables for e in self._variables.values()),
610609
(e for e in self.get_builtin_variables()),
611610
):
612-
if var.name is not None and VariableMatcher(var.name) not in result.keys():
613-
result[VariableMatcher(var.name)] = var
611+
if var.name is not None:
612+
matcher = VariableMatcher(var.name)
613+
if matcher not in yielded.keys():
614+
yielded[matcher] = var
615+
yield matcher, var
614616

615-
return result
617+
@_logger.call
618+
async def get_variables(
619+
self, nodes: Optional[List[ast.AST]] = None, position: Optional[Position] = None
620+
) -> Dict[VariableMatcher, VariableDefinition]:
621+
return {m: v async for m, v in self.yield_variables(nodes, position)}
622+
623+
_match_extended = re.compile(
624+
r"""
625+
(.+?) # base name (group 1)
626+
([^\s\w].+) # extended part (group 2)
627+
""",
628+
re.UNICODE | re.VERBOSE,
629+
)
616630

617631
@_logger.call
618632
async def find_variable(
619633
self, name: str, nodes: Optional[List[ast.AST]], position: Optional[Position] = None
620634
) -> Optional[VariableDefinition]:
621-
return (await self.get_variables(nodes, position)).get(VariableMatcher(name, True), None)
635+
636+
matcher = VariableMatcher(name)
637+
async for m, v in self.yield_variables(nodes, position):
638+
if matcher == m:
639+
return v
640+
641+
match = self._match_extended.match(name[2:-1])
642+
if match is not None:
643+
base_name, extended = match.groups()
644+
name = f"{name[0]}{{{base_name}}}"
645+
646+
matcher = VariableMatcher(name)
647+
async for m, v in self.yield_variables(nodes, position):
648+
if matcher == m:
649+
return v
650+
return None
622651

623652
@_logger.call
624653
async def _import_imports(self, imports: Iterable[Import], base_dir: str, *, top_level: bool = False) -> None:

0 commit comments

Comments
 (0)