Skip to content

Commit 36688f5

Browse files
committed
feat(labs): support disable virtual files by checkbox
1 parent 4573af5 commit 36688f5

File tree

12 files changed

+413
-318
lines changed

12 files changed

+413
-318
lines changed

extensions/labs/README.md

Lines changed: 0 additions & 11 deletions
This file was deleted.

extensions/labs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"volarjs-labs": [
3838
{
3939
"id": "volar-servers",
40-
"name": "Servers",
40+
"name": "Extensions",
4141
"contextualTitle": "Volar.js"
4242
},
4343
{

extensions/labs/src/common/shared.ts

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,37 @@
1-
import { ExportsInfoForLabs, supportLabsVersion } from '@volar/vscode';
1+
import type { LabsInfo } from '@volar/vscode';
22
import * as vscode from 'vscode';
33

4-
export function useVolarExtensions(
4+
export async function useVolarExtensions(
55
context: vscode.ExtensionContext,
6-
addExtension: (extension: vscode.Extension<ExportsInfoForLabs>) => void
6+
addExtension: (extension: vscode.Extension<LabsInfo>) => void
77
) {
88

99
const checked = new Set<string>();
1010

11+
let updateTimeout: NodeJS.Timeout | undefined;
12+
1113
context.subscriptions.push(
1214
vscode.extensions.onDidChange(update),
1315
vscode.window.onDidChangeActiveTextEditor(update),
1416
);
1517

1618
update();
1719

18-
function update() {
19-
vscode.extensions.all.forEach(extension => {
20-
if (!checked.has(extension.id) && extension.isActive) {
21-
22-
checked.add(extension.id);
23-
24-
if (extension.exports && 'volarLabs' in extension.exports) {
25-
26-
const info: ExportsInfoForLabs = extension.exports;
27-
if (info.volarLabs.version !== supportLabsVersion) {
28-
vscode.window.showWarningMessage(`Extension '${extension.id}' is not compatible with this Labs version. Expected version ${supportLabsVersion}, but got ${info.volarLabs.version}. Please downgrade Labs or update '${extension.id}' if available.`);
29-
return;
20+
async function update() {
21+
if (updateTimeout) {
22+
clearTimeout(updateTimeout);
23+
};
24+
updateTimeout = setTimeout(() => {
25+
updateTimeout = undefined;
26+
vscode.extensions.all.forEach(extension => {
27+
if (!checked.has(extension.id) && extension.isActive) {
28+
checked.add(extension.id);
29+
if (extension.exports && 'volarLabs' in extension.exports) {
30+
addExtension(extension);
3031
}
31-
32-
addExtension(extension);
3332
}
34-
}
35-
});
33+
});
34+
}, 1000);
3635
}
3736
}
3837

extensions/labs/src/common/showVirtualFile.ts

Lines changed: 144 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { CodeInformation } from '@volar/language-server';
22
import { SourceMap, Stack } from '@volar/source-map';
3-
import { ExportsInfoForLabs, TextDocument } from '@volar/vscode';
3+
import { BaseLanguageClient, LabsInfo, TextDocument } from '@volar/vscode';
44
import * as vscode from 'vscode';
55

66
const mappingDecorationType = vscode.window.createTextEditorDecorationType({
@@ -35,155 +35,25 @@ export const sourceUriToVirtualUris = new Map<string, Set<string>>();
3535

3636
export const virtualUriToSourceUri = new Map<string, string>();
3737

38-
export async function activate(info: ExportsInfoForLabs) {
38+
export async function activate(info: LabsInfo) {
3939

4040
const subscriptions: vscode.Disposable[] = [];
4141
const docChangeEvent = new vscode.EventEmitter<vscode.Uri>();
42-
const client = info.volarLabs.languageClient;
43-
44-
subscriptions.push(vscode.languages.registerHoverProvider({ scheme: client.name.replace(/ /g, '_').toLowerCase() }, {
45-
async provideHover(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken) {
46-
47-
const maps = virtualUriToSourceMap.get(document.uri.toString());
48-
if (!maps) return;
49-
50-
const data: {
51-
uri: string,
52-
mapping: any,
53-
}[] = [];
54-
55-
for (const [sourceUri, _, map] of maps) {
56-
const source = map.getSourceOffset(document.offsetAt(position));
57-
if (source) {
58-
data.push({
59-
uri: sourceUri,
60-
mapping: source,
61-
});
62-
}
63-
}
64-
65-
if (data.length === 0) return;
66-
67-
return new vscode.Hover(data.map((data) => [
68-
data.uri,
69-
'',
70-
'',
71-
'```json',
72-
JSON.stringify(data.mapping, null, 2),
73-
'```',
74-
].join('\n')));
75-
}
76-
}));
77-
78-
subscriptions.push(vscode.languages.registerDefinitionProvider({ scheme: client.name.replace(/ /g, '_').toLowerCase() }, {
79-
async provideDefinition(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken) {
80-
81-
const stacks = virtualUriToStacks.get(document.uri.toString());
82-
if (!stacks) return;
83-
84-
const offset = document.offsetAt(position);
85-
const stack = stacks.find(stack => stack.range[0] <= offset && offset <= stack.range[1]);
86-
if (!stack) return;
87-
88-
const line = Number(stack.source.split(':').at(-2));
89-
const character = Number(stack.source.split(':').at(-1));
90-
const fileName = stack.source.split(':').slice(0, -2).join(':');
91-
const link: vscode.DefinitionLink = {
92-
originSelectionRange: new vscode.Range(document.positionAt(stack.range[0]), document.positionAt(stack.range[1])),
93-
targetUri: vscode.Uri.file(fileName),
94-
targetRange: new vscode.Range(line - 1, character - 1, line - 1, character - 1),
95-
};
96-
return [link];
97-
}
98-
}));
99-
100-
subscriptions.push(vscode.languages.registerInlayHintsProvider({ scheme: client.name.replace(/ /g, '_').toLowerCase() }, {
101-
provideInlayHints(document, range) {
102-
const stacks = virtualUriToStacks.get(document.uri.toString());
103-
const result: vscode.InlayHint[] = [];
104-
const range2 = [document.offsetAt(range.start), document.offsetAt(range.end)];
105-
const text = document.getText();
106-
for (const stack of stacks ?? []) {
107-
let [start, end] = stack.range;
108-
let startText = '[';
109-
let endText = ']';
110-
while (end > start && text[end - 1] === '\n') {
111-
end--;
112-
endText = '\n' + endText;
113-
}
114-
while (start < end && text[start] === '\n') {
115-
start++;
116-
startText = '\n' + startText;
117-
}
118-
if (start >= range2[0] && start <= range2[1]) {
119-
result.push(new vscode.InlayHint(document.positionAt(start), startText));
120-
result[result.length - 1].paddingLeft = true;
121-
}
122-
if (end >= range2[0] && end <= range2[1]) {
123-
result.push(new vscode.InlayHint(document.positionAt(end), endText));
124-
result[result.length - 1].paddingRight = true;
125-
}
126-
}
127-
return result;
128-
},
129-
}));
130-
131-
subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(
132-
client.name.replace(/ /g, '_').toLowerCase(),
133-
{
134-
onDidChange: docChangeEvent.event,
135-
async provideTextDocumentContent(uri: vscode.Uri): Promise<string | undefined> {
136-
137-
const requestUri = virtualUriToSourceUri.get(uri.toString());
138-
if (requestUri) {
139-
140-
const fileName = uri.with({ scheme: 'file' }).fsPath;
141-
const virtualFile = await client.sendRequest(info.volarLabs.languageServerProtocol.GetVirtualFileRequest.type, { sourceFileUri: requestUri, virtualFileName: fileName });
142-
virtualUriToSourceMap.set(uri.toString(), []);
143-
144-
Object.entries(virtualFile.mappings).forEach(([sourceUri, mappings]) => {
145-
const sourceEditor = vscode.window.visibleTextEditors.find(editor => editor.document.uri.toString() === sourceUri);
146-
if (sourceEditor) {
147-
virtualUriToSourceMap.get(uri.toString())?.push([
148-
sourceEditor.document.uri.toString(),
149-
sourceEditor.document.version,
150-
new SourceMap(mappings),
151-
]);
152-
if (!sourceUriToVirtualUris.has(sourceUri)) {
153-
sourceUriToVirtualUris.set(sourceUri, new Set());
154-
}
155-
sourceUriToVirtualUris.get(sourceUri)?.add(uri.toString());
156-
}
157-
});
158-
virtualDocuments.set(uri.toString(), TextDocument.create('', '', 0, virtualFile.content));
159-
virtualUriToStacks.set(uri.toString(), virtualFile.codegenStacks);
160-
161-
clearTimeout(updateDecorationsTimeout);
162-
updateDecorationsTimeout = setTimeout(updateDecorations, 100);
163-
164-
return virtualFile.content;
165-
}
166-
}
167-
},
168-
));
169-
17042
const virtualUriToSourceMap = new Map<string, [string, number, SourceMap<CodeInformation>][]>();
17143
const virtualUriToStacks = new Map<string, Stack[]>();
17244
const virtualDocuments = new Map<string, TextDocument>();
17345

46+
for (const extension of info.volarLabs.languageClients) {
47+
registerProviders(extension);
48+
}
49+
info.volarLabs.onDidAddLanguageClient(registerProviders);
50+
17451
let updateVirtualDocument: NodeJS.Timeout | undefined;
17552
let updateDecorationsTimeout: NodeJS.Timeout | undefined;
17653

17754
subscriptions.push(vscode.window.onDidChangeActiveTextEditor(updateDecorations));
17855
subscriptions.push(vscode.window.onDidChangeTextEditorSelection(updateDecorations));
17956
subscriptions.push(vscode.window.onDidChangeVisibleTextEditors(updateDecorations));
180-
subscriptions.push(client.onDidChangeState(() => {
181-
for (const virtualUris of sourceUriToVirtualUris.values()) {
182-
virtualUris.forEach(uri => {
183-
docChangeEvent.fire(vscode.Uri.parse(uri));
184-
});
185-
}
186-
}));
18757
subscriptions.push(vscode.workspace.onDidChangeTextDocument(e => {
18858
if (sourceUriToVirtualUris.has(e.document.uri.toString())) {
18959
const virtualUris = sourceUriToVirtualUris.get(e.document.uri.toString());
@@ -198,6 +68,143 @@ export async function activate(info: ExportsInfoForLabs) {
19868

19969
return vscode.Disposable.from(...subscriptions);
20070

71+
function registerProviders(client: BaseLanguageClient) {
72+
73+
subscriptions.push(client.onDidChangeState(() => {
74+
for (const virtualUris of sourceUriToVirtualUris.values()) {
75+
virtualUris.forEach(uri => {
76+
docChangeEvent.fire(vscode.Uri.parse(uri));
77+
});
78+
}
79+
}));
80+
81+
subscriptions.push(vscode.languages.registerHoverProvider({ scheme: client.name.replace(/ /g, '_').toLowerCase() }, {
82+
async provideHover(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken) {
83+
84+
const maps = virtualUriToSourceMap.get(document.uri.toString());
85+
if (!maps) return;
86+
87+
const data: {
88+
uri: string,
89+
mapping: any,
90+
}[] = [];
91+
92+
for (const [sourceUri, _, map] of maps) {
93+
const source = map.getSourceOffset(document.offsetAt(position));
94+
if (source) {
95+
data.push({
96+
uri: sourceUri,
97+
mapping: source,
98+
});
99+
}
100+
}
101+
102+
if (data.length === 0) return;
103+
104+
return new vscode.Hover(data.map((data) => [
105+
data.uri,
106+
'',
107+
'',
108+
'```json',
109+
JSON.stringify(data.mapping, null, 2),
110+
'```',
111+
].join('\n')));
112+
}
113+
}));
114+
115+
subscriptions.push(vscode.languages.registerDefinitionProvider({ scheme: client.name.replace(/ /g, '_').toLowerCase() }, {
116+
async provideDefinition(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken) {
117+
118+
const stacks = virtualUriToStacks.get(document.uri.toString());
119+
if (!stacks) return;
120+
121+
const offset = document.offsetAt(position);
122+
const stack = stacks.find(stack => stack.range[0] <= offset && offset <= stack.range[1]);
123+
if (!stack) return;
124+
125+
const line = Number(stack.source.split(':').at(-2));
126+
const character = Number(stack.source.split(':').at(-1));
127+
const fileName = stack.source.split(':').slice(0, -2).join(':');
128+
const link: vscode.DefinitionLink = {
129+
originSelectionRange: new vscode.Range(document.positionAt(stack.range[0]), document.positionAt(stack.range[1])),
130+
targetUri: vscode.Uri.file(fileName),
131+
targetRange: new vscode.Range(line - 1, character - 1, line - 1, character - 1),
132+
};
133+
return [link];
134+
}
135+
}));
136+
137+
subscriptions.push(vscode.languages.registerInlayHintsProvider({ scheme: client.name.replace(/ /g, '_').toLowerCase() }, {
138+
provideInlayHints(document, range) {
139+
const stacks = virtualUriToStacks.get(document.uri.toString());
140+
const result: vscode.InlayHint[] = [];
141+
const range2 = [document.offsetAt(range.start), document.offsetAt(range.end)];
142+
const text = document.getText();
143+
for (const stack of stacks ?? []) {
144+
let [start, end] = stack.range;
145+
let startText = '[';
146+
let endText = ']';
147+
while (end > start && text[end - 1] === '\n') {
148+
end--;
149+
endText = '\n' + endText;
150+
}
151+
while (start < end && text[start] === '\n') {
152+
start++;
153+
startText = '\n' + startText;
154+
}
155+
if (start >= range2[0] && start <= range2[1]) {
156+
result.push(new vscode.InlayHint(document.positionAt(start), startText));
157+
result[result.length - 1].paddingLeft = true;
158+
}
159+
if (end >= range2[0] && end <= range2[1]) {
160+
result.push(new vscode.InlayHint(document.positionAt(end), endText));
161+
result[result.length - 1].paddingRight = true;
162+
}
163+
}
164+
return result;
165+
},
166+
}));
167+
168+
subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(
169+
client.name.replace(/ /g, '_').toLowerCase(),
170+
{
171+
onDidChange: docChangeEvent.event,
172+
async provideTextDocumentContent(uri: vscode.Uri): Promise<string | undefined> {
173+
174+
const requestUri = virtualUriToSourceUri.get(uri.toString());
175+
if (requestUri) {
176+
177+
const fileName = uri.with({ scheme: 'file' }).fsPath;
178+
const virtualFile = await client.sendRequest(info.volarLabs.languageServerProtocol.GetVirtualFileRequest.type, { sourceFileUri: requestUri, virtualFileName: fileName });
179+
virtualUriToSourceMap.set(uri.toString(), []);
180+
181+
Object.entries(virtualFile.mappings).forEach(([sourceUri, mappings]) => {
182+
const sourceEditor = vscode.window.visibleTextEditors.find(editor => editor.document.uri.toString() === sourceUri);
183+
if (sourceEditor) {
184+
virtualUriToSourceMap.get(uri.toString())?.push([
185+
sourceEditor.document.uri.toString(),
186+
sourceEditor.document.version,
187+
new SourceMap(mappings),
188+
]);
189+
if (!sourceUriToVirtualUris.has(sourceUri)) {
190+
sourceUriToVirtualUris.set(sourceUri, new Set());
191+
}
192+
sourceUriToVirtualUris.get(sourceUri)?.add(uri.toString());
193+
}
194+
});
195+
virtualDocuments.set(uri.toString(), TextDocument.create('', '', 0, virtualFile.content));
196+
virtualUriToStacks.set(uri.toString(), virtualFile.codegenStacks);
197+
198+
clearTimeout(updateDecorationsTimeout);
199+
updateDecorationsTimeout = setTimeout(updateDecorations, 100);
200+
201+
return virtualFile.content;
202+
}
203+
}
204+
},
205+
));
206+
}
207+
201208
function updateDecorations() {
202209
for (const [_, sources] of virtualUriToSourceMap) {
203210
for (const [sourceUri] of sources) {

0 commit comments

Comments
 (0)