Skip to content

Commit a917459

Browse files
committed
implement basic variable completion
1 parent 9a8949e commit a917459

File tree

4 files changed

+289
-18
lines changed

4 files changed

+289
-18
lines changed

.flake8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ exclude =
1111
external
1212
playground
1313

14-
ignore = N805,W503,N818
14+
ignore = N805,W503,N818,E203
1515
per-file-ignores=__init__.py:F401,F403
1616

robotcode/language_server/robotframework/diagnostics/library_doc.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,43 @@ def error_from_exception(ex: BaseException, default_source: Optional[str], defau
714714
)
715715

716716

717+
BUILTIN_VARIABLES = [
718+
"${CURDIR}",
719+
"${TEMPDIR}",
720+
"${EXECDIR}",
721+
"${/}",
722+
"${:}",
723+
"${\\n}",
724+
"${SPACE}",
725+
"${True}",
726+
"${False}",
727+
"${None}",
728+
"${null}",
729+
"${TEST NAME}",
730+
"@{TEST TAGS}",
731+
"${TEST DOCUMENTATION}",
732+
"${TEST STATUS}",
733+
"${TEST MESSAGE}",
734+
"${PREV TEST NAME}",
735+
"${PREV TEST STATUS}",
736+
"${PREV TEST MESSAGE}",
737+
"${SUITE NAME}",
738+
"${SUITE SOURCE}",
739+
"${SUITE DOCUMENTATION}",
740+
"&{SUITE METADATA}",
741+
"${SUITE STATUS}",
742+
"${SUITE MESSAGE}",
743+
"${KEYWORD STATUS}",
744+
"${KEYWORD MESSAGE}",
745+
"${LOG LEVEL}",
746+
"${OUTPUT FILE}",
747+
"${LOG FILE}",
748+
"${REPORT FILE}",
749+
"${DEBUG FILE}",
750+
"${OUTPUT DIR}",
751+
]
752+
753+
717754
def init_builtin_variables(
718755
working_dir: str = ".", base_dir: str = ".", variables: Optional[Dict[str, Optional[Any]]] = None
719756
) -> Dict[str, Optional[Any]]:

robotcode/language_server/robotframework/diagnostics/namespace.py

