Skip to content

Commit cf49c9a

Browse files
committed
implement reference codelenses for keyword definitions
1 parent a9d6cf6 commit cf49c9a

23 files changed

+777
-719
lines changed

CHANGELOG.md

+578-572
Large diffs are not rendered by default.

package.json

+5
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,11 @@
500500
"default": 1000,
501501
"description": "Specifies the maximum number of files for which diagnostics are reported for the whole project/workspace folder. Specifies 0 or less to disable the limit completely.",
502502
"scope": "resource"
503+
},
504+
"robotcode.analysis.referencesCodeLens": {
505+
"type": "boolean",
506+
"default": true,
507+
"description": "Enable/disable references CodeLens for RobotFramework files."
503508
}
504509
}
505510
}

robotcode/language_server/common/parts/code_lens.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ async def _code_lens_resolve(self, params: CodeLens, *args: Any, **kwargs: Any)
7878
if result is not None:
7979
results.append(result)
8080

81-
if len(results) > 0:
81+
if len(results) > 1:
8282
self._logger.warning("More then one resolve result collected.")
8383
return results[-1]
8484

robotcode/language_server/robotframework/configuration.py

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ class AnalysisConfig(ConfigBase):
8282
diagnostic_mode: DiagnosticsMode = DiagnosticsMode.OPENFILESONLY
8383
progress_mode: AnalysisProgressMode = AnalysisProgressMode.SIMPLE
8484
max_project_file_count: int = 1000
85+
references_code_lens: bool = True
8586

8687

8788
@config_section("robotcode")

robotcode/language_server/robotframework/parts/codelens.py

+68-22
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,24 @@
88
from ...common.decorators import language_id
99
from ...common.lsp_types import CodeLens, Command
1010
from ...common.text_document import TextDocument
11+
from ..configuration import AnalysisConfig
1112
from ..utils.ast_utils import range_from_token
13+
from .model_helper import ModelHelperMixin
1214

1315
if TYPE_CHECKING:
1416
from ..protocol import RobotLanguageServerProtocol
1517

1618
from .protocol_part import RobotLanguageServerProtocolPart
1719

1820

19-
class RobotCodeLensProtocolPart(RobotLanguageServerProtocolPart):
21+
class RobotCodeLensProtocolPart(RobotLanguageServerProtocolPart, ModelHelperMixin):
2022
_logger = LoggingDescriptor()
2123

2224
def __init__(self, parent: RobotLanguageServerProtocol) -> None:
2325
super().__init__(parent)
2426

25-
# parent.code_lens.collect.add(self.collect)
27+
parent.code_lens.collect.add(self.collect)
28+
parent.code_lens.resolve.add(self.resolve)
2629

2730
@language_id("robotframework")
2831
@threaded()
@@ -55,27 +58,70 @@ async def visit_Section(self, node: ast.AST) -> None: # noqa: N802
5558
if isinstance(node, KeywordSection):
5659
await self.generic_visit(node)
5760

