robotcode-language-server 2.6.2__py3-none-any.whl → 2.8.0.dev548__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- robotcode/language_server/__version__.py +1 -1
- robotcode/language_server/common/parts/diagnostics.py +50 -17
- robotcode/language_server/common/parts/documents.py +34 -31
- robotcode/language_server/common/parts/workspace.py +57 -55
- robotcode/language_server/common/protocol.py +3 -1
- robotcode/language_server/robotframework/configuration.py +3 -0
- robotcode/language_server/robotframework/parts/code_action_documentation.py +440 -129
- robotcode/language_server/robotframework/parts/code_action_helper_mixin.py +17 -2
- robotcode/language_server/robotframework/parts/code_action_quick_fixes.py +6 -25
- robotcode/language_server/robotframework/parts/code_action_refactor.py +8 -6
- robotcode/language_server/robotframework/parts/code_lens.py +3 -4
- robotcode/language_server/robotframework/parts/completion.py +193 -21
- robotcode/language_server/robotframework/parts/debugging_utils.py +75 -6
- robotcode/language_server/robotframework/parts/diagnostics.py +15 -3
- robotcode/language_server/robotframework/parts/documents_cache.py +5 -1
- robotcode/language_server/robotframework/parts/hover.py +103 -21
- robotcode/language_server/robotframework/parts/http_server.py +17 -7
- robotcode/language_server/robotframework/parts/inline_value.py +69 -7
- robotcode/language_server/robotframework/parts/keywords_treeview.py +151 -53
- robotcode/language_server/robotframework/parts/model_variables.py +331 -0
- robotcode/language_server/robotframework/parts/references.py +47 -31
- robotcode/language_server/robotframework/parts/rename.py +1 -2
- robotcode/language_server/robotframework/parts/robot_workspace.py +229 -25
- robotcode/language_server/robotframework/parts/selection_range.py +26 -13
- robotcode/language_server/robotframework/parts/semantic_tokens.py +414 -113
- robotcode/language_server/robotframework/parts/signature_help.py +162 -70
- robotcode/language_server/robotframework/protocol.py +1 -0
- {robotcode_language_server-2.6.2.dist-info → robotcode_language_server-2.8.0.dev548.dist-info}/METADATA +7 -6
- {robotcode_language_server-2.6.2.dist-info → robotcode_language_server-2.8.0.dev548.dist-info}/RECORD +31 -30
- {robotcode_language_server-2.6.2.dist-info → robotcode_language_server-2.8.0.dev548.dist-info}/WHEEL +1 -1
- {robotcode_language_server-2.6.2.dist-info → robotcode_language_server-2.8.0.dev548.dist-info}/entry_points.txt +0 -0
|
@@ -26,6 +26,7 @@ from robotcode.robot.diagnostics.entities import (
|
|
|
26
26
|
ResourceEntry,
|
|
27
27
|
VariableDefinition,
|
|
28
28
|
VariableDefinitionType,
|
|
29
|
+
VariablesEntry,
|
|
29
30
|
)
|
|
30
31
|
from robotcode.robot.diagnostics.library_doc import (
|
|
31
32
|
RESOURCE_FILE_EXTENSION,
|
|
@@ -33,7 +34,6 @@ from robotcode.robot.diagnostics.library_doc import (
|
|
|
33
34
|
KeywordDoc,
|
|
34
35
|
LibraryDoc,
|
|
35
36
|
)
|
|
36
|
-
from robotcode.robot.diagnostics.model_helper import ModelHelper
|
|
37
37
|
from robotcode.robot.diagnostics.namespace import Namespace
|
|
38
38
|
from robotcode.robot.utils import RF_VERSION
|
|
39
39
|
from robotcode.robot.utils.ast import (
|
|
@@ -55,7 +55,7 @@ _ReferencesMethod = Callable[
|
|
|
55
55
|
]
|
|
56
56
|
|
|
57
57
|
|
|
58
|
-
class RobotReferencesProtocolPart(RobotLanguageServerProtocolPart
|
|
58
|
+
class RobotReferencesProtocolPart(RobotLanguageServerProtocolPart):
|
|
59
59
|
_logger = LoggingDescriptor()
|
|
60
60
|
|
|
61
61
|
def __init__(self, parent: "RobotLanguageServerProtocol") -> None:
|
|
@@ -68,7 +68,7 @@ class RobotReferencesProtocolPart(RobotLanguageServerProtocolPart, ModelHelper):
|
|
|
68
68
|
|
|
69
69
|
parent.references.collect.add(self.collect)
|
|
70
70
|
parent.documents.did_change.add(self.document_did_change)
|
|
71
|
-
parent.documents.on_document_cache_invalidated(self.document_did_change)
|
|
71
|
+
parent.documents.on_document_cache_invalidated.add(self.document_did_change)
|
|
72
72
|
parent.diagnostics.on_workspace_diagnostics_break.add(self.on_workspace_diagnostics_break)
|
|
73
73
|
|
|
74
74
|
@event
|
|
@@ -80,7 +80,8 @@ class RobotReferencesProtocolPart(RobotLanguageServerProtocolPart, ModelHelper):
|
|
|
80
80
|
f"**/*.{{{ROBOT_FILE_EXTENSION[1:]},{RESOURCE_FILE_EXTENSION[1:]}}}",
|
|
81
81
|
WatchKind.CREATE | WatchKind.DELETE,
|
|
82
82
|
)
|
|
83
|
-
self.parent.documents_cache.namespace_invalidated(self.namespace_invalidated)
|
|
83
|
+
self.parent.documents_cache.namespace_invalidated.add(self.namespace_invalidated)
|
|
84
|
+
self.parent.documents_cache.namespace_initialized.add(self.namespace_initialized)
|
|
84
85
|
|
|
85
86
|
def do_on_file_changed(self, sender: Any, files: List[FileEvent]) -> None:
|
|
86
87
|
self.clear_cache()
|
|
@@ -93,6 +94,11 @@ class RobotReferencesProtocolPart(RobotLanguageServerProtocolPart, ModelHelper):
|
|
|
93
94
|
def namespace_invalidated(self, sender: Any, namespace: Namespace) -> None:
|
|
94
95
|
self.clear_cache()
|
|
95
96
|
|
|
97
|
+
@language_id("robotframework")
|
|
98
|
+
def namespace_initialized(self, sender: Any, namespace: Namespace) -> None:
|
|
99
|
+
# the new analysis changed the index, so a result found before it can miss its references
|
|
100
|
+
self.clear_cache()
|
|
101
|
+
|
|
96
102
|
def on_workspace_diagnostics_break(self, sender: Any) -> None:
|
|
97
103
|
self.clear_cache()
|
|
98
104
|
|
|
@@ -330,18 +336,21 @@ class RobotReferencesProtocolPart(RobotLanguageServerProtocolPart, ModelHelper):
|
|
|
330
336
|
def _find_library_import_references_in_file(self, doc: TextDocument, library_doc: LibraryDoc) -> List[Location]:
|
|
331
337
|
namespace = self.parent.documents_cache.get_namespace(doc)
|
|
332
338
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
|
|
339
|
+
# every import statement, also one of a library that is already imported
|
|
340
|
+
result: List[Location] = [
|
|
341
|
+
Location(str(doc.uri), lib_entry.import_range)
|
|
342
|
+
for lib_entry in namespace.import_entries.values()
|
|
343
|
+
if not isinstance(lib_entry, (ResourceEntry, VariablesEntry))
|
|
344
|
+
and lib_entry.import_source == str(doc.uri.to_path())
|
|
345
|
+
and lib_entry.library_doc.source_or_origin == library_doc.source_or_origin
|
|
346
|
+
and lib_entry.library_doc.name == library_doc.name
|
|
347
|
+
]
|
|
348
|
+
|
|
349
|
+
# the analysis also keeps a repeated import as a reference of the first one
|
|
341
350
|
references = namespace.namespace_references
|
|
342
351
|
for k, v in references.items():
|
|
343
352
|
if not k.alias and k.library_doc == library_doc:
|
|
344
|
-
result.extend(v)
|
|
353
|
+
result.extend(location for location in v if location not in result)
|
|
345
354
|
|
|
346
355
|
return result
|
|
347
356
|
|
|
@@ -415,17 +424,20 @@ class RobotReferencesProtocolPart(RobotLanguageServerProtocolPart, ModelHelper):
|
|
|
415
424
|
def _find_resource_import_references_in_file(self, doc: TextDocument, entry: ResourceEntry) -> List[Location]:
|
|
416
425
|
namespace = self.parent.documents_cache.get_namespace(doc)
|
|
417
426
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
)
|
|
424
|
-
|
|
427
|
+
# every import statement, also one of a resource file that is already imported
|
|
428
|
+
result: List[Location] = [
|
|
429
|
+
Location(str(doc.uri), lib_entry.import_range)
|
|
430
|
+
for lib_entry in namespace.import_entries.values()
|
|
431
|
+
if isinstance(lib_entry, ResourceEntry)
|
|
432
|
+
and lib_entry.import_source == str(doc.uri.to_path())
|
|
433
|
+
and lib_entry.library_doc.source == entry.library_doc.source
|
|
434
|
+
]
|
|
425
435
|
|
|
436
|
+
# the calls with the resource's name as prefix, in every file that imports the resource
|
|
426
437
|
references = namespace.namespace_references
|
|
427
|
-
|
|
428
|
-
|
|
438
|
+
for k, v in references.items():
|
|
439
|
+
if isinstance(k, ResourceEntry) and k.library_doc.source == entry.library_doc.source:
|
|
440
|
+
result.extend(location for location in v if location not in result)
|
|
429
441
|
|
|
430
442
|
return result
|
|
431
443
|
|
|
@@ -448,12 +460,14 @@ class RobotReferencesProtocolPart(RobotLanguageServerProtocolPart, ModelHelper):
|
|
|
448
460
|
if not name_token:
|
|
449
461
|
return None
|
|
450
462
|
|
|
451
|
-
|
|
463
|
+
# the entry of this statement, also of a repeated import of the resource file
|
|
452
464
|
entry = next(
|
|
453
465
|
(
|
|
454
466
|
v
|
|
455
|
-
for v in
|
|
456
|
-
if v
|
|
467
|
+
for v in namespace.import_entries.values()
|
|
468
|
+
if isinstance(v, ResourceEntry)
|
|
469
|
+
and v.import_source == namespace.source
|
|
470
|
+
and v.import_range == range_from_token(name_token)
|
|
457
471
|
),
|
|
458
472
|
None,
|
|
459
473
|
)
|
|
@@ -484,12 +498,14 @@ class RobotReferencesProtocolPart(RobotLanguageServerProtocolPart, ModelHelper):
|
|
|
484
498
|
def _find_variables_import_references_in_file(self, doc: TextDocument, library_doc: LibraryDoc) -> List[Location]:
|
|
485
499
|
namespace = self.parent.documents_cache.get_namespace(doc)
|
|
486
500
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
501
|
+
# every import statement, also one of a variable file that is already imported
|
|
502
|
+
return [
|
|
503
|
+
Location(str(doc.uri), lib_entry.import_range)
|
|
504
|
+
for lib_entry in namespace.import_entries.values()
|
|
505
|
+
if isinstance(lib_entry, VariablesEntry)
|
|
506
|
+
and lib_entry.import_source == str(doc.uri.to_path())
|
|
507
|
+
and lib_entry.library_doc.source == library_doc.source
|
|
508
|
+
]
|
|
493
509
|
|
|
494
510
|
def references_VariablesImport( # noqa: N802
|
|
495
511
|
self,
|
|
@@ -33,7 +33,6 @@ from robotcode.robot.diagnostics.entities import (
|
|
|
33
33
|
VariableDefinitionType,
|
|
34
34
|
)
|
|
35
35
|
from robotcode.robot.diagnostics.library_doc import KeywordDoc
|
|
36
|
-
from robotcode.robot.diagnostics.model_helper import ModelHelper
|
|
37
36
|
|
|
38
37
|
from ...common.parts.rename import CantRenameError
|
|
39
38
|
from .protocol_part import RobotLanguageServerProtocolPart
|
|
@@ -48,7 +47,7 @@ _PrepareRenameMethod = Callable[[ast.AST, TextDocument, Position], Optional[Prep
|
|
|
48
47
|
_T = TypeVar("_T", bound=Callable[..., Any])
|
|
49
48
|
|
|
50
49
|
|
|
51
|
-
class RobotRenameProtocolPart(RobotLanguageServerProtocolPart
|
|
50
|
+
class RobotRenameProtocolPart(RobotLanguageServerProtocolPart):
|
|
52
51
|
_logger = LoggingDescriptor()
|
|
53
52
|
|
|
54
53
|
def __init__(self, parent: "RobotLanguageServerProtocol") -> None:
|
|
@@ -1,18 +1,35 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import threading
|
|
1
3
|
from concurrent.futures import CancelledError
|
|
2
4
|
from logging import CRITICAL
|
|
5
|
+
from pathlib import Path
|
|
3
6
|
from threading import Event
|
|
4
|
-
from typing import TYPE_CHECKING, Any, Optional
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
5
8
|
|
|
6
|
-
from robotcode.core.
|
|
9
|
+
from robotcode.core.concurrent import run_as_task
|
|
10
|
+
from robotcode.core.documents_manager import CantReadDocumentError
|
|
11
|
+
from robotcode.core.ignore_spec import (
|
|
12
|
+
DEFAULT_SPEC_RULES,
|
|
13
|
+
GIT_IGNORE_FILE,
|
|
14
|
+
ROBOT_IGNORE_FILE,
|
|
15
|
+
IgnoreSpec,
|
|
16
|
+
iter_files,
|
|
17
|
+
iter_files_includes,
|
|
18
|
+
)
|
|
7
19
|
from robotcode.core.language import language_id
|
|
20
|
+
from robotcode.core.lsp.types import DocumentUri, FileChangeType, FileEvent, WatchKind
|
|
21
|
+
from robotcode.core.text_document import TextDocument
|
|
8
22
|
from robotcode.core.uri import Uri
|
|
9
23
|
from robotcode.core.utils.logging import LoggingDescriptor
|
|
24
|
+
from robotcode.core.utils.path import probe_disk_info
|
|
25
|
+
from robotcode.core.workspace import WorkspaceFolder
|
|
10
26
|
from robotcode.jsonrpc2.protocol import rpc_method
|
|
11
27
|
from robotcode.language_server.common.parts.diagnostics import (
|
|
12
28
|
AnalysisProgressMode,
|
|
13
29
|
DiagnosticsMode,
|
|
14
30
|
)
|
|
15
31
|
from robotcode.language_server.robotframework.configuration import AnalysisConfig
|
|
32
|
+
from robotcode.robot.diagnostics.imports_manager import ImportsManager
|
|
16
33
|
from robotcode.robot.diagnostics.library_doc import (
|
|
17
34
|
RESOURCE_FILE_EXTENSION,
|
|
18
35
|
ROBOT_FILE_EXTENSION,
|
|
@@ -25,6 +42,9 @@ if TYPE_CHECKING:
|
|
|
25
42
|
|
|
26
43
|
from .protocol_part import RobotLanguageServerProtocolPart
|
|
27
44
|
|
|
45
|
+
WORKSPACE_FILE_EXTENSIONS = [ROBOT_FILE_EXTENSION, RESOURCE_FILE_EXTENSION]
|
|
46
|
+
WORKSPACE_IGNORE_FILES = [ROBOT_IGNORE_FILE, GIT_IGNORE_FILE]
|
|
47
|
+
|
|
28
48
|
|
|
29
49
|
class RobotWorkspaceProtocolPart(RobotLanguageServerProtocolPart):
|
|
30
50
|
_logger = LoggingDescriptor()
|
|
@@ -32,11 +52,43 @@ class RobotWorkspaceProtocolPart(RobotLanguageServerProtocolPart):
|
|
|
32
52
|
def __init__(self, parent: "RobotLanguageServerProtocol") -> None:
|
|
33
53
|
super().__init__(parent)
|
|
34
54
|
self.parent.documents.on_read_document_text.add(self.on_read_document_text)
|
|
55
|
+
self.parent.documents.did_create_uri.add(self._on_did_create_uri)
|
|
56
|
+
self.parent.on_initialized.add(self._on_initialized)
|
|
57
|
+
self.parent.diagnostics.on_workspace_loaded.add(self._on_workspace_loaded)
|
|
58
|
+
self.parent.on_shutdown.add(self._stop_file_checks)
|
|
35
59
|
self.parent.diagnostics.load_workspace_documents.add(self.load_workspace_documents)
|
|
36
60
|
self.parent.diagnostics.on_get_diagnostics_mode.add(self.on_get_diagnostics_mode)
|
|
37
61
|
self.parent.diagnostics.on_get_analysis_progress_mode.add(self.on_get_analysis_progress_mode)
|
|
38
62
|
self.documents_loaded = Event()
|
|
39
63
|
|
|
64
|
+
# the periodic check of the files of each workspace folder, by the folder's URI
|
|
65
|
+
self._file_check_lock = threading.RLock()
|
|
66
|
+
self._file_check_timers: Dict[str, threading.Timer] = {}
|
|
67
|
+
self._file_checks_stopped = False
|
|
68
|
+
|
|
69
|
+
def _on_initialized(self, sender: Any) -> None:
|
|
70
|
+
# the workspace exists only after the initialize request, the parts of the protocol already before it
|
|
71
|
+
self.parent.workspace.did_change_configuration.add(self._on_did_change_configuration)
|
|
72
|
+
self.parent.workspace.add_file_watcher(
|
|
73
|
+
self._on_files_deleted,
|
|
74
|
+
f"**/*.{{{ROBOT_FILE_EXTENSION[1:]},{RESOURCE_FILE_EXTENSION[1:]}}}",
|
|
75
|
+
WatchKind.DELETE,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def _on_files_deleted(self, sender: Any, changes: List[FileEvent]) -> None:
|
|
79
|
+
for change in changes:
|
|
80
|
+
if change.type != FileChangeType.DELETED:
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
uri = Uri(change.uri).normalized()
|
|
84
|
+
if uri.scheme != "file":
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
path = uri.to_path()
|
|
88
|
+
# the cache keeps the entries of a file that is there again
|
|
89
|
+
if path.suffix in WORKSPACE_FILE_EXTENSIONS and not path.exists():
|
|
90
|
+
self.parent.documents_cache.remove_cached_analysis(path)
|
|
91
|
+
|
|
40
92
|
@language_id("robotframework")
|
|
41
93
|
def on_read_document_text(self, sender: Any, uri: Uri) -> Optional[str]:
|
|
42
94
|
from robot.utils import FileReader
|
|
@@ -52,36 +104,67 @@ class RobotWorkspaceProtocolPart(RobotLanguageServerProtocolPart):
|
|
|
52
104
|
config = self.parent.workspace.get_configuration(AnalysisConfig, uri)
|
|
53
105
|
return config.progress_mode
|
|
54
106
|
|
|
107
|
+
def _exclude_spec(self, folder: WorkspaceFolder) -> IgnoreSpec:
|
|
108
|
+
config = self.parent.workspace.get_configuration(RobotCodeConfig, folder.uri)
|
|
109
|
+
exclude_patterns = [
|
|
110
|
+
*self.parent.analysis_config.exclude_patterns,
|
|
111
|
+
*(config.workspace.exclude_patterns or []),
|
|
112
|
+
]
|
|
113
|
+
return IgnoreSpec.from_list([*DEFAULT_SPEC_RULES, *exclude_patterns], folder.uri.to_path())
|
|
114
|
+
|
|
115
|
+
def collect_workspace_files(self, folder: WorkspaceFolder) -> List[Path]:
|
|
116
|
+
"""The Robot Framework files of a workspace folder, without the excluded and ignored ones."""
|
|
117
|
+
return [
|
|
118
|
+
f
|
|
119
|
+
for f in iter_files(
|
|
120
|
+
folder.uri.to_path(),
|
|
121
|
+
ignore_files=WORKSPACE_IGNORE_FILES,
|
|
122
|
+
include_hidden=False,
|
|
123
|
+
parent_spec=self._exclude_spec(folder),
|
|
124
|
+
verbose_callback=self._logger.debug,
|
|
125
|
+
verbose_trace=False,
|
|
126
|
+
)
|
|
127
|
+
if f.suffix in WORKSPACE_FILE_EXTENSIONS
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
def is_workspace_file(self, folder: WorkspaceFolder, path: Path) -> bool:
|
|
131
|
+
"""Whether `collect_workspace_files` would return `path`."""
|
|
132
|
+
return path.suffix in WORKSPACE_FILE_EXTENSIONS and iter_files_includes(
|
|
133
|
+
path,
|
|
134
|
+
folder.uri.to_path(),
|
|
135
|
+
ignore_files=WORKSPACE_IGNORE_FILES,
|
|
136
|
+
include_hidden=False,
|
|
137
|
+
parent_spec=self._exclude_spec(folder),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def _on_did_create_uri(self, sender: Any, uri: DocumentUri) -> None:
|
|
141
|
+
# a created file is analyzed like the files found at startup
|
|
142
|
+
file_uri = Uri(uri)
|
|
143
|
+
if file_uri.scheme != "file":
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
path = file_uri.to_path()
|
|
147
|
+
folder = self.parent.workspace.get_workspace_folder(file_uri)
|
|
148
|
+
if folder is None or not self.is_workspace_file(folder, path):
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
document = self.parent.documents.get_or_open_document(path)
|
|
153
|
+
except CantReadDocumentError as e:
|
|
154
|
+
ex = e
|
|
155
|
+
self._logger.debug(lambda: f"Can't load created document {path}: {ex}")
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
self.parent.diagnostics.force_refresh_document(document)
|
|
159
|
+
|
|
55
160
|
def load_workspace_documents(self, sender: Any) -> None:
|
|
56
161
|
with self._logger.measure_time(lambda: "loading workspace documents", context_name="load_workspace_documents"):
|
|
57
162
|
try:
|
|
58
163
|
for folder in self.parent.workspace.workspace_folders:
|
|
59
164
|
config = self.parent.workspace.get_configuration(RobotCodeConfig, folder.uri)
|
|
60
165
|
|
|
61
|
-
extensions = [ROBOT_FILE_EXTENSION, RESOURCE_FILE_EXTENSION]
|
|
62
|
-
|
|
63
|
-
exclude_patterns = [
|
|
64
|
-
*self.parent.analysis_config.exclude_patterns,
|
|
65
|
-
*(config.workspace.exclude_patterns or []),
|
|
66
|
-
]
|
|
67
|
-
|
|
68
166
|
with self.parent.window.progress("Collect sources", cancellable=False):
|
|
69
|
-
files =
|
|
70
|
-
filter(
|
|
71
|
-
lambda f: f.suffix in extensions,
|
|
72
|
-
iter_files(
|
|
73
|
-
folder.uri.to_path(),
|
|
74
|
-
ignore_files=[ROBOT_IGNORE_FILE, GIT_IGNORE_FILE],
|
|
75
|
-
include_hidden=False,
|
|
76
|
-
parent_spec=IgnoreSpec.from_list(
|
|
77
|
-
[*DEFAULT_SPEC_RULES, *exclude_patterns],
|
|
78
|
-
folder.uri.to_path(),
|
|
79
|
-
),
|
|
80
|
-
verbose_callback=self._logger.debug,
|
|
81
|
-
verbose_trace=False,
|
|
82
|
-
),
|
|
83
|
-
)
|
|
84
|
-
)
|
|
167
|
+
files = self.collect_workspace_files(folder)
|
|
85
168
|
|
|
86
169
|
canceled = False
|
|
87
170
|
self._logger.debug(
|
|
@@ -122,6 +205,127 @@ class RobotWorkspaceProtocolPart(RobotLanguageServerProtocolPart):
|
|
|
122
205
|
if canceled:
|
|
123
206
|
self._logger.info(lambda: "Workspace loading canceled")
|
|
124
207
|
|
|
208
|
+
def _documents_of(self, folder: WorkspaceFolder) -> List[TextDocument]:
|
|
209
|
+
result = []
|
|
210
|
+
for document in list(self.parent.documents.documents):
|
|
211
|
+
document_folder = self.parent.workspace.get_workspace_folder(document.uri)
|
|
212
|
+
if document_folder is not None and document_folder.uri == folder.uri:
|
|
213
|
+
result.append(document)
|
|
214
|
+
return result
|
|
215
|
+
|
|
216
|
+
def check_workspace_folder(self, folder: WorkspaceFolder) -> None:
|
|
217
|
+
"""Find the changes of the files of a workspace folder, and handle them like the file events of the client."""
|
|
218
|
+
changes: Dict[str, FileEvent] = {}
|
|
219
|
+
|
|
220
|
+
def report(path: Path, change: FileChangeType) -> None:
|
|
221
|
+
uri = str(Uri.from_path(path))
|
|
222
|
+
if uri not in changes:
|
|
223
|
+
changes[uri] = FileEvent(uri, change)
|
|
224
|
+
|
|
225
|
+
for path in self.collect_workspace_files(folder):
|
|
226
|
+
if self.parent.documents.get(Uri.from_path(path).normalized()) is None:
|
|
227
|
+
report(path, FileChangeType.CREATED)
|
|
228
|
+
|
|
229
|
+
# the documents open in the editor are the editor's
|
|
230
|
+
for document in self._documents_of(folder):
|
|
231
|
+
if document.opened_in_editor or document.uri.scheme != "file":
|
|
232
|
+
continue
|
|
233
|
+
|
|
234
|
+
path = document.uri.to_path()
|
|
235
|
+
disk_info = probe_disk_info(path)
|
|
236
|
+
if disk_info is None:
|
|
237
|
+
report(path, FileChangeType.DELETED)
|
|
238
|
+
elif disk_info != document.disk_info:
|
|
239
|
+
report(path, FileChangeType.CHANGED)
|
|
240
|
+
|
|
241
|
+
imports_manager = self.parent.documents_cache.get_imports_manager_for_workspace_folder(folder)
|
|
242
|
+
for file in imports_manager.changed_import_files():
|
|
243
|
+
report(Path(file), FileChangeType.CHANGED if Path(file).exists() else FileChangeType.DELETED)
|
|
244
|
+
|
|
245
|
+
for file in self._found_failed_imports(folder, imports_manager):
|
|
246
|
+
report(Path(file), FileChangeType.CREATED)
|
|
247
|
+
|
|
248
|
+
if changes:
|
|
249
|
+
self.parent.workspace.did_change_watched_files(self.parent.workspace, list(changes.values()))
|
|
250
|
+
|
|
251
|
+
def _found_failed_imports(self, folder: WorkspaceFolder, imports_manager: ImportsManager) -> List[str]:
|
|
252
|
+
"""The files of the failed imports whose file or module was not found and is there now."""
|
|
253
|
+
# a module created since the import failed is found only with fresh finder caches
|
|
254
|
+
importlib.invalidate_caches()
|
|
255
|
+
|
|
256
|
+
result: List[str] = []
|
|
257
|
+
for document in self._documents_of(folder):
|
|
258
|
+
namespace = self.parent.documents_cache.get_only_initialized_namespace(document)
|
|
259
|
+
if namespace is None:
|
|
260
|
+
continue
|
|
261
|
+
|
|
262
|
+
for failed in namespace.failed_imports:
|
|
263
|
+
if failed.found:
|
|
264
|
+
continue
|
|
265
|
+
file = imports_manager.find_import_source(failed.imp, failed.base_dir, failed.variables)
|
|
266
|
+
if file is not None:
|
|
267
|
+
result.append(file)
|
|
268
|
+
return result
|
|
269
|
+
|
|
270
|
+
def _on_workspace_loaded(self, sender: Any) -> None:
|
|
271
|
+
self._schedule_file_checks()
|
|
272
|
+
|
|
273
|
+
def _on_did_change_configuration(self, sender: Any, settings: Dict[str, Any]) -> None:
|
|
274
|
+
# the interval of a folder may have changed; the configuration is read off the thread of the notification
|
|
275
|
+
run_as_task(self._schedule_file_checks)
|
|
276
|
+
|
|
277
|
+
def _schedule_file_checks(self) -> None:
|
|
278
|
+
if self._file_checks_stopped or not self.parent.diagnostics.workspace_loaded_event.is_set():
|
|
279
|
+
return
|
|
280
|
+
|
|
281
|
+
for folder in self.parent.workspace.workspace_folders:
|
|
282
|
+
self._schedule_file_check(folder)
|
|
283
|
+
|
|
284
|
+
def _file_check_interval(self, folder: WorkspaceFolder) -> int:
|
|
285
|
+
try:
|
|
286
|
+
interval = self.parent.workspace.get_configuration(AnalysisConfig, folder.uri).file_check_interval
|
|
287
|
+
except (SystemExit, KeyboardInterrupt):
|
|
288
|
+
raise
|
|
289
|
+
except BaseException as e:
|
|
290
|
+
# without a timer the folder would not be checked again
|
|
291
|
+
self._logger.exception(e)
|
|
292
|
+
interval = AnalysisConfig().file_check_interval
|
|
293
|
+
return min(max(interval, 1), 1440)
|
|
294
|
+
|
|
295
|
+
def _schedule_file_check(self, folder: WorkspaceFolder) -> None:
|
|
296
|
+
# reading the configuration can wait for the client, so it happens without the lock that the shutdown takes
|
|
297
|
+
interval = self._file_check_interval(folder)
|
|
298
|
+
|
|
299
|
+
with self._file_check_lock:
|
|
300
|
+
if self._file_checks_stopped:
|
|
301
|
+
return
|
|
302
|
+
|
|
303
|
+
timer = self._file_check_timers.pop(str(folder.uri), None)
|
|
304
|
+
if timer is not None:
|
|
305
|
+
timer.cancel()
|
|
306
|
+
|
|
307
|
+
timer = threading.Timer(interval * 60, self._run_file_check, (folder,))
|
|
308
|
+
timer.daemon = True
|
|
309
|
+
self._file_check_timers[str(folder.uri)] = timer
|
|
310
|
+
timer.start()
|
|
311
|
+
|
|
312
|
+
def _run_file_check(self, folder: WorkspaceFolder) -> None:
|
|
313
|
+
try:
|
|
314
|
+
self.check_workspace_folder(folder)
|
|
315
|
+
except (SystemExit, KeyboardInterrupt):
|
|
316
|
+
raise
|
|
317
|
+
except BaseException as e:
|
|
318
|
+
self._logger.exception(e)
|
|
319
|
+
finally:
|
|
320
|
+
self._schedule_file_check(folder)
|
|
321
|
+
|
|
322
|
+
def _stop_file_checks(self, sender: Any) -> None:
|
|
323
|
+
with self._file_check_lock:
|
|
324
|
+
self._file_checks_stopped = True
|
|
325
|
+
for timer in self._file_check_timers.values():
|
|
326
|
+
timer.cancel()
|
|
327
|
+
self._file_check_timers.clear()
|
|
328
|
+
|
|
125
329
|
@rpc_method(name="robot/cache/clear", threaded=True)
|
|
126
330
|
def robot_cache_clear(self) -> None:
|
|
127
331
|
for folder in self.parent.workspace.workspace_folders:
|
|
@@ -14,13 +14,14 @@ from robotcode.robot.utils.ast import (
|
|
|
14
14
|
range_from_token,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
+
from .model_variables import find_model_variable_range_at
|
|
17
18
|
from .protocol_part import RobotLanguageServerProtocolPart
|
|
18
19
|
|
|
19
20
|
if TYPE_CHECKING:
|
|
20
21
|
from ..protocol import RobotLanguageServerProtocol
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
class RobotSelectionRangeProtocolPart(RobotLanguageServerProtocolPart
|
|
24
|
+
class RobotSelectionRangeProtocolPart(RobotLanguageServerProtocolPart):
|
|
24
25
|
_logger = LoggingDescriptor()
|
|
25
26
|
|
|
26
27
|
def __init__(self, parent: "RobotLanguageServerProtocol") -> None:
|
|
@@ -32,10 +33,12 @@ class RobotSelectionRangeProtocolPart(RobotLanguageServerProtocolPart, ModelHelp
|
|
|
32
33
|
@_logger.call
|
|
33
34
|
def collect(self, sender: Any, document: TextDocument, positions: List[Position]) -> Optional[List[SelectionRange]]:
|
|
34
35
|
namespace = self.parent.documents_cache.get_namespace(document)
|
|
36
|
+
semantic_model = namespace.semantic_model
|
|
37
|
+
model = self.parent.documents_cache.get_model(document)
|
|
35
38
|
|
|
36
39
|
results: List[SelectionRange] = []
|
|
37
40
|
for position in positions:
|
|
38
|
-
nodes = get_nodes_at_position(
|
|
41
|
+
nodes = get_nodes_at_position(model, position)
|
|
39
42
|
|
|
40
43
|
if not nodes:
|
|
41
44
|
break
|
|
@@ -52,17 +55,27 @@ class RobotSelectionRangeProtocolPart(RobotLanguageServerProtocolPart, ModelHelp
|
|
|
52
55
|
token = tokens[-1]
|
|
53
56
|
if token is not None:
|
|
54
57
|
current_range = SelectionRange(range_from_token(token), current_range)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
if semantic_model is not None:
|
|
59
|
+
# Model path: variable step from the model's
|
|
60
|
+
# statement tokens; the structural node/token
|
|
61
|
+
# walk above stays on the AST.
|
|
62
|
+
var_range = find_model_variable_range_at(
|
|
63
|
+
semantic_model, position, range_from_token(token)
|
|
64
|
+
)
|
|
65
|
+
if var_range is not None:
|
|
66
|
+
current_range = SelectionRange(var_range, current_range)
|
|
67
|
+
else:
|
|
68
|
+
for var_token, _ in ModelHelper.iter_variables_from_token(
|
|
69
|
+
token,
|
|
70
|
+
namespace,
|
|
71
|
+
position,
|
|
72
|
+
return_not_found=True,
|
|
73
|
+
):
|
|
74
|
+
var_token_range = range_from_token(var_token)
|
|
75
|
+
|
|
76
|
+
if position in var_token_range:
|
|
77
|
+
current_range = SelectionRange(var_token_range, current_range)
|
|
78
|
+
break
|
|
66
79
|
|
|
67
80
|
results.append(current_range)
|
|
68
81
|
|