Skip to content

Commit 6715fd6

Browse files
committed
feat(labs): support service plugins view
1 parent 36688f5 commit 6715fd6

File tree

16 files changed

+301
-82
lines changed

16 files changed

+301
-82
lines changed

extensions/labs/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@
4444
"id": "volar-virtual-files",
4545
"name": "Virtual Files",
4646
"contextualTitle": "Volar.js"
47+
},
48+
{
49+
"id": "volar-service-plugins",
50+
"name": "Service Plugins",
51+
"contextualTitle": "Volar.js"
4752
}
4853
]
4954
}

extensions/labs/src/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import type * as vscode from 'vscode';
22
import * as serverView from './views/serversView';
33
import * as virtualFilesView from './views/virtualFilesView';
4+
import * as servicePluginsView from './views/servicePluginsView';
45

56
export function activate(context: vscode.ExtensionContext) {
67
serverView.activate(context);
78
virtualFilesView.activate(context);
9+
servicePluginsView.activate(context);
810
}

extensions/labs/src/views/serversView.ts

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -295,26 +295,29 @@ export function activate(context: vscode.ExtensionContext) {
295295
}),
296296
);
297297

298-
useVolarExtensions(context, async extension => {
299-
const { version } = extension.exports.volarLabs;
300-
if (version === lsp.currentLabsVersion) {
301-
for (const languageClient of extension.exports.volarLabs.languageClients) {
302-
context.subscriptions.push(
303-
languageClient.onDidChangeState(() => onDidChangeTreeData.fire())
304-
);
298+
useVolarExtensions(
299+
context,
300+
extension => {
301+
const { version } = extension.exports.volarLabs;
302+
if (version === lsp.currentLabsVersion) {
303+
for (const languageClient of extension.exports.volarLabs.languageClients) {
304+
context.subscriptions.push(
305+
languageClient.onDidChangeState(() => onDidChangeTreeData.fire())
306+
);
307+
}
308+
extension.exports.volarLabs.onDidAddLanguageClient(languageClient => {
309+
context.subscriptions.push(
310+
languageClient.onDidChangeState(() => onDidChangeTreeData.fire())
311+
);
312+
onDidChangeTreeData.fire();
313+
});
314+
extensions.push(extension);
315+
onDidChangeTreeData.fire();
305316
}
306-
extension.exports.volarLabs.onDidAddLanguageClient(languageClient => {
307-
context.subscriptions.push(
308-
languageClient.onDidChangeState(() => onDidChangeTreeData.fire())
309-
);
317+
else {
318+
invalidExtensions.push(extension);
310319
onDidChangeTreeData.fire();
311-
});
312-
extensions.push(extension);
313-
onDidChangeTreeData.fire();
314-
}
315-
else {
316-
invalidExtensions.push(extension);
317-
onDidChangeTreeData.fire();
318-
}
319-
});
320+
}
321+
},
322+
);
320323
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import type { GetServicePluginsRequest, UpdateServicePluginStateNotification } from '@volar/language-server';
2+
import { currentLabsVersion, type BaseLanguageClient, type LabsInfo } from '@volar/vscode';
3+
import * as vscode from 'vscode';
4+
import { getIconPath, useVolarExtensions } from '../common/shared';
5+
6+
interface ServicePluginItem {
7+
extension: vscode.Extension<LabsInfo>;
8+
client: BaseLanguageClient;
9+
sourceDocumentUri: string;
10+
servicePlugin: NonNullable<NonNullable<GetServicePluginsRequest.ResponseType>[number]>;
11+
}
12+
13+
export function activate(context: vscode.ExtensionContext) {
14+
15+
const extensions: vscode.Extension<LabsInfo>[] = [];
16+
const onDidChangeTreeData = new vscode.EventEmitter<void>();
17+
const tree: vscode.TreeDataProvider<ServicePluginItem> = {
18+
onDidChangeTreeData: onDidChangeTreeData.event,
19+
async getChildren(element) {
20+
21+
const doc = vscode.window.activeTextEditor?.document;
22+
if (!doc) return [];
23+
24+
if (!element) {
25+
const items: ServicePluginItem[] = [];
26+
for (const extension of extensions) {
27+
for (const client of extension.exports.volarLabs.languageClients) {
28+
const servicePlugins = await client.sendRequest(extension.exports.volarLabs.languageServerProtocol.GetServicePluginsRequest.type, { uri: doc.uri.toString() });
29+
if (servicePlugins) {
30+
for (const servicePlugin of servicePlugins) {
31+
items.push({
32+
extension,
33+
client,
34+
servicePlugin,
35+
sourceDocumentUri: doc.uri.toString(),
36+
});
37+
}
38+
}
39+
}
40+
}
41+
return items;
42+
}
43+
},
44+
getTreeItem(element) {
45+
return {
46+
checkboxState: element.servicePlugin.disabled ? vscode.TreeItemCheckboxState.Unchecked : vscode.TreeItemCheckboxState.Checked,
47+
iconPath: getIconPath(element.extension),
48+
label: element.servicePlugin.name ?? `[${element.servicePlugin.id}]`,
49+
description: element.servicePlugin.features.join(', '),
50+
collapsibleState: vscode.TreeItemCollapsibleState.None,
51+
};
52+
},
53+
};
54+
const treeView = vscode.window.createTreeView('volar-service-plugins', {
55+
treeDataProvider: tree,
56+
showCollapseAll: false,
57+
manageCheckboxStateManually: true,
58+
});
59+
60+
context.subscriptions.push(
61+
vscode.window.onDidChangeActiveTextEditor((e) => {
62+
63+
if (!e) return;
64+
65+
const document = e.document;
66+
const isVirtualFile = extensions
67+
.some(extension => extension.exports.volarLabs.languageClients.some(
68+
client => client.name
69+
.replace(/ /g, '_')
70+
.toLowerCase() === document.uri.scheme
71+
));
72+
if (isVirtualFile) return;
73+
74+
onDidChangeTreeData.fire();
75+
}),
76+
vscode.workspace.onDidChangeTextDocument(() => {
77+
onDidChangeTreeData.fire();
78+
}),
79+
treeView,
80+
treeView.onDidChangeCheckboxState(e => {
81+
for (const [item, state] of e.items) {
82+
item.servicePlugin.disabled = state === vscode.TreeItemCheckboxState.Unchecked;
83+
item.client.sendNotification(
84+
item.extension.exports.volarLabs.languageServerProtocol.UpdateServicePluginStateNotification.type,
85+
{
86+
uri: item.sourceDocumentUri,
87+
serviceId: item.servicePlugin.id,
88+
disabled: state === vscode.TreeItemCheckboxState.Unchecked,
89+
} satisfies UpdateServicePluginStateNotification.ParamsType
90+
);
91+
}
92+
}),
93+
);
94+
95+
useVolarExtensions(
96+
context,
97+
extension => {
98+
const { version } = extension.exports.volarLabs;
99+
if (version === currentLabsVersion) {
100+
for (const languageClient of extension.exports.volarLabs.languageClients) {
101+
context.subscriptions.push(
102+
languageClient.onDidChangeState(() => onDidChangeTreeData.fire())
103+
);
104+
}
105+
extension.exports.volarLabs.onDidAddLanguageClient(languageClient => {
106+
context.subscriptions.push(
107+
languageClient.onDidChangeState(() => onDidChangeTreeData.fire())
108+
);
109+
onDidChangeTreeData.fire();
110+
});
111+
extensions.push(extension);
112+
onDidChangeTreeData.fire();
113+
}
114+
}
115+
);
116+
}

extensions/labs/src/views/virtualFilesView.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { GetVirtualFilesRequest, LabsChangeVirtualFileStateNotification } from '@volar/language-server';
1+
import type { GetVirtualFilesRequest, UpdateVirtualFileStateNotification } from '@volar/language-server';
22
import { currentLabsVersion, type BaseLanguageClient, type LabsInfo } from '@volar/vscode';
33
import * as path from 'path';
44
import * as vscode from 'vscode';
@@ -61,18 +61,15 @@ export function activate(context: vscode.ExtensionContext) {
6161

6262
let label = path.basename(element.virtualFile.fileName);
6363
let description = '';
64-
if (element.virtualFile.typescript) {
65-
description += `tsScriptKind: ${element.virtualFile.typescript.scriptKind}, `;
64+
if (element.virtualFile.tsScriptKind !== undefined) {
65+
description += `tsScriptKind: ${element.virtualFile.tsScriptKind}, `;
6666
}
67-
// @ts-expect-error
6867
description += `version: ${element.virtualFile.version}`;
6968
if (element.isRoot) {
7069
description += ` (${element.client.name})`;
7170
}
72-
// @ts-expect-error
73-
const isIgnored = element.virtualFile.isIgnored;
7471
return {
75-
checkboxState: isIgnored ? vscode.TreeItemCheckboxState.Unchecked : vscode.TreeItemCheckboxState.Checked,
72+
checkboxState: element.virtualFile.disabled ? vscode.TreeItemCheckboxState.Unchecked : vscode.TreeItemCheckboxState.Checked,
7673
iconPath: element.client.clientOptions.initializationOptions.codegenStack ? new vscode.ThemeIcon('debug-breakpoint') : new vscode.ThemeIcon('file'),
7774
label,
7875
description,
@@ -118,14 +115,14 @@ export function activate(context: vscode.ExtensionContext) {
118115
treeView.onDidChangeCheckboxState(e => {
119116
for (const [item, state] of e.items) {
120117
if ('virtualFile' in item) {
121-
// @ts-expect-error
122-
item.virtualFile.isIgnored = state === vscode.TreeItemCheckboxState.Unchecked;
118+
item.virtualFile.disabled = state === vscode.TreeItemCheckboxState.Unchecked;
123119
item.client.sendNotification(
124-
item.extension.exports.volarLabs.languageServerProtocol.LabsChangeVirtualFileStateNotification.type,
120+
item.extension.exports.volarLabs.languageServerProtocol.UpdateVirtualFileStateNotification.type,
125121
{
126-
fileName: item.virtualFile.fileName,
127-
ignore: state === vscode.TreeItemCheckboxState.Unchecked,
128-
} satisfies LabsChangeVirtualFileStateNotification.ParamsType
122+
uri: item.sourceDocumentUri,
123+
virtualFileName: item.virtualFile.fileName,
124+
disabled: state === vscode.TreeItemCheckboxState.Unchecked,
125+
} satisfies UpdateVirtualFileStateNotification.ParamsType
129126
);
130127
}
131128
}

packages/language-server/lib/register/registerEditorFeatures.ts

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import {
1111
DocumentDropRequest,
1212
DocumentDrop_DataTransferItemAsStringRequest,
1313
DocumentDrop_DataTransferItemFileDataRequest,
14-
LabsChangeVirtualFileStateNotification
14+
UpdateVirtualFileStateNotification,
15+
UpdateServicePluginStateNotification,
16+
GetServicePluginsRequest,
1517
} from '../../protocol';
1618
import type { ServerProjectProvider, ServerRuntimeEnvironment } from '../types';
17-
import { skipVirtualFiles, type DataTransferItem } from '@volar/language-service';
19+
import type { DataTransferItem } from '@volar/language-service';
1820

1921
export function registerEditorFeatures(
2022
connection: vscode.Connection,
@@ -64,7 +66,7 @@ export function registerEditorFeatures(
6466
const virtualFile = languageService.context.language.files.getSourceFile(env.uriToFileName(document.uri))?.virtualFile;
6567
return virtualFile ? prune(virtualFile[0]) : undefined;
6668

67-
function prune(file: VirtualFile): VirtualFile {
69+
function prune(file: VirtualFile): GetVirtualFilesRequest.VirtualFileWithState {
6870
let version = scriptVersions.get(file.fileName) ?? 0;
6971
if (!scriptVersionSnapshots.has(file.snapshot)) {
7072
version++;
@@ -74,11 +76,10 @@ export function registerEditorFeatures(
7476
return {
7577
fileName: file.fileName,
7678
languageId: file.languageId,
77-
typescript: file.typescript,
79+
tsScriptKind: file.typescript?.scriptKind,
7880
embeddedFiles: file.embeddedFiles.map(prune),
79-
// @ts-expect-error
8081
version,
81-
isIgnored: skipVirtualFiles.has(file.fileName),
82+
disabled: languageService.context.disabledVirtualFiles.has(file.fileName),
8283
};
8384
}
8485
});
@@ -197,12 +198,46 @@ export function registerEditorFeatures(
197198

198199
return result;
199200
});
200-
connection.onNotification(LabsChangeVirtualFileStateNotification.type, async params => {
201-
if (params.ignore) {
202-
skipVirtualFiles.add(params.fileName);
201+
connection.onNotification(UpdateVirtualFileStateNotification.type, async params => {
202+
const project = await projects.getProject(params.uri);
203+
const context = project.getLanguageServiceDontCreate()?.context;
204+
if (context) {
205+
if (params.disabled) {
206+
context.disabledVirtualFiles.add(params.virtualFileName);
207+
}
208+
else {
209+
context.disabledVirtualFiles.delete(params.virtualFileName);
210+
}
211+
}
212+
});
213+
connection.onNotification(UpdateServicePluginStateNotification.type, async params => {
214+
const project = await projects.getProject(params.uri);
215+
const context = project.getLanguageServiceDontCreate()?.context;
216+
if (context) {
217+
const service = context.services[params.serviceId as any][1];
218+
if (params.disabled) {
219+
context.disabledServicePlugins.add(service);
220+
}
221+
else {
222+
context.disabledServicePlugins.delete(service);
223+
}
203224
}
204-
else {
205-
skipVirtualFiles.delete(params.fileName);
225+
});
226+
connection.onRequest(GetServicePluginsRequest.type, async params => {
227+
const project = await projects.getProject(params.uri);
228+
const context = project.getLanguageServiceDontCreate()?.context;
229+
if (context) {
230+
const result: GetServicePluginsRequest.ResponseType = [];
231+
for (let id in context.services) {
232+
const service = context.services[id];
233+
result.push({
234+
id,
235+
name: service[0].name,
236+
disabled: context.disabledServicePlugins.has(service[1]),
237+
features: Object.keys(service[1]),
238+
});
239+
}
240+
return result;
206241
}
207242
});
208243
}

0 commit comments

Comments
 (0)