Skip to content

Commit 9f2e415

Browse files
committed
extend semantic highlight, complete embeded arguments
1 parent bd88503 commit 9f2e415

File tree

10 files changed

+185
-46
lines changed

10 files changed

+185
-46
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ All notable changes to the "robotcode" extension will be documented in this file
44

55
## [Unreleased]
66

7+
## added
8+
9+
- extend sematic higlightning
10+
- builtin library keywords are declared as default_library modifier
11+
- higlight variables in keyword names and keyword calls
12+
- complete embedded arguments
13+
714
## 0.2.9
815

916
### added

robotcode/debugger/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ async def run_robot(
126126

127127
from ..utils.debugpy import enable_debugpy, wait_for_debugpy_connected
128128
from ..utils.net import check_free_port
129-
from .debugger import Debugger
130129
from .dap_types import Event
130+
from .debugger import Debugger
131131

132132
@_logger.call
133133
async def start_debugpy_async() -> None:

robotcode/debugger/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
from ..jsonrpc2.server import TcpParams
77
from ..utils.logging import LoggingDescriptor
8-
from .protocol import DebugAdapterProtocol
98
from .dap_types import Event
9+
from .protocol import DebugAdapterProtocol
1010

1111

1212
class DAPClientProtocol(DebugAdapterProtocol):

robotcode/debugger/launcher/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from ...jsonrpc2.server import JsonRPCServer, JsonRpcServerMode, TcpParams
1010
from ...utils.logging import LoggingDescriptor
1111
from ..client import DAPClient, DAPClientError
12-
from ..protocol import DebugAdapterProtocol
1312
from ..dap_types import (
1413
Capabilities,
1514
ConfigurationDoneArguments,
@@ -31,6 +30,7 @@
3130
TerminatedEvent,
3231
TerminateRequest,
3332
)
33+
from ..protocol import DebugAdapterProtocol
3434

3535

3636
class OutputProtocol(asyncio.SubprocessProtocol):

robotcode/debugger/listeners.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from typing import Any, Dict, Iterator, List, Optional, Union, cast
22

3-
from .debugger import Debugger
43
from .dap_types import Event, Model
4+
from .debugger import Debugger
55

66

77
class RobotExecutionEvent(Model):

robotcode/debugger/server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
from ..jsonrpc2.protocol import rpc_method
66
from ..jsonrpc2.server import JsonRPCServer, JsonRpcServerMode, TcpParams
77
from ..utils.logging import LoggingDescriptor
8-
from .debugger import Debugger
9-
from .protocol import DebugAdapterProtocol
108
from .dap_types import (
119
ConfigurationDoneArguments,
1210
ContinueArguments,
@@ -40,6 +38,8 @@
4038
VariablesArguments,
4139
VariablesResponseBody,
4240
)
41+
from .debugger import Debugger
42+
from .protocol import DebugAdapterProtocol
4343

4444
TCP_DEFAULT_PORT = 6612
4545

robotcode/language_server/robotframework/diagnostics/library_doc.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ def __repr__(self) -> str:
130130
return f"{type(self).__name__}(name={repr(self.name)})"
131131

132132

133+
class InvalidVariableError(Exception):
134+
pass
135+
136+
133137
class VariableMatcher:
134138
def __init__(self, name: str) -> None:
135139
from robot.utils.normalizing import normalize
@@ -138,6 +142,9 @@ def __init__(self, name: str) -> None:
138142
searcher = VariableSearcher("$@&%", ignore_errors=True)
139143
match = searcher.search(name)
140144
self.name = name
145+
if match.base is None:
146+
raise InvalidVariableError(f"Invalid variable '{name}'")
147+
141148
self.base = match.base
142149
self.normalized_name = str(normalize(match.base, "_"))
143150

robotcode/language_server/robotframework/diagnostics/namespace.py

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import ast
44
import asyncio
5+
import itertools
56
import weakref
67
from collections import OrderedDict
78
from dataclasses import dataclass, field
@@ -20,7 +21,6 @@
2021
Tuple,
2122
cast,
2223
)
23-
import itertools
2424

