Skip to content

Commit 4ba77ab

Browse files
committed
refactor(langserver): speed up hovering for keywords, variables and namespace by using references from code analysis
1 parent 7250709 commit 4ba77ab

File tree

101 files changed

+373
-491
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+373
-491
lines changed

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

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,18 @@
3636
from robotcode.language_server.robotframework.utils.version import get_robot_version
3737

3838
from .entities import (
39+
CommandLineVariableDefinition,
3940
EnvironmentVariableDefinition,
41+
LibraryEntry,
42+
ResourceEntry,
4043
VariableDefinition,
4144
VariableNotFoundDefinition,
4245
)
4346
from .library_doc import KeywordDoc, KeywordMatcher, is_embedded_keyword
4447
from .namespace import (
4548
DIAGNOSTICS_SOURCE_NAME,
4649
KeywordFinder,
47-
LibraryEntry,
4850
Namespace,
49-
ResourceEntry,
5051
)
5152

5253

@@ -55,6 +56,7 @@ class AnalyzerResult:
5556
diagnostics: List[Diagnostic]
5657
keyword_references: Dict[KeywordDoc, Set[Location]]
5758
variable_references: Dict[VariableDefinition, Set[Location]]
59+
namespace_references: Dict[LibraryEntry, Set[Location]]
5860

5961

