@ripple-ts/language-server 0.2.178 → 0.2.180
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/package.json +2 -2
- package/src/autoInsertPlugin.js +3 -24
- package/src/compileErrorDiagnosticPlugin.js +11 -71
- package/src/completionPlugin.js +17 -46
- package/src/definitionPlugin.js +4 -26
- package/src/documentHighlightPlugin.js +121 -0
- package/src/hoverPlugin.js +5 -35
- package/src/server.js +9 -22
- package/src/typescriptDiagnosticPlugin.js +3 -71
- package/src/typescriptService.js +2 -2
- package/src/utils.js +29 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ripple-ts/language-server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.180",
|
|
4
4
|
"description": "Language Server Protocol implementation for Ripple",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"volar-service-typescript": "0.0.65",
|
|
20
20
|
"vscode-languageserver-textdocument": "^1.0.12",
|
|
21
21
|
"vscode-uri": "^3.1.0",
|
|
22
|
-
"@ripple-ts/typescript-plugin": "0.2.
|
|
22
|
+
"@ripple-ts/typescript-plugin": "0.2.180"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"typescript": "^5.9.2"
|
package/src/autoInsertPlugin.js
CHANGED
|
@@ -1,18 +1,8 @@
|
|
|
1
1
|
/** @import { LanguageServicePlugin } from '@volar/language-server' */
|
|
2
|
-
/** @import { RippleVirtualCode } from '@ripple-ts/typescript-plugin/src/language.js') */
|
|
3
2
|
|
|
4
|
-
const {
|
|
3
|
+
const { getVirtualCode, createLogging } = require('./utils.js');
|
|
5
4
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @param {...unknown} args
|
|
10
|
-
*/
|
|
11
|
-
function log(...args) {
|
|
12
|
-
if (DEBUG) {
|
|
13
|
-
console.log('[Ripple Auto-Insert]', ...args);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
5
|
+
const { log } = createLogging('[Ripple Auto-Insert Plugin]');
|
|
16
6
|
|
|
17
7
|
/**
|
|
18
8
|
* List of HTML void/self-closing elements that don't need closing tags
|
|
@@ -74,18 +64,7 @@ function createAutoInsertPlugin() {
|
|
|
74
64
|
return null;
|
|
75
65
|
}
|
|
76
66
|
|
|
77
|
-
const
|
|
78
|
-
const decoded = context.decodeEmbeddedDocumentUri(uri);
|
|
79
|
-
|
|
80
|
-
if (!decoded) {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const [sourceUri, virtualCodeId] = decoded;
|
|
85
|
-
const sourceScript = context.language.scripts.get(sourceUri);
|
|
86
|
-
const virtualCode = /** @type {RippleVirtualCode } */ (
|
|
87
|
-
sourceScript?.generated?.embeddedCodes.get(virtualCodeId)
|
|
88
|
-
);
|
|
67
|
+
const virtualCode = getVirtualCode(document, context);
|
|
89
68
|
|
|
90
69
|
// Map position back to source
|
|
91
70
|
const offset = document.offsetAt(position);
|
|
@@ -1,32 +1,12 @@
|
|
|
1
|
-
const { RippleVirtualCode } = require('@ripple-ts/typescript-plugin/src/language.js');
|
|
2
|
-
const { URI } = require('vscode-uri');
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
|
-
* @
|
|
6
|
-
* @
|
|
7
|
-
* @typedef {import('@volar/language-server').Diagnostic} Diagnostic
|
|
8
|
-
* @typedef {import('@volar/language-server').DiagnosticSeverity} DiagnosticSeverity
|
|
9
|
-
* @typedef {import('@volar/language-server').Position} Position
|
|
10
|
-
* @typedef {import('vscode-languageserver-textdocument').TextDocument} TextDocument
|
|
2
|
+
* @import {Diagnostic, LanguageServicePlugin, LanguageServiceContext} from '@volar/language-server'
|
|
3
|
+
* @import {TextDocument} from 'vscode-languageserver-textdocument'
|
|
11
4
|
*/
|
|
12
5
|
|
|
13
|
-
const
|
|
6
|
+
const { getVirtualCode, createLogging } = require('./utils.js');
|
|
14
7
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*/
|
|
18
|
-
function log(...args) {
|
|
19
|
-
if (DEBUG) {
|
|
20
|
-
console.log('[Ripple Language]', ...args);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @param {...unknown} args
|
|
26
|
-
*/
|
|
27
|
-
function logError(...args) {
|
|
28
|
-
console.error('[Ripple Language ERROR]', ...args);
|
|
29
|
-
}
|
|
8
|
+
const { log, logError } = createLogging('[Ripple Compile Error Diagnostic Plugin]');
|
|
9
|
+
const { DiagnosticSeverity } = require('@volar/language-server');
|
|
30
10
|
|
|
31
11
|
/**
|
|
32
12
|
* @returns {LanguageServicePlugin}
|
|
@@ -44,22 +24,16 @@ function createCompileErrorDiagnosticPlugin() {
|
|
|
44
24
|
},
|
|
45
25
|
create(/** @type {LanguageServiceContext} */ context) {
|
|
46
26
|
return {
|
|
47
|
-
/**
|
|
48
|
-
* @param {TextDocument} document
|
|
49
|
-
* @param {import('@volar/language-server').CancellationToken} _token
|
|
50
|
-
* @returns {import('@volar/language-server').NullableProviderResult<Diagnostic[]>}
|
|
51
|
-
*/
|
|
52
27
|
provideDiagnostics(document, _token) {
|
|
53
28
|
try {
|
|
54
29
|
log('Providing Ripple diagnostics for:', document.uri);
|
|
55
30
|
|
|
56
|
-
const
|
|
31
|
+
const virtualCode = getVirtualCode(document, context);
|
|
57
32
|
|
|
58
|
-
if (!
|
|
33
|
+
if (!virtualCode || !virtualCode.errors || virtualCode.errors.length === 0) {
|
|
59
34
|
return [];
|
|
60
35
|
}
|
|
61
36
|
|
|
62
|
-
const virtualCode = info.virtualCode;
|
|
63
37
|
const diagnostics = [];
|
|
64
38
|
|
|
65
39
|
log('Processing', virtualCode.errors.length, 'errors');
|
|
@@ -127,7 +101,7 @@ function parseCompilationErrorWithDocument(error, fallbackFileName, sourceText,
|
|
|
127
101
|
const zeroBasedEndColumn = Math.max(0, endColumn);
|
|
128
102
|
|
|
129
103
|
return {
|
|
130
|
-
severity:
|
|
104
|
+
severity: DiagnosticSeverity.Error,
|
|
131
105
|
range: {
|
|
132
106
|
start: { line: zeroBasedStartLine, character: zeroBasedStartColumn },
|
|
133
107
|
end: { line: zeroBasedEndLine, character: zeroBasedEndColumn },
|
|
@@ -160,7 +134,7 @@ function parseCompilationErrorWithDocument(error, fallbackFileName, sourceText,
|
|
|
160
134
|
let length = Math.min(1, sourceText.split('\n')[zeroBasedLine]?.length - actualColumn || 1);
|
|
161
135
|
|
|
162
136
|
return {
|
|
163
|
-
severity:
|
|
137
|
+
severity: DiagnosticSeverity.Error,
|
|
164
138
|
range: {
|
|
165
139
|
start: { line: zeroBasedLine, character: actualColumn },
|
|
166
140
|
end: { line: zeroBasedLine, character: actualColumn + length },
|
|
@@ -175,7 +149,7 @@ function parseCompilationErrorWithDocument(error, fallbackFileName, sourceText,
|
|
|
175
149
|
const endPosition = document.positionAt(Math.min(1, sourceText.length));
|
|
176
150
|
|
|
177
151
|
return {
|
|
178
|
-
severity:
|
|
152
|
+
severity: DiagnosticSeverity.Error,
|
|
179
153
|
range: {
|
|
180
154
|
start: startPosition,
|
|
181
155
|
end: endPosition,
|
|
@@ -189,7 +163,7 @@ function parseCompilationErrorWithDocument(error, fallbackFileName, sourceText,
|
|
|
189
163
|
logError('Error parsing compilation error:', parseError);
|
|
190
164
|
|
|
191
165
|
return {
|
|
192
|
-
severity:
|
|
166
|
+
severity: DiagnosticSeverity.Error,
|
|
193
167
|
range: {
|
|
194
168
|
start: { line: 0, character: 0 },
|
|
195
169
|
end: { line: 0, character: 1 },
|
|
@@ -201,40 +175,6 @@ function parseCompilationErrorWithDocument(error, fallbackFileName, sourceText,
|
|
|
201
175
|
}
|
|
202
176
|
}
|
|
203
177
|
|
|
204
|
-
/**
|
|
205
|
-
* @param {LanguageServiceContext} context
|
|
206
|
-
* @param {TextDocument} document
|
|
207
|
-
*/
|
|
208
|
-
function getEmbeddedInfo(context, document) {
|
|
209
|
-
try {
|
|
210
|
-
const uri = URI.parse(document.uri);
|
|
211
|
-
const decoded = context.decodeEmbeddedDocumentUri(uri);
|
|
212
|
-
if (!decoded) {
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const [documentUri, embeddedCodeId] = decoded;
|
|
217
|
-
|
|
218
|
-
const sourceScript = context.language.scripts.get(documentUri);
|
|
219
|
-
if (!sourceScript?.generated) {
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const virtualCode = sourceScript.generated.embeddedCodes.get(embeddedCodeId);
|
|
224
|
-
if (!(virtualCode instanceof RippleVirtualCode)) {
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return {
|
|
229
|
-
sourceScript: sourceScript,
|
|
230
|
-
virtualCode,
|
|
231
|
-
};
|
|
232
|
-
} catch (err) {
|
|
233
|
-
logError('Failed to get embedded info:', err);
|
|
234
|
-
return null;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
178
|
module.exports = {
|
|
239
179
|
createCompileErrorDiagnosticPlugin,
|
|
240
180
|
};
|
package/src/completionPlugin.js
CHANGED
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
2
|
+
* @import {LanguageServicePlugin} from '@volar/language-server'
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const { CompletionItemKind, InsertTextFormat } = require('@volar/language-server');
|
|
6
|
-
const {
|
|
6
|
+
const { getVirtualCode, createLogging } = require('./utils.js');
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @param {...unknown} args
|
|
12
|
-
*/
|
|
13
|
-
function log(...args) {
|
|
14
|
-
if (DEBUG) {
|
|
15
|
-
console.log('[Ripple Completion]', ...args);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
8
|
+
const { log } = createLogging('[Ripple Completion Plugin]');
|
|
18
9
|
|
|
19
10
|
/**
|
|
20
11
|
* Ripple-specific completion enhancements
|
|
@@ -143,6 +134,15 @@ const RIPPLE_SNIPPETS = [
|
|
|
143
134
|
insertTextFormat: InsertTextFormat.Snippet,
|
|
144
135
|
sortText: '0-untrack',
|
|
145
136
|
},
|
|
137
|
+
{
|
|
138
|
+
label: 'try-pending',
|
|
139
|
+
kind: CompletionItemKind.Snippet,
|
|
140
|
+
detail: 'try...pending block',
|
|
141
|
+
documentation: 'Handle async content with loading fallback',
|
|
142
|
+
insertText: "try {\n\t$1\n} pending {\n\t<div>{'Loading...'}</div>\n}",
|
|
143
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
144
|
+
sortText: '0-try-pending',
|
|
145
|
+
},
|
|
146
146
|
];
|
|
147
147
|
|
|
148
148
|
/**
|
|
@@ -217,22 +217,13 @@ function createCompletionPlugin() {
|
|
|
217
217
|
return { items: [], isIncomplete: false };
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
const virtualCode = getVirtualCode(document, context);
|
|
221
|
+
|
|
220
222
|
// Check if we're inside an embedded code (like CSS in <style> blocks)
|
|
221
223
|
// If so, don't provide Ripple snippets - let CSS completions take priority
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const [documentUri, embeddedCodeId] = decoded;
|
|
226
|
-
const sourceScript = context.language.scripts.get(documentUri);
|
|
227
|
-
|
|
228
|
-
if (sourceScript?.generated) {
|
|
229
|
-
const virtualCode = sourceScript.generated.embeddedCodes.get(embeddedCodeId);
|
|
230
|
-
// If we're in a CSS embedded code (from <style> blocks), skip Ripple completions
|
|
231
|
-
if (virtualCode && virtualCode.languageId === 'css') {
|
|
232
|
-
log('Skipping Ripple completions in CSS context');
|
|
233
|
-
return { items: [], isIncomplete: false };
|
|
234
|
-
}
|
|
235
|
-
}
|
|
224
|
+
if (virtualCode && virtualCode.languageId === 'css') {
|
|
225
|
+
log('Skipping Ripple completions in CSS context');
|
|
226
|
+
return { items: [], isIncomplete: false };
|
|
236
227
|
}
|
|
237
228
|
|
|
238
229
|
const line = document.getText({
|
|
@@ -296,26 +287,6 @@ function createCompletionPlugin() {
|
|
|
296
287
|
};
|
|
297
288
|
}
|
|
298
289
|
|
|
299
|
-
/**
|
|
300
|
-
* Heuristic to detect if we're inside a component template
|
|
301
|
-
* @param {string} text
|
|
302
|
-
* @returns {boolean}
|
|
303
|
-
*/
|
|
304
|
-
function isLikelyInTemplate(text) {
|
|
305
|
-
// Simple heuristic: inside component body after opening brace
|
|
306
|
-
const componentMatch = text.match(/component\s+\w+\([^)]*\)\s*\{/);
|
|
307
|
-
if (!componentMatch || componentMatch.index === undefined) return false;
|
|
308
|
-
|
|
309
|
-
// Count braces to see if we're inside
|
|
310
|
-
let braceCount = 0;
|
|
311
|
-
for (let i = componentMatch.index + componentMatch[0].length; i < text.length; i++) {
|
|
312
|
-
if (text[i] === '{') braceCount++;
|
|
313
|
-
if (text[i] === '}') braceCount--;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return braceCount > 0;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
290
|
module.exports = {
|
|
320
291
|
createCompletionPlugin,
|
|
321
292
|
};
|
package/src/definitionPlugin.js
CHANGED
|
@@ -1,18 +1,8 @@
|
|
|
1
1
|
/** @import { LanguageServicePlugin } from '@volar/language-server' */
|
|
2
|
-
/** @import { RippleVirtualCode } from '@ripple-ts/typescript-plugin/src/language.js') */
|
|
3
2
|
|
|
4
|
-
const {
|
|
3
|
+
const { getVirtualCode, createLogging } = require('./utils.js');
|
|
5
4
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @param {...unknown} args
|
|
10
|
-
*/
|
|
11
|
-
function log(...args) {
|
|
12
|
-
if (DEBUG) {
|
|
13
|
-
console.log('[Ripple Definition]', ...args);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
5
|
+
const { log } = createLogging('[Ripple Definition Plugin]');
|
|
16
6
|
|
|
17
7
|
/**
|
|
18
8
|
* @returns {LanguageServicePlugin}
|
|
@@ -26,9 +16,6 @@ function createDefinitionPlugin() {
|
|
|
26
16
|
create(context) {
|
|
27
17
|
return {
|
|
28
18
|
async provideDefinition(document, position, token) {
|
|
29
|
-
const uri = URI.parse(document.uri);
|
|
30
|
-
const decoded = context.decodeEmbeddedDocumentUri(uri);
|
|
31
|
-
|
|
32
19
|
// Get TypeScript definition from typescript-semantic service
|
|
33
20
|
let tsDefinitions = [];
|
|
34
21
|
for (const [plugin, instance] of context.plugins) {
|
|
@@ -47,17 +34,6 @@ function createDefinitionPlugin() {
|
|
|
47
34
|
return;
|
|
48
35
|
}
|
|
49
36
|
|
|
50
|
-
// If not in a Ripple embedded context, just return TypeScript results
|
|
51
|
-
if (!decoded) {
|
|
52
|
-
return tsDefinitions;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const [sourceUri, virtualCodeId] = decoded;
|
|
56
|
-
const sourceScript = context.language.scripts.get(sourceUri);
|
|
57
|
-
const virtualCode = /** @type {RippleVirtualCode } */ (
|
|
58
|
-
sourceScript?.generated?.embeddedCodes.get(virtualCodeId)
|
|
59
|
-
);
|
|
60
|
-
|
|
61
37
|
// Get the range from TypeScript's definition to find the exact token
|
|
62
38
|
// This gives us the precise start and end of the token (e.g., "function")
|
|
63
39
|
const firstDefinition = tsDefinitions[0];
|
|
@@ -69,6 +45,8 @@ function createDefinitionPlugin() {
|
|
|
69
45
|
const rangeStart = document.offsetAt(range.start);
|
|
70
46
|
const rangeEnd = document.offsetAt(range.end);
|
|
71
47
|
|
|
48
|
+
const virtualCode = getVirtualCode(document, context);
|
|
49
|
+
|
|
72
50
|
// Find the mapping using the exact token range for O(1) lookup
|
|
73
51
|
const mapping = virtualCode.findMappingByGeneratedRange(rangeStart, rangeEnd);
|
|
74
52
|
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/** @import { LanguageServicePlugin } from '@volar/language-server' */
|
|
2
|
+
/** @import { LanguageServicePluginInstance } from '@volar/language-server' */
|
|
3
|
+
|
|
4
|
+
const { getVirtualCode, getWordFromPosition, createLogging } = require('./utils.js');
|
|
5
|
+
const { log } = createLogging('[Ripple Document Highlight Plugin]');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Document Highlight plugin for Ripple
|
|
9
|
+
* Provides word highlighting (grey background) for custom Ripple keywords like 'pending'
|
|
10
|
+
* @returns {LanguageServicePlugin}
|
|
11
|
+
*/
|
|
12
|
+
function createDocumentHighlightPlugin() {
|
|
13
|
+
return {
|
|
14
|
+
name: 'ripple-document-highlight',
|
|
15
|
+
capabilities: {
|
|
16
|
+
documentHighlightProvider: true,
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
/** @type {LanguageServicePluginInstance['provideDocumentHighlights']} */
|
|
20
|
+
let originalProvideDocumentHighlights;
|
|
21
|
+
/** @type {LanguageServicePluginInstance} */
|
|
22
|
+
let originalInstance;
|
|
23
|
+
|
|
24
|
+
// Get TypeScript's document highlights provider
|
|
25
|
+
for (const [plugin, instance] of context.plugins) {
|
|
26
|
+
if (plugin.name === 'typescript-semantic' && instance.provideDocumentHighlights) {
|
|
27
|
+
originalInstance = instance;
|
|
28
|
+
originalProvideDocumentHighlights = instance.provideDocumentHighlights;
|
|
29
|
+
instance.provideDocumentHighlights = undefined;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!originalProvideDocumentHighlights) {
|
|
35
|
+
log(
|
|
36
|
+
"'typescript-semantic plugin' was not found or has no 'provideDocumentHighlights'. \
|
|
37
|
+
Document highlights will be limited to custom Ripple keywords only.",
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
async provideDocumentHighlights(document, position, token) {
|
|
43
|
+
if (!originalProvideDocumentHighlights) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let tsHighlights = await originalProvideDocumentHighlights.call(
|
|
48
|
+
originalInstance,
|
|
49
|
+
document,
|
|
50
|
+
position,
|
|
51
|
+
token,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (!tsHighlights || tsHighlights.length > 0) {
|
|
55
|
+
// If TypeScript recognized tokens and provided highlights, return them
|
|
56
|
+
return tsHighlights;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const virtualCode = getVirtualCode(document, context);
|
|
60
|
+
|
|
61
|
+
if (!virtualCode) {
|
|
62
|
+
return tsHighlights;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check if we're on a custom Ripple keyword
|
|
66
|
+
const offset = document.offsetAt(position);
|
|
67
|
+
const text = document.getText();
|
|
68
|
+
|
|
69
|
+
// Find word boundaries
|
|
70
|
+
const { word } = getWordFromPosition(text, offset);
|
|
71
|
+
|
|
72
|
+
// If the word is a Ripple keyword, find all occurrences in the document
|
|
73
|
+
|
|
74
|
+
const regex = new RegExp(`\\b${word}\\b`, 'g');
|
|
75
|
+
let match;
|
|
76
|
+
|
|
77
|
+
while ((match = regex.exec(text)) !== null) {
|
|
78
|
+
const start = match.index;
|
|
79
|
+
const end = match.index + word.length;
|
|
80
|
+
const mapping = virtualCode.findMappingByGeneratedRange(start, end);
|
|
81
|
+
|
|
82
|
+
if (!mapping) {
|
|
83
|
+
// If no mapping, skip all others as well
|
|
84
|
+
// This shouldn't happen as TS handles only mapped ranges
|
|
85
|
+
return tsHighlights;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!mapping.data.customData?.wordHighlight?.kind) {
|
|
89
|
+
// Skip if we didn't define word highlighting in segments
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!tsHighlights) {
|
|
94
|
+
tsHighlights = [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
tsHighlights.push({
|
|
98
|
+
range: {
|
|
99
|
+
start: document.positionAt(start),
|
|
100
|
+
end: document.positionAt(end),
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
kind: mapping.data.customData.wordHighlight.kind,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (tsHighlights.length > 0) {
|
|
108
|
+
log(`Found ${tsHighlights.length} occurrences of '${word}'`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Return TypeScript highlights if no custom keyword was found
|
|
112
|
+
return [...tsHighlights];
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = {
|
|
120
|
+
createDocumentHighlightPlugin,
|
|
121
|
+
};
|
package/src/hoverPlugin.js
CHANGED
|
@@ -1,26 +1,9 @@
|
|
|
1
1
|
/** @import { LanguageServicePlugin } from '@volar/language-server' */
|
|
2
|
-
/** @import { RippleVirtualCode } from '@ripple-ts/typescript-plugin/src/language.js') */
|
|
3
2
|
/** @import { LanguageServicePluginInstance } from '@volar/language-server' */
|
|
4
3
|
|
|
5
|
-
const {
|
|
4
|
+
const { getVirtualCode, createLogging } = require('./utils.js');
|
|
6
5
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @param {...unknown} args
|
|
11
|
-
*/
|
|
12
|
-
function log(...args) {
|
|
13
|
-
if (DEBUG) {
|
|
14
|
-
console.log('[Ripple Hover]', ...args);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @param {...unknown} args
|
|
20
|
-
*/
|
|
21
|
-
function logError(...args) {
|
|
22
|
-
console.error('[Ripple Hover]', ...args);
|
|
23
|
-
}
|
|
6
|
+
const { log, logError } = createLogging('[Ripple Hover Plugin]');
|
|
24
7
|
|
|
25
8
|
/**
|
|
26
9
|
* @returns {LanguageServicePlugin}
|
|
@@ -55,9 +38,6 @@ function createHoverPlugin() {
|
|
|
55
38
|
}
|
|
56
39
|
return {
|
|
57
40
|
async provideHover(document, position, token) {
|
|
58
|
-
const uri = URI.parse(document.uri);
|
|
59
|
-
const decoded = context.decodeEmbeddedDocumentUri(uri);
|
|
60
|
-
|
|
61
41
|
// Get TypeScript hover from typescript-semantic service
|
|
62
42
|
let tsHover = null;
|
|
63
43
|
if (originalProvideHover) {
|
|
@@ -65,23 +45,13 @@ function createHoverPlugin() {
|
|
|
65
45
|
}
|
|
66
46
|
|
|
67
47
|
// If no TypeScript hover, nothing to modify
|
|
68
|
-
if (!tsHover) {
|
|
48
|
+
if (!tsHover || !tsHover.range) {
|
|
69
49
|
return;
|
|
70
50
|
}
|
|
71
51
|
|
|
72
|
-
|
|
73
|
-
if (!decoded) {
|
|
74
|
-
return tsHover;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const [sourceUri, virtualCodeId] = decoded;
|
|
78
|
-
const sourceScript = context.language.scripts.get(sourceUri);
|
|
79
|
-
const virtualCode = /** @type {RippleVirtualCode } */ (
|
|
80
|
-
sourceScript?.generated?.embeddedCodes.get(virtualCodeId)
|
|
81
|
-
);
|
|
52
|
+
const virtualCode = getVirtualCode(document, context);
|
|
82
53
|
|
|
83
|
-
|
|
84
|
-
if (!tsHover.range) {
|
|
54
|
+
if (!virtualCode) {
|
|
85
55
|
return tsHover;
|
|
86
56
|
}
|
|
87
57
|
|
package/src/server.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
/** @import {CompilerOptions} from 'typescript' */
|
|
2
|
+
|
|
3
|
+
const { createLogging } = require('./utils.js');
|
|
4
|
+
const {
|
|
2
5
|
createConnection,
|
|
3
6
|
createServer,
|
|
4
7
|
createTypeScriptProject,
|
|
@@ -9,6 +12,7 @@ const { createHoverPlugin } = require('./hoverPlugin.js');
|
|
|
9
12
|
const { createCompletionPlugin } = require('./completionPlugin.js');
|
|
10
13
|
const { createAutoInsertPlugin } = require('./autoInsertPlugin.js');
|
|
11
14
|
const { createTypeScriptDiagnosticFilterPlugin } = require('./typescriptDiagnosticPlugin.js');
|
|
15
|
+
const { createDocumentHighlightPlugin } = require('./documentHighlightPlugin.js');
|
|
12
16
|
const {
|
|
13
17
|
getRippleLanguagePlugin,
|
|
14
18
|
resolveConfig,
|
|
@@ -16,25 +20,7 @@ const {
|
|
|
16
20
|
const { createTypeScriptServices } = require('./typescriptService.js');
|
|
17
21
|
const { create: createCssService } = require('volar-service-css');
|
|
18
22
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
/** @typedef {import('typescript').CompilerOptions} CompilerOptions */
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @param {...unknown} args
|
|
25
|
-
*/
|
|
26
|
-
function log(...args) {
|
|
27
|
-
if (DEBUG) {
|
|
28
|
-
console.log('[Ripple Server]', ...args);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @param {...unknown} args
|
|
34
|
-
*/
|
|
35
|
-
function logError(...args) {
|
|
36
|
-
console.error('[Ripple Server ERROR]', ...args);
|
|
37
|
-
}
|
|
23
|
+
const { log, logError } = createLogging('[Ripple Language Server]');
|
|
38
24
|
|
|
39
25
|
function createRippleLanguageServer() {
|
|
40
26
|
const connection = createConnection();
|
|
@@ -115,11 +101,12 @@ function createRippleLanguageServer() {
|
|
|
115
101
|
createDefinitionPlugin(),
|
|
116
102
|
createCssService(),
|
|
117
103
|
...createTypeScriptServices(ts),
|
|
118
|
-
// !IMPORTANT 'createTypeScriptDiagnosticFilterPlugin'
|
|
119
|
-
// must come after TypeScript services
|
|
104
|
+
// !IMPORTANT 'createTypeScriptDiagnosticFilterPlugin', 'createHoverPlugin',
|
|
105
|
+
// and 'createDocumentHighlightPlugin' must come after TypeScript services
|
|
120
106
|
// to intercept volar's and vscode default providers
|
|
121
107
|
createTypeScriptDiagnosticFilterPlugin(),
|
|
122
108
|
createHoverPlugin(),
|
|
109
|
+
createDocumentHighlightPlugin(),
|
|
123
110
|
],
|
|
124
111
|
);
|
|
125
112
|
|
|
@@ -1,31 +1,11 @@
|
|
|
1
1
|
/** @import { LanguageServicePlugin } from '@volar/language-server' */
|
|
2
2
|
/** @import { LanguageServicePluginInstance } from '@volar/language-server' */
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
/** @import { CodeMapping } from 'ripple/compiler' */
|
|
4
|
+
const { getVirtualCode, createLogging } = require('./utils.js');
|
|
6
5
|
|
|
7
|
-
const {
|
|
8
|
-
|
|
9
|
-
const DEBUG = process.env.RIPPLE_DEBUG === 'true';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @param {...unknown} args
|
|
13
|
-
*/
|
|
14
|
-
function log(...args) {
|
|
15
|
-
if (DEBUG) {
|
|
16
|
-
console.log('[Ripple Typescript Diagnostics]', ...args);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
6
|
+
const { log, logError } = createLogging('[Ripple TypeScript Diagnostic Plugin]');
|
|
19
7
|
|
|
20
8
|
/**
|
|
21
|
-
* @param {...unknown} args
|
|
22
|
-
*/
|
|
23
|
-
function logError(...args) {
|
|
24
|
-
console.error('[Ripple Typescript Diagnostics]', ...args);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Create a plugin to filter TypeScript diagnostics based on customData in mappings
|
|
29
9
|
* @returns {LanguageServicePlugin}
|
|
30
10
|
*/
|
|
31
11
|
function createTypeScriptDiagnosticFilterPlugin() {
|
|
@@ -45,7 +25,7 @@ function createTypeScriptDiagnosticFilterPlugin() {
|
|
|
45
25
|
/** @type {LanguageServicePluginInstance} */
|
|
46
26
|
let originalInstance;
|
|
47
27
|
|
|
48
|
-
// Disable typescript-semantic's
|
|
28
|
+
// Disable typescript-semantic's provideDiagnostics so it doesn't merge with ours
|
|
49
29
|
for (const [plugin, instance] of context.plugins) {
|
|
50
30
|
if (plugin.name === 'typescript-semantic') {
|
|
51
31
|
originalInstance = instance;
|
|
@@ -118,54 +98,6 @@ function createTypeScriptDiagnosticFilterPlugin() {
|
|
|
118
98
|
};
|
|
119
99
|
}
|
|
120
100
|
|
|
121
|
-
/**
|
|
122
|
-
* Get the mapping at a specific position in the document
|
|
123
|
-
* @param {import('@volar/language-server').LanguageServiceContext} context
|
|
124
|
-
* @param {import('vscode-languageserver-textdocument').TextDocument} document
|
|
125
|
-
* @param {import('@volar/language-server').Position} position
|
|
126
|
-
* @returns {CodeMapping | null}
|
|
127
|
-
*/
|
|
128
|
-
function getMappingAtPosition(context, document, position) {
|
|
129
|
-
try {
|
|
130
|
-
const { URI } = require('vscode-uri');
|
|
131
|
-
const { RippleVirtualCode } = require('@ripple-ts/typescript-plugin/src/language.js');
|
|
132
|
-
|
|
133
|
-
const uri = URI.parse(document.uri);
|
|
134
|
-
const decoded = context.decodeEmbeddedDocumentUri(uri);
|
|
135
|
-
if (!decoded) {
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const [sourceUri, virtualCodeId] = decoded;
|
|
140
|
-
const sourceScript = context.language.scripts.get(sourceUri);
|
|
141
|
-
const virtualCode = /** @type {RippleVirtualCode} */ (
|
|
142
|
-
sourceScript?.generated?.embeddedCodes.get(virtualCodeId)
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
if (!virtualCode?.mappings) {
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Convert position to offset in the virtual document
|
|
150
|
-
const offset = document.offsetAt(position);
|
|
151
|
-
|
|
152
|
-
// Find mapping that contains this offset
|
|
153
|
-
for (const mapping of virtualCode.mappings) {
|
|
154
|
-
const genStart = mapping.generatedOffsets[0];
|
|
155
|
-
const genEnd = genStart + mapping.lengths[0];
|
|
156
|
-
|
|
157
|
-
if (offset >= genStart && offset < genEnd) {
|
|
158
|
-
return mapping;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return null;
|
|
163
|
-
} catch (err) {
|
|
164
|
-
log('Error getting mapping at position:', err);
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
101
|
module.exports = {
|
|
170
102
|
createTypeScriptDiagnosticFilterPlugin,
|
|
171
103
|
};
|
package/src/typescriptService.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
3
|
-
* @
|
|
2
|
+
* @import {LanguageServiceContext} from '@volar/language-server'
|
|
3
|
+
* @import {TextDocument} from 'vscode-languageserver-textdocument'
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// Monkey-patch getUserPreferences before requiring the main module
|
package/src/utils.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
/** @import {RippleVirtualCode} from '@ripple-ts/typescript-plugin/src/language.js' */
|
|
4
4
|
|
|
5
5
|
const { URI } = require('vscode-uri');
|
|
6
|
+
const { createLogging, DEBUG } = require('@ripple-ts/typescript-plugin/src/utils.js');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Get virtual code from the encoded document URI
|
|
@@ -24,6 +25,34 @@ function getVirtualCode(document, context) {
|
|
|
24
25
|
return virtualCode;
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Get the word at a specific position in the text
|
|
30
|
+
* @param {string} text
|
|
31
|
+
* @param {number} start
|
|
32
|
+
* @returns {{word: string, start: number, end: number}}
|
|
33
|
+
*/
|
|
34
|
+
function getWordFromPosition(text, start) {
|
|
35
|
+
let wordStart = start;
|
|
36
|
+
let wordEnd = start;
|
|
37
|
+
while (wordStart > 0 && /\w/.test(text[wordStart - 1])) {
|
|
38
|
+
wordStart--;
|
|
39
|
+
}
|
|
40
|
+
while (wordEnd < text.length && /\w/.test(text[wordEnd])) {
|
|
41
|
+
wordEnd++;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const word = text.substring(wordStart, wordEnd);
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
word,
|
|
48
|
+
start: wordStart,
|
|
49
|
+
end: wordEnd,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
27
53
|
module.exports = {
|
|
28
54
|
getVirtualCode,
|
|
55
|
+
getWordFromPosition,
|
|
56
|
+
createLogging,
|
|
57
|
+
DEBUG,
|
|
29
58
|
};
|