@webgal/language-service 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,293 @@
1
+ //#region src/index.ts
2
+ const normalizePath = (input) => {
3
+ let path = input.replace(/\\/g, "/").replace(/\/+/g, "/");
4
+ if (path === "") return "/";
5
+ if (!path.startsWith("/")) path = "/" + path;
6
+ if (path.length > 1 && path.endsWith("/")) path = path.slice(0, -1);
7
+ return path;
8
+ };
9
+ const joinPaths = (...parts) => {
10
+ return normalizePath(parts.filter((part) => part && part !== "/").join("/"));
11
+ };
12
+ const uriToPath = (uriString) => {
13
+ if (uriString.startsWith("file://")) {
14
+ const stripped = uriString.replace(/^file:\/*/i, "/");
15
+ const decoded = decodeURIComponent(stripped).replace(/\\/g, "/");
16
+ if (/^\/[a-zA-Z]:\//.test(decoded)) return decoded.slice(1);
17
+ return normalizePath(decoded);
18
+ }
19
+ return normalizePath(uriString);
20
+ };
21
+ const toVfsPath = (value) => value.startsWith("file://") ? uriToPath(value) : value;
22
+ function createMemoryFileSystem(options) {
23
+ const root = normalizePath(options?.root ?? "/");
24
+ let rootEntry = options?.tree && options.tree.type === "dir" ? options.tree : {
25
+ type: "dir",
26
+ children: {}
27
+ };
28
+ const listeners = /* @__PURE__ */ new Set();
29
+ const emit = (changes) => {
30
+ for (const listener of listeners) listener(changes);
31
+ };
32
+ const resolveToSegments = (path) => {
33
+ let resolved = normalizePath(path);
34
+ if (root !== "/" && resolved.startsWith(root)) resolved = resolved.slice(root.length);
35
+ if (resolved.startsWith("/")) resolved = resolved.slice(1);
36
+ return resolved ? resolved.split("/") : [];
37
+ };
38
+ const ensureDirectoryEntry = (segments) => {
39
+ let current = rootEntry;
40
+ for (const segment of segments) {
41
+ if (current.type !== "dir") throw new Error("Path segment is not a directory");
42
+ const directory = current;
43
+ const existing = directory.children[segment];
44
+ if (!existing) {
45
+ const created = {
46
+ type: "dir",
47
+ children: {}
48
+ };
49
+ directory.children[segment] = created;
50
+ current = created;
51
+ continue;
52
+ }
53
+ if (existing.type !== "dir") throw new Error("Path segment is not a directory");
54
+ current = existing;
55
+ }
56
+ if (current.type !== "dir") throw new Error("Path is not a directory");
57
+ return current;
58
+ };
59
+ const getEntry = (path) => {
60
+ const segments = resolveToSegments(path);
61
+ let current = rootEntry;
62
+ for (const segment of segments) {
63
+ if (current.type !== "dir") return null;
64
+ const next = current.children[segment];
65
+ if (!next) return null;
66
+ current = next;
67
+ }
68
+ return current;
69
+ };
70
+ const readDirectory = async (path) => {
71
+ const entry = getEntry(path);
72
+ if (!entry || entry.type !== "dir") return null;
73
+ return Object.entries(entry.children).map(([name, child]) => ({
74
+ name,
75
+ isDirectory: child.type === "dir"
76
+ }));
77
+ };
78
+ const readFile = async (path) => {
79
+ const entry = getEntry(path);
80
+ if (!entry || entry.type !== "file") return null;
81
+ return entry.content;
82
+ };
83
+ const stat = async (path) => {
84
+ const entry = getEntry(path);
85
+ if (!entry) return null;
86
+ return {
87
+ isFile: entry.type === "file",
88
+ isDirectory: entry.type === "dir"
89
+ };
90
+ };
91
+ const findFile = async (startPath, targetName) => {
92
+ const startEntry = getEntry(startPath);
93
+ if (!startEntry || startEntry.type !== "dir") return null;
94
+ const stack = [{
95
+ path: normalizePath(startPath),
96
+ entry: startEntry
97
+ }];
98
+ while (stack.length) {
99
+ const current = stack.pop();
100
+ if (!current) break;
101
+ if (current.entry.type === "dir") for (const [name, child] of Object.entries(current.entry.children)) {
102
+ const nextPath = joinPaths(current.path, name);
103
+ if (child.type === "file" && name === targetName) return nextPath;
104
+ if (child.type === "dir") stack.push({
105
+ path: nextPath,
106
+ entry: child
107
+ });
108
+ }
109
+ }
110
+ return null;
111
+ };
112
+ const getResourceDirectory = async (urls) => {
113
+ return readDirectory(joinPaths(root, ...urls));
114
+ };
115
+ const getAllTextWithScene = async () => {
116
+ const scenePath = joinPaths(root, "scene");
117
+ const sceneEntry = getEntry(scenePath);
118
+ if (!sceneEntry || sceneEntry.type !== "dir") return null;
119
+ const map = {};
120
+ for (const [name, child] of Object.entries(sceneEntry.children)) if (child.type === "file" && name.endsWith(".txt")) {
121
+ const fullPath = joinPaths(scenePath, name);
122
+ map[name] = {
123
+ path: fullPath,
124
+ name,
125
+ text: child.content,
126
+ fullPath
127
+ };
128
+ }
129
+ return map;
130
+ };
131
+ const getTree = () => rootEntry;
132
+ const setTree = (tree) => {
133
+ rootEntry = tree.type === "dir" ? tree : {
134
+ type: "dir",
135
+ children: {}
136
+ };
137
+ emit([{
138
+ type: "setTree",
139
+ tree: rootEntry
140
+ }]);
141
+ };
142
+ const writeFile = async (targetPath, content) => {
143
+ const segments = resolveToSegments(targetPath);
144
+ if (segments.length === 0) return;
145
+ const fileName = segments[segments.length - 1];
146
+ const parent = ensureDirectoryEntry(segments.slice(0, -1));
147
+ parent.children[fileName] = {
148
+ type: "file",
149
+ content
150
+ };
151
+ emit([{
152
+ type: "writeFile",
153
+ path: normalizePath(targetPath),
154
+ content
155
+ }]);
156
+ };
157
+ const mkdir = async (targetPath) => {
158
+ ensureDirectoryEntry(resolveToSegments(targetPath));
159
+ emit([{
160
+ type: "mkdir",
161
+ path: normalizePath(targetPath)
162
+ }]);
163
+ };
164
+ const deletePath = async (targetPath) => {
165
+ const segments = resolveToSegments(targetPath);
166
+ if (segments.length === 0) return;
167
+ const name = segments[segments.length - 1];
168
+ const parentSegments = segments.slice(0, -1);
169
+ const parentEntry = parentSegments.length === 0 ? rootEntry : getEntry(joinPaths(root, ...parentSegments));
170
+ if (!parentEntry || parentEntry.type !== "dir") return;
171
+ delete parentEntry.children[name];
172
+ emit([{
173
+ type: "deletePath",
174
+ path: normalizePath(targetPath)
175
+ }]);
176
+ };
177
+ const rename = async (from, to) => {
178
+ const fromSegments = resolveToSegments(from);
179
+ if (fromSegments.length === 0) return;
180
+ const fromName = fromSegments[fromSegments.length - 1];
181
+ const fromParentSegments = fromSegments.slice(0, -1);
182
+ const fromParentEntry = fromParentSegments.length === 0 ? rootEntry : getEntry(joinPaths(root, ...fromParentSegments));
183
+ if (!fromParentEntry || fromParentEntry.type !== "dir") return;
184
+ const entry = fromParentEntry.children[fromName];
185
+ if (!entry) return;
186
+ delete fromParentEntry.children[fromName];
187
+ const toSegments = resolveToSegments(to);
188
+ if (toSegments.length === 0) return;
189
+ const toName = toSegments[toSegments.length - 1];
190
+ const toParent = ensureDirectoryEntry(toSegments.slice(0, -1));
191
+ toParent.children[toName] = entry;
192
+ emit([{
193
+ type: "rename",
194
+ from: normalizePath(from),
195
+ to: normalizePath(to)
196
+ }]);
197
+ };
198
+ const applyChanges = async (changes) => {
199
+ for (const change of changes) {
200
+ if (change.type === "writeFile") {
201
+ await writeFile(change.path, change.content);
202
+ continue;
203
+ }
204
+ if (change.type === "mkdir") {
205
+ await mkdir(change.path);
206
+ continue;
207
+ }
208
+ if (change.type === "deletePath") {
209
+ await deletePath(change.path);
210
+ continue;
211
+ }
212
+ if (change.type === "rename") {
213
+ await rename(change.from, change.to);
214
+ continue;
215
+ }
216
+ if (change.type === "setTree") setTree(change.tree);
217
+ }
218
+ };
219
+ const onDidChange = (listener) => {
220
+ listeners.add(listener);
221
+ return () => {
222
+ listeners.delete(listener);
223
+ };
224
+ };
225
+ return {
226
+ root,
227
+ currentDirectory: () => root,
228
+ join: (...parts) => joinPaths(...parts),
229
+ stat,
230
+ readDirectory,
231
+ readFile,
232
+ findFile,
233
+ getResourceDirectory,
234
+ getAllTextWithScene,
235
+ getTree,
236
+ setTree,
237
+ writeFile,
238
+ deletePath,
239
+ mkdir,
240
+ rename,
241
+ applyChanges,
242
+ onDidChange
243
+ };
244
+ }
245
+ function createWebgalClientHandlers(options) {
246
+ const normalizeChange = (change) => {
247
+ if (change.type === "writeFile") return {
248
+ type: "writeFile",
249
+ path: toVfsPath(change.path),
250
+ content: change.content
251
+ };
252
+ if (change.type === "deletePath") return {
253
+ type: "deletePath",
254
+ path: toVfsPath(change.path)
255
+ };
256
+ if (change.type === "mkdir") return {
257
+ type: "mkdir",
258
+ path: toVfsPath(change.path)
259
+ };
260
+ if (change.type === "rename") return {
261
+ type: "rename",
262
+ from: toVfsPath(change.from),
263
+ to: toVfsPath(change.to)
264
+ };
265
+ return change;
266
+ };
267
+ return {
268
+ "client/showTip": options.showTip ?? (() => null),
269
+ "client/currentDirectory": () => options.vfs.currentDirectory(),
270
+ "client/FJoin": (args) => options.vfs.join(...Array.isArray(args) ? args : [args]),
271
+ "client/FStat": (path) => options.vfs.stat(path),
272
+ "client/findFile": ([startPath, targetName]) => options.vfs.findFile(startPath, targetName),
273
+ "client/goPropertyDoc": options.goPropertyDoc ?? (() => null),
274
+ "client/readDirectory": (uriString) => options.vfs.readDirectory(uriToPath(uriString)),
275
+ "client/getAllTextWithScene": () => options.vfs.getAllTextWithScene(),
276
+ "client/getResourceDirectory": (urls) => options.vfs.getResourceDirectory(urls),
277
+ "client/vfs/getTree": () => options.vfs.getTree(),
278
+ "client/vfs/setTree": (tree) => options.vfs.setTree(tree),
279
+ "client/vfs/readFile": (path) => options.vfs.readFile(toVfsPath(path)),
280
+ "client/vfs/writeFile": ({ path, content }) => options.vfs.writeFile(toVfsPath(path), content),
281
+ "client/vfs/deletePath": (path) => options.vfs.deletePath(toVfsPath(path)),
282
+ "client/vfs/mkdir": (path) => options.vfs.mkdir(toVfsPath(path)),
283
+ "client/vfs/rename": ({ from, to }) => options.vfs.rename(toVfsPath(from), toVfsPath(to)),
284
+ "client/vfs/applyChanges": (changes) => options.vfs.applyChanges(changes.map(normalizeChange)),
285
+ ...options.overrides ?? {}
286
+ };
287
+ }
288
+ function registerWebgalClientHandlers(client, handlers) {
289
+ for (const [method, handler] of Object.entries(handlers)) client.onRequest(method, handler);
290
+ }
291
+
292
+ //#endregion
293
+ export { createMemoryFileSystem, createWebgalClientHandlers, registerWebgalClientHandlers };
@@ -0,0 +1,214 @@
1
+ //#region syntaxes/webgal.tmLanguage.json
2
+ var webgal_tmLanguage_default = {
3
+ $schema: "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
4
+ name: "WebGAL Script",
5
+ patterns: [{ "include": "#comment-line" }, { "include": "#statement" }],
6
+ repository: {
7
+ "argument-list": { "patterns": [{
8
+ "comment": ">1 arguments left, ie -kwarg0=val0 -kwarg1=val1 ...",
9
+ "match": "(\\s\\-)(.*?)(\\s\\-.*)$",
10
+ "captures": {
11
+ "1": { "patterns": [{ "include": "#operator" }] },
12
+ "2": { "patterns": [{ "include": "#parameter" }] },
13
+ "3": { "patterns": [{ "include": "#argument-list" }] }
14
+ }
15
+ }, {
16
+ "comment": "only one argument left, ie -kwarg0=val0",
17
+ "match": "(\\s\\-)(.*)",
18
+ "captures": {
19
+ "1": { "patterns": [{ "include": "#operator" }] },
20
+ "2": { "patterns": [{ "include": "#parameter" }] }
21
+ }
22
+ }] },
23
+ "character": {
24
+ "match": ".*",
25
+ "name": "entity.name.type.character.webgal"
26
+ },
27
+ "command": {
28
+ "match": ".*",
29
+ "name": "support.function.command.webgal"
30
+ },
31
+ "comment-line": {
32
+ "match": "\\;.*?$",
33
+ "name": "comment.line.webgal"
34
+ },
35
+ "operator": {
36
+ "match": "[\\:\\=\\<\\>\\|\\+\\-]",
37
+ "name": "keyword.operator.webgal"
38
+ },
39
+ "parameter": { "patterns": [{
40
+ "comment": "value only, ie val0",
41
+ "match": "(?!.*\\=).+",
42
+ "name": "variable.parameter.webgal"
43
+ }, {
44
+ "comment": "name and value, ie [kwarg0]=[val0]",
45
+ "match": "(.*?)(\\=)(.*?)$",
46
+ "captures": {
47
+ "1": { "name": "variable.parameter.webgal" },
48
+ "2": { "patterns": [{ "include": "#operator" }] },
49
+ "3": { "name": "variable.other.webgal" }
50
+ }
51
+ }] },
52
+ "utterance": { "patterns": [{
53
+ "comment": "utterance only",
54
+ "match": "(?!.*\\s\\-)(.+)",
55
+ "captures": { "0": { "name": "string.unquoted.utterance.webgal" } }
56
+ }, {
57
+ "comment": "utterance and argument list",
58
+ "match": "(.*?)(\\s\\-.*?)$",
59
+ "captures": {
60
+ "1": { "name": "string.unquoted.utterance.webgal" },
61
+ "2": { "patterns": [{ "include": "#argument-list" }] }
62
+ }
63
+ }] },
64
+ "character-colon": {
65
+ "comment": "[char]:[utt[ -args]][;cmt]",
66
+ "match": "^(?!(?:say|changeBg|changeFigure|bgm|playVideo|pixiPerform|pixiInit|intro|miniAvatar|changeScene|choose|end|setComplexAnimation|label|jumpLabel|setVar|callScene|showVars|unlockCg|unlockBgm|filmMode|setTextbox|setAnimation|playEffect|setTempAnimation|setTransform|setTransition|getUserInput|applyStyle|wait)\\:)(.*?)(\\:)([^\\;\\n]*?)($|\\;.*?$)",
67
+ "captures": {
68
+ "1": {
69
+ "name": "meta.character.webgal",
70
+ "patterns": [{ "include": "#character" }]
71
+ },
72
+ "2": { "patterns": [{ "include": "#operator" }] },
73
+ "3": { "patterns": [{ "include": "#utterance" }] },
74
+ "4": { "patterns": [{ "include": "#comment-line" }] }
75
+ }
76
+ },
77
+ "command-colon": {
78
+ "comment": "cmd:[arg0[ -args]][;cmt]",
79
+ "match": "^(say|changeBg|changeFigure|bgm|playVideo|pixiPerform|pixiInit|intro|miniAvatar|changeScene|choose|end|setComplexAnimation|label|jumpLabel|setVar|callScene|showVars|unlockCg|unlockBgm|filmMode|setTextbox|setAnimation|playEffect|setTempAnimation|setTransform|setTransition|getUserInput|applyStyle|wait)(\\:)([^\\;\\n]*?)($|\\;.*?$)",
80
+ "captures": {
81
+ "1": {
82
+ "name": "meta.command.webgal",
83
+ "patterns": [{ "include": "#command" }]
84
+ },
85
+ "2": { "patterns": [{ "include": "#operator" }] },
86
+ "3": { "patterns": [{ "include": "#argument-list" }] },
87
+ "4": { "patterns": [{ "include": "#comment-line" }] }
88
+ }
89
+ },
90
+ "command-semicolon": {
91
+ "comment": "cmd;[cmt]",
92
+ "match": "^(say|changeBg|changeFigure|bgm|playVideo|pixiPerform|pixiInit|intro|miniAvatar|changeScene|choose|end|setComplexAnimation|label|jumpLabel|setVar|callScene|showVars|unlockCg|unlockBgm|filmMode|setTextbox|setAnimation|playEffect|setTempAnimation|setTransform|setTransition|getUserInput|applyStyle|wait)($|\\;.*?$)",
93
+ "captures": {
94
+ "1": {
95
+ "name": "meta.command.webgal",
96
+ "patterns": [{ "include": "#command" }]
97
+ },
98
+ "2": { "patterns": [{ "include": "#comment-line" }] }
99
+ }
100
+ },
101
+ "utterance-semicolon": {
102
+ "comment": "utt[ -args];[cmt]",
103
+ "match": "^(?!(?:say|changeBg|changeFigure|bgm|playVideo|pixiPerform|pixiInit|intro|miniAvatar|changeScene|choose|end|setComplexAnimation|label|jumpLabel|setVar|callScene|showVars|unlockCg|unlockBgm|filmMode|setTextbox|setAnimation|playEffect|setTempAnimation|setTransform|setTransition|getUserInput|applyStyle|wait)\\;)([^\\:\\;\\n]+?)($|\\;.*?$)",
104
+ "captures": {
105
+ "1": { "patterns": [{ "include": "#utterance" }] },
106
+ "2": { "patterns": [{ "include": "#comment-line" }] }
107
+ }
108
+ },
109
+ "statement": { "patterns": [
110
+ { "include": "#character-colon" },
111
+ { "include": "#command-colon" },
112
+ { "include": "#command-semicolon" },
113
+ { "include": "#utterance-semicolon" }
114
+ ] }
115
+ },
116
+ scopeName: "source.webgal"
117
+ };
118
+
119
+ //#endregion
120
+ //#region syntaxes/webgal-config.tmLanguage.json
121
+ var webgal_config_tmLanguage_default = {
122
+ $schema: "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
123
+ name: "WebGAL Config",
124
+ patterns: [{ "include": "#comment-line" }, { "include": "#statement" }],
125
+ repository: {
126
+ "command": {
127
+ "match": ".+",
128
+ "name": "markup.quote.webgal-config"
129
+ },
130
+ "comment-line": {
131
+ "match": "\\;.*?$",
132
+ "name": "comment.line.webgal-config"
133
+ },
134
+ "operator": {
135
+ "match": "[\\:\\=\\<\\>\\|\\+\\-]",
136
+ "name": "keyword.operator.webgal-config"
137
+ },
138
+ "parameter": {
139
+ "match": ".+",
140
+ "name": "variable.other.webgal-config"
141
+ },
142
+ "command-colon": {
143
+ "comment": "cmd:[arg][;cmt]",
144
+ "match": "^(Game_Logo|Game_key|Game_name|Textbox_theme|Title_bgm|Title_img|Enable_Appreciation|Default_Language|Show_panic|Legacy_Expression_Blend_Mode)(\\:)([^\\;\\n]*?)($|\\;.*?$)",
145
+ "captures": {
146
+ "1": { "patterns": [{ "include": "#command" }] },
147
+ "2": { "patterns": [{ "include": "#operator" }] },
148
+ "3": { "patterns": [{ "include": "#parameter" }] },
149
+ "4": { "patterns": [{ "include": "#comment-line" }] }
150
+ }
151
+ },
152
+ "statement": { "patterns": [{ "include": "#command-colon" }] }
153
+ },
154
+ scopeName: "source.webgal-config"
155
+ };
156
+
157
+ //#endregion
158
+ //#region syntaxes/language-configuration.json
159
+ var language_configuration_default = {
160
+ comments: { "lineComment": { "comment": ";" } },
161
+ autoClosingPairs: [
162
+ {
163
+ "open": "{",
164
+ "close": "}"
165
+ },
166
+ {
167
+ "open": "[",
168
+ "close": "]"
169
+ },
170
+ {
171
+ "open": "(",
172
+ "close": ")"
173
+ },
174
+ {
175
+ "open": "'",
176
+ "close": "'",
177
+ "notIn": ["string", "comment"]
178
+ },
179
+ {
180
+ "open": "\"",
181
+ "close": "\"",
182
+ "notIn": ["string"]
183
+ },
184
+ {
185
+ "open": "`",
186
+ "close": "`",
187
+ "notIn": ["string", "comment"]
188
+ },
189
+ {
190
+ "open": ":",
191
+ "close": ";"
192
+ },
193
+ {
194
+ "open": ";area",
195
+ "close": ";endarea"
196
+ }
197
+ ],
198
+ folding: { "markers": {
199
+ "start": "^\\s*;\\s*area\\b",
200
+ "end": "^\\s*;\\s*endarea\\b"
201
+ } },
202
+ surroundingPairs: [
203
+ ["{", "}"],
204
+ ["[", "]"],
205
+ ["(", ")"],
206
+ ["'", "'"],
207
+ ["\"", "\""],
208
+ ["`", "`"]
209
+ ],
210
+ wordPattern: "([^:\\s])+\\.(.[^;\\s])|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)"
211
+ };
212
+
213
+ //#endregion
214
+ export { webgal_config_tmLanguage_default as n, webgal_tmLanguage_default as r, language_configuration_default as t };