@ripple-ts/language-server 0.3.41 → 0.3.42
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/dist/language-server.js +9 -0
- package/dist/language-server.js.map +1 -0
- package/dist/package.json +1 -0
- package/dist/server.js +111 -0
- package/dist/server.js.map +1 -0
- package/dist/typescriptService-CWMxKghf.js +21866 -0
- package/dist/typescriptService-CWMxKghf.js.map +1 -0
- package/package.json +8 -5
- package/CHANGELOG.md +0 -513
- package/bin/language-server.js +0 -5
- package/src/autoInsertPlugin.js +0 -163
- package/src/compileErrorDiagnosticPlugin.js +0 -155
- package/src/completionPlugin.js +0 -508
- package/src/definitionPlugin.js +0 -208
- package/src/documentHighlightPlugin.js +0 -118
- package/src/hoverPlugin.js +0 -146
- package/src/server.js +0 -155
- package/src/typescriptDiagnosticPlugin.js +0 -139
- package/src/typescriptService.js +0 -49
- package/src/utils.js +0 -162
- package/tsconfig.json +0 -17
- package/tsconfig.typecheck.json +0 -7
- package/tsdown.config.js +0 -41
package/src/definitionPlugin.js
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
/** @import { LanguageServicePlugin, LocationLink } from '@volar/language-server'; */
|
|
2
|
-
/** @import { DefinitionLocation } from '@tsrx/ripple'; */
|
|
3
|
-
|
|
4
|
-
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
5
|
-
import { getVirtualCode, createLogging, getWordFromPosition } from './utils.js';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import {
|
|
8
|
-
normalizeFileNameOrUri,
|
|
9
|
-
getRippleDirForFile,
|
|
10
|
-
getCachedTypeDefinitionFile,
|
|
11
|
-
getCachedTypeMatches,
|
|
12
|
-
} from '@tsrx/typescript-plugin/src/language.js';
|
|
13
|
-
|
|
14
|
-
const { log } = createLogging('[Ripple Definition Plugin]');
|
|
15
|
-
/** @type {string | undefined} */
|
|
16
|
-
let ripple_dir;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @returns {LanguageServicePlugin}
|
|
20
|
-
*/
|
|
21
|
-
export function createDefinitionPlugin() {
|
|
22
|
-
return {
|
|
23
|
-
name: 'ripple-definition',
|
|
24
|
-
capabilities: {
|
|
25
|
-
definitionProvider: true,
|
|
26
|
-
},
|
|
27
|
-
create(context) {
|
|
28
|
-
return {
|
|
29
|
-
async provideDefinition(document, position, token) {
|
|
30
|
-
// Get TypeScript definition from typescript-semantic service
|
|
31
|
-
/** @type {LocationLink[]} */
|
|
32
|
-
let tsDefinitions = [];
|
|
33
|
-
for (const [plugin, instance] of context.plugins) {
|
|
34
|
-
if (plugin.name === 'typescript-semantic' && instance.provideDefinition) {
|
|
35
|
-
const result = await instance.provideDefinition(document, position, token);
|
|
36
|
-
if (result) {
|
|
37
|
-
tsDefinitions.push(...(Array.isArray(result) ? result : [result]));
|
|
38
|
-
}
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const { virtualCode, sourceUri } = getVirtualCode(document, context);
|
|
44
|
-
|
|
45
|
-
if (virtualCode.languageId !== 'ripple') {
|
|
46
|
-
// like embedded css
|
|
47
|
-
log(`Skipping definitions processing in the '${virtualCode.languageId}' context`);
|
|
48
|
-
return tsDefinitions;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// First check for custom definitions (e.g., CSS class selectors)
|
|
52
|
-
const offset = document.offsetAt(position);
|
|
53
|
-
const text = document.getText();
|
|
54
|
-
// Find word boundaries
|
|
55
|
-
const { word, start, end } = getWordFromPosition(text, offset);
|
|
56
|
-
const customMapping = virtualCode.findMappingByGeneratedRange(start, end);
|
|
57
|
-
|
|
58
|
-
log(`Cursor position in generated code for word '${word}':`, position);
|
|
59
|
-
log(`Cursor offset in generated code for word '${word}':`, offset);
|
|
60
|
-
|
|
61
|
-
// Handle `typeReplace` definitions
|
|
62
|
-
if (
|
|
63
|
-
customMapping?.data.customData.definition !== false &&
|
|
64
|
-
customMapping?.data.customData.definition?.typeReplace
|
|
65
|
-
) {
|
|
66
|
-
const { name: typeName, path: typePath } =
|
|
67
|
-
customMapping.data.customData.definition.typeReplace;
|
|
68
|
-
|
|
69
|
-
log(`Found replace definition for ${typeName}`);
|
|
70
|
-
|
|
71
|
-
const filePath = sourceUri.fsPath || sourceUri.path;
|
|
72
|
-
ripple_dir = ripple_dir ?? getRippleDirForFile(normalizeFileNameOrUri(filePath));
|
|
73
|
-
|
|
74
|
-
if (!ripple_dir) {
|
|
75
|
-
log(`Could not determine Ripple source directory for file: ${filePath}`);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const typesFilePath = path.join(ripple_dir, ...typePath.split('/'));
|
|
80
|
-
|
|
81
|
-
const fileContent = getCachedTypeDefinitionFile(typesFilePath);
|
|
82
|
-
|
|
83
|
-
if (!fileContent) {
|
|
84
|
-
// the `getCachedTypeDefinitionFile` already logs the error
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const match = getCachedTypeMatches(typeName, fileContent);
|
|
89
|
-
|
|
90
|
-
if (match && match.index !== undefined) {
|
|
91
|
-
const classStart = match.index + match[0].indexOf(typeName);
|
|
92
|
-
const classEnd = classStart + typeName.length;
|
|
93
|
-
|
|
94
|
-
// Convert offset to line/column
|
|
95
|
-
const lines = fileContent.substring(0, classStart).split('\n');
|
|
96
|
-
const line = lines.length - 1;
|
|
97
|
-
const character = lines[lines.length - 1].length;
|
|
98
|
-
|
|
99
|
-
const endLines = fileContent.substring(0, classEnd).split('\n');
|
|
100
|
-
const endLine = endLines.length - 1;
|
|
101
|
-
const endCharacter = endLines[endLines.length - 1].length;
|
|
102
|
-
|
|
103
|
-
// Create the origin selection range for #Map/#Set
|
|
104
|
-
const generatedStart = customMapping.generatedOffsets[0];
|
|
105
|
-
const generatedEnd = generatedStart + customMapping.generatedLengths[0];
|
|
106
|
-
const originStart = document.positionAt(generatedStart);
|
|
107
|
-
const originEnd = document.positionAt(generatedEnd);
|
|
108
|
-
|
|
109
|
-
/** @type {LocationLink} */
|
|
110
|
-
const locationLink = {
|
|
111
|
-
targetUri: `file://${typesFilePath}`,
|
|
112
|
-
targetRange: {
|
|
113
|
-
start: { line, character },
|
|
114
|
-
end: { line: endLine, character: endCharacter },
|
|
115
|
-
},
|
|
116
|
-
targetSelectionRange: {
|
|
117
|
-
start: { line, character },
|
|
118
|
-
end: { line: endLine, character: endCharacter },
|
|
119
|
-
},
|
|
120
|
-
originSelectionRange: {
|
|
121
|
-
start: originStart,
|
|
122
|
-
end: originEnd,
|
|
123
|
-
},
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
log(`Created definition link to ${typesFilePath}:${line}:${character}`);
|
|
127
|
-
return [locationLink];
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Handle embedded code definition location, e.g. CSS class selectors
|
|
132
|
-
if (
|
|
133
|
-
customMapping?.data.customData.definition !== false &&
|
|
134
|
-
customMapping?.data.customData.definition?.location
|
|
135
|
-
) {
|
|
136
|
-
const def = customMapping.data.customData.definition;
|
|
137
|
-
const loc = /** @type {DefinitionLocation} */ (def.location);
|
|
138
|
-
|
|
139
|
-
const embeddedCode = loc.embeddedId
|
|
140
|
-
? virtualCode.embeddedCodes?.find(
|
|
141
|
-
(/** @type {{ id: string }} */ { id }) => id === loc.embeddedId,
|
|
142
|
-
)
|
|
143
|
-
: undefined;
|
|
144
|
-
|
|
145
|
-
if (embeddedCode) {
|
|
146
|
-
const embedMapping = embeddedCode.mappings[0];
|
|
147
|
-
|
|
148
|
-
// Calculate the position in the source document
|
|
149
|
-
// CSS offset relative to embedded code start + source offset of CSS region
|
|
150
|
-
const sourceStartOffset = embedMapping.sourceOffsets[0] + loc.start;
|
|
151
|
-
const sourceEndOffset = embedMapping.sourceOffsets[0] + loc.end;
|
|
152
|
-
|
|
153
|
-
log(
|
|
154
|
-
'Source document offsets - start for matching css:',
|
|
155
|
-
sourceStartOffset,
|
|
156
|
-
'end:',
|
|
157
|
-
sourceEndOffset,
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
// Calculate line/column positions using the source document's proper encoding
|
|
161
|
-
// Create a TextDocument from the source code for proper position calculations
|
|
162
|
-
const sourceDocument = TextDocument.create(
|
|
163
|
-
sourceUri.toString(),
|
|
164
|
-
'ripple',
|
|
165
|
-
0,
|
|
166
|
-
virtualCode.originalCode,
|
|
167
|
-
);
|
|
168
|
-
const targetStart = sourceDocument.positionAt(sourceStartOffset);
|
|
169
|
-
const targetEnd = sourceDocument.positionAt(sourceEndOffset);
|
|
170
|
-
|
|
171
|
-
log('Target positions in source - start:', targetStart, 'end:', targetEnd);
|
|
172
|
-
|
|
173
|
-
// The origin selection range should be in the virtual document
|
|
174
|
-
// not in the source document!
|
|
175
|
-
const generatedStart = customMapping.generatedOffsets[0];
|
|
176
|
-
const generatedEnd = generatedStart + customMapping.generatedLengths[0];
|
|
177
|
-
const originStart = document.positionAt(generatedStart);
|
|
178
|
-
const originEnd = document.positionAt(generatedEnd);
|
|
179
|
-
|
|
180
|
-
log('Origin positions - start:', originStart, 'end:', originEnd);
|
|
181
|
-
|
|
182
|
-
/** @type {LocationLink} */
|
|
183
|
-
tsDefinitions.push({
|
|
184
|
-
targetUri: sourceUri.toString(), // Use the actual source file URI
|
|
185
|
-
targetRange: {
|
|
186
|
-
start: targetStart,
|
|
187
|
-
end: targetEnd,
|
|
188
|
-
},
|
|
189
|
-
targetSelectionRange: {
|
|
190
|
-
start: targetStart,
|
|
191
|
-
end: targetEnd,
|
|
192
|
-
},
|
|
193
|
-
originSelectionRange: {
|
|
194
|
-
start: originStart,
|
|
195
|
-
end: originEnd,
|
|
196
|
-
},
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
return tsDefinitions;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return tsDefinitions;
|
|
204
|
-
},
|
|
205
|
-
};
|
|
206
|
-
},
|
|
207
|
-
};
|
|
208
|
-
}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/** @import { LanguageServicePlugin } from '@volar/language-server' */
|
|
2
|
-
/** @import { LanguageServicePluginInstance } from '@volar/language-server' */
|
|
3
|
-
|
|
4
|
-
import { getVirtualCode, getWordFromPosition, createLogging } from './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
|
-
export 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.languageId !== 'ripple') {
|
|
62
|
-
log(`Skipping highlight processing in the '${virtualCode.languageId}' context`);
|
|
63
|
-
return tsHighlights;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Check if we're on a custom Ripple keyword
|
|
67
|
-
const offset = document.offsetAt(position);
|
|
68
|
-
const text = document.getText();
|
|
69
|
-
|
|
70
|
-
// Find word boundaries
|
|
71
|
-
const { word } = getWordFromPosition(text, offset);
|
|
72
|
-
|
|
73
|
-
// If the word is a Ripple keyword, find all occurrences in the document
|
|
74
|
-
|
|
75
|
-
const regex = new RegExp(`\\b${word}\\b`, 'g');
|
|
76
|
-
let match;
|
|
77
|
-
|
|
78
|
-
while ((match = regex.exec(text)) !== null) {
|
|
79
|
-
const start = match.index;
|
|
80
|
-
const end = match.index + word.length;
|
|
81
|
-
const mapping = virtualCode.findMappingByGeneratedRange(start, end);
|
|
82
|
-
|
|
83
|
-
if (!mapping) {
|
|
84
|
-
// If no mapping, skip all others as well
|
|
85
|
-
// This shouldn't happen as TS handles only mapped ranges
|
|
86
|
-
return tsHighlights;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (!mapping.data.customData?.wordHighlight?.kind) {
|
|
90
|
-
// Skip if we didn't define word highlighting in segments
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (!tsHighlights) {
|
|
95
|
-
tsHighlights = [];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
tsHighlights.push({
|
|
99
|
-
range: {
|
|
100
|
-
start: document.positionAt(start),
|
|
101
|
-
end: document.positionAt(end),
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
kind: mapping.data.customData.wordHighlight.kind,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (tsHighlights.length > 0) {
|
|
109
|
-
log(`Found ${tsHighlights.length} occurrences of '${word}'`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Return TypeScript highlights if no custom keyword was found
|
|
113
|
-
return [...tsHighlights];
|
|
114
|
-
},
|
|
115
|
-
};
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
}
|
package/src/hoverPlugin.js
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
@import {
|
|
3
|
-
LanguageServicePlugin,
|
|
4
|
-
LanguageServicePluginInstance,
|
|
5
|
-
MarkupContent,
|
|
6
|
-
} from '@volar/language-server'; */
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
getVirtualCode,
|
|
10
|
-
createLogging,
|
|
11
|
-
getWordFromPosition,
|
|
12
|
-
concatMarkdownContents,
|
|
13
|
-
deobfuscateIdentifiers,
|
|
14
|
-
} from './utils.js';
|
|
15
|
-
|
|
16
|
-
const { log, logError } = createLogging('[Ripple Hover Plugin]');
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @returns {LanguageServicePlugin}
|
|
20
|
-
*/
|
|
21
|
-
export function createHoverPlugin() {
|
|
22
|
-
return {
|
|
23
|
-
name: 'ripple-hover',
|
|
24
|
-
capabilities: {
|
|
25
|
-
hoverProvider: true,
|
|
26
|
-
},
|
|
27
|
-
create(context) {
|
|
28
|
-
/** @type {LanguageServicePluginInstance['provideHover']} */
|
|
29
|
-
let originalProvideHover;
|
|
30
|
-
/** @type {LanguageServicePluginInstance} */
|
|
31
|
-
let originalInstance;
|
|
32
|
-
|
|
33
|
-
// Disable typescript-semantic's provideHover so it doesn't merge with ours
|
|
34
|
-
for (const [plugin, instance] of context.plugins) {
|
|
35
|
-
if (plugin.name === 'typescript-semantic') {
|
|
36
|
-
originalInstance = instance;
|
|
37
|
-
originalProvideHover = instance.provideHover;
|
|
38
|
-
instance.provideHover = undefined;
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (!originalProvideHover) {
|
|
44
|
-
logError(
|
|
45
|
-
"'typescript-semantic plugin' was not found or has no 'provideHover'. \
|
|
46
|
-
This plugin must be loaded after Volar's typescript-semantic plugin.",
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
return {
|
|
50
|
-
async provideHover(document, position, token) {
|
|
51
|
-
// Get TypeScript hover from typescript-semantic service
|
|
52
|
-
let tsHover = null;
|
|
53
|
-
if (originalProvideHover) {
|
|
54
|
-
tsHover = await originalProvideHover.call(originalInstance, document, position, token);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (tsHover && tsHover.contents) {
|
|
58
|
-
/** @type {MarkupContent} **/ (tsHover.contents).value = deobfuscateIdentifiers(
|
|
59
|
-
/** @type {MarkupContent} **/ (tsHover.contents).value,
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const { virtualCode } = getVirtualCode(document, context);
|
|
64
|
-
|
|
65
|
-
if (!virtualCode) {
|
|
66
|
-
return tsHover;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** @type {number} */
|
|
70
|
-
let starOffset;
|
|
71
|
-
/** @type {number} */
|
|
72
|
-
let endOffset;
|
|
73
|
-
|
|
74
|
-
if (tsHover && tsHover.range) {
|
|
75
|
-
starOffset = document.offsetAt(tsHover.range.start);
|
|
76
|
-
endOffset = document.offsetAt(tsHover.range.end);
|
|
77
|
-
} else {
|
|
78
|
-
const offset = document.offsetAt(position);
|
|
79
|
-
const text = document.getText();
|
|
80
|
-
// Find word boundaries
|
|
81
|
-
const { word, start, end } = getWordFromPosition(text, offset);
|
|
82
|
-
starOffset = start;
|
|
83
|
-
endOffset = end;
|
|
84
|
-
|
|
85
|
-
log(`Cursor position in generated code for word '${word}':`, position);
|
|
86
|
-
log(`Cursor offset in generated code for word '${word}':`, offset);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (virtualCode.languageId !== 'ripple') {
|
|
90
|
-
log(`Skipping hover processing in the '${virtualCode.languageId}' context`);
|
|
91
|
-
return tsHover;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const mapping = virtualCode.findMappingByGeneratedRange(starOffset, endOffset);
|
|
95
|
-
|
|
96
|
-
if (!mapping) {
|
|
97
|
-
return tsHover;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const customHover = mapping?.data?.customData?.hover;
|
|
101
|
-
|
|
102
|
-
if (customHover === undefined) {
|
|
103
|
-
return tsHover;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (typeof customHover === 'function') {
|
|
107
|
-
if (tsHover) {
|
|
108
|
-
/** @type {MarkupContent} **/ (tsHover.contents).value = customHover(
|
|
109
|
-
/** @type {MarkupContent} **/ (tsHover.contents).value,
|
|
110
|
-
);
|
|
111
|
-
log('Modified hover contents using custom hover function');
|
|
112
|
-
}
|
|
113
|
-
return tsHover;
|
|
114
|
-
} else if (typeof customHover === 'string') {
|
|
115
|
-
const contents = tsHover
|
|
116
|
-
? concatMarkdownContents(
|
|
117
|
-
/** @type {MarkupContent} **/ (tsHover.contents).value,
|
|
118
|
-
customHover,
|
|
119
|
-
)
|
|
120
|
-
: customHover;
|
|
121
|
-
log('Found custom hover data in mapping');
|
|
122
|
-
return {
|
|
123
|
-
contents: {
|
|
124
|
-
kind: 'markdown',
|
|
125
|
-
value: contents,
|
|
126
|
-
},
|
|
127
|
-
range: {
|
|
128
|
-
start: position,
|
|
129
|
-
end: position,
|
|
130
|
-
},
|
|
131
|
-
};
|
|
132
|
-
} else if (customHover === false) {
|
|
133
|
-
log(
|
|
134
|
-
`Hover explicitly suppressed in mapping at range start: ${starOffset}, end: ${endOffset}`,
|
|
135
|
-
);
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
log('Found mapping for hover at range', 'start: ', starOffset, 'end: ', endOffset);
|
|
140
|
-
|
|
141
|
-
return tsHover;
|
|
142
|
-
},
|
|
143
|
-
};
|
|
144
|
-
},
|
|
145
|
-
};
|
|
146
|
-
}
|
package/src/server.js
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
/** @import {CompilerOptions} from 'typescript' */
|
|
2
|
-
|
|
3
|
-
import { createLogging } from './utils.js';
|
|
4
|
-
import {
|
|
5
|
-
createConnection,
|
|
6
|
-
createServer,
|
|
7
|
-
createTypeScriptProject,
|
|
8
|
-
} from '@volar/language-server/node';
|
|
9
|
-
import { createCompileErrorDiagnosticPlugin } from './compileErrorDiagnosticPlugin.js';
|
|
10
|
-
import { createDefinitionPlugin } from './definitionPlugin.js';
|
|
11
|
-
import { createHoverPlugin } from './hoverPlugin.js';
|
|
12
|
-
import { createCompletionPlugin } from './completionPlugin.js';
|
|
13
|
-
import { createAutoInsertPlugin } from './autoInsertPlugin.js';
|
|
14
|
-
import { createTypeScriptDiagnosticFilterPlugin } from './typescriptDiagnosticPlugin.js';
|
|
15
|
-
import { createDocumentHighlightPlugin } from './documentHighlightPlugin.js';
|
|
16
|
-
import { getRippleLanguagePlugin, resolveConfig } from '@tsrx/typescript-plugin/src/language.js';
|
|
17
|
-
import { createTypeScriptServices } from './typescriptService.js';
|
|
18
|
-
import { create as createCssService } from 'volar-service-css';
|
|
19
|
-
|
|
20
|
-
const { log, logError } = createLogging('[Ripple Language Server]');
|
|
21
|
-
|
|
22
|
-
export function createRippleLanguageServer() {
|
|
23
|
-
const connection = createConnection();
|
|
24
|
-
const server = createServer(connection);
|
|
25
|
-
|
|
26
|
-
connection.listen();
|
|
27
|
-
|
|
28
|
-
// Create language plugin instance once and reuse it
|
|
29
|
-
// This prevents creating multiple instances if the callback is called multiple times
|
|
30
|
-
const rippleLanguagePlugin = getRippleLanguagePlugin();
|
|
31
|
-
log('Language plugin instance created');
|
|
32
|
-
|
|
33
|
-
/** @type {WeakSet<Function>} */
|
|
34
|
-
const wrappedFunctions = new WeakSet();
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Ensure TypeScript hosts always see compiler options with Ripple defaults.
|
|
38
|
-
* @param {unknown} target
|
|
39
|
-
* @param {string} method
|
|
40
|
-
*/
|
|
41
|
-
function wrapCompilerOptionsProvider(target, method) {
|
|
42
|
-
if (!target) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const host = /** @type {{ [key: string]: unknown }} */ (target);
|
|
47
|
-
const original = host[method];
|
|
48
|
-
if (typeof original !== 'function' || wrappedFunctions.has(original)) {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** @type {CompilerOptions | undefined} */
|
|
53
|
-
let cachedInput;
|
|
54
|
-
/** @type {CompilerOptions | undefined} */
|
|
55
|
-
let cachedOutput;
|
|
56
|
-
|
|
57
|
-
const wrapped = () => {
|
|
58
|
-
/** @type {CompilerOptions} */
|
|
59
|
-
const input = original.call(host);
|
|
60
|
-
if (cachedInput !== input) {
|
|
61
|
-
cachedInput = input;
|
|
62
|
-
cachedOutput = resolveConfig({ options: input }).options;
|
|
63
|
-
}
|
|
64
|
-
return cachedOutput;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
wrappedFunctions.add(original);
|
|
68
|
-
wrappedFunctions.add(wrapped);
|
|
69
|
-
host[method] = wrapped;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
connection.onInitialize(async (params) => {
|
|
73
|
-
try {
|
|
74
|
-
log('Initializing Ripple language server...');
|
|
75
|
-
log('Initialization options:', JSON.stringify(params.initializationOptions, null, 2));
|
|
76
|
-
|
|
77
|
-
const ts = require('typescript');
|
|
78
|
-
|
|
79
|
-
const initResult = server.initialize(
|
|
80
|
-
params,
|
|
81
|
-
createTypeScriptProject(ts, undefined, ({ projectHost }) => {
|
|
82
|
-
wrapCompilerOptionsProvider(projectHost, 'getCompilationSettings');
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
languagePlugins: [rippleLanguagePlugin],
|
|
86
|
-
setup({ project }) {
|
|
87
|
-
wrapCompilerOptionsProvider(
|
|
88
|
-
project?.typescript?.languageServiceHost,
|
|
89
|
-
'getCompilationSettings',
|
|
90
|
-
);
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
}),
|
|
94
|
-
[
|
|
95
|
-
createAutoInsertPlugin(),
|
|
96
|
-
createCompletionPlugin(),
|
|
97
|
-
createCompileErrorDiagnosticPlugin(),
|
|
98
|
-
createDefinitionPlugin(),
|
|
99
|
-
createCssService(),
|
|
100
|
-
...createTypeScriptServices(ts),
|
|
101
|
-
// !IMPORTANT 'createTypeScriptDiagnosticFilterPlugin', 'createHoverPlugin',
|
|
102
|
-
// and 'createDocumentHighlightPlugin' must come after TypeScript services
|
|
103
|
-
// to intercept volar's and vscode default providers
|
|
104
|
-
createTypeScriptDiagnosticFilterPlugin(),
|
|
105
|
-
createHoverPlugin(),
|
|
106
|
-
createDocumentHighlightPlugin(),
|
|
107
|
-
],
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
log('Server initialization complete');
|
|
111
|
-
return initResult;
|
|
112
|
-
} catch (initError) {
|
|
113
|
-
logError('Server initialization failed:', initError);
|
|
114
|
-
throw initError;
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
connection.onInitialized(async () => {
|
|
119
|
-
log('Server initialized.');
|
|
120
|
-
server.initialized();
|
|
121
|
-
|
|
122
|
-
// Register file watchers for TypeScript/JavaScript files so the language
|
|
123
|
-
// server is notified when they change on disk. Without this, changes to
|
|
124
|
-
// .ts files that are imported by .tsrx files are not detected, causing
|
|
125
|
-
// stale diagnostics until the server is restarted.
|
|
126
|
-
try {
|
|
127
|
-
await server.fileWatcher.watchFiles([
|
|
128
|
-
'**/*.ts',
|
|
129
|
-
'**/*.tsx',
|
|
130
|
-
'**/*.cts',
|
|
131
|
-
'**/*.mts',
|
|
132
|
-
'**/*.js',
|
|
133
|
-
'**/*.jsx',
|
|
134
|
-
'**/*.cjs',
|
|
135
|
-
'**/*.mjs',
|
|
136
|
-
'**/*.d.ts',
|
|
137
|
-
'**/tsconfig.json',
|
|
138
|
-
'**/jsconfig.json',
|
|
139
|
-
]);
|
|
140
|
-
log('File watchers registered for TypeScript/JavaScript files.');
|
|
141
|
-
} catch (err) {
|
|
142
|
-
logError('Failed to register file watchers:', err);
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
process.on('uncaughtException', (err) => {
|
|
147
|
-
logError('Uncaught exception:', err);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
process.on('unhandledRejection', (reason, promise) => {
|
|
151
|
-
logError('Unhandled rejection at:', promise, 'reason:', reason);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
return { connection, server };
|
|
155
|
-
}
|