58-
async def visit_Keyword(self, node: ast.AST) -> None: # noqa: N802
61+
async def visit_KeywordName(self, node: ast.AST) -> None: # noqa: N802
5962
from robot.parsing.lexer.tokens import Token as RobotToken
60-
from robot.parsing.model.blocks import Keyword
61-
62-
keyword = cast(Keyword, node)
63-
64-
if keyword.header:
65-
name_token = keyword.header.get_token(RobotToken.KEYWORD_NAME)
66-
if name_token is None:
67-
return
68-
69-
r = range_from_token(name_token)
70-
self.result.append(
71-
CodeLens(
72-
r,
73-
Command(
74-
"references",
75-
"robotcode.action.findReferences",
76-
[str(document.uri), {"lineNumber": r.start.line, "column": r.start.character}],
77-
),
78-
)
63+
from robot.parsing.model.statements import KeywordName
64+
65+
kw_node = cast(KeywordName, node)
66+
name_token = cast(RobotToken, kw_node.get_token(RobotToken.KEYWORD_NAME))
67+
if not name_token:
68+
return None
69+
70+
self.result.append(
71+
CodeLens(
72+
range_from_token(name_token),
73+
command=None,
74+
data={"uri": str(document.uri), "name": name_token.value, "line": name_token.lineno},
7975
)
76+
)
77+
78+
if not (await self.parent.workspace.get_configuration(AnalysisConfig, document.uri)).references_code_lens:
79+
return None
8080

8181
return await Visitor.find_from(await self.parent.documents_cache.get_model(document), self)
82+
83+
async def resolve(self, sender: Any, code_lens: CodeLens) -> Optional[CodeLens]:
84+
if code_lens.data is None:
85+
return code_lens
86+
87+
document = await self.parent.documents.get(code_lens.data.get("uri", None))
88+
if document is None:
89+
return None
90+
91+
if not (await self.parent.workspace.get_configuration(AnalysisConfig, document.uri)).references_code_lens:
92+
return None
93+
94+
namespace = await self.parent.documents_cache.get_namespace(document)
95+
96+
if namespace is None:
97+
return None
98+
99+
name = code_lens.data["name"]
100+
line = code_lens.data["line"]
101+
102+
if self.parent.robot_workspace.workspace_loaded:
103+
kw_doc = await self.get_keyword_definition_at_line(namespace, name, line)
104+
105+
if kw_doc is not None and not kw_doc.is_error_handler:
106+
references = await self.parent.robot_references.find_keyword_references(
107+
document, kw_doc, include_declaration=False
108+
)
109+
code_lens.command = Command(
110+
f"{len(references)} references",
111+
"editor.action.showReferences",
112+
[str(document.uri), code_lens.range.start, references],
113+
)
114+
else:
115+
code_lens.command = Command(
116+
"0 references",
117+
"editor.action.showReferences",
118+
[str(document.uri), code_lens.range.start, []],
119+
)
120+
else:
121+
code_lens.command = Command(
122+
"...",
123+
"editor.action.showReferences",
124+
[str(document.uri), code_lens.range.start, []],
125+
)
126+
127+
return code_lens

robotcode/language_server/robotframework/parts/model_helper.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,11 @@ def is_bdd_token(cls, token: Token) -> bool:
519519

520520
@classmethod
521521
async def get_keyword_definition_at_token(cls, namespace: Namespace, token: Token) -> Optional[KeywordDoc]:
522+
return await cls.get_keyword_definition_at_line(namespace, token.value, token.lineno)
523+
524+
@classmethod
525+
async def get_keyword_definition_at_line(cls, namespace: Namespace, value: str, line: int) -> Optional[KeywordDoc]:
522526
return next(
523-
(k for k in (await namespace.get_library_doc()).keywords.get_all(token.value) if k.line_no == token.lineno),
527+
(k for k in (await namespace.get_library_doc()).keywords.get_all(value) if k.line_no == line),
524528
None,
525529
)

robotcode/language_server/robotframework/parts/references.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ async def references_KeywordName( # noqa: N802
358358
keyword = await self.get_keyword_definition_at_token(namespace, name_token)
359359

360360
if keyword is not None and keyword.source and not keyword.is_error_handler:
361-
return await self.find_keyword_references(document, keyword, context.include_declaration)
361+
return await self.find_keyword_references(document, keyword, False)
362362

363363
return None
364364

@@ -595,7 +595,7 @@ async def get_keyword_references_from_tokens(
595595
) -> AsyncGenerator[Location, None]:
596596
from robot.utils.escaping import unescape
597597

598-
if kw_token is not None and is_not_variable_token(kw_token):
598+
if kw_token is not None:
599599
kw_token = self.strip_bdd_prefix(kw_token)
600600

601601
kw: Optional[KeywordDoc] = None

robotcode/language_server/robotframework/parts/robot_workspace.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ class RobotWorkspaceProtocolPart(RobotLanguageServerProtocolPart):
2828
def __init__(self, parent: RobotLanguageServerProtocol) -> None:
2929
super().__init__(parent)
3030
self.parent.documents.on_read_document_text.add(self._on_read_document_text)
31-
self.parent.diagnostics.collect_workspace_documents.add(self.collect_workspace_diagnostics)
31+
self.parent.diagnostics.collect_workspace_documents.add(self._collect_workspace_documents)
3232
self.parent.diagnostics.on_get_diagnostics_mode.add(self.on_get_diagnostics_mode)
33+
self.workspace_loaded = False
3334

3435
@language_id("robotframework")
3536
async def _on_read_document_text(self, sender: Any, uri: Uri) -> Optional[str]:
@@ -43,7 +44,7 @@ async def on_get_diagnostics_mode(self, sender: Any, uri: Uri) -> Optional[Diagn
4344
return config.diagnostic_mode
4445

4546
@threaded()
46-
async def collect_workspace_diagnostics(self, sender: Any) -> List[WorkspaceDocumentsResult]:
47+
async def _collect_workspace_documents(self, sender: Any) -> List[WorkspaceDocumentsResult]:
4748

4849
result: List[WorkspaceDocumentsResult] = []
4950

@@ -100,6 +101,10 @@ async def collect_workspace_diagnostics(self, sender: Any) -> List[WorkspaceDocu
100101
except BaseException as e:
101102
self._logger.exception(e)
102103

104+
self.workspace_loaded = True
105+
if config.analysis.references_code_lens:
106+
await self.parent.code_lens.refresh()
107+
103108
if canceled:
104109
return []
105110

robotcode/utils/async_tools.py

+13
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,19 @@ def create_sub_task(
689689
return result
690690

691691

692+
def create_delayed_sub_task(
693+
coro: Awaitable[_T], *, delay: float, name: Optional[str] = None, loop: Optional[asyncio.AbstractEventLoop] = None
694+
) -> asyncio.Task[Optional[_T]]:
695+
async def run() -> Optional[_T]:
696+
try:
697+
await asyncio.sleep(delay)
698+
return await coro
699+
except asyncio.CancelledError:
700+
return None
701+
702+
return create_sub_task(run(), name=name, loop=loop)
703+
704+
692705
def create_sub_future(loop: Optional[asyncio.AbstractEventLoop] = None) -> asyncio.Future[Any]:
693706

694707
ct = get_current_future_info()

tests/robotcode/language_server/robotframework/parts/data/.vscode/settings.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
}
1414
},
1515
"robotcode.languageServer.args": [
16-
"--debugpy",
16+
// "--debugpy",
1717
// "--log",
1818
// "--log-level",
1919
// "TRACE",

tests/robotcode/language_server/robotframework/parts/data/tests/variables.robot

+8-2
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,19 @@ templated with embedded
138138
1 2 3
139139
3 4 7
140140

141+
templated with embedded
142+
[Template] check that ${a} plus ${b} is ${expected}
143+
a c b
144+
${EMPTY} 4 7
145+
1 2 4 7
146+
141147
templated with embedded not defined
142148
[Template] verify that ${a} plus ${b} is ${expected}
143149
1 2 3
144150
3 4 7
145151

146152
environmentvars
147-
log ${%{LEIDAD_TESTENV}.leidad_server.ip} port=${%{LEIDAD_TESTENV}.leidad_server.port} # TODO
153+
log ${%{TESTENV}.server.ip} port=${%{TESTENV}.server.port} # TODO
148154

149155
*** Keywords ***
150156
do something
@@ -158,7 +164,7 @@ a keyword with loop
158164
Log ${i} ${aaa}
159165
END
160166

161-
check that ${a} plus ${b} is ${expected}
167+
check that ${a:[0-9\ ]*} plus ${b} is ${expected}
162168
log ${a} ${b} ${expected}
163169

164170
templated kw

tests/robotcode/language_server/robotframework/parts/test_references/test_references_robot_034_004_simple_keyword_call_.yml

+14-14
Original file line numberDiff line numberDiff line change
@@ -1699,64 +1699,64 @@ result:
16991699
range:
17001700
end:
17011701
character: 7
1702-
line: 146
1702+
line: 152
17031703
start:
17041704
character: 4
1705-
line: 146
1705+
line: 152
17061706
uri: tests/variables.robot
17071707
- !Location
17081708
range:
17091709
end:
17101710
character: 7
1711-
line: 150
1711+
line: 156
17121712
start:
17131713
character: 4
1714-
line: 150
1714+
line: 156
17151715
uri: tests/variables.robot
17161716
- !Location
17171717
range:
17181718
end:
17191719
character: 37
1720-
line: 151
1720+
line: 157
17211721
start:
17221722
character: 34
1723-
line: 151
1723+
line: 157
17241724
uri: tests/variables.robot
17251725
- !Location
17261726
range:
17271727
end:
17281728
character: 72
1729-
line: 151
1729+
line: 157
17301730
start:
17311731
character: 69
1732-
line: 151
1732+
line: 157
17331733
uri: tests/variables.robot
17341734
- !Location
17351735
range:
17361736
end:
17371737
character: 11
1738-
line: 157
1738+
line: 163
17391739
start:
17401740
character: 8
1741-
line: 157
1741+
line: 163
17421742
uri: tests/variables.robot
17431743
- !Location
17441744
range:
17451745
end:
17461746
character: 7
1747-
line: 161
1747+
line: 167
17481748
start:
17491749
character: 4
1750-
line: 161
1750+
line: 167
17511751
uri: tests/variables.robot
17521752
- !Location
17531753
range:
17541754
end:
17551755
character: 7
1756-
line: 165
1756+
line: 171
17571757
start:
17581758
character: 4
1759-
line: 165
1759+
line: 171
17601760
uri: tests/variables.robot
17611761
- !Location
17621762
range:

0 commit comments

Comments
 (0)