python-delphi-lsp 3.6.0__cp310-abi3-win_amd64.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.
- delphi_lsp/__init__.py +184 -0
- delphi_lsp/_version.py +1 -0
- delphi_lsp/agent_cache.py +1574 -0
- delphi_lsp/agent_cli.py +902 -0
- delphi_lsp/agent_context.py +3907 -0
- delphi_lsp/agent_cpg.py +349 -0
- delphi_lsp/agent_cpg_builder.py +666 -0
- delphi_lsp/agent_layers.py +1410 -0
- delphi_lsp/agent_metrics.py +526 -0
- delphi_lsp/agent_protocol.py +445 -0
- delphi_lsp/agent_relations.py +1881 -0
- delphi_lsp/agent_templates.py +614 -0
- delphi_lsp/agent_wiki.py +2853 -0
- delphi_lsp/agent_workspace.py +860 -0
- delphi_lsp/ast_serialize.py +126 -0
- delphi_lsp/binary.py +250 -0
- delphi_lsp/comment_builder.py +28 -0
- delphi_lsp/consts.py +345 -0
- delphi_lsp/delphi_lsp_native.pyd +0 -0
- delphi_lsp/delphiast_lexer.py +399 -0
- delphi_lsp/delphiast_parser.py +2189 -0
- delphi_lsp/delphiast_tokens.py +41 -0
- delphi_lsp/grammar.py +459 -0
- delphi_lsp/incremental.py +1139 -0
- delphi_lsp/lark_builder.py +2684 -0
- delphi_lsp/lark_tokens.py +236 -0
- delphi_lsp/lexical_scanner.py +30 -0
- delphi_lsp/lsp_server.py +6698 -0
- delphi_lsp/metrics.py +1452 -0
- delphi_lsp/native_bridge.py +300 -0
- delphi_lsp/navigation_cache.py +189 -0
- delphi_lsp/nodes.py +462 -0
- delphi_lsp/parallel_outline.py +662 -0
- delphi_lsp/parse_artifacts.py +969 -0
- delphi_lsp/parser.py +342 -0
- delphi_lsp/parser_backend.py +139 -0
- delphi_lsp/preprocessor.py +1122 -0
- delphi_lsp/progress.py +26 -0
- delphi_lsp/project_config.py +285 -0
- delphi_lsp/project_discovery.py +932 -0
- delphi_lsp/project_indexer.py +470 -0
- delphi_lsp/py.typed +1 -0
- delphi_lsp/response_cache.py +465 -0
- delphi_lsp/semantic.py +394 -0
- delphi_lsp/semantic_builder.py +2302 -0
- delphi_lsp/source_reader.py +17 -0
- delphi_lsp/wiki_external_sort.py +392 -0
- delphi_lsp/wiki_memory.py +548 -0
- delphi_lsp/workspace.py +83 -0
- delphi_lsp/writer.py +73 -0
- python_delphi_lsp-3.6.0.dist-info/METADATA +766 -0
- python_delphi_lsp-3.6.0.dist-info/RECORD +56 -0
- python_delphi_lsp-3.6.0.dist-info/WHEEL +4 -0
- python_delphi_lsp-3.6.0.dist-info/entry_points.txt +3 -0
- python_delphi_lsp-3.6.0.dist-info/licenses/LICENSE +373 -0
- python_delphi_lsp-3.6.0.dist-info/sboms/python-delphi-lsp.cyclonedx.json +770 -0
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Iterable
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .consts import AttributeName, SyntaxNodeType
|
|
9
|
+
from .nodes import SyntaxNode
|
|
10
|
+
from .parser import DelphiParser
|
|
11
|
+
from .parser_backend import ParserBackend, ParserMode, normalize_backend, normalize_mode
|
|
12
|
+
from .preprocessor import IncludeLoader, PreprocessedSource
|
|
13
|
+
from .progress import ProgressCallback, ProgressEvent
|
|
14
|
+
from .project_config import ProjectPathConfig
|
|
15
|
+
from .source_reader import read_source_text
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ProjectProblemType(str, Enum):
|
|
19
|
+
CANT_FIND_FILE = 'cant_find_file'
|
|
20
|
+
CANT_OPEN_FILE = 'cant_open_file'
|
|
21
|
+
CANT_PARSE_FILE = 'cant_parse_file'
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class ProjectProblem:
|
|
26
|
+
problem_type: ProjectProblemType
|
|
27
|
+
file_name: str
|
|
28
|
+
description: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class UnitErrorInfo:
|
|
33
|
+
line: int = 0
|
|
34
|
+
col: int = 0
|
|
35
|
+
error: str = ''
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class UnitInfo:
|
|
40
|
+
name: str
|
|
41
|
+
path: str
|
|
42
|
+
syntax_tree: SyntaxNode | None
|
|
43
|
+
preprocessed: PreprocessedSource | None = None
|
|
44
|
+
has_error: bool = False
|
|
45
|
+
error_info: UnitErrorInfo = field(default_factory=UnitErrorInfo)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class IncludeFileInfo:
|
|
50
|
+
name: str
|
|
51
|
+
path: str
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class ProjectIndexResult:
|
|
56
|
+
parsed_units: list[UnitInfo]
|
|
57
|
+
include_files: list[IncludeFileInfo]
|
|
58
|
+
problems: list[ProjectProblem]
|
|
59
|
+
not_found_units: list[str]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
GetUnitSyntaxHook = Callable[[str], tuple[SyntaxNode | None, bool, bool]]
|
|
63
|
+
UnitParsedHook = Callable[[str, str, SyntaxNode, bool], bool]
|
|
64
|
+
SourceTransform = Callable[[str], str]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ProjectIndexer:
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
*,
|
|
71
|
+
search_paths: Iterable[str] = (),
|
|
72
|
+
include_paths: Iterable[str] = (),
|
|
73
|
+
defines: Iterable[str] = (),
|
|
74
|
+
include_loader: IncludeLoader | None = None,
|
|
75
|
+
on_get_unit_syntax: GetUnitSyntaxHook | None = None,
|
|
76
|
+
on_unit_parsed: UnitParsedHook | None = None,
|
|
77
|
+
source_transform: SourceTransform | None = None,
|
|
78
|
+
retain_syntax: bool = True,
|
|
79
|
+
on_progress: ProgressCallback | None = None,
|
|
80
|
+
backend: ParserBackend | str = ParserBackend.DELPHIAST,
|
|
81
|
+
mode: ParserMode | str = ParserMode.STRICT,
|
|
82
|
+
source_roots: Iterable[str | Path] = (),
|
|
83
|
+
project_config: ProjectPathConfig | None = None,
|
|
84
|
+
) -> None:
|
|
85
|
+
self.search_paths = [Path(path) for path in search_paths]
|
|
86
|
+
self.include_paths = [Path(path) for path in include_paths]
|
|
87
|
+
self.defines = list(defines)
|
|
88
|
+
self.include_loader = include_loader
|
|
89
|
+
self.on_get_unit_syntax = on_get_unit_syntax
|
|
90
|
+
self.on_unit_parsed = on_unit_parsed
|
|
91
|
+
self.source_transform = source_transform
|
|
92
|
+
self.retain_syntax = retain_syntax
|
|
93
|
+
self.on_progress = on_progress
|
|
94
|
+
self.backend = normalize_backend(backend)
|
|
95
|
+
self.mode = normalize_mode(mode)
|
|
96
|
+
self.source_roots = tuple(
|
|
97
|
+
Path(path).expanduser().resolve() for path in source_roots
|
|
98
|
+
)
|
|
99
|
+
self.project_config = project_config
|
|
100
|
+
|
|
101
|
+
self._parsed_units: dict[str, UnitInfo] = {}
|
|
102
|
+
self._problems: list[ProjectProblem] = []
|
|
103
|
+
self._not_found_units: set[str] = set()
|
|
104
|
+
self._include_files: dict[str, IncludeFileInfo] = {}
|
|
105
|
+
self._project_folder: Path = Path('.')
|
|
106
|
+
self._aborting: bool = False
|
|
107
|
+
self._files_discovered = 0
|
|
108
|
+
self._files_completed = 0
|
|
109
|
+
self._lines_processed = 0
|
|
110
|
+
|
|
111
|
+
def index(self, file_name: str) -> ProjectIndexResult:
|
|
112
|
+
self._parsed_units = {}
|
|
113
|
+
self._problems = []
|
|
114
|
+
self._not_found_units = set()
|
|
115
|
+
self._include_files = {}
|
|
116
|
+
self._aborting = False
|
|
117
|
+
self._files_discovered = 0
|
|
118
|
+
self._files_completed = 0
|
|
119
|
+
self._lines_processed = 0
|
|
120
|
+
|
|
121
|
+
entry = Path(file_name).expanduser().resolve()
|
|
122
|
+
self._project_folder = entry.parent
|
|
123
|
+
self._emit_progress("discovery", str(entry), "project index started")
|
|
124
|
+
|
|
125
|
+
is_project = entry.suffix.casefold() in {'.dpr', '.dpk'}
|
|
126
|
+
self._parse_unit(entry.stem, entry, is_project=is_project)
|
|
127
|
+
|
|
128
|
+
parsed_units = sorted(
|
|
129
|
+
self._parsed_units.values(),
|
|
130
|
+
key=lambda item: (
|
|
131
|
+
item.name.casefold(),
|
|
132
|
+
item.name,
|
|
133
|
+
item.path.casefold(),
|
|
134
|
+
item.path,
|
|
135
|
+
),
|
|
136
|
+
)
|
|
137
|
+
include_files = sorted(
|
|
138
|
+
self._include_files.values(),
|
|
139
|
+
key=lambda item: (
|
|
140
|
+
item.name.casefold(),
|
|
141
|
+
item.name,
|
|
142
|
+
item.path.casefold(),
|
|
143
|
+
item.path,
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
problems = list(self._problems)
|
|
147
|
+
not_found = sorted(
|
|
148
|
+
self._not_found_units,
|
|
149
|
+
key=lambda value: (value.casefold(), value),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
result = ProjectIndexResult(
|
|
153
|
+
parsed_units=parsed_units,
|
|
154
|
+
include_files=include_files,
|
|
155
|
+
problems=problems,
|
|
156
|
+
not_found_units=not_found,
|
|
157
|
+
)
|
|
158
|
+
self._emit_progress("complete", str(entry), "project index complete")
|
|
159
|
+
return result
|
|
160
|
+
|
|
161
|
+
def _parse_unit(self, unit_name: str, file_path: Path, *, is_project: bool) -> None:
|
|
162
|
+
if self._aborting:
|
|
163
|
+
return
|
|
164
|
+
if not self._source_is_allowed(file_path):
|
|
165
|
+
self._not_found_units.add(unit_name)
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
normalized_name = unit_name.casefold()
|
|
169
|
+
if normalized_name in self._parsed_units:
|
|
170
|
+
return
|
|
171
|
+
self._files_discovered += 1
|
|
172
|
+
self._emit_progress("inventory", str(file_path), "unit discovered")
|
|
173
|
+
|
|
174
|
+
hook_tree: SyntaxNode | None = None
|
|
175
|
+
do_parse_unit = True
|
|
176
|
+
if self.on_get_unit_syntax is not None:
|
|
177
|
+
hook_tree, do_parse_unit, do_abort = self.on_get_unit_syntax(str(file_path))
|
|
178
|
+
if do_abort:
|
|
179
|
+
self._aborting = True
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
syntax_tree: SyntaxNode | None = hook_tree
|
|
183
|
+
preprocessed: PreprocessedSource | None = None
|
|
184
|
+
from_parser = False
|
|
185
|
+
parser_error = UnitErrorInfo()
|
|
186
|
+
|
|
187
|
+
if syntax_tree is None and do_parse_unit:
|
|
188
|
+
source = self._read_file(file_path)
|
|
189
|
+
if source is None:
|
|
190
|
+
self._files_completed += 1
|
|
191
|
+
self._emit_progress("detail", str(file_path), "unit unavailable")
|
|
192
|
+
return
|
|
193
|
+
self._lines_processed += source.count("\n") + (
|
|
194
|
+
0 if not source or source.endswith("\n") else 1
|
|
195
|
+
)
|
|
196
|
+
parser = DelphiParser(
|
|
197
|
+
include_paths=[str(path) for path in self.include_paths],
|
|
198
|
+
defines=self.defines,
|
|
199
|
+
include_loader=self._build_include_loader(),
|
|
200
|
+
backend=self.backend,
|
|
201
|
+
mode=self.mode,
|
|
202
|
+
)
|
|
203
|
+
try:
|
|
204
|
+
if self.source_transform is not None:
|
|
205
|
+
source = self.source_transform(source)
|
|
206
|
+
result = parser.parse(source, str(file_path), build_semantic=False)
|
|
207
|
+
syntax_tree = result.root
|
|
208
|
+
preprocessed = result.preprocessed
|
|
209
|
+
from_parser = True
|
|
210
|
+
if result.problems:
|
|
211
|
+
first = result.problems[0]
|
|
212
|
+
parser_error = UnitErrorInfo(
|
|
213
|
+
line=first.line,
|
|
214
|
+
col=first.column,
|
|
215
|
+
error=first.message,
|
|
216
|
+
)
|
|
217
|
+
self._problems.append(
|
|
218
|
+
ProjectProblem(
|
|
219
|
+
problem_type=ProjectProblemType.CANT_PARSE_FILE,
|
|
220
|
+
file_name=str(file_path),
|
|
221
|
+
description=(
|
|
222
|
+
f"Partial DelphiAST parse at {first.line}:{first.column}: "
|
|
223
|
+
f"{first.message}"
|
|
224
|
+
),
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
except Exception as exc: # pragma: no cover - parser error branch
|
|
228
|
+
self._problems.append(
|
|
229
|
+
ProjectProblem(
|
|
230
|
+
problem_type=ProjectProblemType.CANT_PARSE_FILE,
|
|
231
|
+
file_name=str(file_path),
|
|
232
|
+
description=str(exc),
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
self._parsed_units[normalized_name] = UnitInfo(
|
|
236
|
+
name=unit_name,
|
|
237
|
+
path=str(file_path),
|
|
238
|
+
syntax_tree=None,
|
|
239
|
+
has_error=True,
|
|
240
|
+
error_info=UnitErrorInfo(error=str(exc)),
|
|
241
|
+
)
|
|
242
|
+
self._files_completed += 1
|
|
243
|
+
self._emit_progress("detail", str(file_path), "unit parse failed")
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
if syntax_tree is None:
|
|
247
|
+
self._files_completed += 1
|
|
248
|
+
self._emit_progress("detail", str(file_path), "unit skipped")
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
actual_name = syntax_tree.get_attribute(AttributeName.anName) or unit_name
|
|
252
|
+
normalized_name = actual_name.casefold()
|
|
253
|
+
unit_info = UnitInfo(
|
|
254
|
+
name=actual_name,
|
|
255
|
+
path=str(file_path),
|
|
256
|
+
syntax_tree=syntax_tree if self.retain_syntax else None,
|
|
257
|
+
preprocessed=preprocessed if self.retain_syntax else None,
|
|
258
|
+
has_error=bool(parser_error.error),
|
|
259
|
+
error_info=parser_error,
|
|
260
|
+
)
|
|
261
|
+
self._parsed_units[normalized_name] = unit_info
|
|
262
|
+
self._files_completed += 1
|
|
263
|
+
self._emit_progress("detail", str(file_path), "unit parsed")
|
|
264
|
+
|
|
265
|
+
if self.on_unit_parsed is not None:
|
|
266
|
+
do_abort = self.on_unit_parsed(actual_name, str(file_path), syntax_tree, from_parser)
|
|
267
|
+
if do_abort:
|
|
268
|
+
self._aborting = True
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
dependencies = self._collect_dependencies(
|
|
272
|
+
syntax_tree,
|
|
273
|
+
file_path,
|
|
274
|
+
is_project=is_project,
|
|
275
|
+
)
|
|
276
|
+
if not self.retain_syntax:
|
|
277
|
+
syntax_tree = None
|
|
278
|
+
preprocessed = None
|
|
279
|
+
hook_tree = None
|
|
280
|
+
if from_parser:
|
|
281
|
+
del result
|
|
282
|
+
del parser
|
|
283
|
+
source = ""
|
|
284
|
+
|
|
285
|
+
for dep_name, dep_path in dependencies:
|
|
286
|
+
if self._aborting:
|
|
287
|
+
return
|
|
288
|
+
resolved = self._resolve_unit_path(dep_name, dep_path, relative_to=file_path.parent)
|
|
289
|
+
if resolved is None:
|
|
290
|
+
self._not_found_units.add(dep_name)
|
|
291
|
+
self._problems.append(
|
|
292
|
+
ProjectProblem(
|
|
293
|
+
problem_type=ProjectProblemType.CANT_FIND_FILE,
|
|
294
|
+
file_name=dep_name,
|
|
295
|
+
description=f'Unit not found: {dep_name}',
|
|
296
|
+
)
|
|
297
|
+
)
|
|
298
|
+
continue
|
|
299
|
+
self._parse_unit(dep_name, resolved, is_project=False)
|
|
300
|
+
|
|
301
|
+
def _read_file(self, file_path: Path) -> str | None:
|
|
302
|
+
try:
|
|
303
|
+
return read_source_text(file_path)
|
|
304
|
+
except (OSError, UnicodeError) as exc:
|
|
305
|
+
self._problems.append(
|
|
306
|
+
ProjectProblem(
|
|
307
|
+
problem_type=ProjectProblemType.CANT_OPEN_FILE,
|
|
308
|
+
file_name=str(file_path),
|
|
309
|
+
description=str(exc),
|
|
310
|
+
)
|
|
311
|
+
)
|
|
312
|
+
return None
|
|
313
|
+
|
|
314
|
+
def _collect_dependencies(
|
|
315
|
+
self,
|
|
316
|
+
syntax_tree: SyntaxNode,
|
|
317
|
+
file_path: Path,
|
|
318
|
+
*,
|
|
319
|
+
is_project: bool,
|
|
320
|
+
) -> list[tuple[str, str | None]]:
|
|
321
|
+
deps: list[tuple[str, str | None]] = []
|
|
322
|
+
if is_project:
|
|
323
|
+
self._append_units_from_node(syntax_tree, SyntaxNodeType.ntUses, deps)
|
|
324
|
+
self._append_units_from_node(syntax_tree, SyntaxNodeType.ntContains, deps)
|
|
325
|
+
return deps
|
|
326
|
+
|
|
327
|
+
intf = syntax_tree.find_node(SyntaxNodeType.ntInterface)
|
|
328
|
+
impl = syntax_tree.find_node(SyntaxNodeType.ntImplementation)
|
|
329
|
+
if intf is not None:
|
|
330
|
+
self._append_units_from_node(intf, SyntaxNodeType.ntUses, deps)
|
|
331
|
+
if impl is not None:
|
|
332
|
+
self._append_units_from_node(impl, SyntaxNodeType.ntUses, deps)
|
|
333
|
+
return deps
|
|
334
|
+
|
|
335
|
+
def _append_units_from_node(
|
|
336
|
+
self,
|
|
337
|
+
root: SyntaxNode,
|
|
338
|
+
node_type: SyntaxNodeType,
|
|
339
|
+
deps: list[tuple[str, str | None]],
|
|
340
|
+
) -> None:
|
|
341
|
+
target = root.find_node(node_type)
|
|
342
|
+
if target is None:
|
|
343
|
+
return
|
|
344
|
+
for child in target.child_nodes:
|
|
345
|
+
if child.typ != SyntaxNodeType.ntUnit:
|
|
346
|
+
continue
|
|
347
|
+
name = child.get_attribute(AttributeName.anName)
|
|
348
|
+
if not name:
|
|
349
|
+
continue
|
|
350
|
+
dep_path = child.get_attribute(AttributeName.anPath) or None
|
|
351
|
+
deps.append((name, dep_path))
|
|
352
|
+
|
|
353
|
+
def _resolve_unit_path(self, unit_name: str, unit_path: str | None, *, relative_to: Path) -> Path | None:
|
|
354
|
+
candidates: list[Path] = []
|
|
355
|
+
|
|
356
|
+
if unit_path:
|
|
357
|
+
hint = Path(unit_path.replace('\\', '/'))
|
|
358
|
+
if hint.is_absolute():
|
|
359
|
+
candidates.append(hint)
|
|
360
|
+
else:
|
|
361
|
+
candidates.append((relative_to / hint).resolve())
|
|
362
|
+
|
|
363
|
+
unit_file_names: list[str]
|
|
364
|
+
normalized_unit_name = unit_name.replace('\\', '/')
|
|
365
|
+
if Path(normalized_unit_name).suffix.casefold() in {'.pas', '.dpr', '.dpk'}:
|
|
366
|
+
unit_file_names = [normalized_unit_name]
|
|
367
|
+
else:
|
|
368
|
+
unit_file_names = [
|
|
369
|
+
f'{normalized_unit_name}.pas',
|
|
370
|
+
f'{normalized_unit_name}.dpr',
|
|
371
|
+
f'{normalized_unit_name}.dpk',
|
|
372
|
+
]
|
|
373
|
+
|
|
374
|
+
search_roots = [relative_to, self._project_folder, *self.search_paths, *self.include_paths]
|
|
375
|
+
for root in search_roots:
|
|
376
|
+
for file_name in unit_file_names:
|
|
377
|
+
candidates.append((root / file_name).resolve())
|
|
378
|
+
|
|
379
|
+
seen: set[str] = set()
|
|
380
|
+
for candidate in candidates:
|
|
381
|
+
key = str(candidate).casefold()
|
|
382
|
+
if key in seen:
|
|
383
|
+
continue
|
|
384
|
+
seen.add(key)
|
|
385
|
+
if (
|
|
386
|
+
candidate.exists()
|
|
387
|
+
and candidate.is_file()
|
|
388
|
+
and self._source_is_allowed(candidate)
|
|
389
|
+
):
|
|
390
|
+
return candidate
|
|
391
|
+
return None
|
|
392
|
+
|
|
393
|
+
def _build_include_loader(self) -> IncludeLoader:
|
|
394
|
+
base_loader = self.include_loader or self._default_include_loader
|
|
395
|
+
|
|
396
|
+
def wrapped(parent_file: str, include_name: str) -> tuple[str, str] | None:
|
|
397
|
+
resolved = base_loader(parent_file, include_name)
|
|
398
|
+
if resolved is None:
|
|
399
|
+
return None
|
|
400
|
+
content, resolved_path = resolved
|
|
401
|
+
if not self._source_is_allowed(Path(resolved_path)):
|
|
402
|
+
return None
|
|
403
|
+
cache_key = f'{include_name.casefold()}::{resolved_path.casefold()}'
|
|
404
|
+
self._include_files[cache_key] = IncludeFileInfo(name=include_name, path=resolved_path)
|
|
405
|
+
return content, resolved_path
|
|
406
|
+
|
|
407
|
+
return wrapped
|
|
408
|
+
|
|
409
|
+
def _source_is_allowed(self, path: Path) -> bool:
|
|
410
|
+
try:
|
|
411
|
+
resolved = path.expanduser().resolve()
|
|
412
|
+
except OSError:
|
|
413
|
+
return False
|
|
414
|
+
if (
|
|
415
|
+
self.project_config is not None
|
|
416
|
+
and self.project_config.excludes_workspace_path(resolved)
|
|
417
|
+
):
|
|
418
|
+
return False
|
|
419
|
+
if not self.source_roots:
|
|
420
|
+
return True
|
|
421
|
+
return any(resolved.is_relative_to(root) for root in self.source_roots)
|
|
422
|
+
|
|
423
|
+
def _emit_progress(self, phase: str, path: str, detail: str) -> None:
|
|
424
|
+
if self.on_progress is not None:
|
|
425
|
+
self.on_progress(
|
|
426
|
+
ProgressEvent(
|
|
427
|
+
phase=phase,
|
|
428
|
+
language="delphi",
|
|
429
|
+
path=path,
|
|
430
|
+
files_discovered=self._files_discovered,
|
|
431
|
+
files_completed=self._files_completed,
|
|
432
|
+
files_total=self._files_discovered,
|
|
433
|
+
lines_processed=self._lines_processed,
|
|
434
|
+
symbols_discovered=0,
|
|
435
|
+
cached_files=0,
|
|
436
|
+
detail=detail,
|
|
437
|
+
)
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
def _default_include_loader(self, parent_file: str, include_name: str) -> tuple[str, str] | None:
|
|
441
|
+
parent = Path(parent_file).resolve().parent
|
|
442
|
+
include_path = Path(include_name.replace('\\', '/'))
|
|
443
|
+
search_roots = [parent, self._project_folder, *self.include_paths, *self.search_paths]
|
|
444
|
+
for root in search_roots:
|
|
445
|
+
candidate = (root / include_path).resolve()
|
|
446
|
+
if (
|
|
447
|
+
candidate.exists()
|
|
448
|
+
and candidate.is_file()
|
|
449
|
+
and self._source_is_allowed(candidate)
|
|
450
|
+
):
|
|
451
|
+
try:
|
|
452
|
+
content = read_source_text(candidate)
|
|
453
|
+
except OSError:
|
|
454
|
+
continue
|
|
455
|
+
return content, str(candidate)
|
|
456
|
+
return None
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
__all__ = [
|
|
460
|
+
'GetUnitSyntaxHook',
|
|
461
|
+
'IncludeFileInfo',
|
|
462
|
+
'ProjectIndexResult',
|
|
463
|
+
'ProjectIndexer',
|
|
464
|
+
'ProjectProblem',
|
|
465
|
+
'ProjectProblemType',
|
|
466
|
+
'SourceTransform',
|
|
467
|
+
'UnitErrorInfo',
|
|
468
|
+
'UnitInfo',
|
|
469
|
+
'UnitParsedHook',
|
|
470
|
+
]
|
delphi_lsp/py.typed
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|