agent-codinglanguage-mapper 1.0.0__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.
Files changed (42) hide show
  1. agent_codinglanguage_mapper/__init__.py +20 -0
  2. agent_codinglanguage_mapper/__main__.py +4 -0
  3. agent_codinglanguage_mapper/_version.py +2 -0
  4. agent_codinglanguage_mapper/adapters/__init__.py +4 -0
  5. agent_codinglanguage_mapper/adapters/delphi.py +822 -0
  6. agent_codinglanguage_mapper/adapters/delphi_frontend/__init__.py +6 -0
  7. agent_codinglanguage_mapper/adapters/delphi_frontend/comment_builder.py +29 -0
  8. agent_codinglanguage_mapper/adapters/delphi_frontend/consts.py +345 -0
  9. agent_codinglanguage_mapper/adapters/delphi_frontend/grammar.py +460 -0
  10. agent_codinglanguage_mapper/adapters/delphi_frontend/lark_builder.py +2674 -0
  11. agent_codinglanguage_mapper/adapters/delphi_frontend/lark_tokens.py +237 -0
  12. agent_codinglanguage_mapper/adapters/delphi_frontend/lsp_server.py +1832 -0
  13. agent_codinglanguage_mapper/adapters/delphi_frontend/nodes.py +371 -0
  14. agent_codinglanguage_mapper/adapters/delphi_frontend/parser.py +193 -0
  15. agent_codinglanguage_mapper/adapters/delphi_frontend/preprocessor.py +997 -0
  16. agent_codinglanguage_mapper/adapters/delphi_frontend/project_discovery.py +518 -0
  17. agent_codinglanguage_mapper/adapters/delphi_frontend/project_indexer.py +319 -0
  18. agent_codinglanguage_mapper/adapters/delphi_frontend/semantic.py +375 -0
  19. agent_codinglanguage_mapper/adapters/delphi_frontend/semantic_builder.py +1384 -0
  20. agent_codinglanguage_mapper/adapters/delphi_frontend/source_reader.py +17 -0
  21. agent_codinglanguage_mapper/adapters/delphi_frontend/workspace.py +67 -0
  22. agent_codinglanguage_mapper/adapters/tree_sitter.py +1722 -0
  23. agent_codinglanguage_mapper/cli.py +138 -0
  24. agent_codinglanguage_mapper/discovery.py +1328 -0
  25. agent_codinglanguage_mapper/indexer.py +1102 -0
  26. agent_codinglanguage_mapper/integration.py +186 -0
  27. agent_codinglanguage_mapper/lsp_server.py +413 -0
  28. agent_codinglanguage_mapper/lsp_service.py +447 -0
  29. agent_codinglanguage_mapper/mapper.py +596 -0
  30. agent_codinglanguage_mapper/models.py +101 -0
  31. agent_codinglanguage_mapper/protocol.py +152 -0
  32. agent_codinglanguage_mapper/rendering.py +153 -0
  33. agent_codinglanguage_mapper/templates/opencode/plugins/codebase_map.ts +191 -0
  34. agent_codinglanguage_mapper/templates/skill/SKILL.md +52 -0
  35. agent_codinglanguage_mapper/worker.py +29 -0
  36. agent_codinglanguage_mapper/workspace.py +114 -0
  37. agent_codinglanguage_mapper-1.0.0.dist-info/METADATA +383 -0
  38. agent_codinglanguage_mapper-1.0.0.dist-info/RECORD +42 -0
  39. agent_codinglanguage_mapper-1.0.0.dist-info/WHEEL +5 -0
  40. agent_codinglanguage_mapper-1.0.0.dist-info/entry_points.txt +2 -0
  41. agent_codinglanguage_mapper-1.0.0.dist-info/licenses/LICENSE +373 -0
  42. agent_codinglanguage_mapper-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,186 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+ from tempfile import NamedTemporaryFile
