@ripple-ts/language-server 0.2.216 → 0.3.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/CHANGELOG.md +20 -0
- package/package.json +6 -6
- package/src/compileErrorDiagnosticPlugin.js +1 -4
- package/src/completionPlugin.js +271 -68
- package/src/definitionPlugin.js +2 -4
- package/src/utils.js +7 -28
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @ripple-ts/language-server
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#779](https://github.com/Ripple-TS/ripple/pull/779)
|
|
8
|
+
[`74a10cc`](https://github.com/Ripple-TS/ripple/commit/74a10cc5701962cd7c72b144d59b35ecb76263a3)
|
|
9
|
+
Thanks [@leonidaz](https://github.com/leonidaz)! - Introduces #ripple namespace
|
|
10
|
+
for creating ripple reactive entities without imports, such as array, object,
|
|
11
|
+
map, set, date, url, urlSearchParams, mediaQuery. Adds track, untrack,
|
|
12
|
+
trackSplit, effect, context, server, style to the namespace. Deprecates #[] and
|
|
13
|
+
#{} in favor of #ripple[] and #ripple{}. Renames types and actual reactive
|
|
14
|
+
imports for TrackedX entities, such as TrackedArray, TrackedObject, etc. into
|
|
15
|
+
RippleArray, RippleObjec, etc.
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies
|
|
20
|
+
[[`74a10cc`](https://github.com/Ripple-TS/ripple/commit/74a10cc5701962cd7c72b144d59b35ecb76263a3)]:
|
|
21
|
+
- @ripple-ts/typescript-plugin@0.3.0
|
|
22
|
+
|
|
3
23
|
## 0.2.216
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ripple-ts/language-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Language Server Protocol implementation for Ripple",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -15,17 +15,17 @@
|
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@volar/language-server": "~2.4.28",
|
|
18
|
-
"volar-service-css": "0.0.
|
|
19
|
-
"volar-service-typescript": "0.0.
|
|
18
|
+
"volar-service-css": "0.0.70",
|
|
19
|
+
"volar-service-typescript": "0.0.70",
|
|
20
20
|
"vscode-languageserver-textdocument": "^1.0.12",
|
|
21
21
|
"vscode-uri": "^3.1.0",
|
|
22
|
-
"@ripple-ts/typescript-plugin": "0.
|
|
22
|
+
"@ripple-ts/typescript-plugin": "0.3.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"ripple": "0.
|
|
25
|
+
"ripple": "0.3.0"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"typescript": "^5.9.
|
|
28
|
+
"typescript": "^5.9.3"
|
|
29
29
|
},
|
|
30
30
|
"publishConfig": {
|
|
31
31
|
"access": "public"
|
|
@@ -93,10 +93,7 @@ function parseCompilationErrorWithDocument(error, virtualCode, sourceMap, docume
|
|
|
93
93
|
|
|
94
94
|
if (mapping) {
|
|
95
95
|
start = document.positionAt(mapping.generatedOffsets[0]);
|
|
96
|
-
end = document.positionAt(
|
|
97
|
-
mapping.generatedOffsets[0] +
|
|
98
|
-
(mapping.generatedLengths || mapping.data.customData.generatedLengths)[0],
|
|
99
|
-
);
|
|
96
|
+
end = document.positionAt(mapping.generatedOffsets[0] + mapping.generatedLengths[0]);
|
|
100
97
|
} else if (sourceMap) {
|
|
101
98
|
// try to find the match even across multiple mappings
|
|
102
99
|
const result = sourceMap.toGeneratedRange(start_offset, end_offset, true).next().value;
|
package/src/completionPlugin.js
CHANGED
|
@@ -11,84 +11,68 @@ const { log } = createLogging('[Ripple Completion Plugin]');
|
|
|
11
11
|
*/
|
|
12
12
|
const TRACKED_COLLECTION_SNIPPETS = [
|
|
13
13
|
{
|
|
14
|
-
label: '
|
|
15
|
-
filterText: '
|
|
16
|
-
detail: 'Create a
|
|
14
|
+
label: 'RippleMap',
|
|
15
|
+
filterText: 'RippleMap',
|
|
16
|
+
detail: 'Create a RippleMap',
|
|
17
17
|
documentation: 'A reactive Map that triggers updates when modified',
|
|
18
|
-
insertText: 'new
|
|
19
|
-
importName:
|
|
18
|
+
insertText: 'new RippleMap(${1})',
|
|
19
|
+
importName: 'RippleMap',
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
|
-
label: '
|
|
23
|
-
filterText: '
|
|
24
|
-
detail: 'Create a
|
|
22
|
+
label: 'RippleSet',
|
|
23
|
+
filterText: 'RippleSet',
|
|
24
|
+
detail: 'Create a RippleSet',
|
|
25
25
|
documentation: 'A reactive Set that triggers updates when modified',
|
|
26
|
-
insertText: 'new
|
|
27
|
-
importName:
|
|
26
|
+
insertText: 'new RippleSet(${1})',
|
|
27
|
+
importName: 'RippleSet',
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
|
-
label: '
|
|
31
|
-
filterText: '
|
|
32
|
-
detail: 'Create a
|
|
33
|
-
documentation: 'A reactive Map that triggers updates when modified',
|
|
34
|
-
insertText: 'new TrackedMap(${1})',
|
|
35
|
-
importName: 'TrackedMap',
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
label: 'TrackedSet',
|
|
39
|
-
filterText: 'TrackedSet',
|
|
40
|
-
detail: 'Create a TrackedSet',
|
|
41
|
-
documentation: 'A reactive Set that triggers updates when modified',
|
|
42
|
-
insertText: 'new TrackedSet(${1})',
|
|
43
|
-
importName: 'TrackedSet',
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
label: 'TrackedArray',
|
|
47
|
-
filterText: 'TrackedArray',
|
|
48
|
-
detail: 'Create a TrackedArray',
|
|
30
|
+
label: 'RippleArray',
|
|
31
|
+
filterText: 'RippleArray',
|
|
32
|
+
detail: 'Create a RippleArray',
|
|
49
33
|
documentation: 'A reactive Array that triggers updates when modified',
|
|
50
|
-
insertText: 'new
|
|
51
|
-
importName: '
|
|
34
|
+
insertText: 'new RippleArray(${1})',
|
|
35
|
+
importName: 'RippleArray',
|
|
52
36
|
},
|
|
53
37
|
{
|
|
54
|
-
label: '
|
|
55
|
-
filterText: '
|
|
56
|
-
detail: 'Create a
|
|
38
|
+
label: 'RippleArray.from',
|
|
39
|
+
filterText: 'RippleArray.from',
|
|
40
|
+
detail: 'Create a RippleArray.from',
|
|
57
41
|
documentation: 'A reactive Array that triggers when modified',
|
|
58
|
-
insertText: 'new
|
|
59
|
-
importName: '
|
|
42
|
+
insertText: 'new RippleArray.from(${1})',
|
|
43
|
+
importName: 'RippleArray',
|
|
60
44
|
},
|
|
61
45
|
{
|
|
62
|
-
label: '
|
|
63
|
-
filterText: '
|
|
64
|
-
detail: 'Create a
|
|
46
|
+
label: 'RippleObject',
|
|
47
|
+
filterText: 'RippleObject',
|
|
48
|
+
detail: 'Create a RippleObject',
|
|
65
49
|
documentation: 'A reactive Object that triggers updates when modified',
|
|
66
|
-
insertText: 'new
|
|
67
|
-
importName: '
|
|
50
|
+
insertText: 'new RippleObject(${1})',
|
|
51
|
+
importName: 'RippleObject',
|
|
68
52
|
},
|
|
69
53
|
{
|
|
70
|
-
label: '
|
|
71
|
-
filterText: '
|
|
72
|
-
detail: 'Create a
|
|
54
|
+
label: 'RippleDate',
|
|
55
|
+
filterText: 'RippleDate',
|
|
56
|
+
detail: 'Create a RippleDate',
|
|
73
57
|
documentation: 'A reactive Date that triggers updates when modified',
|
|
74
|
-
insertText: 'new
|
|
75
|
-
importName: '
|
|
58
|
+
insertText: 'new RippleDate(${1})',
|
|
59
|
+
importName: 'RippleDate',
|
|
76
60
|
},
|
|
77
61
|
{
|
|
78
|
-
label: '
|
|
79
|
-
filterText: '
|
|
80
|
-
detail: 'Create a
|
|
62
|
+
label: 'RippleURL',
|
|
63
|
+
filterText: 'RippleURL',
|
|
64
|
+
detail: 'Create a RippleURL',
|
|
81
65
|
documentation: 'A reactive URL that triggers updates when modified',
|
|
82
|
-
insertText: 'new
|
|
83
|
-
importName: '
|
|
66
|
+
insertText: 'new RippleURL(${1})',
|
|
67
|
+
importName: 'RippleURL',
|
|
84
68
|
},
|
|
85
69
|
{
|
|
86
|
-
label: '
|
|
87
|
-
filterText: '
|
|
88
|
-
detail: 'Create a
|
|
70
|
+
label: 'RippleURLSearchParams',
|
|
71
|
+
filterText: 'RippleURLSearchParams',
|
|
72
|
+
detail: 'Create a RippleURLSearchParams',
|
|
89
73
|
documentation: 'A reactive URLSearchParams that triggers updates when modified',
|
|
90
|
-
insertText: 'new
|
|
91
|
-
importName: '
|
|
74
|
+
insertText: 'new RippleURLSearchParams(${1})',
|
|
75
|
+
importName: 'RippleURLSearchParams',
|
|
92
76
|
},
|
|
93
77
|
{
|
|
94
78
|
label: 'MediaQuery',
|
|
@@ -132,7 +116,7 @@ function findRippleImport(text) {
|
|
|
132
116
|
/**
|
|
133
117
|
* Generate additionalTextEdits to add an import
|
|
134
118
|
* @param {string} documentText - Full document text
|
|
135
|
-
* @param {string} importName - Name to import (e.g., '
|
|
119
|
+
* @param {string} importName - Name to import (e.g., 'RippleMap')
|
|
136
120
|
* @returns {TextEdit[]}
|
|
137
121
|
*/
|
|
138
122
|
function generateImportEdit(documentText, importName) {
|
|
@@ -187,20 +171,30 @@ function generateImportEdit(documentText, importName) {
|
|
|
187
171
|
*/
|
|
188
172
|
const RIPPLE_SNIPPETS = [
|
|
189
173
|
{
|
|
190
|
-
label: '#
|
|
174
|
+
label: '#ripple.',
|
|
191
175
|
kind: CompletionItemKind.Snippet,
|
|
192
|
-
detail: 'Ripple
|
|
176
|
+
detail: 'Ripple namespace APIs',
|
|
177
|
+
documentation:
|
|
178
|
+
'Type #ripple. to access all built-in Ripple namespace APIs (track, map, set, style, server, etc.).',
|
|
179
|
+
insertText: '#ripple.',
|
|
180
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
181
|
+
sortText: '0-#-namespace',
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
label: '#ripple[]',
|
|
185
|
+
kind: CompletionItemKind.Snippet,
|
|
186
|
+
detail: 'Ripple Reactive Array Literal, shorthand for new RippleArray',
|
|
193
187
|
documentation: 'Create a new Ripple Array Literal',
|
|
194
|
-
insertText: '#[${1}]',
|
|
188
|
+
insertText: '#ripple[${1}]',
|
|
195
189
|
insertTextFormat: InsertTextFormat.Snippet,
|
|
196
190
|
sortText: '0-#-array-literal',
|
|
197
191
|
},
|
|
198
192
|
{
|
|
199
|
-
label: '#{}',
|
|
193
|
+
label: '#ripple{}',
|
|
200
194
|
kind: CompletionItemKind.Snippet,
|
|
201
|
-
detail: 'Ripple Reactive Object Literal, shorthand for new
|
|
195
|
+
detail: 'Ripple Reactive Object Literal, shorthand for new RippleObject',
|
|
202
196
|
documentation: 'Create a new Ripple Object Literal',
|
|
203
|
-
insertText: '#{${1}}',
|
|
197
|
+
insertText: '#ripple{${1}}',
|
|
204
198
|
insertTextFormat: InsertTextFormat.Snippet,
|
|
205
199
|
sortText: '0-#-object-literal',
|
|
206
200
|
},
|
|
@@ -337,6 +331,174 @@ const RIPPLE_SNIPPETS = [
|
|
|
337
331
|
},
|
|
338
332
|
];
|
|
339
333
|
|
|
334
|
+
/**
|
|
335
|
+
* All #ripple.* namespace completions — no imports required.
|
|
336
|
+
* Shown when the cursor follows `#ripple` or `#ripple.`
|
|
337
|
+
* @type {CompletionItem[]}
|
|
338
|
+
*/
|
|
339
|
+
const RIPPLE_NAMESPACE_SNIPPETS = [
|
|
340
|
+
{
|
|
341
|
+
label: '#ripple.track',
|
|
342
|
+
kind: CompletionItemKind.Snippet,
|
|
343
|
+
detail: 'Reactive tracked value (no import needed)',
|
|
344
|
+
documentation:
|
|
345
|
+
'Creates a reactive tracked value. Equivalent to track() but requires no import.\n\nUsage: let count = #ripple.track(0);\nDerived: let double = #ripple.track(() => @count * 2);',
|
|
346
|
+
insertText: '#ripple.track(${1:initialValue})',
|
|
347
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
348
|
+
sortText: '0-ripple-track',
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
label: '#ripple.trackSplit',
|
|
352
|
+
kind: CompletionItemKind.Snippet,
|
|
353
|
+
detail: 'Reactive prop splitting (no import needed)',
|
|
354
|
+
documentation:
|
|
355
|
+
"Destructures props while preserving reactivity. Equivalent to trackSplit() but requires no import.\n\nUsage: const [children, rest] = #ripple.trackSplit(props, ['children']);",
|
|
356
|
+
insertText: "#ripple.trackSplit(${1:props}, ['${2:children}'])",
|
|
357
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
358
|
+
sortText: '0-ripple-trackSplit',
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
label: '#ripple.effect',
|
|
362
|
+
kind: CompletionItemKind.Snippet,
|
|
363
|
+
detail: 'Reactive effect (no import needed)',
|
|
364
|
+
documentation:
|
|
365
|
+
'Registers a reactive side effect. Equivalent to effect() but requires no import.\n\nUsage: #ripple.effect(() => {\n console.log(@count);\n});',
|
|
366
|
+
insertText: '#ripple.effect(() => {\n\t$0\n})',
|
|
367
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
368
|
+
sortText: '0-ripple-effect',
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
label: '#ripple.untrack',
|
|
372
|
+
kind: CompletionItemKind.Snippet,
|
|
373
|
+
detail: 'Read without dependency tracking (no import needed)',
|
|
374
|
+
documentation:
|
|
375
|
+
'Reads reactive values without creating dependencies. Equivalent to untrack() but requires no import.\n\nUsage: const snapshot = #ripple.untrack(() => @count);',
|
|
376
|
+
insertText: '#ripple.untrack(() => ${1:@value})',
|
|
377
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
378
|
+
sortText: '0-ripple-untrack',
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
label: '#ripple.validate',
|
|
382
|
+
kind: CompletionItemKind.Snippet,
|
|
383
|
+
detail: 'Validation wrapper (no import needed)',
|
|
384
|
+
documentation:
|
|
385
|
+
"Wraps a value with validation logic.\n\nUsage: let email = #ripple.validate(#ripple.track(''));",
|
|
386
|
+
insertText: '#ripple.validate(${1})',
|
|
387
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
388
|
+
sortText: '0-ripple-validate',
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
label: '#ripple.context',
|
|
392
|
+
kind: CompletionItemKind.Snippet,
|
|
393
|
+
detail: 'Create a Context (no import needed)',
|
|
394
|
+
documentation:
|
|
395
|
+
"Creates a reactive context. Equivalent to new Context() but requires no import.\n\nUsage: const ThemeCtx = #ripple.context('light');",
|
|
396
|
+
insertText: '#ripple.context(${1:defaultValue})',
|
|
397
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
398
|
+
sortText: '0-ripple-context',
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
label: '#ripple.array',
|
|
402
|
+
kind: CompletionItemKind.Snippet,
|
|
403
|
+
detail: 'RippleArray class reference (no import needed)',
|
|
404
|
+
documentation:
|
|
405
|
+
'Reference to the RippleArray class. Use for static methods or as a type annotation.\n\nUsage: const copy = #ripple.array.from(existing);',
|
|
406
|
+
insertText: '#ripple.array',
|
|
407
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
408
|
+
sortText: '0-ripple-array',
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
label: '#ripple.object',
|
|
412
|
+
kind: CompletionItemKind.Snippet,
|
|
413
|
+
detail: 'RippleObject class reference (no import needed)',
|
|
414
|
+
documentation:
|
|
415
|
+
'Reference to the RippleObject class. Use for static methods or as a type annotation.\n\nUsage: const obj = #ripple.object({ a: 1 });',
|
|
416
|
+
insertText: '#ripple.object',
|
|
417
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
418
|
+
sortText: '0-ripple-object',
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
label: '#ripple.map',
|
|
422
|
+
kind: CompletionItemKind.Snippet,
|
|
423
|
+
detail: 'RippleMap (no import needed)',
|
|
424
|
+
documentation:
|
|
425
|
+
'Creates a reactive Map. Equivalent to new RippleMap() but requires no import.\n\nUsage: const map = #ripple.map([[key, value]]);',
|
|
426
|
+
insertText: '#ripple.map(${1})',
|
|
427
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
428
|
+
sortText: '0-ripple-map',
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
label: '#ripple.set',
|
|
432
|
+
kind: CompletionItemKind.Snippet,
|
|
433
|
+
detail: 'RippleSet (no import needed)',
|
|
434
|
+
documentation:
|
|
435
|
+
'Creates a reactive Set. Equivalent to new RippleSet() but requires no import.\n\nUsage: const set = #ripple.set([1, 2, 3]);',
|
|
436
|
+
insertText: '#ripple.set(${1})',
|
|
437
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
438
|
+
sortText: '0-ripple-set',
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
label: '#ripple.date',
|
|
442
|
+
kind: CompletionItemKind.Snippet,
|
|
443
|
+
detail: 'RippleDate (no import needed)',
|
|
444
|
+
documentation:
|
|
445
|
+
'Creates a reactive Date. Equivalent to new RippleDate() but requires no import.\n\nUsage: const today = #ripple.date();',
|
|
446
|
+
insertText: '#ripple.date(${1})',
|
|
447
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
448
|
+
sortText: '0-ripple-date',
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
label: '#ripple.url',
|
|
452
|
+
kind: CompletionItemKind.Snippet,
|
|
453
|
+
detail: 'RippleURL (no import needed)',
|
|
454
|
+
documentation:
|
|
455
|
+
"Creates a reactive URL. Equivalent to new RippleURL() but requires no import.\n\nUsage: const url = #ripple.url('https://example.com');",
|
|
456
|
+
insertText: "#ripple.url('${1:https://example.com}')",
|
|
457
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
458
|
+
sortText: '0-ripple-url',
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
label: '#ripple.urlSearchParams',
|
|
462
|
+
kind: CompletionItemKind.Snippet,
|
|
463
|
+
detail: 'RippleURLSearchParams (no import needed)',
|
|
464
|
+
documentation:
|
|
465
|
+
'Creates a reactive URLSearchParams. Equivalent to new RippleURLSearchParams() but requires no import.\n\nUsage: const params = #ripple.urlSearchParams(${1});',
|
|
466
|
+
insertText: '#ripple.urlSearchParams(${1})',
|
|
467
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
468
|
+
sortText: '0-ripple-urlSearchParams',
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
label: '#ripple.mediaQuery',
|
|
472
|
+
kind: CompletionItemKind.Snippet,
|
|
473
|
+
detail: 'Reactive CSS media query (no import needed)',
|
|
474
|
+
documentation:
|
|
475
|
+
"Creates a reactive media query that tracks whether the query currently matches.\n\nUsage: const isMobile = #ripple.mediaQuery('(max-width: 768px)');",
|
|
476
|
+
insertText: "#ripple.mediaQuery('${1:(max-width: 768px)}')",
|
|
477
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
478
|
+
sortText: '0-ripple-mediaQuery',
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
label: '#ripple.style',
|
|
482
|
+
kind: CompletionItemKind.Snippet,
|
|
483
|
+
detail: 'Scoped CSS class reference (no import needed)',
|
|
484
|
+
documentation:
|
|
485
|
+
'Produces a scoped CSS class string for passing to child components.\nThe class must be defined as a standalone selector in <style>.\n\nUsage: <Child cls={#ripple.style.highlight} />',
|
|
486
|
+
insertText: '#ripple.style.${1:className}',
|
|
487
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
488
|
+
sortText: '0-ripple-style',
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
label: '#ripple.server',
|
|
492
|
+
kind: CompletionItemKind.Snippet,
|
|
493
|
+
detail: 'Server-only code block (module level)',
|
|
494
|
+
documentation:
|
|
495
|
+
'Marks a block as server-only. Code inside is tree-shaken on the client.\nMust be at module top level.\n\nUsage:\n#ripple.server {\n export async function loadData() { ... }\n}',
|
|
496
|
+
insertText: '#ripple.server {\n\t$0\n}',
|
|
497
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
498
|
+
sortText: '0-ripple-server',
|
|
499
|
+
},
|
|
500
|
+
];
|
|
501
|
+
|
|
340
502
|
/**
|
|
341
503
|
* Import suggestions for Ripple
|
|
342
504
|
*/
|
|
@@ -383,6 +545,38 @@ const RIPPLE_IMPORTS = [
|
|
|
383
545
|
// },
|
|
384
546
|
];
|
|
385
547
|
|
|
548
|
+
/**
|
|
549
|
+
* @param {string} line
|
|
550
|
+
* @returns {RegExpMatchArray | null}
|
|
551
|
+
*/
|
|
552
|
+
function get_ripple_namespace_match(line) {
|
|
553
|
+
return line.match(/#ripple(?:\.(\w*))?$/);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* @param {RegExpMatchArray} namespace_match
|
|
558
|
+
* @param {{ line: number, character: number }} position
|
|
559
|
+
* @returns {CompletionItem[]}
|
|
560
|
+
*/
|
|
561
|
+
function create_ripple_namespace_completion_items(namespace_match, position) {
|
|
562
|
+
const start_character = position.character - namespace_match[0].length;
|
|
563
|
+
|
|
564
|
+
return RIPPLE_NAMESPACE_SNIPPETS.map((snippet) => ({
|
|
565
|
+
...snippet,
|
|
566
|
+
filterText: snippet.label,
|
|
567
|
+
textEdit: {
|
|
568
|
+
range: {
|
|
569
|
+
start: {
|
|
570
|
+
line: position.line,
|
|
571
|
+
character: start_character,
|
|
572
|
+
},
|
|
573
|
+
end: position,
|
|
574
|
+
},
|
|
575
|
+
newText: snippet.insertText ?? snippet.label,
|
|
576
|
+
},
|
|
577
|
+
}));
|
|
578
|
+
}
|
|
579
|
+
|
|
386
580
|
/**
|
|
387
581
|
* @returns {LanguageServicePlugin}
|
|
388
582
|
*/
|
|
@@ -393,9 +587,9 @@ function createCompletionPlugin() {
|
|
|
393
587
|
completionProvider: {
|
|
394
588
|
// Trigger on Ripple-specific syntax:
|
|
395
589
|
// '<' - JSX/HTML tags
|
|
396
|
-
// '#' -
|
|
397
|
-
//
|
|
398
|
-
triggerCharacters: ['<', '#'],
|
|
590
|
+
// '#' - RippleMap/RippleSet shortcuts
|
|
591
|
+
// '.' - #ripple namespace member access
|
|
592
|
+
triggerCharacters: ['<', '#', '.'],
|
|
399
593
|
resolveProvider: false,
|
|
400
594
|
},
|
|
401
595
|
},
|
|
@@ -412,7 +606,7 @@ function createCompletionPlugin() {
|
|
|
412
606
|
|
|
413
607
|
const { virtualCode } = getVirtualCode(document, context);
|
|
414
608
|
|
|
415
|
-
if (virtualCode.languageId !== 'ripple') {
|
|
609
|
+
if (virtualCode && virtualCode.languageId !== 'ripple') {
|
|
416
610
|
// Check if we're inside an embedded code (like CSS in <style> blocks)
|
|
417
611
|
// If so, don't provide Ripple snippets - let CSS completions take priority
|
|
418
612
|
log(`Skipping Ripple completions in the '${virtualCode.languageId}' context`);
|
|
@@ -454,6 +648,15 @@ function createCompletionPlugin() {
|
|
|
454
648
|
return { items, isIncomplete: false };
|
|
455
649
|
}
|
|
456
650
|
|
|
651
|
+
const ripple_namespace_match = get_ripple_namespace_match(line);
|
|
652
|
+
|
|
653
|
+
if (ripple_namespace_match) {
|
|
654
|
+
items.push(
|
|
655
|
+
...create_ripple_namespace_completion_items(ripple_namespace_match, position),
|
|
656
|
+
);
|
|
657
|
+
return { items, isIncomplete: false };
|
|
658
|
+
}
|
|
659
|
+
|
|
457
660
|
// @ accessor hint when typing after @
|
|
458
661
|
if (/@\w*$/.test(line)) {
|
|
459
662
|
items.push({
|
|
@@ -464,7 +667,7 @@ function createCompletionPlugin() {
|
|
|
464
667
|
});
|
|
465
668
|
}
|
|
466
669
|
|
|
467
|
-
//
|
|
670
|
+
// RippleMap/RippleSet completions when typing T...
|
|
468
671
|
// Also detects if 'new' is already typed before it to avoid duplicating
|
|
469
672
|
const trackedMatch = line.match(/(new\s+)?[T,M,#]([\w\.]*)$/);
|
|
470
673
|
|
package/src/definitionPlugin.js
CHANGED
|
@@ -105,8 +105,7 @@ function createDefinitionPlugin() {
|
|
|
105
105
|
|
|
106
106
|
// Create the origin selection range for #Map/#Set
|
|
107
107
|
const generatedStart = customMapping.generatedOffsets[0];
|
|
108
|
-
const generatedEnd =
|
|
109
|
-
generatedStart + customMapping.data.customData.generatedLengths[0];
|
|
108
|
+
const generatedEnd = generatedStart + customMapping.generatedLengths[0];
|
|
110
109
|
const originStart = document.positionAt(generatedStart);
|
|
111
110
|
const originEnd = document.positionAt(generatedEnd);
|
|
112
111
|
|
|
@@ -175,8 +174,7 @@ function createDefinitionPlugin() {
|
|
|
175
174
|
// The origin selection range should be in the virtual document
|
|
176
175
|
// not in the source document!
|
|
177
176
|
const generatedStart = customMapping.generatedOffsets[0];
|
|
178
|
-
const generatedEnd =
|
|
179
|
-
generatedStart + customMapping.data.customData.generatedLengths[0];
|
|
177
|
+
const generatedEnd = generatedStart + customMapping.generatedLengths[0];
|
|
180
178
|
const originStart = document.positionAt(generatedStart);
|
|
181
179
|
const originEnd = document.positionAt(generatedEnd);
|
|
182
180
|
|
package/src/utils.js
CHANGED
|
@@ -5,9 +5,13 @@
|
|
|
5
5
|
/** @import {is_identifier_obfuscated, deobfuscate_identifier, IDENTIFIER_OBFUSCATION_PREFIX} from 'ripple/compiler/internal/identifier/utils' */
|
|
6
6
|
|
|
7
7
|
const { URI } = require('vscode-uri');
|
|
8
|
-
const {
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
const {
|
|
9
|
+
createLogging,
|
|
10
|
+
getWordFromPosition,
|
|
11
|
+
charAllowedWordRegex,
|
|
12
|
+
DEBUG,
|
|
13
|
+
} = require('@ripple-ts/typescript-plugin/src/utils.js');
|
|
14
|
+
|
|
11
15
|
const IMPORT_EXPORT_REGEX = {
|
|
12
16
|
import: {
|
|
13
17
|
findBefore: /import\s+(?:\{[^}]*|\*\s+as\s+\w*|\w*)$/s,
|
|
@@ -93,31 +97,6 @@ function getVirtualCode(document, context) {
|
|
|
93
97
|
return { virtualCode, sourceUri, sourceScript, sourceMap };
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
/**
|
|
97
|
-
* Get the word at a specific position in the text
|
|
98
|
-
* @param {string} text
|
|
99
|
-
* @param {number} start
|
|
100
|
-
* @returns {{word: string, start: number, end: number}}
|
|
101
|
-
*/
|
|
102
|
-
function getWordFromPosition(text, start) {
|
|
103
|
-
let wordStart = start;
|
|
104
|
-
let wordEnd = start;
|
|
105
|
-
while (wordStart > 0 && charAllowedWordRegex.test(text[wordStart - 1])) {
|
|
106
|
-
wordStart--;
|
|
107
|
-
}
|
|
108
|
-
while (wordEnd < text.length && charAllowedWordRegex.test(text[wordEnd])) {
|
|
109
|
-
wordEnd++;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const word = text.substring(wordStart, wordEnd);
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
word,
|
|
116
|
-
start: wordStart,
|
|
117
|
-
end: wordEnd,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
100
|
/**
|
|
122
101
|
* @param {'import' | 'export'} type
|
|
123
102
|
* @param {string} text
|