vne-language 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/launch.json +11 -0
- package/.vscodeignore +6 -0
- package/CHANGELOG.md +9 -0
- package/README.md +0 -0
- package/language-configuration.json +22 -0
- package/package.json +48 -0
- package/src/build-grammar.js +85 -0
- package/src/collect-command-tokens.js +119 -0
- package/src/extension.js +125 -0
- package/src/load-commands.js +103 -0
- package/src/resolve-game-root.js +28 -0
- package/src/semantic-tokens.js +22 -0
- package/src/smoke-check.js +69 -0
- package/syntaxes/base.json +219 -0
- package/syntaxes/vne.json +223 -0
- package/vne-language-0.0.1.vsix +0 -0
- package/vsc-extension-quickstart.md +29 -0
package/.vscodeignore
ADDED
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"comments": {
|
|
3
|
+
"lineComment": "//"
|
|
4
|
+
},
|
|
5
|
+
"brackets": [
|
|
6
|
+
["{", "}"],
|
|
7
|
+
["[", "]"],
|
|
8
|
+
["(", ")"]
|
|
9
|
+
],
|
|
10
|
+
"autoClosingPairs": [
|
|
11
|
+
["{", "}"],
|
|
12
|
+
["[", "]"],
|
|
13
|
+
["(", ")"],
|
|
14
|
+
["\"", "\""]
|
|
15
|
+
],
|
|
16
|
+
"surroundingPairs": [
|
|
17
|
+
["{", "}"],
|
|
18
|
+
["[", "]"],
|
|
19
|
+
["(", ")"],
|
|
20
|
+
["\"", "\""]
|
|
21
|
+
]
|
|
22
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vne-language",
|
|
3
|
+
"displayName": "vne-language",
|
|
4
|
+
"description": "Syntax highlighting for .vne scenario files",
|
|
5
|
+
"version": "0.0.2",
|
|
6
|
+
"main": "./src/extension.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"package:vsix": "vsce package",
|
|
9
|
+
"publish:major": "npx @vnejs/monorepo publish major --access public",
|
|
10
|
+
"publish:minor": "npx @vnejs/monorepo publish minor --access public",
|
|
11
|
+
"publish:patch": "npx @vnejs/monorepo publish patch --access public"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"vscode": "^1.100.0"
|
|
15
|
+
},
|
|
16
|
+
"categories": [
|
|
17
|
+
"Programming Languages"
|
|
18
|
+
],
|
|
19
|
+
"activationEvents": [
|
|
20
|
+
"onLanguage:vnelang"
|
|
21
|
+
],
|
|
22
|
+
"exports": {
|
|
23
|
+
"./package.json": "./package.json",
|
|
24
|
+
"./syntaxes/vne.json": "./syntaxes/vne.json",
|
|
25
|
+
"./language-configuration.json": "./language-configuration.json",
|
|
26
|
+
"./load-commands": "./src/load-commands.js",
|
|
27
|
+
"./collect-command-tokens": "./src/collect-command-tokens.js",
|
|
28
|
+
"./semantic-tokens": "./src/semantic-tokens.js"
|
|
29
|
+
},
|
|
30
|
+
"contributes": {
|
|
31
|
+
"languages": [
|
|
32
|
+
{
|
|
33
|
+
"id": "vnelang",
|
|
34
|
+
"extensions": [
|
|
35
|
+
".vne"
|
|
36
|
+
],
|
|
37
|
+
"configuration": "./language-configuration.json"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"grammars": [
|
|
41
|
+
{
|
|
42
|
+
"language": "vnelang",
|
|
43
|
+
"scopeName": "source.vnelang",
|
|
44
|
+
"path": "./syntaxes/vne.json"
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2
|
+
|
|
3
|
+
const actionLiterals = (entry = {}) =>
|
|
4
|
+
Object.values(entry.actions || {}).filter((value) => typeof value === "string" && value.length > 0);
|
|
5
|
+
|
|
6
|
+
const mergeCommands = (manifests = []) => {
|
|
7
|
+
const modules = {};
|
|
8
|
+
const blocks = {};
|
|
9
|
+
|
|
10
|
+
for (const manifest of manifests) {
|
|
11
|
+
if (!manifest || typeof manifest !== "object") continue;
|
|
12
|
+
|
|
13
|
+
Object.assign(modules, manifest.modules || {});
|
|
14
|
+
Object.assign(blocks, manifest.blocks || {});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return { modules, blocks };
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const patternsForNamedCommands = (commands = {}) => {
|
|
21
|
+
const patterns = [];
|
|
22
|
+
|
|
23
|
+
for (const [name, entry] of Object.entries(commands)) {
|
|
24
|
+
const actions = actionLiterals(entry);
|
|
25
|
+
|
|
26
|
+
if (!actions.length) continue;
|
|
27
|
+
|
|
28
|
+
const namePattern = escapeRegExp(name);
|
|
29
|
+
const actionsPattern = actions.map(escapeRegExp).join("|");
|
|
30
|
+
|
|
31
|
+
patterns.push({
|
|
32
|
+
name: "keyword.control",
|
|
33
|
+
match: `(?<=\\b${namePattern}\\s+)(${actionsPattern})\\b`,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
patterns.push({
|
|
37
|
+
name: "entity.name.function",
|
|
38
|
+
match: `(?<=\\b${namePattern}\\s+(?:${actionsPattern})\\s+)[a-zA-Z0-9_/-]+`,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return patterns;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const lineKeywordPattern = (keywords = []) => {
|
|
46
|
+
const values = keywords.filter((value) => typeof value === "string" && value.length > 0);
|
|
47
|
+
|
|
48
|
+
if (!values.length) return null;
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
name: "keyword.control",
|
|
52
|
+
match: `(?<=\\s\\|\\s.*)(${values.map(escapeRegExp).join("|")})(?=\\s|$)`,
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build TextMate grammar from structural base + SCENARIO_COMMANDS manifests.
|
|
58
|
+
* @param {object} baseGrammar
|
|
59
|
+
* @param {object[]} manifests
|
|
60
|
+
* @param {{ lineKeywords?: string[] }} [options]
|
|
61
|
+
*/
|
|
62
|
+
const buildGrammar = (baseGrammar, manifests = [], options = {}) => {
|
|
63
|
+
const { modules, blocks } = mergeCommands(manifests);
|
|
64
|
+
const commandPatterns = [
|
|
65
|
+
...patternsForNamedCommands(modules),
|
|
66
|
+
...patternsForNamedCommands(blocks),
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const keywordPattern = lineKeywordPattern(options.lineKeywords || []);
|
|
70
|
+
const basePatterns = Array.isArray(baseGrammar.patterns) ? baseGrammar.patterns : [];
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
...baseGrammar,
|
|
74
|
+
patterns: [...basePatterns, ...(keywordPattern ? [keywordPattern] : []), ...commandPatterns],
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
actionLiterals,
|
|
80
|
+
buildGrammar,
|
|
81
|
+
escapeRegExp,
|
|
82
|
+
lineKeywordPattern,
|
|
83
|
+
mergeCommands,
|
|
84
|
+
patternsForNamedCommands,
|
|
85
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
if (typeof module === "object" && module.exports) {
|
|
3
|
+
module.exports = factory();
|
|
4
|
+
} else {
|
|
5
|
+
root.VneCommandTokens = factory();
|
|
6
|
+
}
|
|
7
|
+
})(typeof globalThis !== "undefined" ? globalThis : this, function () {
|
|
8
|
+
const TOKEN_TYPES = ["keyword", "function"];
|
|
9
|
+
|
|
10
|
+
const escapeRegExp = (value) => String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
11
|
+
|
|
12
|
+
const actionLiterals = (entry) =>
|
|
13
|
+
Object.values((entry && entry.actions) || {}).filter((value) => typeof value === "string" && value.length > 0);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Collect semantic token ranges for document text.
|
|
17
|
+
* @returns {{ line: number, start: number, length: number, type: string }[]}
|
|
18
|
+
*/
|
|
19
|
+
const collectCommandTokenRanges = (text, commands) => {
|
|
20
|
+
const modules = (commands && commands.modules) || {};
|
|
21
|
+
const blocks = (commands && commands.blocks) || {};
|
|
22
|
+
const lineKeywords = (commands && commands.lineKeywords) || [];
|
|
23
|
+
const ranges = [];
|
|
24
|
+
const lines = String(text).split(/\r?\n/);
|
|
25
|
+
|
|
26
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
27
|
+
const line = lines[lineIndex];
|
|
28
|
+
const trimmedStart = line.search(/\S/);
|
|
29
|
+
|
|
30
|
+
if (trimmedStart === -1) continue;
|
|
31
|
+
|
|
32
|
+
const lead = line.slice(trimmedStart);
|
|
33
|
+
if (lead.startsWith("//") || lead.startsWith("#")) continue;
|
|
34
|
+
|
|
35
|
+
const execMatch = /^(\s*)([%$])\s+([a-zA-Z0-9_.-]+)(?:\s+([a-zA-Z0-9_-]+))?/.exec(line);
|
|
36
|
+
|
|
37
|
+
if (execMatch) {
|
|
38
|
+
const prefix = execMatch[2];
|
|
39
|
+
const commandName = execMatch[3];
|
|
40
|
+
const action = execMatch[4];
|
|
41
|
+
const dict = prefix === "$" ? modules : blocks;
|
|
42
|
+
const entry = dict[commandName];
|
|
43
|
+
const actions = actionLiterals(entry);
|
|
44
|
+
|
|
45
|
+
if (entry && action && actions.includes(action)) {
|
|
46
|
+
const nameOffset = line.indexOf(commandName, execMatch[1].length);
|
|
47
|
+
const actionOffset = line.indexOf(action, nameOffset + commandName.length);
|
|
48
|
+
|
|
49
|
+
if (actionOffset !== -1) {
|
|
50
|
+
ranges.push({ line: lineIndex, start: actionOffset, length: action.length, type: "keyword" });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (actionOffset !== -1) {
|
|
54
|
+
const afterAction = line.slice(actionOffset + action.length);
|
|
55
|
+
const idMatch = /^\s+([a-zA-Z0-9_/-]+)/.exec(afterAction);
|
|
56
|
+
|
|
57
|
+
if (idMatch) {
|
|
58
|
+
const idOffset = actionOffset + action.length + afterAction.indexOf(idMatch[1]);
|
|
59
|
+
ranges.push({ line: lineIndex, start: idOffset, length: idMatch[1].length, type: "function" });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const pipeIndex = line.indexOf("|");
|
|
66
|
+
|
|
67
|
+
if (pipeIndex !== -1 && lineKeywords.length) {
|
|
68
|
+
const rest = line.slice(pipeIndex + 1);
|
|
69
|
+
|
|
70
|
+
for (const keyword of lineKeywords) {
|
|
71
|
+
const pattern = new RegExp(`\\b${escapeRegExp(keyword)}\\b`, "g");
|
|
72
|
+
let match;
|
|
73
|
+
|
|
74
|
+
while ((match = pattern.exec(rest))) {
|
|
75
|
+
ranges.push({
|
|
76
|
+
line: lineIndex,
|
|
77
|
+
start: pipeIndex + 1 + match.index,
|
|
78
|
+
length: keyword.length,
|
|
79
|
+
type: "keyword",
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return ranges;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/** Encode ranges to Monaco/VS Code semantic tokens Uint32Array. */
|
|
90
|
+
const encodeSemanticTokenData = (ranges, tokenTypes) => {
|
|
91
|
+
const typeIndex = new Map((tokenTypes || TOKEN_TYPES).map((name, index) => [name, index]));
|
|
92
|
+
const sorted = [...ranges].sort((a, b) => (a.line === b.line ? a.start - b.start : a.line - b.line));
|
|
93
|
+
const data = [];
|
|
94
|
+
let prevLine = 0;
|
|
95
|
+
let prevStart = 0;
|
|
96
|
+
|
|
97
|
+
for (const range of sorted) {
|
|
98
|
+
const index = typeIndex.get(range.type);
|
|
99
|
+
if (index === undefined) continue;
|
|
100
|
+
|
|
101
|
+
const deltaLine = range.line - prevLine;
|
|
102
|
+
const deltaStart = deltaLine === 0 ? range.start - prevStart : range.start;
|
|
103
|
+
|
|
104
|
+
data.push(deltaLine, deltaStart, range.length, index, 0);
|
|
105
|
+
prevLine = range.line;
|
|
106
|
+
prevStart = range.start;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return new Uint32Array(data);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
TOKEN_TYPES,
|
|
114
|
+
actionLiterals,
|
|
115
|
+
collectCommandTokenRanges,
|
|
116
|
+
encodeSemanticTokenData,
|
|
117
|
+
escapeRegExp,
|
|
118
|
+
};
|
|
119
|
+
});
|
package/src/extension.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const vscode = require("vscode");
|
|
2
|
+
|
|
3
|
+
const { resolveGameRoot } = require("./resolve-game-root.js");
|
|
4
|
+
const { loadCommandsFromNodeModules, EMPTY_COMMANDS } = require("./load-commands.js");
|
|
5
|
+
const { TOKEN_TYPES, buildSemanticTokens } = require("./semantic-tokens.js");
|
|
6
|
+
|
|
7
|
+
/** @type {Map<string, { modules: object, blocks: object, lineKeywords: string[] }>} */
|
|
8
|
+
const commandsCache = new Map();
|
|
9
|
+
|
|
10
|
+
/** @type {Map<string, Promise<{ modules: object, blocks: object, lineKeywords: string[] }>>} */
|
|
11
|
+
const commandsInflight = new Map();
|
|
12
|
+
|
|
13
|
+
const legend = new vscode.SemanticTokensLegend(TOKEN_TYPES, []);
|
|
14
|
+
const onDidChangeSemanticTokensEmitter = new vscode.EventEmitter();
|
|
15
|
+
|
|
16
|
+
const getCommandsForGameRoot = async (gameRoot) => {
|
|
17
|
+
if (!gameRoot) return { modules: {}, blocks: {}, lineKeywords: [] };
|
|
18
|
+
|
|
19
|
+
if (commandsCache.has(gameRoot)) return commandsCache.get(gameRoot);
|
|
20
|
+
|
|
21
|
+
if (commandsInflight.has(gameRoot)) return commandsInflight.get(gameRoot);
|
|
22
|
+
|
|
23
|
+
const pending = loadCommandsFromNodeModules(gameRoot)
|
|
24
|
+
.then((commands) => {
|
|
25
|
+
commandsCache.set(gameRoot, commands);
|
|
26
|
+
commandsInflight.delete(gameRoot);
|
|
27
|
+
return commands;
|
|
28
|
+
})
|
|
29
|
+
.catch(() => {
|
|
30
|
+
commandsInflight.delete(gameRoot);
|
|
31
|
+
return { modules: {}, blocks: {}, lineKeywords: [] };
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
commandsInflight.set(gameRoot, pending);
|
|
35
|
+
return pending;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const invalidateGameRoot = (gameRoot) => {
|
|
39
|
+
if (!gameRoot) {
|
|
40
|
+
commandsCache.clear();
|
|
41
|
+
commandsInflight.clear();
|
|
42
|
+
} else {
|
|
43
|
+
commandsCache.delete(gameRoot);
|
|
44
|
+
commandsInflight.delete(gameRoot);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
onDidChangeSemanticTokensEmitter.fire();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const createSemanticTokensProvider = () => ({
|
|
51
|
+
onDidChangeSemanticTokens: onDidChangeSemanticTokensEmitter.event,
|
|
52
|
+
|
|
53
|
+
async provideDocumentSemanticTokens(document) {
|
|
54
|
+
if (document.languageId !== "vnelang") {
|
|
55
|
+
return new vscode.SemanticTokens(new Uint32Array());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const gameRoot = resolveGameRoot(document.uri.fsPath);
|
|
59
|
+
const commands = gameRoot ? await getCommandsForGameRoot(gameRoot) : EMPTY_COMMANDS;
|
|
60
|
+
|
|
61
|
+
return buildSemanticTokens(vscode, legend, commands, document);
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const watchGameRoot = (context, gameRoot) => {
|
|
66
|
+
if (!gameRoot) return;
|
|
67
|
+
|
|
68
|
+
const pattern = new vscode.RelativePattern(gameRoot, "{package.json,node_modules/@vnejs/contracts.*/**}");
|
|
69
|
+
const watcher = vscode.workspace.createFileSystemWatcher(pattern);
|
|
70
|
+
|
|
71
|
+
const invalidate = () => invalidateGameRoot(gameRoot);
|
|
72
|
+
|
|
73
|
+
watcher.onDidCreate(invalidate);
|
|
74
|
+
watcher.onDidChange(invalidate);
|
|
75
|
+
watcher.onDidDelete(invalidate);
|
|
76
|
+
|
|
77
|
+
context.subscriptions.push(watcher);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** @type {Set<string>} */
|
|
81
|
+
const watchedRoots = new Set();
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {import("vscode").ExtensionContext} context
|
|
85
|
+
*/
|
|
86
|
+
const activate = (context) => {
|
|
87
|
+
const provider = createSemanticTokensProvider();
|
|
88
|
+
|
|
89
|
+
context.subscriptions.push(
|
|
90
|
+
vscode.languages.registerDocumentSemanticTokensProvider({ language: "vnelang" }, provider, legend),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
context.subscriptions.push(onDidChangeSemanticTokensEmitter);
|
|
94
|
+
|
|
95
|
+
context.subscriptions.push(
|
|
96
|
+
vscode.workspace.onDidOpenTextDocument((document) => {
|
|
97
|
+
if (document.languageId !== "vnelang") return;
|
|
98
|
+
|
|
99
|
+
const gameRoot = resolveGameRoot(document.uri.fsPath);
|
|
100
|
+
if (!gameRoot || watchedRoots.has(gameRoot)) return;
|
|
101
|
+
|
|
102
|
+
watchedRoots.add(gameRoot);
|
|
103
|
+
watchGameRoot(context, gameRoot);
|
|
104
|
+
getCommandsForGameRoot(gameRoot).then(() => onDidChangeSemanticTokensEmitter.fire());
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
for (const document of vscode.workspace.textDocuments) {
|
|
109
|
+
if (document.languageId !== "vnelang") continue;
|
|
110
|
+
|
|
111
|
+
const gameRoot = resolveGameRoot(document.uri.fsPath);
|
|
112
|
+
if (!gameRoot || watchedRoots.has(gameRoot)) continue;
|
|
113
|
+
|
|
114
|
+
watchedRoots.add(gameRoot);
|
|
115
|
+
watchGameRoot(context, gameRoot);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const deactivate = () => {
|
|
120
|
+
commandsCache.clear();
|
|
121
|
+
commandsInflight.clear();
|
|
122
|
+
watchedRoots.clear();
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
module.exports = { activate, deactivate, invalidateGameRoot, getCommandsForGameRoot, legend };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { pathToFileURL } = require("url");
|
|
4
|
+
|
|
5
|
+
const { mergeCommands } = require("./build-grammar.js");
|
|
6
|
+
|
|
7
|
+
const EMPTY_COMMANDS = Object.freeze({
|
|
8
|
+
modules: Object.freeze({}),
|
|
9
|
+
blocks: Object.freeze({}),
|
|
10
|
+
lineKeywords: Object.freeze([]),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Collect @vnejs/contracts.* package dirs visible from gameRoot (including hoisted ancestors).
|
|
15
|
+
* @param {string} gameRoot
|
|
16
|
+
* @returns {Map<string, string>} short name (e.g. contracts.core.stack) → absolute package dir
|
|
17
|
+
*/
|
|
18
|
+
const findContractPackageDirs = (gameRoot) => {
|
|
19
|
+
const found = new Map();
|
|
20
|
+
let dir = path.resolve(gameRoot);
|
|
21
|
+
|
|
22
|
+
while (true) {
|
|
23
|
+
const scopeDir = path.join(dir, "node_modules", "@vnejs");
|
|
24
|
+
|
|
25
|
+
if (fs.existsSync(scopeDir) && fs.statSync(scopeDir).isDirectory()) {
|
|
26
|
+
for (const name of fs.readdirSync(scopeDir)) {
|
|
27
|
+
if (!name.startsWith("contracts.")) continue;
|
|
28
|
+
if (found.has(name)) continue;
|
|
29
|
+
|
|
30
|
+
const packageDir = path.join(scopeDir, name);
|
|
31
|
+
if (fs.statSync(packageDir).isDirectory()) found.set(name, packageDir);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const parent = path.dirname(dir);
|
|
36
|
+
if (parent === dir) break;
|
|
37
|
+
dir = parent;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return found;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const importModule = async (filePath) => import(pathToFileURL(filePath).href);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Load and merge SCENARIO_COMMANDS from contracts in node_modules of the game package.
|
|
47
|
+
*
|
|
48
|
+
* Imports only `dist/commands.js` when present (not the full package entry), so we do not
|
|
49
|
+
* pull every contract's side-effect graph into the editor/extension host.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} gameRoot
|
|
52
|
+
* @returns {Promise<{ modules: Record<string, object>, blocks: Record<string, object>, lineKeywords: string[] }>}
|
|
53
|
+
*/
|
|
54
|
+
const loadCommandsFromNodeModules = async (gameRoot) => {
|
|
55
|
+
if (!gameRoot) return { ...EMPTY_COMMANDS, modules: {}, blocks: {}, lineKeywords: [] };
|
|
56
|
+
|
|
57
|
+
const packageDirs = findContractPackageDirs(gameRoot);
|
|
58
|
+
const manifests = [];
|
|
59
|
+
let lineKeywords = [];
|
|
60
|
+
|
|
61
|
+
for (const [shortName, packageDir] of packageDirs) {
|
|
62
|
+
const commandsPath = path.join(packageDir, "dist", "commands.js");
|
|
63
|
+
|
|
64
|
+
if (!fs.existsSync(commandsPath)) continue;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const mod = await importModule(commandsPath);
|
|
68
|
+
|
|
69
|
+
if (mod.SCENARIO_COMMANDS) manifests.push(mod.SCENARIO_COMMANDS);
|
|
70
|
+
|
|
71
|
+
if (shortName === "contracts.core.scenario") {
|
|
72
|
+
const constPath = path.join(packageDir, "dist", "const.js");
|
|
73
|
+
|
|
74
|
+
if (fs.existsSync(constPath)) {
|
|
75
|
+
const consts = await importModule(constPath);
|
|
76
|
+
const keywords = consts.LINE_KEYWORDS || consts.CONSTANTS?.LINE_KEYWORDS;
|
|
77
|
+
|
|
78
|
+
if (keywords && typeof keywords === "object") {
|
|
79
|
+
lineKeywords = Object.values(keywords).filter(
|
|
80
|
+
(value) => typeof value === "string" && value.length > 0,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// Skip unreadable / incompatible packages.
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const merged = mergeCommands(manifests);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
modules: merged.modules,
|
|
94
|
+
blocks: merged.blocks,
|
|
95
|
+
lineKeywords,
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
module.exports = {
|
|
100
|
+
EMPTY_COMMANDS,
|
|
101
|
+
findContractPackageDirs,
|
|
102
|
+
loadCommandsFromNodeModules,
|
|
103
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Nearest package root for a .vne file: directory that has package.json and a game/ folder.
|
|
6
|
+
* @param {string} filePath
|
|
7
|
+
* @returns {string | null}
|
|
8
|
+
*/
|
|
9
|
+
const resolveGameRoot = (filePath) => {
|
|
10
|
+
if (!filePath || typeof filePath !== "string") return null;
|
|
11
|
+
|
|
12
|
+
let dir = path.dirname(path.resolve(filePath));
|
|
13
|
+
|
|
14
|
+
while (true) {
|
|
15
|
+
const packageJsonPath = path.join(dir, "package.json");
|
|
16
|
+
const gameDir = path.join(dir, "game");
|
|
17
|
+
|
|
18
|
+
if (fs.existsSync(packageJsonPath) && fs.existsSync(gameDir) && fs.statSync(gameDir).isDirectory()) {
|
|
19
|
+
return dir;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const parent = path.dirname(dir);
|
|
23
|
+
if (parent === dir) return null;
|
|
24
|
+
dir = parent;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
module.exports = { resolveGameRoot };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const { TOKEN_TYPES, collectCommandTokenRanges } = require("./collect-command-tokens.js");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {import("vscode")} vscode
|
|
5
|
+
* @param {import("vscode").SemanticTokensLegend} legend
|
|
6
|
+
* @param {{ modules?: Record<string, object>, blocks?: Record<string, object>, lineKeywords?: string[] }} commands
|
|
7
|
+
* @param {import("vscode").TextDocument} document
|
|
8
|
+
*/
|
|
9
|
+
const buildSemanticTokens = (vscode, legend, commands, document) => {
|
|
10
|
+
const builder = new vscode.SemanticTokensBuilder(legend);
|
|
11
|
+
const ranges = collectCommandTokenRanges(document.getText(), commands);
|
|
12
|
+
|
|
13
|
+
for (const range of ranges) {
|
|
14
|
+
const typeIndex = legend.tokenTypes.indexOf(range.type);
|
|
15
|
+
if (typeIndex === -1) continue;
|
|
16
|
+
builder.push(range.line, range.start, range.length, typeIndex, 0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return builder.build();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
module.exports = { TOKEN_TYPES, buildSemanticTokens, collectCommandTokenRanges };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
|
|
3
|
+
const { resolveGameRoot } = require("./resolve-game-root.js");
|
|
4
|
+
const { loadCommandsFromNodeModules } = require("./load-commands.js");
|
|
5
|
+
const { collectCommandTokenRanges } = require("./semantic-tokens.js");
|
|
6
|
+
|
|
7
|
+
const sampleVne = path.resolve(
|
|
8
|
+
__dirname,
|
|
9
|
+
"../../../games/vn.skeleton/game/main/scenario/tests/index.vne",
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
const assert = (condition, message) => {
|
|
13
|
+
if (!condition) throw new Error(message);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const main = async () => {
|
|
17
|
+
const gameRoot = resolveGameRoot(sampleVne);
|
|
18
|
+
assert(gameRoot, "expected game root for vn.skeleton scenario");
|
|
19
|
+
assert(gameRoot.endsWith(`${path.sep}vn.skeleton`) || gameRoot.endsWith("/vn.skeleton"), `unexpected root: ${gameRoot}`);
|
|
20
|
+
|
|
21
|
+
const commands = await loadCommandsFromNodeModules(gameRoot);
|
|
22
|
+
|
|
23
|
+
assert(commands.modules.stack, "expected stack module from contracts");
|
|
24
|
+
assert(commands.modules.scenario, "expected scenario module from contracts");
|
|
25
|
+
assert(
|
|
26
|
+
Object.values(commands.modules.stack.actions || {}).includes("jump"),
|
|
27
|
+
"expected stack jump action",
|
|
28
|
+
);
|
|
29
|
+
assert(
|
|
30
|
+
Object.values(commands.modules.scenario.actions || {}).includes("goto"),
|
|
31
|
+
"expected scenario goto action",
|
|
32
|
+
);
|
|
33
|
+
assert(commands.lineKeywords.includes("async"), "expected LINE_KEYWORDS from scenario contract");
|
|
34
|
+
|
|
35
|
+
const ranges = collectCommandTokenRanges(
|
|
36
|
+
[' $ stack jump testsCanvas', ' "hi" | async global', ' $ scenario goto loop'].join("\n"),
|
|
37
|
+
commands,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
assert(
|
|
41
|
+
ranges.some((range) => range.type === "keyword" && range.line === 0),
|
|
42
|
+
"expected keyword token on stack jump",
|
|
43
|
+
);
|
|
44
|
+
assert(
|
|
45
|
+
ranges.some((range) => range.type === "function" && range.line === 0),
|
|
46
|
+
"expected function token for jump target",
|
|
47
|
+
);
|
|
48
|
+
assert(
|
|
49
|
+
ranges.some((range) => range.type === "keyword" && range.line === 1),
|
|
50
|
+
"expected keyword token for line keywords",
|
|
51
|
+
);
|
|
52
|
+
assert(
|
|
53
|
+
ranges.some((range) => range.type === "keyword" && range.line === 2),
|
|
54
|
+
"expected keyword token on scenario goto",
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
console.log("ok", {
|
|
58
|
+
gameRoot,
|
|
59
|
+
modules: Object.keys(commands.modules),
|
|
60
|
+
blocks: Object.keys(commands.blocks),
|
|
61
|
+
lineKeywords: commands.lineKeywords,
|
|
62
|
+
sampleRanges: ranges.length,
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
main().catch((error) => {
|
|
67
|
+
console.error(error);
|
|
68
|
+
process.exitCode = 1;
|
|
69
|
+
});
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scopeName": "source.vnelang",
|
|
3
|
+
"patterns": [
|
|
4
|
+
{
|
|
5
|
+
"name": "comment.line",
|
|
6
|
+
"match": "(//|#).*$"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"name": "string.quoted.double",
|
|
10
|
+
"match": "\".*\""
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "variable.other",
|
|
14
|
+
"match": "\\{[^}]*\\}"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "keyword.control",
|
|
18
|
+
"match": "[%$]"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "keyword.declaration",
|
|
22
|
+
"match": "\\blabel\\b"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "keyword.other",
|
|
26
|
+
"match": "\\bimport\\b"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "keyword.operator.comparison",
|
|
30
|
+
"match": "!==|===|>=|<=|<|>"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "keyword.operator.assignment",
|
|
34
|
+
"match": "\\+=|-=|\\+\\+|--|="
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "keyword.operator",
|
|
38
|
+
"match": "[+\\-*:|]"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "constant.numeric",
|
|
42
|
+
"match": "(?<=\\s|[+\\-:])\\d+(\\.\\d+)?"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "constant.language",
|
|
46
|
+
"match": "\\b(true|false|null)\\b"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "entity.name.namespace",
|
|
50
|
+
"match": "(?<=\\bimport\\s+)[a-zA-Z0-9_/-]+"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "entity.name.function",
|
|
54
|
+
"match": "(?<=\\blabel\\s+)[a-zA-Z0-9_/-]+"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "entity.name.function",
|
|
58
|
+
"match": "(?<=[%$]\\s+)[a-zA-Z0-9_.-]+"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "variable.other",
|
|
62
|
+
"match": "(?<=\\s)[a-zA-Z0-9_-]+:(?=\\s|$)"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"name": "variable.other",
|
|
66
|
+
"match": "(?<=\\b(counter|flag)\\s+)[a-zA-Z0-9_-]+"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "keyword.other",
|
|
70
|
+
"match": "(?<=^\\s*)[a-zA-Z0-9_-]+(?=\\s+\")"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "keyword.control",
|
|
74
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.sprite\\s)(show|hide|change|move)\\b"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "keyword.control",
|
|
78
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.image\\s)(show|hide|change)\\b"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"name": "keyword.control",
|
|
82
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.video\\s)(show|hide|change)\\b"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"name": "keyword.control",
|
|
86
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.text\\s)(show|hide|change|move)\\b"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "keyword.control",
|
|
90
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.filter\\s)(set|unset)\\b"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "keyword.control",
|
|
94
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.clip\\s)(set|unset)\\b"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "keyword.control",
|
|
98
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.mask\\s)(set|unset|change)\\b"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"name": "keyword.control",
|
|
102
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.scale\\s)(set|unset)\\b"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"name": "keyword.control",
|
|
106
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.animation\\s)(set|unset)\\b"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"name": "keyword.control",
|
|
110
|
+
"match": "(?<=\\baudio\\.(music|sound|ambient|voice)\\s)(play|stop|pause|resume|volume)\\b"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"name": "keyword.control",
|
|
114
|
+
"match": "(?<=\\bvideo\\s)play\\b"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"name": "keyword.control",
|
|
118
|
+
"match": "(?<=\\binterface\\s)(show|hide|view)\\b"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"name": "keyword.control",
|
|
122
|
+
"match": "(?<=\\bsave\\s)key\\b"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"name": "keyword.control",
|
|
126
|
+
"match": "(?<=\\bflag\\s+[a-zA-Z0-9_-]+\\s)(set|unset)\\b"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"name": "keyword.control",
|
|
130
|
+
"match": "(?<=\\btime\\s+)(add|set)\\b"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"name": "keyword.control",
|
|
134
|
+
"match": "(?<=\\bdate\\s+)(add|set)\\b"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"name": "keyword.control",
|
|
138
|
+
"match": "(?<=\\btext\\.wall\\s+)(clear|start|end)\\b"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"name": "keyword.control",
|
|
142
|
+
"match": "(?<=\\btext\\.meet\\s+)[a-zA-Z0-9_-]+"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"name": "keyword.control",
|
|
146
|
+
"match": "(?<=\\bcomment\\s+)hide\\b"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"name": "keyword.control",
|
|
150
|
+
"match": "(?<=\\bachievement\\s+)unlock\\b"
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"name": "keyword.control",
|
|
154
|
+
"match": "(?<=\\bwallet\\s+[a-zA-Z0-9_-]+\\s)(add|set|reset)\\b"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"name": "keyword.control",
|
|
158
|
+
"match": "(?<=\\bcredits\\s+)(show|hide)\\b"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"name": "entity.name.function",
|
|
162
|
+
"match": "(?<=\\bachievement\\s+unlock\\s+)[a-zA-Z0-9_-]+"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"name": "entity.name.function",
|
|
166
|
+
"match": "(?<=\\bwallet\\s+)[a-zA-Z0-9_-]+(?=\\s+(add|set|reset))"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"name": "entity.name.type",
|
|
170
|
+
"match": "(?<=\\binterface\\s+view\\s+)[a-zA-Z0-9_-]+"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"name": "entity.name.function",
|
|
174
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.image\\sshow\\s\\{[^}]*\\}\\s)[a-zA-Z0-9_-]+"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"name": "entity.name.function",
|
|
178
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.image\\shide\\s)[a-zA-Z0-9_-]+"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"name": "entity.name.function",
|
|
182
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.sprite\\sshow\\s)[a-zA-Z0-9_-]+"
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"name": "entity.name.function",
|
|
186
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.sprite\\s(hide|change|move)\\s)[a-zA-Z0-9_-]+"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"name": "entity.name.function",
|
|
190
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.video\\s(show|change)\\s)[a-zA-Z0-9_{}/-]+"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"name": "entity.name.function",
|
|
194
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.text\\s(show|hide|change|move)\\s)[a-zA-Z0-9_-]+"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"name": "entity.name.function",
|
|
198
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.filter\\sset\\s)[a-zA-Z0-9_-]+"
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"name": "entity.name.function",
|
|
202
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.clip\\sset\\s)[a-zA-Z0-9_-]+"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"name": "entity.name.function",
|
|
206
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.mask\\sset\\s)[a-zA-Z0-9_-]+"
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"name": "entity.name.function",
|
|
210
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.animation\\sset\\s)[a-zA-Z0-9_-]+"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"name": "keyword.other",
|
|
214
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.sprite\\s(show|hide|change|move)\\s[a-zA-Z0-9_-]+\\s)[a-zA-Z0-9_-]+"
|
|
215
|
+
}
|
|
216
|
+
],
|
|
217
|
+
"repository": {},
|
|
218
|
+
"uuid": "vne-language-grammar"
|
|
219
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scopeName": "source.vnelang",
|
|
3
|
+
"patterns": [
|
|
4
|
+
{
|
|
5
|
+
"name": "comment.line",
|
|
6
|
+
"match": "(//|#).*$"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"name": "string.quoted.double",
|
|
10
|
+
"match": "\".*\""
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "variable.other",
|
|
14
|
+
"match": "\\{[^}]*\\}"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "keyword.control",
|
|
18
|
+
"match": "[%$]"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "keyword.declaration",
|
|
22
|
+
"match": "\\blabel\\b"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "keyword.other",
|
|
26
|
+
"match": "\\bimport\\b"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "keyword.operator.comparison",
|
|
30
|
+
"match": "!==|===|>=|<=|<|>"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "keyword.operator.assignment",
|
|
34
|
+
"match": "\\+=|-=|\\+\\+|--|="
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "keyword.operator",
|
|
38
|
+
"match": "[+\\-*:|]"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "constant.numeric",
|
|
42
|
+
"match": "(?<=\\s|[+\\-:])\\d+(\\.\\d+)?"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "constant.language",
|
|
46
|
+
"match": "\\b(true|false|null)\\b"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "entity.name.namespace",
|
|
50
|
+
"match": "(?<=\\bimport\\s+)[a-zA-Z0-9_/-]+"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "entity.name.function",
|
|
54
|
+
"match": "(?<=\\blabel\\s+)[a-zA-Z0-9_/-]+"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "entity.name.function",
|
|
58
|
+
"match": "(?<=[%$]\\s+)[a-zA-Z0-9_.-]+"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "variable.other",
|
|
62
|
+
"match": "(?<=\\s)[a-zA-Z0-9_-]+:(?=\\s|$)"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"name": "variable.other",
|
|
66
|
+
"match": "(?<=\\b(counter|flag)\\s+)[a-zA-Z0-9_-]+"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "keyword.other",
|
|
70
|
+
"match": "(?<=^\\s*)[a-zA-Z0-9_-]+(?=\\s+\")"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "keyword.control",
|
|
74
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.sprite\\s)(show|hide|change|move)\\b"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "keyword.control",
|
|
78
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.image\\s)(show|hide|change)\\b"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"name": "keyword.control",
|
|
82
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.video\\s)(show|hide|change)\\b"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"name": "keyword.control",
|
|
86
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.text\\s)(show|hide|change|move)\\b"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "keyword.control",
|
|
90
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.filter\\s)(set|unset)\\b"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "keyword.control",
|
|
94
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.clip\\s)(set|unset)\\b"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "keyword.control",
|
|
98
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.mask\\s)(set|unset|change)\\b"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"name": "keyword.control",
|
|
102
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.scale\\s)(set|unset)\\b"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"name": "keyword.control",
|
|
106
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.animation\\s)(set|unset)\\b"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"name": "keyword.control",
|
|
110
|
+
"match": "(?<=\\baudio\\.(music|sound|ambient|voice)\\s)(play|stop|pause|resume|volume)\\b"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"name": "keyword.control",
|
|
114
|
+
"match": "(?<=\\bvideo\\s)play\\b"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"name": "keyword.control",
|
|
118
|
+
"match": "(?<=\\binterface\\s)(show|hide|view)\\b"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"name": "keyword.control",
|
|
122
|
+
"match": "(?<=\\bsave\\s)key\\b"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"name": "keyword.control",
|
|
126
|
+
"match": "(?<=\\bflag\\s+[a-zA-Z0-9_-]+\\s)(set|unset)\\b"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"name": "keyword.control",
|
|
130
|
+
"match": "(?<=\\btime\\s+)(add|set)\\b"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"name": "keyword.control",
|
|
134
|
+
"match": "(?<=\\bdate\\s+)(add|set)\\b"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"name": "keyword.control",
|
|
138
|
+
"match": "(?<=\\btext\\.wall\\s+)(clear|start|end)\\b"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"name": "keyword.control",
|
|
142
|
+
"match": "(?<=\\btext\\.meet\\s+)[a-zA-Z0-9_-]+"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"name": "keyword.control",
|
|
146
|
+
"match": "(?<=\\bcomment\\s+)hide\\b"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"name": "keyword.control",
|
|
150
|
+
"match": "(?<=\\bachievement\\s+)unlock\\b"
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"name": "keyword.control",
|
|
154
|
+
"match": "(?<=\\bwallet\\s+[a-zA-Z0-9_-]+\\s)(add|set|reset)\\b"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"name": "keyword.control",
|
|
158
|
+
"match": "(?<=\\bcredits\\s+)(show|hide)\\b"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"name": "entity.name.function",
|
|
162
|
+
"match": "(?<=\\bachievement\\s+unlock\\s+)[a-zA-Z0-9_-]+"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"name": "entity.name.function",
|
|
166
|
+
"match": "(?<=\\bwallet\\s+)[a-zA-Z0-9_-]+(?=\\s+(add|set|reset))"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"name": "entity.name.type",
|
|
170
|
+
"match": "(?<=\\binterface\\s+view\\s+)[a-zA-Z0-9_-]+"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"name": "entity.name.function",
|
|
174
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.image\\sshow\\s\\{[^}]*\\}\\s)[a-zA-Z0-9_-]+"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"name": "entity.name.function",
|
|
178
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.image\\shide\\s)[a-zA-Z0-9_-]+"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"name": "entity.name.function",
|
|
182
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.sprite\\sshow\\s)[a-zA-Z0-9_-]+"
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"name": "entity.name.function",
|
|
186
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.sprite\\s(hide|change|move)\\s)[a-zA-Z0-9_-]+"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"name": "entity.name.function",
|
|
190
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.video\\s(show|change)\\s)[a-zA-Z0-9_{}/-]+"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"name": "entity.name.function",
|
|
194
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.text\\s(show|hide|change|move)\\s)[a-zA-Z0-9_-]+"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"name": "entity.name.function",
|
|
198
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.filter\\sset\\s)[a-zA-Z0-9_-]+"
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"name": "entity.name.function",
|
|
202
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.clip\\sset\\s)[a-zA-Z0-9_-]+"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"name": "entity.name.function",
|
|
206
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.mask\\sset\\s)[a-zA-Z0-9_-]+"
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"name": "entity.name.function",
|
|
210
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.animation\\sset\\s)[a-zA-Z0-9_-]+"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"name": "keyword.other",
|
|
214
|
+
"match": "(?<=\\b(bg|mg|fg|sg)\\.sprite\\s(show|hide|change|move)\\s[a-zA-Z0-9_-]+\\s)[a-zA-Z0-9_-]+"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"name": "keyword.control",
|
|
218
|
+
"match": "(?<=\\s\\|\\s.*)(global|skip-if-possible|async|extend|autonext|dangerous)(?=\\s|$)"
|
|
219
|
+
}
|
|
220
|
+
],
|
|
221
|
+
"repository": {},
|
|
222
|
+
"uuid": "vne-language-grammar"
|
|
223
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Welcome to your VS Code Extension
|
|
2
|
+
|
|
3
|
+
## What's in the folder
|
|
4
|
+
|
|
5
|
+
- This folder contains all of the files necessary for your extension.
|
|
6
|
+
- `package.json` - this is the manifest file in which you declare your language support and define the location of the grammar file that has been copied into your extension.
|
|
7
|
+
- `syntaxes/vne.tmLanguage.json` - this is the Text mate grammar file that is used for tokenization.
|
|
8
|
+
- `language-configuration.json` - this is the language configuration, defining the tokens that are used for comments and brackets.
|
|
9
|
+
|
|
10
|
+
## Get up and running straight away
|
|
11
|
+
|
|
12
|
+
- Make sure the language configuration settings in `language-configuration.json` are accurate.
|
|
13
|
+
- Press `F5` to open a new window with your extension loaded.
|
|
14
|
+
- Create a new file with a file name suffix matching your language.
|
|
15
|
+
- Verify that syntax highlighting works and that the language configuration settings are working.
|
|
16
|
+
|
|
17
|
+
## Make changes
|
|
18
|
+
|
|
19
|
+
- You can relaunch the extension from the debug toolbar after making changes to the files listed above.
|
|
20
|
+
- You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
|
|
21
|
+
|
|
22
|
+
## Add more language features
|
|
23
|
+
|
|
24
|
+
- To add features such as IntelliSense, hovers and validators check out the VS Code extenders documentation at https://code.visualstudio.com/api/language-extensions/overview
|
|
25
|
+
|
|
26
|
+
## Install your extension
|
|
27
|
+
|
|
28
|
+
- To start using your extension with Visual Studio Code copy it into the `<user home>/.vscode/extensions` folder and restart Code.
|
|
29
|
+
- To share your extension with the world, read on https://code.visualstudio.com/api/working-with-extensions/publishing-extension about publishing an extension.
|