redscript-mc 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +31 -0
- package/.github/ISSUE_TEMPLATE/wrong_output.md +33 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +34 -0
- package/.github/workflows/ci.yml +29 -0
- package/.github/workflows/publish-extension.yml +35 -0
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/README.zh.md +261 -0
- package/dist/__tests__/cli.test.d.ts +1 -0
- package/dist/__tests__/cli.test.js +140 -0
- package/dist/__tests__/codegen.test.d.ts +1 -0
- package/dist/__tests__/codegen.test.js +121 -0
- package/dist/__tests__/diagnostics.test.d.ts +4 -0
- package/dist/__tests__/diagnostics.test.js +149 -0
- package/dist/__tests__/e2e.test.d.ts +6 -0
- package/dist/__tests__/e2e.test.js +1528 -0
- package/dist/__tests__/lexer.test.d.ts +1 -0
- package/dist/__tests__/lexer.test.js +316 -0
- package/dist/__tests__/lowering.test.d.ts +1 -0
- package/dist/__tests__/lowering.test.js +819 -0
- package/dist/__tests__/mc-integration.test.d.ts +12 -0
- package/dist/__tests__/mc-integration.test.js +395 -0
- package/dist/__tests__/mc-syntax.test.d.ts +1 -0
- package/dist/__tests__/mc-syntax.test.js +112 -0
- package/dist/__tests__/nbt.test.d.ts +1 -0
- package/dist/__tests__/nbt.test.js +82 -0
- package/dist/__tests__/optimizer-advanced.test.d.ts +1 -0
- package/dist/__tests__/optimizer-advanced.test.js +124 -0
- package/dist/__tests__/optimizer.test.d.ts +1 -0
- package/dist/__tests__/optimizer.test.js +118 -0
- package/dist/__tests__/parser.test.d.ts +1 -0
- package/dist/__tests__/parser.test.js +717 -0
- package/dist/__tests__/repl.test.d.ts +1 -0
- package/dist/__tests__/repl.test.js +27 -0
- package/dist/__tests__/runtime.test.d.ts +1 -0
- package/dist/__tests__/runtime.test.js +276 -0
- package/dist/__tests__/structure-optimizer.test.d.ts +1 -0
- package/dist/__tests__/structure-optimizer.test.js +33 -0
- package/dist/__tests__/typechecker.test.d.ts +1 -0
- package/dist/__tests__/typechecker.test.js +364 -0
- package/dist/ast/types.d.ts +357 -0
- package/dist/ast/types.js +9 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +407 -0
- package/dist/codegen/cmdblock/index.d.ts +26 -0
- package/dist/codegen/cmdblock/index.js +45 -0
- package/dist/codegen/mcfunction/index.d.ts +34 -0
- package/dist/codegen/mcfunction/index.js +413 -0
- package/dist/codegen/structure/index.d.ts +18 -0
- package/dist/codegen/structure/index.js +249 -0
- package/dist/compile.d.ts +30 -0
- package/dist/compile.js +152 -0
- package/dist/data/arena/function/__load.mcfunction +6 -0
- package/dist/data/arena/function/__tick.mcfunction +2 -0
- package/dist/data/arena/function/announce_leaders/else_1.mcfunction +3 -0
- package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +3 -0
- package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +7 -0
- package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +4 -0
- package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +6 -0
- package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/announce_leaders/then_0.mcfunction +4 -0
- package/dist/data/arena/function/announce_leaders.mcfunction +6 -0
- package/dist/data/arena/function/arena_tick/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/arena_tick/then_0.mcfunction +4 -0
- package/dist/data/arena/function/arena_tick.mcfunction +11 -0
- package/dist/data/counter/function/__load.mcfunction +5 -0
- package/dist/data/counter/function/__tick.mcfunction +2 -0
- package/dist/data/counter/function/counter_tick/merge_2.mcfunction +1 -0
- package/dist/data/counter/function/counter_tick/then_0.mcfunction +3 -0
- package/dist/data/counter/function/counter_tick.mcfunction +11 -0
- package/dist/data/minecraft/tags/function/load.json +5 -0
- package/dist/data/minecraft/tags/function/tick.json +5 -0
- package/dist/data/quiz/function/__load.mcfunction +16 -0
- package/dist/data/quiz/function/__tick.mcfunction +6 -0
- package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/answer_a.mcfunction +4 -0
- package/dist/data/quiz/function/answer_b.mcfunction +4 -0
- package/dist/data/quiz/function/answer_c.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/else_1.mcfunction +5 -0
- package/dist/data/quiz/function/ask_question/else_4.mcfunction +5 -0
- package/dist/data/quiz/function/ask_question/else_7.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/merge_2.mcfunction +1 -0
- package/dist/data/quiz/function/ask_question/merge_5.mcfunction +2 -0
- package/dist/data/quiz/function/ask_question/merge_8.mcfunction +2 -0
- package/dist/data/quiz/function/ask_question/then_0.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/then_3.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/then_6.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question.mcfunction +7 -0
- package/dist/data/quiz/function/finish_quiz.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer/else_1.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/else_10.mcfunction +3 -0
- package/dist/data/quiz/function/handle_answer/else_16.mcfunction +3 -0
- package/dist/data/quiz/function/handle_answer/else_4.mcfunction +3 -0
- package/dist/data/quiz/function/handle_answer/else_7.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +8 -0
- package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/then_0.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/then_12.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/then_15.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer/then_3.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer/then_6.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/then_9.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer.mcfunction +11 -0
- package/dist/data/quiz/function/start_quiz.mcfunction +5 -0
- package/dist/data/shop/function/__load.mcfunction +7 -0
- package/dist/data/shop/function/__tick.mcfunction +3 -0
- package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase/else_1.mcfunction +5 -0
- package/dist/data/shop/function/complete_purchase/else_4.mcfunction +5 -0
- package/dist/data/shop/function/complete_purchase/else_7.mcfunction +3 -0
- package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +2 -0
- package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +2 -0
- package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +2 -0
- package/dist/data/shop/function/complete_purchase/then_0.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase/then_3.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase/then_6.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase.mcfunction +7 -0
- package/dist/data/shop/function/handle_shop_trigger.mcfunction +3 -0
- package/dist/data/turret/function/__load.mcfunction +5 -0
- package/dist/data/turret/function/__tick.mcfunction +4 -0
- package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +4 -0
- package/dist/data/turret/function/deploy_turret.mcfunction +8 -0
- package/dist/data/turret/function/turret_tick/at_1.mcfunction +2 -0
- package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +2 -0
- package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +2 -0
- package/dist/data/turret/function/turret_tick/tick_body.mcfunction +3 -0
- package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +1 -0
- package/dist/data/turret/function/turret_tick.mcfunction +5 -0
- package/dist/diagnostics/index.d.ts +44 -0
- package/dist/diagnostics/index.js +140 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +126 -0
- package/dist/ir/builder.d.ts +32 -0
- package/dist/ir/builder.js +99 -0
- package/dist/ir/types.d.ts +117 -0
- package/dist/ir/types.js +15 -0
- package/dist/lexer/index.d.ts +36 -0
- package/dist/lexer/index.js +458 -0
- package/dist/lowering/index.d.ts +106 -0
- package/dist/lowering/index.js +2041 -0
- package/dist/mc-test/client.d.ts +128 -0
- package/dist/mc-test/client.js +174 -0
- package/dist/mc-test/runner.d.ts +28 -0
- package/dist/mc-test/runner.js +150 -0
- package/dist/mc-test/setup.d.ts +11 -0
- package/dist/mc-test/setup.js +98 -0
- package/dist/mc-validator/index.d.ts +17 -0
- package/dist/mc-validator/index.js +322 -0
- package/dist/nbt/index.d.ts +86 -0
- package/dist/nbt/index.js +250 -0
- package/dist/optimizer/commands.d.ts +36 -0
- package/dist/optimizer/commands.js +349 -0
- package/dist/optimizer/passes.d.ts +34 -0
- package/dist/optimizer/passes.js +227 -0
- package/dist/optimizer/structure.d.ts +8 -0
- package/dist/optimizer/structure.js +344 -0
- package/dist/pack.mcmeta +6 -0
- package/dist/parser/index.d.ts +76 -0
- package/dist/parser/index.js +1193 -0
- package/dist/repl.d.ts +16 -0
- package/dist/repl.js +165 -0
- package/dist/runtime/index.d.ts +101 -0
- package/dist/runtime/index.js +1288 -0
- package/dist/typechecker/index.d.ts +42 -0
- package/dist/typechecker/index.js +629 -0
- package/docs/COMPILATION_STATS.md +142 -0
- package/docs/IMPLEMENTATION_GUIDE.md +512 -0
- package/docs/LANGUAGE_REFERENCE.md +415 -0
- package/docs/MC_MAPPING.md +280 -0
- package/docs/STRUCTURE_TARGET.md +80 -0
- package/docs/mc-reference/commands.md +259 -0
- package/editors/vscode/.vscodeignore +10 -0
- package/editors/vscode/LICENSE +21 -0
- package/editors/vscode/README.md +78 -0
- package/editors/vscode/build.mjs +28 -0
- package/editors/vscode/icon.png +0 -0
- package/editors/vscode/mcfunction-language-configuration.json +28 -0
- package/editors/vscode/out/extension.js +7236 -0
- package/editors/vscode/package-lock.json +566 -0
- package/editors/vscode/package.json +137 -0
- package/editors/vscode/redscript-language-configuration.json +28 -0
- package/editors/vscode/snippets/redscript.json +114 -0
- package/editors/vscode/src/codeactions.ts +89 -0
- package/editors/vscode/src/completion.ts +130 -0
- package/editors/vscode/src/extension.ts +239 -0
- package/editors/vscode/src/hover.ts +1120 -0
- package/editors/vscode/src/symbols.ts +207 -0
- package/editors/vscode/syntaxes/mcfunction.tmLanguage.json +740 -0
- package/editors/vscode/syntaxes/redscript.tmLanguage.json +357 -0
- package/editors/vscode/tsconfig.json +13 -0
- package/jest.config.js +5 -0
- package/package.json +38 -0
- package/src/__tests__/cli.test.ts +130 -0
- package/src/__tests__/codegen.test.ts +128 -0
- package/src/__tests__/diagnostics.test.ts +195 -0
- package/src/__tests__/e2e.test.ts +1721 -0
- package/src/__tests__/fixtures/mc-commands-1.21.4.json +18734 -0
- package/src/__tests__/formatter.test.ts +46 -0
- package/src/__tests__/lexer.test.ts +356 -0
- package/src/__tests__/lowering.test.ts +962 -0
- package/src/__tests__/mc-integration.test.ts +409 -0
- package/src/__tests__/mc-syntax.test.ts +96 -0
- package/src/__tests__/nbt.test.ts +58 -0
- package/src/__tests__/optimizer-advanced.test.ts +144 -0
- package/src/__tests__/optimizer.test.ts +129 -0
- package/src/__tests__/parser.test.ts +800 -0
- package/src/__tests__/repl.test.ts +33 -0
- package/src/__tests__/runtime.test.ts +289 -0
- package/src/__tests__/structure-optimizer.test.ts +38 -0
- package/src/__tests__/typechecker.test.ts +395 -0
- package/src/ast/types.ts +248 -0
- package/src/cli.ts +445 -0
- package/src/codegen/cmdblock/index.ts +63 -0
- package/src/codegen/mcfunction/index.ts +471 -0
- package/src/codegen/structure/index.ts +305 -0
- package/src/compile.ts +188 -0
- package/src/diagnostics/index.ts +186 -0
- package/src/examples/README.md +77 -0
- package/src/examples/SHOWCASE_GAME.md +43 -0
- package/src/examples/arena.rs +44 -0
- package/src/examples/counter.rs +12 -0
- package/src/examples/pvp_arena.rs +131 -0
- package/src/examples/quiz.rs +90 -0
- package/src/examples/rpg.rs +13 -0
- package/src/examples/shop.rs +30 -0
- package/src/examples/showcase_game.rs +552 -0
- package/src/examples/stdlib_demo.rs +181 -0
- package/src/examples/turret.rs +27 -0
- package/src/examples/world_manager.rs +23 -0
- package/src/formatter/index.ts +22 -0
- package/src/index.ts +161 -0
- package/src/ir/builder.ts +114 -0
- package/src/ir/types.ts +119 -0
- package/src/lexer/index.ts +555 -0
- package/src/lowering/index.ts +2406 -0
- package/src/mc-test/client.ts +259 -0
- package/src/mc-test/runner.ts +140 -0
- package/src/mc-test/setup.ts +70 -0
- package/src/mc-validator/index.ts +367 -0
- package/src/nbt/index.ts +321 -0
- package/src/optimizer/commands.ts +416 -0
- package/src/optimizer/passes.ts +233 -0
- package/src/optimizer/structure.ts +441 -0
- package/src/parser/index.ts +1437 -0
- package/src/repl.ts +165 -0
- package/src/runtime/index.ts +1403 -0
- package/src/stdlib/README.md +156 -0
- package/src/stdlib/combat.rs +20 -0
- package/src/stdlib/cooldown.rs +45 -0
- package/src/stdlib/math.rs +49 -0
- package/src/stdlib/mobs.rs +99 -0
- package/src/stdlib/player.rs +29 -0
- package/src/stdlib/strings.rs +7 -0
- package/src/stdlib/timer.rs +51 -0
- package/src/templates/README.md +126 -0
- package/src/templates/combat.rs +96 -0
- package/src/templates/economy.rs +40 -0
- package/src/templates/mini-game-framework.rs +117 -0
- package/src/templates/quest.rs +78 -0
- package/src/test_programs/zombie_game.rs +25 -0
- package/src/typechecker/index.ts +737 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"comments": {
|
|
3
|
+
"lineComment": "//",
|
|
4
|
+
"blockComment": ["/*", "*/"]
|
|
5
|
+
},
|
|
6
|
+
"brackets": [
|
|
7
|
+
["{", "}"],
|
|
8
|
+
["[", "]"],
|
|
9
|
+
["(", ")"]
|
|
10
|
+
],
|
|
11
|
+
"autoClosingPairs": [
|
|
12
|
+
{ "open": "{", "close": "}" },
|
|
13
|
+
{ "open": "[", "close": "]" },
|
|
14
|
+
{ "open": "(", "close": ")" },
|
|
15
|
+
{ "open": "\"", "close": "\"" }
|
|
16
|
+
],
|
|
17
|
+
"surroundingPairs": [
|
|
18
|
+
["{", "}"],
|
|
19
|
+
["[", "]"],
|
|
20
|
+
["(", ")"],
|
|
21
|
+
["\"", "\""]
|
|
22
|
+
],
|
|
23
|
+
"indentationRules": {
|
|
24
|
+
"increaseIndentPattern": "\\{\\s*$",
|
|
25
|
+
"decreaseIndentPattern": "^\\s*\\}"
|
|
26
|
+
},
|
|
27
|
+
"wordPattern": "[a-zA-Z_][a-zA-Z0-9_]*"
|
|
28
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Function": {
|
|
3
|
+
"prefix": "fn",
|
|
4
|
+
"body": [
|
|
5
|
+
"fn ${1:name}(${2:params})${3: -> ${4:ReturnType}} {",
|
|
6
|
+
" $0",
|
|
7
|
+
"}"
|
|
8
|
+
],
|
|
9
|
+
"description": "Define a function"
|
|
10
|
+
},
|
|
11
|
+
"Tick Function": {
|
|
12
|
+
"prefix": "fntick",
|
|
13
|
+
"body": [
|
|
14
|
+
"@tick",
|
|
15
|
+
"fn ${1:name}(${2:params})${3: -> ${4:void}} {",
|
|
16
|
+
" $0",
|
|
17
|
+
"}"
|
|
18
|
+
],
|
|
19
|
+
"description": "Define an @tick function"
|
|
20
|
+
},
|
|
21
|
+
"Load Function": {
|
|
22
|
+
"prefix": "fnload",
|
|
23
|
+
"body": [
|
|
24
|
+
"@on_load",
|
|
25
|
+
"fn ${1:name}(${2:params})${3: -> ${4:void}} {",
|
|
26
|
+
" $0",
|
|
27
|
+
"}"
|
|
28
|
+
],
|
|
29
|
+
"description": "Define an @on_load function"
|
|
30
|
+
},
|
|
31
|
+
"If Statement": {
|
|
32
|
+
"prefix": "if",
|
|
33
|
+
"body": [
|
|
34
|
+
"if (${1:condition}) {",
|
|
35
|
+
" $0",
|
|
36
|
+
"}"
|
|
37
|
+
],
|
|
38
|
+
"description": "Insert an if statement"
|
|
39
|
+
},
|
|
40
|
+
"If Else Statement": {
|
|
41
|
+
"prefix": "ife",
|
|
42
|
+
"body": [
|
|
43
|
+
"if (${1:condition}) {",
|
|
44
|
+
" ${2}",
|
|
45
|
+
"} else {",
|
|
46
|
+
" $0",
|
|
47
|
+
"}"
|
|
48
|
+
],
|
|
49
|
+
"description": "Insert an if-else statement"
|
|
50
|
+
},
|
|
51
|
+
"Match Expression": {
|
|
52
|
+
"prefix": "match",
|
|
53
|
+
"body": [
|
|
54
|
+
"match (${1:value}) {",
|
|
55
|
+
" ${2:pattern} => {",
|
|
56
|
+
" ${3}",
|
|
57
|
+
" }",
|
|
58
|
+
" _ => {",
|
|
59
|
+
" $0",
|
|
60
|
+
" }",
|
|
61
|
+
"}"
|
|
62
|
+
],
|
|
63
|
+
"description": "Insert a match expression"
|
|
64
|
+
},
|
|
65
|
+
"Foreach Selector": {
|
|
66
|
+
"prefix": "foreach",
|
|
67
|
+
"body": [
|
|
68
|
+
"foreach (${1:entity} in ${2:@a}) {",
|
|
69
|
+
" $0",
|
|
70
|
+
"}"
|
|
71
|
+
],
|
|
72
|
+
"description": "Iterate over a selector or iterable"
|
|
73
|
+
},
|
|
74
|
+
"Foreach Selector Body": {
|
|
75
|
+
"prefix": "fors",
|
|
76
|
+
"body": [
|
|
77
|
+
"foreach (${1:entity} in ${2:@a}) {",
|
|
78
|
+
" ${3}",
|
|
79
|
+
" $0",
|
|
80
|
+
"}"
|
|
81
|
+
],
|
|
82
|
+
"description": "Iterate over a selector with a prepared body"
|
|
83
|
+
},
|
|
84
|
+
"Let Declaration": {
|
|
85
|
+
"prefix": "let",
|
|
86
|
+
"body": [
|
|
87
|
+
"let ${1:name}: ${2:Type} = ${0:value};"
|
|
88
|
+
],
|
|
89
|
+
"description": "Declare a typed local variable"
|
|
90
|
+
},
|
|
91
|
+
"Const Declaration": {
|
|
92
|
+
"prefix": "const",
|
|
93
|
+
"body": [
|
|
94
|
+
"const ${1:NAME}: ${2:Type} = ${0:value}"
|
|
95
|
+
],
|
|
96
|
+
"description": "Declare a constant"
|
|
97
|
+
},
|
|
98
|
+
"Struct Definition": {
|
|
99
|
+
"prefix": "struct",
|
|
100
|
+
"body": [
|
|
101
|
+
"struct ${1:Name} {",
|
|
102
|
+
" ${2:field}: ${3:Type}",
|
|
103
|
+
"}"
|
|
104
|
+
],
|
|
105
|
+
"description": "Define a struct"
|
|
106
|
+
},
|
|
107
|
+
"Enum Definition": {
|
|
108
|
+
"prefix": "enum",
|
|
109
|
+
"body": [
|
|
110
|
+
"enum ${1:Name} { ${2:VariantA}, ${3:VariantB} }"
|
|
111
|
+
],
|
|
112
|
+
"description": "Define an enum"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as vscode from 'vscode'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Code action provider for RedScript.
|
|
5
|
+
* Currently provides:
|
|
6
|
+
* - "Add minecraft: namespace" quick fix for unnamespaced entity types
|
|
7
|
+
* e.g. type=zombie → type=minecraft:zombie
|
|
8
|
+
*/
|
|
9
|
+
export function registerCodeActions(context: vscode.ExtensionContext): void {
|
|
10
|
+
context.subscriptions.push(
|
|
11
|
+
vscode.languages.registerCodeActionsProvider(
|
|
12
|
+
{ language: 'redscript', scheme: 'file' },
|
|
13
|
+
new RedScriptCodeActionProvider(),
|
|
14
|
+
{ providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] }
|
|
15
|
+
)
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class RedScriptCodeActionProvider implements vscode.CodeActionProvider {
|
|
20
|
+
provideCodeActions(
|
|
21
|
+
document: vscode.TextDocument,
|
|
22
|
+
range: vscode.Range,
|
|
23
|
+
context: vscode.CodeActionContext
|
|
24
|
+
): vscode.CodeAction[] {
|
|
25
|
+
const actions: vscode.CodeAction[] = []
|
|
26
|
+
|
|
27
|
+
for (const diag of context.diagnostics) {
|
|
28
|
+
if (diag.source !== 'redscript') continue
|
|
29
|
+
|
|
30
|
+
// W_UNNAMESPACED_TYPE: e.g. 'Unnamespaced entity type "zombie"'
|
|
31
|
+
if (diag.code === 'W_UNNAMESPACED_TYPE') {
|
|
32
|
+
const m = diag.message.match(/Unnamespaced entity type "([^"]+)"/)
|
|
33
|
+
if (!m) continue
|
|
34
|
+
const typeName = m[1]
|
|
35
|
+
|
|
36
|
+
// Find and replace in the diagnostic range area
|
|
37
|
+
const fix = new vscode.CodeAction(
|
|
38
|
+
`Add namespace: type=minecraft:${typeName}`,
|
|
39
|
+
vscode.CodeActionKind.QuickFix
|
|
40
|
+
)
|
|
41
|
+
fix.diagnostics = [diag]
|
|
42
|
+
fix.isPreferred = true
|
|
43
|
+
|
|
44
|
+
// Search the document for type=<typeName> (without namespace)
|
|
45
|
+
const text = document.getText()
|
|
46
|
+
const re = new RegExp(`\\btype=${escapeRe(typeName)}(?![a-zA-Z0-9_:.])`, 'g')
|
|
47
|
+
const edit = new vscode.WorkspaceEdit()
|
|
48
|
+
let match: RegExpExecArray | null
|
|
49
|
+
while ((match = re.exec(text)) !== null) {
|
|
50
|
+
const start = document.positionAt(match.index + 'type='.length)
|
|
51
|
+
const end = document.positionAt(match.index + 'type='.length + typeName.length)
|
|
52
|
+
edit.replace(document.uri, new vscode.Range(start, end), `minecraft:${typeName}`)
|
|
53
|
+
}
|
|
54
|
+
fix.edit = edit
|
|
55
|
+
actions.push(fix)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Also scan the current line for unnamespaced type= patterns
|
|
60
|
+
// even without a diagnostic (as a proactive suggestion)
|
|
61
|
+
const lineText = document.lineAt(range.start.line).text
|
|
62
|
+
const lineTypeRe = /\btype=([a-z][a-z0-9_]*)(?!\s*[:a-z0-9_])/g
|
|
63
|
+
let lm: RegExpExecArray | null
|
|
64
|
+
while ((lm = lineTypeRe.exec(lineText)) !== null) {
|
|
65
|
+
const typeName = lm[1]
|
|
66
|
+
// Skip already-namespaced or already have a fix above
|
|
67
|
+
if (typeName.includes(':')) continue
|
|
68
|
+
if (actions.some(a => a.title.includes(typeName))) continue
|
|
69
|
+
|
|
70
|
+
const fix = new vscode.CodeAction(
|
|
71
|
+
`Add namespace: type=minecraft:${typeName}`,
|
|
72
|
+
vscode.CodeActionKind.QuickFix
|
|
73
|
+
)
|
|
74
|
+
const col = lm.index + 'type='.length
|
|
75
|
+
const start = new vscode.Position(range.start.line, col)
|
|
76
|
+
const end = new vscode.Position(range.start.line, col + typeName.length)
|
|
77
|
+
const edit = new vscode.WorkspaceEdit()
|
|
78
|
+
edit.replace(document.uri, new vscode.Range(start, end), `minecraft:${typeName}`)
|
|
79
|
+
fix.edit = edit
|
|
80
|
+
actions.push(fix)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return actions
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function escapeRe(s: string): string {
|
|
88
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
89
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as vscode from 'vscode'
|
|
2
|
+
|
|
3
|
+
interface BuiltinFunction {
|
|
4
|
+
name: string
|
|
5
|
+
detail: string
|
|
6
|
+
doc: string
|
|
7
|
+
insertText?: string
|
|
8
|
+
kind?: vscode.CompletionItemKind
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const BUILTIN_FUNCTIONS: BuiltinFunction[] = [
|
|
12
|
+
{ name: 'say', detail: 'say(msg: string)', doc: 'Broadcast a message to all players as the server.' },
|
|
13
|
+
{ name: 'tell', detail: 'tell(target: selector, msg: string)', doc: 'Send a private message to a player or selector.' },
|
|
14
|
+
{ name: 'announce', detail: 'announce(msg: string)', doc: 'Send a message to all players in chat.' },
|
|
15
|
+
{ name: 'title', detail: 'title(target: selector, msg: string)', doc: 'Show a large title on screen for target players.' },
|
|
16
|
+
{ name: 'subtitle', detail: 'subtitle(target: selector, msg: string)', doc: 'Show subtitle text below the title.' },
|
|
17
|
+
{ name: 'actionbar', detail: 'actionbar(target: selector, msg: string)', doc: 'Show text in the action bar (above hotbar).' },
|
|
18
|
+
{ name: 'title_times', detail: 'title_times(target: selector, fadeIn: int, stay: int, fadeOut: int)', doc: 'Set title display timing (in ticks).' },
|
|
19
|
+
{ name: 'give', detail: 'give(target: selector, item: string, count?: int)', doc: 'Give item(s) to a player.' },
|
|
20
|
+
{ name: 'kill', detail: 'kill(target?: selector)', doc: 'Kill entity/entities. Defaults to @s.' },
|
|
21
|
+
{ name: 'effect', detail: 'effect(target: selector, effect: string, duration?: int, amplifier?: int)', doc: 'Apply a status effect.' },
|
|
22
|
+
{ name: 'clear', detail: 'clear(target: selector, item?: string)', doc: 'Remove items from inventory.' },
|
|
23
|
+
{ name: 'kick', detail: 'kick(player: selector, reason?: string)', doc: 'Kick a player from the server.' },
|
|
24
|
+
{ name: 'xp_add', detail: 'xp_add(target: selector, amount: int, type?: string)', doc: 'Add experience to a player.' },
|
|
25
|
+
{ name: 'xp_set', detail: 'xp_set(target: selector, amount: int, type?: string)', doc: "Set a player's experience." },
|
|
26
|
+
{ name: 'tp', detail: 'tp(target: selector, destination: selector | BlockPos)', doc: 'Teleport entity to a player or coordinates.' },
|
|
27
|
+
{ name: 'setblock', detail: 'setblock(pos: BlockPos, block: string)', doc: 'Place a block at coordinates.' },
|
|
28
|
+
{ name: 'fill', detail: 'fill(from: BlockPos, to: BlockPos, block: string)', doc: 'Fill a region with blocks.' },
|
|
29
|
+
{ name: 'clone', detail: 'clone(from: BlockPos, to: BlockPos, dest: BlockPos)', doc: 'Clone a region of blocks to a new location.' },
|
|
30
|
+
{ name: 'summon', detail: 'summon(type: string, pos: BlockPos)', doc: 'Spawn an entity at a location.' },
|
|
31
|
+
{ name: 'weather', detail: 'weather(type: string)', doc: 'Set the weather.' },
|
|
32
|
+
{ name: 'time_set', detail: 'time_set(value: int | string)', doc: 'Set the world time.' },
|
|
33
|
+
{ name: 'time_add', detail: 'time_add(ticks: int)', doc: 'Advance world time by ticks.' },
|
|
34
|
+
{ name: 'gamerule', detail: 'gamerule(rule: string, value: bool | int)', doc: 'Set a gamerule value.' },
|
|
35
|
+
{ name: 'difficulty', detail: 'difficulty(level: string)', doc: 'Set the game difficulty.' },
|
|
36
|
+
{ name: 'particle', detail: 'particle(name: string, pos: BlockPos)', doc: 'Spawn a particle effect.' },
|
|
37
|
+
{ name: 'playsound', detail: 'playsound(sound: string, source: string, target: selector, pos?: BlockPos, volume?: float, pitch?: float)', doc: 'Play a sound for a player.' },
|
|
38
|
+
{ name: 'tag_add', detail: 'tag_add(target: selector, tag: string)', doc: 'Add an entity tag.' },
|
|
39
|
+
{ name: 'tag_remove', detail: 'tag_remove(target: selector, tag: string)', doc: 'Remove an entity tag.' },
|
|
40
|
+
{ name: 'scoreboard_get', detail: 'scoreboard_get(target: selector | string, objective: string) -> int', doc: 'Read a scoreboard value.' },
|
|
41
|
+
{ name: 'score', detail: 'score(target: selector | string, objective: string) -> int', doc: 'Alias for scoreboard_get. Read a scoreboard value.' },
|
|
42
|
+
{ name: 'scoreboard_set', detail: 'scoreboard_set(target: selector | string, objective: string, value: int)', doc: 'Set a scoreboard value.' },
|
|
43
|
+
{ name: 'scoreboard_add', detail: 'scoreboard_add(target: selector | string, objective: string, amount: int)', doc: 'Add to a scoreboard value.' },
|
|
44
|
+
{ name: 'scoreboard_display', detail: 'scoreboard_display(slot: string, objective: string)', doc: 'Display a scoreboard objective in a slot.' },
|
|
45
|
+
{ name: 'scoreboard_add_objective', detail: 'scoreboard_add_objective(name: string, criteria: string)', doc: 'Create a new scoreboard objective.' },
|
|
46
|
+
{ name: 'scoreboard_remove_objective', detail: 'scoreboard_remove_objective(name: string)', doc: 'Remove a scoreboard objective.' },
|
|
47
|
+
{ name: 'scoreboard_hide', detail: 'scoreboard_hide(slot: string)', doc: 'Clear the display in a scoreboard slot.' },
|
|
48
|
+
{ name: 'random', detail: 'random(min: int, max: int) -> int', doc: 'Generate a random integer in range [min, max] using scoreboard arithmetic.' },
|
|
49
|
+
{ name: 'random_native', detail: 'random_native(min: int, max: int) -> int', doc: 'Generate a random integer using /random command (MC 1.20.3+). Faster than random().' },
|
|
50
|
+
{ name: 'str_len', detail: 'str_len(s: string) -> int', doc: 'Get the length of a string (stored in NBT storage).' },
|
|
51
|
+
{ name: 'push', detail: 'push(arr: T[], value: T)', doc: 'Append a value to the end of an array.' },
|
|
52
|
+
{ name: 'pop', detail: 'pop(arr: T[]) -> T', doc: 'Remove and return the last element of an array.' },
|
|
53
|
+
{ name: 'len', detail: 'arr.len', doc: 'Get the number of elements in an array (property access, not a function call).', kind: vscode.CompletionItemKind.Property },
|
|
54
|
+
{ name: 'data_get', detail: 'data_get(target: string, path: string) -> int', doc: 'Read NBT data from entity/block/storage.' },
|
|
55
|
+
{ name: 'bossbar_add', detail: 'bossbar_add(id: string, name: string)', doc: 'Create a new boss bar.' },
|
|
56
|
+
{ name: 'bossbar_set_value', detail: 'bossbar_set_value(id: string, value: int)', doc: 'Set boss bar current value.' },
|
|
57
|
+
{ name: 'bossbar_set_max', detail: 'bossbar_set_max(id: string, max: int)', doc: 'Set boss bar maximum value.' },
|
|
58
|
+
{ name: 'bossbar_remove', detail: 'bossbar_remove(id: string)', doc: 'Remove a boss bar.' },
|
|
59
|
+
{ name: 'bossbar_set_players', detail: 'bossbar_set_players(id: string, target: selector)', doc: 'Set which players see the boss bar.' },
|
|
60
|
+
{ name: 'bossbar_set_color', detail: 'bossbar_set_color(id: string, color: string)', doc: 'Set boss bar color.' },
|
|
61
|
+
{ name: 'bossbar_set_style', detail: 'bossbar_set_style(id: string, style: string)', doc: 'Set boss bar segmentation style.' },
|
|
62
|
+
{ name: 'bossbar_set_visible', detail: 'bossbar_set_visible(id: string, visible: bool)', doc: 'Show or hide a boss bar.' },
|
|
63
|
+
{ name: 'bossbar_get_value', detail: 'bossbar_get_value(id: string) -> int', doc: 'Get the current value of a boss bar.' },
|
|
64
|
+
{ name: 'team_add', detail: 'team_add(name: string)', doc: 'Create a new team.' },
|
|
65
|
+
{ name: 'team_remove', detail: 'team_remove(name: string)', doc: 'Remove a team.' },
|
|
66
|
+
{ name: 'team_join', detail: 'team_join(name: string, target: selector)', doc: 'Add entities to a team.' },
|
|
67
|
+
{ name: 'team_leave', detail: 'team_leave(target: selector)', doc: 'Remove entities from their team.' },
|
|
68
|
+
{ name: 'team_option', detail: 'team_option(name: string, option: string, value: string)', doc: 'Set a team option.' },
|
|
69
|
+
{ name: 'tick', detail: '@tick | @tick(rate: int)', doc: 'Run this function every tick (rate=1) or every N ticks.', insertText: '@tick', kind: vscode.CompletionItemKind.Event },
|
|
70
|
+
{ name: 'on_advancement', detail: '@on_advancement(id: string)', doc: 'Trigger when a player earns an advancement.', insertText: '@on_advancement', kind: vscode.CompletionItemKind.Event },
|
|
71
|
+
{ name: 'on_death', detail: '@on_death', doc: 'Trigger when the executing entity dies.', insertText: '@on_death', kind: vscode.CompletionItemKind.Event },
|
|
72
|
+
{ name: 'on_craft', detail: '@on_craft(item: string)', doc: 'Trigger when a player crafts an item.', insertText: '@on_craft', kind: vscode.CompletionItemKind.Event },
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
const KEYWORDS = [
|
|
76
|
+
'fn',
|
|
77
|
+
'let',
|
|
78
|
+
'const',
|
|
79
|
+
'if',
|
|
80
|
+
'else',
|
|
81
|
+
'match',
|
|
82
|
+
'foreach',
|
|
83
|
+
'in',
|
|
84
|
+
'return',
|
|
85
|
+
'struct',
|
|
86
|
+
'enum',
|
|
87
|
+
'execute',
|
|
88
|
+
'as',
|
|
89
|
+
'at',
|
|
90
|
+
'true',
|
|
91
|
+
'false',
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
const TYPES = ['int', 'float', 'string', 'bool', 'void', 'BlockPos', 'selector']
|
|
95
|
+
const TRIGGER_CHARACTERS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_@'.split('')
|
|
96
|
+
|
|
97
|
+
export function registerCompletionProvider(context: vscode.ExtensionContext): void {
|
|
98
|
+
const provider = vscode.languages.registerCompletionItemProvider(
|
|
99
|
+
{ language: 'redscript', scheme: 'file' },
|
|
100
|
+
{
|
|
101
|
+
provideCompletionItems() {
|
|
102
|
+
const items: vscode.CompletionItem[] = []
|
|
103
|
+
|
|
104
|
+
for (const builtin of BUILTIN_FUNCTIONS) {
|
|
105
|
+
const item = new vscode.CompletionItem(
|
|
106
|
+
builtin.name,
|
|
107
|
+
builtin.kind ?? vscode.CompletionItemKind.Function,
|
|
108
|
+
)
|
|
109
|
+
item.detail = builtin.detail
|
|
110
|
+
item.documentation = builtin.doc
|
|
111
|
+
item.insertText = builtin.insertText ?? builtin.name
|
|
112
|
+
items.push(item)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (const keyword of KEYWORDS) {
|
|
116
|
+
items.push(new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (const type of TYPES) {
|
|
120
|
+
items.push(new vscode.CompletionItem(type, vscode.CompletionItemKind.TypeParameter))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return items
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
...TRIGGER_CHARACTERS,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
context.subscriptions.push(provider)
|
|
130
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import * as vscode from 'vscode'
|
|
2
|
+
import { registerHoverProvider } from './hover'
|
|
3
|
+
import { registerCodeActions } from './codeactions'
|
|
4
|
+
import { registerCompletionProvider } from './completion'
|
|
5
|
+
import { registerSymbolProviders } from './symbols'
|
|
6
|
+
// The compiler is bundled directly into this extension by esbuild.
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
8
|
+
const { compile: _compile } = require('redscript') as {
|
|
9
|
+
compile: (source: string, opts?: { filePath?: string }) => {
|
|
10
|
+
files: { path: string; content: string }[]
|
|
11
|
+
warnings: { message: string; code: string; line?: number; col?: number }[]
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getCompile() {
|
|
16
|
+
return _compile ?? null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const DEBOUNCE_MS = 600
|
|
20
|
+
|
|
21
|
+
export function activate(context: vscode.ExtensionContext): void {
|
|
22
|
+
const diagnostics = vscode.languages.createDiagnosticCollection('redscript')
|
|
23
|
+
context.subscriptions.push(diagnostics)
|
|
24
|
+
|
|
25
|
+
// Debounce timer per document URI
|
|
26
|
+
const timers = new Map<string, NodeJS.Timeout>()
|
|
27
|
+
|
|
28
|
+
function scheduleValidation(doc: vscode.TextDocument) {
|
|
29
|
+
if (doc.languageId !== 'redscript') return
|
|
30
|
+
const key = doc.uri.toString()
|
|
31
|
+
const existing = timers.get(key)
|
|
32
|
+
if (existing) clearTimeout(existing)
|
|
33
|
+
timers.set(key, setTimeout(() => {
|
|
34
|
+
validateDocument(doc, diagnostics)
|
|
35
|
+
timers.delete(key)
|
|
36
|
+
}, DEBOUNCE_MS))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Validate on open
|
|
40
|
+
context.subscriptions.push(
|
|
41
|
+
vscode.workspace.onDidOpenTextDocument(doc => scheduleValidation(doc))
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
// Validate on change
|
|
45
|
+
context.subscriptions.push(
|
|
46
|
+
vscode.workspace.onDidChangeTextDocument(e => scheduleValidation(e.document))
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
// Clear diagnostics on close
|
|
50
|
+
context.subscriptions.push(
|
|
51
|
+
vscode.workspace.onDidCloseTextDocument(doc => {
|
|
52
|
+
diagnostics.delete(doc.uri)
|
|
53
|
+
const key = doc.uri.toString()
|
|
54
|
+
const t = timers.get(key)
|
|
55
|
+
if (t) { clearTimeout(t); timers.delete(key) }
|
|
56
|
+
})
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
// Validate all already-open .rs files
|
|
60
|
+
vscode.workspace.textDocuments
|
|
61
|
+
.filter(d => d.languageId === 'redscript')
|
|
62
|
+
.forEach(d => scheduleValidation(d))
|
|
63
|
+
|
|
64
|
+
// Register hover documentation
|
|
65
|
+
registerHoverProvider(context)
|
|
66
|
+
|
|
67
|
+
// Register completions
|
|
68
|
+
registerCompletionProvider(context)
|
|
69
|
+
|
|
70
|
+
// Register code actions (quick fixes)
|
|
71
|
+
registerCodeActions(context)
|
|
72
|
+
|
|
73
|
+
// Register Go-to-Definition and Find-References
|
|
74
|
+
registerSymbolProviders(context)
|
|
75
|
+
|
|
76
|
+
// Status bar item to show compilation state
|
|
77
|
+
const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10)
|
|
78
|
+
statusBar.text = '$(pass) RedScript'
|
|
79
|
+
statusBar.tooltip = 'RedScript compiler'
|
|
80
|
+
statusBar.show()
|
|
81
|
+
context.subscriptions.push(statusBar)
|
|
82
|
+
|
|
83
|
+
// Track the active editor's compile state
|
|
84
|
+
context.subscriptions.push(
|
|
85
|
+
vscode.window.onDidChangeActiveTextEditor(editor => {
|
|
86
|
+
if (editor?.document.languageId === 'redscript') {
|
|
87
|
+
statusBar.show()
|
|
88
|
+
} else {
|
|
89
|
+
statusBar.hide()
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function validateDocument(
|
|
96
|
+
doc: vscode.TextDocument,
|
|
97
|
+
collection: vscode.DiagnosticCollection
|
|
98
|
+
): void {
|
|
99
|
+
const compile = getCompile()
|
|
100
|
+
if (!compile) {
|
|
101
|
+
// Compiler not available — show a one-time info message
|
|
102
|
+
collection.set(doc.uri, [{
|
|
103
|
+
message: 'RedScript compiler not found. Run `npm install -g redscript` to enable diagnostics.',
|
|
104
|
+
range: new vscode.Range(0, 0, 0, 0),
|
|
105
|
+
severity: vscode.DiagnosticSeverity.Information,
|
|
106
|
+
source: 'redscript'
|
|
107
|
+
}])
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const source = doc.getText()
|
|
112
|
+
const docDiagnostics: vscode.Diagnostic[] = []
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const result = compile(source, { filePath: doc.uri.fsPath })
|
|
116
|
+
|
|
117
|
+
// Convert warnings to VS Code diagnostics
|
|
118
|
+
for (const w of result.warnings ?? []) {
|
|
119
|
+
// Use real line/col from AST span when available, fall back to text search
|
|
120
|
+
const range = (w.line && w.col)
|
|
121
|
+
? new vscode.Range(w.line - 1, w.col - 1, w.line - 1, w.col - 1 + 20)
|
|
122
|
+
: findWarningRange(w.message, w.code, source, doc)
|
|
123
|
+
docDiagnostics.push({
|
|
124
|
+
message: w.message,
|
|
125
|
+
range,
|
|
126
|
+
severity: vscode.DiagnosticSeverity.Warning,
|
|
127
|
+
source: 'redscript',
|
|
128
|
+
code: w.code
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
} catch (err: unknown) {
|
|
132
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
133
|
+
|
|
134
|
+
// Try to get location from DiagnosticError.location first
|
|
135
|
+
let range: vscode.Range
|
|
136
|
+
const loc = (err as { location?: { line?: number; col?: number } }).location
|
|
137
|
+
if (loc?.line && loc?.col) {
|
|
138
|
+
const l = Math.max(0, loc.line - 1)
|
|
139
|
+
const c = Math.max(0, loc.col - 1)
|
|
140
|
+
range = new vscode.Range(l, c, l, c + 20)
|
|
141
|
+
} else {
|
|
142
|
+
// Fallback: parse the error message for line/column info
|
|
143
|
+
range = extractRange(msg, doc)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
docDiagnostics.push({
|
|
147
|
+
message: msg,
|
|
148
|
+
range,
|
|
149
|
+
severity: vscode.DiagnosticSeverity.Error,
|
|
150
|
+
source: 'redscript'
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
collection.set(doc.uri, docDiagnostics)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* For warnings without position info, search the source for the relevant token
|
|
159
|
+
* mentioned in the warning message.
|
|
160
|
+
*/
|
|
161
|
+
function findWarningRange(
|
|
162
|
+
message: string,
|
|
163
|
+
code: string | undefined,
|
|
164
|
+
source: string,
|
|
165
|
+
doc: vscode.TextDocument
|
|
166
|
+
): vscode.Range {
|
|
167
|
+
// W_UNNAMESPACED_TYPE: message contains the unqualified type name in quotes
|
|
168
|
+
// e.g. 'Unnamespaced entity type "zombie"'
|
|
169
|
+
if (code === 'W_UNNAMESPACED_TYPE') {
|
|
170
|
+
const m = message.match(/"([^"]+)"/)
|
|
171
|
+
if (m) return searchToken(source, doc, `type=${m[1]}`) ?? searchToken(source, doc, m[1]) ?? topLine(doc)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// W_QUOTED_SELECTOR: message contains the quoted selector
|
|
175
|
+
// e.g. 'Quoted selector "@a" is deprecated'
|
|
176
|
+
if (code === 'W_QUOTED_SELECTOR') {
|
|
177
|
+
const m = message.match(/"(@[^"]+)"/)
|
|
178
|
+
if (m) return searchToken(source, doc, `"${m[1]}"`) ?? topLine(doc)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// W_DEPRECATED: usually about tp_to
|
|
182
|
+
if (code === 'W_DEPRECATED') {
|
|
183
|
+
const m = message.match(/^(\w+) is deprecated/)
|
|
184
|
+
if (m) return searchToken(source, doc, m[1]) ?? topLine(doc)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return topLine(doc)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Search source for a literal string, return range of first match. */
|
|
191
|
+
function searchToken(
|
|
192
|
+
source: string,
|
|
193
|
+
doc: vscode.TextDocument,
|
|
194
|
+
token: string
|
|
195
|
+
): vscode.Range | null {
|
|
196
|
+
const idx = source.indexOf(token)
|
|
197
|
+
if (idx < 0) return null
|
|
198
|
+
const pos = doc.positionAt(idx)
|
|
199
|
+
return new vscode.Range(pos, doc.positionAt(idx + token.length))
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function topLine(doc: vscode.TextDocument): vscode.Range {
|
|
203
|
+
return new vscode.Range(0, 0, 0, doc.lineAt(0).text.length)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Try to extract line/column from common error formats:
|
|
208
|
+
* "Error at line 5, column 12: ..."
|
|
209
|
+
* "5:12: ..."
|
|
210
|
+
* "[line 5] ..."
|
|
211
|
+
*/
|
|
212
|
+
function extractRange(msg: string, doc: vscode.TextDocument): vscode.Range {
|
|
213
|
+
// "line N, column M"
|
|
214
|
+
let m = msg.match(/line[: ]+(\d+)[,\s]+col(?:umn)?[: ]+(\d+)/i)
|
|
215
|
+
if (m) {
|
|
216
|
+
const l = Math.max(0, parseInt(m[1]) - 1)
|
|
217
|
+
const c = Math.max(0, parseInt(m[2]) - 1)
|
|
218
|
+
return new vscode.Range(l, c, l, c + 80)
|
|
219
|
+
}
|
|
220
|
+
// "N:M"
|
|
221
|
+
m = msg.match(/^(\d+):(\d+)/)
|
|
222
|
+
if (m) {
|
|
223
|
+
const l = Math.max(0, parseInt(m[1]) - 1)
|
|
224
|
+
const c = Math.max(0, parseInt(m[2]) - 1)
|
|
225
|
+
return new vscode.Range(l, c, l, c + 80)
|
|
226
|
+
}
|
|
227
|
+
// "[line N]"
|
|
228
|
+
m = msg.match(/\[line (\d+)\]/i)
|
|
229
|
+
if (m) {
|
|
230
|
+
const l = Math.max(0, parseInt(m[1]) - 1)
|
|
231
|
+
return new vscode.Range(l, 0, l, 200)
|
|
232
|
+
}
|
|
233
|
+
// Fallback: highlight first line
|
|
234
|
+
return new vscode.Range(0, 0, 0, doc.lineAt(0).text.length)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function deactivate(): void {
|
|
238
|
+
// nothing
|
|
239
|
+
}
|