@singapore-editor/lsp-plugin 0.1.1
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 +103 -0
- package/dist/bufferSync.d.ts +12 -0
- package/dist/bufferSync.d.ts.map +1 -0
- package/dist/bufferSync.js +35 -0
- package/dist/bufferSync.js.map +1 -0
- package/dist/codeActions.d.ts +83 -0
- package/dist/codeActions.d.ts.map +1 -0
- package/dist/codeActions.js +294 -0
- package/dist/codeActions.js.map +1 -0
- package/dist/colorAlpha.d.ts +4 -0
- package/dist/colorAlpha.d.ts.map +1 -0
- package/dist/colorAlpha.js +28 -0
- package/dist/colorAlpha.js.map +1 -0
- package/dist/completion.d.ts +124 -0
- package/dist/completion.d.ts.map +1 -0
- package/dist/completion.js +609 -0
- package/dist/completion.js.map +1 -0
- package/dist/completionCommit.d.ts +19 -0
- package/dist/completionCommit.d.ts.map +1 -0
- package/dist/completionCommit.js +57 -0
- package/dist/completionCommit.js.map +1 -0
- package/dist/completionController.d.ts +89 -0
- package/dist/completionController.d.ts.map +1 -0
- package/dist/completionController.js +376 -0
- package/dist/completionController.js.map +1 -0
- package/dist/completionProviders.d.ts +50 -0
- package/dist/completionProviders.d.ts.map +1 -0
- package/dist/completionProviders.js +64 -0
- package/dist/completionProviders.js.map +1 -0
- package/dist/connectionContext.d.ts +7 -0
- package/dist/connectionContext.d.ts.map +1 -0
- package/dist/definitionLinkController.d.ts +54 -0
- package/dist/definitionLinkController.d.ts.map +1 -0
- package/dist/definitionLinkController.js +221 -0
- package/dist/definitionLinkController.js.map +1 -0
- package/dist/definitionNavigation.d.ts +76 -0
- package/dist/definitionNavigation.d.ts.map +1 -0
- package/dist/definitionNavigation.js +143 -0
- package/dist/definitionNavigation.js.map +1 -0
- package/dist/diagnosticNotes.d.ts +7 -0
- package/dist/diagnosticNotes.d.ts.map +1 -0
- package/dist/diagnosticNotes.js +37 -0
- package/dist/diagnosticNotes.js.map +1 -0
- package/dist/diagnosticProjection.d.ts +31 -0
- package/dist/diagnosticProjection.d.ts.map +1 -0
- package/dist/diagnosticProjection.js +79 -0
- package/dist/diagnosticProjection.js.map +1 -0
- package/dist/diagnostics.d.ts +16 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +94 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/diagnosticsPresenter.d.ts +64 -0
- package/dist/diagnosticsPresenter.d.ts.map +1 -0
- package/dist/diagnosticsPresenter.js +305 -0
- package/dist/diagnosticsPresenter.js.map +1 -0
- package/dist/document.d.ts +69 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +190 -0
- package/dist/document.js.map +1 -0
- package/dist/documentHighlightController.d.ts +34 -0
- package/dist/documentHighlightController.d.ts.map +1 -0
- package/dist/documentHighlightController.js +101 -0
- package/dist/documentHighlightController.js.map +1 -0
- package/dist/documentSnapshot.d.ts +9 -0
- package/dist/documentSnapshot.d.ts.map +1 -0
- package/dist/documentSnapshot.js +31 -0
- package/dist/documentSnapshot.js.map +1 -0
- package/dist/documentSync.d.ts +53 -0
- package/dist/documentSync.d.ts.map +1 -0
- package/dist/documentSync.js +334 -0
- package/dist/documentSync.js.map +1 -0
- package/dist/documentSyncController.d.ts +26 -0
- package/dist/documentSyncController.d.ts.map +1 -0
- package/dist/documentSyncController.js +24 -0
- package/dist/documentSyncController.js.map +1 -0
- package/dist/formatOnType.d.ts +43 -0
- package/dist/formatOnType.d.ts.map +1 -0
- package/dist/formatOnType.js +198 -0
- package/dist/formatOnType.js.map +1 -0
- package/dist/formatting.d.ts +18 -0
- package/dist/formatting.d.ts.map +1 -0
- package/dist/formatting.js +52 -0
- package/dist/formatting.js.map +1 -0
- package/dist/fuzzyMatch.d.ts +22 -0
- package/dist/fuzzyMatch.d.ts.map +1 -0
- package/dist/fuzzyMatch.js +216 -0
- package/dist/fuzzyMatch.js.map +1 -0
- package/dist/hoverParticipant.d.ts +24 -0
- package/dist/hoverParticipant.d.ts.map +1 -0
- package/dist/hoverParticipant.js +156 -0
- package/dist/hoverParticipant.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/keyCommands.d.ts +19 -0
- package/dist/keyCommands.d.ts.map +1 -0
- package/dist/keyCommands.js +21 -0
- package/dist/keyCommands.js.map +1 -0
- package/dist/lane.d.ts +28 -0
- package/dist/lane.d.ts.map +1 -0
- package/dist/lane.js +138 -0
- package/dist/lane.js.map +1 -0
- package/dist/lspConnection.d.ts +94 -0
- package/dist/lspConnection.d.ts.map +1 -0
- package/dist/lspConnection.js +195 -0
- package/dist/lspConnection.js.map +1 -0
- package/dist/lspConnectionPool.d.ts +34 -0
- package/dist/lspConnectionPool.d.ts.map +1 -0
- package/dist/lspConnectionPool.js +255 -0
- package/dist/lspConnectionPool.js.map +1 -0
- package/dist/minimalEdits.d.ts +21 -0
- package/dist/minimalEdits.d.ts.map +1 -0
- package/dist/minimalEdits.js +181 -0
- package/dist/minimalEdits.js.map +1 -0
- package/dist/paths.d.ts +6 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +38 -0
- package/dist/paths.js.map +1 -0
- package/dist/plugin.d.ts +100 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +837 -0
- package/dist/plugin.js.map +1 -0
- package/dist/plugin.styles.d.ts +31 -0
- package/dist/plugin.styles.d.ts.map +1 -0
- package/dist/plugin.styles.js +102 -0
- package/dist/plugin.styles.js.map +1 -0
- package/dist/pluginTypes.d.ts +41 -0
- package/dist/pluginTypes.d.ts.map +1 -0
- package/dist/pluginTypes.js +0 -0
- package/dist/pullDiagnostics.d.ts +30 -0
- package/dist/pullDiagnostics.d.ts.map +1 -0
- package/dist/pullDiagnostics.js +94 -0
- package/dist/pullDiagnostics.js.map +1 -0
- package/dist/renameWidget.d.ts +30 -0
- package/dist/renameWidget.d.ts.map +1 -0
- package/dist/renameWidget.js +117 -0
- package/dist/renameWidget.js.map +1 -0
- package/dist/semanticTokenDecoder.d.ts +81 -0
- package/dist/semanticTokenDecoder.d.ts.map +1 -0
- package/dist/semanticTokenDecoder.js +132 -0
- package/dist/semanticTokenDecoder.js.map +1 -0
- package/dist/semanticTokens.d.ts +47 -0
- package/dist/semanticTokens.d.ts.map +1 -0
- package/dist/semanticTokens.js +68 -0
- package/dist/semanticTokens.js.map +1 -0
- package/dist/serverSet.d.ts +69 -0
- package/dist/serverSet.d.ts.map +1 -0
- package/dist/serverSet.js +331 -0
- package/dist/serverSet.js.map +1 -0
- package/dist/signatureHelp.d.ts +40 -0
- package/dist/signatureHelp.d.ts.map +1 -0
- package/dist/signatureHelp.js +108 -0
- package/dist/signatureHelp.js.map +1 -0
- package/dist/signatureHelpController.d.ts +52 -0
- package/dist/signatureHelpController.d.ts.map +1 -0
- package/dist/signatureHelpController.js +160 -0
- package/dist/signatureHelpController.js.map +1 -0
- package/dist/sourceText.d.ts +22 -0
- package/dist/sourceText.d.ts.map +1 -0
- package/dist/sourceText.js +70 -0
- package/dist/sourceText.js.map +1 -0
- package/dist/types.d.ts +238 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/dist/viewDocumentSnapshot.d.ts +5 -0
- package/dist/viewDocumentSnapshot.d.ts.map +1 -0
- package/dist/viewDocumentSnapshot.js +12 -0
- package/dist/viewDocumentSnapshot.js.map +1 -0
- package/dist/websocket.d.ts +6 -0
- package/dist/websocket.d.ts.map +1 -0
- package/dist/websocket.js +4 -0
- package/dist/workspaceEdit.d.ts +66 -0
- package/dist/workspaceEdit.d.ts.map +1 -0
- package/dist/workspaceEdit.js +276 -0
- package/dist/workspaceEdit.js.map +1 -0
- package/dist/workspaceEditProvenance.d.ts +5 -0
- package/dist/workspaceEditProvenance.d.ts.map +1 -0
- package/dist/workspaceEditProvenance.js +16 -0
- package/dist/workspaceEditProvenance.js.map +1 -0
- package/dist/workspaceTextEdits.d.ts +60 -0
- package/dist/workspaceTextEdits.d.ts.map +1 -0
- package/dist/workspaceTextEdits.js +395 -0
- package/dist/workspaceTextEdits.js.map +1 -0
- package/package.json +118 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { LANGUAGE_SERVER_FEATURE_IDS } from "./types.js";
|
|
2
|
+
//#region src/serverSet.ts
|
|
3
|
+
var LanguageServerSet = class {
|
|
4
|
+
#lanes;
|
|
5
|
+
#provenance = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
constructor(lanes) {
|
|
7
|
+
this.#lanes = lanes;
|
|
8
|
+
}
|
|
9
|
+
declared(feature) {
|
|
10
|
+
return rankedLanguageServerLanes(this.#lanes, feature);
|
|
11
|
+
}
|
|
12
|
+
ready(feature, method) {
|
|
13
|
+
return this.declared(feature).filter((lane) => {
|
|
14
|
+
if (!lane.connection.isReady()) return false;
|
|
15
|
+
return laneSupports(lane, feature, method);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
hasReady(feature, method) {
|
|
19
|
+
return this.ready(feature, method).length > 0;
|
|
20
|
+
}
|
|
21
|
+
canResolveCodeActions() {
|
|
22
|
+
return this.ready("codeActions").some((lane) => {
|
|
23
|
+
const provider = lane.connection.client.serverCapabilities?.codeActionProvider;
|
|
24
|
+
return isRecord(provider) && provider.resolveProvider === true;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
ownerOf(value) {
|
|
28
|
+
return this.provenanceOf(value)?.lane ?? null;
|
|
29
|
+
}
|
|
30
|
+
provenanceOf(value) {
|
|
31
|
+
if (!isRecord(value)) return null;
|
|
32
|
+
return this.#provenance.get(value) ?? null;
|
|
33
|
+
}
|
|
34
|
+
request(method, params, options = {}) {
|
|
35
|
+
if (method === "textDocument/hover") return this.requestHover(params, options);
|
|
36
|
+
if (method === "textDocument/codeAction") {
|
|
37
|
+
const lanes = this.ready("codeActions", method);
|
|
38
|
+
const lane = lanes.length === 1 ? lanes[0] : void 0;
|
|
39
|
+
if (lane) {
|
|
40
|
+
const provenance = codeActionProvenance(lane);
|
|
41
|
+
return this.requestSingle(lane, method, params, options, [], (result) => this.mergeArrayResults("codeActions", [{
|
|
42
|
+
lane,
|
|
43
|
+
provenance,
|
|
44
|
+
result
|
|
45
|
+
}]));
|
|
46
|
+
}
|
|
47
|
+
return this.mergedArray("codeActions", method, params, options);
|
|
48
|
+
}
|
|
49
|
+
if (method === "textDocument/documentHighlight") return this.requestFirst("documentHighlights", method, params, options, []);
|
|
50
|
+
if (method === "codeAction/resolve") return this.resolveCodeAction(params, options);
|
|
51
|
+
const feature = featureForMethod(method);
|
|
52
|
+
if (!feature) return Promise.resolve(null);
|
|
53
|
+
if (feature === "navigation") {
|
|
54
|
+
const lanes = this.ready(feature, method);
|
|
55
|
+
const lane = lanes.length === 1 ? lanes[0] : void 0;
|
|
56
|
+
if (lane) return this.requestSingle(lane, method, params, options, [], (result) => mergeNavigationResults([{
|
|
57
|
+
lane,
|
|
58
|
+
result
|
|
59
|
+
}]));
|
|
60
|
+
return this.requestAll(feature, method, params, options).then(mergeNavigationResults);
|
|
61
|
+
}
|
|
62
|
+
if (feature === "signatureHelp") return this.requestFirst(feature, method, params, options, null);
|
|
63
|
+
return this.requestOwner(feature, method, params, options, null);
|
|
64
|
+
}
|
|
65
|
+
async requestHover(params, options = {}, onUpdate) {
|
|
66
|
+
const lanes = this.ready("hover", "textDocument/hover");
|
|
67
|
+
if (lanes.length === 0) return null;
|
|
68
|
+
const results = lanes.map(() => null);
|
|
69
|
+
let settled = 0;
|
|
70
|
+
const requests = lanes.map((lane, index) => this.requestHoverLane(lane, params, options).then((result) => {
|
|
71
|
+
results[index] = {
|
|
72
|
+
lane,
|
|
73
|
+
result
|
|
74
|
+
};
|
|
75
|
+
settled += 1;
|
|
76
|
+
onUpdate?.({
|
|
77
|
+
hovers: hoverResults(results),
|
|
78
|
+
pending: settled < lanes.length
|
|
79
|
+
});
|
|
80
|
+
}));
|
|
81
|
+
await Promise.all(requests);
|
|
82
|
+
return mergedHover(hoverResults(results));
|
|
83
|
+
}
|
|
84
|
+
async requestHoverLane(lane, params, options) {
|
|
85
|
+
try {
|
|
86
|
+
const result = await lane.connection.client.request("textDocument/hover", params, options);
|
|
87
|
+
lane.onInteractiveReady?.();
|
|
88
|
+
return result;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (!isAbortError(error)) lane.onRequestError?.("textDocument/hover", error);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async mergedArray(feature, method, params, options) {
|
|
95
|
+
const results = await this.requestAll(feature, method, params, options);
|
|
96
|
+
return this.mergeArrayResults(feature, results);
|
|
97
|
+
}
|
|
98
|
+
mergeArrayResults(feature, results) {
|
|
99
|
+
const merged = [];
|
|
100
|
+
for (const { lane, provenance, result } of results) {
|
|
101
|
+
if (!Array.isArray(result)) continue;
|
|
102
|
+
if (!provenance) continue;
|
|
103
|
+
for (const item of result) {
|
|
104
|
+
if (!isRecord(item)) continue;
|
|
105
|
+
if (feature === "codeActions" && !usableCodeAction(item, lane)) continue;
|
|
106
|
+
this.#provenance.set(item, provenance);
|
|
107
|
+
merged.push(item);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return merged;
|
|
111
|
+
}
|
|
112
|
+
async resolveCodeAction(params, options) {
|
|
113
|
+
if (!isCodeAction(params)) return params ?? null;
|
|
114
|
+
return (await this.resolveOwnedCodeAction(params, options))?.action ?? params;
|
|
115
|
+
}
|
|
116
|
+
async resolveOwnedCodeAction(action, options) {
|
|
117
|
+
const current = this.provenanceOf(action);
|
|
118
|
+
if (!current) return null;
|
|
119
|
+
const result = await this.requestSingle(current.lane, "codeAction/resolve", action, options, null);
|
|
120
|
+
if (!isCodeAction(result)) return null;
|
|
121
|
+
this.#provenance.set(result, current);
|
|
122
|
+
return {
|
|
123
|
+
action: result,
|
|
124
|
+
...current
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
requestSingle(lane, method, params, options, fallback, transform = (result) => result) {
|
|
128
|
+
if (!lane) return Promise.resolve(fallback);
|
|
129
|
+
return lane.connection.client.request(method, params, options).then((result) => {
|
|
130
|
+
lane.onInteractiveReady?.();
|
|
131
|
+
return transform(result);
|
|
132
|
+
}, (error) => {
|
|
133
|
+
if (!isAbortError(error)) lane.onRequestError?.(method, error);
|
|
134
|
+
return fallback;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
async requestFirst(feature, method, params, options = {}, fallback, accepts = isNonNullResult) {
|
|
138
|
+
for (const lane of this.ready(feature, method)) {
|
|
139
|
+
const result = await this.requestLane(lane, method, params, options);
|
|
140
|
+
if (accepts(result)) return result;
|
|
141
|
+
}
|
|
142
|
+
return fallback;
|
|
143
|
+
}
|
|
144
|
+
requestOwner(feature, method, params, options = {}, fallback) {
|
|
145
|
+
return this.requestSingle(this.ready(feature, method)[0], method, params, options, fallback);
|
|
146
|
+
}
|
|
147
|
+
async requestAll(feature, method, params, options) {
|
|
148
|
+
const requests = this.ready(feature, method).map(async (lane) => {
|
|
149
|
+
try {
|
|
150
|
+
const provenance = feature === "codeActions" ? codeActionProvenance(lane) : void 0;
|
|
151
|
+
const result = await lane.connection.client.request(method, params, options);
|
|
152
|
+
lane.onInteractiveReady?.();
|
|
153
|
+
return {
|
|
154
|
+
lane,
|
|
155
|
+
provenance,
|
|
156
|
+
result
|
|
157
|
+
};
|
|
158
|
+
} catch (error) {
|
|
159
|
+
if (!isAbortError(error)) lane.onRequestError?.(method, error);
|
|
160
|
+
return {
|
|
161
|
+
lane,
|
|
162
|
+
result: null
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
return Promise.all(requests);
|
|
167
|
+
}
|
|
168
|
+
async requestLane(lane, method, params, options) {
|
|
169
|
+
try {
|
|
170
|
+
const result = await lane.connection.client.request(method, params, options);
|
|
171
|
+
lane.onInteractiveReady?.();
|
|
172
|
+
return result;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (!isAbortError(error)) lane.onRequestError?.(method, error);
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
function rankedLanguageServerLanes(lanes, feature) {
|
|
180
|
+
return lanes.filter((lane) => lane.features[feature] !== void 0).toSorted((left, right) => compareFeatureRank(left, right, feature, lanes));
|
|
181
|
+
}
|
|
182
|
+
function allLanguageServerFeatures(rank = 0) {
|
|
183
|
+
return Object.fromEntries(LANGUAGE_SERVER_FEATURE_IDS.map((feature) => [feature, rank]));
|
|
184
|
+
}
|
|
185
|
+
function laneSupports(lane, feature, method) {
|
|
186
|
+
const capabilities = lane.connection.client.serverCapabilities;
|
|
187
|
+
if (!capabilities) return false;
|
|
188
|
+
if (feature === "diagnostics") return true;
|
|
189
|
+
if (feature === "completion") return Boolean(capabilities.completionProvider);
|
|
190
|
+
if (feature === "hover") return Boolean(capabilities.hoverProvider);
|
|
191
|
+
if (feature === "navigation") return supportsNavigation(capabilities, method);
|
|
192
|
+
if (feature === "signatureHelp") return Boolean(capabilities.signatureHelpProvider);
|
|
193
|
+
if (feature === "codeActions") return Boolean(capabilities.codeActionProvider);
|
|
194
|
+
if (feature === "formatting") {
|
|
195
|
+
if (method === "textDocument/onTypeFormatting") return Boolean(capabilities.documentOnTypeFormattingProvider);
|
|
196
|
+
return Boolean(capabilities.documentFormattingProvider);
|
|
197
|
+
}
|
|
198
|
+
if (feature === "rename") return Boolean(capabilities.renameProvider);
|
|
199
|
+
if (feature === "documentHighlights") return Boolean(capabilities.documentHighlightProvider);
|
|
200
|
+
return Boolean(capabilities.semanticTokensProvider);
|
|
201
|
+
}
|
|
202
|
+
function supportsNavigation(capabilities, method) {
|
|
203
|
+
if (method === "textDocument/references") return Boolean(capabilities.referencesProvider);
|
|
204
|
+
if (method === "textDocument/implementation") return Boolean(capabilities.implementationProvider);
|
|
205
|
+
if (method === "textDocument/typeDefinition") return Boolean(capabilities.typeDefinitionProvider);
|
|
206
|
+
return Boolean(capabilities.definitionProvider);
|
|
207
|
+
}
|
|
208
|
+
function featureForMethod(method) {
|
|
209
|
+
if (method === "textDocument/signatureHelp") return "signatureHelp";
|
|
210
|
+
if (method === "textDocument/formatting") return "formatting";
|
|
211
|
+
if (method === "textDocument/onTypeFormatting") return "formatting";
|
|
212
|
+
if (method === "textDocument/rename") return "rename";
|
|
213
|
+
if (method === "textDocument/definition") return "navigation";
|
|
214
|
+
if (method === "textDocument/references") return "navigation";
|
|
215
|
+
if (method === "textDocument/implementation") return "navigation";
|
|
216
|
+
if (method === "textDocument/typeDefinition") return "navigation";
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
function compareFeatureRank(left, right, feature, lanes) {
|
|
220
|
+
const rank = (left.features[feature] ?? 0) - (right.features[feature] ?? 0);
|
|
221
|
+
if (rank !== 0) return rank;
|
|
222
|
+
return lanes.indexOf(left) - lanes.indexOf(right);
|
|
223
|
+
}
|
|
224
|
+
function usableCodeAction(item, lane) {
|
|
225
|
+
if (item.edit !== void 0) return true;
|
|
226
|
+
if (typeof item.command === "string") return true;
|
|
227
|
+
const provider = lane.connection.client.serverCapabilities?.codeActionProvider;
|
|
228
|
+
return isRecord(provider) && provider.resolveProvider === true;
|
|
229
|
+
}
|
|
230
|
+
function isHover(value) {
|
|
231
|
+
return isRecord(value) && value.contents !== void 0;
|
|
232
|
+
}
|
|
233
|
+
function hoverResults(results) {
|
|
234
|
+
const hovers = [];
|
|
235
|
+
const seen = /* @__PURE__ */ new Set();
|
|
236
|
+
for (const entry of results) {
|
|
237
|
+
if (!entry || !isHover(entry.result)) continue;
|
|
238
|
+
const text = hoverContentsText(entry.result.contents).trim();
|
|
239
|
+
if (text.length === 0 || seen.has(text)) continue;
|
|
240
|
+
seen.add(text);
|
|
241
|
+
hovers.push(entry.result);
|
|
242
|
+
}
|
|
243
|
+
return hovers;
|
|
244
|
+
}
|
|
245
|
+
function mergeNavigationResults(results) {
|
|
246
|
+
const merged = [];
|
|
247
|
+
const seen = /* @__PURE__ */ new Set();
|
|
248
|
+
for (const entry of results) {
|
|
249
|
+
const items = Array.isArray(entry.result) ? entry.result : [entry.result];
|
|
250
|
+
for (const item of items) {
|
|
251
|
+
const key = navigationResultKey(item);
|
|
252
|
+
if (!key || seen.has(key)) continue;
|
|
253
|
+
seen.add(key);
|
|
254
|
+
merged.push(item);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return merged;
|
|
258
|
+
}
|
|
259
|
+
function navigationResultKey(value) {
|
|
260
|
+
if (!isRecord(value)) return null;
|
|
261
|
+
if (typeof value.uri === "string" && isRecord(value.range)) return `${value.uri}:${JSON.stringify(value.range)}`;
|
|
262
|
+
if (typeof value.targetUri === "string" && isRecord(value.targetSelectionRange)) return `${value.targetUri}:${JSON.stringify(value.targetSelectionRange)}`;
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
function isNonNullResult(result) {
|
|
266
|
+
return result !== null && result !== void 0;
|
|
267
|
+
}
|
|
268
|
+
function isCodeAction(value) {
|
|
269
|
+
if (!isRecord(value)) return false;
|
|
270
|
+
if (typeof value.title !== "string") return false;
|
|
271
|
+
return typeof value.command !== "string";
|
|
272
|
+
}
|
|
273
|
+
function mergedHover(hovers) {
|
|
274
|
+
if (hovers.length === 0) return null;
|
|
275
|
+
return {
|
|
276
|
+
contents: {
|
|
277
|
+
kind: "markdown",
|
|
278
|
+
value: hovers.map((hover) => hoverContentsText(hover.contents)).join("\n\n---\n\n")
|
|
279
|
+
},
|
|
280
|
+
range: hovers.find((hover) => hover.range)?.range
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function hoverContentsText(contents) {
|
|
284
|
+
if (typeof contents === "string") return contents;
|
|
285
|
+
if (Array.isArray(contents)) return contents.map(markedStringText).join("\n\n");
|
|
286
|
+
if ("kind" in contents) return contents.value;
|
|
287
|
+
return markedStringText(contents);
|
|
288
|
+
}
|
|
289
|
+
function markedStringText(value) {
|
|
290
|
+
if (typeof value === "string") return value;
|
|
291
|
+
return [
|
|
292
|
+
"```" + value.language,
|
|
293
|
+
value.value,
|
|
294
|
+
"```"
|
|
295
|
+
].join("\n");
|
|
296
|
+
}
|
|
297
|
+
function isAbortError(error) {
|
|
298
|
+
if (error instanceof DOMException && error.name === "AbortError") return true;
|
|
299
|
+
return isRecord(error) && error.name === "LspRequestCancelledError";
|
|
300
|
+
}
|
|
301
|
+
function codeActionProvenance(lane) {
|
|
302
|
+
return {
|
|
303
|
+
guard: captureWorkspaceEditOriginGuard(lane.connection.workspace),
|
|
304
|
+
lane
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function captureWorkspaceEditOriginGuard(workspace) {
|
|
308
|
+
const documents = Object.freeze(workspace.documents.map((document) => Object.freeze({
|
|
309
|
+
textSnapshot: document.textSnapshot,
|
|
310
|
+
uri: document.uri,
|
|
311
|
+
version: document.version
|
|
312
|
+
})));
|
|
313
|
+
const documentsByUri = new Map(documents.map((document) => [document.uri, document]));
|
|
314
|
+
return Object.freeze({
|
|
315
|
+
documents,
|
|
316
|
+
isCurrent(uri) {
|
|
317
|
+
const captured = documentsByUri.get(uri);
|
|
318
|
+
if (!captured) return false;
|
|
319
|
+
const current = workspace.getDocument(uri);
|
|
320
|
+
if (!current) return false;
|
|
321
|
+
return current.uri === captured.uri && current.version === captured.version && current.textSnapshot === captured.textSnapshot;
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
function isRecord(value) {
|
|
326
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
327
|
+
}
|
|
328
|
+
//#endregion
|
|
329
|
+
export { LanguageServerSet, allLanguageServerFeatures, captureWorkspaceEditOriginGuard, laneSupports, rankedLanguageServerLanes };
|
|
330
|
+
|
|
331
|
+
//# sourceMappingURL=serverSet.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serverSet.js","names":[],"sources":["../src/serverSet.ts"],"sourcesContent":["import type { LspRequestOptions, LspWorkspace } from '@singapore-editor/lsp'\nimport type * as lsp from 'vscode-languageserver-protocol'\n\nimport type { AcquiredLanguageServerLane } from './lane'\nimport {\n LANGUAGE_SERVER_FEATURE_IDS,\n type LanguageServerFeatureId,\n type LanguageServerFeatureRanks,\n type OnApplyWorkspaceEdit,\n type WorkspaceEditOriginGuard,\n type WorkspaceTextDocumentProvenance,\n} from './types'\n\nexport type LanguageServerSetLane = {\n readonly id: string\n readonly features: LanguageServerFeatureRanks\n readonly connection: AcquiredLanguageServerLane\n readonly onInteractiveReady?: () => void\n readonly onApplyWorkspaceEdit?: OnApplyWorkspaceEdit\n readonly onRequestError?: (method: string, error: unknown) => void\n}\n\nexport type LanguageServerLaneResult = {\n readonly lane: LanguageServerSetLane\n readonly provenance?: LanguageServerCodeActionProvenance\n readonly result: unknown\n}\n\nexport type LanguageServerHoverUpdate = {\n /** Hover answers in feature-rank order, regardless of the order in which they resolved. */\n readonly hovers: readonly lsp.Hover[]\n /** True until every ready hover lane has either answered or failed. */\n readonly pending: boolean\n}\n\nexport type LanguageServerFeatureRouter = {\n canResolveCodeActions(): boolean\n hasReady(feature: LanguageServerFeatureId, method?: string): boolean\n request<TResult = unknown, TParams = unknown>(\n method: string,\n params?: TParams,\n options?: LspRequestOptions,\n ): Promise<TResult>\n}\n\nexport type LanguageServerCodeActionRouter = LanguageServerFeatureRouter & {\n ownerOf(value: unknown): LanguageServerSetLane | null\n provenanceOf(value: unknown): LanguageServerCodeActionProvenance | null\n resolveOwnedCodeAction(\n action: lsp.CodeAction,\n options: LspRequestOptions,\n ): Promise<LanguageServerOwnedCodeAction | null>\n}\n\nexport type LanguageServerCodeActionProvenance = {\n readonly guard: WorkspaceEditOriginGuard\n readonly lane: LanguageServerSetLane\n}\n\nexport type LanguageServerOwnedCodeAction = LanguageServerCodeActionProvenance & {\n readonly action: lsp.CodeAction\n}\n\nexport class LanguageServerSet {\n readonly #lanes: readonly LanguageServerSetLane[]\n readonly #provenance = new WeakMap<object, LanguageServerCodeActionProvenance>()\n\n public constructor(lanes: readonly LanguageServerSetLane[]) {\n this.#lanes = lanes\n }\n\n public declared(feature: LanguageServerFeatureId): readonly LanguageServerSetLane[] {\n return rankedLanguageServerLanes(this.#lanes, feature)\n }\n\n public ready(\n feature: LanguageServerFeatureId,\n method?: string,\n ): readonly LanguageServerSetLane[] {\n return this.declared(feature).filter((lane) => {\n if (!lane.connection.isReady()) return false\n return laneSupports(lane, feature, method)\n })\n }\n\n public hasReady(feature: LanguageServerFeatureId, method?: string): boolean {\n return this.ready(feature, method).length > 0\n }\n\n public canResolveCodeActions(): boolean {\n return this.ready('codeActions').some((lane) => {\n const provider = lane.connection.client.serverCapabilities?.codeActionProvider\n return isRecord(provider) && provider.resolveProvider === true\n })\n }\n\n public ownerOf(value: unknown): LanguageServerSetLane | null {\n return this.provenanceOf(value)?.lane ?? null\n }\n\n public provenanceOf(value: unknown): LanguageServerCodeActionProvenance | null {\n if (!isRecord(value)) return null\n return this.#provenance.get(value) ?? null\n }\n\n public request<TResult = unknown, TParams = unknown>(\n method: string,\n params?: TParams,\n options: LspRequestOptions = {},\n ): Promise<TResult> {\n if (method === 'textDocument/hover') {\n return this.requestHover(params, options) as Promise<TResult>\n }\n if (method === 'textDocument/codeAction') {\n const lanes = this.ready('codeActions', method)\n const lane = lanes.length === 1 ? lanes[0] : undefined\n if (lane) {\n const provenance = codeActionProvenance(lane)\n return this.requestSingle(lane, method, params, options, [], (result) =>\n this.mergeArrayResults('codeActions', [{ lane, provenance, result }]),\n ) as Promise<TResult>\n }\n\n return this.mergedArray('codeActions', method, params, options) as Promise<TResult>\n }\n if (method === 'textDocument/documentHighlight') {\n return this.requestFirst(\n 'documentHighlights',\n method,\n params,\n options,\n [],\n ) as Promise<TResult>\n }\n if (method === 'codeAction/resolve')\n return this.resolveCodeAction(params, options) as Promise<TResult>\n\n const feature = featureForMethod(method)\n if (!feature) return Promise.resolve(null as TResult)\n\n if (feature === 'navigation') {\n const lanes = this.ready(feature, method)\n const lane = lanes.length === 1 ? lanes[0] : undefined\n if (lane) {\n return this.requestSingle(lane, method, params, options, [], (result) =>\n mergeNavigationResults([{ lane, result }]),\n ) as Promise<TResult>\n }\n\n return this.requestAll(feature, method, params, options).then(\n mergeNavigationResults,\n ) as Promise<TResult>\n }\n if (feature === 'signatureHelp') {\n return this.requestFirst(feature, method, params, options, null) as Promise<TResult>\n }\n\n return this.requestOwner(feature, method, params, options, null) as Promise<TResult>\n }\n\n public async requestHover<TParams>(\n params: TParams | undefined,\n options: LspRequestOptions = {},\n onUpdate?: (update: LanguageServerHoverUpdate) => void,\n ): Promise<lsp.Hover | null> {\n const lanes = this.ready('hover', 'textDocument/hover')\n if (lanes.length === 0) return null\n\n const results: Array<LanguageServerLaneResult | null> = lanes.map(() => null)\n let settled = 0\n const requests = lanes.map((lane, index) =>\n this.requestHoverLane(lane, params, options).then((result) => {\n results[index] = { lane, result }\n settled += 1\n onUpdate?.({ hovers: hoverResults(results), pending: settled < lanes.length })\n }),\n )\n await Promise.all(requests)\n return mergedHover(hoverResults(results))\n }\n\n private async requestHoverLane<TParams>(\n lane: LanguageServerSetLane,\n params: TParams | undefined,\n options: LspRequestOptions,\n ): Promise<unknown> {\n try {\n const result = await lane.connection.client.request('textDocument/hover', params, options)\n lane.onInteractiveReady?.()\n return result\n } catch (error) {\n if (!isAbortError(error)) lane.onRequestError?.('textDocument/hover', error)\n return null\n }\n }\n\n async mergedArray<TParams>(\n feature: 'codeActions',\n method: string,\n params: TParams | undefined,\n options: LspRequestOptions,\n ) {\n const results = await this.requestAll(feature, method, params, options)\n return this.mergeArrayResults(feature, results)\n }\n\n mergeArrayResults(feature: 'codeActions', results: readonly LanguageServerLaneResult[]) {\n const merged: unknown[] = []\n for (const { lane, provenance, result } of results) {\n if (!Array.isArray(result)) continue\n if (!provenance) continue\n\n for (const item of result) {\n if (!isRecord(item)) continue\n if (feature === 'codeActions' && !usableCodeAction(item, lane)) continue\n\n this.#provenance.set(item, provenance)\n merged.push(item)\n }\n }\n\n return merged\n }\n\n async resolveCodeAction<TParams>(params: TParams | undefined, options: LspRequestOptions) {\n if (!isCodeAction(params)) return params ?? null\n\n const resolved = await this.resolveOwnedCodeAction(params, options)\n return resolved?.action ?? params\n }\n\n public async resolveOwnedCodeAction(\n action: lsp.CodeAction,\n options: LspRequestOptions,\n ): Promise<LanguageServerOwnedCodeAction | null> {\n const current = this.provenanceOf(action)\n if (!current) return null\n\n const result = await this.requestSingle(\n current.lane,\n 'codeAction/resolve',\n action,\n options,\n null,\n )\n if (!isCodeAction(result)) return null\n\n this.#provenance.set(result, current)\n return { action: result, ...current }\n }\n\n requestSingle<TParams, TResult = unknown>(\n lane: LanguageServerSetLane | undefined,\n method: string,\n params: TParams | undefined,\n options: LspRequestOptions,\n fallback: TResult,\n transform: (result: unknown) => TResult = (result) => result as TResult,\n ): Promise<TResult> {\n if (!lane) return Promise.resolve(fallback)\n\n return lane.connection.client.request(method, params, options).then(\n (result) => {\n lane.onInteractiveReady?.()\n return transform(result)\n },\n (error) => {\n if (!isAbortError(error)) lane.onRequestError?.(method, error)\n return fallback\n },\n )\n }\n\n public async requestFirst<TParams, TResult = unknown>(\n feature: LanguageServerFeatureId,\n method: string,\n params: TParams | undefined,\n options: LspRequestOptions = {},\n fallback: TResult,\n accepts: (result: unknown) => boolean = isNonNullResult,\n ): Promise<TResult> {\n for (const lane of this.ready(feature, method)) {\n const result = await this.requestLane(lane, method, params, options)\n if (accepts(result)) return result as TResult\n }\n\n return fallback\n }\n\n public requestOwner<TParams, TResult = unknown>(\n feature: LanguageServerFeatureId,\n method: string,\n params: TParams | undefined,\n options: LspRequestOptions = {},\n fallback: TResult,\n ): Promise<TResult> {\n return this.requestSingle(this.ready(feature, method)[0], method, params, options, fallback)\n }\n\n async requestAll<TParams>(\n feature: LanguageServerFeatureId,\n method: string,\n params: TParams | undefined,\n options: LspRequestOptions,\n ): Promise<readonly LanguageServerLaneResult[]> {\n const requests = this.ready(feature, method).map(async (lane) => {\n try {\n const provenance = feature === 'codeActions' ? codeActionProvenance(lane) : undefined\n const result = await lane.connection.client.request(method, params, options)\n lane.onInteractiveReady?.()\n return { lane, provenance, result }\n } catch (error) {\n if (!isAbortError(error)) lane.onRequestError?.(method, error)\n return { lane, result: null }\n }\n })\n\n return Promise.all(requests)\n }\n\n private async requestLane<TParams>(\n lane: LanguageServerSetLane,\n method: string,\n params: TParams | undefined,\n options: LspRequestOptions,\n ): Promise<unknown> {\n try {\n const result = await lane.connection.client.request(method, params, options)\n lane.onInteractiveReady?.()\n return result\n } catch (error) {\n if (!isAbortError(error)) lane.onRequestError?.(method, error)\n return null\n }\n }\n}\n\nexport function rankedLanguageServerLanes<\n TLane extends { readonly features: LanguageServerFeatureRanks },\n>(lanes: readonly TLane[], feature: LanguageServerFeatureId): readonly TLane[] {\n return lanes\n .filter((lane) => lane.features[feature] !== undefined)\n .toSorted((left, right) => compareFeatureRank(left, right, feature, lanes))\n}\n\nexport function allLanguageServerFeatures(rank = 0): LanguageServerFeatureRanks {\n return Object.fromEntries(LANGUAGE_SERVER_FEATURE_IDS.map((feature) => [feature, rank]))\n}\n\nexport function laneSupports(\n lane: LanguageServerSetLane,\n feature: LanguageServerFeatureId,\n method?: string,\n): boolean {\n const capabilities = lane.connection.client.serverCapabilities\n if (!capabilities) return false\n if (feature === 'diagnostics') return true\n if (feature === 'completion') return Boolean(capabilities.completionProvider)\n if (feature === 'hover') return Boolean(capabilities.hoverProvider)\n if (feature === 'navigation') return supportsNavigation(capabilities, method)\n if (feature === 'signatureHelp') return Boolean(capabilities.signatureHelpProvider)\n if (feature === 'codeActions') return Boolean(capabilities.codeActionProvider)\n if (feature === 'formatting') {\n if (method === 'textDocument/onTypeFormatting') {\n return Boolean(capabilities.documentOnTypeFormattingProvider)\n }\n\n return Boolean(capabilities.documentFormattingProvider)\n }\n if (feature === 'rename') return Boolean(capabilities.renameProvider)\n if (feature === 'documentHighlights') return Boolean(capabilities.documentHighlightProvider)\n return Boolean(capabilities.semanticTokensProvider)\n}\n\nfunction supportsNavigation(capabilities: lsp.ServerCapabilities, method?: string): boolean {\n if (method === 'textDocument/references') return Boolean(capabilities.referencesProvider)\n if (method === 'textDocument/implementation') return Boolean(capabilities.implementationProvider)\n if (method === 'textDocument/typeDefinition') return Boolean(capabilities.typeDefinitionProvider)\n return Boolean(capabilities.definitionProvider)\n}\n\nfunction featureForMethod(method: string): LanguageServerFeatureId | null {\n if (method === 'textDocument/signatureHelp') return 'signatureHelp'\n if (method === 'textDocument/formatting') return 'formatting'\n if (method === 'textDocument/onTypeFormatting') return 'formatting'\n if (method === 'textDocument/rename') return 'rename'\n if (method === 'textDocument/definition') return 'navigation'\n if (method === 'textDocument/references') return 'navigation'\n if (method === 'textDocument/implementation') return 'navigation'\n if (method === 'textDocument/typeDefinition') return 'navigation'\n return null\n}\n\nfunction compareFeatureRank(\n left: { readonly features: LanguageServerFeatureRanks },\n right: { readonly features: LanguageServerFeatureRanks },\n feature: LanguageServerFeatureId,\n lanes: readonly { readonly features: LanguageServerFeatureRanks }[],\n): number {\n const rank = (left.features[feature] ?? 0) - (right.features[feature] ?? 0)\n if (rank !== 0) return rank\n\n return lanes.indexOf(left) - lanes.indexOf(right)\n}\n\nfunction usableCodeAction(item: Record<string, unknown>, lane: LanguageServerSetLane): boolean {\n if (item.edit !== undefined) return true\n if (typeof item.command === 'string') return true\n\n const provider = lane.connection.client.serverCapabilities?.codeActionProvider\n return isRecord(provider) && provider.resolveProvider === true\n}\n\nfunction isHover(value: unknown): value is lsp.Hover {\n return isRecord(value) && value.contents !== undefined\n}\n\nfunction hoverResults(results: readonly (LanguageServerLaneResult | null)[]): readonly lsp.Hover[] {\n const hovers: lsp.Hover[] = []\n const seen = new Set<string>()\n for (const entry of results) {\n if (!entry || !isHover(entry.result)) continue\n\n const text = hoverContentsText(entry.result.contents).trim()\n if (text.length === 0 || seen.has(text)) continue\n\n seen.add(text)\n hovers.push(entry.result)\n }\n\n return hovers\n}\n\nfunction mergeNavigationResults(results: readonly LanguageServerLaneResult[]): readonly unknown[] {\n const merged: unknown[] = []\n const seen = new Set<string>()\n for (const entry of results) {\n const items = Array.isArray(entry.result) ? entry.result : [entry.result]\n for (const item of items) {\n const key = navigationResultKey(item)\n if (!key || seen.has(key)) continue\n\n seen.add(key)\n merged.push(item)\n }\n }\n\n return merged\n}\n\nfunction navigationResultKey(value: unknown): string | null {\n if (!isRecord(value)) return null\n if (typeof value.uri === 'string' && isRecord(value.range)) {\n return `${value.uri}:${JSON.stringify(value.range)}`\n }\n if (typeof value.targetUri === 'string' && isRecord(value.targetSelectionRange)) {\n return `${value.targetUri}:${JSON.stringify(value.targetSelectionRange)}`\n }\n\n return null\n}\n\nfunction isNonNullResult(result: unknown): boolean {\n return result !== null && result !== undefined\n}\n\nfunction isCodeAction(value: unknown): value is lsp.CodeAction {\n if (!isRecord(value)) return false\n if (typeof value.title !== 'string') return false\n return typeof value.command !== 'string'\n}\n\nfunction mergedHover(hovers: readonly lsp.Hover[]): lsp.Hover | null {\n if (hovers.length === 0) return null\n\n return {\n contents: {\n kind: 'markdown',\n value: hovers.map((hover) => hoverContentsText(hover.contents)).join('\\n\\n---\\n\\n'),\n },\n range: hovers.find((hover) => hover.range)?.range,\n }\n}\n\nfunction hoverContentsText(contents: lsp.Hover['contents']): string {\n if (typeof contents === 'string') return contents\n if (Array.isArray(contents)) return contents.map(markedStringText).join('\\n\\n')\n if ('kind' in contents) return contents.value\n return markedStringText(contents)\n}\n\nfunction markedStringText(value: lsp.MarkedString): string {\n if (typeof value === 'string') return value\n return ['```' + value.language, value.value, '```'].join('\\n')\n}\n\nfunction isAbortError(error: unknown): boolean {\n if (error instanceof DOMException && error.name === 'AbortError') return true\n return isRecord(error) && error.name === 'LspRequestCancelledError'\n}\n\nfunction codeActionProvenance(lane: LanguageServerSetLane): LanguageServerCodeActionProvenance {\n return {\n guard: captureWorkspaceEditOriginGuard(lane.connection.workspace),\n lane,\n }\n}\n\nexport function captureWorkspaceEditOriginGuard(workspace: LspWorkspace): WorkspaceEditOriginGuard {\n const documents: readonly WorkspaceTextDocumentProvenance[] = Object.freeze(\n workspace.documents.map((document) =>\n Object.freeze({\n textSnapshot: document.textSnapshot,\n uri: document.uri,\n version: document.version,\n }),\n ),\n )\n const documentsByUri = new Map(documents.map((document) => [document.uri, document]))\n\n return Object.freeze({\n documents,\n isCurrent(uri: string) {\n const captured = documentsByUri.get(uri)\n if (!captured) return false\n\n const current = workspace.getDocument(uri)\n if (!current) return false\n return (\n current.uri === captured.uri &&\n current.version === captured.version &&\n current.textSnapshot === captured.textSnapshot\n )\n },\n })\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type * as lsp from 'vscode-languageserver-protocol';
|
|
2
|
+
/**
|
|
3
|
+
* What a typed character means for the signature widget.
|
|
4
|
+
*
|
|
5
|
+
* `open` and `argument` ask the server for help; `close` dismisses, because the call the widget was
|
|
6
|
+
* describing has ended. Anything else leaves an open widget alone, so typing an argument does not
|
|
7
|
+
* tear down the signature it belongs to.
|
|
8
|
+
*/
|
|
9
|
+
export type SignatureHelpTrigger = {
|
|
10
|
+
readonly kind: 'open';
|
|
11
|
+
readonly triggerCharacter: '(';
|
|
12
|
+
} | {
|
|
13
|
+
readonly kind: 'argument';
|
|
14
|
+
readonly triggerCharacter: ',';
|
|
15
|
+
} | {
|
|
16
|
+
readonly kind: 'close';
|
|
17
|
+
};
|
|
18
|
+
export type SignatureHelpDisplay = {
|
|
19
|
+
/** Markdown for the tooltip: the active signature, with its active parameter emphasized. */
|
|
20
|
+
readonly markdown: string;
|
|
21
|
+
/** 0-based index of the signature shown, for overload cycling. */
|
|
22
|
+
readonly activeSignature: number;
|
|
23
|
+
readonly signatureCount: number;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Read from the keystroke, never from the edit it caused. Auto-closing writes a typed `(` as the
|
|
27
|
+
* two-character `()`, and typing over the closer it inserted moves the caret without changing any
|
|
28
|
+
* text, so an edit-derived trigger misses both of the characters that matter.
|
|
29
|
+
*/
|
|
30
|
+
export declare function signatureHelpTriggerFromTypedText(text: string): SignatureHelpTrigger | null;
|
|
31
|
+
/**
|
|
32
|
+
* Formats a server response for the tooltip, or null when there is nothing worth showing.
|
|
33
|
+
*
|
|
34
|
+
* `activeSignature` and `activeParameter` are optional in the protocol and some servers omit them
|
|
35
|
+
* or send an out-of-range index, so both are clamped rather than trusted.
|
|
36
|
+
*/
|
|
37
|
+
export declare function formatSignatureHelp(help: lsp.SignatureHelp | null | undefined, overrideSignature?: number): SignatureHelpDisplay | null;
|
|
38
|
+
/** Next signature for overload cycling, wrapping at both ends. */
|
|
39
|
+
export declare function nextSignatureIndex(current: number, count: number, delta: number): number;
|
|
40
|
+
//# sourceMappingURL=signatureHelp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signatureHelp.d.ts","sourceRoot":"","sources":["../src/signatureHelp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,GAAG,MAAM,gCAAgC,CAAA;AAE1D;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,gBAAgB,EAAE,GAAG,CAAA;CAAE,GACzD;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,gBAAgB,EAAE,GAAG,CAAA;CAAE,GAC7D;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAA;AAE9B,MAAM,MAAM,oBAAoB,GAAG;IACjC,4FAA4F;IAC5F,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,kEAAkE;IAClE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;CAChC,CAAA;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAM3F;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,GAAG,CAAC,aAAa,GAAG,IAAI,GAAG,SAAS,EAC1C,iBAAiB,CAAC,EAAE,MAAM,GACzB,oBAAoB,GAAG,IAAI,CAkB7B;AAmFD,kEAAkE;AAClE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAIxF"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
//#region src/signatureHelp.ts
|
|
2
|
+
/**
|
|
3
|
+
* Read from the keystroke, never from the edit it caused. Auto-closing writes a typed `(` as the
|
|
4
|
+
* two-character `()`, and typing over the closer it inserted moves the caret without changing any
|
|
5
|
+
* text, so an edit-derived trigger misses both of the characters that matter.
|
|
6
|
+
*/
|
|
7
|
+
function signatureHelpTriggerFromTypedText(text) {
|
|
8
|
+
if (text === "(") return {
|
|
9
|
+
kind: "open",
|
|
10
|
+
triggerCharacter: "("
|
|
11
|
+
};
|
|
12
|
+
if (text === ",") return {
|
|
13
|
+
kind: "argument",
|
|
14
|
+
triggerCharacter: ","
|
|
15
|
+
};
|
|
16
|
+
if (text === ")") return { kind: "close" };
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Formats a server response for the tooltip, or null when there is nothing worth showing.
|
|
21
|
+
*
|
|
22
|
+
* `activeSignature` and `activeParameter` are optional in the protocol and some servers omit them
|
|
23
|
+
* or send an out-of-range index, so both are clamped rather than trusted.
|
|
24
|
+
*/
|
|
25
|
+
function formatSignatureHelp(help, overrideSignature) {
|
|
26
|
+
const signatures = help?.signatures ?? [];
|
|
27
|
+
if (signatures.length === 0) return null;
|
|
28
|
+
const activeSignature = clampIndex(overrideSignature ?? help?.activeSignature ?? 0, signatures.length);
|
|
29
|
+
const signature = signatures[activeSignature];
|
|
30
|
+
if (!signature) return null;
|
|
31
|
+
const label = emphasizeActiveParameter(signature, signature.activeParameter ?? help?.activeParameter ?? null);
|
|
32
|
+
const documentation = documentationText(signature.documentation);
|
|
33
|
+
return {
|
|
34
|
+
activeSignature,
|
|
35
|
+
markdown: documentation ? `${label}\n\n${documentation}` : label,
|
|
36
|
+
signatureCount: signatures.length
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Wraps the active parameter in bold. A parameter label is either a substring of the signature or
|
|
41
|
+
* an explicit `[start, end]` range into it; the range form is authoritative because a name like
|
|
42
|
+
* `x` would otherwise match the first `x` anywhere in the signature.
|
|
43
|
+
*/
|
|
44
|
+
function emphasizeActiveParameter(signature, activeParameter) {
|
|
45
|
+
const label = signature.label;
|
|
46
|
+
const parameters = signature.parameters ?? [];
|
|
47
|
+
if (activeParameter === null) return label;
|
|
48
|
+
const parameter = parameters[clampIndex(activeParameter, parameters.length)];
|
|
49
|
+
if (!parameter) return label;
|
|
50
|
+
const range = parameterRange(label, parameter.label);
|
|
51
|
+
if (!range) return label;
|
|
52
|
+
return `${label.slice(0, range.start)}**${label.slice(range.start, range.end)}**${label.slice(range.end)}`;
|
|
53
|
+
}
|
|
54
|
+
function parameterRange(label, parameterLabel) {
|
|
55
|
+
if (Array.isArray(parameterLabel)) {
|
|
56
|
+
const [start, end] = parameterLabel;
|
|
57
|
+
if (start < 0 || end > label.length || start >= end) return null;
|
|
58
|
+
return {
|
|
59
|
+
end,
|
|
60
|
+
start
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const start = findTokenBoundedIndex(label, parameterLabel);
|
|
64
|
+
if (start === -1) return null;
|
|
65
|
+
return {
|
|
66
|
+
end: start + parameterLabel.length,
|
|
67
|
+
start
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* First occurrence of `needle` that is not part of a longer identifier — a plain indexOf for the
|
|
72
|
+
* parameter `a` in `add(a)` finds the `a` of "add".
|
|
73
|
+
*/
|
|
74
|
+
function findTokenBoundedIndex(label, needle) {
|
|
75
|
+
if (needle.length === 0) return -1;
|
|
76
|
+
let index = label.indexOf(needle);
|
|
77
|
+
while (index !== -1) {
|
|
78
|
+
const before = label[index - 1];
|
|
79
|
+
const after = label[index + needle.length];
|
|
80
|
+
if (!isIdentifierChar(before) && !isIdentifierChar(after)) return index;
|
|
81
|
+
index = label.indexOf(needle, index + 1);
|
|
82
|
+
}
|
|
83
|
+
return -1;
|
|
84
|
+
}
|
|
85
|
+
function isIdentifierChar(char) {
|
|
86
|
+
if (char === void 0) return false;
|
|
87
|
+
return /[A-Za-z0-9_$]/.test(char);
|
|
88
|
+
}
|
|
89
|
+
function documentationText(documentation) {
|
|
90
|
+
if (!documentation) return null;
|
|
91
|
+
if (typeof documentation === "string") return documentation.trim() || null;
|
|
92
|
+
return documentation.value.trim() || null;
|
|
93
|
+
}
|
|
94
|
+
/** Keeps an index inside `[0, length)`; servers do send out-of-range values. */
|
|
95
|
+
function clampIndex(index, length) {
|
|
96
|
+
if (!Number.isFinite(index) || index < 0) return 0;
|
|
97
|
+
if (index >= length) return Math.max(0, length - 1);
|
|
98
|
+
return Math.trunc(index);
|
|
99
|
+
}
|
|
100
|
+
/** Next signature for overload cycling, wrapping at both ends. */
|
|
101
|
+
function nextSignatureIndex(current, count, delta) {
|
|
102
|
+
if (count <= 0) return 0;
|
|
103
|
+
return ((current + delta) % count + count) % count;
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
export { formatSignatureHelp, nextSignatureIndex, signatureHelpTriggerFromTypedText };
|
|
107
|
+
|
|
108
|
+
//# sourceMappingURL=signatureHelp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signatureHelp.js","names":[],"sources":["../src/signatureHelp.ts"],"sourcesContent":["import type * as lsp from 'vscode-languageserver-protocol'\n\n/**\n * What a typed character means for the signature widget.\n *\n * `open` and `argument` ask the server for help; `close` dismisses, because the call the widget was\n * describing has ended. Anything else leaves an open widget alone, so typing an argument does not\n * tear down the signature it belongs to.\n */\nexport type SignatureHelpTrigger =\n | { readonly kind: 'open'; readonly triggerCharacter: '(' }\n | { readonly kind: 'argument'; readonly triggerCharacter: ',' }\n | { readonly kind: 'close' }\n\nexport type SignatureHelpDisplay = {\n /** Markdown for the tooltip: the active signature, with its active parameter emphasized. */\n readonly markdown: string\n /** 0-based index of the signature shown, for overload cycling. */\n readonly activeSignature: number\n readonly signatureCount: number\n}\n\n/**\n * Read from the keystroke, never from the edit it caused. Auto-closing writes a typed `(` as the\n * two-character `()`, and typing over the closer it inserted moves the caret without changing any\n * text, so an edit-derived trigger misses both of the characters that matter.\n */\nexport function signatureHelpTriggerFromTypedText(text: string): SignatureHelpTrigger | null {\n if (text === '(') return { kind: 'open', triggerCharacter: '(' }\n if (text === ',') return { kind: 'argument', triggerCharacter: ',' }\n if (text === ')') return { kind: 'close' }\n\n return null\n}\n\n/**\n * Formats a server response for the tooltip, or null when there is nothing worth showing.\n *\n * `activeSignature` and `activeParameter` are optional in the protocol and some servers omit them\n * or send an out-of-range index, so both are clamped rather than trusted.\n */\nexport function formatSignatureHelp(\n help: lsp.SignatureHelp | null | undefined,\n overrideSignature?: number,\n): SignatureHelpDisplay | null {\n const signatures = help?.signatures ?? []\n if (signatures.length === 0) return null\n\n const requested = overrideSignature ?? help?.activeSignature ?? 0\n const activeSignature = clampIndex(requested, signatures.length)\n const signature = signatures[activeSignature]\n if (!signature) return null\n\n const activeParameter = signature.activeParameter ?? help?.activeParameter ?? null\n const label = emphasizeActiveParameter(signature, activeParameter)\n const documentation = documentationText(signature.documentation)\n\n return {\n activeSignature,\n markdown: documentation ? `${label}\\n\\n${documentation}` : label,\n signatureCount: signatures.length,\n }\n}\n\n/**\n * Wraps the active parameter in bold. A parameter label is either a substring of the signature or\n * an explicit `[start, end]` range into it; the range form is authoritative because a name like\n * `x` would otherwise match the first `x` anywhere in the signature.\n */\nfunction emphasizeActiveParameter(\n signature: lsp.SignatureInformation,\n activeParameter: number | null,\n): string {\n const label = signature.label\n const parameters = signature.parameters ?? []\n if (activeParameter === null) return label\n\n const parameter = parameters[clampIndex(activeParameter, parameters.length)]\n if (!parameter) return label\n\n const range = parameterRange(label, parameter.label)\n if (!range) return label\n\n return `${label.slice(0, range.start)}**${label.slice(range.start, range.end)}**${label.slice(range.end)}`\n}\n\nfunction parameterRange(\n label: string,\n parameterLabel: lsp.ParameterInformation['label'],\n): { readonly start: number; readonly end: number } | null {\n if (Array.isArray(parameterLabel)) {\n const [start, end] = parameterLabel\n if (start < 0 || end > label.length || start >= end) return null\n\n return { end, start }\n }\n\n const start = findTokenBoundedIndex(label, parameterLabel)\n if (start === -1) return null\n\n return { end: start + parameterLabel.length, start }\n}\n\n/**\n * First occurrence of `needle` that is not part of a longer identifier — a plain indexOf for the\n * parameter `a` in `add(a)` finds the `a` of \"add\".\n */\nfunction findTokenBoundedIndex(label: string, needle: string): number {\n if (needle.length === 0) return -1\n\n let index = label.indexOf(needle)\n while (index !== -1) {\n const before = label[index - 1]\n const after = label[index + needle.length]\n if (!isIdentifierChar(before) && !isIdentifierChar(after)) return index\n\n index = label.indexOf(needle, index + 1)\n }\n\n return -1\n}\n\nfunction isIdentifierChar(char: string | undefined): boolean {\n if (char === undefined) return false\n\n return /[A-Za-z0-9_$]/.test(char)\n}\n\nfunction documentationText(\n documentation: lsp.SignatureInformation['documentation'],\n): string | null {\n if (!documentation) return null\n if (typeof documentation === 'string') return documentation.trim() || null\n\n return documentation.value.trim() || null\n}\n\n/** Keeps an index inside `[0, length)`; servers do send out-of-range values. */\nfunction clampIndex(index: number, length: number): number {\n if (!Number.isFinite(index) || index < 0) return 0\n if (index >= length) return Math.max(0, length - 1)\n\n return Math.trunc(index)\n}\n\n/** Next signature for overload cycling, wrapping at both ends. */\nexport function nextSignatureIndex(current: number, count: number, delta: number): number {\n if (count <= 0) return 0\n\n return (((current + delta) % count) + count) % count\n}\n"],"mappings":";;;;;;AA2BA,SAAgB,kCAAkC,MAA2C;CAC3F,IAAI,SAAS,KAAK,OAAO;EAAE,MAAM;EAAQ,kBAAkB;CAAI;CAC/D,IAAI,SAAS,KAAK,OAAO;EAAE,MAAM;EAAY,kBAAkB;CAAI;CACnE,IAAI,SAAS,KAAK,OAAO,EAAE,MAAM,QAAQ;CAEzC,OAAO;AACT;;;;;;;AAQA,SAAgB,oBACd,MACA,mBAC6B;CAC7B,MAAM,aAAa,MAAM,cAAc,CAAC;CACxC,IAAI,WAAW,WAAW,GAAG,OAAO;CAGpC,MAAM,kBAAkB,WADN,qBAAqB,MAAM,mBAAmB,GAClB,WAAW,MAAM;CAC/D,MAAM,YAAY,WAAW;CAC7B,IAAI,CAAC,WAAW,OAAO;CAGvB,MAAM,QAAQ,yBAAyB,WADf,UAAU,mBAAmB,MAAM,mBAAmB,IACb;CACjE,MAAM,gBAAgB,kBAAkB,UAAU,aAAa;CAE/D,OAAO;EACL;EACA,UAAU,gBAAgB,GAAG,MAAM,MAAM,kBAAkB;EAC3D,gBAAgB,WAAW;CAC7B;AACF;;;;;;AAOA,SAAS,yBACP,WACA,iBACQ;CACR,MAAM,QAAQ,UAAU;CACxB,MAAM,aAAa,UAAU,cAAc,CAAC;CAC5C,IAAI,oBAAoB,MAAM,OAAO;CAErC,MAAM,YAAY,WAAW,WAAW,iBAAiB,WAAW,MAAM;CAC1E,IAAI,CAAC,WAAW,OAAO;CAEvB,MAAM,QAAQ,eAAe,OAAO,UAAU,KAAK;CACnD,IAAI,CAAC,OAAO,OAAO;CAEnB,OAAO,GAAG,MAAM,MAAM,GAAG,MAAM,KAAK,EAAE,IAAI,MAAM,MAAM,MAAM,OAAO,MAAM,GAAG,EAAE,IAAI,MAAM,MAAM,MAAM,GAAG;AACzG;AAEA,SAAS,eACP,OACA,gBACyD;CACzD,IAAI,MAAM,QAAQ,cAAc,GAAG;EACjC,MAAM,CAAC,OAAO,OAAO;EACrB,IAAI,QAAQ,KAAK,MAAM,MAAM,UAAU,SAAS,KAAK,OAAO;EAE5D,OAAO;GAAE;GAAK;EAAM;CACtB;CAEA,MAAM,QAAQ,sBAAsB,OAAO,cAAc;CACzD,IAAI,UAAU,IAAI,OAAO;CAEzB,OAAO;EAAE,KAAK,QAAQ,eAAe;EAAQ;CAAM;AACrD;;;;;AAMA,SAAS,sBAAsB,OAAe,QAAwB;CACpE,IAAI,OAAO,WAAW,GAAG,OAAO;CAEhC,IAAI,QAAQ,MAAM,QAAQ,MAAM;CAChC,OAAO,UAAU,IAAI;EACnB,MAAM,SAAS,MAAM,QAAQ;EAC7B,MAAM,QAAQ,MAAM,QAAQ,OAAO;EACnC,IAAI,CAAC,iBAAiB,MAAM,KAAK,CAAC,iBAAiB,KAAK,GAAG,OAAO;EAElE,QAAQ,MAAM,QAAQ,QAAQ,QAAQ,CAAC;CACzC;CAEA,OAAO;AACT;AAEA,SAAS,iBAAiB,MAAmC;CAC3D,IAAI,SAAS,KAAA,GAAW,OAAO;CAE/B,OAAO,gBAAgB,KAAK,IAAI;AAClC;AAEA,SAAS,kBACP,eACe;CACf,IAAI,CAAC,eAAe,OAAO;CAC3B,IAAI,OAAO,kBAAkB,UAAU,OAAO,cAAc,KAAK,KAAK;CAEtE,OAAO,cAAc,MAAM,KAAK,KAAK;AACvC;;AAGA,SAAS,WAAW,OAAe,QAAwB;CACzD,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GAAG,OAAO;CACjD,IAAI,SAAS,QAAQ,OAAO,KAAK,IAAI,GAAG,SAAS,CAAC;CAElD,OAAO,KAAK,MAAM,KAAK;AACzB;;AAGA,SAAgB,mBAAmB,SAAiB,OAAe,OAAuB;CACxF,IAAI,SAAS,GAAG,OAAO;CAEvB,SAAU,UAAU,SAAS,QAAS,SAAS;AACjD"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { EditorViewContributionContext, EditorViewContributionUpdateKind, EditorViewSnapshot } from '@singapore-editor/core';
|
|
2
|
+
import type { ActiveDocument } from './pluginTypes';
|
|
3
|
+
import type { SignatureHelpKeyCommand } from './keyCommands';
|
|
4
|
+
import type { LanguageServerFeatureRouter } from './serverSet';
|
|
5
|
+
export type SignatureHelpControllerOptions = {
|
|
6
|
+
readonly context: EditorViewContributionContext;
|
|
7
|
+
readonly router: LanguageServerFeatureRouter;
|
|
8
|
+
readonly getActiveDocument: () => ActiveDocument | null;
|
|
9
|
+
readonly tooltipClassNamespace?: string;
|
|
10
|
+
readonly onRequestSuccess?: () => void;
|
|
11
|
+
readonly onRequestError: (error: unknown) => void;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Shows the signature of the call the caret is inside, requested when an argument list opens or the
|
|
15
|
+
* caret moves to the next argument.
|
|
16
|
+
*
|
|
17
|
+
* Reuses the hover tooltip rather than introducing a second floating surface, so signature text
|
|
18
|
+
* inherits the same theming, markdown rendering, and placement behaviour.
|
|
19
|
+
*/
|
|
20
|
+
export declare class SignatureHelpController {
|
|
21
|
+
private readonly options;
|
|
22
|
+
private readonly context;
|
|
23
|
+
private readonly tooltip;
|
|
24
|
+
private abort;
|
|
25
|
+
private requestId;
|
|
26
|
+
private currentTheme;
|
|
27
|
+
private lastHelp;
|
|
28
|
+
private display;
|
|
29
|
+
private disposed;
|
|
30
|
+
private readonly keymapKeys;
|
|
31
|
+
constructor(options: SignatureHelpControllerOptions);
|
|
32
|
+
update(snapshot: EditorViewSnapshot, kind: EditorViewContributionUpdateKind): void;
|
|
33
|
+
/** The keystroke, not the edit it caused; see signatureHelpTriggerFromTypedText. */
|
|
34
|
+
handleTypedText(text: string): void;
|
|
35
|
+
hide(): void;
|
|
36
|
+
containsTarget(target: EventTarget | null): boolean;
|
|
37
|
+
dispose(): void;
|
|
38
|
+
/** The keymap's commands; false when there is nothing on screen for them to act on. */
|
|
39
|
+
runKeyCommand(command: SignatureHelpKeyCommand): boolean;
|
|
40
|
+
private request;
|
|
41
|
+
private showSignature;
|
|
42
|
+
/**
|
|
43
|
+
* Follows the caret when the view moves under the signature.
|
|
44
|
+
*
|
|
45
|
+
* The call being typed is where the signature belongs, and a caret that has scrolled out of the
|
|
46
|
+
* rendered rows has no rect to belong to any more — leaving the widget parked over unrelated code
|
|
47
|
+
* is worse than losing it, and the next keystroke in the argument list asks for it again.
|
|
48
|
+
*/
|
|
49
|
+
private reanchor;
|
|
50
|
+
private caretAnchor;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=signatureHelpController.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signatureHelpController.d.ts","sourceRoot":"","sources":["../src/signatureHelpController.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGV,6BAA6B,EAC7B,gCAAgC,EAChC,kBAAkB,EACnB,MAAM,wBAAwB,CAAA;AAG/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AAC5D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAA;AAa9D,MAAM,MAAM,8BAA8B,GAAG;IAC3C,QAAQ,CAAC,OAAO,EAAE,6BAA6B,CAAA;IAC/C,QAAQ,CAAC,MAAM,EAAE,2BAA2B,CAAA;IAC5C,QAAQ,CAAC,iBAAiB,EAAE,MAAM,cAAc,GAAG,IAAI,CAAA;IACvD,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAA;IACvC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAA;IACtC,QAAQ,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAClD,CAAA;AAED;;;;;;GAMG;AACH,qBAAa,uBAAuB;IAWf,OAAO,CAAC,QAAQ,CAAC,OAAO;IAV3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IACvD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,OAAO,CAAoC;IACnD,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;gBAEpB,OAAO,EAAE,8BAA8B;IAiBpE,MAAM,CAAC,QAAQ,EAAE,kBAAkB,EAAE,IAAI,EAAE,gCAAgC,GAAG,IAAI;IASzF,oFAAoF;IAC7E,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAWnC,IAAI,IAAI,IAAI;IAQZ,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,OAAO;IAInD,OAAO,IAAI,IAAI;IAStB,uFAAuF;IAChF,aAAa,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO;YAcjD,OAAO;IAuCrB,OAAO,CAAC,aAAa;IAwBrB;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ;IAYhB,OAAO,CAAC,WAAW;CAMpB"}
|