robotcode-language-server 2.7.0__py3-none-any.whl → 2.8.0.dev8__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/cli.py +30 -5
- robotcode/language_server/common/parts/diagnostics.py +50 -17
- robotcode/language_server/common/parts/documents.py +32 -26
- 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 +437 -146
- 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/documentation_viewer.py +127 -0
- 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 +107 -45
- 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 +446 -113
- robotcode/language_server/robotframework/parts/signature_help.py +162 -70
- robotcode/language_server/robotframework/protocol.py +12 -1
- robotcode/language_server/robotframework/server.py +4 -2
- {robotcode_language_server-2.7.0.dist-info → robotcode_language_server-2.8.0.dev8.dist-info}/METADATA +7 -6
- {robotcode_language_server-2.7.0.dist-info → robotcode_language_server-2.8.0.dev8.dist-info}/RECORD +34 -32
- {robotcode_language_server-2.7.0.dist-info → robotcode_language_server-2.8.0.dev8.dist-info}/WHEEL +1 -1
- {robotcode_language_server-2.7.0.dist-info → robotcode_language_server-2.8.0.dev8.dist-info}/entry_points.txt +0 -0
|
@@ -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
|
|