@lokascript/language-server 3.2.0 → 4.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/README.md +6 -5
- package/dist/server.js +144 -240
- package/package.json +11 -6
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ The language server supports four operating modes:
|
|
|
15
15
|
|
|
16
16
|
### Mode Selection
|
|
17
17
|
|
|
18
|
-
- **auto** (default): `lokascript` mode when the semantic package exposes its API; `hyperscript` mode in builds that replace it with a shim
|
|
18
|
+
- **auto** (default): `lokascript` mode when the semantic package exposes its API; `hyperscript` mode in builds that replace it with a shim
|
|
19
19
|
- **hyperscript**: Enforces \_hyperscript-compatible syntax, English keywords only
|
|
20
20
|
- **hyperscript-i18n**: Enforces \_hyperscript-compatible syntax with multilingual keyword support. Use this if you have original \_hyperscript with `@lokascript/hyperscript-adapter` for writing in non-English languages
|
|
21
21
|
- **lokascript**: Enables all features including LokaScript extensions and multilingual support
|
|
@@ -117,8 +117,8 @@ require('lspconfig').lokascript.setup{}
|
|
|
117
117
|
|
|
118
118
|
The server reads its settings three ways, and every field is optional (a partial object is merged over the defaults):
|
|
119
119
|
|
|
120
|
-
1. `initializationOptions` on `initialize` (
|
|
121
|
-
2. a `workspace/didChangeConfiguration` push carrying a `lokascript` or `hyperscript` section (vscode-languageclient only sends one when the client sets `synchronize.configurationSection`;
|
|
120
|
+
1. `initializationOptions` on `initialize` (the VS Code extension passes `{ language }` here);
|
|
121
|
+
2. a `workspace/didChangeConfiguration` push carrying a `lokascript` or `hyperscript` section (vscode-languageclient only sends one when the client sets `synchronize.configurationSection`; the VS Code extension does);
|
|
122
122
|
3. a `workspace/configuration` pull for the `lokascript` and `hyperscript` sections, made at startup and whenever a push arrives with no payload, for clients that advertise the capability.
|
|
123
123
|
|
|
124
124
|
When both namespaces are present the `lokascript` one wins, unless the server was launched with `HYPERSCRIPT_LS_DEFAULT_MODE` set (the standalone hyperscript product), in which case `hyperscript` wins.
|
|
@@ -227,9 +227,10 @@ For users who prefer the `hyperscript` namespace:
|
|
|
227
227
|
## Dependencies
|
|
228
228
|
|
|
229
229
|
- `@lokascript/semantic` and `@lokascript/framework` are **required** (regular dependencies): the semantic package registers the 24 languages at startup, and the framework renders the LSE bracket notation shown in hover. They used to be declared as optional peers while being imported statically, so a server installed without them crashed at startup instead of falling back.
|
|
230
|
-
- `@hyperfixi/
|
|
230
|
+
- `@hyperfixi/engine` is an **optional** peer: the engine that runs hyperscript. With it the server surfaces its parse errors (at the position the engine stopped) and gives the AST nodes their source positions for hover, symbols and completions.
|
|
231
|
+
- `@hyperfixi/core` is an **optional** peer, for two of its entries: `/ast-utils` (complexity diagnostics, hover, symbols and completions on the interchange) and `/lsp-metadata` (keyword and hover docs). The AST nodes come from `@lokascript/semantic`, which names each command's roles itself. Without either, diagnostics degrade to the pattern-based quote/bracket checks and hover uses built-in fallback docs. (Until 4.0 the parse errors and nodes came from `@hyperfixi/core`'s parser.)
|
|
231
232
|
|
|
232
|
-
A bundler that wants an English-only, dependency-free server
|
|
233
|
+
A bundler that wants an English-only, dependency-free server replaces the semantic, framework, engine and core imports with throwing or empty shims at bundle time; the server's capability probes are written for that case.
|
|
233
234
|
|
|
234
235
|
## Development
|
|
235
236
|
|
package/dist/server.js
CHANGED
|
@@ -24,13 +24,25 @@ var defaultSettings = {
|
|
|
24
24
|
|
|
25
25
|
// src/command-tiers.ts
|
|
26
26
|
var HYPERSCRIPT_COMMANDS = [
|
|
27
|
-
//
|
|
27
|
+
// The engine's commands.
|
|
28
28
|
"add",
|
|
29
|
+
"answer",
|
|
29
30
|
"append",
|
|
31
|
+
"ask",
|
|
32
|
+
"beep!",
|
|
33
|
+
"blur",
|
|
34
|
+
"break",
|
|
35
|
+
"breakpoint",
|
|
30
36
|
"call",
|
|
37
|
+
"clear",
|
|
38
|
+
"close",
|
|
39
|
+
"continue",
|
|
40
|
+
"decrement",
|
|
31
41
|
"default",
|
|
42
|
+
"empty",
|
|
32
43
|
"exit",
|
|
33
44
|
"fetch",
|
|
45
|
+
"focus",
|
|
34
46
|
"for",
|
|
35
47
|
"get",
|
|
36
48
|
"go",
|
|
@@ -38,16 +50,27 @@ var HYPERSCRIPT_COMMANDS = [
|
|
|
38
50
|
"hide",
|
|
39
51
|
"if",
|
|
40
52
|
"increment",
|
|
41
|
-
"decrement",
|
|
42
53
|
"js",
|
|
43
54
|
"log",
|
|
55
|
+
"make",
|
|
56
|
+
"measure",
|
|
57
|
+
"morph",
|
|
58
|
+
"open",
|
|
59
|
+
"pick",
|
|
44
60
|
"put",
|
|
45
61
|
"remove",
|
|
62
|
+
"render",
|
|
46
63
|
"repeat",
|
|
64
|
+
"reset",
|
|
47
65
|
"return",
|
|
66
|
+
"scroll",
|
|
67
|
+
"select",
|
|
48
68
|
"send",
|
|
49
69
|
"set",
|
|
70
|
+
"settle",
|
|
50
71
|
"show",
|
|
72
|
+
"start",
|
|
73
|
+
"swap",
|
|
51
74
|
"take",
|
|
52
75
|
"tell",
|
|
53
76
|
"throw",
|
|
@@ -55,203 +78,63 @@ var HYPERSCRIPT_COMMANDS = [
|
|
|
55
78
|
"transition",
|
|
56
79
|
"trigger",
|
|
57
80
|
"wait",
|
|
81
|
+
// Continues a `repeat`; completed like a command.
|
|
58
82
|
"while",
|
|
59
|
-
//
|
|
60
|
-
// on hyperscript.org@0.9.93 in the form shown and produced the command node
|
|
61
|
-
// named beside it; none of them is a LokaScript invention.
|
|
62
|
-
"blur",
|
|
63
|
-
// `blur #a` → BlurCommand
|
|
64
|
-
"break",
|
|
65
|
-
// `repeat 3 times break end` → controlflow.js BreakCommand
|
|
66
|
-
"breakpoint",
|
|
67
|
-
// `breakpoint` → BreakpointCommand (core debug.js)
|
|
68
|
-
"clear",
|
|
69
|
-
// `clear #a` → EmptyCommand — upstream spells the
|
|
70
|
-
"empty",
|
|
71
|
-
// `empty #a` pair as ONE command with two keywords
|
|
72
|
-
"close",
|
|
73
|
-
// `close #a` → CloseCommand
|
|
74
|
-
"continue",
|
|
75
|
-
// `repeat 3 times continue end` → controlflow.js ContinueCommand
|
|
76
|
-
"focus",
|
|
77
|
-
// `focus #a` → FocusCommand
|
|
78
|
-
"open",
|
|
79
|
-
// `open #a` → OpenCommand
|
|
80
|
-
"pick",
|
|
81
|
-
// `pick items 1 to 3 from "hello"` → PickCommand
|
|
82
|
-
"render",
|
|
83
|
-
// `render #tpl` → RenderCommand
|
|
84
|
-
"reset",
|
|
85
|
-
// `reset #a` → ResetCommand
|
|
86
|
-
"scroll",
|
|
87
|
-
// `scroll to #a` → ScrollCommand
|
|
88
|
-
"select",
|
|
89
|
-
// `select #a` → SelectCommand
|
|
90
|
-
"start",
|
|
91
|
-
// `start view transition end` → ViewTransitionCommand
|
|
92
|
-
"swap",
|
|
93
|
-
// `swap $a with $b` → SwapCommand. The htmx-style
|
|
94
|
-
// `swap <content> into <target>` form is an EXTENSION of this same
|
|
95
|
-
// command, not a different one — `commands/dom/swap.ts` implements the
|
|
96
|
-
// upstream value-swap variant explicitly, under a `variant` discriminator.
|
|
97
|
-
// Never written by a user: `pseudo-command` is the internal registry name for
|
|
98
|
-
// method-call-as-command (`foo() on #a`), and `-` is not an identifier
|
|
99
|
-
// character, so no source text can contain the token. Listed so the
|
|
100
|
-
// partition is total. The mechanism is upstream — the probe above yields
|
|
101
|
-
// PseudoCommand from `parsetree/commands/pseudoCommand.js`, which the core
|
|
102
|
-
// `_hyperscript.js` bundle registers.
|
|
103
|
-
"pseudo-command",
|
|
104
|
-
// Moved OUT of LOKASCRIPT_ONLY_COMMANDS by the same 4.1 oracle run. Each was
|
|
105
|
-
// asserted to be a LokaScript extension and each parses on stock
|
|
106
|
-
// hyperscript.org@0.9.93, so the LSP was raising a spurious *error*
|
|
107
|
-
// diagnostic on portable code — the same defect as the omissions above, with
|
|
108
|
-
// the sign flipped.
|
|
109
|
-
"make",
|
|
110
|
-
// `make a <div/>` → MakeCommand (basic.js)
|
|
111
|
-
"measure",
|
|
112
|
-
// `measure #a` → MeasureCommand (dom.js)
|
|
113
|
-
"morph",
|
|
114
|
-
// `morph #a to "<p>x</p>"` → MorphCommand (dom.js)
|
|
115
|
-
"settle",
|
|
116
|
-
// `settle` → SettleCommand (animations.js)
|
|
117
|
-
// `install` is upstream at the position it is actually written — `_="install
|
|
118
|
-
// Foo"` parses as InstallFeature. It is a FEATURE upstream and a registered
|
|
119
|
-
// command here, so `on click install Foo` works only in LokaScript. The
|
|
120
|
-
// lists are keyed by name and cannot express "upstream in feature position
|
|
121
|
-
// only"; classified by the canonical usage, which is portable.
|
|
122
|
-
"install",
|
|
123
|
-
// Definitions
|
|
83
|
+
// The engine's features (`set` and `js` are features too, listed above).
|
|
124
84
|
"behavior",
|
|
85
|
+
"bind",
|
|
125
86
|
"def",
|
|
126
87
|
"init",
|
|
88
|
+
"install",
|
|
89
|
+
"live",
|
|
127
90
|
"on",
|
|
91
|
+
"when",
|
|
92
|
+
// Upstream extensions (ext/eventsource.js, ext/socket.js, ext/worker.js); not in the engine.
|
|
128
93
|
"eventsource",
|
|
129
94
|
"socket",
|
|
130
95
|
"worker"
|
|
131
96
|
];
|
|
132
|
-
var LOKASCRIPT_ONLY_COMMANDS = [
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
// `prepend "x" to #a` → Unexpected Token : prepend
|
|
137
|
-
"process",
|
|
138
|
-
// `process "<p>x</p>"` → Unexpected Token : process
|
|
139
|
-
// Added by Arc A step 4.1 — the omissions that produced NO diagnostic.
|
|
140
|
-
"copy",
|
|
141
|
-
// `copy "x"` → Unexpected Token : copy
|
|
142
|
-
"push",
|
|
143
|
-
// `push url "/x"` → Unexpected Token : push
|
|
144
|
-
"replace",
|
|
145
|
-
// `replace url "/x"` → Unexpected Token : replace
|
|
146
|
-
// Upstream spells this command `beep!`, with the bang as part of the token:
|
|
147
|
-
// `beep! me` parses (BeepCommand), `beep me` does not. hyperfixi registers it
|
|
148
|
-
// as `beep` and accepts both spellings, so the portable spelling is `beep!`
|
|
149
|
-
// and the bare one is the extension. Classified by the registered name,
|
|
150
|
-
// which is what these lists key on.
|
|
151
|
-
"beep",
|
|
152
|
-
// `beep me` → Unexpected Token : beep
|
|
153
|
-
// Upstream has `unless` only as a TRAILING statement modifier
|
|
154
|
-
// (`log "x" unless true` → UnlessStatementModifier, kernel.js
|
|
155
|
-
// parseIndirectStatement). The registered `unless` COMMAND — the leading
|
|
156
|
-
// block form, an alias of `if` — is the LokaScript addition.
|
|
157
|
-
"unless"
|
|
158
|
-
// `unless true log "x" end` → Unexpected Token : unless
|
|
97
|
+
var LOKASCRIPT_ONLY_COMMANDS = [];
|
|
98
|
+
var ALL_COMMANDS = [
|
|
99
|
+
...HYPERSCRIPT_COMMANDS,
|
|
100
|
+
...LOKASCRIPT_ONLY_COMMANDS
|
|
159
101
|
];
|
|
160
|
-
var
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Optional chaining: my?.value
|
|
170
|
-
*/
|
|
171
|
-
optionalChaining: /\b(my|your|its)\?\.\w+/,
|
|
172
|
-
/**
|
|
173
|
-
* Extended 'as' conversions
|
|
174
|
-
*/
|
|
175
|
-
extendedAsConversion: /\bas\s+(Math|Values)\b/i,
|
|
176
|
-
/**
|
|
177
|
-
* Debounce/throttle modifiers with duration: .debounce(300), .throttle(1s)
|
|
178
|
-
*/
|
|
179
|
-
temporalModifiers: /\.(debounce|throttle)\s*\(\s*\d+/i
|
|
180
|
-
};
|
|
181
|
-
function blankStringsAndComments(code) {
|
|
182
|
-
let out = "";
|
|
183
|
-
let i = 0;
|
|
184
|
-
while (i < code.length) {
|
|
185
|
-
const ch = code[i];
|
|
186
|
-
if (ch === "-" && code[i + 1] === "-") {
|
|
187
|
-
const eol = code.indexOf("\n", i);
|
|
188
|
-
const end = eol === -1 ? code.length : eol;
|
|
189
|
-
out += " ".repeat(end - i);
|
|
190
|
-
i = end;
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
const prev = i === 0 ? " " : code[i - 1];
|
|
194
|
-
const opensString = ch === '"' || ch === "`" || ch === "'" && /[\s(,\[=]/.test(prev);
|
|
195
|
-
if (opensString) {
|
|
196
|
-
let j = i + 1;
|
|
197
|
-
while (j < code.length && code[j] !== ch) {
|
|
198
|
-
if (code[j] === "\\") j++;
|
|
199
|
-
if (code[j] === "\n" && ch !== "`") break;
|
|
200
|
-
j++;
|
|
201
|
-
}
|
|
202
|
-
const end = Math.min(j + 1, code.length);
|
|
203
|
-
out += " ".repeat(end - i);
|
|
204
|
-
i = end;
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
out += ch;
|
|
208
|
-
i++;
|
|
102
|
+
var ENGINE_ADDITIONS = {
|
|
103
|
+
newExpression: {
|
|
104
|
+
pattern: "new-expression",
|
|
105
|
+
description: "'new X()' is an @hyperfixi/engine addition; upstream reads `new` as a variable (use `make a X`)"
|
|
106
|
+
},
|
|
107
|
+
toggleElementCommand: {
|
|
108
|
+
pattern: "toggle-element",
|
|
109
|
+
description: "'toggle <element>' (open or close a dialog, details or popover) is an @hyperfixi/engine addition"
|
|
209
110
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
function detectLokascriptFeatures(code) {
|
|
111
|
+
};
|
|
112
|
+
function detectLokascriptFeatures(engineTree) {
|
|
213
113
|
const detected = [];
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
114
|
+
const walk = (node) => {
|
|
115
|
+
if (Array.isArray(node)) {
|
|
116
|
+
for (const item of node) walk(item);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (typeof node !== "object" || node === null) return;
|
|
120
|
+
const type = Reflect.get(node, "type");
|
|
121
|
+
const addition = typeof type === "string" ? ENGINE_ADDITIONS[type] : void 0;
|
|
122
|
+
if (addition) {
|
|
123
|
+
const start = Reflect.get(node, "start");
|
|
124
|
+
const end = Reflect.get(node, "end");
|
|
218
125
|
detected.push({
|
|
219
|
-
feature: "
|
|
220
|
-
description:
|
|
221
|
-
pattern:
|
|
126
|
+
feature: "syntax",
|
|
127
|
+
description: addition.description,
|
|
128
|
+
pattern: addition.pattern,
|
|
129
|
+
...typeof start === "number" && { start },
|
|
130
|
+
...typeof end === "number" && { end }
|
|
222
131
|
});
|
|
223
132
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
pattern: "dot-notation"
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
if (LOKASCRIPT_SYNTAX_PATTERNS.optionalChaining.test(code)) {
|
|
233
|
-
detected.push({
|
|
234
|
-
feature: "syntax",
|
|
235
|
-
description: "Optional chaining (my?.property) is a LokaScript extension",
|
|
236
|
-
pattern: "optional-chaining"
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
const asMatch = code.match(LOKASCRIPT_SYNTAX_PATTERNS.extendedAsConversion);
|
|
240
|
-
if (asMatch) {
|
|
241
|
-
detected.push({
|
|
242
|
-
feature: "conversion",
|
|
243
|
-
description: `'as ${asMatch[1]}' is a LokaScript extension`,
|
|
244
|
-
pattern: `as-${asMatch[1].toLowerCase()}`
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
const temporalMatch = code.match(LOKASCRIPT_SYNTAX_PATTERNS.temporalModifiers);
|
|
248
|
-
if (temporalMatch) {
|
|
249
|
-
detected.push({
|
|
250
|
-
feature: "modifier",
|
|
251
|
-
description: `'.${temporalMatch[1]}()' modifier is a LokaScript extension`,
|
|
252
|
-
pattern: temporalMatch[1].toLowerCase()
|
|
253
|
-
});
|
|
254
|
-
}
|
|
133
|
+
for (const value of Object.values(node)) {
|
|
134
|
+
if (typeof value === "object" && value !== null) walk(value);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
walk(engineTree);
|
|
255
138
|
return detected;
|
|
256
139
|
}
|
|
257
140
|
|
|
@@ -1355,28 +1238,21 @@ function getOtherLanguages() {
|
|
|
1355
1238
|
return semanticPackage.getRegisteredLanguages().filter((l) => l !== "en");
|
|
1356
1239
|
}
|
|
1357
1240
|
var interchangeLSP = null;
|
|
1358
|
-
var
|
|
1359
|
-
var parseFunction = null;
|
|
1241
|
+
var engineParse = null;
|
|
1360
1242
|
var lspMetadata = null;
|
|
1361
1243
|
try {
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
"[lokascript-ls] @hyperfixi/core/multilingual not available \u2014 hover shows roles for `set` and `go` only. Install a core build that ships the multilingual entry."
|
|
1370
|
-
);
|
|
1371
|
-
}
|
|
1372
|
-
fromCoreASTFn = (node) => core.fromCoreAST(
|
|
1373
|
-
node,
|
|
1374
|
-
inferRoles ? { inferRoles } : void 0
|
|
1375
|
-
);
|
|
1244
|
+
const engine = await import("@hyperfixi/engine");
|
|
1245
|
+
engine.register(...engine.everything);
|
|
1246
|
+
engineParse = engine.parse;
|
|
1247
|
+
} catch {
|
|
1248
|
+
console.error("[lokascript-ls] @hyperfixi/engine not available \u2014 parse errors disabled");
|
|
1249
|
+
}
|
|
1250
|
+
try {
|
|
1376
1251
|
interchangeLSP = await import("@hyperfixi/core/ast-utils");
|
|
1377
|
-
console.error("[lokascript-ls] @hyperfixi/core loaded \u2014 AST parsing + interchange LSP enabled");
|
|
1378
1252
|
} catch {
|
|
1379
|
-
console.error(
|
|
1253
|
+
console.error(
|
|
1254
|
+
"[lokascript-ls] @hyperfixi/core/ast-utils not available \u2014 AST diagnostics disabled"
|
|
1255
|
+
);
|
|
1380
1256
|
}
|
|
1381
1257
|
try {
|
|
1382
1258
|
lspMetadata = await import("@hyperfixi/core/lsp-metadata");
|
|
@@ -1398,8 +1274,46 @@ try {
|
|
|
1398
1274
|
);
|
|
1399
1275
|
}
|
|
1400
1276
|
console.error(
|
|
1401
|
-
`[lokascript-ls] capabilities:
|
|
1277
|
+
`[lokascript-ls] capabilities: engine=${!!engineParse}, interchange=${!!interchangeLSP}, metadata=${!!lspMetadata}, framework=${!!frameworkIR}`
|
|
1402
1278
|
);
|
|
1279
|
+
function parseToInterchange(code, language) {
|
|
1280
|
+
const errors = [];
|
|
1281
|
+
let engineTree = null;
|
|
1282
|
+
if (engineParse && (language === "en" || language.startsWith("en-"))) {
|
|
1283
|
+
try {
|
|
1284
|
+
engineTree = engineParse(code);
|
|
1285
|
+
} catch (e) {
|
|
1286
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1287
|
+
const token = typeof e === "object" && e !== null ? Reflect.get(e, "token") : void 0;
|
|
1288
|
+
const start = typeof token === "object" && token !== null ? Reflect.get(token, "start") : void 0;
|
|
1289
|
+
const before = typeof start === "number" ? code.slice(0, start) : null;
|
|
1290
|
+
errors.push({
|
|
1291
|
+
message: message.split("\n")[0] ?? message,
|
|
1292
|
+
...before !== null ? {
|
|
1293
|
+
line: before.split("\n").length,
|
|
1294
|
+
column: before.length - (before.lastIndexOf("\n") + 1)
|
|
1295
|
+
} : {}
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
let nodes = [];
|
|
1300
|
+
try {
|
|
1301
|
+
const node = semanticPackage?.parseSemantic?.(code, language)?.node;
|
|
1302
|
+
const ast = node ? semanticPackage.buildAST(node)?.ast : null;
|
|
1303
|
+
if (ast) {
|
|
1304
|
+
const converted = semanticPackage.fromSemanticAST(ast);
|
|
1305
|
+
nodes = (Array.isArray(converted) ? converted : [converted]).filter(
|
|
1306
|
+
(n) => n?.type !== "error"
|
|
1307
|
+
);
|
|
1308
|
+
if (engineTree && interchangeLSP?.withEnginePositions) {
|
|
1309
|
+
nodes = interchangeLSP.withEnginePositions(nodes, code, engineTree);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
} catch {
|
|
1313
|
+
nodes = [];
|
|
1314
|
+
}
|
|
1315
|
+
return { nodes, errors };
|
|
1316
|
+
}
|
|
1403
1317
|
var FALLBACK_KEYWORDS = [
|
|
1404
1318
|
"toggle",
|
|
1405
1319
|
"add",
|
|
@@ -1446,8 +1360,7 @@ var FALLBACK_KEYWORDS = [
|
|
|
1446
1360
|
"not",
|
|
1447
1361
|
"is",
|
|
1448
1362
|
"exists",
|
|
1449
|
-
"empty"
|
|
1450
|
-
"has"
|
|
1363
|
+
"empty"
|
|
1451
1364
|
];
|
|
1452
1365
|
var FALLBACK_HOVER_DOCS = {
|
|
1453
1366
|
toggle: {
|
|
@@ -1556,11 +1469,6 @@ var FALLBACK_HOVER_DOCS = {
|
|
|
1556
1469
|
description: "Appends content to an element.",
|
|
1557
1470
|
example: 'append "<li>item</li>" to #list'
|
|
1558
1471
|
},
|
|
1559
|
-
prepend: {
|
|
1560
|
-
title: "prepend",
|
|
1561
|
-
description: "Prepends content to an element.",
|
|
1562
|
-
example: 'prepend "<li>first</li>" to #list'
|
|
1563
|
-
},
|
|
1564
1472
|
take: {
|
|
1565
1473
|
title: "take",
|
|
1566
1474
|
description: "Takes a class from sibling elements (exclusive toggle).",
|
|
@@ -1613,13 +1521,8 @@ var FALLBACK_HOVER_DOCS = {
|
|
|
1613
1521
|
},
|
|
1614
1522
|
tell: {
|
|
1615
1523
|
title: "tell",
|
|
1616
|
-
description: "
|
|
1617
|
-
example: "tell #modal
|
|
1618
|
-
},
|
|
1619
|
-
copy: {
|
|
1620
|
-
title: "copy",
|
|
1621
|
-
description: "Copies text to the clipboard.",
|
|
1622
|
-
example: 'copy "Hello" to the clipboard'
|
|
1524
|
+
description: "Runs commands with another element as `you` (and the implicit target).",
|
|
1525
|
+
example: "tell #modal add .visible end"
|
|
1623
1526
|
}
|
|
1624
1527
|
};
|
|
1625
1528
|
var FALLBACK_EVENT_NAMES = [
|
|
@@ -1854,13 +1757,17 @@ function nameCollisionDiagnostics(code, language, source) {
|
|
|
1854
1757
|
async function getDiagnostics(code, language) {
|
|
1855
1758
|
const diagnostics = [];
|
|
1856
1759
|
const brand = getBranding(resolvedMode);
|
|
1857
|
-
if (isHyperscriptCompatMode(resolvedMode)) {
|
|
1858
|
-
|
|
1859
|
-
|
|
1760
|
+
if (isHyperscriptCompatMode(resolvedMode) && engineParse) {
|
|
1761
|
+
let tree = null;
|
|
1762
|
+
try {
|
|
1763
|
+
tree = engineParse(code);
|
|
1764
|
+
} catch {
|
|
1765
|
+
}
|
|
1766
|
+
for (const feature of detectLokascriptFeatures(tree)) {
|
|
1860
1767
|
diagnostics.push({
|
|
1861
1768
|
range: {
|
|
1862
|
-
start:
|
|
1863
|
-
end:
|
|
1769
|
+
start: offsetToPosition(code, feature.start ?? 0),
|
|
1770
|
+
end: offsetToPosition(code, feature.end ?? code.split("\n")[0]?.length ?? 0)
|
|
1864
1771
|
},
|
|
1865
1772
|
severity: DiagnosticSeverity2.Error,
|
|
1866
1773
|
code: "lokascript-only",
|
|
@@ -1871,6 +1778,7 @@ async function getDiagnostics(code, language) {
|
|
|
1871
1778
|
}
|
|
1872
1779
|
const analyzer = isMultilingualEnabled(resolvedMode) ? getSemanticAnalyzer() : null;
|
|
1873
1780
|
let semanticParsedForeign = false;
|
|
1781
|
+
let detectedLanguage = "en";
|
|
1874
1782
|
if (analyzer) {
|
|
1875
1783
|
try {
|
|
1876
1784
|
let result = analyzer.analyze(code, language);
|
|
@@ -1918,6 +1826,7 @@ async function getDiagnostics(code, language) {
|
|
|
1918
1826
|
});
|
|
1919
1827
|
}
|
|
1920
1828
|
semanticParsedForeign = result.confidence >= 0.5 && usedLanguage !== "en";
|
|
1829
|
+
if (semanticParsedForeign) detectedLanguage = usedLanguage;
|
|
1921
1830
|
if (semanticParsedForeign) {
|
|
1922
1831
|
diagnostics.push(...nameCollisionDiagnostics(code, usedLanguage, brand));
|
|
1923
1832
|
}
|
|
@@ -1947,11 +1856,11 @@ async function getDiagnostics(code, language) {
|
|
|
1947
1856
|
connection.console.log(`[${brand}-ls] Semantic parsing failed, trying fallback: ${e}`);
|
|
1948
1857
|
}
|
|
1949
1858
|
}
|
|
1950
|
-
if (
|
|
1859
|
+
if (engineParse || interchangeLSP) {
|
|
1951
1860
|
try {
|
|
1952
|
-
const
|
|
1953
|
-
if (
|
|
1954
|
-
for (const err of
|
|
1861
|
+
const parsed = parseToInterchange(code, detectedLanguage);
|
|
1862
|
+
if (parsed.errors.length && !semanticParsedForeign) {
|
|
1863
|
+
for (const err of parsed.errors) {
|
|
1955
1864
|
diagnostics.push({
|
|
1956
1865
|
range: {
|
|
1957
1866
|
start: {
|
|
@@ -1970,14 +1879,10 @@ async function getDiagnostics(code, language) {
|
|
|
1970
1879
|
});
|
|
1971
1880
|
}
|
|
1972
1881
|
}
|
|
1973
|
-
if (
|
|
1974
|
-
const
|
|
1975
|
-
if (
|
|
1976
|
-
|
|
1977
|
-
const astDiagnostics = interchangeLSP.interchangeToLSPDiagnostics(nodes, {
|
|
1978
|
-
source: brand
|
|
1979
|
-
});
|
|
1980
|
-
diagnostics.push(...astDiagnostics);
|
|
1882
|
+
if (parsed.nodes.length && interchangeLSP) {
|
|
1883
|
+
const nodes = parsed.nodes.filter((n) => n.type !== "literal");
|
|
1884
|
+
if (nodes.length) {
|
|
1885
|
+
diagnostics.push(...interchangeLSP.interchangeToLSPDiagnostics(nodes, { source: brand }));
|
|
1981
1886
|
}
|
|
1982
1887
|
}
|
|
1983
1888
|
} catch (parseError) {
|
|
@@ -2418,19 +2323,18 @@ connection.onHover((params) => {
|
|
|
2418
2323
|
}
|
|
2419
2324
|
});
|
|
2420
2325
|
function tryGetLSE(code) {
|
|
2421
|
-
if (!interchangeLSP || !
|
|
2326
|
+
if (!interchangeLSP || !frameworkIR) return null;
|
|
2422
2327
|
try {
|
|
2423
|
-
const
|
|
2424
|
-
if (!
|
|
2425
|
-
const
|
|
2426
|
-
if (!interchange) return null;
|
|
2427
|
-
const semanticNode = frameworkIR.fromInterchangeNode(interchange);
|
|
2328
|
+
const [node] = parseToInterchange(code, globalSettings.language || "en").nodes;
|
|
2329
|
+
if (!node) return null;
|
|
2330
|
+
const semanticNode = frameworkIR.fromInterchangeNode(node);
|
|
2428
2331
|
return frameworkIR.renderExplicit(semanticNode);
|
|
2429
2332
|
} catch {
|
|
2430
2333
|
return null;
|
|
2431
2334
|
}
|
|
2432
2335
|
}
|
|
2433
2336
|
function expandSigilPrefix(line, word) {
|
|
2337
|
+
if (line[word.end] === "!") return { text: word.text + "!", start: word.start };
|
|
2434
2338
|
if (word.start === 0) return null;
|
|
2435
2339
|
const prev = line[word.start - 1];
|
|
2436
2340
|
if (prev !== "^" && prev !== "#") return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lokascript/language-server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Language Server Protocol implementation for LokaScript/hyperscript with 24 language support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/server.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"test": "vitest",
|
|
15
15
|
"typecheck": "tsc --noEmit",
|
|
16
16
|
"test:check": "npm run pretest --silent && VITEST_QUIET=1 bash ../../scripts/vitest-run.sh --reporter=dot",
|
|
17
|
-
"pretest": "bash ../../scripts/ensure-fresh.sh ../intent ../framework ../semantic ../core && npm run build --silent"
|
|
17
|
+
"pretest": "bash ../../scripts/ensure-fresh.sh ../intent ../framework ../semantic ../core ../engine && npm run build --silent"
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|
|
20
20
|
"lsp",
|
|
@@ -28,21 +28,26 @@
|
|
|
28
28
|
"author": "LokaScript Contributors",
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@lokascript/framework": "^
|
|
32
|
-
"@lokascript/semantic": "^
|
|
31
|
+
"@lokascript/framework": "^4.0.0",
|
|
32
|
+
"@lokascript/semantic": "^4.0.0",
|
|
33
33
|
"vscode-languageserver": "^9.0.1",
|
|
34
34
|
"vscode-languageserver-textdocument": "^1.0.15"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@hyperfixi/core": "^
|
|
37
|
+
"@hyperfixi/core": "^4.0.0",
|
|
38
|
+
"@hyperfixi/engine": "^4.0.0"
|
|
38
39
|
},
|
|
39
40
|
"peerDependenciesMeta": {
|
|
40
41
|
"@hyperfixi/core": {
|
|
41
42
|
"optional": true
|
|
43
|
+
},
|
|
44
|
+
"@hyperfixi/engine": {
|
|
45
|
+
"optional": true
|
|
42
46
|
}
|
|
43
47
|
},
|
|
44
48
|
"devDependencies": {
|
|
45
|
-
"@hyperfixi/core": "^
|
|
49
|
+
"@hyperfixi/core": "^4.0.0",
|
|
50
|
+
"@hyperfixi/engine": "^4.0.0",
|
|
46
51
|
"@types/node": "^26.6.3",
|
|
47
52
|
"@vitest/coverage-v8": "^5.0.0",
|
|
48
53
|
"tsup": "^8.0.0",
|