@oml/language 0.9.0 → 0.11.0
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.
- package/out/oml/oml-candidates.d.ts +17 -7
- package/out/oml/oml-candidates.js +119 -21
- package/out/oml/oml-candidates.js.map +1 -1
- package/out/oml/oml-completion.js +2 -1
- package/out/oml/oml-completion.js.map +1 -1
- package/out/oml/oml-document.d.ts +4 -4
- package/out/oml/oml-document.js +42 -7
- package/out/oml/oml-document.js.map +1 -1
- package/out/oml/oml-edit.d.ts +12 -0
- package/out/oml/oml-edit.js +201 -43
- package/out/oml/oml-edit.js.map +1 -1
- package/out/oml/oml-index.d.ts +26 -6
- package/out/oml/oml-index.js +305 -43
- package/out/oml/oml-index.js.map +1 -1
- package/out/oml/oml-rename.js +19 -1
- package/out/oml/oml-rename.js.map +1 -1
- package/out/oml/oml-utils.js +1 -0
- package/out/oml/oml-utils.js.map +1 -1
- package/package.json +1 -1
- package/src/oml/oml-candidates.ts +144 -23
- package/src/oml/oml-completion.ts +2 -1
- package/src/oml/oml-document.ts +49 -9
- package/src/oml/oml-edit.ts +211 -42
- package/src/oml/oml-index.ts +330 -44
- package/src/oml/oml-rename.ts +21 -1
- package/src/oml/oml-utils.ts +1 -0
- package/out/oml/oml-index-manager.d.ts +0 -10
- package/out/oml/oml-index-manager.js +0 -48
- package/out/oml/oml-index-manager.js.map +0 -1
- package/src/oml/oml-index-manager.ts +0 -56
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
2
|
|
|
3
3
|
import { URI, stream, type AstNodeDescription, type AstNodeLocator, type IndexManager, type LangiumDocuments, type Stream } from 'langium';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
isAspect,
|
|
6
|
+
isBooleanLiteral,
|
|
7
|
+
isConcept,
|
|
8
|
+
isConceptInstance,
|
|
9
|
+
isDecimalLiteral,
|
|
10
|
+
isDoubleLiteral,
|
|
11
|
+
isIntegerLiteral,
|
|
12
|
+
isMember,
|
|
13
|
+
isQuotedLiteral,
|
|
14
|
+
isRelationEntity,
|
|
15
|
+
isRelationInstance,
|
|
16
|
+
isScalar,
|
|
17
|
+
} from './generated/ast.js';
|
|
18
|
+
import { getOntologyModelIndex, type OmlIndex } from './oml-index.js';
|
|
5
19
|
import { getIriForNode, getWorkspaceSnapshot } from './oml-utils.js';
|
|
6
20
|
|
|
7
|
-
export const
|
|
21
|
+
export const OmlCompletionRequest = 'oml/completion';
|
|
8
22
|
|
|
9
|
-
export interface
|
|
10
|
-
|
|
23
|
+
export interface OmlCompletionParams {
|
|
24
|
+
instanceIri?: string;
|
|
25
|
+
propertyIri?: string;
|
|
26
|
+
typeIri?: string;
|
|
11
27
|
}
|
|
12
28
|
|
|
13
|
-
export interface
|
|
29
|
+
export interface OmlCompletionResult {
|
|
14
30
|
candidates: string[];
|
|
15
31
|
}
|
|
16
32
|
|
|
@@ -18,14 +34,14 @@ const MaxReferenceTypeCaches = 64;
|
|
|
18
34
|
const workspacePreloadState = new WeakMap<object, { completed: boolean; promise?: Promise<void> }>();
|
|
19
35
|
|
|
20
36
|
type ConnectionLike = {
|
|
21
|
-
onRequest: (type: string, handler: (params:
|
|
37
|
+
onRequest: (type: string, handler: (params: OmlCompletionParams) => OmlCompletionResult | Promise<OmlCompletionResult>) => void;
|
|
22
38
|
};
|
|
23
39
|
|
|
24
40
|
export class OmlCandidates {
|
|
25
41
|
private readonly referenceCandidatesCache = new Map<string, { snapshot: string; candidates: AstNodeDescription[] }>();
|
|
26
|
-
private readonly workspaceCandidateIrisCache = new Map<string, { snapshot: string; candidates: string[] }>();
|
|
27
42
|
|
|
28
43
|
constructor(
|
|
44
|
+
private readonly ontologyIndex: OmlIndex,
|
|
29
45
|
private readonly indexManager: IndexManager,
|
|
30
46
|
private readonly langiumDocuments: LangiumDocuments,
|
|
31
47
|
private readonly astNodeLocator: AstNodeLocator,
|
|
@@ -41,12 +57,7 @@ export class OmlCandidates {
|
|
|
41
57
|
if (!normalizedReferenceType) {
|
|
42
58
|
return [];
|
|
43
59
|
}
|
|
44
|
-
const
|
|
45
|
-
const cached = this.workspaceCandidateIrisCache.get(normalizedReferenceType);
|
|
46
|
-
if (cached && cached.snapshot === snapshot) {
|
|
47
|
-
return cached.candidates;
|
|
48
|
-
}
|
|
49
|
-
const workspace = this.getWorkspaceCandidates(normalizedReferenceType);
|
|
60
|
+
const workspace = this.indexManager.allElements(normalizedReferenceType).toArray();
|
|
50
61
|
const iris: string[] = [];
|
|
51
62
|
for (const description of workspace) {
|
|
52
63
|
const node = this.resolveDescriptionNode(description);
|
|
@@ -55,10 +66,39 @@ export class OmlCandidates {
|
|
|
55
66
|
iris.push(iri);
|
|
56
67
|
}
|
|
57
68
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
return [...new Set(iris)];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getCompletionCandidateIris(params: OmlCompletionParams): string[] {
|
|
73
|
+
const typeIri = (params.typeIri ?? '').trim();
|
|
74
|
+
if (!typeIri) {
|
|
75
|
+
return this.ontologyIndex.getAllDescriptionInstanceIris();
|
|
76
|
+
}
|
|
77
|
+
const typeNode = this.resolveTypeNodeByIri(typeIri);
|
|
78
|
+
if (!typeNode) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
if (isScalar(typeNode)) {
|
|
82
|
+
const literals = typeNode.ownedEnumeration?.literals ?? [];
|
|
83
|
+
const values = literals
|
|
84
|
+
.map((literal) => this.serializeLiteralCandidate(literal))
|
|
85
|
+
.filter((value): value is string => typeof value === 'string' && value.length > 0);
|
|
86
|
+
return [...new Set(values)];
|
|
87
|
+
}
|
|
88
|
+
if (isConcept(typeNode)) {
|
|
89
|
+
const enumeration = typeNode.ownedEnumeration;
|
|
90
|
+
if (enumeration?.instances?.length) {
|
|
91
|
+
const values = enumeration.instances
|
|
92
|
+
.map((reference) => this.resolveNamedInstanceReferenceIri(reference))
|
|
93
|
+
.filter((value): value is string => typeof value === 'string' && value.length > 0);
|
|
94
|
+
return [...new Set(values)];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (isAspect(typeNode) || isConcept(typeNode) || isRelationEntity(typeNode)) {
|
|
98
|
+
const iris = this.ontologyIndex.getTypeMemberIris(typeIri);
|
|
99
|
+
return iris;
|
|
100
|
+
}
|
|
101
|
+
return [];
|
|
62
102
|
}
|
|
63
103
|
|
|
64
104
|
private getWorkspaceCandidates(referenceType: string): AstNodeDescription[] {
|
|
@@ -89,6 +129,89 @@ export class OmlCandidates {
|
|
|
89
129
|
return undefined;
|
|
90
130
|
}
|
|
91
131
|
|
|
132
|
+
private normalizeIri(iri: string): string {
|
|
133
|
+
return iri.replace(/^<|>$/g, '').trim();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private resolveTypeNodeByIri(typeIri: string): unknown {
|
|
137
|
+
const normalized = this.normalizeIri(typeIri);
|
|
138
|
+
if (!normalized) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
for (const referenceType of ['Scalar', 'Concept', 'Aspect', 'RelationEntity']) {
|
|
142
|
+
for (const description of this.indexManager.allElements(referenceType).toArray()) {
|
|
143
|
+
const node = this.resolveDescriptionNode(description);
|
|
144
|
+
const iri = this.normalizeIri(getIriForNode(node) ?? '');
|
|
145
|
+
if (iri === normalized) {
|
|
146
|
+
return node;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private resolveNamedInstanceReferenceIri(reference: any): string | undefined {
|
|
154
|
+
const resolved = reference?.ref;
|
|
155
|
+
const resolvedIri = resolved ? this.normalizeIri(getIriForNode(resolved) ?? '') : '';
|
|
156
|
+
if (resolvedIri) {
|
|
157
|
+
return resolvedIri;
|
|
158
|
+
}
|
|
159
|
+
const refText = reference?.$refText;
|
|
160
|
+
if (typeof refText === 'string' && refText.trim().length > 0) {
|
|
161
|
+
return this.normalizeIri(refText);
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private serializeLiteralCandidate(literal: any): string {
|
|
167
|
+
if (isQuotedLiteral(literal)) {
|
|
168
|
+
return this.serializeQuotedLiteralCandidate(literal);
|
|
169
|
+
}
|
|
170
|
+
if (isBooleanLiteral(literal)) {
|
|
171
|
+
return literal.value ? 'true' : 'false';
|
|
172
|
+
}
|
|
173
|
+
if (isIntegerLiteral(literal) || isDecimalLiteral(literal) || isDoubleLiteral(literal)) {
|
|
174
|
+
return `${literal.value}`;
|
|
175
|
+
}
|
|
176
|
+
return '';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private serializeQuotedLiteralCandidate(literal: any): string {
|
|
180
|
+
const base = this.serializeStringLiteral(literal.value ?? '');
|
|
181
|
+
if (literal.type?.ref) {
|
|
182
|
+
const datatypeIri = this.normalizeIri(getIriForNode(literal.type.ref) ?? literal.type.$refText ?? '');
|
|
183
|
+
if (datatypeIri) {
|
|
184
|
+
return `${base}^^${datatypeIri}`;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (typeof literal.langTag === 'string' && literal.langTag.trim().length > 0) {
|
|
188
|
+
return `${base}$${literal.langTag.trim()}`;
|
|
189
|
+
}
|
|
190
|
+
return base;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private serializeStringLiteral(value: string): string {
|
|
194
|
+
const raw = value ?? '';
|
|
195
|
+
if (raw.includes('\n') || raw.includes('\r')) {
|
|
196
|
+
if (!raw.includes('"""')) {
|
|
197
|
+
return `"""${raw}"""`;
|
|
198
|
+
}
|
|
199
|
+
if (!raw.includes("'''")) {
|
|
200
|
+
return `'''${raw}'''`;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (!raw.includes('"')) {
|
|
204
|
+
return `"${raw}"`;
|
|
205
|
+
}
|
|
206
|
+
if (!raw.includes("'")) {
|
|
207
|
+
return `'${raw}'`;
|
|
208
|
+
}
|
|
209
|
+
if (!raw.includes('"""')) {
|
|
210
|
+
return `"""${raw}"""`;
|
|
211
|
+
}
|
|
212
|
+
return `'''${raw}'''`;
|
|
213
|
+
}
|
|
214
|
+
|
|
92
215
|
private resolveDescriptionNode(description: AstNodeDescription): unknown {
|
|
93
216
|
if (description.node) {
|
|
94
217
|
return description.node;
|
|
@@ -118,18 +241,16 @@ export class OmlCandidates {
|
|
|
118
241
|
}
|
|
119
242
|
|
|
120
243
|
export function registerOmlCandidatesRequests(connection: ConnectionLike, shared: any): void {
|
|
244
|
+
const ontologyIndex = getOntologyModelIndex(shared);
|
|
121
245
|
const candidates = new OmlCandidates(
|
|
246
|
+
ontologyIndex,
|
|
122
247
|
shared.workspace.IndexManager,
|
|
123
248
|
shared.workspace.LangiumDocuments,
|
|
124
249
|
shared.workspace.AstNodeLocator
|
|
125
250
|
);
|
|
126
|
-
connection.onRequest(
|
|
251
|
+
connection.onRequest(OmlCompletionRequest, async (params: OmlCompletionParams): Promise<OmlCompletionResult> => {
|
|
127
252
|
await ensureWorkspaceIndexed(shared);
|
|
128
|
-
|
|
129
|
-
if (!referenceType) {
|
|
130
|
-
return { candidates: [] };
|
|
131
|
-
}
|
|
132
|
-
return { candidates: candidates.getWorkspaceCandidateIris(referenceType) };
|
|
253
|
+
return { candidates: candidates.getCompletionCandidateIris(params) };
|
|
133
254
|
});
|
|
134
255
|
}
|
|
135
256
|
|
|
@@ -4,6 +4,7 @@ import type { AstNodeDescription, ReferenceInfo, Stream, LangiumDocuments, AstNo
|
|
|
4
4
|
import type { CompletionItem, TextEdit } from 'vscode-languageserver-types';
|
|
5
5
|
import { DefaultCompletionProvider, type CompletionContext, type CompletionValueItem, type LangiumServices } from 'langium/lsp';
|
|
6
6
|
import { Element, isDescription, isDescriptionBox, isMember, isOntology, isVocabulary, isVocabularyBundle, Member, Ontology } from './generated/ast.js';
|
|
7
|
+
import { getOntologyModelIndex } from './oml-index.js';
|
|
7
8
|
import { getLangiumDocumentVersion } from './oml-utils.js';
|
|
8
9
|
import { OmlCandidates } from './oml-candidates.js';
|
|
9
10
|
|
|
@@ -32,7 +33,7 @@ export class OmlCompletionProvider extends DefaultCompletionProvider {
|
|
|
32
33
|
this.indexManager = services.shared.workspace.IndexManager;
|
|
33
34
|
this.langiumDocuments = services.shared.workspace.LangiumDocuments;
|
|
34
35
|
this.astNodeLocator = services.workspace.AstNodeLocator;
|
|
35
|
-
this.candidates = new OmlCandidates(this.indexManager, this.langiumDocuments, this.astNodeLocator);
|
|
36
|
+
this.candidates = new OmlCandidates(getOntologyModelIndex(services.shared), this.indexManager, this.langiumDocuments, this.astNodeLocator);
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
/**
|
package/src/oml/oml-document.ts
CHANGED
|
@@ -1,24 +1,64 @@
|
|
|
1
1
|
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
2
|
|
|
3
|
+
import { URI } from 'langium';
|
|
3
4
|
import { DefaultDocumentUpdateHandler } from 'langium/lsp';
|
|
4
5
|
import type { LangiumSharedServices } from 'langium/lsp';
|
|
5
|
-
import type
|
|
6
|
-
import
|
|
6
|
+
import { FileChangeType, type DidChangeWatchedFilesParams } from 'vscode-languageserver';
|
|
7
|
+
import { getOntologyModelIndex } from './oml-index.js';
|
|
7
8
|
|
|
8
9
|
export class OmlDocumentUpdateHandler extends DefaultDocumentUpdateHandler {
|
|
9
|
-
private readonly
|
|
10
|
+
private readonly services: LangiumSharedServices;
|
|
10
11
|
|
|
11
12
|
constructor(services: LangiumSharedServices) {
|
|
12
13
|
super(services);
|
|
14
|
+
this.services = services;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
override
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
override didChangeWatchedFiles(params: DidChangeWatchedFilesParams): void {
|
|
18
|
+
this.cleanDeletedOntologyDocuments(params);
|
|
19
|
+
super.didChangeWatchedFiles(params);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private cleanDeletedOntologyDocuments(params: DidChangeWatchedFilesParams): void {
|
|
23
|
+
const deletedUris = new Set(
|
|
24
|
+
params.changes
|
|
25
|
+
.filter((change) => change.type === FileChangeType.Deleted)
|
|
26
|
+
.map((change) => change.uri)
|
|
27
|
+
);
|
|
28
|
+
if (deletedUris.size === 0) {
|
|
19
29
|
return;
|
|
20
30
|
}
|
|
21
|
-
|
|
22
|
-
|
|
31
|
+
|
|
32
|
+
const ontologyIndex = getOntologyModelIndex(this.services);
|
|
33
|
+
const documents = this.services.workspace.LangiumDocuments.all.toArray();
|
|
34
|
+
for (const document of documents) {
|
|
35
|
+
if (!matchesDeletedUri(document.uri.toString(), deletedUris)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
ontologyIndex.removeDocument(document);
|
|
39
|
+
const language = this.services.ServiceRegistry.getServices(document.uri) as any;
|
|
40
|
+
language?.reasoning?.ReasoningService?.onDocumentDeleted?.(document);
|
|
41
|
+
}
|
|
23
42
|
}
|
|
24
43
|
}
|
|
44
|
+
|
|
45
|
+
function matchesDeletedUri(candidateUri: string, deletedUris: ReadonlySet<string>): boolean {
|
|
46
|
+
for (const deletedUri of deletedUris) {
|
|
47
|
+
const deleted = URI.parse(deletedUri);
|
|
48
|
+
const candidate = URI.parse(candidateUri);
|
|
49
|
+
if (candidate.scheme !== deleted.scheme || (candidate.authority ?? '') !== (deleted.authority ?? '')) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const deletedPath = normalizeUriPath(deleted.path);
|
|
53
|
+
const candidatePath = normalizeUriPath(candidate.path);
|
|
54
|
+
if (candidatePath === deletedPath || candidatePath.startsWith(`${deletedPath}/`)) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeUriPath(path: string): string {
|
|
62
|
+
const normalized = path.replace(/\/+$/, '');
|
|
63
|
+
return normalized || '/';
|
|
64
|
+
}
|