Skip to content

Commit cefdfb1

Browse files
committed
highlight/hover/references namespaces from resources and libraries
1 parent b0cd4a5 commit cefdfb1

File tree

5 files changed

+151
-58
lines changed

5 files changed

+151
-58
lines changed

robotcode/language_server/robotframework/parts/document_highlight.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,27 +133,39 @@ async def highlight_KeywordCall( # noqa: N802
133133
return None
134134

135135
kw_node = cast(KeywordCall, node)
136-
keyword = await self.get_keyworddoc_and_token_from_position(
136+
result = await self.get_keyworddoc_and_token_from_position(
137137
kw_node.keyword,
138138
cast(Token, kw_node.get_token(RobotToken.KEYWORD)),
139139
[cast(Token, t) for t in kw_node.get_tokens(RobotToken.ARGUMENT)],
140140
namespace,
141141
position,
142142
)
143143

144-
if keyword is not None and keyword[0] is not None:
145-
source = keyword[0].source
146-
if source is not None:
144+
if result is not None:
145+
keyword_doc, keyword_token = result
146+
147+
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
148+
149+
kw_range = range_from_token(keyword_token)
150+
151+
if lib_entry and kw_namespace:
152+
r = range_from_token(keyword_token)
153+
r.end.character = r.start.character + len(kw_namespace)
154+
kw_range.start.character = r.end.character + 1
155+
if position in r:
156+
# TODO highlight namespaces
157+
return None
158+
if keyword_doc is not None and not keyword_doc.is_error_handler and keyword_doc.source:
147159
return [
148160
*(
149-
[DocumentHighlight(keyword[0].range, DocumentHighlightKind.TEXT)]
150-
if keyword[0].source == str(document.uri.to_path())
161+
[DocumentHighlight(keyword_doc.range, DocumentHighlightKind.TEXT)]
162+
if keyword_doc.source == str(document.uri.to_path())
151163
else []
152164
),
153165
*(
154166
DocumentHighlight(e.range, DocumentHighlightKind.TEXT)
155167
for e in await self.parent.robot_references.find_keyword_references_in_file(
156-
document, keyword[0]
168+
document, keyword_doc
157169
)
158170
),
159171
]

robotcode/language_server/robotframework/parts/hover.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ async def _hover_Template_or_TestTemplate( # noqa: N802
231231
return None
232232

233233
keyword_doc = await namespace.find_keyword(template_node.value)
234+
234235
if keyword_doc is not None:
235236

236237
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)

robotcode/language_server/robotframework/parts/references.py

Lines changed: 98 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -293,25 +293,40 @@ async def references_KeywordCall( # noqa: N802
293293
return None
294294

295295
kw_node = cast(KeywordCall, node)
296-
keyword = await self.get_keyworddoc_and_token_from_position(
296+
result = await self.get_keyworddoc_and_token_from_position(
297297
kw_node.keyword,
298298
cast(Token, kw_node.get_token(RobotToken.KEYWORD)),
299299
[cast(Token, t) for t in kw_node.get_tokens(RobotToken.ARGUMENT)],
300300
namespace,
301301
position,
302302
)
303303

304-
if keyword is not None and keyword[0] is not None:
305-
source = keyword[0].source
306-
if source is not None:
307-
return [
308-
*(
309-
[Location(uri=str(Uri.from_path(source)), range=keyword[0].range)]
310-
if context.include_declaration
311-
else []
312-
),
313-
*await self.find_keyword_references(document, keyword[0]),
314-
]
304+
if result is not None:
305+
keyword_doc, keyword_token = result
306+
307+
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
308+
309+
kw_range = range_from_token(keyword_token)
310+
311+
if lib_entry and kw_namespace:
312+
r = range_from_token(keyword_token)
313+
r.end.character = r.start.character + len(kw_namespace)
314+
kw_range.start.character = r.end.character + 1
315+
if position in r:
316+
# TODO: find references for Library Namespace
317+
return None
318+
319+
if keyword_doc is not None:
320+
source = keyword_doc.source
321+
if source is not None:
322+
return [
323+
*(
324+
[Location(uri=str(Uri.from_path(source)), range=kw_range)]
325+
if context.include_declaration
326+
else []
327+
),
328+
*await self.find_keyword_references(document, keyword_doc),
329+
]
315330

316331
return None
317332

@@ -326,25 +341,39 @@ async def references_Fixture( # noqa: N802
326341
return None
327342

328343
fixture_node = cast(Fixture, node)
329-
keyword = await self.get_keyworddoc_and_token_from_position(
344+
result = await self.get_keyworddoc_and_token_from_position(
330345
fixture_node.name,
331346
cast(Token, fixture_node.get_token(RobotToken.NAME)),
332347
[cast(Token, t) for t in fixture_node.get_tokens(RobotToken.ARGUMENT)],
333348
namespace,
334349
position,
335350
)
336351

337-
if keyword is not None and keyword[0] is not None:
338-
source = keyword[0].source
339-
if source is not None:
340-
return [
341-
*(
342-
[Location(uri=str(Uri.from_path(source)), range=keyword[0].range)]
343-
if context.include_declaration
344-
else []
345-
),
346-
*await self.find_keyword_references(document, keyword[0]),
347-
]
352+
if result is not None:
353+
keyword_doc, keyword_token = result
354+
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
355+
356+
kw_range = range_from_token(keyword_token)
357+
358+
if lib_entry and kw_namespace:
359+
r = range_from_token(keyword_token)
360+
r.end.character = r.start.character + len(kw_namespace)
361+
kw_range.start.character = r.end.character + 1
362+
if position in r:
363+
# TODO: find references for Library Namespace
364+
return None
365+
366+
if keyword_doc is not None and not keyword_doc.is_error_handler:
367+
source = keyword_doc.source
368+
if source is not None:
369+
return [
370+
*(
371+
[Location(uri=str(Uri.from_path(source)), range=kw_range)]
372+
if context.include_declaration
373+
else []
374+
),
375+
*await self.find_keyword_references(document, keyword_doc),
376+
]
348377

349378
return None
350379

@@ -357,30 +386,44 @@ async def _references_Template_or_TestTemplate( # noqa: N802
357386
node = cast(Union[Template, TestTemplate], node)
358387
if node.value:
359388

360-
name_token = cast(RobotToken, node.get_token(RobotToken.NAME, RobotToken.ARGUMENT))
361-
if name_token is None:
389+
keyword_token = cast(RobotToken, node.get_token(RobotToken.NAME, RobotToken.ARGUMENT))
390+
if keyword_token is None:
362391
return None
363392

364-
if position.is_in_range(range_from_token(name_token)):
393+
if position.is_in_range(range_from_token(keyword_token)):
365394
namespace = await self.parent.documents_cache.get_namespace(document)
366395
if namespace is None:
367396
return None
368397

369-
keyword = await namespace.find_keyword(node.value)
370-
if keyword is not None and keyword.source is not None:
371-
return [
372-
*(
373-
[
374-
Location(
375-
uri=str(Uri.from_path(keyword.source)),
376-
range=keyword.range,
377-
)
378-
]
379-
if context.include_declaration
380-
else []
381-
),
382-
*await self.find_keyword_references(document, keyword),
383-
]
398+
keyword_doc = await namespace.find_keyword(node.value)
399+
400+
if keyword_doc is not None:
401+
402+
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
403+
404+
kw_range = range_from_token(keyword_token)
405+
406+
if lib_entry and kw_namespace:
407+
r = range_from_token(keyword_token)
408+
r.end.character = r.start.character + len(kw_namespace)
409+
kw_range.start.character = r.end.character + 1
410+
if position in r:
411+
# TODO: find references for Library Namespace
412+
return None
413+
if keyword_doc and keyword_doc.source:
414+
return [
415+
*(
416+
[
417+
Location(
418+
uri=str(Uri.from_path(keyword_doc.source)),
419+
range=kw_range,
420+
)
421+
]
422+
if context.include_declaration
423+
else []
424+
),
425+
*await self.find_keyword_references(document, keyword_doc),
426+
]
384427

385428
return None
386429

@@ -471,15 +514,23 @@ async def get_keyword_references_from_tokens(
471514
kw_matcher = KeywordMatcher(kw_doc.name)
472515
kw_name = unescape(kw_token.value) if unescape_kw_token else kw_token.value
473516

517+
libraries_matchers = await namespace.get_libraries_matchers()
518+
resources_matchers = await namespace.get_resources_matchers()
519+
474520
for lib, name in iter_over_keyword_names_and_owners(kw_name):
475521
if lib is not None:
476522
lib_matcher = KeywordMatcher(lib)
477-
if (
478-
lib_matcher not in (await namespace.get_libraries_matchers()).keys()
479-
and lib_matcher not in (await namespace.get_resources_matchers()).keys()
480-
):
523+
if lib_matcher not in libraries_matchers and lib_matcher not in resources_matchers:
481524
continue
482525

526+
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, kw_token)
527+
kw_range = range_from_token(kw_token)
528+
529+
if lib_entry and kw_namespace:
530+
r = range_from_token(kw_token)
531+
r.end.character = r.start.character + len(kw_namespace)
532+
kw_range.start.character = r.end.character + 1
533+
483534
if name is not None:
484535
name_matcher = KeywordMatcher(name)
485536
if kw_matcher == name_matcher:
@@ -488,7 +539,7 @@ async def get_keyword_references_from_tokens(
488539
if kw is not None and kw == kw_doc:
489540
yield Location(
490541
str(Uri.from_path(namespace.source).normalized()),
491-
range=range_from_token_or_node(node, kw_token),
542+
range=kw_range,
492543
)
493544

494545
if name_matcher in ALL_RUN_KEYWORDS_MATCHERS and arguments:

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,27 @@ first
1818
Log Hello ${A VAR}
1919
# ^^^^^^^ Variable: len(result) == 1 and result[0].target_uri.endswith("/data/goto.robot")
2020
# ^^^ BuiltIn Keyword: len(result) == 1 and result[0].target_uri.endswith("robot/libraries/BuiltIn.py")
21+
2122
Collections.Log Dictionary ${A DICT}
2223
# ^^^^^^^^^ Variable: len(result) == 1 and result[0].target_uri.endswith("/data/goto.robot")
2324
# ^^^^^^^^^^^^^^ Robot Library Keyword: len(result) == 1 and result[0].target_uri.endswith("robot/libraries/Collections.py")
25+
# ^^^^^^^^^^^ Robot Namespace from Library: len(result) == 1 and result[0].target_uri.endswith("data/goto.robot")
26+
27+
BuiltIn.Log Hello ${A VAR}
28+
# ^^^ BuiltIn Keyword with Namespace: len(result) == 1 and result[0].target_uri.endswith("robot/libraries/BuiltIn.py")
29+
# ^^^^^^^ Robot BuilIn Namespace: len(result) == 1 and result[0].target_uri.endswith("robot/libraries/BuiltIn.py")
30+
2431
FOR ${key} ${value} IN &{A DICT}
2532
Log ${key}=${value}
2633
# ^^^^^^ For Variable: len(result) == 1 and result[0].target_uri.endswith("/data/goto.robot")
2734
# ^^^^^^^^ For Variable: len(result) == 1 and result[0].target_uri.endswith("/data/goto.robot")
2835
END
2936
Log ${A_VAR_FROM_LIB}
3037
# ^^^^^^^^^^^^^^^^^ Imported Variable: len(result) == 1 and result[0].target_uri.endswith("libs/myvariables.py")
38+
39+
do something in a resource
40+
# ^^^^^^^^^^^^^^^^^^^^^^^^^^ Keyword from resource: len(result) == 1 and result[0].target_uri.endswith("/data/resources/firstresource.resource")
41+
42+
firstresource.do something in a resource
43+
# ^^^^^^^^^^^^^^^^^^^^^^^^^^ Keyword from resource: len(result) == 1 and result[0].target_uri.endswith("/data/resources/firstresource.resource")
44+
# ^^^^^^^^^^^^^ Namespace from resource: len(result) == 1 and result[0].target_uri.endswith("/data/goto.robot")

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

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ Library Collections
33
# ^^^^^^^^^^^ library import by module name: re.match(r'## Library \*Collections\*.*', value)
44
Library ${CURDIR}/libs/myvariables.py
55
# ^^^^^^^^^^^^^^ library import by path name: re.match(r'## Library \*myvariables.*', value)
6-
# TODO ^^^^^^^^^ variable in library import: value == '(builtin variable) ${CURDIR}'
6+
# ^^^^^^^^^ variable in library import: value == '(builtin variable) ${CURDIR}'
77
Variables ${CURDIR}/libs/myvariables.py
8-
# TODO ^^^^^^^^^ variable in variables import: value == '(builtin variable) ${CURDIR}'
8+
# ^^^^^^^^^ variable in variables import: value == '(builtin variable) ${CURDIR}'
99
# ^^^^^^^^^^^^^^ variable import by path name: re.match(r'## Variables \*myvariables.*', value)
1010
Resource ${CURDIR}/resources/firstresource.resource
1111
# ^^^^^^^^^^^^^^ resource import by path name: re.match(r'## Resource \*firstresource.*', value)
12-
# TODO ^^^^^^^^^ variable in resource import: value == '(builtin variable) ${CURDIR}'
12+
# ^^^^^^^^^ variable in resource import: value == '(builtin variable) ${CURDIR}'
1313

1414
*** Variables ***
1515
${A VAR} i'm a var
@@ -19,10 +19,17 @@ ${A VAR} i'm a var
1919

2020
*** Test Cases ***
2121
first
22+
[Setup] Log Hello ${A VAR}
23+
# ^^^ Keyword in Setup: re.match(r'.*Log.*', value)
24+
[Teardown] Log Hello ${A VAR}
25+
# ^^^ Keyword in Teardown: re.match(r'.*Log.*', value)
26+
2227
Log Hello ${A VAR}
28+
# ^^^ Keyword from Library: re.match(r'.*Log.*', value)
29+
2330
Collections.Log Dictionary ${A DICT}
2431
# ^^^^^^^^^^^^^^ Keyword with namespace: re.match(r'.*Log Dictionary.*', value)
25-
# TODO ^^^^^^^^^^^ namespace before keyword: re.match(r'.*Collections.*', value)
32+
# ^^^^^^^^^^^ namespace before keyword: re.match(r'.*Collections.*', value)
2633
FOR ${key} ${value} IN &{A DICT}
2734
# ^^^^^^ FOR loop variable declaration: value == '(local variable) ${key}'
2835
Log ${key}=${value}
@@ -37,6 +44,13 @@ first
3744
Log ${A_VAR_FROM_LIB}
3845
# ^^^^^^^^^^^^^^^^^ variable from lib: value == '(imported variable) ${A_VAR_FROM_LIB}'
3946

47+
do something in a resource
48+
# ^^^^^^^^^^^^^^^^^^^^^^^^^^ Keyword from resource: re.match(r'.*do something in a resource.*', value)
49+
50+
firstresource.do something in a resource
51+
# ^^^^^^^^^^^^^^^^^^^^^^^^^^ KeywordCall from resource with Namespace: re.match(r'.*do something in a resource.*', value)
52+
# ^^^^^^^^^^^^^ Namespace from resource: re.match(r'## Resource \*firstresource.*', value)
53+
4054

4155
*** Keywords ***
4256
a keyword
@@ -57,6 +71,7 @@ a keyword
5771
# ^^^ AND: result is None
5872

5973
a simple keyword
74+
#^^^^^^^^^^^^^^^ simple keyword with extra spaces and parameter: re.match(r'.*a simple keyword.*', value)
6075
Pass Execution
6176

6277
sleep a while

0 commit comments

Comments
 (0)