umple-lsp-server 0.2.0 → 0.2.2
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/completions.scm +21 -6
- package/definitions.scm +4 -0
- package/out/completionAnalysis.d.ts +44 -0
- package/out/completionAnalysis.js +391 -0
- package/out/completionAnalysis.js.map +1 -0
- package/out/completionBuilder.d.ts +28 -0
- package/out/completionBuilder.js +251 -0
- package/out/completionBuilder.js.map +1 -0
- package/out/documentSymbolBuilder.d.ts +13 -0
- package/out/documentSymbolBuilder.js +95 -0
- package/out/documentSymbolBuilder.js.map +1 -0
- package/out/formatter.d.ts +31 -0
- package/out/formatter.js +96 -0
- package/out/formatter.js.map +1 -0
- package/out/hoverBuilder.d.ts +21 -0
- package/out/hoverBuilder.js +308 -0
- package/out/hoverBuilder.js.map +1 -0
- package/out/importGraph.d.ts +28 -0
- package/out/importGraph.js +91 -0
- package/out/importGraph.js.map +1 -0
- package/out/referenceSearch.d.ts +22 -0
- package/out/referenceSearch.js +271 -0
- package/out/referenceSearch.js.map +1 -0
- package/out/resolver.d.ts +21 -0
- package/out/resolver.js +174 -0
- package/out/resolver.js.map +1 -0
- package/out/server.js +350 -328
- package/out/server.js.map +1 -1
- package/out/symbolIndex.d.ts +86 -94
- package/out/symbolIndex.js +357 -399
- package/out/symbolIndex.js.map +1 -1
- package/out/symbolTypes.d.ts +34 -0
- package/out/symbolTypes.js +9 -0
- package/out/symbolTypes.js.map +1 -0
- package/out/tokenAnalysis.d.ts +24 -0
- package/out/tokenAnalysis.js +195 -0
- package/out/tokenAnalysis.js.map +1 -0
- package/out/tokenTypes.d.ts +46 -0
- package/out/tokenTypes.js +28 -0
- package/out/tokenTypes.js.map +1 -0
- package/out/treeUtils.d.ts +32 -0
- package/out/treeUtils.js +89 -0
- package/out/treeUtils.js.map +1 -0
- package/package.json +4 -2
- package/references.scm +78 -10
- package/tree-sitter-umple.wasm +0 -0
package/out/server.js
CHANGED
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const child_process_1 = require("child_process");
|
|
4
4
|
const fs = require("fs");
|
|
5
|
-
const net = require("net");
|
|
6
5
|
const os = require("os");
|
|
7
6
|
const path = require("path");
|
|
8
7
|
const url_1 = require("url");
|
|
9
8
|
const node_1 = require("vscode-languageserver/node");
|
|
10
9
|
const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
|
|
11
|
-
const keywords_1 = require("./keywords");
|
|
12
10
|
const symbolIndex_1 = require("./symbolIndex");
|
|
11
|
+
const resolver_1 = require("./resolver");
|
|
12
|
+
const completionBuilder_1 = require("./completionBuilder");
|
|
13
|
+
const hoverBuilder_1 = require("./hoverBuilder");
|
|
14
|
+
const documentSymbolBuilder_1 = require("./documentSymbolBuilder");
|
|
15
|
+
const formatter_1 = require("./formatter");
|
|
13
16
|
const connection = (0, node_1.createConnection)(node_1.ProposedFeatures.all);
|
|
14
17
|
const documents = new Map();
|
|
15
18
|
const pendingValidations = new Map();
|
|
@@ -72,29 +75,17 @@ function findFile(candidates) {
|
|
|
72
75
|
return undefined;
|
|
73
76
|
}
|
|
74
77
|
let umpleSyncJarPath;
|
|
75
|
-
let
|
|
76
|
-
let umpleSyncPort = 5555;
|
|
77
|
-
let umpleSyncTimeoutMs = 50000;
|
|
78
|
+
let umpleSyncTimeoutMs = 30000;
|
|
78
79
|
let jarWarningShown = false;
|
|
79
|
-
let serverProcess;
|
|
80
80
|
let treeSitterWasmPath;
|
|
81
81
|
let symbolIndexReady = false;
|
|
82
|
-
const DEFAULT_UMPLESYNC_TIMEOUT_MS =
|
|
82
|
+
const DEFAULT_UMPLESYNC_TIMEOUT_MS = 30000;
|
|
83
|
+
// Track in-flight validations so we can abort stale ones
|
|
84
|
+
const inFlightValidations = new Map();
|
|
83
85
|
connection.onInitialize((params) => {
|
|
84
86
|
const initOptions = params.initializationOptions;
|
|
85
87
|
umpleSyncJarPath =
|
|
86
88
|
initOptions?.umpleSyncJarPath || process.env.UMPLESYNC_JAR_PATH;
|
|
87
|
-
umpleSyncHost =
|
|
88
|
-
initOptions?.umpleSyncHost || process.env.UMPLESYNC_HOST || "localhost";
|
|
89
|
-
if (typeof initOptions?.umpleSyncPort === "number") {
|
|
90
|
-
umpleSyncPort = initOptions.umpleSyncPort;
|
|
91
|
-
}
|
|
92
|
-
else if (process.env.UMPLESYNC_PORT) {
|
|
93
|
-
const parsed = Number(process.env.UMPLESYNC_PORT);
|
|
94
|
-
if (!Number.isNaN(parsed)) {
|
|
95
|
-
umpleSyncPort = parsed;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
89
|
if (typeof initOptions?.umpleSyncTimeoutMs === "number") {
|
|
99
90
|
umpleSyncTimeoutMs = initOptions.umpleSyncTimeoutMs;
|
|
100
91
|
}
|
|
@@ -113,9 +104,16 @@ connection.onInitialize((params) => {
|
|
|
113
104
|
textDocumentSync: node_1.TextDocumentSyncKind.Incremental,
|
|
114
105
|
completionProvider: {
|
|
115
106
|
resolveProvider: false,
|
|
116
|
-
triggerCharacters: ["/"],
|
|
107
|
+
triggerCharacters: ["/", "."],
|
|
117
108
|
},
|
|
118
109
|
definitionProvider: true,
|
|
110
|
+
referencesProvider: true,
|
|
111
|
+
renameProvider: {
|
|
112
|
+
prepareProvider: true,
|
|
113
|
+
},
|
|
114
|
+
hoverProvider: true,
|
|
115
|
+
documentSymbolProvider: true,
|
|
116
|
+
documentFormattingProvider: true,
|
|
119
117
|
},
|
|
120
118
|
};
|
|
121
119
|
});
|
|
@@ -154,7 +152,7 @@ connection.onDidOpenTextDocument((params) => {
|
|
|
154
152
|
if (symbolIndexReady) {
|
|
155
153
|
try {
|
|
156
154
|
const filePath = (0, url_1.fileURLToPath)(params.textDocument.uri);
|
|
157
|
-
symbolIndex_1.symbolIndex.
|
|
155
|
+
symbolIndex_1.symbolIndex.indexFile(filePath, params.textDocument.text);
|
|
158
156
|
}
|
|
159
157
|
catch {
|
|
160
158
|
// Ignore errors for non-file URIs
|
|
@@ -190,6 +188,14 @@ connection.onDidChangeTextDocument((params) => {
|
|
|
190
188
|
}
|
|
191
189
|
const updated = vscode_languageserver_textdocument_1.TextDocument.update(document, params.contentChanges, params.textDocument.version);
|
|
192
190
|
setDocument(params.textDocument.uri, updated);
|
|
191
|
+
// Keep the symbol index current so the clean baseline stays fresh.
|
|
192
|
+
// Without this, state symbols added during clean edits would be lost
|
|
193
|
+
// when the file later enters an errored state (error preservation
|
|
194
|
+
// would use a stale clean snapshot).
|
|
195
|
+
const changedPath = getDocumentFilePath(updated);
|
|
196
|
+
if (changedPath && symbolIndexReady) {
|
|
197
|
+
symbolIndex_1.symbolIndex.updateFile(changedPath, updated.getText());
|
|
198
|
+
}
|
|
193
199
|
scheduleValidation(updated);
|
|
194
200
|
// Re-validate other open documents that might depend on this file
|
|
195
201
|
scheduleDependentValidation(params.textDocument.uri);
|
|
@@ -202,6 +208,12 @@ connection.onDidCloseTextDocument((params) => {
|
|
|
202
208
|
clearTimeout(pendingValidation);
|
|
203
209
|
pendingValidations.delete(normalizedUri);
|
|
204
210
|
}
|
|
211
|
+
// Abort any in-flight validation so stale results aren't published after close
|
|
212
|
+
const inFlight = inFlightValidations.get(normalizedUri);
|
|
213
|
+
if (inFlight) {
|
|
214
|
+
inFlight.abort();
|
|
215
|
+
inFlightValidations.delete(normalizedUri);
|
|
216
|
+
}
|
|
205
217
|
connection.sendDiagnostics({ uri: params.textDocument.uri, diagnostics: [] });
|
|
206
218
|
});
|
|
207
219
|
connection.onCompletion(async (params) => {
|
|
@@ -245,170 +257,261 @@ connection.onCompletion(async (params) => {
|
|
|
245
257
|
else if (params.context?.triggerCharacter === "/") {
|
|
246
258
|
return [];
|
|
247
259
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
kind: node_1.CompletionItemKind.TypeParameter,
|
|
271
|
-
detail: "type",
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
// 5d. Constraint scope: only own attributes (Umple E28)
|
|
277
|
-
if (symbolKinds === "own_attribute" && info.enclosingClass) {
|
|
278
|
-
const symbols = symbolIndex_1.symbolIndex
|
|
279
|
-
.getSymbols({ container: info.enclosingClass, kind: "attribute" })
|
|
280
|
-
.filter((s) => reachableFiles.has(path.normalize(s.file)));
|
|
281
|
-
for (const sym of symbols) {
|
|
282
|
-
if (!seen.has(sym.name)) {
|
|
283
|
-
seen.add(sym.name);
|
|
284
|
-
items.push({
|
|
285
|
-
label: sym.name,
|
|
286
|
-
kind: symbolKindToCompletionKind("attribute"),
|
|
287
|
-
detail: "attribute",
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
return items;
|
|
292
|
-
}
|
|
293
|
-
// 5e. Symbol completions from index (scoped to reachable files)
|
|
294
|
-
if (Array.isArray(symbolKinds)) {
|
|
295
|
-
for (const symKind of symbolKinds) {
|
|
296
|
-
let symbols;
|
|
297
|
-
// Scoped lookups for container-aware kinds
|
|
298
|
-
if (symKind === "attribute" && info.enclosingClass) {
|
|
299
|
-
symbols = symbolIndex_1.symbolIndex
|
|
300
|
-
.getSymbols({
|
|
301
|
-
container: info.enclosingClass,
|
|
302
|
-
kind: "attribute",
|
|
303
|
-
inherited: true,
|
|
304
|
-
})
|
|
305
|
-
.filter((s) => reachableFiles.has(path.normalize(s.file)));
|
|
306
|
-
}
|
|
307
|
-
else if (symKind === "state" && info.enclosingStateMachine) {
|
|
308
|
-
symbols = symbolIndex_1.symbolIndex
|
|
309
|
-
.getSymbols({ container: info.enclosingStateMachine, kind: "state" })
|
|
310
|
-
.filter((s) => reachableFiles.has(path.normalize(s.file)));
|
|
311
|
-
}
|
|
312
|
-
else if (symKind === "template" && info.enclosingClass) {
|
|
313
|
-
symbols = symbolIndex_1.symbolIndex
|
|
314
|
-
.getSymbols({ container: info.enclosingClass, kind: "template" })
|
|
315
|
-
.filter((s) => reachableFiles.has(path.normalize(s.file)));
|
|
316
|
-
}
|
|
317
|
-
else {
|
|
318
|
-
symbols = symbolIndex_1.symbolIndex
|
|
319
|
-
.getSymbols({ kind: symKind })
|
|
320
|
-
.filter((s) => reachableFiles.has(path.normalize(s.file)));
|
|
321
|
-
}
|
|
322
|
-
for (const sym of symbols) {
|
|
323
|
-
if (!seen.has(sym.name)) {
|
|
324
|
-
seen.add(sym.name);
|
|
325
|
-
items.push({
|
|
326
|
-
label: sym.name,
|
|
327
|
-
kind: symbolKindToCompletionKind(symKind),
|
|
328
|
-
detail: symKind,
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
}
|
|
260
|
+
else if (params.context?.triggerCharacter === ".") {
|
|
261
|
+
if (!info.dottedStatePrefix)
|
|
262
|
+
return [];
|
|
263
|
+
// Dot-state completion: return only child state names, skip all
|
|
264
|
+
// generic phases (keywords, operators, types, other symbol kinds).
|
|
265
|
+
const childNames = info.enclosingStateMachine
|
|
266
|
+
? symbolIndex_1.symbolIndex.getChildStateNames(info.dottedStatePrefix, info.enclosingStateMachine, reachableFiles)
|
|
267
|
+
: [];
|
|
268
|
+
return childNames.map((name) => ({
|
|
269
|
+
label: name,
|
|
270
|
+
kind: (0, completionBuilder_1.symbolKindToCompletionKind)("state"),
|
|
271
|
+
detail: "state",
|
|
272
|
+
sortText: `0_${name}`,
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
275
|
+
// 5. Build semantic completion items (keywords, operators, types, symbols)
|
|
276
|
+
const semanticItems = (0, completionBuilder_1.buildSemanticCompletionItems)(info, symbolKinds, symbolIndex_1.symbolIndex, reachableFiles);
|
|
277
|
+
// Merge use_path items (if any) with semantic items, deduplicating
|
|
278
|
+
for (const item of semanticItems) {
|
|
279
|
+
if (!seen.has(item.label)) {
|
|
280
|
+
seen.add(item.label);
|
|
281
|
+
items.push(item);
|
|
332
282
|
}
|
|
333
283
|
}
|
|
334
284
|
return items;
|
|
335
285
|
});
|
|
286
|
+
// ── Shared symbol resolution (used by go-to-def and hover) ──────────────────
|
|
287
|
+
/**
|
|
288
|
+
* Resolve symbol(s) at a given position. Thin wrapper around the shared
|
|
289
|
+
* resolver that handles reachable-file computation from the document context.
|
|
290
|
+
*/
|
|
291
|
+
function resolveSymbolAtPosition(docPath, content, line, col) {
|
|
292
|
+
const reachableFiles = ensureImportsIndexed(docPath, content);
|
|
293
|
+
return (0, resolver_1.resolveSymbolAtPosition)(symbolIndex_1.symbolIndex, docPath, content, line, col, reachableFiles);
|
|
294
|
+
}
|
|
336
295
|
connection.onDefinition(async (params) => {
|
|
337
296
|
const document = getDocument(params.textDocument.uri);
|
|
338
|
-
if (!document)
|
|
297
|
+
if (!document || !symbolIndexReady)
|
|
339
298
|
return [];
|
|
340
|
-
}
|
|
341
|
-
if (!symbolIndexReady) {
|
|
342
|
-
return [];
|
|
343
|
-
}
|
|
344
299
|
const docPath = getDocumentFilePath(document);
|
|
345
|
-
if (!docPath)
|
|
300
|
+
if (!docPath)
|
|
346
301
|
return [];
|
|
347
|
-
}
|
|
348
302
|
const token = symbolIndex_1.symbolIndex.getTokenAtPosition(docPath, document.getText(), params.position.line, params.position.character);
|
|
349
|
-
if (!token)
|
|
303
|
+
if (!token)
|
|
350
304
|
return [];
|
|
351
|
-
}
|
|
352
305
|
// use statement with .ump extension: resolve as file reference
|
|
353
306
|
if (token.word.endsWith(".ump")) {
|
|
354
307
|
const baseDir = path.dirname(docPath);
|
|
355
308
|
const targetPath = path.isAbsolute(token.word)
|
|
356
309
|
? token.word
|
|
357
310
|
: path.join(baseDir, token.word);
|
|
358
|
-
if (!fs.existsSync(targetPath))
|
|
311
|
+
if (!fs.existsSync(targetPath))
|
|
359
312
|
return [];
|
|
360
|
-
}
|
|
361
313
|
return [
|
|
362
314
|
node_1.Location.create((0, url_1.pathToFileURL)(targetPath).toString(), node_1.Range.create(node_1.Position.create(0, 0), node_1.Position.create(0, 0))),
|
|
363
315
|
];
|
|
364
316
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
317
|
+
const resolved = resolveSymbolAtPosition(docPath, document.getText(), params.position.line, params.position.character);
|
|
318
|
+
if (!resolved || resolved.symbols.length === 0)
|
|
319
|
+
return [];
|
|
320
|
+
return resolved.symbols.map((sym) => node_1.Location.create((0, url_1.pathToFileURL)(sym.file).toString(), node_1.Range.create(node_1.Position.create(sym.line, sym.column), node_1.Position.create(sym.endLine, sym.endColumn))));
|
|
321
|
+
});
|
|
322
|
+
// ── Find References ──────────────────────────────────────────────────────────
|
|
323
|
+
connection.onReferences(async (params) => {
|
|
324
|
+
const document = getDocument(params.textDocument.uri);
|
|
325
|
+
if (!document || !symbolIndexReady)
|
|
326
|
+
return [];
|
|
327
|
+
const docPath = getDocumentFilePath(document);
|
|
328
|
+
if (!docPath)
|
|
329
|
+
return [];
|
|
330
|
+
// 1. Identify symbol (full declaration set)
|
|
331
|
+
const resolved = resolveSymbolAtPosition(docPath, document.getText(), params.position.line, params.position.character);
|
|
332
|
+
if (!resolved || resolved.symbols.length === 0)
|
|
333
|
+
return [];
|
|
334
|
+
// 2. Index all workspace files (content-hash skips unchanged files)
|
|
335
|
+
symbolIndex_1.symbolIndex.indexWorkspace(workspaceRoots, (filePath) => {
|
|
336
|
+
const uri = (0, url_1.pathToFileURL)(filePath).toString();
|
|
337
|
+
return getDocument(uri)?.getText();
|
|
338
|
+
});
|
|
339
|
+
// 3. Compute search scope: declaration files + reverse importers
|
|
340
|
+
const declFiles = new Set(resolved.symbols.map((s) => path.normalize(s.file)));
|
|
341
|
+
const filesToSearch = symbolIndex_1.symbolIndex.getReverseImporters(declFiles);
|
|
342
|
+
// Include declaration files themselves
|
|
343
|
+
for (const f of declFiles)
|
|
344
|
+
filesToSearch.add(f);
|
|
345
|
+
// 4. Find references
|
|
346
|
+
const refs = symbolIndex_1.symbolIndex.findReferences(resolved.symbols, filesToSearch, params.context.includeDeclaration);
|
|
347
|
+
// 5. Convert to Location[]
|
|
348
|
+
return refs.map((r) => node_1.Location.create((0, url_1.pathToFileURL)(r.file).toString(), node_1.Range.create(node_1.Position.create(r.line, r.column), node_1.Position.create(r.endLine, r.endColumn))));
|
|
349
|
+
});
|
|
350
|
+
// ── Rename ───────────────────────────────────────────────────────────────────
|
|
351
|
+
const RENAMEABLE_KINDS = new Set([
|
|
352
|
+
"class",
|
|
353
|
+
"interface",
|
|
354
|
+
"trait",
|
|
355
|
+
"enum",
|
|
356
|
+
"mixset",
|
|
357
|
+
"attribute",
|
|
358
|
+
"const",
|
|
359
|
+
"state",
|
|
360
|
+
"statemachine",
|
|
361
|
+
"tracecase",
|
|
362
|
+
]);
|
|
363
|
+
function isUnambiguousRename(symbols) {
|
|
364
|
+
if (symbols.length <= 1)
|
|
365
|
+
return symbols.length === 1;
|
|
366
|
+
// All symbols must share the same kind
|
|
367
|
+
const kind = symbols[0].kind;
|
|
368
|
+
if (!symbols.every((s) => s.kind === kind))
|
|
369
|
+
return false;
|
|
370
|
+
// State: must share same statePath (different paths = different states)
|
|
371
|
+
if (kind === "state") {
|
|
372
|
+
const refPath = symbols[0].statePath?.join(".");
|
|
373
|
+
return symbols.every((s) => s.statePath?.join(".") === refPath);
|
|
374
|
+
}
|
|
375
|
+
// Container-scoped kinds: must share container + name
|
|
376
|
+
const containerScoped = new Set([
|
|
369
377
|
"attribute",
|
|
378
|
+
"const",
|
|
370
379
|
"method",
|
|
371
380
|
"template",
|
|
372
|
-
"
|
|
381
|
+
"statemachine",
|
|
373
382
|
]);
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
if (
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
383
|
+
if (containerScoped.has(kind)) {
|
|
384
|
+
const { container, name } = symbols[0];
|
|
385
|
+
return symbols.every((s) => s.container === container && s.name === name);
|
|
386
|
+
}
|
|
387
|
+
// Top-level mergeable kinds (class, interface, trait, enum, mixset):
|
|
388
|
+
// same name = partial definitions of the same entity
|
|
389
|
+
const name = symbols[0].name;
|
|
390
|
+
return symbols.every((s) => s.name === name);
|
|
391
|
+
}
|
|
392
|
+
connection.onPrepareRename(async (params) => {
|
|
393
|
+
const document = getDocument(params.textDocument.uri);
|
|
394
|
+
if (!document || !symbolIndexReady)
|
|
395
|
+
return null;
|
|
396
|
+
const docPath = getDocumentFilePath(document);
|
|
397
|
+
if (!docPath)
|
|
398
|
+
return null;
|
|
399
|
+
// Full semantic resolution
|
|
400
|
+
const resolved = resolveSymbolAtPosition(docPath, document.getText(), params.position.line, params.position.character);
|
|
401
|
+
if (!resolved || resolved.symbols.length === 0)
|
|
402
|
+
return null;
|
|
403
|
+
// Kind must be in the renameable set
|
|
404
|
+
if (!RENAMEABLE_KINDS.has(resolved.symbols[0].kind))
|
|
405
|
+
return null;
|
|
406
|
+
// Identity must be unambiguous
|
|
407
|
+
if (!isUnambiguousRename(resolved.symbols))
|
|
408
|
+
return null;
|
|
409
|
+
// Get precise identifier range
|
|
410
|
+
const range = symbolIndex_1.symbolIndex.getNodeRangeAtPosition(docPath, document.getText(), params.position.line, params.position.character);
|
|
411
|
+
if (!range)
|
|
412
|
+
return null;
|
|
413
|
+
return {
|
|
414
|
+
range: node_1.Range.create(node_1.Position.create(range.startLine, range.startColumn), node_1.Position.create(range.endLine, range.endColumn)),
|
|
415
|
+
placeholder: resolved.token.word,
|
|
416
|
+
};
|
|
417
|
+
});
|
|
418
|
+
connection.onRenameRequest(async (params) => {
|
|
419
|
+
const document = getDocument(params.textDocument.uri);
|
|
420
|
+
if (!document || !symbolIndexReady)
|
|
421
|
+
return null;
|
|
422
|
+
const docPath = getDocumentFilePath(document);
|
|
423
|
+
if (!docPath)
|
|
424
|
+
return null;
|
|
425
|
+
// Validate new name is a legal identifier
|
|
426
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(params.newName))
|
|
427
|
+
return null;
|
|
428
|
+
// 1. Full semantic resolution (same checks as prepareRename)
|
|
429
|
+
const resolved = resolveSymbolAtPosition(docPath, document.getText(), params.position.line, params.position.character);
|
|
430
|
+
if (!resolved || resolved.symbols.length === 0)
|
|
431
|
+
return null;
|
|
432
|
+
if (!RENAMEABLE_KINDS.has(resolved.symbols[0].kind))
|
|
433
|
+
return null;
|
|
434
|
+
if (!isUnambiguousRename(resolved.symbols))
|
|
435
|
+
return null;
|
|
436
|
+
// 2. Index workspace
|
|
437
|
+
symbolIndex_1.symbolIndex.indexWorkspace(workspaceRoots, (filePath) => {
|
|
438
|
+
const uri = (0, url_1.pathToFileURL)(filePath).toString();
|
|
439
|
+
return getDocument(uri)?.getText();
|
|
440
|
+
});
|
|
441
|
+
// 3. Compute search scope
|
|
442
|
+
const declFiles = new Set(resolved.symbols.map((s) => path.normalize(s.file)));
|
|
443
|
+
const filesToSearch = symbolIndex_1.symbolIndex.getReverseImporters(declFiles);
|
|
444
|
+
for (const f of declFiles)
|
|
445
|
+
filesToSearch.add(f);
|
|
446
|
+
// 4. Find ALL references including declarations
|
|
447
|
+
const refs = symbolIndex_1.symbolIndex.findReferences(resolved.symbols, filesToSearch, true);
|
|
448
|
+
// 5. Build WorkspaceEdit
|
|
449
|
+
const changes = {};
|
|
450
|
+
for (const r of refs) {
|
|
451
|
+
const uri = (0, url_1.pathToFileURL)(r.file).toString();
|
|
452
|
+
if (!changes[uri])
|
|
453
|
+
changes[uri] = [];
|
|
454
|
+
changes[uri].push(node_1.TextEdit.replace(node_1.Range.create(node_1.Position.create(r.line, r.column), node_1.Position.create(r.endLine, r.endColumn)), params.newName));
|
|
455
|
+
}
|
|
456
|
+
return { changes };
|
|
457
|
+
});
|
|
458
|
+
// ── Hover ───────────────────────────────────────────────────────────────────
|
|
459
|
+
connection.onHover(async (params) => {
|
|
460
|
+
const document = getDocument(params.textDocument.uri);
|
|
461
|
+
if (!document || !symbolIndexReady)
|
|
462
|
+
return null;
|
|
463
|
+
const docPath = getDocumentFilePath(document);
|
|
464
|
+
if (!docPath)
|
|
465
|
+
return null;
|
|
466
|
+
const resolved = resolveSymbolAtPosition(docPath, document.getText(), params.position.line, params.position.character);
|
|
467
|
+
if (!resolved || resolved.symbols.length === 0)
|
|
468
|
+
return null;
|
|
469
|
+
const sym = resolved.symbols[0];
|
|
470
|
+
const markdown = (0, hoverBuilder_1.buildHoverMarkdown)(sym, resolved.symbols, {
|
|
471
|
+
getTree: (fp) => symbolIndex_1.symbolIndex.getTree(fp),
|
|
472
|
+
getIsAParents: (name) => symbolIndex_1.symbolIndex.getIsAParents(name),
|
|
473
|
+
});
|
|
474
|
+
if (!markdown)
|
|
475
|
+
return null;
|
|
476
|
+
return { contents: { kind: "markdown", value: markdown } };
|
|
477
|
+
});
|
|
478
|
+
// ── Document Symbols (Outline) ──────────────────────────────────────────────
|
|
479
|
+
connection.onDocumentSymbol(async (params) => {
|
|
480
|
+
const document = getDocument(params.textDocument.uri);
|
|
481
|
+
if (!document || !symbolIndexReady)
|
|
482
|
+
return [];
|
|
483
|
+
const docPath = getDocumentFilePath(document);
|
|
484
|
+
if (!docPath)
|
|
485
|
+
return [];
|
|
486
|
+
symbolIndex_1.symbolIndex.updateFile(docPath, document.getText());
|
|
487
|
+
return (0, documentSymbolBuilder_1.buildDocumentSymbolTree)(symbolIndex_1.symbolIndex.getFileSymbols(docPath));
|
|
488
|
+
});
|
|
489
|
+
// ── Formatting ──────────────────────────────────────────────────────────────
|
|
490
|
+
connection.onDocumentFormatting(async (params) => {
|
|
491
|
+
const document = getDocument(params.textDocument.uri);
|
|
492
|
+
if (!document)
|
|
493
|
+
return [];
|
|
494
|
+
const docPath = getDocumentFilePath(document);
|
|
495
|
+
if (!docPath || !symbolIndexReady)
|
|
496
|
+
return [];
|
|
497
|
+
symbolIndex_1.symbolIndex.updateFile(docPath, document.getText());
|
|
498
|
+
const tree = symbolIndex_1.symbolIndex.getTree(docPath);
|
|
499
|
+
if (!tree)
|
|
500
|
+
return [];
|
|
501
|
+
const skipRanges = (0, formatter_1.getCodeContentRanges)(tree);
|
|
502
|
+
return (0, formatter_1.computeIndentEdits)(document.getText(), params.options, skipRanges);
|
|
401
503
|
});
|
|
402
504
|
function scheduleValidation(document) {
|
|
403
|
-
const
|
|
505
|
+
const uriKey = normalizeUri(document.uri);
|
|
506
|
+
const existing = pendingValidations.get(uriKey);
|
|
404
507
|
if (existing) {
|
|
405
508
|
clearTimeout(existing);
|
|
406
509
|
}
|
|
407
510
|
const handle = setTimeout(() => {
|
|
408
|
-
pendingValidations.delete(
|
|
511
|
+
pendingValidations.delete(uriKey);
|
|
409
512
|
void validateTextDocument(document);
|
|
410
513
|
}, 300);
|
|
411
|
-
pendingValidations.set(
|
|
514
|
+
pendingValidations.set(uriKey, handle);
|
|
412
515
|
}
|
|
413
516
|
// Debounce key for dependent validation
|
|
414
517
|
const dependentValidationKey = "__dependent__";
|
|
@@ -470,33 +573,57 @@ async function validateTextDocument(document) {
|
|
|
470
573
|
if (!jarPath) {
|
|
471
574
|
return;
|
|
472
575
|
}
|
|
576
|
+
const uriKey = normalizeUri(document.uri);
|
|
577
|
+
const docVersion = document.version;
|
|
578
|
+
// Abort any in-flight validation for this document
|
|
579
|
+
const previous = inFlightValidations.get(uriKey);
|
|
580
|
+
if (previous) {
|
|
581
|
+
previous.abort();
|
|
582
|
+
}
|
|
583
|
+
const abortController = new AbortController();
|
|
584
|
+
inFlightValidations.set(uriKey, abortController);
|
|
473
585
|
try {
|
|
474
|
-
const diagnostics = await runUmpleSyncAndParseDiagnostics(jarPath, document);
|
|
586
|
+
const diagnostics = await runUmpleSyncAndParseDiagnostics(jarPath, document, abortController.signal);
|
|
587
|
+
if (abortController.signal.aborted) {
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
// Drop stale results if the document has been edited since we started
|
|
591
|
+
const current = getDocument(document.uri);
|
|
592
|
+
if (!current || current.version !== docVersion) {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
475
595
|
connection.sendDiagnostics({ uri: document.uri, diagnostics });
|
|
476
596
|
}
|
|
477
597
|
catch (error) {
|
|
598
|
+
if (abortController.signal.aborted) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
const current = getDocument(document.uri);
|
|
602
|
+
if (!current || current.version !== docVersion) {
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
478
605
|
connection.console.error(`Diagnostics failed: ${String(error)}`);
|
|
479
606
|
connection.sendDiagnostics({ uri: document.uri, diagnostics: [] });
|
|
480
607
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
if (!jarWarningShown) {
|
|
485
|
-
connection.window.showWarningMessage("UmpleSync jar path not set. Configure initializationOptions.umpleSyncJarPath or UMPLESYNC_JAR.");
|
|
486
|
-
jarWarningShown = true;
|
|
608
|
+
finally {
|
|
609
|
+
if (inFlightValidations.get(uriKey) === abortController) {
|
|
610
|
+
inFlightValidations.delete(uriKey);
|
|
487
611
|
}
|
|
488
|
-
return undefined;
|
|
489
612
|
}
|
|
490
|
-
|
|
613
|
+
}
|
|
614
|
+
function resolveJarPath() {
|
|
615
|
+
if (!umpleSyncJarPath || !fs.existsSync(umpleSyncJarPath)) {
|
|
491
616
|
if (!jarWarningShown) {
|
|
492
|
-
connection.window.showWarningMessage(
|
|
617
|
+
connection.window.showWarningMessage("Umple diagnostics are disabled: umplesync.jar was not found. " +
|
|
618
|
+
"Completion and go-to-definition still work. " +
|
|
619
|
+
"Reload the window to retry.");
|
|
493
620
|
jarWarningShown = true;
|
|
494
621
|
}
|
|
495
622
|
return undefined;
|
|
496
623
|
}
|
|
497
624
|
return umpleSyncJarPath;
|
|
498
625
|
}
|
|
499
|
-
async function runUmpleSyncAndParseDiagnostics(jarPath, document) {
|
|
626
|
+
async function runUmpleSyncAndParseDiagnostics(jarPath, document, signal) {
|
|
500
627
|
const docPath = getDocumentFilePath(document);
|
|
501
628
|
if (!docPath) {
|
|
502
629
|
return [];
|
|
@@ -513,8 +640,7 @@ async function runUmpleSyncAndParseDiagnostics(jarPath, document) {
|
|
|
513
640
|
text = text.replace(/\n?$/, "\n\n");
|
|
514
641
|
}
|
|
515
642
|
await fs.promises.writeFile(shadow.targetFile, text, "utf8");
|
|
516
|
-
const
|
|
517
|
-
const { stdout, stderr } = await sendUmpleSyncCommand(jarPath, commandLine);
|
|
643
|
+
const { stdout, stderr } = await runUmpleDirect(jarPath, shadow.targetFile, signal);
|
|
518
644
|
const tempFilename = path.basename(shadow.targetFile);
|
|
519
645
|
const documentDir = getDocumentDirectory(document);
|
|
520
646
|
return parseUmpleDiagnostics(stderr, stdout, document, tempFilename, documentDir);
|
|
@@ -523,13 +649,39 @@ async function runUmpleSyncAndParseDiagnostics(jarPath, document) {
|
|
|
523
649
|
await shadow.cleanup();
|
|
524
650
|
}
|
|
525
651
|
}
|
|
652
|
+
/**
|
|
653
|
+
* Run umplesync.jar directly as a subprocess (one process per request).
|
|
654
|
+
* This is simpler and more reliable than the socket server approach —
|
|
655
|
+
* no persistent state, no stuck connections between requests.
|
|
656
|
+
*/
|
|
657
|
+
function runUmpleDirect(jarPath, filePath, signal) {
|
|
658
|
+
return new Promise((resolve, reject) => {
|
|
659
|
+
if (signal?.aborted) {
|
|
660
|
+
reject(new Error("aborted"));
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
(0, child_process_1.execFile)("java", ["-jar", jarPath, "-generate", "nothing", filePath], { signal, timeout: umpleSyncTimeoutMs, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
664
|
+
if (signal?.aborted) {
|
|
665
|
+
reject(new Error("aborted"));
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
// Umplesync writes diagnostics to stderr and exits 0 on compile errors.
|
|
669
|
+
// Any non-null error here is a real execution failure (java not found,
|
|
670
|
+
// corrupt jar, runtime crash, timeout kill) — reject unconditionally.
|
|
671
|
+
if (error) {
|
|
672
|
+
reject(error);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
resolve({ stdout, stderr });
|
|
676
|
+
});
|
|
677
|
+
});
|
|
678
|
+
}
|
|
526
679
|
/**
|
|
527
680
|
* Create a shadow workspace with only the files needed for compilation:
|
|
528
681
|
* the current document and all files it imports via `use` statements.
|
|
529
682
|
*/
|
|
530
683
|
async function createShadowWorkspace(documentPath) {
|
|
531
684
|
const documentDir = path.dirname(documentPath);
|
|
532
|
-
const documentName = path.basename(documentPath);
|
|
533
685
|
// Get document content (from open doc or disk)
|
|
534
686
|
const fileUri = (0, url_1.pathToFileURL)(documentPath).toString();
|
|
535
687
|
const openDoc = getDocument(fileUri);
|
|
@@ -543,12 +695,18 @@ async function createShadowWorkspace(documentPath) {
|
|
|
543
695
|
// Find only files reachable via use statements (lazy approach)
|
|
544
696
|
const reachableFiles = collectReachableFiles(documentPath, documentContent, documentDir);
|
|
545
697
|
// Also include the current document
|
|
546
|
-
|
|
698
|
+
const normalizedDocPath = path.normalize(documentPath);
|
|
699
|
+
reachableFiles.add(normalizedDocPath);
|
|
700
|
+
// Compute a common ancestor directory so all relative paths stay inside
|
|
701
|
+
// the shadow workspace (no "../" escapes). Always include the document
|
|
702
|
+
// path in the ancestor calculation even if it doesn't exist on disk,
|
|
703
|
+
// since we always write it to the shadow workspace.
|
|
704
|
+
const allForBase = Array.from(reachableFiles);
|
|
705
|
+
const baseDir = findCommonAncestor(allForBase);
|
|
706
|
+
const allPaths = allForBase.filter((f) => fs.existsSync(f));
|
|
547
707
|
// Create directory structure and symlink/copy files
|
|
548
|
-
for (const filePath of
|
|
549
|
-
|
|
550
|
-
continue;
|
|
551
|
-
const relativePath = path.relative(documentDir, filePath);
|
|
708
|
+
for (const filePath of allPaths) {
|
|
709
|
+
const relativePath = path.relative(baseDir, filePath);
|
|
552
710
|
const shadowPath = path.join(shadowDir, relativePath);
|
|
553
711
|
const shadowFileDir = path.dirname(shadowPath);
|
|
554
712
|
// Create directory structure
|
|
@@ -565,7 +723,10 @@ async function createShadowWorkspace(documentPath) {
|
|
|
565
723
|
await fs.promises.symlink(filePath, shadowPath);
|
|
566
724
|
}
|
|
567
725
|
}
|
|
568
|
-
const targetFile = path.join(shadowDir,
|
|
726
|
+
const targetFile = path.join(shadowDir, path.relative(baseDir, normalizedDocPath));
|
|
727
|
+
// Ensure target directory exists (document may not be on disk,
|
|
728
|
+
// so the symlink/copy loop above may not have created it)
|
|
729
|
+
await fs.promises.mkdir(path.dirname(targetFile), { recursive: true });
|
|
569
730
|
return {
|
|
570
731
|
shadowDir,
|
|
571
732
|
targetFile,
|
|
@@ -580,117 +741,28 @@ async function createShadowWorkspace(documentPath) {
|
|
|
580
741
|
throw error;
|
|
581
742
|
}
|
|
582
743
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
throw retryError;
|
|
602
|
-
}
|
|
603
|
-
await delay(150);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
throw error;
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
// Send command to UmpleSync.jar socket server and receive the output
|
|
610
|
-
function connectAndSend(commandLine) {
|
|
611
|
-
return new Promise((resolve, reject) => {
|
|
612
|
-
const socket = new net.Socket();
|
|
613
|
-
const chunks = [];
|
|
614
|
-
let settled = false;
|
|
615
|
-
const finishSuccess = (raw) => {
|
|
616
|
-
if (settled) {
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
619
|
-
settled = true;
|
|
620
|
-
const { stdout, stderr } = splitUmpleSyncOutput(raw);
|
|
621
|
-
resolve({ stdout, stderr });
|
|
622
|
-
};
|
|
623
|
-
const finishError = (err) => {
|
|
624
|
-
if (settled) {
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
settled = true;
|
|
628
|
-
socket.destroy();
|
|
629
|
-
reject(err);
|
|
630
|
-
};
|
|
631
|
-
socket.setEncoding("utf8");
|
|
632
|
-
socket.setTimeout(umpleSyncTimeoutMs);
|
|
633
|
-
socket.on("data", (chunk) => {
|
|
634
|
-
if (typeof chunk === "string") {
|
|
635
|
-
chunks.push(chunk);
|
|
636
|
-
}
|
|
637
|
-
else {
|
|
638
|
-
chunks.push(chunk.toString("utf8"));
|
|
744
|
+
/**
|
|
745
|
+
* Find the deepest common ancestor directory of a list of file paths.
|
|
746
|
+
* Used to ensure all shadow workspace relative paths stay positive (no "../").
|
|
747
|
+
*/
|
|
748
|
+
function findCommonAncestor(filePaths) {
|
|
749
|
+
if (filePaths.length === 0) {
|
|
750
|
+
return os.tmpdir();
|
|
751
|
+
}
|
|
752
|
+
const dirs = filePaths.map((f) => path.dirname(path.normalize(f)));
|
|
753
|
+
const segments = dirs[0].split(path.sep);
|
|
754
|
+
let commonLength = segments.length;
|
|
755
|
+
for (let i = 1; i < dirs.length; i++) {
|
|
756
|
+
const parts = dirs[i].split(path.sep);
|
|
757
|
+
commonLength = Math.min(commonLength, parts.length);
|
|
758
|
+
for (let j = 0; j < commonLength; j++) {
|
|
759
|
+
if (segments[j] !== parts[j]) {
|
|
760
|
+
commonLength = j;
|
|
761
|
+
break;
|
|
639
762
|
}
|
|
640
|
-
});
|
|
641
|
-
socket.on("end", () => {
|
|
642
|
-
finishSuccess(chunks.join(""));
|
|
643
|
-
});
|
|
644
|
-
socket.on("error", (err) => {
|
|
645
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
646
|
-
finishError(error);
|
|
647
|
-
});
|
|
648
|
-
socket.on("timeout", () => {
|
|
649
|
-
finishError(new Error("umplesync socket timeout"));
|
|
650
|
-
});
|
|
651
|
-
socket.connect(umpleSyncPort, umpleSyncHost, () => {
|
|
652
|
-
socket.end(commandLine);
|
|
653
|
-
});
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
async function startUmpleSyncServer(jarPath) {
|
|
657
|
-
if (serverProcess) {
|
|
658
|
-
return true;
|
|
659
|
-
}
|
|
660
|
-
return new Promise((resolve) => {
|
|
661
|
-
const child = (0, child_process_1.spawn)("java", ["-jar", jarPath, "-server", String(umpleSyncPort)], {
|
|
662
|
-
detached: true,
|
|
663
|
-
stdio: "ignore",
|
|
664
|
-
});
|
|
665
|
-
child.on("error", (err) => {
|
|
666
|
-
connection.console.error(`Failed to start umplesync: ${String(err)}`);
|
|
667
|
-
resolve(false);
|
|
668
|
-
});
|
|
669
|
-
child.unref();
|
|
670
|
-
serverProcess = child;
|
|
671
|
-
resolve(true);
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
function splitUmpleSyncOutput(raw) {
|
|
675
|
-
let stdout = "";
|
|
676
|
-
let stderr = "";
|
|
677
|
-
let index = 0;
|
|
678
|
-
while (index < raw.length) {
|
|
679
|
-
const start = raw.indexOf("ERROR!!", index);
|
|
680
|
-
if (start === -1) {
|
|
681
|
-
stdout += raw.slice(index);
|
|
682
|
-
break;
|
|
683
763
|
}
|
|
684
|
-
stdout += raw.slice(index, start);
|
|
685
|
-
const end = raw.indexOf("!!ERROR", start + 7);
|
|
686
|
-
if (end === -1) {
|
|
687
|
-
stderr += raw.slice(start + 7);
|
|
688
|
-
break;
|
|
689
|
-
}
|
|
690
|
-
stderr += raw.slice(start + 7, end);
|
|
691
|
-
index = end + 7;
|
|
692
764
|
}
|
|
693
|
-
return
|
|
765
|
+
return segments.slice(0, commonLength).join(path.sep) || path.sep;
|
|
694
766
|
}
|
|
695
767
|
/**
|
|
696
768
|
* Collect all file paths reachable via transitive use statements.
|
|
@@ -737,20 +809,6 @@ function collectReachableFilesRecursive(filePath, content, documentDir, visited)
|
|
|
737
809
|
}
|
|
738
810
|
}
|
|
739
811
|
}
|
|
740
|
-
function isConnectionError(error) {
|
|
741
|
-
if (!error || typeof error !== "object") {
|
|
742
|
-
return false;
|
|
743
|
-
}
|
|
744
|
-
const maybeError = error;
|
|
745
|
-
return (maybeError.code === "ECONNREFUSED" ||
|
|
746
|
-
maybeError.code === "ECONNRESET" ||
|
|
747
|
-
maybeError.code === "EPIPE" ||
|
|
748
|
-
maybeError.code === "ETIMEDOUT" ||
|
|
749
|
-
(maybeError.message || "").includes("umplesync socket timeout"));
|
|
750
|
-
}
|
|
751
|
-
function delay(ms) {
|
|
752
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
753
|
-
}
|
|
754
812
|
function parseUmpleDiagnostics(stderr, stdout, document, tempFilename, documentDir) {
|
|
755
813
|
const jsonDiagnostics = parseUmpleJsonDiagnostics(stderr, document, tempFilename, documentDir);
|
|
756
814
|
if (jsonDiagnostics.length === 0 && stdout.includes("Success")) {
|
|
@@ -961,39 +1019,6 @@ function getDocumentFilePath(document) {
|
|
|
961
1019
|
return null;
|
|
962
1020
|
}
|
|
963
1021
|
}
|
|
964
|
-
/**
|
|
965
|
-
* Map a SymbolKind to the appropriate LSP CompletionItemKind.
|
|
966
|
-
*/
|
|
967
|
-
function symbolKindToCompletionKind(kind) {
|
|
968
|
-
switch (kind) {
|
|
969
|
-
case "class":
|
|
970
|
-
return node_1.CompletionItemKind.Class;
|
|
971
|
-
case "interface":
|
|
972
|
-
return node_1.CompletionItemKind.Interface;
|
|
973
|
-
case "trait":
|
|
974
|
-
return node_1.CompletionItemKind.Class;
|
|
975
|
-
case "enum":
|
|
976
|
-
return node_1.CompletionItemKind.Enum;
|
|
977
|
-
case "state":
|
|
978
|
-
return node_1.CompletionItemKind.EnumMember;
|
|
979
|
-
case "statemachine":
|
|
980
|
-
return node_1.CompletionItemKind.Enum;
|
|
981
|
-
case "attribute":
|
|
982
|
-
return node_1.CompletionItemKind.Field;
|
|
983
|
-
case "method":
|
|
984
|
-
return node_1.CompletionItemKind.Method;
|
|
985
|
-
case "association":
|
|
986
|
-
return node_1.CompletionItemKind.Reference;
|
|
987
|
-
case "mixset":
|
|
988
|
-
return node_1.CompletionItemKind.Module;
|
|
989
|
-
case "requirement":
|
|
990
|
-
return node_1.CompletionItemKind.Reference;
|
|
991
|
-
case "template":
|
|
992
|
-
return node_1.CompletionItemKind.Property;
|
|
993
|
-
default:
|
|
994
|
-
return node_1.CompletionItemKind.Text;
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
1022
|
function getUseFileCompletions(document, prefix, line, character) {
|
|
998
1023
|
const docDir = getDocumentDirectory(document);
|
|
999
1024
|
if (!docDir) {
|
|
@@ -1065,8 +1090,5 @@ function extractJson(text) {
|
|
|
1065
1090
|
}
|
|
1066
1091
|
return text.slice(start, end + 1);
|
|
1067
1092
|
}
|
|
1068
|
-
function formatUmpleArg(filePath) {
|
|
1069
|
-
return JSON.stringify(filePath);
|
|
1070
|
-
}
|
|
1071
1093
|
connection.listen();
|
|
1072
1094
|
//# sourceMappingURL=server.js.map
|