@rohal12/spindle-lsp 0.6.0 → 0.7.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/dist/bin.js +402 -20
- package/dist/bin.js.map +3 -3
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -210,6 +210,10 @@ var init_passage_index = __esm({
|
|
|
210
210
|
getStoryVariables() {
|
|
211
211
|
return this.getPassage("StoryVariables");
|
|
212
212
|
}
|
|
213
|
+
/** Get the StoryTransients passage, if any. */
|
|
214
|
+
getStoryTransients() {
|
|
215
|
+
return this.getPassage("StoryTransients");
|
|
216
|
+
}
|
|
213
217
|
/** Get the StoryInit passage, if any. */
|
|
214
218
|
getStoryInit() {
|
|
215
219
|
return this.getPassage("StoryInit");
|
|
@@ -412,14 +416,16 @@ var init_macro_registry = __esm({
|
|
|
412
416
|
});
|
|
413
417
|
|
|
414
418
|
// src/core/workspace/variable-tracker.ts
|
|
415
|
-
var varRefRegex, EXCLUDED_PASSAGES, CLEAN_PATTERNS, VariableTracker;
|
|
419
|
+
var varRefRegex, transientRefRegex, EXCLUDED_PASSAGES, CLEAN_PATTERNS, VariableTracker;
|
|
416
420
|
var init_variable_tracker = __esm({
|
|
417
421
|
"src/core/workspace/variable-tracker.ts"() {
|
|
418
422
|
"use strict";
|
|
419
423
|
init_passage_parser();
|
|
420
424
|
varRefRegex = /\$([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
425
|
+
transientRefRegex = /(?<!\w)%([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
421
426
|
EXCLUDED_PASSAGES = /* @__PURE__ */ new Set([
|
|
422
427
|
"StoryVariables",
|
|
428
|
+
"StoryTransients",
|
|
423
429
|
"StoryInit",
|
|
424
430
|
"StoryData",
|
|
425
431
|
"StoryScript",
|
|
@@ -442,8 +448,12 @@ var init_variable_tracker = __esm({
|
|
|
442
448
|
VariableTracker = class {
|
|
443
449
|
declared = /* @__PURE__ */ new Map();
|
|
444
450
|
_hasStoryVariables = false;
|
|
451
|
+
declaredTransient = /* @__PURE__ */ new Map();
|
|
452
|
+
_hasStoryTransients = false;
|
|
445
453
|
/** Per-URI list of variable usages. */
|
|
446
454
|
usagesByUri = /* @__PURE__ */ new Map();
|
|
455
|
+
/** Per-URI list of transient variable usages. */
|
|
456
|
+
transientUsagesByUri = /* @__PURE__ */ new Map();
|
|
447
457
|
/**
|
|
448
458
|
* Parse the StoryVariables passage content for declarations.
|
|
449
459
|
* Each line like `$name = value` becomes a declaration.
|
|
@@ -474,14 +484,46 @@ var init_variable_tracker = __esm({
|
|
|
474
484
|
this.declared.set(name, decl);
|
|
475
485
|
}
|
|
476
486
|
}
|
|
487
|
+
/**
|
|
488
|
+
* Parse the StoryTransients passage content for declarations.
|
|
489
|
+
* Each line like `%name = value` becomes a declaration.
|
|
490
|
+
*/
|
|
491
|
+
parseStoryTransients(content) {
|
|
492
|
+
this.declaredTransient.clear();
|
|
493
|
+
this._hasStoryTransients = true;
|
|
494
|
+
const lines = content.split("\n");
|
|
495
|
+
for (const line of lines) {
|
|
496
|
+
const trimmed = line.trim();
|
|
497
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("<!--")) continue;
|
|
498
|
+
const match = /^%([A-Za-z_$][\w$]*)\s*=\s*(.*)$/.exec(trimmed);
|
|
499
|
+
if (!match) continue;
|
|
500
|
+
const name = match[1];
|
|
501
|
+
const expr = match[2].trim();
|
|
502
|
+
const decl = { name, sigil: "%" };
|
|
503
|
+
if (expr.startsWith("{")) {
|
|
504
|
+
const fieldRegex = /(\w+)\s*:/g;
|
|
505
|
+
let fieldMatch;
|
|
506
|
+
const fields = [];
|
|
507
|
+
while ((fieldMatch = fieldRegex.exec(expr)) !== null) {
|
|
508
|
+
fields.push(fieldMatch[1]);
|
|
509
|
+
}
|
|
510
|
+
if (fields.length > 0) {
|
|
511
|
+
decl.fields = fields;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
this.declaredTransient.set(name, decl);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
477
517
|
/**
|
|
478
518
|
* Scan a document for variable usages.
|
|
479
519
|
* Identifies passages in the document and scans non-special ones.
|
|
480
520
|
*/
|
|
481
521
|
scanDocument(uri, text, _macros) {
|
|
482
522
|
this.usagesByUri.delete(uri);
|
|
523
|
+
this.transientUsagesByUri.delete(uri);
|
|
483
524
|
const lines = text.split("\n");
|
|
484
525
|
const usages = [];
|
|
526
|
+
const transientUsages = [];
|
|
485
527
|
const passageBoundaries = [];
|
|
486
528
|
for (let i = 0; i < lines.length; i++) {
|
|
487
529
|
const header = parsePassageHeader(lines[i], i);
|
|
@@ -523,10 +565,32 @@ var init_variable_tracker = __esm({
|
|
|
523
565
|
};
|
|
524
566
|
usages.push({ uri, baseName, fullName, range });
|
|
525
567
|
}
|
|
568
|
+
const tre = new RegExp(transientRefRegex.source, "g");
|
|
569
|
+
let tmatch;
|
|
570
|
+
while ((tmatch = tre.exec(cleaned)) !== null) {
|
|
571
|
+
const fullName = tmatch[1];
|
|
572
|
+
const baseName = fullName.split(".")[0];
|
|
573
|
+
const charOffset = tmatch.index;
|
|
574
|
+
let localLine = 0;
|
|
575
|
+
for (let i = 0; i < lineOffsets.length; i++) {
|
|
576
|
+
if (lineOffsets[i] > charOffset) break;
|
|
577
|
+
localLine = i;
|
|
578
|
+
}
|
|
579
|
+
const character = charOffset - lineOffsets[localLine];
|
|
580
|
+
const absoluteLine = contentStartLine + localLine;
|
|
581
|
+
const range = {
|
|
582
|
+
start: { line: absoluteLine, character },
|
|
583
|
+
end: { line: absoluteLine, character: character + tmatch[0].length }
|
|
584
|
+
};
|
|
585
|
+
transientUsages.push({ uri, baseName, fullName, range });
|
|
586
|
+
}
|
|
526
587
|
}
|
|
527
588
|
if (usages.length > 0) {
|
|
528
589
|
this.usagesByUri.set(uri, usages);
|
|
529
590
|
}
|
|
591
|
+
if (transientUsages.length > 0) {
|
|
592
|
+
this.transientUsagesByUri.set(uri, transientUsages);
|
|
593
|
+
}
|
|
530
594
|
}
|
|
531
595
|
/** Get all declared variables. */
|
|
532
596
|
getDeclared() {
|
|
@@ -562,6 +626,40 @@ var init_variable_tracker = __esm({
|
|
|
562
626
|
hasStoryVariables() {
|
|
563
627
|
return this._hasStoryVariables;
|
|
564
628
|
}
|
|
629
|
+
/** Get all declared transient variables. */
|
|
630
|
+
getDeclaredTransient() {
|
|
631
|
+
return this.declaredTransient;
|
|
632
|
+
}
|
|
633
|
+
/** Get all usages of a transient variable by base name. */
|
|
634
|
+
getTransientUsages(name) {
|
|
635
|
+
const results = [];
|
|
636
|
+
for (const usages of this.transientUsagesByUri.values()) {
|
|
637
|
+
for (const u of usages) {
|
|
638
|
+
if (u.baseName === name) {
|
|
639
|
+
results.push({ uri: u.uri, range: u.range });
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return results;
|
|
644
|
+
}
|
|
645
|
+
/** Get undeclared transient variable usages in a specific document. */
|
|
646
|
+
getUndeclaredTransient(uri) {
|
|
647
|
+
const usages = this.transientUsagesByUri.get(uri);
|
|
648
|
+
if (!usages) return [];
|
|
649
|
+
const results = [];
|
|
650
|
+
const seen = /* @__PURE__ */ new Set();
|
|
651
|
+
for (const u of usages) {
|
|
652
|
+
if (!this.declaredTransient.has(u.baseName) && !seen.has(u.baseName)) {
|
|
653
|
+
seen.add(u.baseName);
|
|
654
|
+
results.push({ name: u.baseName, range: u.range });
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return results;
|
|
658
|
+
}
|
|
659
|
+
/** Whether a StoryTransients passage has been parsed. */
|
|
660
|
+
hasStoryTransients() {
|
|
661
|
+
return this._hasStoryTransients;
|
|
662
|
+
}
|
|
565
663
|
};
|
|
566
664
|
}
|
|
567
665
|
});
|
|
@@ -721,7 +819,7 @@ var variableInterpolationRegex, macroRegex;
|
|
|
721
819
|
var init_macro_parser = __esm({
|
|
722
820
|
"src/core/parsing/macro-parser.ts"() {
|
|
723
821
|
"use strict";
|
|
724
|
-
variableInterpolationRegex = /(?<!\\)\{([$_
|
|
822
|
+
variableInterpolationRegex = /(?<!\\)\{([$_@%][A-Za-z_$][\w$.]*)\}/g;
|
|
725
823
|
macroRegex = /(?<!\\)\{(\/)?(?:((?:[#.][a-zA-Z][\w-]*\s*)*)([A-Za-z][\w-]*))(?:\s+((?:(?:`(?:\\.|[^`\\])*?`)|(?:"(?:\\.|[^"\\])*?")|(?:'(?:\\.|[^'\\])*?')|(?:\[(?:[<>]?[Ii][Mm][Gg])?\[[^\r\n]*?\]\]+)|[^}]|(?:}))*?))?\}/gmi;
|
|
726
824
|
}
|
|
727
825
|
});
|
|
@@ -799,12 +897,12 @@ var init_macro_supplements = __esm({
|
|
|
799
897
|
},
|
|
800
898
|
set: {
|
|
801
899
|
name: "set",
|
|
802
|
-
description: "Sets variables based on the given expression.\n\nUsage: `{set $var to value}`\n\nSupports `$var` (story), `_var` (temporary),
|
|
900
|
+
description: "Sets variables based on the given expression.\n\nUsage: `{set $var to value}`\n\nSupports `$var` (story), `_var` (temporary), `@var` (local), and `%var` (transient) variables.",
|
|
803
901
|
parameters: ["...text"]
|
|
804
902
|
},
|
|
805
903
|
unset: {
|
|
806
904
|
name: "unset",
|
|
807
|
-
description: "Unsets one or more variables.\n\nUsage: `{unset $var}`",
|
|
905
|
+
description: "Unsets one or more variables.\n\nUsage: `{unset $var}`\n\nSupports `$var` (story), `_var` (temporary), `@var` (local), and `%var` (transient) variables.",
|
|
808
906
|
parameters: ["...text"]
|
|
809
907
|
},
|
|
810
908
|
computed: {
|
|
@@ -1143,6 +1241,23 @@ var init_workspace_model = __esm({
|
|
|
1143
1241
|
this.variables.parseStoryVariables(content);
|
|
1144
1242
|
}
|
|
1145
1243
|
}
|
|
1244
|
+
const storyTransients = this.passages.getStoryTransients();
|
|
1245
|
+
if (storyTransients) {
|
|
1246
|
+
const text = this.documents.getText(storyTransients.uri);
|
|
1247
|
+
if (text) {
|
|
1248
|
+
const lines = text.split("\n");
|
|
1249
|
+
const contentStart = storyTransients.headerEnd.end.line + 1;
|
|
1250
|
+
let contentEnd = lines.length;
|
|
1251
|
+
for (let i = contentStart; i < lines.length; i++) {
|
|
1252
|
+
if (/^::\s+/.test(lines[i])) {
|
|
1253
|
+
contentEnd = i;
|
|
1254
|
+
break;
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
const content = lines.slice(contentStart, contentEnd).join("\n");
|
|
1258
|
+
this.variables.parseStoryTransients(content);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1146
1261
|
for (const uri of this.documents.getUris()) {
|
|
1147
1262
|
const text = this.documents.getText(uri);
|
|
1148
1263
|
if (text) {
|
|
@@ -1190,6 +1305,7 @@ var init_diagnostic_codes = __esm({
|
|
|
1190
1305
|
UndeclaredVariable: "SP200",
|
|
1191
1306
|
UndeclaredField: "SP201",
|
|
1192
1307
|
NoStoryVariables: "SP202",
|
|
1308
|
+
UndeclaredTransient: "SP203",
|
|
1193
1309
|
BrokenPassageLink: "SP300",
|
|
1194
1310
|
WidgetArgCountMismatch: "SP301",
|
|
1195
1311
|
DeadEndPassage: "SP400",
|
|
@@ -1211,6 +1327,7 @@ var init_diagnostic_codes = __esm({
|
|
|
1211
1327
|
SP200: "warning",
|
|
1212
1328
|
SP201: "warning",
|
|
1213
1329
|
SP202: "info",
|
|
1330
|
+
SP203: "warning",
|
|
1214
1331
|
SP300: "warning",
|
|
1215
1332
|
SP301: "warning",
|
|
1216
1333
|
SP400: "hint",
|
|
@@ -1411,7 +1528,7 @@ var varTestRegexp, EOF, Lexer, notSpaceRegex, spaceRegex;
|
|
|
1411
1528
|
var init_argument_lexer = __esm({
|
|
1412
1529
|
"src/core/parsing/argument-lexer.ts"() {
|
|
1413
1530
|
"use strict";
|
|
1414
|
-
varTestRegexp = /^[$_
|
|
1531
|
+
varTestRegexp = /^[$_@%][$A-Z_a-z][$0-9A-Z_.a-z]*/;
|
|
1415
1532
|
EOF = -1;
|
|
1416
1533
|
Lexer = class {
|
|
1417
1534
|
source;
|
|
@@ -2292,7 +2409,7 @@ function validateArguments(macros, workspace, passageNames, diagnostics) {
|
|
|
2292
2409
|
}
|
|
2293
2410
|
function validateVariables(uri, workspace, diagnostics) {
|
|
2294
2411
|
if (!workspace.variables.hasStoryVariables()) {
|
|
2295
|
-
const
|
|
2412
|
+
const undeclared = workspace.variables.getUndeclared(uri);
|
|
2296
2413
|
const text = workspace.documents.getText(uri);
|
|
2297
2414
|
if (text && /\$[A-Za-z_$]/.test(text)) {
|
|
2298
2415
|
const passages = workspace.passages.getPassagesInDocument(uri);
|
|
@@ -2308,15 +2425,25 @@ function validateVariables(uri, workspace, diagnostics) {
|
|
|
2308
2425
|
));
|
|
2309
2426
|
}
|
|
2310
2427
|
}
|
|
2311
|
-
|
|
2428
|
+
} else {
|
|
2429
|
+
const undeclared = workspace.variables.getUndeclared(uri);
|
|
2430
|
+
for (const u of undeclared) {
|
|
2431
|
+
diagnostics.push(makeDiag(
|
|
2432
|
+
u.range,
|
|
2433
|
+
DiagnosticCode.UndeclaredVariable,
|
|
2434
|
+
`Variable '$${u.name}' is not declared in StoryVariables`
|
|
2435
|
+
));
|
|
2436
|
+
}
|
|
2312
2437
|
}
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2438
|
+
if (workspace.variables.hasStoryTransients()) {
|
|
2439
|
+
const undeclaredTransient = workspace.variables.getUndeclaredTransient(uri);
|
|
2440
|
+
for (const u of undeclaredTransient) {
|
|
2441
|
+
diagnostics.push(makeDiag(
|
|
2442
|
+
u.range,
|
|
2443
|
+
DiagnosticCode.UndeclaredTransient,
|
|
2444
|
+
`Transient variable '%${u.name}' is not declared in StoryTransients`
|
|
2445
|
+
));
|
|
2446
|
+
}
|
|
2320
2447
|
}
|
|
2321
2448
|
}
|
|
2322
2449
|
function validateLinks(text, passages, passageNames, diagnostics) {
|
|
@@ -3216,7 +3343,10 @@ async function formatDocument(text, options) {
|
|
|
3216
3343
|
}
|
|
3217
3344
|
continue;
|
|
3218
3345
|
}
|
|
3219
|
-
|
|
3346
|
+
let indented = indentMacros(region.lines, isBlock, isDedenting);
|
|
3347
|
+
if (options?.maxLineLength) {
|
|
3348
|
+
indented = wrapLines(indented, options.maxLineLength);
|
|
3349
|
+
}
|
|
3220
3350
|
resultLines.push(...indented);
|
|
3221
3351
|
}
|
|
3222
3352
|
}
|
|
@@ -3263,6 +3393,65 @@ function indentMacros(lines, isBlock, isDedenting) {
|
|
|
3263
3393
|
async function formatRange(text, _range, options) {
|
|
3264
3394
|
return formatDocument(text, options);
|
|
3265
3395
|
}
|
|
3396
|
+
function wrapLines(lines, maxLen) {
|
|
3397
|
+
const result = [];
|
|
3398
|
+
for (const line of lines) {
|
|
3399
|
+
if (line.length <= maxLen) {
|
|
3400
|
+
result.push(line);
|
|
3401
|
+
continue;
|
|
3402
|
+
}
|
|
3403
|
+
const trimmed = line.trim();
|
|
3404
|
+
if (trimmed === "" || /^::/.test(trimmed) || /^\{[\/#.]/.test(trimmed) || // closing/opening-only macro lines
|
|
3405
|
+
/^\[\[/.test(trimmed) || // links
|
|
3406
|
+
/^\{choice\b/.test(trimmed) || // choice macros
|
|
3407
|
+
/^\{choices\b/.test(trimmed)) {
|
|
3408
|
+
result.push(line);
|
|
3409
|
+
continue;
|
|
3410
|
+
}
|
|
3411
|
+
const indentMatch = line.match(/^(\s*)/);
|
|
3412
|
+
const indent = indentMatch ? indentMatch[1] : "";
|
|
3413
|
+
const wrapped = wordWrap(trimmed, maxLen, indent);
|
|
3414
|
+
result.push(...wrapped);
|
|
3415
|
+
}
|
|
3416
|
+
return result;
|
|
3417
|
+
}
|
|
3418
|
+
function wordWrap(text, maxLen, indent) {
|
|
3419
|
+
const lines = [];
|
|
3420
|
+
let remaining = text;
|
|
3421
|
+
while (remaining.length > 0) {
|
|
3422
|
+
const currentMax = lines.length === 0 ? maxLen - indent.length : maxLen - indent.length;
|
|
3423
|
+
if (remaining.length <= currentMax) {
|
|
3424
|
+
lines.push(indent + remaining);
|
|
3425
|
+
break;
|
|
3426
|
+
}
|
|
3427
|
+
let breakIdx = -1;
|
|
3428
|
+
let inMacro = 0;
|
|
3429
|
+
for (let i = 0; i < remaining.length && i <= currentMax; i++) {
|
|
3430
|
+
if (remaining[i] === "{") inMacro++;
|
|
3431
|
+
else if (remaining[i] === "}") inMacro = Math.max(0, inMacro - 1);
|
|
3432
|
+
else if (remaining[i] === " " && inMacro === 0 && i > 0) {
|
|
3433
|
+
breakIdx = i;
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
if (breakIdx === -1) {
|
|
3437
|
+
for (let i = currentMax + 1; i < remaining.length; i++) {
|
|
3438
|
+
if (remaining[i] === "{") inMacro++;
|
|
3439
|
+
else if (remaining[i] === "}") inMacro = Math.max(0, inMacro - 1);
|
|
3440
|
+
else if (remaining[i] === " " && inMacro === 0) {
|
|
3441
|
+
breakIdx = i;
|
|
3442
|
+
break;
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
if (breakIdx === -1) {
|
|
3447
|
+
lines.push(indent + remaining);
|
|
3448
|
+
break;
|
|
3449
|
+
}
|
|
3450
|
+
lines.push(indent + remaining.substring(0, breakIdx));
|
|
3451
|
+
remaining = remaining.substring(breakIdx + 1);
|
|
3452
|
+
}
|
|
3453
|
+
return lines;
|
|
3454
|
+
}
|
|
3266
3455
|
function normalizePassageHeader(line) {
|
|
3267
3456
|
const headerMatch = line.match(/^::\s+(.*)/);
|
|
3268
3457
|
if (!headerMatch) return line;
|
|
@@ -3352,13 +3541,28 @@ import { glob as glob2 } from "glob";
|
|
|
3352
3541
|
function parseArgs2(args2) {
|
|
3353
3542
|
const options = {
|
|
3354
3543
|
check: false,
|
|
3544
|
+
maxLineLength: null,
|
|
3355
3545
|
patterns: []
|
|
3356
3546
|
};
|
|
3357
|
-
|
|
3547
|
+
let i = 0;
|
|
3548
|
+
while (i < args2.length) {
|
|
3549
|
+
const arg = args2[i];
|
|
3358
3550
|
if (arg === "--check") {
|
|
3359
3551
|
options.check = true;
|
|
3552
|
+
i++;
|
|
3553
|
+
} else if (arg === "--max-line-length" && i + 1 < args2.length) {
|
|
3554
|
+
const n = parseInt(args2[i + 1], 10);
|
|
3555
|
+
if (!isNaN(n) && n > 0) options.maxLineLength = n;
|
|
3556
|
+
i += 2;
|
|
3557
|
+
} else if (arg.startsWith("--max-line-length=")) {
|
|
3558
|
+
const n = parseInt(arg.slice("--max-line-length=".length), 10);
|
|
3559
|
+
if (!isNaN(n) && n > 0) options.maxLineLength = n;
|
|
3560
|
+
i++;
|
|
3360
3561
|
} else if (!arg.startsWith("--")) {
|
|
3361
3562
|
options.patterns.push(arg);
|
|
3563
|
+
i++;
|
|
3564
|
+
} else {
|
|
3565
|
+
i++;
|
|
3362
3566
|
}
|
|
3363
3567
|
}
|
|
3364
3568
|
if (options.patterns.length === 0) {
|
|
@@ -3383,11 +3587,15 @@ async function runFormat(args2) {
|
|
|
3383
3587
|
console.log("No files found");
|
|
3384
3588
|
return 0;
|
|
3385
3589
|
}
|
|
3590
|
+
const formatOpts = {};
|
|
3591
|
+
if (options.maxLineLength !== null) {
|
|
3592
|
+
formatOpts.maxLineLength = options.maxLineLength;
|
|
3593
|
+
}
|
|
3386
3594
|
const unformatted = [];
|
|
3387
3595
|
for (const filePath of uniqueFiles) {
|
|
3388
3596
|
try {
|
|
3389
3597
|
const text = readFileSync5(filePath, "utf-8");
|
|
3390
|
-
const formatted = await formatDocument(text);
|
|
3598
|
+
const formatted = await formatDocument(text, formatOpts);
|
|
3391
3599
|
if (formatted !== text) {
|
|
3392
3600
|
if (options.check) {
|
|
3393
3601
|
const relative = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
|
|
@@ -3663,6 +3871,10 @@ function getCompletions(uri, position, triggerChar, workspace) {
|
|
|
3663
3871
|
if (/\{\/[A-Za-z\w-]*$/.test(lineText)) {
|
|
3664
3872
|
return getClosingMacroCompletions(text, position, workspace);
|
|
3665
3873
|
}
|
|
3874
|
+
const transientDotPathMatch = /%([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)?$/.exec(lineText);
|
|
3875
|
+
if (transientDotPathMatch) {
|
|
3876
|
+
return getTransientDotPathCompletions(transientDotPathMatch[1], workspace);
|
|
3877
|
+
}
|
|
3666
3878
|
const dotPathMatch = /\$([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)?$/.exec(lineText);
|
|
3667
3879
|
if (dotPathMatch) {
|
|
3668
3880
|
return getDotPathCompletions(dotPathMatch[1], workspace);
|
|
@@ -3676,6 +3888,9 @@ function getCompletions(uri, position, triggerChar, workspace) {
|
|
|
3676
3888
|
if (/@[A-Za-z_$]?[\w$]*$/.test(lineText)) {
|
|
3677
3889
|
return getLocalVariableCompletions(text);
|
|
3678
3890
|
}
|
|
3891
|
+
if (/%[A-Za-z_$]?[\w$]*$/.test(lineText) && !/%[A-Za-z_$][\w$]*\./.test(lineText)) {
|
|
3892
|
+
return getTransientVariableCompletions(workspace);
|
|
3893
|
+
}
|
|
3679
3894
|
if (/\[\[[^\]]*$/.test(lineText)) {
|
|
3680
3895
|
return getPassageNameCompletions(workspace);
|
|
3681
3896
|
}
|
|
@@ -3803,6 +4018,33 @@ function getDotPathCompletions(varName, workspace) {
|
|
|
3803
4018
|
detail: `field of $${varName}`
|
|
3804
4019
|
}));
|
|
3805
4020
|
}
|
|
4021
|
+
function getTransientVariableCompletions(workspace) {
|
|
4022
|
+
const declared = workspace.variables.getDeclaredTransient();
|
|
4023
|
+
if (declared.size === 0) return [];
|
|
4024
|
+
const items = [];
|
|
4025
|
+
for (const [name, decl] of declared) {
|
|
4026
|
+
items.push({
|
|
4027
|
+
label: `%${name}`,
|
|
4028
|
+
kind: 6,
|
|
4029
|
+
// CompletionItemKind.Variable
|
|
4030
|
+
detail: "transient variable",
|
|
4031
|
+
insertText: name,
|
|
4032
|
+
documentation: decl.fields && decl.fields.length > 0 ? `Fields: ${decl.fields.join(", ")}` : void 0
|
|
4033
|
+
});
|
|
4034
|
+
}
|
|
4035
|
+
return items;
|
|
4036
|
+
}
|
|
4037
|
+
function getTransientDotPathCompletions(varName, workspace) {
|
|
4038
|
+
const declared = workspace.variables.getDeclaredTransient();
|
|
4039
|
+
const decl = declared.get(varName);
|
|
4040
|
+
if (!decl || !decl.fields || decl.fields.length === 0) return [];
|
|
4041
|
+
return decl.fields.map((field) => ({
|
|
4042
|
+
label: field,
|
|
4043
|
+
kind: 5,
|
|
4044
|
+
// CompletionItemKind.Field
|
|
4045
|
+
detail: `field of %${varName}`
|
|
4046
|
+
}));
|
|
4047
|
+
}
|
|
3806
4048
|
function getPassageNameCompletions(workspace) {
|
|
3807
4049
|
const passages = workspace.passages.getAllPassages();
|
|
3808
4050
|
if (passages.length === 0) return [];
|
|
@@ -3822,7 +4064,7 @@ var init_completions = __esm({
|
|
|
3822
4064
|
id: "completions",
|
|
3823
4065
|
capabilities: {
|
|
3824
4066
|
completionProvider: {
|
|
3825
|
-
triggerCharacters: ["{", "$", "_", "@", "[", "."]
|
|
4067
|
+
triggerCharacters: ["{", "$", "_", "@", "%", "[", "."]
|
|
3826
4068
|
}
|
|
3827
4069
|
},
|
|
3828
4070
|
initialize(ctx) {
|
|
@@ -3946,6 +4188,28 @@ Fields: ${decl.fields.map((f) => `\`${f}\``).join(", ")}` : "";
|
|
|
3946
4188
|
}
|
|
3947
4189
|
}
|
|
3948
4190
|
}
|
|
4191
|
+
{
|
|
4192
|
+
const re = /(?<!\w)%([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
4193
|
+
let match;
|
|
4194
|
+
while ((match = re.exec(line)) !== null) {
|
|
4195
|
+
const start = match.index;
|
|
4196
|
+
const end = start + match[0].length;
|
|
4197
|
+
if (position.character >= start && position.character <= end) {
|
|
4198
|
+
const baseName = match[1].split(".")[0];
|
|
4199
|
+
const decl = workspace.variables.getDeclaredTransient().get(baseName);
|
|
4200
|
+
const typeInfo = decl?.fields && decl.fields.length > 0 ? `
|
|
4201
|
+
|
|
4202
|
+
Fields: ${decl.fields.map((f) => `\`${f}\``).join(", ")}` : "";
|
|
4203
|
+
return {
|
|
4204
|
+
contents: `**Transient variable** \`%${match[1]}\`${typeInfo}`,
|
|
4205
|
+
range: {
|
|
4206
|
+
start: { line: position.line, character: start },
|
|
4207
|
+
end: { line: position.line, character: end }
|
|
4208
|
+
}
|
|
4209
|
+
};
|
|
4210
|
+
}
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
3949
4213
|
return null;
|
|
3950
4214
|
}
|
|
3951
4215
|
function getWidgetHover(line, position, workspace) {
|
|
@@ -4228,6 +4492,18 @@ function findReferences(uri, position, workspace, includeDeclaration) {
|
|
|
4228
4492
|
}
|
|
4229
4493
|
}
|
|
4230
4494
|
}
|
|
4495
|
+
{
|
|
4496
|
+
const transRegex = /(?<!\w)%([A-Za-z_$][\w$]*)/g;
|
|
4497
|
+
let match;
|
|
4498
|
+
while ((match = transRegex.exec(line)) !== null) {
|
|
4499
|
+
const start = match.index;
|
|
4500
|
+
const end = start + match[0].length;
|
|
4501
|
+
if (position.character >= start && position.character <= end) {
|
|
4502
|
+
const varName = match[1];
|
|
4503
|
+
return findTransientReferences(varName, workspace, includeDeclaration);
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4231
4507
|
{
|
|
4232
4508
|
const widgetRegex = /\{([A-Za-z_$][\w$]*)/g;
|
|
4233
4509
|
let match;
|
|
@@ -4322,6 +4598,20 @@ function findVariableReferences(varName, workspace, includeDeclaration) {
|
|
|
4322
4598
|
}
|
|
4323
4599
|
return locations;
|
|
4324
4600
|
}
|
|
4601
|
+
function findTransientReferences(varName, workspace, includeDeclaration) {
|
|
4602
|
+
const results = [];
|
|
4603
|
+
if (includeDeclaration) {
|
|
4604
|
+
const decl = workspace.variables.getDeclaredTransient().get(varName);
|
|
4605
|
+
if (decl?.declarationUri && decl.declarationRange) {
|
|
4606
|
+
results.push({ uri: decl.declarationUri, range: decl.declarationRange });
|
|
4607
|
+
}
|
|
4608
|
+
}
|
|
4609
|
+
const usages = workspace.variables.getTransientUsages(varName);
|
|
4610
|
+
for (const u of usages) {
|
|
4611
|
+
results.push({ uri: u.uri, range: u.range });
|
|
4612
|
+
}
|
|
4613
|
+
return results;
|
|
4614
|
+
}
|
|
4325
4615
|
function findWidgetReferences(widgetName, workspace, includeDeclaration) {
|
|
4326
4616
|
const locations = [];
|
|
4327
4617
|
if (includeDeclaration) {
|
|
@@ -4414,8 +4704,9 @@ function computeRename(uri, position, newName, workspace) {
|
|
|
4414
4704
|
break;
|
|
4415
4705
|
}
|
|
4416
4706
|
case "variable": {
|
|
4417
|
-
const bareName = newName.startsWith("$") ? newName.slice(1) : newName;
|
|
4418
|
-
const
|
|
4707
|
+
const bareName = newName.startsWith("$") ? newName.slice(1) : newName.startsWith("%") ? newName.slice(1) : newName;
|
|
4708
|
+
const isTransient = workspace.variables.getDeclaredTransient().has(symbol.name);
|
|
4709
|
+
const refs = isTransient ? findTransientReferences(symbol.name, workspace, true) : findVariableReferences(symbol.name, workspace, true);
|
|
4419
4710
|
for (const ref of refs) {
|
|
4420
4711
|
addEdit(ref.uri, ref.range, bareName);
|
|
4421
4712
|
}
|
|
@@ -4494,6 +4785,24 @@ function resolveSymbolAtCursor(uri, position, workspace) {
|
|
|
4494
4785
|
}
|
|
4495
4786
|
}
|
|
4496
4787
|
}
|
|
4788
|
+
{
|
|
4789
|
+
const transRegex = /(?<!\w)%([A-Za-z_$][\w$]*)/g;
|
|
4790
|
+
let match;
|
|
4791
|
+
while ((match = transRegex.exec(line)) !== null) {
|
|
4792
|
+
const start = match.index;
|
|
4793
|
+
const end = start + match[0].length;
|
|
4794
|
+
if (position.character >= start && position.character <= end) {
|
|
4795
|
+
return {
|
|
4796
|
+
kind: "variable",
|
|
4797
|
+
name: match[1],
|
|
4798
|
+
range: {
|
|
4799
|
+
start: { line: position.line, character: start },
|
|
4800
|
+
end: { line: position.line, character: end }
|
|
4801
|
+
}
|
|
4802
|
+
};
|
|
4803
|
+
}
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4497
4806
|
{
|
|
4498
4807
|
const widgetDefRegex2 = /\{widget\s+"([^"]+)"/gi;
|
|
4499
4808
|
let match;
|
|
@@ -4691,6 +5000,7 @@ function computeInlayHints(uri, range, workspace) {
|
|
|
4691
5000
|
const hints = [];
|
|
4692
5001
|
addWidgetParamHints(text, range, workspace, hints);
|
|
4693
5002
|
addVariableTypeHints(uri, text, range, workspace, hints);
|
|
5003
|
+
addTransientTypeHints(uri, text, range, workspace, hints);
|
|
4694
5004
|
return hints;
|
|
4695
5005
|
}
|
|
4696
5006
|
function addWidgetParamHints(text, range, workspace, hints) {
|
|
@@ -4747,6 +5057,28 @@ function addVariableTypeHints(uri, text, range, workspace, hints) {
|
|
|
4747
5057
|
});
|
|
4748
5058
|
}
|
|
4749
5059
|
}
|
|
5060
|
+
function addTransientTypeHints(uri, text, range, workspace, hints) {
|
|
5061
|
+
const storyTransientsPassage = workspace.passages.getStoryTransients();
|
|
5062
|
+
if (!storyTransientsPassage) return;
|
|
5063
|
+
if (storyTransientsPassage.uri !== uri) return;
|
|
5064
|
+
const lines = text.split("\n");
|
|
5065
|
+
const startLine = Math.max(range.start.line, 0);
|
|
5066
|
+
const endLine = Math.min(range.end.line, lines.length - 1);
|
|
5067
|
+
for (let lineNum = startLine; lineNum <= endLine; lineNum++) {
|
|
5068
|
+
const line = lines[lineNum];
|
|
5069
|
+
const declMatch = line.match(/^%([A-Za-z_$][\w$]*)\s*=\s*(.+)$/);
|
|
5070
|
+
if (!declMatch) continue;
|
|
5071
|
+
const valueStr = declMatch[2].trim();
|
|
5072
|
+
const inferredType = inferType(valueStr);
|
|
5073
|
+
if (!inferredType) continue;
|
|
5074
|
+
const eqIdx = line.indexOf("=");
|
|
5075
|
+
hints.push({
|
|
5076
|
+
position: { line: lineNum, character: eqIdx },
|
|
5077
|
+
label: `: ${inferredType}`,
|
|
5078
|
+
kind: "type"
|
|
5079
|
+
});
|
|
5080
|
+
}
|
|
5081
|
+
}
|
|
4750
5082
|
function inferType(value) {
|
|
4751
5083
|
if (/^\d+(\.\d+)?$/.test(value)) return "number";
|
|
4752
5084
|
if (/^"[^"]*"$/.test(value) || /^'[^']*'$/.test(value)) return "string";
|
|
@@ -4851,6 +5183,7 @@ function computeSemanticTokensAbsolute(uri, workspace) {
|
|
|
4851
5183
|
const storyVarRegex = /(?<!\w)\$([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
4852
5184
|
const tempVarRegex = /(?<!\w)_([A-Za-z_$][\w$]*)/g;
|
|
4853
5185
|
const localVarRegex = /(?<!\w)@([A-Za-z_$][\w$]*)/g;
|
|
5186
|
+
const transientVarRegex = /(?<!\w)%([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
4854
5187
|
const sugarKeywordRegex = /\b(to|is|isnot|eq|neq|gt|gte|lt|lte|and|or|not|def|ndef)\b/g;
|
|
4855
5188
|
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
4856
5189
|
if (headerLines.has(lineIndex)) continue;
|
|
@@ -4886,6 +5219,16 @@ function computeSemanticTokensAbsolute(uri, workspace) {
|
|
|
4886
5219
|
tokenModifiers: encodeModifiers(["readonly"])
|
|
4887
5220
|
});
|
|
4888
5221
|
}
|
|
5222
|
+
transientVarRegex.lastIndex = 0;
|
|
5223
|
+
while ((m = transientVarRegex.exec(line)) !== null) {
|
|
5224
|
+
tokens.push({
|
|
5225
|
+
line: lineIndex,
|
|
5226
|
+
startChar: m.index,
|
|
5227
|
+
length: m[0].length,
|
|
5228
|
+
tokenType: encodeType("variable"),
|
|
5229
|
+
tokenModifiers: encodeModifiers(["defaultLibrary"])
|
|
5230
|
+
});
|
|
5231
|
+
}
|
|
4889
5232
|
sugarKeywordRegex.lastIndex = 0;
|
|
4890
5233
|
while ((m = sugarKeywordRegex.exec(line)) !== null) {
|
|
4891
5234
|
tokens.push({
|
|
@@ -4990,6 +5333,11 @@ function computeCodeActions(uri, diagnostics, workspace) {
|
|
|
4990
5333
|
if (action) actions.push(action);
|
|
4991
5334
|
break;
|
|
4992
5335
|
}
|
|
5336
|
+
case DiagnosticCode.UndeclaredTransient: {
|
|
5337
|
+
const action = fixUndeclaredTransient(diag, workspace);
|
|
5338
|
+
if (action) actions.push(action);
|
|
5339
|
+
break;
|
|
5340
|
+
}
|
|
4993
5341
|
}
|
|
4994
5342
|
}
|
|
4995
5343
|
return actions;
|
|
@@ -5072,6 +5420,40 @@ function fixNoStoryVariables(uri, workspace) {
|
|
|
5072
5420
|
}]
|
|
5073
5421
|
};
|
|
5074
5422
|
}
|
|
5423
|
+
function fixUndeclaredTransient(diag, workspace) {
|
|
5424
|
+
const match = diag.message.match(/'%(\w+)'/);
|
|
5425
|
+
if (!match) return null;
|
|
5426
|
+
const varName = match[1];
|
|
5427
|
+
const storyTransients = workspace.passages.getStoryTransients();
|
|
5428
|
+
if (!storyTransients) return null;
|
|
5429
|
+
const storyTransientsUri = storyTransients.uri;
|
|
5430
|
+
const text = workspace.documents.getText(storyTransientsUri);
|
|
5431
|
+
if (text === void 0) return null;
|
|
5432
|
+
const lines = text.split("\n");
|
|
5433
|
+
const contentStart = storyTransients.headerEnd.end.line + 1;
|
|
5434
|
+
let contentEnd = lines.length;
|
|
5435
|
+
for (let i = contentStart; i < lines.length; i++) {
|
|
5436
|
+
if (/^::\s+/.test(lines[i])) {
|
|
5437
|
+
contentEnd = i;
|
|
5438
|
+
break;
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
5441
|
+
const insertLine = contentEnd;
|
|
5442
|
+
return {
|
|
5443
|
+
title: `Declare '%${varName}' in StoryTransients`,
|
|
5444
|
+
kind: "quickfix",
|
|
5445
|
+
diagnosticCodes: [DiagnosticCode.UndeclaredTransient],
|
|
5446
|
+
edits: [{
|
|
5447
|
+
uri: storyTransientsUri,
|
|
5448
|
+
range: {
|
|
5449
|
+
start: { line: insertLine, character: 0 },
|
|
5450
|
+
end: { line: insertLine, character: 0 }
|
|
5451
|
+
},
|
|
5452
|
+
newText: `%${varName} = null
|
|
5453
|
+
`
|
|
5454
|
+
}]
|
|
5455
|
+
};
|
|
5456
|
+
}
|
|
5075
5457
|
function toLspRange6(r) {
|
|
5076
5458
|
return {
|
|
5077
5459
|
start: { line: r.start.line, character: r.start.character },
|