7
+ from typing import Any, Final
8
+
9
+ AGENT_NAME: Final[str] = "mixed-codebase-navigator"
10
+ WORKTREE_PERMISSIONS: Final[dict[str, str]] = {
11
+ "*": "deny",
12
+ "skill": "allow",
13
+ "codebase_map": "allow",
14
+ }
15
+
16
+ SKILL_RELATIVE_PATH: Final[Path] = Path(".agents/skills/mixed-codebase-navigator/SKILL.md")
17
+ OPENCODE_PLUGIN_RELATIVE_PATH: Final[Path] = Path(".opencode/plugins/codebase_map.ts")
18
+ OPENCODE_CONFIG_RELATIVE_PATH: Final[Path] = Path("opencode.json")
19
+
20
+ SKILL_TEMPLATE_PATH: Final[Path] = Path("skill", "SKILL.md")
21
+ OPENCODE_PLUGIN_TEMPLATE_PATH: Final[Path] = Path("opencode", "plugins", "codebase_map.ts")
22
+ _TEMPLATES_ROOT: Final[Path] = Path(__file__).resolve().parent / "templates"
23
+
24
+
25
+ def install_skill(target: Path, force: bool = False) -> Path:
26
+ return _install_file(_normalize_target(target), SKILL_RELATIVE_PATH, SKILL_TEMPLATE_PATH, force=force)
27
+
28
+
29
+ def install_opencode(
30
+ target: Path,
31
+ python_executable: str | os.PathLike[str],
32
+ force: bool = False,
33
+ write_config: bool = False,
34
+ ) -> tuple[Path, Path] | tuple[Path, Path, Path]:
35
+ target_root = _normalize_target(target)
36
+ skill_path = _safe_join(target_root, SKILL_RELATIVE_PATH)
37
+ plugin_path = _safe_join(target_root, OPENCODE_PLUGIN_RELATIVE_PATH)
38
+ _preflight_overwrite((skill_path, plugin_path), force=force)
39
+ rendered_skill = _load_template(SKILL_TEMPLATE_PATH)
40
+ rendered_plugin = _render_plugin(str(python_executable))
41
+ config_path: Path | None = None
42
+ config: dict[str, Any] | None = None
43
+ changed = False
44
+ if write_config:
45
+ config_path = _safe_join(target_root, OPENCODE_CONFIG_RELATIVE_PATH)
46
+ config = _load_json_object(config_path)
47
+ changed = _merge_opencode_agent_config(config, force=force)
48
+
49
+ _write_atomic_text(skill_path, rendered_skill, force=True)
50
+ _write_atomic_text(plugin_path, rendered_plugin, force=True)
51
+ if config_path is not None and config is not None and changed:
52
+ _write_atomic_json(config_path, config)
53
+ if not write_config:
54
+ return skill_path, plugin_path
55
+ assert config_path is not None
56
+ return skill_path, plugin_path, config_path
57
+
58
+
59
+ def _normalize_target(target: Path) -> Path:
60
+ normalized = target.expanduser().resolve()
61
+ if normalized.exists() and not normalized.is_dir():
62
+ raise NotADirectoryError(f"Target must be a directory: {normalized}")
63
+ normalized.mkdir(parents=True, exist_ok=True)
64
+ return normalized
65
+
66
+
67
+ def _install_file(
68
+ target: Path,
69
+ destination: Path,
70
+ template_path: Path | None = None,
71
+ rendered_template: str | None = None,
72
+ force: bool = False,
73
+ ) -> Path:
74
+ if template_path is None and rendered_template is None:
75
+ raise ValueError("Either template_path or rendered_template must be provided.")
76
+ if template_path is not None and rendered_template is not None:
77
+ raise ValueError("Provide only one source template.")
78
+
79
+ if template_path is not None:
80
+ rendered_template = _load_template(template_path)
81
+ assert rendered_template is not None
82
+
83
+ destination_path = _safe_join(target, destination)
84
+ _write_atomic_text(destination_path, rendered_template, force=force)
85
+ return destination_path
86
+
87
+
88
+ def _safe_join(target: Path, relative: Path) -> Path:
89
+ destination = (target / relative).resolve()
90
+ target_resolved = target.resolve()
91
+ destination.relative_to(target_resolved)
92
+ destination.parent.mkdir(parents=True, exist_ok=True)
93
+ return destination
94
+
95
+
96
+ def _preflight_overwrite(paths: tuple[Path, ...], *, force: bool) -> None:
97
+ if force:
98
+ return
99
+ existing = [path for path in paths if path.exists()]
100
+ if existing:
101
+ raise FileExistsError(f"Refusing to overwrite existing file: {existing[0]}")
102
+
103
+
104
+ def _load_template(relative_path: Path) -> str:
105
+ template = _TEMPLATES_ROOT / relative_path
106
+ return template.read_text(encoding="utf-8")
107
+
108
+
109
+ def _render_plugin(python_executable: str) -> str:
110
+ template = _load_template(OPENCODE_PLUGIN_TEMPLATE_PATH)
111
+ escaped_python_executable = json.dumps(str(python_executable))
112
+ return template.replace("{{PYTHON_EXECUTABLE}}", escaped_python_executable)
113
+
114
+
115
+ def _write_atomic_text(path: Path, content: str, force: bool = False) -> None:
116
+ if path.exists() and not force:
117
+ raise FileExistsError(f"Refusing to overwrite existing file: {path}")
118
+
119
+ path.parent.mkdir(parents=True, exist_ok=True)
120
+ with NamedTemporaryFile("w", encoding="utf-8", newline="\n", dir=path.parent, delete=False) as handle:
121
+ handle.write(content)
122
+ tmp_path = Path(handle.name)
123
+ os.chmod(tmp_path, 0o644)
124
+ os.replace(tmp_path, path)
125
+
126
+
127
+ def _load_json_object(path: Path) -> dict[str, Any]:
128
+ if not path.exists():
129
+ return {}
130
+ with path.open("r", encoding="utf-8") as handle:
131
+ data: Any = json.load(handle)
132
+ if not isinstance(data, dict):
133
+ raise ValueError(f"Invalid opencode config object in {path}")
134
+ return data
135
+
136
+
137
+ def _merge_opencode_agent_config(config: dict[str, Any], *, force: bool) -> bool:
138
+ changed = False
139
+ if "$schema" not in config:
140
+ config["$schema"] = "https://opencode.ai/config.json"
141
+ changed = True
142
+ agents = config.get("agent")
143
+ if agents is None:
144
+ config["agent"] = {}
145
+ agents = config["agent"]
146
+ if not isinstance(agents, dict):
147
+ raise ValueError("Expected 'agent' to be an object.")
148
+
149
+ agent_config = agents.get(AGENT_NAME)
150
+ if agent_config is None:
151
+ agents[AGENT_NAME] = {
152
+ "description": "Navigate mixed-language codebases through the bounded semantic mapper.",
153
+ "permission": WORKTREE_PERMISSIONS.copy(),
154
+ }
155
+ return True
156
+ if not isinstance(agent_config, dict):
157
+ raise ValueError(f"Invalid agent block for {AGENT_NAME}")
158
+
159
+ permissions = agent_config.get("permission")
160
+ if permissions is None:
161
+ agent_config["permission"] = WORKTREE_PERMISSIONS.copy()
162
+ return True
163
+ if not isinstance(permissions, dict):
164
+ raise ValueError("permission must be an object")
165
+
166
+ for key, required_value in WORKTREE_PERMISSIONS.items():
167
+ current_value = permissions.get(key)
168
+ if current_value is not None and current_value != required_value:
169
+ if not force:
170
+ raise FileExistsError(
171
+ "Permission conflict in .opencode/opencode.json: "
172
+ f"key={key} expected={required_value!r} got={current_value!r}"
173
+ )
174
+ changed = True
175
+
176
+ normalized_permissions = WORKTREE_PERMISSIONS.copy()
177
+ if permissions != normalized_permissions:
178
+ permissions.clear()
179
+ permissions.update(normalized_permissions)
180
+ changed = True
181
+ return changed
182
+
183
+
184
+ def _write_atomic_json(path: Path, payload: dict[str, Any]) -> None:
185
+ serialized = json.dumps(payload, sort_keys=True, indent=2) + "\n"
186
+ _write_atomic_text(path, serialized, force=True)
@@ -0,0 +1,413 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ from typing import TYPE_CHECKING, Any, cast
6
+ import re
7
+ from urllib.parse import quote, unquote, urlparse
8
+
9
+ from ._version import __version__
10
+ from .lsp_service import LanguageService, SafeRenameError
11
+ from .models import Problem, SourceRange
12
+
13
+ if TYPE_CHECKING:
14
+ from pygls.server import LanguageServer
15
+ from lsprotocol.types import (
16
+ CompletionItem,
17
+ CompletionItemKind,
18
+ CompletionList,
19
+ CompletionParams,
20
+ DefinitionParams,
21
+ Diagnostic,
22
+ DocumentSymbol,
23
+ DocumentSymbolParams,
24
+ Hover,
25
+ HoverParams,
26
+ InitializeParams,
27
+ InitializeResult,
28
+ Location,
29
+ Range,
30
+ ReferenceParams,
31
+ RenameParams,
32
+ SymbolInformation,
33
+ SymbolKind,
34
+ WorkspaceSymbolParams,
35
+ )
36
+
37
+
38
+ @dataclass
39
+ class _ServerState:
40
+ service: LanguageService | None = None
41
+
42
+
43
+ def _feature(constants: object, name: str, fallback: str) -> str:
44
+ return str(getattr(constants, name, fallback))
45
+
46
+
47
+ def _uri_to_path(value: str) -> str:
48
+ parsed = urlparse(value)
49
+ if parsed.scheme == "file":
50
+ path = unquote(parsed.path)
51
+ if re.match(r"^/[A-Za-z]:/", path):
52
+ return path[1:]
53
+ return path
54
+
55
+ if parsed.scheme:
56
+ return unquote(value)
57
+
58
+ return unquote(value)
59
+
60
+
61
+ def _to_file_uri(path: str | Path) -> str:
62
+ text = str(path)
63
+ if re.match(r"^[A-Za-z]:[\\/]", text):
64
+ drive = text[0].upper()
65
+ rest = quote(text[2:].replace("\\", "/"), safe="/")
66
+ return f"file:///{drive}:{rest}"
67
+
68
+ parsed = urlparse(text)
69
+ if parsed.scheme:
70
+ return text
71
+
72
+ candidate = Path(text).expanduser()
73
+ if not candidate.is_absolute():
74
+ candidate = Path.cwd() / candidate
75
+ return candidate.resolve().as_uri()
76
+
77
+
78
+ def _source_range_to_lsp_range(source_range: SourceRange) -> "Range":
79
+ from lsprotocol.types import Position, Range
80
+
81
+ return Range(
82
+ start=Position(line=source_range.start_line, character=source_range.start_character),
83
+ end=Position(line=source_range.end_line, character=source_range.end_character),
84
+ )
85
+
86
+
87
+ def _problem_to_diagnostic(problem: Problem) -> "Diagnostic":
88
+ from lsprotocol.types import Diagnostic, DiagnosticSeverity
89
+
90
+ source_range = problem.range or SourceRange("", 0, 0, 0, 1)
91
+
92
+ severity_map = {
93
+ "error": DiagnosticSeverity.Error,
94
+ "warning": DiagnosticSeverity.Warning,
95
+ "hint": DiagnosticSeverity.Hint,
96
+ "info": DiagnosticSeverity.Information,
97
+ "information": DiagnosticSeverity.Information,
98
+ }
99
+ severity = severity_map.get(problem.severity.casefold(), DiagnosticSeverity.Warning)
100
+
101
+ return Diagnostic(
102
+ range=_source_range_to_lsp_range(source_range),
103
+ message=problem.message,
104
+ severity=severity,
105
+ code=problem.code,
106
+ )
107
+
108
+
109
+ def _symbol_kind(kind: str) -> "SymbolKind":
110
+ from lsprotocol.types import SymbolKind
111
+
112
+ mapping = {
113
+ "module": SymbolKind.Module,
114
+ "class": SymbolKind.Class,
115
+ "interface": SymbolKind.Interface,
116
+ "enum": SymbolKind.Enum,
117
+ "enum_value": SymbolKind.EnumMember,
118
+ "record": SymbolKind.Struct,
119
+ "method": SymbolKind.Method,
120
+ "function": SymbolKind.Function,
121
+ "procedure": SymbolKind.Function,
122
+ "constructor": SymbolKind.Constructor,
123
+ "destructor": SymbolKind.Method,
124
+ "variable": SymbolKind.Variable,
125
+ "parameter": SymbolKind.Variable,
126
+ "property": SymbolKind.Property,
127
+ "field": SymbolKind.Field,
128
+ "constant": SymbolKind.Constant,
129
+ }
130
+ return mapping.get(kind.lower(), SymbolKind.String)
131
+
132
+
133
+ def _completion_kind(kind: str) -> "CompletionItemKind":
134
+ from lsprotocol.types import CompletionItemKind
135
+
136
+ mapping = {
137
+ "class": CompletionItemKind.Class,
138
+ "interface": CompletionItemKind.Interface,
139
+ "enum": CompletionItemKind.Enum,
140
+ "enum_value": CompletionItemKind.EnumMember,
141
+ "record": CompletionItemKind.Struct,
142
+ "method": CompletionItemKind.Method,
143
+ "function": CompletionItemKind.Function,
144
+ "procedure": CompletionItemKind.Function,
145
+ "constructor": CompletionItemKind.Constructor,
146
+ "destructor": CompletionItemKind.Method,
147
+ "variable": CompletionItemKind.Variable,
148
+ "parameter": CompletionItemKind.Variable,
149
+ "property": CompletionItemKind.Property,
150
+ "field": CompletionItemKind.Field,
151
+ "constant": CompletionItemKind.Constant,
152
+ }
153
+ return mapping.get(kind.lower(), CompletionItemKind.Text)
154
+
155
+
156
+ def _state(server: "LanguageServer") -> _ServerState:
157
+ if not hasattr(server, "_acm_lsp_state"):
158
+ setattr(server, "_acm_lsp_state", _ServerState())
159
+ return cast(_ServerState, getattr(server, "_acm_lsp_state"))
160
+
161
+
162
+ def _resolve_workspace_root(params: object) -> Path:
163
+ workspace_folders = getattr(params, "workspace_folders", None)
164
+ if workspace_folders:
165
+ first = workspace_folders[0]
166
+ candidate = getattr(first, "uri", None)
167
+ if isinstance(candidate, str) and candidate:
168
+ return Path(_uri_to_path(candidate)).expanduser().resolve()
169
+
170
+ root_uri = getattr(params, "root_uri", None)
171
+ if isinstance(root_uri, str) and root_uri:
172
+ return Path(_uri_to_path(root_uri)).expanduser().resolve()
173
+
174
+ root_path = getattr(params, "root_path", None)
175
+ if isinstance(root_path, str) and root_path:
176
+ return Path(root_path).expanduser().resolve()
177
+
178
+ return Path.cwd()
179
+
180
+
181
+ def _workspace_service(server: "LanguageServer") -> LanguageService:
182
+ state = _state(server)
183
+ if state.service is None:
184
+ state.service = LanguageService(Path.cwd())
185
+ return state.service
186
+
187
+
188
+ def _publish_diagnostics_for_uri(server: "LanguageServer", service: LanguageService, uri: str) -> None:
189
+ path = _uri_to_path(uri)
190
+ diagnostics = [_problem_to_diagnostic(problem) for problem in service.diagnostics(path)]
191
+ server.publish_diagnostics(uri, diagnostics)
192
+
193
+
194
+ def create_server() -> "LanguageServer":
195
+ try:
196
+ from lsprotocol import types as lsp_types
197
+ from pygls.server import LanguageServer
198
+ except ImportError as exc: # pragma: no cover - optional dependency
199
+ raise RuntimeError("pygls and lsprotocol are required for the LSP server") from exc
200
+
201
+ server = LanguageServer("agent-codinglanguage-mapper", __version__)
202
+ state = _state(server)
203
+ # ensure deterministic test access to internal state
204
+ state.service = None
205
+
206
+ initialize = _feature(lsp_types, "INITIALIZE", "initialize")
207
+ did_open = _feature(lsp_types, "TEXT_DOCUMENT_DID_OPEN", "textDocument/didOpen")
208
+ did_change = _feature(lsp_types, "TEXT_DOCUMENT_DID_CHANGE", "textDocument/didChange")
209
+ did_close = _feature(lsp_types, "TEXT_DOCUMENT_DID_CLOSE", "textDocument/didClose")
210
+ definition = _feature(lsp_types, "TEXT_DOCUMENT_DEFINITION", "textDocument/definition")
211
+ hover = _feature(lsp_types, "TEXT_DOCUMENT_HOVER", "textDocument/hover")
212
+ references = _feature(lsp_types, "TEXT_DOCUMENT_REFERENCES", "textDocument/references")
213
+ rename = _feature(lsp_types, "TEXT_DOCUMENT_RENAME", "textDocument/rename")
214
+ document_symbol = _feature(lsp_types, "TEXT_DOCUMENT_DOCUMENT_SYMBOL", "textDocument/documentSymbol")
215
+ workspace_symbol = _feature(lsp_types, "WORKSPACE_SYMBOL", "workspace/symbol")
216
+ completion = _feature(lsp_types, "TEXT_DOCUMENT_COMPLETION", "textDocument/completion")
217
+
218
+ @server.feature(initialize)
219
+ def on_initialize(ls: "LanguageServer", params: "InitializeParams") -> "InitializeResult":
220
+ root = _resolve_workspace_root(params)
221
+ _state(ls).service = LanguageService(root)
222
+ return lsp_types.InitializeResult(
223
+ capabilities=lsp_types.ServerCapabilities(
224
+ text_document_sync=lsp_types.TextDocumentSyncOptions(
225
+ open_close=True,
226
+ change=lsp_types.TextDocumentSyncKind.Full,
227
+ ),
228
+ definition_provider=True,
229
+ hover_provider=True,
230
+ references_provider=True,
231
+ rename_provider=True,
232
+ document_symbol_provider=True,
233
+ workspace_symbol_provider=True,
234
+ completion_provider=lsp_types.CompletionOptions(),
235
+ )
236
+ )
237
+
238
+ @server.feature(did_open)
239
+ def on_did_open(ls: "LanguageServer", params: Any) -> None:
240
+ service = _workspace_service(ls)
241
+ uri = getattr(params.text_document, "uri")
242
+ text = getattr(params.text_document, "text")
243
+ service.update_document(_uri_to_path(uri), text)
244
+ _publish_diagnostics_for_uri(ls, service, uri)
245
+
246
+ @server.feature(did_change)
247
+ def on_did_change(ls: "LanguageServer", params: Any) -> None:
248
+ changes = getattr(params, "content_changes", ())
249
+ if not changes:
250
+ return
251
+ service = _workspace_service(ls)
252
+ text = changes[-1].text
253
+ uri = params.text_document.uri
254
+ service.update_document(_uri_to_path(uri), text)
255
+ _publish_diagnostics_for_uri(ls, service, uri)
256
+
257
+ @server.feature(did_close)
258
+ def on_did_close(ls: "LanguageServer", params: Any) -> None:
259
+ service = _workspace_service(ls)
260
+ uri = params.text_document.uri
261
+ service.close_document(_uri_to_path(uri))
262
+ ls.publish_diagnostics(uri, [])
263
+
264
+ @server.feature(definition)
265
+ def on_definition(ls: "LanguageServer", params: "DefinitionParams") -> "Location | None":
266
+ service = _workspace_service(ls)
267
+ path = _uri_to_path(params.text_document.uri)
268
+ symbol_range = service.definition(path, params.position.line, params.position.character)
269
+ if symbol_range is None:
270
+ return None
271
+
272
+ return lsp_types.Location(
273
+ uri=service.path_uri(symbol_range.path),
274
+ range=_source_range_to_lsp_range(symbol_range),
275
+ )
276
+
277
+ @server.feature(hover)
278
+ def on_hover(ls: "LanguageServer", params: "HoverParams") -> "Hover | None":
279
+ service = _workspace_service(ls)
280
+ path = _uri_to_path(params.text_document.uri)
281
+ contents = service.hover(path, params.position.line, params.position.character)
282
+ if contents is None:
283
+ return None
284
+
285
+ return lsp_types.Hover(
286
+ contents=lsp_types.MarkupContent(kind=lsp_types.MarkupKind.Markdown, value=contents)
287
+ )
288
+
289
+ @server.feature(references)
290
+ def on_references(ls: "LanguageServer", params: "ReferenceParams") -> list["Location"]:
291
+ service = _workspace_service(ls)
292
+ path = _uri_to_path(params.text_document.uri)
293
+
294
+ context = getattr(params, "context", None)
295
+ include_declaration = True
296
+ if context is not None and getattr(context, "include_declaration", None) is not None:
297
+ include_declaration = bool(context.include_declaration)
298
+
299
+ return [
300
+ lsp_types.Location(
301
+ uri=service.path_uri(item.path),
302
+ range=_source_range_to_lsp_range(item),
303
+ )
304
+ for item in service.references(path, params.position.line, params.position.character, include_declaration=include_declaration)
305
+ ]
306
+
307
+ @server.feature(rename)
308
+ def on_rename(ls: "LanguageServer", params: "RenameParams") -> Any:
309
+ service = _workspace_service(ls)
310
+ path = _uri_to_path(params.text_document.uri)
311
+ try:
312
+ edits = service.rename(path, params.position.line, params.position.character, params.new_name)
313
+ except SafeRenameError:
314
+ return None
315
+
316
+ if not edits:
317
+ return None
318
+
319
+ return lsp_types.WorkspaceEdit(
320
+ changes={
321
+ service.path_uri(file_name): [
322
+ lsp_types.TextEdit(
323
+ range=_source_range_to_lsp_range(edit.range),
324
+ new_text=edit.new_text,
325
+ )
326
+ for edit in file_edits
327
+ ]
328
+ for file_name, file_edits in edits.items()
329
+ }
330
+ )
331
+
332
+ @server.feature(document_symbol)
333
+ def on_document_symbols(
334
+ ls: "LanguageServer",
335
+ params: "DocumentSymbolParams",
336
+ ) -> list["DocumentSymbol"]:
337
+ service = _workspace_service(ls)
338
+ path = _uri_to_path(params.text_document.uri)
339
+ symbols = service.document_symbols(path)
340
+
341
+ return [
342
+ lsp_types.DocumentSymbol(
343
+ name=item.name,
344
+ detail=item.signature or None,
345
+ kind=_symbol_kind(item.kind),
346
+ range=_source_range_to_lsp_range(item.range),
347
+ selection_range=_source_range_to_lsp_range(item.name_range or item.range),
348
+ )
349
+ for item in symbols
350
+ ]
351
+
352
+ @server.feature(workspace_symbol)
353
+ def on_workspace_symbols(
354
+ ls: "LanguageServer",
355
+ params: "WorkspaceSymbolParams",
356
+ ) -> list["SymbolInformation"]:
357
+ service = _workspace_service(ls)
358
+ symbols = service.workspace_symbols(getattr(params, "query", "") or "")
359
+ return [
360
+ lsp_types.SymbolInformation(
361
+ name=item.name,
362
+ kind=_symbol_kind(item.kind),
363
+ location=lsp_types.Location(
364
+ uri=service.path_uri(item.range.path),
365
+ range=_source_range_to_lsp_range(item.range),
366
+ ),
367
+ container_name=item.qualified_name.rpartition(".")[0] or None,
368
+ )
369
+ for item in symbols
370
+ ]
371
+
372
+ @server.feature(completion)
373
+ def on_completion(
374
+ ls: "LanguageServer",
375
+ params: "CompletionParams",
376
+ ) -> "CompletionList | list[CompletionItem]":
377
+ service = _workspace_service(ls)
378
+ path = _uri_to_path(params.text_document.uri)
379
+ matches = service.completions(path)
380
+ items = [
381
+ lsp_types.CompletionItem(
382
+ label=item.name,
383
+ kind=_completion_kind(item.kind),
384
+ detail=item.signature,
385
+ documentation=item.qualified_name,
386
+ )
387
+ for item in matches
388
+ ]
389
+ return lsp_types.CompletionList(is_incomplete=False, items=items)
390
+
391
+ # Expose handlers for focused non-stdio unit tests.
392
+ setattr(server, "_handle_initialize", on_initialize)
393
+ setattr(server, "_handle_did_open", on_did_open)
394
+ setattr(server, "_handle_did_change", on_did_change)
395
+ setattr(server, "_handle_did_close", on_did_close)
396
+ setattr(server, "_handle_definition", on_definition)
397
+ setattr(server, "_handle_hover", on_hover)
398
+ setattr(server, "_handle_references", on_references)
399
+ setattr(server, "_handle_rename", on_rename)
400
+ setattr(server, "_handle_document_symbols", on_document_symbols)
401
+ setattr(server, "_handle_workspace_symbols", on_workspace_symbols)
402
+ setattr(server, "_handle_completion", on_completion)
403
+ setattr(server, "_acm_state", state)
404
+ setattr(server, "_acm_lsp_state", state)
405
+
406
+ return server
407
+
408
+
409
+ def main() -> None:
410
+ create_server().start_io()
411
+
412
+
413
+ __all__ = ["create_server", "main", "_to_file_uri", "_uri_to_path", "_source_range_to_lsp_range"]