Skip to content

Commit 42fe633

Browse files
committed
fix(langserver): correct highlight, completion, analyze, etc. keyword calls with . that are also valid namespaces
1 parent b480282 commit 42fe633

File tree

7 files changed

+72
-50
lines changed

7 files changed

+72
-50
lines changed

packages/language_server/src/robotcode/language_server/common/parts/completion.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
CompletionItem,
1313
CompletionList,
1414
CompletionOptions,
15+
CompletionOptionsCompletionItemType,
1516
CompletionParams,
1617
CompletionTriggerKind,
1718
InsertReplaceEdit,
@@ -78,6 +79,7 @@ def extend_capabilities(self, capabilities: ServerCapabilities) -> None:
7879
all_commit_characters=commit_chars if commit_chars else None,
7980
resolve_provider=len(self.resolve) > 0,
8081
work_done_progress=True,
82+
completion_item=CompletionOptionsCompletionItemType(label_details_support=True),
8183
)
8284

8385
@rpc_method(name="textDocument/completion", param_type=CompletionParams)

packages/language_server/src/robotcode/language_server/robotframework/diagnostics/analyzer.py

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -426,28 +426,11 @@ async def _analyze_keyword_call(
426426
result is not None
427427
and lib_entry is not None
428428
and kw_namespace
429-
and result.parent_digest is not None
430-
and result.parent_digest != lib_entry.library_doc.digest
429+
and result.parent is not None
430+
and result.parent != lib_entry.library_doc
431431
):
432-
entry = next(
433-
(
434-
v
435-
for v in (await self.namespace.get_libraries()).values()
436-
if v.library_doc.digest == result.parent_digest
437-
),
438-
None,
439-
)
440-
if entry is None:
441-
entry = next(
442-
(
443-
v
444-
for v in (await self.namespace.get_resources()).values()
445-
if v.library_doc.digest == result.parent_digest
446-
),
447-
None,
448-
)
449-
if entry is not None:
450-
lib_entry = entry
432+
lib_entry = None
433+
kw_range = range_from_token(keyword_token)
451434

452435
if kw_namespace and lib_entry is not None and lib_range is not None:
453436
if self.namespace.document is not None:

packages/language_server/src/robotcode/language_server/robotframework/diagnostics/library_doc.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
from robotcode.language_server.robotframework.utils.markdownformatter import (
4545
MarkDownFormatter,
4646
)
47-
from robotcode.language_server.robotframework.utils.match import normalize
47+
from robotcode.language_server.robotframework.utils.match import normalize, normalize_namespace
4848
from robotcode.language_server.robotframework.utils.version import get_robot_version
4949

5050
from .entities import (
@@ -150,16 +150,17 @@ def is_embedded_keyword(name: str) -> bool:
150150

151151

152152
class KeywordMatcher:
153-
def __init__(self, name: str, can_have_embedded: bool = True) -> None:
153+
def __init__(self, name: str, can_have_embedded: bool = True, is_namespace: bool = False) -> None:
154154
self.name = name
155-
self._can_have_embedded = can_have_embedded
155+
self._can_have_embedded = can_have_embedded and not is_namespace
156+
self._is_namespace = is_namespace
156157
self._normalized_name: Optional[str] = None
157158
self._embedded_arguments: Any = None
158159

159160
@property
160161
def normalized_name(self) -> str:
161162
if self._normalized_name is None:
162-
self._normalized_name = str(normalize(self.name))
163+
self._normalized_name = str(normalize_namespace(self.name) if self._is_namespace else normalize(self.name))
163164

164165
return self._normalized_name
165166

@@ -198,7 +199,7 @@ def __eq__(self, o: Any) -> bool:
198199

199200
return self.embedded_arguments.name.match(o) is not None
200201

201-
return self.normalized_name == str(normalize(o))
202+
return self.normalized_name == str(normalize_namespace(o) if self._is_namespace else normalize(o))
202203

203204
@single_call
204205
def __hash__(self) -> int:

packages/language_server/src/robotcode/language_server/robotframework/diagnostics/namespace.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
tokenize_variables,
5252
)
5353
from robotcode.language_server.robotframework.utils.async_ast import Visitor
54-
from robotcode.language_server.robotframework.utils.match import eq
54+
from robotcode.language_server.robotframework.utils.match import eq_namespace
5555
from robotcode.language_server.robotframework.utils.variables import BUILTIN_VARIABLES
5656
from robotcode.language_server.robotframework.utils.version import get_robot_version
5757