6062
class Analyzer(AsyncVisitor, ModelHelperMixin):
@@ -83,14 +85,17 @@ def __init__(
8385
self._diagnostics: List[Diagnostic] = []
8486
self._keyword_references: Dict[KeywordDoc, Set[Location]] = defaultdict(set)
8587
self._variable_references: Dict[VariableDefinition, Set[Location]] = defaultdict(set)
88+
self._namespace_references: Dict[LibraryEntry, Set[Location]] = defaultdict(set)
8689

8790
async def run(self) -> AnalyzerResult:
8891
self._diagnostics = []
8992
self._keyword_references = defaultdict(set)
9093

9194
await self.visit(self.model)
9295

93-
return AnalyzerResult(self._diagnostics, self._keyword_references, self._variable_references)
96+
return AnalyzerResult(
97+
self._diagnostics, self._keyword_references, self._variable_references, self._namespace_references
98+
)
9499

95100
def yield_argument_name_and_rest(self, node: ast.AST, token: Token) -> Iterator[Token]:
96101
from robot.parsing.lexer.tokens import Token as RobotToken
@@ -160,6 +165,13 @@ async def visit_Variable(self, node: ast.AST) -> None: # noqa: N802
160165
if var_def is None:
161166
return
162167

168+
cmd_line_var = await self.namespace.find_variable(
169+
name, skip_commandline_variables=False, position=r.start, ignore_error=True
170+
)
171+
if isinstance(cmd_line_var, CommandLineVariableDefinition):
172+
if self.namespace.document is not None:
173+
self._variable_references[cmd_line_var].add(Location(self.namespace.document.document_uri, r))
174+
163175
if var_def not in self._variable_references:
164176
self._variable_references[var_def] = set()
165177

@@ -340,6 +352,10 @@ async def _analyze_keyword_call(
340352

341353
kw_range = range_from_token(keyword_token)
342354

355+
lib_entry = None
356+
lib_range = None
357+
kw_namespace = None
358+
343359
if keyword is not None:
344360
for lib, name in iter_over_keyword_names_and_owners(keyword):
345361
if (
@@ -354,8 +370,10 @@ async def _analyze_keyword_call(
354370
)
355371
if lib_entry and kw_namespace:
356372
r = range_from_token(keyword_token)
373+
lib_range = r
357374
r.end.character = r.start.character + len(kw_namespace)
358375
kw_range.start.character = r.end.character + 1
376+
lib_range.end.character = kw_range.start.character - 1
359377

360378
result = self.finder.find_keyword(keyword)
361379

@@ -368,6 +386,10 @@ async def _analyze_keyword_call(
368386
code=e.code,
369387
)
370388

389+
if kw_namespace and lib_entry is not None and lib_range is not None:
390+
if self.namespace.document is not None:
391+
self._namespace_references[lib_entry].add(Location(self.namespace.document.document_uri, lib_range))
392+
371393
if result is not None:
372394
if self.namespace.document is not None:
373395
self._keyword_references[result].add(Location(self.namespace.document.document_uri, kw_range))

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

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from dataclasses import dataclass, field
22
from enum import Enum
3-
from typing import TYPE_CHECKING, Any, Callable, Optional, Tuple, TypeVar, cast
3+
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple, TypeVar, cast
44

55
from robotcode.core.lsp.types import Position, Range
6-
from robotcode.language_server.robotframework.utils.ast_utils import Token, range_from_token
6+
7+
from ..utils.ast_utils import Token, range_from_token
78

89
if TYPE_CHECKING:
9-
from .library_doc import KeywordDoc
10+
from .library_doc import KeywordDoc, LibraryDoc
1011

1112
_F = TypeVar("_F", bound=Callable[..., Any])
1213

@@ -289,3 +290,45 @@ class VariableNotFoundDefinition(VariableDefinition):
289290
@single_call
290291
def __hash__(self) -> int:
291292
return hash((type(self), self.name, self.type))
293+
294+
295+
@dataclass
296+
class LibraryEntry:
297+
name: str
298+
import_name: str
299+
library_doc: "LibraryDoc"
300+
args: Tuple[Any, ...] = ()
301+
alias: Optional[str] = None
302+
import_range: Range = field(default_factory=lambda: Range.zero())
303+
import_source: Optional[str] = None
304+
305+
def __str__(self) -> str:
306+
result = self.import_name
307+
if self.args:
308+
result += f" {self.args!s}"
309+
if self.alias:
310+
result += f" WITH NAME {self.alias}"
311+
return result
312+
313+
@single_call
314+
def __hash__(self) -> int:
315+
return hash(
316+
(type(self), self.name, self.import_name, self.args, self.alias, self.import_range, self.import_source)
317+
)
318+
319+
320+
@dataclass
321+
class ResourceEntry(LibraryEntry):
322+
imports: List[Import] = field(default_factory=list)
323+
variables: List[VariableDefinition] = field(default_factory=list)
324+
325+
@single_call
326+
def __hash__(self) -> int:
327+
return hash(
328+
(type(self), self.name, self.import_name, self.args, self.alias, self.import_range, self.import_source)
329+
)
330+
331+
332+
@dataclass
333+
class VariablesEntry(LibraryEntry):
334+
variables: List[ImportedVariableDefinition] = field(default_factory=list)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,7 +1118,7 @@ async def _get_libdoc(name: str, args: Tuple[Any, ...], working_dir: str, base_d
11181118
),
11191119
LOAD_LIBRARY_TIME_OUT,
11201120
)
1121-
except (SystemExit, KeyboardInterrupt):
1121+
except (SystemExit, KeyboardInterrupt, asyncio.CancelledError):
11221122
raise
11231123
except BaseException as e:
11241124
self._logger.exception(e)
@@ -1373,7 +1373,7 @@ async def _get_libdoc(name: str, args: Tuple[Any, ...], working_dir: str, base_d
13731373
),
13741374
LOAD_LIBRARY_TIME_OUT,
13751375
)
1376-
except (SystemExit, KeyboardInterrupt):
1376+
except (SystemExit, KeyboardInterrupt, asyncio.CancelledError):
13771377
raise
13781378
except BaseException as e:
13791379
self._logger.exception(e)

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

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import time
1010
import weakref
1111
from collections import OrderedDict
12-
from dataclasses import dataclass, field
1312
from itertools import chain
1413
from pathlib import Path
1514
from typing import (
@@ -62,13 +61,15 @@
6261
CommandLineVariableDefinition,
6362
EnvironmentVariableDefinition,
6463
Import,
65-
ImportedVariableDefinition,
6664
InvalidVariableError,
65+
LibraryEntry,
6766
LibraryImport,
6867
LocalVariableDefinition,
68+
ResourceEntry,
6969
ResourceImport,
7070
VariableDefinition,
7171
VariableMatcher,
72+
VariablesEntry,
7273
VariablesImport,
7374
)
7475
from .imports_manager import ImportsManager
@@ -506,36 +507,6 @@ def visit_VariablesImport(self, node: ast.AST) -> None: # noqa: N802
506507
)
507508