Lines changed: 134 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import weakref
66
from collections import OrderedDict
77
from dataclasses import dataclass, field
8+
from enum import Enum
89
from pathlib import Path
910
from typing import (
1011
Any,
@@ -38,6 +39,7 @@
3839
from .imports_manager import ImportsManager
3940
from .library_doc import (
4041
BUILTIN_LIBRARY_NAME,
42+
BUILTIN_VARIABLES,
4143
DEFAULT_LIBRARIES,
4244
KeywordDoc,
4345
KeywordMatcher,
@@ -61,15 +63,19 @@ class ImportError(DiagnosticsError):
6163

6264

6365
@dataclass
64-
class Import:
65-
name: Optional[str]
66-
name_token: Optional[Token]
66+
class SourceEntity:
6767
line_no: int
6868
col_offset: int
6969
end_line_no: int
7070
end_col_offset: int
7171
source: str
7272

73+
74+
@dataclass
75+
class Import(SourceEntity):
76+
name: Optional[str]
77+
name_token: Optional[Token]
78+
7379
def range(self) -> Range:
7480
return Range(
7581
start=Position(
@@ -114,14 +120,55 @@ def __hash__(self) -> int:
114120
class VariablesImport(Import):
115121
args: Tuple[str, ...] = ()
116122

123+
def __hash__(self) -> int:
124+
return hash(
125+
(
126+
type(self),
127+
self.name,
128+
self.args,
129+
)
130+
)
131+
132+
133+
class VariableDefinitionType(Enum):
134+
VARIABLE = "Variable"
135+
ARGUMENT = "Argument"
136+
BUILTIN_VARIABLE = "Variable (Builtin)"
137+
138+
139+
@dataclass
140+
class VariableDefinition(SourceEntity):
141+
name: Optional[str]
142+
name_token: Optional[Token]
143+
type: VariableDefinitionType = VariableDefinitionType.VARIABLE
144+
145+
def __hash__(self) -> int:
146+
return hash((type(self), self.name, self.type))
147+
148+
149+
@dataclass
150+
class BuiltInVariableDefinition(VariableDefinition):
151+
type: VariableDefinitionType = VariableDefinitionType.BUILTIN_VARIABLE
152+
153+
def __hash__(self) -> int:
154+
return hash((type(self), self.name, self.type))
155+
156+
157+
@dataclass
158+
class ArgumentDefinition(VariableDefinition):
159+
type: VariableDefinitionType = VariableDefinitionType.ARGUMENT
160+
161+
def __hash__(self) -> int:
162+
return hash((type(self), self.name, self.type))
163+
117164

118165
class NameSpaceError(Exception):
119166
pass
120167

121168

122169
class VariablesVisitor(AsyncVisitor):
123-
async def get(self, source: str, model: ast.AST) -> List[str]:
124-
self._results: List[str] = []
170+
async def get(self, source: str, model: ast.AST) -> List[VariableDefinition]:
171+
self._results: List[VariableDefinition] = []
125172
self.source = source
126173
await self.visit(model)
127174
return self._results
@@ -133,11 +180,58 @@ async def visit_Section(self, node: ast.AST) -> None: # noqa: N802
133180
await self.generic_visit(node)
134181

135182
async def visit_Variable(self, node: ast.AST) -> None: # noqa: N802
183+
from robot.parsing.lexer.tokens import Token
136184
from robot.parsing.model.statements import Variable
137185

138186
n = cast(Variable, node)
187+
name = n.get_value(Token.VARIABLE)
139188
if n.name:
140-
self._results.append(n.name)
189+
self._results.append(
190+
VariableDefinition(
191+
name=n.name,
192+
name_token=name if name is not None else None,
193+
line_no=node.lineno,
194+
col_offset=node.col_offset,
195+
end_line_no=node.end_lineno if node.end_lineno is not None else -1,
196+
end_col_offset=node.end_col_offset if node.end_col_offset is not None else -1,
197+
source=self.source,
198+
)
199+
)
200+
201+
202+
class ArgumentsVisitor(AsyncVisitor):
203+
async def get(self, source: str, model: ast.AST) -> List[VariableDefinition]:
204+
self._results: List[VariableDefinition] = []
205+
self.source = source
206+
await self.visit(model)
207+
return self._results
208+
209+
async def visit_Section(self, node: ast.AST) -> None: # noqa: N802
210+
from robot.parsing.model.blocks import VariableSection
211+
212+
if isinstance(node, VariableSection):
213+
await self.generic_visit(node)
214+
215+
async def visit_Arguments(self, node: ast.AST) -> None: # noqa: N802
216+
from robot.parsing.lexer.tokens import Token as RobotToken
217+
from robot.parsing.model.statements import Arguments
218+
from robot.variables.search import is_variable
219+
220+
n = cast(Arguments, node)
221+
arguments = n.get_tokens(RobotToken.ARGUMENT)
222+
for argument in (cast(RobotToken, e) for e in arguments):
223+
if is_variable(argument.value):
224+
self._results.append(
225+
ArgumentDefinition(
226+
name=argument.value,
227+
name_token=argument,
228+
line_no=argument.lineno,
229+
col_offset=argument.col_offset,
230+
end_line_no=argument.lineno if argument.lineno is not None else -1,
231+
end_col_offset=argument.end_col_offset if argument.end_col_offset is not None else -1,
232+
source=self.source,
233+
)
234+
)
141235

142236

143237
class ImportVisitor(AsyncVisitor):
@@ -590,18 +684,14 @@ def __str__(self) -> str:
590684
@dataclass
591685
class ResourceEntry(LibraryEntry):
592686
imports: List[Import] = field(default_factory=lambda: [])
593-
variables: List[str] = field(default_factory=lambda: [])
687+
variables: List[VariableDefinition] = field(default_factory=lambda: [])
594688

595689

596690
@dataclass
597691
class VariablesEntry(LibraryEntry):
598692
pass
599693

600694

601-
IMPORTS_KEY = object()
602-
REFERENCED_DOCUMENTS_KEY = object()
603-
604-
605695
class Namespace:
606696
_logger = LoggingDescriptor()
607697

@@ -632,7 +722,8 @@ def __init__(
632722
self._analyze_lock = asyncio.Lock()
633723
self._library_doc: Optional[LibraryDoc] = None
634724
self._imports: Optional[List[Import]] = None
635-
self._own_variables: Optional[List[str]] = None
725+
self._own_variables: Optional[List[VariableDefinition]] = None
726+
self._variables_definitions: Optional[Dict[str, VariableDefinition]] = None
636727
self._diagnostics: List[Diagnostic] = []
637728

638729
self._keywords: Optional[List[KeywordDoc]] = None
@@ -732,12 +823,42 @@ async def get_imports(self) -> List[Import]:
732823

733824
return self._imports
734825

735-
async def get_own_variables(self) -> List[str]:
826+
async def get_own_variables(self) -> List[VariableDefinition]:
736827
if self._own_variables is None:
737828
self._own_variables = await VariablesVisitor().get(self.source, self.model)
738829

739830
return self._own_variables
740831

832+
_builtin_variables: Optional[List[BuiltInVariableDefinition]] = None
833+
834+
@classmethod
835+
def get_builtin_variables(cls) -> List[BuiltInVariableDefinition]:
836+
if cls._builtin_variables is None:
837+
cls._builtin_variables = [BuiltInVariableDefinition(0, 0, 0, 0, "", n, None) for n in BUILTIN_VARIABLES]
838+
839+
return cls._builtin_variables
840+
841+
async def get_variables(self, nodes: Optional[List[ast.AST]] = None) -> Dict[str, VariableDefinition]:
842+
from robot.parsing.model.blocks import Keyword
843+
844+
await self._ensure_initialized()
845+
846+
if self._variables_definitions is None:
847+
result: Dict[str, VariableDefinition] = {}
848+
849+
async for var in async_chain(
850+
*[await ArgumentsVisitor().get(self.source, n) for n in nodes or [] if isinstance(n, Keyword)],
851+
(e for e in await self.get_own_variables()),
852+
*(e.variables for e in self._resources.values()),
853+
(e for e in self.get_builtin_variables()),
854+
):
855+
if var.name is not None and var.name not in result.keys():
856+
result[var.name] = var
857+
858+
self._variables_definitions = result
859+
860+
return self._variables_definitions
861+
741862
async def _import_imports(self, imports: Iterable[Import], base_dir: str, *, top_level: bool = False) -> None:
742863
async def _import(value: Import) -> Optional[LibraryEntry]:
743864
result: Optional[LibraryEntry] = None

0 commit comments

Comments
 (0)