@@ -740,7 +740,7 @@ async def get_libraries(self) -> OrderedDict[str, LibraryEntry]:
740740
async def get_libraries_matchers(self) -> Dict[KeywordMatcher, LibraryEntry]:
741741
if self._libraries_matchers is None:
742742
self._libraries_matchers = {
743-
KeywordMatcher(v.alias or v.name or v.import_name, False): v
743+
KeywordMatcher(v.alias or v.name or v.import_name, is_namespace=True): v
744744
for v in (await self.get_libraries()).values()
745745
}
746746
return self._libraries_matchers
@@ -753,7 +753,7 @@ async def get_resources(self) -> OrderedDict[str, ResourceEntry]:
753753
async def get_resources_matchers(self) -> Dict[KeywordMatcher, ResourceEntry]:
754754
if self._resources_matchers is None:
755755
self._resources_matchers = {
756-
KeywordMatcher(v.alias or v.name or v.import_name, False): v
756+
KeywordMatcher(v.alias or v.name or v.import_name, is_namespace=True): v
757757
for v in (await self.get_resources()).values()
758758
}
759759
return self._resources_matchers
@@ -1839,13 +1839,13 @@ def find_keywords(self, owner_name: str, name: str) -> List[Tuple[LibraryEntry,
18391839
if get_robot_version() >= (6, 0):
18401840
result: List[Tuple[LibraryEntry, KeywordDoc]] = []
18411841
for v in self._all_keywords:
1842-
if eq(v.alias or v.name, owner_name):
1842+
if eq_namespace(v.alias or v.name, owner_name):
18431843
result.extend((v, kw) for kw in v.library_doc.keywords.get_all(name))
18441844
return result
18451845

18461846
result = []
18471847
for v in self._all_keywords:
1848-
if eq(v.alias or v.name, owner_name):
1848+
if eq_namespace(v.alias or v.name, owner_name):
18491849
kw = v.library_doc.keywords.get(name, None)
18501850
if kw is not None:
18511851
result.append((v, kw))
@@ -1973,7 +1973,7 @@ def _get_keyword_based_on_search_order(
19731973
) -> List[Tuple[Optional[LibraryEntry], KeywordDoc]]:
19741974
for libname in self.namespace.search_order:
19751975
for e in entries:
1976-
if e[0] is not None and eq(libname, e[0].alias or e[0].name):
1976+
if e[0] is not None and eq_namespace(libname, e[0].alias or e[0].name):
19771977
return [e]
19781978

19791979
return entries

packages/language_server/src/robotcode/language_server/robotframework/parts/completion.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,10 @@ async def create_keyword_completion_items(
720720
if not has_bdd and token is not None:
721721
token = old_token
722722

723+
namespace_name = None
724+
namespace_matcher = None
725+
valid_namespace = False
726+
723727
if token is not None:
724728
r = range_from_token(token)
725729

@@ -737,20 +741,21 @@ def enumerate_indexes(s: str, c: str) -> Iterator[int]:
737741
lib_name_index = e
738742

739743
if lib_name_index >= 0:
740-
library_name = token.value[0 : lib_name_index - r.start.character]
744+
namespace_name = token.value[0 : lib_name_index - r.start.character]
741745

742746
libraries = await self.namespace.get_libraries()
743747

744-
library_name_matcher = KeywordMatcher(library_name)
745-
library_name = next(
746-
(e for e in libraries.keys() if library_name_matcher == KeywordMatcher(e)), library_name
748+
namespace_matcher = KeywordMatcher(namespace_name, is_namespace=True)
749+
namespace_name = next(
750+
(e for e in libraries.keys() if namespace_matcher == KeywordMatcher(e, is_namespace=True)),
751+
namespace_name,
747752
)
748-
valid_namespace = False
749-
if library_name in libraries:
753+
754+
if namespace_name in libraries:
750755
valid_namespace = True
751756

752757
r.start.character = lib_name_index + 1
753-
for kw in libraries[library_name].library_doc.keywords.values():
758+
for kw in libraries[namespace_name].library_doc.keywords.values():
754759
if kw.is_error_handler:
755760
continue
756761
result.append(
@@ -759,7 +764,7 @@ def enumerate_indexes(s: str, c: str) -> Iterator[int]:
759764
kind=CompletionItemKind.FUNCTION,
760765
detail=f"{CompleteResultKind.KEYWORD.value} "
761766
f"{f'({kw.libname})' if kw.libname is not None else ''}",
762-
sort_text=f"020_{kw.name}",
767+
sort_text=f"019_{kw.name}",
763768
insert_text_format=InsertTextFormat.PLAIN_TEXT
764769
if not kw.is_embedded
765770
else InsertTextFormat.SNIPPET,
@@ -785,7 +790,7 @@ def enumerate_indexes(s: str, c: str) -> Iterator[int]:
785790
resources = {
786791
k: v
787792
for k, v in (await self.namespace.get_resources()).items()
788-
if library_name_matcher == KeywordMatcher(v.name)
793+
if namespace_matcher == KeywordMatcher(v.name, is_namespace=True)
789794
}
790795

791796
if resources:
@@ -802,7 +807,7 @@ def enumerate_indexes(s: str, c: str) -> Iterator[int]:
802807
kind=CompletionItemKind.FUNCTION,
803808
detail=f"{CompleteResultKind.KEYWORD.value} "
804809
f"{f'({kw.libname})' if kw.libname is not None else ''}",
805-
sort_text=f"020_{kw.name}",
810+
sort_text=f"019_{kw.name}",
806811
insert_text_format=InsertTextFormat.PLAIN_TEXT
807812
if not kw.is_embedded
808813
else InsertTextFormat.SNIPPET,
@@ -822,15 +827,25 @@ def enumerate_indexes(s: str, c: str) -> Iterator[int]:
822827
),
823828
)
824829
)
825-
if result and valid_namespace:
826-
return result
830+
831+
if token is not None:
832+
r = range_from_token(token)
827833

828834
if r is None:
829835
r = Range(position, position)
830836

837+
if namespace_matcher is not None:
838+
namespace_matcher = KeywordMatcher(namespace_matcher.name)
839+
831840
for kw in await self.namespace.get_keywords():
832841
if kw.is_error_handler:
833842
continue
843+
if (
844+
valid_namespace
845+
and namespace_matcher is not None
846+
and not kw.matcher.normalized_name.startswith(namespace_matcher.normalized_name)
847+
):
848+
continue
834849

835850
result.append(
836851
CompletionItem(
@@ -855,6 +870,9 @@ def enumerate_indexes(s: str, c: str) -> Iterator[int]:
855870
)
856871
)
857872

873+
if valid_namespace and namespace_matcher is not None:
874+
return result
875+
858876
for k, v in (await self.namespace.get_libraries()).items():
859877
result.append(
860878
CompletionItem(

packages/language_server/src/robotcode/language_server/robotframework/parts/semantic_tokens.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ def mapping(cls) -> Dict[str, Tuple[Enum, Optional[Set[Enum]]]]:
261261
)
262262
BDD_TOKEN_REGEX = re.compile(r"^(Given|When|Then|And|But)\s", flags=re.IGNORECASE)
263263

264-
BUILTIN_MATCHER = KeywordMatcher("BuiltIn")
264+
BUILTIN_MATCHER = KeywordMatcher("BuiltIn", is_namespace=True)
265265

266266
@classmethod
267267
async def generate_sem_sub_tokens(
@@ -382,11 +382,20 @@ async def generate_sem_sub_tokens(
382382

383383
kw_namespace: Optional[str] = None
384384
kw: str = token.value
385+
kw_doc = await namespace.find_keyword(token.value, raise_keyword_error=False)
385386

386387
for lib, name in iter_over_keyword_names_and_owners(token.value):
387388
if lib is not None and (
388-
any(k for k in libraries_matchers.keys() if k == lib)
389-
or any(k for k in resources_matchers.keys() if k == lib)
389+
any(
390+
k
391+
for k, v in libraries_matchers.items()
392+
if k == lib and (kw_doc is None or kw_doc.parent == v.library_doc)
393+
)
394+
or any(
395+
k
396+
for k, v in resources_matchers.items()
397+
if k == lib and (kw_doc is None or kw_doc.parent == v.library_doc)
398+
)
390399
):
391400
kw_namespace = lib
392401
if name:
@@ -411,13 +420,11 @@ async def generate_sem_sub_tokens(
411420
)
412421

413422
if builtin_library_doc is not None and kw in builtin_library_doc.keywords:
414-
doc = await namespace.find_keyword(token.value)
415-
if doc is not None and doc.libname == cls.BUILTIN_MATCHER and doc.matcher == kw:
423+
if kw_doc is not None and kw_doc.libname == cls.BUILTIN_MATCHER and kw_doc.matcher == kw:
416424
if not sem_mod:
417425
sem_mod = set()
418426
sem_mod.add(RobotSemTokenModifiers.BUILTIN)
419427

420-
kw_doc = await namespace.find_keyword(token.value, raise_keyword_error=False)
421428
if kw_doc is not None and kw_doc.is_embedded:
422429
if get_robot_version() >= (6, 0):
423430
m = kw_doc.matcher.embedded_arguments.match(kw)

packages/language_server/src/robotcode/language_server/robotframework/utils/match.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,18 @@ def normalize(text: str) -> str:
66
return text.lower().replace("_", "").replace(" ", "")
77

88

9+
@lru_cache(maxsize=5000)
10+
def normalize_namespace(text: str) -> str:
11+
return text.lower().replace(" ", "")
12+
13+
914
def eq(str1: str, str2: str) -> bool:
1015
str1 = normalize(str1)
1116
str2 = normalize(str2)
1217
return str1 == str2
18+
19+
20+
def eq_namespace(str1: str, str2: str) -> bool:
21+
str1 = normalize_namespace(str1)
22+
str2 = normalize_namespace(str2)
23+
return str1 == str2

0 commit comments

Comments
 (0)