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.
Files changed (56) hide show
  1. delphi_lsp/__init__.py +184 -0
  2. delphi_lsp/_version.py +1 -0
  3. delphi_lsp/agent_cache.py +1574 -0
  4. delphi_lsp/agent_cli.py +902 -0
  5. delphi_lsp/agent_context.py +3907 -0
  6. delphi_lsp/agent_cpg.py +349 -0
  7. delphi_lsp/agent_cpg_builder.py +666 -0
  8. delphi_lsp/agent_layers.py +1410 -0
  9. delphi_lsp/agent_metrics.py +526 -0
  10. delphi_lsp/agent_protocol.py +445 -0
  11. delphi_lsp/agent_relations.py +1881 -0
  12. delphi_lsp/agent_templates.py +614 -0
  13. delphi_lsp/agent_wiki.py +2853 -0
  14. delphi_lsp/agent_workspace.py +860 -0
  15. delphi_lsp/ast_serialize.py +126 -0
  16. delphi_lsp/binary.py +250 -0
  17. delphi_lsp/comment_builder.py +28 -0
  18. delphi_lsp/consts.py +345 -0
  19. delphi_lsp/delphi_lsp_native.pyd +0 -0
  20. delphi_lsp/delphiast_lexer.py +399 -0
  21. delphi_lsp/delphiast_parser.py +2189 -0
  22. delphi_lsp/delphiast_tokens.py +41 -0
  23. delphi_lsp/grammar.py +459 -0
  24. delphi_lsp/incremental.py +1139 -0
  25. delphi_lsp/lark_builder.py +2684 -0
  26. delphi_lsp/lark_tokens.py +236 -0
  27. delphi_lsp/lexical_scanner.py +30 -0
  28. delphi_lsp/lsp_server.py +6698 -0
  29. delphi_lsp/metrics.py +1452 -0
  30. delphi_lsp/native_bridge.py +300 -0
  31. delphi_lsp/navigation_cache.py +189 -0
  32. delphi_lsp/nodes.py +462 -0
  33. delphi_lsp/parallel_outline.py +662 -0
  34. delphi_lsp/parse_artifacts.py +969 -0
  35. delphi_lsp/parser.py +342 -0
  36. delphi_lsp/parser_backend.py +139 -0
  37. delphi_lsp/preprocessor.py +1122 -0
  38. delphi_lsp/progress.py +26 -0
  39. delphi_lsp/project_config.py +285 -0
  40. delphi_lsp/project_discovery.py +932 -0
  41. delphi_lsp/project_indexer.py +470 -0
  42. delphi_lsp/py.typed +1 -0
  43. delphi_lsp/response_cache.py +465 -0
  44. delphi_lsp/semantic.py +394 -0
  45. delphi_lsp/semantic_builder.py +2302 -0
  46. delphi_lsp/source_reader.py +17 -0
  47. delphi_lsp/wiki_external_sort.py +392 -0
  48. delphi_lsp/wiki_memory.py +548 -0
  49. delphi_lsp/workspace.py +83 -0
  50. delphi_lsp/writer.py +73 -0
  51. python_delphi_lsp-3.6.0.dist-info/METADATA +766 -0
  52. python_delphi_lsp-3.6.0.dist-info/RECORD +56 -0
  53. python_delphi_lsp-3.6.0.dist-info/WHEEL +4 -0
  54. python_delphi_lsp-3.6.0.dist-info/entry_points.txt +3 -0
  55. python_delphi_lsp-3.6.0.dist-info/licenses/LICENSE +373 -0
  56. python_delphi_lsp-3.6.0.dist-info/sboms/python-delphi-lsp.cyclonedx.json +770 -0