508509

509-
@dataclass
510-
class LibraryEntry:
511-
name: str
512-
import_name: str
513-
library_doc: LibraryDoc
514-
args: Tuple[Any, ...] = ()
515-
alias: Optional[str] = None
516-
import_range: Range = field(default_factory=lambda: Range.zero())
517-
import_source: Optional[str] = None
518-
519-
def __str__(self) -> str:
520-
result = self.import_name
521-
if self.args:
522-
result += f" {self.args!s}"
523-
if self.alias:
524-
result += f" WITH NAME {self.alias}"
525-
return result
526-
527-
528-
@dataclass
529-
class ResourceEntry(LibraryEntry):
530-
imports: List[Import] = field(default_factory=list)
531-
variables: List[VariableDefinition] = field(default_factory=list)
532-
533-
534-
@dataclass
535-
class VariablesEntry(LibraryEntry):
536-
variables: List[ImportedVariableDefinition] = field(default_factory=list)
537-
538-
539510
class DocumentType(enum.Enum):
540511
UNKNOWN = "unknown"
541512
GENERAL = "robot"
@@ -589,6 +560,7 @@ def __init__(
589560
self._diagnostics: List[Diagnostic] = []
590561
self._keyword_references: Dict[KeywordDoc, Set[Location]] = {}
591562
self._variable_references: Dict[VariableDefinition, Set[Location]] = {}
563+
self._namespace_references: Dict[LibraryEntry, Set[Location]] = {}
592564

593565
self._imported_keywords: Optional[List[KeywordDoc]] = None
594566
self._imported_keywords_lock = Lock()
@@ -705,6 +677,7 @@ async def _invalidate(self) -> None:
705677
self._diagnostics = []
706678
self._keyword_references = {}
707679
self._variable_references = {}
680+
self._namespace_references = {}
708681
self._finder = None
709682
self._in_initialize = False
710683
self._ignored_lines = None
@@ -740,6 +713,13 @@ async def get_variable_references(self) -> Dict[VariableDefinition, Set[Location
740713

741714
return self._variable_references
742715

716+
async def get_namespace_references(self) -> Dict[LibraryEntry, Set[Location]]:
717+
await self.ensure_initialized()
718+
719+
await self._analyze()
720+
721+
return self._namespace_references
722+
743723
@_logger.call
744724
async def get_libraries(self) -> OrderedDict[str, LibraryEntry]:
745725
await self.ensure_initialized()
@@ -1593,6 +1573,7 @@ async def _analyze(self) -> None:
15931573
self._diagnostics += result.diagnostics
15941574
self._keyword_references = result.keyword_references
15951575
self._variable_references = result.variable_references
1576+
self._namespace_references = result.namespace_references
15961577

15971578
lib_doc = await self.get_library_doc()
15981579

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@
3232
from robotcode.language_server.robotframework.configuration import (
3333
DocumentationServerConfig,
3434
)
35+
from robotcode.language_server.robotframework.diagnostics.entities import LibraryEntry
3536
from robotcode.language_server.robotframework.diagnostics.library_doc import (
3637
get_library_doc,
3738
get_robot_library_html_doc_str,
3839
resolve_robot_variables,
3940
)
4041
from robotcode.language_server.robotframework.diagnostics.namespace import (
41-
LibraryEntry,
4242
Namespace,
4343
)
4444
from robotcode.language_server.robotframework.utils.ast_utils import (

0 commit comments

Comments
 (0)