2525
from ....utils.async_itertools import async_chain
2626
from ....utils.logging import LoggingDescriptor
@@ -35,7 +35,12 @@
3535
Position,
3636
Range,
3737
)
38-
from ..utils.ast import Token, is_non_variable_token, range_from_token_or_node
38+
from ..utils.ast import (
39+
Token,
40+
is_non_variable_token,
41+
range_from_token_or_node,
42+
tokenize_variables,
43+
)
3944
from ..utils.async_ast import AsyncVisitor
4045
from .imports_manager import ImportsManager
4146
from .library_doc import (
@@ -220,6 +225,46 @@ async def get(self, source: str, model: ast.AST) -> List[VariableDefinition]:
220225
await self.visit(model)
221226
return self._results
222227

228+
async def visit_KeywordName(self, node: ast.AST) -> None: # noqa: N802
229+
from robot.parsing.lexer.tokens import Token as RobotToken
230+
from robot.parsing.model.statements import KeywordName
231+
from robot.variables.search import VariableSearcher
232+
233+
n = cast(KeywordName, node)
234+
name_token = cast(Token, n.get_token(RobotToken.KEYWORD_NAME))
235+
236+
if name_token is not None and name_token.value:
237+
for a in filter(
238+
lambda e: e.type == RobotToken.VARIABLE,
239+
tokenize_variables(name_token, identifiers="$", ignore_errors=True),
240+
):
241+
if a.value:
242+
searcher = VariableSearcher("$", ignore_errors=True)
243+
match = searcher.search(a.value)
244+
if match.base is None:
245+
continue
246+
name = f"{match.identifier}{{{match.base.split(':', 1)[0]}}}"
247+
248+
self._results.append(
249+
ArgumentDefinition(
250+
name=name,
251+
name_token=a,
252+
line_no=a.lineno,
253+
col_offset=node.col_offset,
254+
end_line_no=node.end_lineno
255+
if node.end_lineno is not None
256+
else a.lineno
257+
if a.lineno is not None
258+
else -1,
259+
end_col_offset=node.end_col_offset
260+
if node.end_col_offset is not None
261+
else a.end_col_offset
262+
if name_token.end_col_offset is not None
263+
else -1,
264+
source=self.source,
265+
)
266+
)
267+
223268
async def visit_Arguments(self, node: ast.AST) -> None: # noqa: N802
224269
from robot.errors import VariableError
225270
from robot.parsing.lexer.tokens import Token as RobotToken
@@ -781,7 +826,7 @@ def __init__(
781826
self._libraries: OrderedDict[str, LibraryEntry] = OrderedDict()
782827
self._resources: OrderedDict[str, ResourceEntry] = OrderedDict()
783828
self._variables: OrderedDict[str, VariablesEntry] = OrderedDict()
784-
self._initialzed = False
829+
self._initialized = False
785830
self._initialize_lock = asyncio.Lock()
786831
self._analyzed = False
787832
self._analyze_lock = asyncio.Lock()
@@ -791,6 +836,7 @@ def __init__(
791836
self._diagnostics: List[Diagnostic] = []
792837

793838
self._keywords: Optional[List[KeywordDoc]] = None
839+
self._loop = asyncio.get_event_loop()
794840

795841
# TODO: how to get the search order from model
796842
self.search_order: Tuple[str, ...] = ()
@@ -814,21 +860,21 @@ async def resources_changed(self, sender: Any, params: List[LibraryDoc]) -> None
814860

815861
@_logger.call
816862
async def get_diagnostisc(self) -> List[Diagnostic]:
817-
await self._ensure_initialized()
863+
await self.ensure_initialized()
818864

819865
await self._analyze()
820866

821867
return self._diagnostics
822868

823869
@_logger.call
824870
async def get_libraries(self) -> OrderedDict[str, LibraryEntry]:
825-
await self._ensure_initialized()
871+
await self.ensure_initialized()
826872

827873
return self._libraries
828874

829875
@_logger.call
830876
async def get_resources(self) -> OrderedDict[str, ResourceEntry]:
831-
await self._ensure_initialized()
877+
await self.ensure_initialized()
832878

833879
return self._resources
834880

@@ -856,9 +902,9 @@ async def get_library_doc(self) -> LibraryDoc:
856902
return self._library_doc
857903

858904
@_logger.call
859-
async def _ensure_initialized(self) -> None:
905+
async def ensure_initialized(self) -> bool:
860906
async with self._initialize_lock:
861-
if not self._initialzed:
907+
if not self._initialized:
862908
imports = await self.get_imports()
863909

864910
if self.document is not None:
@@ -879,7 +925,12 @@ async def _ensure_initialized(self) -> None:
879925
await self._import_default_libraries()
880926
await self._import_imports(imports, str(Path(self.source).parent), top_level=True)
881927

882-
self._initialzed = True
928+
self._initialized = True
929+
return self._initialized
930+
931+
@property
932+
def initialized(self) -> bool:
933+
return self._initialized
883934

884935
async def get_imports(self) -> List[Import]:
885936
if self._imports is None:
@@ -905,7 +956,7 @@ def get_builtin_variables(cls) -> List[BuiltInVariableDefinition]:
905956
async def get_variables(self, nodes: Optional[List[ast.AST]] = None) -> Dict[VariableMatcher, VariableDefinition]:
906957
from robot.parsing.model.blocks import Keyword, TestCase
907958

908-
await self._ensure_initialized()
959+
await self.ensure_initialized()
909960

910961
result: Dict[VariableMatcher, VariableDefinition] = {}
911962

@@ -1246,7 +1297,7 @@ async def _get_resource_entry(self, name: str, base_dir: str, sentinel: Any = No
12461297

12471298
@_logger.call
12481299
async def get_keywords(self) -> List[KeywordDoc]:
1249-
await self._ensure_initialized()
1300+
await self.ensure_initialized()
12501301

12511302
if self._keywords is None:
12521303
result: Dict[KeywordMatcher, KeywordDoc] = {}
@@ -1296,10 +1347,13 @@ async def _analyze(self) -> None:
12961347
self._analyzed = True
12971348

12981349
async def find_keyword(self, name: Optional[str]) -> Optional[KeywordDoc]:
1299-
await self._ensure_initialized()
1350+
await self.ensure_initialized()
13001351

13011352
return await KeywordFinder(self).find_keyword(name)
13021353

1354+
async def find_keyword_threadsafe(self, name: Optional[str]) -> Optional[KeywordDoc]:
1355+
return await asyncio.wrap_future(asyncio.run_coroutine_threadsafe(self.find_keyword(name), self._loop))
1356+
13031357

13041358
class DiagnosticsEntry(NamedTuple):
13051359
message: str

0 commit comments

Comments
 (0)