delphi_lsp/progress.py ADDED
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from dataclasses import dataclass
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class ProgressEvent:
9
+ """A package-controlled snapshot of Delphi indexing progress."""
10
+
11
+ phase: str
12
+ language: str
13
+ path: str
14
+ files_discovered: int
15
+ files_completed: int
16
+ files_total: int | None
17
+ lines_processed: int
18
+ symbols_discovered: int
19
+ cached_files: int
20
+ detail: str
21
+
22
+
23
+ ProgressCallback = Callable[[ProgressEvent], None]
24
+
25
+
26
+ __all__ = ["ProgressCallback", "ProgressEvent"]
@@ -0,0 +1,285 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import re
5
+ from collections.abc import Iterable, Mapping
6
+ from dataclasses import dataclass
7
+ from functools import lru_cache
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from .preprocessor import IncludeLoader
12
+ from .source_reader import read_source_text
13
+
14
+ try:
15
+ import tomllib
16
+ except ModuleNotFoundError: # pragma: no cover - exercised by Python 3.10 CI
17
+ import tomli as tomllib # type: ignore[import-not-found,no-redef]
18
+
19
+
20
+ CONFIG_FILE_NAME = ".delphi-lsp.toml"
21
+ _PROJECT_KEYS = frozenset({"include", "exclude"})
22
+ _WORKSPACE_KEYS = frozenset({"exclude"})
23
+ _WINDOWS_ABSOLUTE_RE = re.compile(r"^[A-Za-z]:/")
24
+
25
+
26
+ class ProjectConfigError(ValueError):
27
+ """Raised when repository project selection configuration is invalid."""
28
+
29
+
30
+ @dataclass(frozen=True, slots=True)
31
+ class ProjectPathConfig:
32
+ root: Path
33
+ source: Path
34
+ include: tuple[str, ...] = ()
35
+ exclude: tuple[str, ...] = ()
36
+ workspace_exclude: tuple[str, ...] = ()
37
+
38
+ @property
39
+ def has_filters(self) -> bool:
40
+ return bool(self.include or self.exclude)
41
+
42
+ def selects(self, *paths: str | Path) -> bool:
43
+ relative_paths = tuple(
44
+ relative
45
+ for path in paths
46
+ if (relative := _repository_relative_path(path, self.root)) is not None
47
+ )
48
+ if not relative_paths:
49
+ return False
50
+ if self.include and not _matches_any(relative_paths, self.include):
51
+ return False
52
+ return not _matches_any(relative_paths, self.exclude)
53
+
54
+ def excludes_workspace_path(self, path: str | Path) -> bool:
55
+ if not self.workspace_exclude:
56
+ return False
57
+ lexical = _lexical_repository_relative_path(path, self.root)
58
+ resolved = _repository_relative_path(path, self.root)
59
+ if lexical is None or resolved is None:
60
+ return True
61
+ return _matches_any(
62
+ (lexical, resolved),
63
+ self.workspace_exclude,
64
+ )
65
+
66
+
67
+ def load_project_path_config(root: str | Path) -> ProjectPathConfig | None:
68
+ root_path = Path(root).expanduser().resolve()
69
+ config_path = root_path / CONFIG_FILE_NAME
70
+ if not config_path.is_file():
71
+ return None
72
+ try:
73
+ with config_path.open("rb") as stream:
74
+ document = tomllib.load(stream)
75
+ except (OSError, tomllib.TOMLDecodeError) as exc:
76
+ raise ProjectConfigError(f"{config_path}: Invalid TOML: {exc}") from exc
77
+
78
+ projects = _load_table(document, "projects", _PROJECT_KEYS, config_path)
79
+ workspace = _load_table(document, "workspace", _WORKSPACE_KEYS, config_path)
80
+ include = _load_patterns(projects, "include", config_path, section="projects")
81
+ exclude = _load_patterns(projects, "exclude", config_path, section="projects")
82
+ workspace_exclude = _load_patterns(
83
+ workspace,
84
+ "exclude",
85
+ config_path,
86
+ section="workspace",
87
+ )
88
+ return ProjectPathConfig(
89
+ root=root_path,
90
+ source=config_path.resolve(),
91
+ include=include,
92
+ exclude=exclude,
93
+ workspace_exclude=workspace_exclude,
94
+ )
95
+
96
+
97
+ def workspace_include_loader(
98
+ project_config: ProjectPathConfig | None,
99
+ include_paths: Iterable[str | Path] = (),
100
+ ) -> IncludeLoader | None:
101
+ """Build an include loader that enforces the repository's hard path boundary."""
102
+
103
+ enforce_boundary = (
104
+ project_config is not None
105
+ and bool(project_config.workspace_exclude)
106
+ )
107
+ search_paths = tuple(
108
+ Path(path).expanduser().resolve()
109
+ for path in include_paths
110
+ )
111
+
112
+ def load(parent_file: str, include_name: str) -> tuple[str, str] | None:
113
+ include_path = Path(include_name.replace("\\", "/"))
114
+ parent = Path(parent_file).expanduser().resolve().parent
115
+ namespaced_source = (
116
+ parent.parent / "src"
117
+ if parent.name.casefold() == "namespaced"
118
+ else None
119
+ )
120
+ bases = tuple(
121
+ dict.fromkeys(
122
+ base
123
+ for base in (parent, namespaced_source, *search_paths)
124
+ if base is not None
125
+ )
126
+ )
127
+ for base in bases:
128
+ candidate = (base / include_path).resolve()
129
+ if (
130
+ enforce_boundary
131
+ and project_config is not None
132
+ and project_config.excludes_workspace_path(candidate)
133
+ ):
134
+ continue
135
+ try:
136
+ if candidate.is_file():
137
+ return read_source_text(candidate), str(candidate)
138
+ except (OSError, UnicodeError):
139
+ continue
140
+ return None
141
+
142
+ return load
143
+
144
+
145
+ def _load_table(
146
+ document: Mapping[str, Any],
147
+ name: str,
148
+ supported_keys: frozenset[str],
149
+ config_path: Path,
150
+ ) -> Mapping[str, Any]:
151
+ table = document.get(name, {})
152
+ if not isinstance(table, Mapping):
153
+ raise ProjectConfigError(f"{config_path}: [{name}] must be a TOML table.")
154
+ unknown = sorted(set(table) - supported_keys)
155
+ if unknown:
156
+ rendered = ", ".join(str(key) for key in unknown)
157
+ raise ProjectConfigError(
158
+ f"{config_path}: Unsupported key in [{name}]: {rendered}."
159
+ )
160
+ return table
161
+
162
+
163
+ def _load_patterns(
164
+ projects: Mapping[str, Any],
165
+ name: str,
166
+ config_path: Path,
167
+ *,
168
+ section: str,
169
+ ) -> tuple[str, ...]:
170
+ values = projects.get(name, [])
171
+ if not isinstance(values, list):
172
+ raise ProjectConfigError(
173
+ f"{config_path}: {section}.{name} must be an array of strings."
174
+ )
175
+ if any(not isinstance(value, str) for value in values):
176
+ raise ProjectConfigError(
177
+ f"{config_path}: {section}.{name} must contain only strings."
178
+ )
179
+ normalized = tuple(
180
+ _normalize_pattern(
181
+ value,
182
+ config_path=config_path,
183
+ setting=f"{section}.{name}",
184
+ )
185
+ for value in values
186
+ )
187
+ return tuple(dict.fromkeys(normalized))
188
+
189
+
190
+ def _normalize_pattern(value: str, *, config_path: Path, setting: str) -> str:
191
+ pattern = value.strip().replace("\\", "/")
192
+ while pattern.startswith("./"):
193
+ pattern = pattern[2:]
194
+ pattern = pattern.rstrip("/")
195
+ pattern = re.sub(r"(?:\*\*/)+", "**/", pattern)
196
+ if not pattern:
197
+ raise ProjectConfigError(
198
+ f"{config_path}: {setting} patterns must not be empty."
199
+ )
200
+ if pattern.startswith("/") or _WINDOWS_ABSOLUTE_RE.match(pattern):
201
+ raise ProjectConfigError(
202
+ f"{config_path}: {setting} patterns must be repository-relative."
203
+ )
204
+ if any(part == ".." for part in pattern.split("/")):
205
+ raise ProjectConfigError(
206
+ f"{config_path}: {setting} patterns must not contain '..'."
207
+ )
208
+ return pattern
209
+
210
+
211
+ def _repository_relative_path(path: str | Path, root: Path) -> str | None:
212
+ candidate = Path(path).expanduser()
213
+ if not candidate.is_absolute():
214
+ candidate = root / candidate
215
+ try:
216
+ relative = candidate.resolve().relative_to(root)
217
+ except (OSError, ValueError):
218
+ return None
219
+ return relative.as_posix()
220
+
221
+
222
+ def _lexical_repository_relative_path(
223
+ path: str | Path,
224
+ root: Path,
225
+ ) -> str | None:
226
+ candidate = Path(path).expanduser()
227
+ if not candidate.is_absolute():
228
+ candidate = root / candidate
229
+ try:
230
+ absolute = Path(os.path.abspath(os.path.normpath(candidate)))
231
+ relative = absolute.relative_to(root)
232
+ except (OSError, ValueError):
233
+ return None
234
+ return relative.as_posix()
235
+
236
+
237
+ def _matches_any(paths: tuple[str, ...], patterns: tuple[str, ...]) -> bool:
238
+ return any(
239
+ _path_matches_pattern(path.casefold(), pattern.casefold())
240
+ for path in paths
241
+ for pattern in patterns
242
+ )
243
+
244
+
245
+ def _path_matches_pattern(path: str, pattern: str) -> bool:
246
+ if not any(token in pattern for token in ("*", "?")):
247
+ return path == pattern or path.startswith(f"{pattern}/")
248
+ if pattern.endswith("/**"):
249
+ directory_pattern = pattern[:-3].rstrip("/")
250
+ if _compile_pattern(directory_pattern).fullmatch(path):
251
+ return True
252
+ return bool(_compile_pattern(pattern).fullmatch(path))
253
+
254
+
255
+ @lru_cache(maxsize=256)
256
+ def _compile_pattern(pattern: str) -> re.Pattern[str]:
257
+ translated: list[str] = []
258
+ index = 0
259
+ while index < len(pattern):
260
+ if pattern.startswith("**/", index):
261
+ translated.append("(?:.*/)?")
262
+ index += 3
263
+ continue
264
+ if pattern.startswith("**", index):
265
+ translated.append(".*")
266
+ index += 2
267
+ continue
268
+ character = pattern[index]
269
+ if character == "*":
270
+ translated.append("[^/]*")
271
+ elif character == "?":
272
+ translated.append("[^/]")
273
+ else:
274
+ translated.append(re.escape(character))
275
+ index += 1
276
+ return re.compile("".join(translated))
277
+
278
+
279
+ __all__ = [
280
+ "CONFIG_FILE_NAME",
281
+ "ProjectConfigError",
282
+ "ProjectPathConfig",
283
+ "load_project_path_config",
284
+ "workspace_include_loader",
285
+ ]