@webgal/language-server 0.0.2-alpha.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.
@@ -0,0 +1,1530 @@
1
+ import { StateConfig, defaultSettings, documentSettings, findTokenRange, getPatternAtPosition, getStageCompletionContext, getWordAtPosition, setGlobalSettings, updateGlobalMap, validateTextDocument } from "./utils.mjs";
2
+ import { GlobalMap } from "@webgal/language-core";
3
+ import { CompletionItemKind, DidChangeConfigurationNotification, FoldingRangeKind, InsertTextFormat, LocationLink, MarkupKind, Position, Range, TextDocumentSyncKind } from "@volar/language-server";
4
+
5
+ //#region src/events/onDid.ts
6
+ var onDid_default = function(documents, connection) {
7
+ documents.onDidClose((e) => {
8
+ documentSettings.delete(e.document.uri);
9
+ });
10
+ connection.onDidChangeConfiguration((change) => {
11
+ if (StateConfig.hasConfigurationCapability) documentSettings.clear();
12
+ else setGlobalSettings(change.settings.XRWebGalLanguageServer || defaultSettings);
13
+ connection.languages.diagnostics.refresh();
14
+ });
15
+ };
16
+
17
+ //#endregion
18
+ //#region src/events/onInitialized.ts
19
+ var onInitialized_default = function(_, connection) {
20
+ if (StateConfig.hasConfigurationCapability) connection.client.register(DidChangeConfigurationNotification.type, void 0);
21
+ if (StateConfig.hasWorkspaceFolderCapability) connection.workspace.onDidChangeWorkspaceFolders((_event) => {
22
+ connection.console.log("Workspace folder change event received.");
23
+ });
24
+ connection.sendRequest("client/showTip", "WebGal LSP Initialized");
25
+ };
26
+
27
+ //#endregion
28
+ //#region src/utils/provider.ts
29
+ const WebGALCommandPrefix = "https://docs.openwebgal.com/script-reference/commands/";
30
+ let commandType = /* @__PURE__ */ function(commandType) {
31
+ commandType[commandType["say"] = 0] = "say";
32
+ commandType[commandType["changeBg"] = 1] = "changeBg";
33
+ commandType[commandType["changeFigure"] = 2] = "changeFigure";
34
+ commandType[commandType["bgm"] = 3] = "bgm";
35
+ commandType[commandType["video"] = 4] = "video";
36
+ commandType[commandType["pixi"] = 5] = "pixi";
37
+ commandType[commandType["pixiInit"] = 6] = "pixiInit";
38
+ commandType[commandType["intro"] = 7] = "intro";
39
+ commandType[commandType["miniAvatar"] = 8] = "miniAvatar";
40
+ commandType[commandType["changeScene"] = 9] = "changeScene";
41
+ commandType[commandType["choose"] = 10] = "choose";
42
+ commandType[commandType["end"] = 11] = "end";
43
+ commandType[commandType["setComplexAnimation"] = 12] = "setComplexAnimation";
44
+ commandType[commandType["setFilter"] = 13] = "setFilter";
45
+ commandType[commandType["label"] = 14] = "label";
46
+ commandType[commandType["jumpLabel"] = 15] = "jumpLabel";
47
+ commandType[commandType["chooseLabel"] = 16] = "chooseLabel";
48
+ commandType[commandType["setVar"] = 17] = "setVar";
49
+ commandType[commandType["if"] = 18] = "if";
50
+ commandType[commandType["callScene"] = 19] = "callScene";
51
+ commandType[commandType["showVars"] = 20] = "showVars";
52
+ commandType[commandType["unlockCg"] = 21] = "unlockCg";
53
+ commandType[commandType["unlockBgm"] = 22] = "unlockBgm";
54
+ commandType[commandType["filmMode"] = 23] = "filmMode";
55
+ commandType[commandType["setTextbox"] = 24] = "setTextbox";
56
+ commandType[commandType["setAnimation"] = 25] = "setAnimation";
57
+ commandType[commandType["playEffect"] = 26] = "playEffect";
58
+ commandType[commandType["setTempAnimation"] = 27] = "setTempAnimation";
59
+ commandType[commandType["comment"] = 28] = "comment";
60
+ commandType[commandType["setTransform"] = 29] = "setTransform";
61
+ commandType[commandType["setTransition"] = 30] = "setTransition";
62
+ commandType[commandType["getUserInput"] = 31] = "getUserInput";
63
+ commandType[commandType["applyStyle"] = 32] = "applyStyle";
64
+ commandType[commandType["wait"] = 33] = "wait";
65
+ commandType[commandType["callSteam"] = 34] = "callSteam";
66
+ return commandType;
67
+ }({});
68
+ const argsMap = {
69
+ when: {
70
+ arg: "when",
71
+ desc: "条件",
72
+ label: "-when",
73
+ kind: CompletionItemKind.Constant,
74
+ documentation: `条件执行本句
75
+ \`\`\`webgal
76
+ changeScene:2.txt -when=a>1;
77
+ \`\`\`
78
+ a>1时跳转到场景2`,
79
+ detail: `option -when=<condition>`,
80
+ insertText: "when="
81
+ },
82
+ next: {
83
+ arg: "next",
84
+ desc: "下一句对话",
85
+ label: "-next",
86
+ kind: CompletionItemKind.Constant,
87
+ documentation: `连续执行本句和下一句`,
88
+ detail: `option -next`,
89
+ insertText: "next"
90
+ },
91
+ notend: {
92
+ arg: "notend",
93
+ desc: "本句对话连接在上一句对话之后",
94
+ label: "-notend",
95
+ kind: CompletionItemKind.Constant,
96
+ documentation: `用于对话,表示该对话未结束`,
97
+ detail: `option -notend`,
98
+ insertText: "notend"
99
+ },
100
+ concat: {
101
+ arg: "concat",
102
+ desc: "本句对话没有结束,在后面可能连接演出或对话。",
103
+ label: "-concat",
104
+ kind: CompletionItemKind.Constant,
105
+ documentation: `用于对话,将该对话与上一句连接`,
106
+ detail: `option -concat`,
107
+ insertText: "concat"
108
+ },
109
+ hold: {
110
+ arg: "hold",
111
+ desc: "独白结束后保持独白界面",
112
+ label: "-hold",
113
+ kind: CompletionItemKind.Constant,
114
+ documentation: `结束后保持`,
115
+ detail: `option -hold`,
116
+ insertText: "hold"
117
+ },
118
+ title: {
119
+ arg: "title",
120
+ desc: "标题",
121
+ label: "-title",
122
+ kind: CompletionItemKind.Constant,
123
+ documentation: `设置标题`,
124
+ detail: `option -title=<titleText>`,
125
+ insertText: "title="
126
+ },
127
+ buttonText: {
128
+ arg: "buttonText",
129
+ desc: "按钮文字",
130
+ label: "-buttonText",
131
+ kind: CompletionItemKind.Constant,
132
+ documentation: `设置按钮文字`,
133
+ detail: `option -buttonText=<text>`,
134
+ insertText: "buttonText="
135
+ },
136
+ defaultValue: {
137
+ arg: "defaultValue",
138
+ desc: "默认文字",
139
+ label: "-defaultValue",
140
+ kind: CompletionItemKind.Constant,
141
+ documentation: `设置默认文字`,
142
+ detail: `option -defaultValue=<text>`,
143
+ insertText: "defaultValue="
144
+ },
145
+ left: {
146
+ arg: "left",
147
+ desc: "放置位置",
148
+ label: "-left",
149
+ kind: CompletionItemKind.Constant,
150
+ documentation: `立绘设置在左侧`,
151
+ detail: `option -left`,
152
+ insertText: "left"
153
+ },
154
+ right: {
155
+ arg: "right",
156
+ desc: "放置位置",
157
+ label: "-right",
158
+ kind: CompletionItemKind.Constant,
159
+ documentation: `立绘设置在右侧`,
160
+ detail: `option -right`,
161
+ insertText: "right"
162
+ },
163
+ id: {
164
+ arg: "id",
165
+ desc: "对应ID",
166
+ label: "-id",
167
+ kind: CompletionItemKind.Constant,
168
+ documentation: `设置立绘 ID`,
169
+ detail: `option -id=<figureId>`,
170
+ insertText: "id="
171
+ },
172
+ zIndex: {
173
+ arg: "zIndex",
174
+ desc: "立绘层级",
175
+ label: "-zIndex",
176
+ kind: CompletionItemKind.Constant,
177
+ documentation: `设置立绘层级`,
178
+ detail: `option -zIndex=<number>`,
179
+ insertText: "zIndex="
180
+ },
181
+ none: {
182
+ arg: "none",
183
+ desc: "将语句内容替换为空字符串",
184
+ label: "-none",
185
+ kind: CompletionItemKind.Constant,
186
+ documentation: `将语句内容替换为空字符串`,
187
+ detail: `option -none`,
188
+ insertText: "none"
189
+ },
190
+ animationFlag: {
191
+ arg: "animationFlag",
192
+ desc: "动画标识",
193
+ label: "-animationFlag",
194
+ kind: CompletionItemKind.Constant,
195
+ documentation: `设置动画标识`,
196
+ detail: `option -animationFlag=<flag>`,
197
+ insertText: "animationFlag="
198
+ },
199
+ mouthOpen: {
200
+ arg: "mouthOpen",
201
+ desc: "立绘张嘴立绘差分",
202
+ label: "-mouthOpen",
203
+ kind: CompletionItemKind.Constant,
204
+ documentation: `设置立绘张嘴立绘差分`,
205
+ detail: `option -mouthOpen=<fileName>`,
206
+ insertText: "mouthOpen="
207
+ },
208
+ mouthHalfOpen: {
209
+ arg: "mouthHalfOpen",
210
+ desc: "立绘半张嘴立绘差分",
211
+ label: "-mouthHalfOpen",
212
+ kind: CompletionItemKind.Constant,
213
+ documentation: `设置立绘半张嘴立绘差分`,
214
+ detail: `option -mouthHalfOpen=<fileName>`,
215
+ insertText: "mouthHalfOpen="
216
+ },
217
+ mouthClose: {
218
+ arg: "mouthClose",
219
+ desc: "立绘闭嘴立绘差分",
220
+ label: "-mouthClose",
221
+ kind: CompletionItemKind.Constant,
222
+ documentation: `设置立绘闭嘴立绘差分`,
223
+ detail: `option -mouthClose=<fileName>`,
224
+ insertText: "mouthClose="
225
+ },
226
+ eyesOpen: {
227
+ arg: "eyesOpen",
228
+ desc: "立绘睁眼立绘差分",
229
+ label: "-eyesOpen",
230
+ kind: CompletionItemKind.Constant,
231
+ documentation: `设置立绘睁眼立绘差分`,
232
+ detail: `option -eyesOpen=<fileName>`,
233
+ insertText: "eyesOpen="
234
+ },
235
+ eyesClose: {
236
+ arg: "eyesClose",
237
+ desc: "立绘闭眼立绘差分",
238
+ label: "-eyesClose",
239
+ kind: CompletionItemKind.Constant,
240
+ documentation: `设置立绘闭眼立绘差分`,
241
+ detail: `option -eyesClose=<fileName>`,
242
+ insertText: "eyesClose="
243
+ },
244
+ motion: {
245
+ arg: "motion",
246
+ desc: "立绘动作",
247
+ label: "-motion",
248
+ kind: CompletionItemKind.Constant,
249
+ documentation: `设置立绘动作`,
250
+ detail: `option -motion=<motionName>`,
251
+ insertText: "motion="
252
+ },
253
+ expression: {
254
+ arg: "expression",
255
+ desc: "立绘表情",
256
+ label: "-expression",
257
+ kind: CompletionItemKind.Constant,
258
+ documentation: `设置立绘表情`,
259
+ detail: `option -expression=<expressionName>`,
260
+ insertText: "expression="
261
+ },
262
+ bounds: {
263
+ arg: "bounds",
264
+ desc: "拓展或收缩立绘的显示区域",
265
+ label: "-bounds",
266
+ kind: CompletionItemKind.Constant,
267
+ documentation: `拓展或收缩立绘的显示区域`,
268
+ detail: `option -bounds=<boundsValue>`,
269
+ insertText: "bounds="
270
+ },
271
+ series: {
272
+ arg: "series",
273
+ desc: "收录到指定名称的系列中",
274
+ label: "-series",
275
+ kind: CompletionItemKind.Constant,
276
+ documentation: `收录到指定名称的系列中`,
277
+ detail: `option -series=<seriesName>`,
278
+ insertText: "series="
279
+ },
280
+ transform: {
281
+ arg: "transform",
282
+ desc: "变换和滤镜效果",
283
+ label: "-transform",
284
+ kind: CompletionItemKind.Constant,
285
+ documentation: `设置一些变换和滤镜效果`,
286
+ detail: `option -transform`,
287
+ insertText: "transform"
288
+ },
289
+ duration: {
290
+ arg: "duration",
291
+ desc: "持续时间",
292
+ label: "-duration",
293
+ kind: CompletionItemKind.Constant,
294
+ documentation: `设置持续时间`,
295
+ detail: `option -duration=<number>`,
296
+ insertText: "duration="
297
+ },
298
+ volume: {
299
+ arg: "volume",
300
+ desc: "音量",
301
+ label: "-volume",
302
+ kind: CompletionItemKind.Constant,
303
+ documentation: `调整它的音量,默认值 100`,
304
+ detail: `option -volume`,
305
+ insertText: "volume"
306
+ },
307
+ skipOff: {
308
+ arg: "skipOff",
309
+ desc: "关闭跳过",
310
+ label: "-skipOff",
311
+ kind: CompletionItemKind.Constant,
312
+ documentation: `阻止用户跳过视频`,
313
+ detail: `option -skipOff`,
314
+ insertText: "skipOff"
315
+ },
316
+ enter: {
317
+ arg: "enter",
318
+ desc: "进行淡入播放",
319
+ label: "-enter",
320
+ kind: CompletionItemKind.Constant,
321
+ documentation: `进行淡入播放`,
322
+ detail: `option -enter`,
323
+ insertText: "enter"
324
+ },
325
+ global: {
326
+ arg: "global",
327
+ desc: "全局变量",
328
+ label: "-global",
329
+ kind: CompletionItemKind.Constant,
330
+ documentation: `设置长效(全局)变量`,
331
+ detail: `option -global`,
332
+ insertText: "global"
333
+ },
334
+ exit: {
335
+ arg: "exit",
336
+ desc: "进行淡出播放",
337
+ label: "-exit",
338
+ kind: CompletionItemKind.Constant,
339
+ documentation: `进行淡出播放`,
340
+ detail: `option -exit`,
341
+ insertText: "exit"
342
+ },
343
+ ease: {
344
+ arg: "ease",
345
+ desc: "缓动类型",
346
+ label: "-ease",
347
+ kind: CompletionItemKind.Constant,
348
+ documentation: `设置缓动类型`,
349
+ detail: `option -ease=<easeType>`,
350
+ insertText: "ease="
351
+ },
352
+ unlockname: {
353
+ arg: "unlockname",
354
+ desc: "鉴赏将收录该音乐",
355
+ label: "-unlockname",
356
+ kind: CompletionItemKind.Constant,
357
+ documentation: `鉴赏将收录该音乐`,
358
+ detail: `option -unlockname=<name>`,
359
+ insertText: "unlockname="
360
+ },
361
+ speaker: {
362
+ arg: "speaker",
363
+ desc: "说话者",
364
+ label: "-speaker",
365
+ kind: CompletionItemKind.Constant,
366
+ documentation: `设置说话者`,
367
+ detail: `option -speaker=<speakerName>`,
368
+ insertText: "speaker="
369
+ },
370
+ clear: {
371
+ arg: "clear",
372
+ desc: "清除说话者",
373
+ label: "-clear",
374
+ kind: CompletionItemKind.Constant,
375
+ documentation: `清除说话者`,
376
+ detail: `option -clear`,
377
+ insertText: "clear"
378
+ },
379
+ vocal: {
380
+ arg: "vocal",
381
+ desc: "说话时将播放该声音文件。",
382
+ label: "-vocal",
383
+ kind: CompletionItemKind.Constant,
384
+ documentation: `说话时将播放该声音文件`,
385
+ detail: `option -vocal=<audioFile>`,
386
+ insertText: "vocal="
387
+ },
388
+ fontSize: {
389
+ arg: "fontSize",
390
+ desc: "字体大小",
391
+ label: "-fontSize",
392
+ kind: CompletionItemKind.Constant,
393
+ documentation: `文字大小`,
394
+ detail: `option -fontSize`,
395
+ insertText: "fontSize"
396
+ },
397
+ fontColor: {
398
+ arg: "fontColor",
399
+ desc: "字体颜色",
400
+ label: "-fontColor",
401
+ kind: CompletionItemKind.Constant,
402
+ documentation: `字体颜色`,
403
+ detail: `option -fontColor`,
404
+ insertText: "fontColor"
405
+ },
406
+ backgroundColor: {
407
+ arg: "backgroundColor",
408
+ desc: "背景颜色",
409
+ label: "-backgroundColor",
410
+ kind: CompletionItemKind.Constant,
411
+ documentation: `背景颜色`,
412
+ detail: `option -backgroundColor`,
413
+ insertText: "backgroundColor"
414
+ },
415
+ backgroundImage: {
416
+ arg: "backgroundImage",
417
+ desc: "背景图片",
418
+ label: "-backgroundImage",
419
+ kind: CompletionItemKind.Constant,
420
+ documentation: `设置背景图片`,
421
+ detail: `option -backgroundImage=<imageFile>`,
422
+ insertText: "backgroundImage="
423
+ },
424
+ animation: {
425
+ arg: "animation",
426
+ desc: "动画效果",
427
+ label: "-animation",
428
+ kind: CompletionItemKind.Constant,
429
+ documentation: `过渡动画`,
430
+ detail: `option -animation`,
431
+ insertText: "animation"
432
+ },
433
+ delay: {
434
+ arg: "delay",
435
+ desc: "延迟时间",
436
+ label: "-delay",
437
+ kind: CompletionItemKind.Constant,
438
+ documentation: `动画延迟时间`,
439
+ detail: `option -delay`,
440
+ insertText: "delay"
441
+ },
442
+ useForward: {
443
+ arg: "useForward",
444
+ desc: "需要用户手动点击屏幕",
445
+ label: "-useForward",
446
+ kind: CompletionItemKind.Constant,
447
+ documentation: `需要用户手动点击屏幕`,
448
+ detail: `option -useForward`,
449
+ insertText: "useForward"
450
+ },
451
+ center: {
452
+ arg: "center",
453
+ desc: "驱动中间立绘张嘴说话",
454
+ label: "-center",
455
+ kind: CompletionItemKind.Constant,
456
+ documentation: `驱动中间立绘张嘴说话`,
457
+ detail: `option -center`,
458
+ insertText: "center"
459
+ },
460
+ figureId: {
461
+ arg: "figureId",
462
+ desc: "驱动对应 id 的立绘张嘴说话",
463
+ label: "-figureId",
464
+ kind: CompletionItemKind.Constant,
465
+ documentation: `驱动对应 id 的立绘张嘴说话`,
466
+ detail: `option -figureId=<id>`,
467
+ insertText: "figureId="
468
+ },
469
+ target: {
470
+ arg: "target",
471
+ desc: "作用目标",
472
+ label: "-target",
473
+ kind: CompletionItemKind.Constant,
474
+ documentation: `设置动画目标 ID`,
475
+ detail: `option -target=<targetId>`,
476
+ insertText: "target="
477
+ },
478
+ name: {
479
+ arg: "name",
480
+ desc: "名称",
481
+ label: "-name",
482
+ kind: CompletionItemKind.Constant,
483
+ documentation: `设置名称`,
484
+ detail: `option -name`,
485
+ insertText: "name="
486
+ },
487
+ writeDefault: {
488
+ arg: "writeDefault",
489
+ desc: "变换与效果属性写入默认值",
490
+ label: "-writeDefault",
491
+ kind: CompletionItemKind.Constant,
492
+ documentation: `变换与效果属性写入默认值`,
493
+ detail: `option -writeDefault`,
494
+ insertText: "writeDefault"
495
+ },
496
+ keep: {
497
+ arg: "keep",
498
+ desc: "将该动画转换为跨语句动画",
499
+ label: "-keep",
500
+ kind: CompletionItemKind.Constant,
501
+ documentation: `将该动画转换为跨语句动画`,
502
+ detail: `option -keep`,
503
+ insertText: "keep"
504
+ },
505
+ achievementId: {
506
+ arg: "achievementId",
507
+ desc: "成就ID",
508
+ label: "-achievementId",
509
+ kind: CompletionItemKind.Constant,
510
+ documentation: `设置成就ID`,
511
+ detail: `option -achievementId=<achievementId>`,
512
+ insertText: "achievementId="
513
+ }
514
+ };
515
+ const globalArgs = [
516
+ argsMap.when,
517
+ argsMap.next,
518
+ argsMap.notend,
519
+ argsMap.concat,
520
+ argsMap.hold
521
+ ];
522
+ /**
523
+ * @description: 关键字
524
+ */
525
+ const WebGALKeywords = {
526
+ say: {
527
+ type: commandType.say,
528
+ desc: "对话命令。任何识别不了的命令,都将尝试作为对话命令执行。",
529
+ args: [
530
+ argsMap.notend,
531
+ argsMap.concat,
532
+ argsMap.hold,
533
+ argsMap.speaker,
534
+ argsMap.clear,
535
+ argsMap.vocal,
536
+ argsMap.fontSize,
537
+ argsMap.figureId,
538
+ argsMap.center,
539
+ argsMap.left,
540
+ argsMap.right
541
+ ],
542
+ label: "say",
543
+ kind: CompletionItemKind.Function,
544
+ documentation: `对话
545
+ \`\`\`webgal
546
+ say:你好;
547
+ \`\`\``,
548
+ detail: `say:<content> [...args];`,
549
+ insertText: "say:$1;$2"
550
+ },
551
+ changeBg: {
552
+ type: commandType.changeBg,
553
+ desc: "更改背景命令。",
554
+ args: [
555
+ argsMap.transform,
556
+ argsMap.enter,
557
+ argsMap.exit,
558
+ argsMap.duration,
559
+ argsMap.ease,
560
+ argsMap.unlockname,
561
+ argsMap.series
562
+ ],
563
+ label: "changeBg",
564
+ kind: CompletionItemKind.Function,
565
+ documentation: `更新背景图片
566
+ \`\`\`webgal
567
+ changeBg:testBG03.jpg -next;
568
+ \`\`\``,
569
+ detail: `changeBg:<fileName> [-next];`,
570
+ insertText: "changeBg:$1;$2"
571
+ },
572
+ changeFigure: {
573
+ type: commandType.changeFigure,
574
+ desc: "更改立绘命令。",
575
+ args: [
576
+ argsMap.transform,
577
+ argsMap.enter,
578
+ argsMap.exit,
579
+ argsMap.duration,
580
+ argsMap.ease,
581
+ argsMap.left,
582
+ argsMap.right,
583
+ argsMap.id,
584
+ argsMap.zIndex,
585
+ argsMap.none,
586
+ argsMap.animationFlag,
587
+ argsMap.mouthOpen,
588
+ argsMap.mouthHalfOpen,
589
+ argsMap.mouthClose,
590
+ argsMap.eyesOpen,
591
+ argsMap.eyesClose,
592
+ argsMap.motion,
593
+ argsMap.expression,
594
+ argsMap.bounds
595
+ ],
596
+ label: "changeFigure",
597
+ kind: CompletionItemKind.Function,
598
+ documentation: `更新立绘
599
+ \`\`\`webgal
600
+ changeFigure:testFigure03.png -left -next;
601
+ \`\`\``,
602
+ detail: `changeFigure:<fileName> [-left] [-right] [id=figureId] [-next];`,
603
+ insertText: "changeFigure:$1;$2"
604
+ },
605
+ bgm: {
606
+ type: commandType.bgm,
607
+ desc: "更改背景音乐命令。",
608
+ args: [
609
+ argsMap.volume,
610
+ argsMap.duration,
611
+ argsMap.series,
612
+ argsMap.unlockname
613
+ ],
614
+ label: "bgm",
615
+ kind: CompletionItemKind.Function,
616
+ documentation: `背景音乐(BGM)
617
+ \`\`\`webgal
618
+ bgm:夏影.mp3;
619
+ \`\`\``,
620
+ detail: `bgm:<fileName>;`,
621
+ insertText: "bgm:$1;$2"
622
+ },
623
+ playVideo: {
624
+ type: commandType.video,
625
+ desc: "播放视频命令。",
626
+ args: [argsMap.skipOff],
627
+ label: "playVideo",
628
+ kind: CompletionItemKind.Function,
629
+ documentation: `播放视频
630
+ \`\`\`webgal
631
+ playVideo:OP.mp4;
632
+ \`\`\``,
633
+ detail: `playVideo:<fileName>;`,
634
+ insertText: "playVideo:$1;$2"
635
+ },
636
+ pixiPerform: {
637
+ type: commandType.pixi,
638
+ desc: "添加舞台特效",
639
+ args: [],
640
+ label: "pixiPerform",
641
+ kind: CompletionItemKind.Function,
642
+ documentation: `初始化 Pixi 特效
643
+ 注意:特效作用后,如果没有初始化,特效会一直运行。`,
644
+ detail: `pixiPerform:<performName>;`,
645
+ insertText: "pixiPerform:$1;$2"
646
+ },
647
+ pixiInit: {
648
+ type: commandType.pixiInit,
649
+ desc: "pixi初始化命令。",
650
+ args: [],
651
+ label: "pixiInit",
652
+ kind: CompletionItemKind.Function,
653
+ documentation: `初始化 Pixi 特效
654
+ 1.如果你要使用特效,那么你必须先运行这个命令来初始化 Pixi。
655
+ 2.如果你想要消除已经作用的效果,你可以使用这个语法来清空效果。`,
656
+ detail: `pixiInit;`,
657
+ insertText: "pixiInit;"
658
+ },
659
+ intro: {
660
+ type: commandType.intro,
661
+ desc: "黑屏文字演示命令。",
662
+ args: [
663
+ argsMap.fontSize,
664
+ argsMap.fontColor,
665
+ argsMap.backgroundColor,
666
+ argsMap.backgroundImage,
667
+ argsMap.animation,
668
+ argsMap.delay,
669
+ argsMap.useForward
670
+ ],
671
+ label: "intro",
672
+ kind: CompletionItemKind.Function,
673
+ documentation: `黑屏独白
674
+ 在许多游戏中,会以黑屏显示一些文字,用来引入主题或表现人物的心理活动。你可以使用 intro 命令来演出独白。
675
+ 独白的分拆以分隔符(|)来分割,也就是说,每一个 | 代表一个换行。
676
+
677
+ \`\`\`webgal
678
+ intro:回忆不需要适合的剧本,|反正一说出口,|都成了戏言。;
679
+ \`\`\``,
680
+ detail: `intro:<text> [|<text of line 2>] ...;`,
681
+ insertText: "intro:$1;$2"
682
+ },
683
+ miniAvatar: {
684
+ type: commandType.miniAvatar,
685
+ desc: "小头像命令。",
686
+ args: [],
687
+ label: "miniAvatar",
688
+ kind: CompletionItemKind.Function,
689
+ documentation: `放置小头像
690
+ 很多游戏可以在文本框的左下角放置小头像,以下是在本引擎中使用的语法
691
+
692
+ \`\`\`webgal
693
+ miniAvatar:minipic_test.png;显示
694
+ miniAvatar:none;关闭
695
+ \`\`\``,
696
+ detail: `miniAvatar:<fileName>;`,
697
+ insertText: "miniAvatar"
698
+ },
699
+ changeScene: {
700
+ type: commandType.changeScene,
701
+ desc: "切换场景命令。",
702
+ args: [],
703
+ label: "changeScene",
704
+ kind: CompletionItemKind.Function,
705
+ documentation: `场景跳转
706
+ 你可以将你的剧本拆分成多个 txt 文档,并使用一个简单的语句来切换当前运行的剧本。
707
+ \`\`\`webgal
708
+ changeScene:Chapter-2.txt;
709
+ \`\`\``,
710
+ detail: `changeScene:<newSceneFileName>;`,
711
+ insertText: "changeScene:$1;$2"
712
+ },
713
+ choose: {
714
+ type: commandType.choose,
715
+ desc: "分支选择命令。",
716
+ args: [],
717
+ label: "choose",
718
+ kind: CompletionItemKind.Function,
719
+ documentation: `分支选择
720
+ 如果你的剧本存在分支选项,你希望通过选择不同的选项进入不同的章节,请使用以下语句。
721
+ 其中,|是分隔符。
722
+ \`\`\`webgal
723
+ choose:叫住她:Chapter-2.txt|回家:Chapter-3.txt;
724
+ \`\`\``,
725
+ detail: `choose:<chooseText:newSceneName> [|<chooseText:newSceneName>] ...;`,
726
+ insertText: "choose:$1|$2;"
727
+ },
728
+ end: {
729
+ type: commandType.end,
730
+ desc: "结束游戏命令。",
731
+ args: [],
732
+ label: "end",
733
+ kind: CompletionItemKind.Function,
734
+ documentation: `结束游戏并返回到标题
735
+ \`\`\`webgal
736
+ end;
737
+ \`\`\``,
738
+ detail: `end;`,
739
+ insertText: "end;"
740
+ },
741
+ setComplexAnimation: {
742
+ type: commandType.setComplexAnimation,
743
+ desc: "动画演出命令。",
744
+ args: [argsMap.target, argsMap.duration],
745
+ label: "setComplexAnimation",
746
+ kind: CompletionItemKind.Function,
747
+ documentation: `填写复杂动画的名称。
748
+ 目前 WebGAL 提供的复杂动画有:
749
+ universalSoftIn:通用透明度淡入
750
+ universalSoftOff:通用透明度淡出
751
+
752
+ \`\`\`webgal
753
+ setComplexAnimation:universalSoftIn -target=aaa -duration=1000;
754
+ \`\`\``,
755
+ detail: `setComplexAnimation:<name> [-target=...|-duration=...];`,
756
+ insertText: "setComplexAnimation:$1;$2"
757
+ },
758
+ label: {
759
+ type: commandType.label,
760
+ desc: "标签命令。",
761
+ args: [],
762
+ label: "label",
763
+ kind: CompletionItemKind.Function,
764
+ documentation: `定义标签`,
765
+ detail: `label:<Name>;`,
766
+ insertText: "label:$1;$2"
767
+ },
768
+ jumpLabel: {
769
+ type: commandType.jumpLabel,
770
+ desc: "跳转标签命令。",
771
+ args: [],
772
+ label: "jumpLabel",
773
+ kind: CompletionItemKind.Function,
774
+ documentation: `跳转到指定标签`,
775
+ detail: `jumpLabel:<Laebl Name>;`,
776
+ insertText: "jumpLabel:$1;$2"
777
+ },
778
+ setVar: {
779
+ type: commandType.setVar,
780
+ desc: "设置变量命令。",
781
+ args: [argsMap.global],
782
+ label: "setVar",
783
+ kind: CompletionItemKind.Function,
784
+ documentation: `使用变量
785
+ \`\`\`webgal
786
+ setVar:a=1;可以设置数字
787
+ setVar:a=true;可以设置布尔值
788
+ setVar:a=人物名称;可以设置字符串
789
+ \`\`\``,
790
+ detail: `setVar:<expression>;`,
791
+ insertText: "setVar:$1;$2"
792
+ },
793
+ callScene: {
794
+ type: commandType.callScene,
795
+ desc: "调用场景命令。",
796
+ args: [],
797
+ label: "callScene",
798
+ kind: CompletionItemKind.Function,
799
+ documentation: `
800
+ 如果你需要在执行完调用的场景后回到先前的场景(即父场景),你可以使用 callScene 来调用场景
801
+ \`\`\`webgal
802
+ callScene:Chapter-2.txt;
803
+ \`\`\``,
804
+ detail: `callScene:<newSceneFileName>;`,
805
+ insertText: "callScene:$1;$2"
806
+ },
807
+ showVars: {
808
+ type: commandType.showVars,
809
+ desc: "显示所有本地/全局变量值",
810
+ args: [],
811
+ label: "showVars",
812
+ kind: CompletionItemKind.Function,
813
+ documentation: `在对话框中,显示所有本地变量与全局变量的值。
814
+ \`\`\`webgal
815
+ showVars;
816
+ \`\`\``,
817
+ detail: `showVars;`,
818
+ insertText: "showVars;"
819
+ },
820
+ unlockCg: {
821
+ type: commandType.unlockCg,
822
+ desc: "解锁立绘命令。",
823
+ args: [argsMap.name, argsMap.series],
824
+ label: "unlockCg",
825
+ kind: CompletionItemKind.Function,
826
+ documentation: `解锁 CG 鉴赏
827
+ \`\`\`webgal
828
+ unlockCg:xgmain.jpeg -name=星光咖啡馆与死神之蝶 -series=1;
829
+ \`\`\``,
830
+ detail: `unlockCg:<fileName> -name=cgName -series=serisId;`,
831
+ insertText: "unlockCg:$1;$2"
832
+ },
833
+ unlockBgm: {
834
+ type: commandType.unlockBgm,
835
+ desc: "解锁背景音乐命令。",
836
+ args: [argsMap.name, argsMap.series],
837
+ label: "unlockBgm",
838
+ kind: CompletionItemKind.Function,
839
+ documentation: `解锁 BGM 鉴赏
840
+ \`\`\`webgal
841
+ unlockBgm:s_Title.mp3 -name=Smiling-Swinging!!;
842
+ \`\`\``,
843
+ detail: `unlockBgm:<fileName> -name=bgmName;`,
844
+ insertText: "unlockBgm:$1;$2"
845
+ },
846
+ filmMode: {
847
+ type: commandType.filmMode,
848
+ desc: "电影模式命令。",
849
+ args: [],
850
+ label: "filmMode",
851
+ kind: CompletionItemKind.Function,
852
+ documentation: `当不填写或填写 none 时,关闭电影模式。其他任何字符串均表示开启电影模式。
853
+ \`\`\`webgal
854
+ filmMode:on;
855
+ 角色A:真相只有一个;
856
+ filmMode:none;
857
+ \`\`\``,
858
+ detail: `filmMode:[on|none];`,
859
+ insertText: "filmMode:$1;$2"
860
+ },
861
+ setTextbox: {
862
+ type: commandType.setTextbox,
863
+ desc: "设置文本框命令。",
864
+ args: [],
865
+ label: "setTextbox",
866
+ kind: CompletionItemKind.Function,
867
+ documentation: `设置文本框开启/关闭
868
+ \`\`\`webgal
869
+ setTextbox:hide;关闭文本框
870
+ setTextbox:on;开启文本框,可以是除 hide 以外的任意值。
871
+ \`\`\``,
872
+ detail: `setTextbox:[hide] [others];`,
873
+ insertText: "setTextbox:$1;$2"
874
+ },
875
+ setAnimation: {
876
+ type: commandType.setAnimation,
877
+ desc: "设置动画命令。",
878
+ args: [
879
+ argsMap.target,
880
+ argsMap.writeDefault,
881
+ argsMap.keep
882
+ ],
883
+ label: "setAnimation",
884
+ kind: CompletionItemKind.Function,
885
+ documentation: `设置动画
886
+ \`\`\`webgal
887
+ setAnimation:enter-from-bottom -target=fig-center -next;为中间立绘设置一个从下方进入的动画,并转到下一句。
888
+ \`\`\``,
889
+ detail: `setAnimation:<animationName> -target=targetId;`,
890
+ insertText: "setAnimation"
891
+ },
892
+ playEffect: {
893
+ type: commandType.playEffect,
894
+ desc: "播放效果命令。",
895
+ args: [argsMap.id],
896
+ label: "playEffect",
897
+ kind: CompletionItemKind.Function,
898
+ documentation: `效果音
899
+ \`\`\`webgal
900
+ playEffect:xxx.mp3;
901
+ \`\`\``,
902
+ detail: `playEffect:<fileName>;`,
903
+ insertText: "playEffect:$1;$2"
904
+ },
905
+ setTempAnimation: {
906
+ type: commandType.setTempAnimation,
907
+ desc: "设置临时动画命令。",
908
+ args: [
909
+ argsMap.target,
910
+ argsMap.writeDefault,
911
+ argsMap.keep
912
+ ],
913
+ label: "setTempAnimation",
914
+ kind: CompletionItemKind.Function,
915
+ documentation: `与 setAnimation 读取动画文件不同,setTempAnimation 允许用户直接在代码里定义多段动画。
916
+ 语句内容格式为动画文件的单行形式,即 [{},{},{}] 。
917
+
918
+ 相关信息
919
+
920
+ 如果您想复用动画,请使用 setAnimation 命令。
921
+ 如果您只想设置单段动画,请使用 setTransform 命令。
922
+
923
+ \`\`\`webgal
924
+ changeFigure:1/open_eyes.png -id=aaa;
925
+ ; 闪光弹动画
926
+ setTempAnimation:[{"duration":0},{"brightness":2,"contrast":0,"duration":200,"ease":"circIn"},{"brightness":1,"contrast":1,"duration":200},{"brightness":2,"contrast":0,"duration":200,"ease":"circIn"},{"brightness":1,"contrast":1,"duration":2500}] -target=aaa;
927
+ \`\`\`
928
+ `,
929
+ detail: `setTempAnimation:<name>|<JSON> [-target=...|-writeDefault...|-keep=...];";`,
930
+ insertText: "setTempAnimation:$1;$2"
931
+ },
932
+ setTransform: {
933
+ type: commandType.setTransform,
934
+ desc: "设置变换命令。",
935
+ args: [
936
+ argsMap.target,
937
+ argsMap.duration,
938
+ argsMap.writeDefault,
939
+ argsMap.keep
940
+ ],
941
+ label: "setTransform",
942
+ kind: CompletionItemKind.Function,
943
+ documentation: `设置效果`,
944
+ detail: `setTransform:<expression>;`,
945
+ insertText: "setTransform:"
946
+ },
947
+ setTransition: {
948
+ type: commandType.setTransition,
949
+ desc: "设置过渡命令。",
950
+ args: [
951
+ argsMap.target,
952
+ argsMap.enter,
953
+ argsMap.exit
954
+ ],
955
+ label: "setTransition",
956
+ kind: CompletionItemKind.Function,
957
+ documentation: `不需要填写任何语句内容。
958
+ 详情请见设置进出场效果。
959
+
960
+ \`\`\`webgal
961
+ changeFigure:1/open_eyes.png -id=aaa -next;
962
+ setTransition: -target=aaa -enter=enter-from-left;
963
+ 角色A: 你好!
964
+ setTransition: -target=aaa -exit=exit-to-right;
965
+ changeFigure:none -id=aaa -next;
966
+ 角色A: 再见!
967
+ \`\`\``,
968
+ detail: `setTransition:[name] [-target=...|-enter=...|-exit=...];`,
969
+ insertText: "setTransition:$1;$2"
970
+ },
971
+ getUserInput: {
972
+ type: commandType.getUserInput,
973
+ desc: "获取用户输入命令。",
974
+ label: "getUserInput",
975
+ args: [
976
+ argsMap.title,
977
+ argsMap.buttonText,
978
+ argsMap.defaultValue
979
+ ],
980
+ kind: CompletionItemKind.Function,
981
+ documentation: `获取用户输入
982
+ \`\`\`webgal
983
+ 填写变量名称,用户输入的值将保存在该变量中。
984
+ 角色B:真的是太感谢您了,能告诉我您的名字吗?;
985
+ getUserInput:player_name -title=您的名字 -buttonText=确认 -defaultValue=Bob;
986
+ 角色B:{player_name},我记住了。;
987
+ \`\`\`
988
+ `,
989
+ detail: `getUserInput:[...args];`,
990
+ insertText: "getUserInput:$1;$2"
991
+ },
992
+ applyStyle: {
993
+ type: commandType.applyStyle,
994
+ desc: "应用样式命令。",
995
+ args: [],
996
+ label: "applyStyle",
997
+ kind: CompletionItemKind.Function,
998
+ documentation: `首先需要在 UI 模板中新写一个样式,然后可以用 applyStyle 命令,将新样式替换原样式。
999
+ 原样式名与新样式名之间用 -> 连接,您可以同时替换多个样式,每个替换之间用英文逗号 , 分隔。
1000
+ 格式如:原样式名->新样式名,原样式名2->新样式名2,...
1001
+ \`\`\`webgal
1002
+ ; 将角色名背景替换为红色,前提是在 UI 模板里写了新样式
1003
+ applyStyle:TextBox_ShowName_Background->TextBox_ShowName_Background_Red;
1004
+ 角色名:这是一句话;
1005
+ ; 同时替换多个样式
1006
+ applyStyle:TextBox_ShowName_Background->TextBox_ShowName_Background_Green,TextBox_main->TextBox_main_Black;
1007
+ 无论原样式被替换为什么新样式,替换样式依旧是原样式名在前;
1008
+ applyStyle:原样式名->新样式名1;
1009
+ applyStyle:新样式名1->新样式名2; 错误
1010
+ applyStyle:原样式名->新样式名2;
1011
+ \`\`\``,
1012
+ detail: `applyStyle:<old_style_name>-><new_style_name>;`,
1013
+ insertText: "applyStyle:$1;$2"
1014
+ },
1015
+ wait: {
1016
+ type: commandType.wait,
1017
+ desc: "等待命令。",
1018
+ args: [],
1019
+ label: "wait",
1020
+ kind: CompletionItemKind.Function,
1021
+ documentation: `填写一个数字,作为等待时间,单位为毫秒。
1022
+ 有时出于演出效果的需要,可能需要等待一段时间,再执行下一句。
1023
+ \`\`\`webgal
1024
+ 角色A:让我想想;
1025
+ 角色A:......;
1026
+ wait:5000; 等待 5 秒
1027
+ 角色A:想不出来,算了。;
1028
+ \`\`\``,
1029
+ detail: `wait:<number>;`,
1030
+ insertText: "wait:$1;$2"
1031
+ },
1032
+ callSteam: {
1033
+ type: commandType.callSteam,
1034
+ desc: "调用 Steam 命令。",
1035
+ args: [argsMap.achievementId],
1036
+ label: "callSteam",
1037
+ kind: CompletionItemKind.Function,
1038
+ documentation: `调用 Steam 命令。
1039
+ \`\`\`webgal
1040
+ callSteam:xxx;
1041
+ \`\`\``,
1042
+ detail: `callSteam: <...arguments>;`,
1043
+ insertText: "callSteam:$1;$2"
1044
+ }
1045
+ };
1046
+ const WebGALKeywordsKeys = Object.keys(WebGALKeywords);
1047
+ const WebgGALKeywordsCompletionMap = WebGALKeywordsKeys.map((v) => ({
1048
+ label: WebGALKeywords[v]?.label || v,
1049
+ kind: WebGALKeywords[v]?.kind || CompletionItemKind.Keyword,
1050
+ documentation: {
1051
+ kind: MarkupKind.Markdown,
1052
+ value: WebGALKeywords[v].documentation?.replace(/\t+/g, "") || WebGALKeywords[v].desc
1053
+ },
1054
+ detail: WebGALKeywords[v]?.detail || WebGALKeywords[v].desc,
1055
+ insertText: WebGALKeywords[v]?.insertText || v,
1056
+ insertTextFormat: InsertTextFormat.Snippet
1057
+ }));
1058
+ const WebGALConfigMap = {
1059
+ Game_name: {
1060
+ desc: "游戏名称",
1061
+ require: true
1062
+ },
1063
+ Game_key: {
1064
+ desc: "游戏识别码,长度 6-10 字符,不要与别的游戏重复",
1065
+ require: true
1066
+ },
1067
+ Title_img: {
1068
+ desc: "标题图片,放在 background 文件夹",
1069
+ require: true
1070
+ },
1071
+ Title_bgm: {
1072
+ desc: "标题背景音乐,放在 bgm 文件夹",
1073
+ require: true
1074
+ },
1075
+ Game_Logo: { desc: "游戏 Logo,可以显示多个,用 | 分割" },
1076
+ Enable_Appreciation: { desc: "是否启用鉴赏功能,包括 CG 和背景音乐鉴赏。" },
1077
+ Default_Language: { desc: "默认语言,可设置为 'zh_CN', 'zh_TW', 'en', 'ja', 'fr', 'de'" },
1078
+ Show_panic: { desc: "是否启用紧急回避功能,设置为 true 或 false" },
1079
+ Legacy_Expression_Blend_Mode: { desc: "是否启用 Live2D 的旧表情混合模式,设置为 true 或 false" }
1080
+ };
1081
+ const WebGALConfigCompletionMap = Object.fromEntries(Object.entries(WebGALConfigMap).map(([key, token]) => {
1082
+ const label = key;
1083
+ const documentation = token?.documentation ?? token?.desc ?? "";
1084
+ const detail = `${key}:<value>;`;
1085
+ const insertText = `${key}:$1;`;
1086
+ return [key, {
1087
+ ...token,
1088
+ label,
1089
+ kind: CompletionItemKind.Function,
1090
+ documentation,
1091
+ detail,
1092
+ insertText,
1093
+ insertTextFormat: InsertTextFormat.Snippet
1094
+ }];
1095
+ }));
1096
+
1097
+ //#endregion
1098
+ //#region src/utils/resources.ts
1099
+ var ResourceType = /* @__PURE__ */ function(ResourceType) {
1100
+ ResourceType["animation"] = "animation";
1101
+ ResourceType["background"] = "background";
1102
+ ResourceType["bgm"] = "bgm";
1103
+ ResourceType["figure"] = "figure";
1104
+ ResourceType["scene"] = "scene";
1105
+ ResourceType["tex"] = "tex";
1106
+ ResourceType["video"] = "video";
1107
+ ResourceType["vocal"] = "vocal";
1108
+ return ResourceType;
1109
+ }(ResourceType || {});
1110
+ const resourceExtsMap = {
1111
+ animation: ["json"],
1112
+ background: [
1113
+ "png",
1114
+ "jpg",
1115
+ "jpeg",
1116
+ "webp",
1117
+ "mp4",
1118
+ "webm",
1119
+ "mkv"
1120
+ ],
1121
+ bgm: [
1122
+ "mp3",
1123
+ "ogg",
1124
+ "wav"
1125
+ ],
1126
+ figure: [
1127
+ "png",
1128
+ "jpg",
1129
+ "jpeg",
1130
+ "webp",
1131
+ "json"
1132
+ ],
1133
+ scene: ["txt"],
1134
+ tex: ["png", "webp"],
1135
+ video: [
1136
+ "mp4",
1137
+ "webm",
1138
+ "mkv"
1139
+ ],
1140
+ vocal: [
1141
+ "mp3",
1142
+ "ogg",
1143
+ "wav"
1144
+ ]
1145
+ };
1146
+ const resourcesMap = {
1147
+ [WebGALKeywords.changeBg.label]: ResourceType.background,
1148
+ [WebGALKeywords.changeFigure.label]: ResourceType.figure,
1149
+ [WebGALKeywords.bgm.label]: ResourceType.bgm,
1150
+ [WebGALKeywords.playVideo.label]: ResourceType.video,
1151
+ [WebGALKeywords.miniAvatar.label]: ResourceType.figure,
1152
+ [WebGALKeywords.unlockCg.label]: ResourceType.background,
1153
+ [WebGALKeywords.unlockBgm.label]: ResourceType.bgm,
1154
+ [WebGALKeywords.playEffect.label]: ResourceType.vocal,
1155
+ [WebGALKeywords.playEffect.label]: ResourceType.vocal,
1156
+ [WebGALKeywords.changeScene.label]: ResourceType.scene,
1157
+ [WebGALKeywords.callScene.label]: ResourceType.scene,
1158
+ [WebGALKeywords.choose.label]: ResourceType.scene
1159
+ };
1160
+ function getTypeDirectory(command, fileName) {
1161
+ const ext = fileName.split(".").pop();
1162
+ for (const type in resourcesMap) {
1163
+ const current = resourcesMap[type];
1164
+ if (command == type && resourceExtsMap[current].includes(ext)) return current;
1165
+ }
1166
+ for (const type in resourcesMap) {
1167
+ const current = resourcesMap[type];
1168
+ if (resourceExtsMap[current].includes(ext)) return current;
1169
+ }
1170
+ return "scene";
1171
+ }
1172
+
1173
+ //#endregion
1174
+ //#region src/events/onCompletion.ts
1175
+ async function provideCompletionItems(document, position, connection) {
1176
+ const file_name = document.uri;
1177
+ const documentTextArray = document.getText().split("\n");
1178
+ const { token } = findTokenRange(document, position);
1179
+ const CompletionItemSuggestions = [];
1180
+ if (file_name.endsWith("/game/config.txt")) {
1181
+ const completionItems = [];
1182
+ for (const key in WebGALConfigCompletionMap) {
1183
+ const keyData = WebGALConfigCompletionMap[key];
1184
+ if (key.toLowerCase().includes(token.toLowerCase())) completionItems.push(keyData);
1185
+ }
1186
+ return completionItems;
1187
+ }
1188
+ const findWordWithPattern = getPatternAtPosition(document, position, /\$(stage|userData)(?:\.[\w-]*)*/);
1189
+ const isStateMap = (value) => {
1190
+ return typeof value.key === "string" && typeof value.description === "string";
1191
+ };
1192
+ if (findWordWithPattern) {
1193
+ const { replaceRange, fullSegments, querySegments, prefix } = getStageCompletionContext(document, position, findWordWithPattern);
1194
+ const info = await connection.sendRequest("client/goPropertyDoc", querySegments.length ? querySegments : fullSegments);
1195
+ if (info) {
1196
+ delete info.__WG$key;
1197
+ delete info.__WG$description;
1198
+ if (!isStateMap(info)) for (const key in info) {
1199
+ if (prefix && !key.includes(prefix)) continue;
1200
+ const current = info[key];
1201
+ CompletionItemSuggestions.push({
1202
+ label: key,
1203
+ kind: CompletionItemKind.Constant,
1204
+ documentation: current.description,
1205
+ filterText: key,
1206
+ textEdit: {
1207
+ range: replaceRange,
1208
+ newText: key
1209
+ }
1210
+ });
1211
+ }
1212
+ else {
1213
+ if (prefix && !info.key.includes(prefix)) return CompletionItemSuggestions;
1214
+ CompletionItemSuggestions.push({
1215
+ label: info.key,
1216
+ kind: CompletionItemKind.Constant,
1217
+ documentation: info.description,
1218
+ filterText: info.key,
1219
+ textEdit: {
1220
+ range: replaceRange,
1221
+ newText: info.key
1222
+ }
1223
+ });
1224
+ }
1225
+ }
1226
+ return CompletionItemSuggestions;
1227
+ }
1228
+ const wordMeta = getWordAtPosition(document, Position.create(position.line, 0));
1229
+ const currentLine = documentTextArray[position.line];
1230
+ const commandType = currentLine.substring(0, currentLine.indexOf(":") !== -1 ? currentLine.indexOf(":") : currentLine.indexOf(";"));
1231
+ const isSayCommandType = !resourcesMap[commandType];
1232
+ if (token.startsWith("./") || !!~token.indexOf("/") || Object.keys(resourcesMap).includes(commandType) || token.startsWith("-")) {
1233
+ const resourceBaseDir = isSayCommandType ? "vocal" : resourcesMap[commandType];
1234
+ if (resourceBaseDir) {
1235
+ const dirs = await connection.sendRequest("client/getResourceDirectory", [resourceBaseDir, token]);
1236
+ if (dirs) for (const dir of dirs) CompletionItemSuggestions.push({
1237
+ label: dir.name,
1238
+ kind: dir.isDirectory ? CompletionItemKind.Folder : CompletionItemKind.File
1239
+ });
1240
+ }
1241
+ if (wordMeta && token.startsWith("-")) {
1242
+ const uniqueData = [...(WebGALKeywords[wordMeta.word] ?? WebGALKeywords["say"]).args, ...globalArgs].map((arg) => {
1243
+ return {
1244
+ label: arg.arg,
1245
+ kind: CompletionItemKind.Constant,
1246
+ documentation: arg.desc,
1247
+ detail: arg.desc
1248
+ };
1249
+ }).filter((parentItem, index, self) => index === self.findIndex((item) => item.label === parentItem.label));
1250
+ CompletionItemSuggestions.push(...uniqueData);
1251
+ }
1252
+ return CompletionItemSuggestions;
1253
+ }
1254
+ if (token) {
1255
+ updateGlobalMap(documentTextArray);
1256
+ const currentPool = GlobalMap.setVar;
1257
+ for (const key in currentPool) if (key.includes(token)) {
1258
+ const latest = GlobalMap.setVar[key][GlobalMap.setVar[key].length - 1];
1259
+ CompletionItemSuggestions.push({
1260
+ label: key,
1261
+ kind: CompletionItemKind.Variable,
1262
+ documentation: latest.desc
1263
+ });
1264
+ }
1265
+ }
1266
+ if (!wordMeta && position.character === 0 || token && !~currentLine.indexOf(":")) CompletionItemSuggestions.push(...WebgGALKeywordsCompletionMap);
1267
+ CompletionItemSuggestions.push({
1268
+ label: "$stage",
1269
+ kind: CompletionItemKind.Variable
1270
+ }, {
1271
+ label: "$userData",
1272
+ kind: CompletionItemKind.Variable
1273
+ });
1274
+ return CompletionItemSuggestions;
1275
+ }
1276
+
1277
+ //#endregion
1278
+ //#region src/events/onDefinition.ts
1279
+ function provideDefinition(document, position) {
1280
+ const text = document.getText();
1281
+ const findWord = getWordAtPosition(document, position);
1282
+ const definitionLinks = [];
1283
+ if (!findWord) return definitionLinks;
1284
+ const documentTextArray = text.split("\n");
1285
+ const currentLine = documentTextArray[position.line];
1286
+ const commandType = currentLine.substring(0, currentLine.indexOf(":") !== -1 ? currentLine.indexOf(":") : currentLine.indexOf(";"));
1287
+ updateGlobalMap(documentTextArray);
1288
+ const jumpLabelMap = GlobalMap.label;
1289
+ const setVarMap = GlobalMap.setVar;
1290
+ const targetPool = ["jumpLabel", "choose"].includes(commandType) ? jumpLabelMap : setVarMap;
1291
+ if (!targetPool) return definitionLinks;
1292
+ const targetPoolArray = targetPool[findWord.word];
1293
+ if (!targetPoolArray) return definitionLinks;
1294
+ for (const current of targetPoolArray) if (current.word === findWord.word) definitionLinks.push(LocationLink.create(document.uri, Range.create(Position.create(position.line, findWord.start), Position.create(position.line, findWord.end)), Range.create(current.position, current.position), Range.create(Position.create(0, 0), Position.create(0, 0))));
1295
+ return definitionLinks;
1296
+ }
1297
+
1298
+ //#endregion
1299
+ //#region src/events/onDocumentLinks.ts
1300
+ async function provideDocumentLinks(document, connection) {
1301
+ const documentTextArray = document.getText().split("\n");
1302
+ const pathArray = document.uri.split("/");
1303
+ const currentDirectory = await connection.sendRequest("client/currentDirectory");
1304
+ const documentLinks = [];
1305
+ for (let i = 0; i < documentTextArray.length; i++) {
1306
+ const currentLine = documentTextArray[i];
1307
+ let startText = currentLine.substring(0, currentLine.indexOf(":") !== -1 ? currentLine.indexOf(":") : currentLine.indexOf(";"));
1308
+ startText = startText.startsWith(";") ? startText.substring(1) : startText;
1309
+ let match;
1310
+ const regex = /\$?\{?(\w+)\.(\w+)\}?/g;
1311
+ while (match = regex.exec(currentLine)) {
1312
+ if (match[0].startsWith("$")) continue;
1313
+ const matchText = match[0];
1314
+ const pathName = pathArray[pathArray.length - 3 > 0 ? pathArray.length - 3 : pathArray.length - 2];
1315
+ const isConfig = pathArray[pathArray.length - 1] === "config.txt" && pathArray[pathArray.length - 2] === "game" && pathName === pathArray[pathArray.length - 3];
1316
+ const dirResources = getTypeDirectory(match.input.substring(0, match.input.indexOf(":")), matchText);
1317
+ let targetPath;
1318
+ if (isConfig) targetPath = await connection.sendRequest("client/FJoin", currentDirectory + "/");
1319
+ else targetPath = await connection.sendRequest("client/FJoin", currentDirectory + "/" + dirResources);
1320
+ let basePath = await connection.sendRequest("client/FJoin", targetPath + "/" + matchText);
1321
+ if (!await connection.sendRequest("client/FStat", basePath)) basePath = await connection.sendRequest("client/findFile", [currentDirectory, matchText]);
1322
+ documentLinks.push({
1323
+ target: "file:///" + basePath,
1324
+ range: Range.create(Position.create(i, match.index), Position.create(i, match.index + matchText.length)),
1325
+ tooltip: basePath
1326
+ });
1327
+ if (regex.lastIndex === match.index) regex.lastIndex++;
1328
+ }
1329
+ }
1330
+ return [...documentLinks];
1331
+ }
1332
+
1333
+ //#endregion
1334
+ //#region src/events/onFoldingRanges.ts
1335
+ function provideFoldingRanges(doc) {
1336
+ const docText = doc.getText();
1337
+ const foldingRanges = [];
1338
+ const regex = /label:([\s\S]*?)(?=(?:\r?\n|^)end|(?:\r?\n|^)label:|$)/g;
1339
+ let match;
1340
+ while (match = regex.exec(docText)) if (match !== null) {
1341
+ const startLine = doc.positionAt(match.index).line;
1342
+ const endPos = doc.positionAt(match.index + match[0].length);
1343
+ let endLine = endPos.line;
1344
+ if (endPos.character === 0) endLine = endPos.line - 1;
1345
+ if (endLine > startLine) foldingRanges.push({
1346
+ startLine,
1347
+ endLine,
1348
+ collapsedText: match[1].split("\n")[0].replace(/;/g, "").trim() || "...",
1349
+ kind: FoldingRangeKind.Region
1350
+ });
1351
+ }
1352
+ return foldingRanges;
1353
+ }
1354
+
1355
+ //#endregion
1356
+ //#region src/events/onHover.ts
1357
+ async function provideHover(document, position, connection) {
1358
+ const file_name = document.uri;
1359
+ const documentTextArray = document.getText().split("\n");
1360
+ const currentLine = documentTextArray[position.line];
1361
+ const commandType = currentLine.substring(0, currentLine.indexOf(":") !== -1 ? currentLine.indexOf(":") : currentLine.indexOf(";"));
1362
+ let findWordWithPattern = getPatternAtPosition(document, position, /\{([^}]*)\}/);
1363
+ findWordWithPattern = getPatternAtPosition(document, position, /(?<=-)[\w]+/);
1364
+ if (findWordWithPattern) {
1365
+ const argsData = argsMap[findWordWithPattern.text];
1366
+ if (argsData) return {
1367
+ contents: {
1368
+ kind: MarkupKind.Markdown,
1369
+ value: [
1370
+ `### ${argsData.label}`,
1371
+ `${argsData?.documentation}`,
1372
+ `\`${argsData.detail}\``
1373
+ ].join("\n\n")
1374
+ },
1375
+ range: Range.create(findWordWithPattern.startPos, findWordWithPattern.endPos)
1376
+ };
1377
+ }
1378
+ findWordWithPattern = getPatternAtPosition(document, position, /\$(stage|userData)((?:\.[\w-]+)+|\b)/);
1379
+ if (findWordWithPattern) {
1380
+ const strArray = findWordWithPattern.text.slice(1).split(".");
1381
+ const info = await connection.sendRequest("client/goPropertyDoc", strArray);
1382
+ if (info) return {
1383
+ contents: {
1384
+ kind: MarkupKind.Markdown,
1385
+ value: [
1386
+ `### ${info.key ?? findWordWithPattern.text}`,
1387
+ `\`${info.__WG$key ?? info.type?.key ?? ""}\``,
1388
+ `${info?.description ?? info.__WG$description}`
1389
+ ].join("\n\n")
1390
+ },
1391
+ range: Range.create(findWordWithPattern.startPos, findWordWithPattern.endPos)
1392
+ };
1393
+ }
1394
+ const findWord = getWordAtPosition(document, position);
1395
+ if (!findWord) return { contents: [] };
1396
+ if (file_name.endsWith("/game/config.txt")) {
1397
+ for (const i in WebGALConfigMap) {
1398
+ const kw_val = WebGALConfigMap[i];
1399
+ if (findWord.word === i) return { contents: {
1400
+ kind: MarkupKind.Markdown,
1401
+ value: [`**${i}**`, `\n${kw_val.desc}`].join("\n")
1402
+ } };
1403
+ }
1404
+ return { contents: [] };
1405
+ }
1406
+ const maybeCommandMap = WebGALKeywords[commandType];
1407
+ if (maybeCommandMap) {
1408
+ for (const key in WebGALKeywords) if (findWord.word === key && commandType === key) return { contents: {
1409
+ kind: MarkupKind.Markdown,
1410
+ value: [
1411
+ `### ${key}`,
1412
+ maybeCommandMap.documentation?.replace(/\t+/g, "") || maybeCommandMap.desc,
1413
+ `${WebGALCommandPrefix}${key}`
1414
+ ].join("\n\n")
1415
+ } };
1416
+ }
1417
+ updateGlobalMap(documentTextArray);
1418
+ if (findWord && `{${findWord.word}}` !== "{}") {
1419
+ const current = GlobalMap.setVar[findWord.word];
1420
+ if (!current || current.length <= 0) return { contents: [] };
1421
+ const currentVariable = current[current.length - 1];
1422
+ const hoverContent = [`### ${findWord.word}`];
1423
+ if (!currentVariable) return { contents: [] };
1424
+ if (currentVariable.desc && currentVariable.desc.length > 0) {
1425
+ hoverContent.push("<hr>");
1426
+ hoverContent.push(currentVariable.desc);
1427
+ }
1428
+ hoverContent.push("<hr>");
1429
+ if (findWord.word in GlobalMap.setVar) {
1430
+ hoverContent.push(`Position: ${currentVariable.position?.line + 1},${currentVariable.position?.character + 1}`);
1431
+ hoverContent.push("```webgal");
1432
+ hoverContent.push(`${currentVariable.input?.replace(/\t\r\n/g, "")}\n\n\`\`\``);
1433
+ }
1434
+ return { contents: {
1435
+ kind: MarkupKind.Markdown,
1436
+ value: hoverContent.join("\n\n")
1437
+ } };
1438
+ }
1439
+ return { contents: [] };
1440
+ }
1441
+
1442
+ //#endregion
1443
+ //#region src/events/onDiagnostics.ts
1444
+ async function provideDiagnostics(document, connection) {
1445
+ return validateTextDocument(connection, document);
1446
+ }
1447
+
1448
+ //#endregion
1449
+ //#region src/events/index.ts
1450
+ function registerConnectionHandlers(documents, connection) {
1451
+ onInitialized_default(documents, connection);
1452
+ onDid_default(documents, connection);
1453
+ }
1454
+ function createWebgalService(connection) {
1455
+ return {
1456
+ name: "webgal-service",
1457
+ capabilities: {
1458
+ completionProvider: { triggerCharacters: [
1459
+ ".",
1460
+ ":",
1461
+ "-",
1462
+ "/"
1463
+ ] },
1464
+ hoverProvider: true,
1465
+ diagnosticProvider: {
1466
+ interFileDependencies: false,
1467
+ workspaceDiagnostics: false
1468
+ },
1469
+ documentLinkProvider: { resolveProvider: true },
1470
+ foldingRangeProvider: true,
1471
+ definitionProvider: true
1472
+ },
1473
+ create() {
1474
+ return {
1475
+ async provideCompletionItems(document, position, _context, _token) {
1476
+ return {
1477
+ isIncomplete: false,
1478
+ items: await provideCompletionItems(document, position, connection)
1479
+ };
1480
+ },
1481
+ async provideHover(document, position, _token) {
1482
+ return provideHover(document, position, connection);
1483
+ },
1484
+ provideDefinition(document, position, _token) {
1485
+ return provideDefinition(document, position);
1486
+ },
1487
+ async provideDocumentLinks(document, _token) {
1488
+ return provideDocumentLinks(document, connection);
1489
+ },
1490
+ provideFoldingRanges(document, _token) {
1491
+ return provideFoldingRanges(document);
1492
+ },
1493
+ async provideDiagnostics(document, _token) {
1494
+ return provideDiagnostics(document, connection);
1495
+ }
1496
+ };
1497
+ }
1498
+ };
1499
+ }
1500
+
1501
+ //#endregion
1502
+ //#region src/events/onInitialize.ts
1503
+ function applyClientCapabilities(params) {
1504
+ const capabilities = params.capabilities;
1505
+ StateConfig.hasConfigurationCapability = !!(capabilities.workspace && !!capabilities.workspace.configuration);
1506
+ StateConfig.hasWorkspaceFolderCapability = !!(capabilities.workspace && !!capabilities.workspace.workspaceFolders);
1507
+ StateConfig.hasDiagnosticRelatedInformationCapability = !!(capabilities.textDocument && capabilities.textDocument.publishDiagnostics && capabilities.textDocument.publishDiagnostics.relatedInformation);
1508
+ }
1509
+ function applyServerCapabilities(result) {
1510
+ result.capabilities.textDocumentSync = TextDocumentSyncKind.Incremental;
1511
+ result.capabilities.completionProvider = { triggerCharacters: [
1512
+ ".",
1513
+ ":",
1514
+ "-",
1515
+ "/"
1516
+ ] };
1517
+ result.capabilities.hoverProvider = true;
1518
+ result.capabilities.diagnosticProvider = {
1519
+ identifier: "webgal-diagnostics",
1520
+ interFileDependencies: false,
1521
+ workspaceDiagnostics: false
1522
+ };
1523
+ result.capabilities.documentLinkProvider = { resolveProvider: true };
1524
+ result.capabilities.foldingRangeProvider = true;
1525
+ result.capabilities.definitionProvider = true;
1526
+ if (StateConfig.hasWorkspaceFolderCapability) result.capabilities.workspace = { workspaceFolders: { supported: true } };
1527
+ }
1528
+
1529
+ //#endregion
1530
+ export { registerConnectionHandlers as i, applyServerCapabilities as n, createWebgalService as r, applyClientCapabilities as t };