modelable 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.
Potentially problematic release.
This version of modelable might be problematic. Click here for more details.
- modelable/__init__.py +1 -0
- modelable/__main__.py +3 -0
- modelable/_pydantic_py314_compat.py +31 -0
- modelable/cli.py +41 -0
- modelable/commands/__init__.py +1 -0
- modelable/commands/apicurio.py +84 -0
- modelable/commands/codegen.py +241 -0
- modelable/commands/common.py +43 -0
- modelable/commands/compile.py +237 -0
- modelable/commands/create.py +164 -0
- modelable/commands/diff.py +82 -0
- modelable/commands/graph.py +53 -0
- modelable/commands/llm.py +564 -0
- modelable/commands/lsp.py +15 -0
- modelable/commands/runtime.py +37 -0
- modelable/commands/scenario.py +104 -0
- modelable/commands/spec.py +197 -0
- modelable/commands/workspace.py +240 -0
- modelable/compat/__init__.py +11 -0
- modelable/compat/checker.py +179 -0
- modelable/compat/diff.py +169 -0
- modelable/compiler/__init__.py +3 -0
- modelable/compiler/compiler.py +19 -0
- modelable/compiler/workspace.py +346 -0
- modelable/diagnostics/__init__.py +3 -0
- modelable/diagnostics/model.py +27 -0
- modelable/emitters/__init__.py +0 -0
- modelable/emitters/base.py +22 -0
- modelable/emitters/csharp.py +245 -0
- modelable/emitters/dbt_yaml.py +290 -0
- modelable/emitters/diagnostics.py +25 -0
- modelable/emitters/fhir.py +694 -0
- modelable/emitters/fhir_validator.py +36 -0
- modelable/emitters/go.py +334 -0
- modelable/emitters/java.py +264 -0
- modelable/emitters/json_schema.py +458 -0
- modelable/emitters/markdown.py +252 -0
- modelable/emitters/odcs.py +355 -0
- modelable/emitters/openlineage.py +315 -0
- modelable/emitters/openmetadata.py +258 -0
- modelable/emitters/python.py +282 -0
- modelable/emitters/rust.py +643 -0
- modelable/emitters/shapes.py +261 -0
- modelable/emitters/sql.py +266 -0
- modelable/emitters/targets.py +141 -0
- modelable/emitters/typescript.py +352 -0
- modelable/expressions/__init__.py +0 -0
- modelable/expressions/cel.py +547 -0
- modelable/governance/__init__.py +3 -0
- modelable/governance/checker.py +271 -0
- modelable/governance/por.py +46 -0
- modelable/grammar/__init__.py +1 -0
- modelable/grammar/modelable.lark +257 -0
- modelable/graph/__init__.py +5 -0
- modelable/graph/export.py +442 -0
- modelable/llm/__init__.py +43 -0
- modelable/llm/chat.py +255 -0
- modelable/llm/config.py +87 -0
- modelable/llm/context.py +194 -0
- modelable/llm/engine.py +976 -0
- modelable/llm/importers.py +1077 -0
- modelable/llm/provenance.py +84 -0
- modelable/llm/providers.py +182 -0
- modelable/llm/qa.py +126 -0
- modelable/llm/recommendations.py +33 -0
- modelable/llm/redaction.py +19 -0
- modelable/llm/render.py +279 -0
- modelable/llm/update_plan.py +101 -0
- modelable/llm/validation_help.py +10 -0
- modelable/lsp/__init__.py +3 -0
- modelable/lsp/__main__.py +4 -0
- modelable/lsp/code_actions.py +210 -0
- modelable/lsp/completion.py +480 -0
- modelable/lsp/definition.py +343 -0
- modelable/lsp/diagnostics.py +31 -0
- modelable/lsp/document_symbols.py +197 -0
- modelable/lsp/federation.py +261 -0
- modelable/lsp/folding.py +33 -0
- modelable/lsp/formatting.py +64 -0
- modelable/lsp/highlight.py +30 -0
- modelable/lsp/hover.py +370 -0
- modelable/lsp/inlay_hints.py +158 -0
- modelable/lsp/references.py +511 -0
- modelable/lsp/rename.py +564 -0
- modelable/lsp/semantic_tokens.py +412 -0
- modelable/lsp/server.py +370 -0
- modelable/lsp/workspace.py +83 -0
- modelable/lsp/workspace_symbols.py +104 -0
- modelable/parser/__init__.py +94 -0
- modelable/parser/ir.py +451 -0
- modelable/parser/parse.py +47 -0
- modelable/parser/transformer.py +798 -0
- modelable/parser/wire.py +68 -0
- modelable/planner/__init__.py +0 -0
- modelable/planner/lineage.py +91 -0
- modelable/planner/planner.py +134 -0
- modelable/planner/plans.py +122 -0
- modelable/py.typed +0 -0
- modelable/registry/__init__.py +9 -0
- modelable/registry/apicurio.py +166 -0
- modelable/registry/base.py +18 -0
- modelable/registry/factory.py +18 -0
- modelable/registry/index.py +419 -0
- modelable/registry/local.py +26 -0
- modelable/registry/oci.py +22 -0
- modelable/registry/resolver.py +213 -0
- modelable/registry/schema.sql +119 -0
- modelable/registry/signature.py +26 -0
- modelable/release.py +125 -0
- modelable/runtime/__init__.py +5 -0
- modelable/runtime/adapter/__init__.py +17 -0
- modelable/runtime/adapter/base.py +18 -0
- modelable/runtime/adapter/postgres.py +82 -0
- modelable/specs/__init__.py +23 -0
- modelable/specs/tracking.py +220 -0
- modelable/validation/__init__.py +3 -0
- modelable/validation/semantic.py +659 -0
- modelable-1.0.0.dist-info/METADATA +61 -0
- modelable-1.0.0.dist-info/RECORD +122 -0
- modelable-1.0.0.dist-info/WHEEL +4 -0
- modelable-1.0.0.dist-info/entry_points.txt +2 -0
- modelable-1.0.0.dist-info/licenses/LICENSE +201 -0
modelable/lsp/server.py
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import contextlib
|
|
5
|
+
import inspect
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from lsprotocol import types
|
|
9
|
+
from pygls import feature_manager
|
|
10
|
+
from pygls.lsp.server import LanguageServer
|
|
11
|
+
from pygls.protocol import json_rpc
|
|
12
|
+
|
|
13
|
+
from modelable.lsp.code_actions import build_code_actions
|
|
14
|
+
from modelable.lsp.completion import build_completion
|
|
15
|
+
from modelable.lsp.definition import build_definition
|
|
16
|
+
from modelable.lsp.diagnostics import to_lsp_diagnostics
|
|
17
|
+
from modelable.lsp.document_symbols import build_document_symbols
|
|
18
|
+
from modelable.lsp.federation import build_import_diagnostics
|
|
19
|
+
from modelable.lsp.folding import build_folding_ranges
|
|
20
|
+
from modelable.lsp.formatting import build_document_formatting
|
|
21
|
+
from modelable.lsp.highlight import build_document_highlight
|
|
22
|
+
from modelable.lsp.hover import build_hover
|
|
23
|
+
from modelable.lsp.inlay_hints import build_inlay_hints
|
|
24
|
+
from modelable.lsp.references import build_references
|
|
25
|
+
from modelable.lsp.rename import build_prepare_rename, build_rename
|
|
26
|
+
from modelable.lsp.semantic_tokens import build_semantic_tokens, semantic_tokens_legend
|
|
27
|
+
from modelable.lsp.workspace import LspWorkspaceIndex, uri_to_path
|
|
28
|
+
from modelable.lsp.workspace_symbols import build_workspace_symbols
|
|
29
|
+
|
|
30
|
+
feature_manager.asyncio.iscoroutinefunction = inspect.iscoroutinefunction
|
|
31
|
+
json_rpc.asyncio.iscoroutinefunction = inspect.iscoroutinefunction
|
|
32
|
+
|
|
33
|
+
_DEBOUNCE_DELAY = 0.2
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ModelableLanguageServer(LanguageServer):
|
|
37
|
+
def __init__(self) -> None:
|
|
38
|
+
super().__init__("modelable-lsp", "0.1.0")
|
|
39
|
+
self.index = LspWorkspaceIndex()
|
|
40
|
+
self._indexes: dict[Path, LspWorkspaceIndex] = {}
|
|
41
|
+
self._debounce_tasks: dict[str, asyncio.Task] = {}
|
|
42
|
+
self._root_uri: str | None = None
|
|
43
|
+
self._scanned_dirs: set[Path] = set()
|
|
44
|
+
|
|
45
|
+
def index_for(self, uri: str) -> LspWorkspaceIndex:
|
|
46
|
+
path = uri_to_path(uri)
|
|
47
|
+
if path is not None:
|
|
48
|
+
# Keep index routing stable even when no workspace.mdl exists:
|
|
49
|
+
# use the file's parent directory as the effective root.
|
|
50
|
+
root = _find_workspace_root(path) or path.parent
|
|
51
|
+
if root not in self._indexes:
|
|
52
|
+
self._indexes[root] = LspWorkspaceIndex()
|
|
53
|
+
return self._indexes[root]
|
|
54
|
+
return self.index
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
server = ModelableLanguageServer()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@server.feature(types.INITIALIZE)
|
|
61
|
+
def initialize(ls: ModelableLanguageServer, params: types.InitializeParams) -> types.InitializeResult:
|
|
62
|
+
ls._root_uri = params.root_uri
|
|
63
|
+
return _build_initialize_result()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _build_initialize_result() -> types.InitializeResult:
|
|
67
|
+
return types.InitializeResult(
|
|
68
|
+
capabilities=types.ServerCapabilities(
|
|
69
|
+
text_document_sync=types.TextDocumentSyncOptions(
|
|
70
|
+
open_close=True,
|
|
71
|
+
change=types.TextDocumentSyncKind.Incremental,
|
|
72
|
+
save=types.SaveOptions(include_text=True),
|
|
73
|
+
),
|
|
74
|
+
hover_provider=True,
|
|
75
|
+
definition_provider=True,
|
|
76
|
+
references_provider=True,
|
|
77
|
+
document_symbol_provider=True,
|
|
78
|
+
workspace_symbol_provider=types.WorkspaceSymbolOptions(resolve_provider=False),
|
|
79
|
+
document_formatting_provider=True,
|
|
80
|
+
code_action_provider=types.CodeActionOptions(
|
|
81
|
+
code_action_kinds=[types.CodeActionKind.QuickFix],
|
|
82
|
+
resolve_provider=False,
|
|
83
|
+
),
|
|
84
|
+
rename_provider=types.RenameOptions(prepare_provider=True),
|
|
85
|
+
semantic_tokens_provider=types.SemanticTokensOptions(
|
|
86
|
+
legend=semantic_tokens_legend(),
|
|
87
|
+
full=True,
|
|
88
|
+
range=False,
|
|
89
|
+
),
|
|
90
|
+
document_highlight_provider=True,
|
|
91
|
+
folding_range_provider=True,
|
|
92
|
+
inlay_hint_provider=types.InlayHintOptions(resolve_provider=False),
|
|
93
|
+
completion_provider=types.CompletionOptions(
|
|
94
|
+
trigger_characters=["@", "."],
|
|
95
|
+
),
|
|
96
|
+
workspace=types.WorkspaceOptions(
|
|
97
|
+
workspace_folders=types.WorkspaceFoldersServerCapabilities(
|
|
98
|
+
supported=True,
|
|
99
|
+
change_notifications=True,
|
|
100
|
+
)
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@server.feature(types.INITIALIZED)
|
|
107
|
+
def initialized(ls: ModelableLanguageServer, _params: types.InitializedParams) -> None:
|
|
108
|
+
scan_paths: list[Path] = []
|
|
109
|
+
for folder in (ls.workspace.folders or {}).values():
|
|
110
|
+
folder_path = uri_to_path(folder.uri)
|
|
111
|
+
if folder_path is not None and (folder_path / "workspace.mdl").exists():
|
|
112
|
+
scan_paths.append(folder_path)
|
|
113
|
+
if not scan_paths and ls._root_uri:
|
|
114
|
+
root_path = uri_to_path(ls._root_uri)
|
|
115
|
+
if root_path is not None and (root_path / "workspace.mdl").exists():
|
|
116
|
+
scan_paths.append(root_path)
|
|
117
|
+
loaded_uris: list[str] = []
|
|
118
|
+
for path in scan_paths:
|
|
119
|
+
index = _get_index_for_root(ls, path)
|
|
120
|
+
loaded_uris.extend(_scan_and_load_path(ls, path, index))
|
|
121
|
+
for uri in loaded_uris:
|
|
122
|
+
_publish_document_diagnostics(ls, uri, ls.index_for(uri))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@server.feature(types.WORKSPACE_DID_CHANGE_WATCHED_FILES)
|
|
126
|
+
def did_change_watched_files(ls: ModelableLanguageServer, params: types.DidChangeWatchedFilesParams) -> None:
|
|
127
|
+
for change in params.changes:
|
|
128
|
+
uri = change.uri
|
|
129
|
+
index = ls.index_for(uri)
|
|
130
|
+
if change.type == types.FileChangeType.Deleted:
|
|
131
|
+
index.remove_document(uri)
|
|
132
|
+
else:
|
|
133
|
+
path = uri_to_path(uri)
|
|
134
|
+
if path is not None and path.exists():
|
|
135
|
+
with contextlib.suppress(Exception):
|
|
136
|
+
index.load_background_document(uri, path.read_text(encoding="utf-8"))
|
|
137
|
+
_publish_document_diagnostics(ls, uri, index)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@server.feature(types.TEXT_DOCUMENT_DID_OPEN)
|
|
141
|
+
def did_open(ls: ModelableLanguageServer, params: types.DidOpenTextDocumentParams) -> None:
|
|
142
|
+
uri = params.text_document.uri
|
|
143
|
+
path = uri_to_path(uri)
|
|
144
|
+
if path is not None:
|
|
145
|
+
scan_root = _find_workspace_root(path) or path.parent
|
|
146
|
+
if scan_root not in ls._scanned_dirs:
|
|
147
|
+
index = _get_index_for_root(ls, scan_root)
|
|
148
|
+
_scan_and_load_path(ls, scan_root, index)
|
|
149
|
+
index = ls.index_for(uri)
|
|
150
|
+
index.upsert_document(uri, params.text_document.text)
|
|
151
|
+
_publish_document_diagnostics(ls, uri, index)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@server.feature(types.TEXT_DOCUMENT_DID_CHANGE)
|
|
155
|
+
async def did_change(ls: ModelableLanguageServer, params: types.DidChangeTextDocumentParams) -> None:
|
|
156
|
+
document = ls.workspace.get_text_document(params.text_document.uri)
|
|
157
|
+
uri = document.uri
|
|
158
|
+
ls.index_for(uri).upsert_document(uri, document.source)
|
|
159
|
+
|
|
160
|
+
existing = ls._debounce_tasks.get(uri)
|
|
161
|
+
if existing is not None and not existing.done():
|
|
162
|
+
existing.cancel()
|
|
163
|
+
|
|
164
|
+
async def _delayed_publish() -> None:
|
|
165
|
+
await asyncio.sleep(_DEBOUNCE_DELAY)
|
|
166
|
+
_publish_document_diagnostics(ls, uri, ls.index_for(uri))
|
|
167
|
+
|
|
168
|
+
ls._debounce_tasks[uri] = asyncio.ensure_future(_delayed_publish())
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@server.feature(types.TEXT_DOCUMENT_DID_CLOSE)
|
|
172
|
+
def did_close(ls: ModelableLanguageServer, params: types.DidCloseTextDocumentParams) -> None:
|
|
173
|
+
uri = params.text_document.uri
|
|
174
|
+
existing = ls._debounce_tasks.pop(uri, None)
|
|
175
|
+
if existing is not None and not existing.done():
|
|
176
|
+
existing.cancel()
|
|
177
|
+
ls.index_for(uri).close_document(uri)
|
|
178
|
+
ls.text_document_publish_diagnostics(types.PublishDiagnosticsParams(uri=uri, diagnostics=[]))
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@server.feature(types.TEXT_DOCUMENT_HOVER)
|
|
182
|
+
def hover(ls: ModelableLanguageServer, params: types.HoverParams) -> types.Hover | None:
|
|
183
|
+
return build_hover(
|
|
184
|
+
ls.index_for(params.text_document.uri),
|
|
185
|
+
params.text_document.uri,
|
|
186
|
+
params.position.line,
|
|
187
|
+
params.position.character,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@server.feature(types.TEXT_DOCUMENT_DEFINITION)
|
|
192
|
+
def definition(
|
|
193
|
+
ls: ModelableLanguageServer, params: types.DefinitionParams
|
|
194
|
+
) -> types.Location | list[types.Location] | None:
|
|
195
|
+
return build_definition(
|
|
196
|
+
ls.index_for(params.text_document.uri),
|
|
197
|
+
params.text_document.uri,
|
|
198
|
+
params.position.line,
|
|
199
|
+
params.position.character,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@server.feature(types.TEXT_DOCUMENT_REFERENCES)
|
|
204
|
+
def references(ls: ModelableLanguageServer, params: types.ReferenceParams) -> list[types.Location] | None:
|
|
205
|
+
return build_references(
|
|
206
|
+
ls.index_for(params.text_document.uri),
|
|
207
|
+
params.text_document.uri,
|
|
208
|
+
params.position.line,
|
|
209
|
+
params.position.character,
|
|
210
|
+
params.context.include_declaration,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@server.feature(types.TEXT_DOCUMENT_COMPLETION)
|
|
215
|
+
def completion(ls: ModelableLanguageServer, params: types.CompletionParams) -> types.CompletionList:
|
|
216
|
+
return build_completion(
|
|
217
|
+
ls.index_for(params.text_document.uri),
|
|
218
|
+
params.text_document.uri,
|
|
219
|
+
params.position.line,
|
|
220
|
+
params.position.character,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@server.feature(types.TEXT_DOCUMENT_DOCUMENT_SYMBOL)
|
|
225
|
+
def document_symbol(
|
|
226
|
+
ls: ModelableLanguageServer, params: types.DocumentSymbolParams
|
|
227
|
+
) -> list[types.DocumentSymbol] | None:
|
|
228
|
+
return build_document_symbols(ls.index_for(params.text_document.uri), params.text_document.uri)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@server.feature(types.WORKSPACE_SYMBOL)
|
|
232
|
+
def workspace_symbol(
|
|
233
|
+
ls: ModelableLanguageServer, params: types.WorkspaceSymbolParams
|
|
234
|
+
) -> list[types.WorkspaceSymbol] | None:
|
|
235
|
+
results: list[types.WorkspaceSymbol] = []
|
|
236
|
+
seen_roots: set[int] = set()
|
|
237
|
+
for index in [ls.index, *ls._indexes.values()]:
|
|
238
|
+
if id(index) not in seen_roots:
|
|
239
|
+
seen_roots.add(id(index))
|
|
240
|
+
found = build_workspace_symbols(index, params.query)
|
|
241
|
+
if found:
|
|
242
|
+
results.extend(found)
|
|
243
|
+
return results or None
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@server.feature(types.TEXT_DOCUMENT_FORMATTING)
|
|
247
|
+
def document_formatting(
|
|
248
|
+
ls: ModelableLanguageServer, params: types.DocumentFormattingParams
|
|
249
|
+
) -> list[types.TextEdit] | None:
|
|
250
|
+
return build_document_formatting(
|
|
251
|
+
ls.index_for(params.text_document.uri),
|
|
252
|
+
params.text_document.uri,
|
|
253
|
+
params.options.tab_size,
|
|
254
|
+
params.options.insert_spaces,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@server.feature(types.TEXT_DOCUMENT_CODE_ACTION)
|
|
259
|
+
def code_action(ls: ModelableLanguageServer, params: types.CodeActionParams) -> list[types.CodeAction] | None:
|
|
260
|
+
return build_code_actions(
|
|
261
|
+
ls.index_for(params.text_document.uri),
|
|
262
|
+
params.text_document.uri,
|
|
263
|
+
params.range.start.line,
|
|
264
|
+
params.range.start.character,
|
|
265
|
+
list(params.context.diagnostics),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@server.feature(types.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL)
|
|
270
|
+
def semantic_tokens_full(
|
|
271
|
+
ls: ModelableLanguageServer, params: types.SemanticTokensParams
|
|
272
|
+
) -> types.SemanticTokens | None:
|
|
273
|
+
source = ls.index_for(params.text_document.uri).documents.get(params.text_document.uri)
|
|
274
|
+
if source is None:
|
|
275
|
+
return types.SemanticTokens(data=[])
|
|
276
|
+
return build_semantic_tokens(source.text)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@server.feature(types.TEXT_DOCUMENT_PREPARE_RENAME)
|
|
280
|
+
def prepare_rename(ls: ModelableLanguageServer, params: types.PrepareRenameParams) -> types.Range | None:
|
|
281
|
+
return build_prepare_rename(
|
|
282
|
+
ls.index_for(params.text_document.uri),
|
|
283
|
+
params.text_document.uri,
|
|
284
|
+
params.position.line,
|
|
285
|
+
params.position.character,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@server.feature(types.TEXT_DOCUMENT_RENAME)
|
|
290
|
+
def rename(ls: ModelableLanguageServer, params: types.RenameParams) -> types.WorkspaceEdit | None:
|
|
291
|
+
return build_rename(
|
|
292
|
+
ls.index_for(params.text_document.uri),
|
|
293
|
+
params.text_document.uri,
|
|
294
|
+
params.position.line,
|
|
295
|
+
params.position.character,
|
|
296
|
+
params.new_name,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@server.feature(types.TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT)
|
|
301
|
+
def document_highlight(
|
|
302
|
+
ls: ModelableLanguageServer, params: types.DocumentHighlightParams
|
|
303
|
+
) -> list[types.DocumentHighlight] | None:
|
|
304
|
+
return build_document_highlight(
|
|
305
|
+
ls.index_for(params.text_document.uri),
|
|
306
|
+
params.text_document.uri,
|
|
307
|
+
params.position.line,
|
|
308
|
+
params.position.character,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@server.feature(types.TEXT_DOCUMENT_FOLDING_RANGE)
|
|
313
|
+
def folding_range(ls: ModelableLanguageServer, params: types.FoldingRangeParams) -> list[types.FoldingRange] | None:
|
|
314
|
+
return build_folding_ranges(ls.index_for(params.text_document.uri), params.text_document.uri)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@server.feature(types.TEXT_DOCUMENT_INLAY_HINT)
|
|
318
|
+
def inlay_hint(ls: ModelableLanguageServer, params: types.InlayHintParams) -> list[types.InlayHint] | None:
|
|
319
|
+
return build_inlay_hints(ls.index_for(params.text_document.uri), params.text_document.uri, params.range)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _find_workspace_root(file_path: Path) -> Path | None:
|
|
323
|
+
"""Walk up the ancestor chain to find the nearest directory containing workspace.mdl."""
|
|
324
|
+
directory = file_path.parent
|
|
325
|
+
while True:
|
|
326
|
+
if (directory / "workspace.mdl").exists():
|
|
327
|
+
return directory
|
|
328
|
+
parent = directory.parent
|
|
329
|
+
if parent == directory:
|
|
330
|
+
return None
|
|
331
|
+
directory = parent
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _get_index_for_root(ls: ModelableLanguageServer, root: Path) -> LspWorkspaceIndex:
|
|
335
|
+
if root not in ls._indexes:
|
|
336
|
+
ls._indexes[root] = LspWorkspaceIndex()
|
|
337
|
+
return ls._indexes[root]
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _scan_and_load_path(ls: ModelableLanguageServer, folder_path: Path, index: LspWorkspaceIndex) -> list[str]:
|
|
341
|
+
ls._scanned_dirs.add(folder_path)
|
|
342
|
+
loaded: list[str] = []
|
|
343
|
+
for mdl_file in sorted(folder_path.rglob("*.mdl")):
|
|
344
|
+
file_uri = mdl_file.as_uri()
|
|
345
|
+
ls._scanned_dirs.add(mdl_file.parent)
|
|
346
|
+
try:
|
|
347
|
+
index.load_background_document(file_uri, mdl_file.read_text(encoding="utf-8"))
|
|
348
|
+
loaded.append(file_uri)
|
|
349
|
+
except Exception:
|
|
350
|
+
pass
|
|
351
|
+
return loaded
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _publish_document_diagnostics(ls: ModelableLanguageServer, uri: str, index: LspWorkspaceIndex) -> None:
|
|
355
|
+
workspace = index.workspace
|
|
356
|
+
if workspace is None:
|
|
357
|
+
ls.text_document_publish_diagnostics(types.PublishDiagnosticsParams(uri=uri, diagnostics=[]))
|
|
358
|
+
return
|
|
359
|
+
diagnostics = []
|
|
360
|
+
for diagnostic in workspace.errors:
|
|
361
|
+
if diagnostic.path == uri or diagnostic.path == "<workspace>":
|
|
362
|
+
diagnostics.append(diagnostic)
|
|
363
|
+
diagnostics.extend(build_import_diagnostics(index, uri))
|
|
364
|
+
ls.text_document_publish_diagnostics(
|
|
365
|
+
types.PublishDiagnosticsParams(uri=uri, diagnostics=to_lsp_diagnostics(diagnostics))
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def main() -> None:
|
|
370
|
+
server.start_io()
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from urllib.parse import unquote, urlparse
|
|
6
|
+
|
|
7
|
+
from modelable.compiler.workspace import (
|
|
8
|
+
Workspace,
|
|
9
|
+
WorkspaceDocumentSource,
|
|
10
|
+
load_workspace_from_sources,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class LspWorkspaceIndex:
|
|
16
|
+
documents: dict[str, WorkspaceDocumentSource] = field(default_factory=dict)
|
|
17
|
+
workspace: Workspace | None = None
|
|
18
|
+
_user_opened: set[str] = field(default_factory=set, init=False, repr=False)
|
|
19
|
+
|
|
20
|
+
def upsert_document(self, uri: str, text: str) -> Workspace | None:
|
|
21
|
+
self._user_opened.add(uri)
|
|
22
|
+
source = WorkspaceDocumentSource(
|
|
23
|
+
path=uri_to_path(uri),
|
|
24
|
+
uri=uri,
|
|
25
|
+
text=text,
|
|
26
|
+
)
|
|
27
|
+
current = self.documents.get(uri)
|
|
28
|
+
if current is not None and current.text == text:
|
|
29
|
+
return self.workspace
|
|
30
|
+
self.documents[uri] = source
|
|
31
|
+
return self.rebuild()
|
|
32
|
+
|
|
33
|
+
def load_background_document(self, uri: str, text: str) -> None:
|
|
34
|
+
"""Load a document from disk without marking it as user-opened."""
|
|
35
|
+
if uri in self._user_opened:
|
|
36
|
+
return
|
|
37
|
+
source = WorkspaceDocumentSource(path=uri_to_path(uri), uri=uri, text=text)
|
|
38
|
+
current = self.documents.get(uri)
|
|
39
|
+
if current is not None and current.text == text:
|
|
40
|
+
return
|
|
41
|
+
self.documents[uri] = source
|
|
42
|
+
self.rebuild()
|
|
43
|
+
|
|
44
|
+
def close_document(self, uri: str) -> Workspace | None:
|
|
45
|
+
"""Called when the user closes a tab — revert to on-disk content if available."""
|
|
46
|
+
self._user_opened.discard(uri)
|
|
47
|
+
path = uri_to_path(uri)
|
|
48
|
+
if path is not None and path.exists():
|
|
49
|
+
try:
|
|
50
|
+
text = path.read_text(encoding="utf-8")
|
|
51
|
+
source = WorkspaceDocumentSource(path=path, uri=uri, text=text)
|
|
52
|
+
self.documents[uri] = source
|
|
53
|
+
return self.rebuild()
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
56
|
+
return self.remove_document(uri)
|
|
57
|
+
|
|
58
|
+
def remove_document(self, uri: str) -> Workspace | None:
|
|
59
|
+
self._user_opened.discard(uri)
|
|
60
|
+
if uri in self.documents:
|
|
61
|
+
del self.documents[uri]
|
|
62
|
+
return self.rebuild()
|
|
63
|
+
return self.workspace
|
|
64
|
+
|
|
65
|
+
def rebuild(self) -> Workspace | None:
|
|
66
|
+
if not self.documents:
|
|
67
|
+
self.workspace = None
|
|
68
|
+
return None
|
|
69
|
+
ordered_sources = [self.documents[uri] for uri in sorted(self.documents)]
|
|
70
|
+
self.workspace = load_workspace_from_sources(ordered_sources)
|
|
71
|
+
return self.workspace
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def uri_to_path(uri: str) -> Path | None:
|
|
75
|
+
parsed = urlparse(uri)
|
|
76
|
+
if parsed.scheme != "file":
|
|
77
|
+
return None
|
|
78
|
+
path = unquote(parsed.path)
|
|
79
|
+
if parsed.netloc:
|
|
80
|
+
path = f"//{parsed.netloc}{path}"
|
|
81
|
+
elif len(path) >= 3 and path.startswith("/") and path[2] == ":":
|
|
82
|
+
path = path[1:]
|
|
83
|
+
return Path(path)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from lsprotocol import types
|
|
6
|
+
|
|
7
|
+
from modelable.lsp.document_symbols import build_document_symbols
|
|
8
|
+
from modelable.lsp.workspace import LspWorkspaceIndex
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class _WorkspaceSymbolEntry:
|
|
13
|
+
name: str
|
|
14
|
+
kind: types.SymbolKind
|
|
15
|
+
uri: str
|
|
16
|
+
start_line: int
|
|
17
|
+
start_character: int
|
|
18
|
+
end_line: int
|
|
19
|
+
end_character: int
|
|
20
|
+
container_name: str | None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def build_workspace_symbols(
|
|
24
|
+
index: LspWorkspaceIndex,
|
|
25
|
+
query: str,
|
|
26
|
+
) -> list[types.WorkspaceSymbol] | None:
|
|
27
|
+
workspace = index.workspace
|
|
28
|
+
if workspace is None:
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
entries: list[_WorkspaceSymbolEntry] = []
|
|
32
|
+
lowered_query = query.strip().lower()
|
|
33
|
+
|
|
34
|
+
for source in workspace.sources:
|
|
35
|
+
symbols = build_document_symbols(index, source.uri)
|
|
36
|
+
if not symbols:
|
|
37
|
+
continue
|
|
38
|
+
entries.extend(_flatten_symbols(source.uri, symbols))
|
|
39
|
+
|
|
40
|
+
filtered = [
|
|
41
|
+
entry
|
|
42
|
+
for entry in entries
|
|
43
|
+
if not lowered_query
|
|
44
|
+
or lowered_query in entry.name.lower()
|
|
45
|
+
or (entry.container_name is not None and lowered_query in entry.container_name.lower())
|
|
46
|
+
]
|
|
47
|
+
filtered.sort(key=lambda item: (item.name.lower(), item.container_name or "", item.uri))
|
|
48
|
+
return [
|
|
49
|
+
types.WorkspaceSymbol(
|
|
50
|
+
name=entry.name,
|
|
51
|
+
kind=entry.kind,
|
|
52
|
+
location=types.Location(
|
|
53
|
+
uri=entry.uri,
|
|
54
|
+
range=types.Range(
|
|
55
|
+
start=types.Position(line=entry.start_line, character=entry.start_character),
|
|
56
|
+
end=types.Position(line=entry.end_line, character=entry.end_character),
|
|
57
|
+
),
|
|
58
|
+
),
|
|
59
|
+
container_name=entry.container_name,
|
|
60
|
+
)
|
|
61
|
+
for entry in filtered
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _flatten_symbols(uri: str, symbols: list[types.DocumentSymbol]) -> list[_WorkspaceSymbolEntry]:
|
|
66
|
+
entries: list[_WorkspaceSymbolEntry] = []
|
|
67
|
+
for symbol in symbols:
|
|
68
|
+
entries.append(
|
|
69
|
+
_WorkspaceSymbolEntry(
|
|
70
|
+
name=symbol.name,
|
|
71
|
+
kind=symbol.kind,
|
|
72
|
+
uri=uri,
|
|
73
|
+
start_line=symbol.selection_range.start.line,
|
|
74
|
+
start_character=symbol.selection_range.start.character,
|
|
75
|
+
end_line=symbol.selection_range.end.line,
|
|
76
|
+
end_character=symbol.selection_range.end.character,
|
|
77
|
+
container_name=None,
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
for child in symbol.children or []:
|
|
81
|
+
entries.extend(_flatten_child(uri, symbol.name, child))
|
|
82
|
+
return entries
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _flatten_child(
|
|
86
|
+
uri: str,
|
|
87
|
+
container_name: str,
|
|
88
|
+
symbol: types.DocumentSymbol,
|
|
89
|
+
) -> list[_WorkspaceSymbolEntry]:
|
|
90
|
+
entries = [
|
|
91
|
+
_WorkspaceSymbolEntry(
|
|
92
|
+
name=symbol.name,
|
|
93
|
+
kind=symbol.kind,
|
|
94
|
+
uri=uri,
|
|
95
|
+
start_line=symbol.selection_range.start.line,
|
|
96
|
+
start_character=symbol.selection_range.start.character,
|
|
97
|
+
end_line=symbol.selection_range.end.line,
|
|
98
|
+
end_character=symbol.selection_range.end.character,
|
|
99
|
+
container_name=container_name,
|
|
100
|
+
)
|
|
101
|
+
]
|
|
102
|
+
for child in symbol.children or []:
|
|
103
|
+
entries.extend(_flatten_child(uri, symbol.name, child))
|
|
104
|
+
return entries
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from modelable.parser.ir import (
|
|
2
|
+
AccessBlock,
|
|
3
|
+
AccessGrant,
|
|
4
|
+
AiConfig,
|
|
5
|
+
AnnClassification,
|
|
6
|
+
AnnDeprecated,
|
|
7
|
+
AnnKey,
|
|
8
|
+
AnnOwner,
|
|
9
|
+
AnnPii,
|
|
10
|
+
AnnServer,
|
|
11
|
+
AnnWire,
|
|
12
|
+
ArrayType,
|
|
13
|
+
AutoProjectionDecl,
|
|
14
|
+
AutoProjectionTarget,
|
|
15
|
+
BindingDef,
|
|
16
|
+
ChangeKind,
|
|
17
|
+
ClassificationLevel,
|
|
18
|
+
ComputedMapping,
|
|
19
|
+
DecimalType,
|
|
20
|
+
DirectMapping,
|
|
21
|
+
DomainDef,
|
|
22
|
+
EnumType,
|
|
23
|
+
FieldDef,
|
|
24
|
+
FieldMapping,
|
|
25
|
+
GenerateTarget,
|
|
26
|
+
JoinRef,
|
|
27
|
+
MapType,
|
|
28
|
+
MdlFile,
|
|
29
|
+
ModelKind,
|
|
30
|
+
ModelVersion,
|
|
31
|
+
NamedType,
|
|
32
|
+
ObjectType,
|
|
33
|
+
PrimitiveType,
|
|
34
|
+
ProjectionField,
|
|
35
|
+
ProjectionVersion,
|
|
36
|
+
RefType,
|
|
37
|
+
SourceRef,
|
|
38
|
+
ValidationError,
|
|
39
|
+
VersionExact,
|
|
40
|
+
VersionMin,
|
|
41
|
+
VersionRange,
|
|
42
|
+
WireTargetHint,
|
|
43
|
+
WorkspaceDef,
|
|
44
|
+
)
|
|
45
|
+
from modelable.parser.parse import parse_file, parse_file_to_ir, parse_text, parse_text_to_ir
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"AccessBlock",
|
|
49
|
+
"AccessGrant",
|
|
50
|
+
"AiConfig",
|
|
51
|
+
"AnnClassification",
|
|
52
|
+
"AnnDeprecated",
|
|
53
|
+
"AnnKey",
|
|
54
|
+
"AnnOwner",
|
|
55
|
+
"AnnPii",
|
|
56
|
+
"AnnServer",
|
|
57
|
+
"AnnWire",
|
|
58
|
+
"ArrayType",
|
|
59
|
+
"AutoProjectionDecl",
|
|
60
|
+
"AutoProjectionTarget",
|
|
61
|
+
"BindingDef",
|
|
62
|
+
"ChangeKind",
|
|
63
|
+
"ClassificationLevel",
|
|
64
|
+
"ComputedMapping",
|
|
65
|
+
"DecimalType",
|
|
66
|
+
"DirectMapping",
|
|
67
|
+
"DomainDef",
|
|
68
|
+
"EnumType",
|
|
69
|
+
"FieldDef",
|
|
70
|
+
"FieldMapping",
|
|
71
|
+
"GenerateTarget",
|
|
72
|
+
"JoinRef",
|
|
73
|
+
"MapType",
|
|
74
|
+
"MdlFile",
|
|
75
|
+
"ModelKind",
|
|
76
|
+
"ModelVersion",
|
|
77
|
+
"NamedType",
|
|
78
|
+
"ObjectType",
|
|
79
|
+
"PrimitiveType",
|
|
80
|
+
"ProjectionField",
|
|
81
|
+
"ProjectionVersion",
|
|
82
|
+
"RefType",
|
|
83
|
+
"SourceRef",
|
|
84
|
+
"ValidationError",
|
|
85
|
+
"VersionExact",
|
|
86
|
+
"VersionMin",
|
|
87
|
+
"VersionRange",
|
|
88
|
+
"WireTargetHint",
|
|
89
|
+
"WorkspaceDef",
|
|
90
|
+
"parse_file",
|
|
91
|
+
"parse_file_to_ir",
|
|
92
|
+
"parse_text",
|
|
93
|
+
"parse_text_to_ir",
|
|
94
|
+
]
|