Skip to content

Commit 62719ab

Browse files
committed
Clean up client API
- Don't use "any" for the API type. - Remove everything from the Coder API that can eventually be done through the VS Code API. - Move the event emission to our own client to minimize patching.
1 parent 0315b00 commit 62719ab

4 files changed

Lines changed: 141 additions & 358 deletions

File tree

scripts/vscode.patch

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -573,34 +573,27 @@ index 5a758eb786..7fcacb5ca7 100644
573573
templateData.actionBar.context = (<TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle });
574574
templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false });
575575
diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts
576-
index 1986fb6642..a3e4cbdb56 100644
576+
index 1986fb6642..453d3e3e48 100644
577577
--- a/src/vs/workbench/browser/web.main.ts
578578
+++ b/src/vs/workbench/browser/web.main.ts
579579
@@ -35,6 +35,7 @@ import { SignService } from 'vs/platform/sign/browser/signService';
580580
import { hash } from 'vs/base/common/hash';
581581
import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api';
582582
import { ProductService } from 'vs/platform/product/browser/productService';
583-
+import { coderApi, vscodeApi } from 'vs/server/src/api';
583+
+import { initialize } from 'vs/server/src/client';
584584

585585
class CodeRendererMain extends Disposable {
586586

587-
@@ -71,6 +72,15 @@ class CodeRendererMain extends Disposable {
587+
@@ -71,6 +72,8 @@ class CodeRendererMain extends Disposable {
588588

589589
// Startup
590590
this.workbench.startup();
591591
+
592-
+ const target = window as any;
593-
+ target.ide = coderApi(services.serviceCollection);
594-
+ target.vscode = vscodeApi(services.serviceCollection);
595-
+
596-
+ const event = new CustomEvent('ide-ready');
597-
+ (event as any).ide = target.ide;
598-
+ (event as any).vscode = target.vscode;
599-
+ window.dispatchEvent(event);
592+
+ initialize(services.serviceCollection);
600593
}
601594

602595
private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService }> {
603-
@@ -114,7 +124,8 @@ class CodeRendererMain extends Disposable {
596+
@@ -114,7 +117,8 @@ class CodeRendererMain extends Disposable {
604597
const channel = connection.getChannel<IChannel>(REMOTE_FILE_SYSTEM_CHANNEL_NAME);
605598
const remoteFileSystemProvider = this._register(new RemoteExtensionsFileSystemProvider(channel, remoteAgentService.getEnvironment()));
606599

@@ -1422,7 +1415,7 @@ index 306d58f915..58c603ad3d 100644
14221415
if (definition.fontCharacter || definition.fontColor) {
14231416
let body = '';
14241417
diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts
1425-
index c28adc0ad9..3d1adba3d9 100644
1418+
index c28adc0ad9..4517c308da 100644
14261419
--- a/src/vs/workbench/workbench.web.main.ts
14271420
+++ b/src/vs/workbench/workbench.web.main.ts
14281421
@@ -128,7 +128,7 @@ import 'vs/workbench/services/extensions/browser/extensionService';
@@ -1447,9 +1440,3 @@ index c28adc0ad9..3d1adba3d9 100644
14471440

14481441
// Output Panel
14491442
import 'vs/workbench/contrib/output/browser/output.contribution';
1450-
@@ -356,3 +356,5 @@ import 'vs/workbench/contrib/outline/browser/outline.contribution';
1451-
// import 'vs/workbench/contrib/issue/electron-browser/issue.contribution';
1452-
1453-
//#endregion
1454-
+
1455-
+import 'vs/server/src/client';

src/api.ts

Lines changed: 116 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,19 @@
11
import * as vscode from "vscode";
22

33
import { localize } from "vs/nls";
4-
import { Action } from "vs/base/common/actions";
5-
import { SyncActionDescriptor, MenuRegistry, MenuId } from "vs/platform/actions/common/actions";
4+
import { SyncActionDescriptor } from "vs/platform/actions/common/actions";
65
import { Registry } from "vs/platform/registry/common/platform";
76
import { IWorkbenchActionRegistry, Extensions as ActionExtensions} from "vs/workbench/common/actions";
87
import { CommandsRegistry, ICommandService } from "vs/platform/commands/common/commands";
9-
import { IStat, IWatchOptions, FileOverwriteOptions, FileDeleteOptions, FileOpenOptions, IFileChange, FileWriteOptions, FileSystemProviderCapabilities, IFileService, FileType, FileOperation, IFileSystemProvider } from "vs/platform/files/common/files";
10-
import { ITextFileService } from "vs/workbench/services/textfile/common/textfiles";
11-
import { IModelService } from "vs/editor/common/services/modelService";
12-
import { ITerminalService } from "vs/workbench/contrib/terminal/common/terminal";
8+
import { IStat, IWatchOptions, FileOverwriteOptions, FileDeleteOptions, FileOpenOptions, IFileChange, FileWriteOptions, FileSystemProviderCapabilities, IFileService, FileType, IFileSystemProvider } from "vs/platform/files/common/files";
139
import { IStorageService } from "vs/platform/storage/common/storage";
1410
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
1511
import { INotificationService } from "vs/platform/notification/common/notification";
16-
import { IStatusbarService, StatusbarAlignment } from "vs/platform/statusbar/common/statusbar";
17-
import Severity from "vs/base/common/severity";
1812
import { Emitter, Event } from "vs/base/common/event";
1913
import * as extHostTypes from "vs/workbench/api/common/extHostTypes";
2014
import { ServiceIdentifier, IInstantiationService } from "vs/platform/instantiation/common/instantiation";
2115
import { URI } from "vs/base/common/uri";
22-
import { ITreeViewDataProvider, IViewsRegistry, ITreeViewDescriptor, Extensions as ViewsExtensions, IViewContainersRegistry } from "vs/workbench/common/views";
16+
import { ITreeItem, ITreeViewDataProvider, IViewsRegistry, ITreeViewDescriptor, Extensions as ViewsExtensions, IViewContainersRegistry, TreeItemCollapsibleState } from "vs/workbench/common/views";
2317
import { CustomTreeViewPanel, CustomTreeView } from "vs/workbench/browser/parts/views/customView";
2418
import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction } from "vs/workbench/browser/viewlet";
2519
import { IExtensionService } from "vs/workbench/services/extensions/common/extensions";
@@ -35,14 +29,15 @@ import { IViewletService } from "vs/workbench/services/viewlet/browser/viewlet";
3529
import { IEditorGroupsService } from "vs/workbench/services/editor/common/editorGroupsService";
3630
import { createCSSRule } from "vs/base/browser/dom";
3731
import { IDisposable } from "vs/base/common/lifecycle";
32+
import { generateUuid } from "vs/base/common/uuid";
3833

3934
/**
4035
* Client-side implementation of VS Code's API.
4136
* TODO: Views aren't quite working.
4237
* TODO: Implement menu items for views (for item actions).
4338
* TODO: File system provider doesn't work.
4439
*/
45-
export const vscodeApi = (serviceCollection: ServiceCollection): typeof vscode => {
40+
export const vscodeApi = (serviceCollection: ServiceCollection): Partial<typeof vscode> => {
4641
const getService = <T>(id: ServiceIdentifier<T>): T => serviceCollection.get<T>(id) as T;
4742
const commandService = getService(ICommandService);
4843
const notificationService = getService(INotificationService);
@@ -61,149 +56,84 @@ export const vscodeApi = (serviceCollection: ServiceCollection): typeof vscode =
6156
FileType: FileType,
6257
Uri: URI,
6358
commands: {
64-
executeCommand: (commandId: string, ...args: any[]): any => {
59+
executeCommand: <T = any>(commandId: string, ...args: any[]): Promise<T | undefined> => {
6560
return commandService.executeCommand(commandId, ...args);
6661
},
67-
registerCommand: (id: string, command: () => void): any => {
62+
registerCommand: (id: string, command: (...args: any[]) => any): IDisposable => {
6863
return CommandsRegistry.registerCommand(id, command);
6964
},
70-
},
65+
} as Partial<typeof vscode.commands>,
7166
window: {
72-
registerTreeDataProvider: (id: string, dataProvider: ITreeViewDataProvider): void => {
67+
registerTreeDataProvider: <T>(id: string, dataProvider: vscode.TreeDataProvider<T>): IDisposable => {
68+
const tree = new TreeViewDataProvider(dataProvider);
7369
const view = viewsRegistry.getView(id);
74-
if (view) {
75-
(view as ITreeViewDescriptor).treeView.dataProvider = dataProvider;
76-
}
70+
(view as ITreeViewDescriptor).treeView.dataProvider = tree;
71+
return {
72+
dispose: () => tree.dispose(),
73+
};
7774
},
78-
showErrorMessage: (message: string): void => {
75+
showErrorMessage: async (message: string): Promise<string | undefined> => {
7976
notificationService.error(message);
77+
return undefined;
8078
},
81-
},
79+
} as Partial<typeof vscode.window>,
8280
workspace: {
8381
registerFileSystemProvider: (scheme: string, provider: vscode.FileSystemProvider): IDisposable => {
8482
return fileService.registerProvider(scheme, new FileSystemProvider(provider));
8583
},
86-
},
87-
} as any;
84+
} as Partial<typeof vscode.workspace>,
85+
} as Partial<typeof vscode>; // Without this it complains that the type isn't `| undefined`.
8886
};
8987

9088
/**
91-
* Coder API.
89+
* Coder API. This should only provide functionality that can't be made
90+
* available through the VS Code API.
9291
*/
9392
export const coderApi = (serviceCollection: ServiceCollection): typeof coder => {
9493
const getService = <T>(id: ServiceIdentifier<T>): T => serviceCollection.get<T>(id) as T;
9594
return {
96-
workbench: {
97-
action: Action,
98-
syncActionDescriptor: SyncActionDescriptor,
99-
commandRegistry: CommandsRegistry,
100-
actionsRegistry: Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions),
101-
registerView: (viewId, viewName, containerId, containerName, icon): void => {
102-
const cssClass = `extensionViewlet-${containerId}`;
103-
const id = `workbench.view.extension.${containerId}`;
104-
class CustomViewlet extends ViewContainerViewlet {
105-
public constructor(
106-
@IConfigurationService configurationService: IConfigurationService,
107-
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
108-
@ITelemetryService telemetryService: ITelemetryService,
109-
@IWorkspaceContextService contextService: IWorkspaceContextService,
110-
@IStorageService storageService: IStorageService,
111-
@IEditorService _editorService: IEditorService,
112-
@IInstantiationService instantiationService: IInstantiationService,
113-
@IThemeService themeService: IThemeService,
114-
@IContextMenuService contextMenuService: IContextMenuService,
115-
@IExtensionService extensionService: IExtensionService,
116-
) {
117-
super(id, `${id}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
118-
}
95+
registerView: (viewId, viewName, containerId, containerName, icon): void => {
96+
const cssClass = `extensionViewlet-${containerId}`;
97+
const id = `workbench.view.extension.${containerId}`;
98+
class CustomViewlet extends ViewContainerViewlet {
99+
public constructor(
100+
@IConfigurationService configurationService: IConfigurationService,
101+
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
102+
@ITelemetryService telemetryService: ITelemetryService,
103+
@IWorkspaceContextService contextService: IWorkspaceContextService,
104+
@IStorageService storageService: IStorageService,
105+
@IEditorService _editorService: IEditorService,
106+
@IInstantiationService instantiationService: IInstantiationService,
107+
@IThemeService themeService: IThemeService,
108+
@IContextMenuService contextMenuService: IContextMenuService,
109+
@IExtensionService extensionService: IExtensionService,
110+
) {
111+
super(id, `${id}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
119112
}
113+
}
120114

121-
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(
122-
new ViewletDescriptor(CustomViewlet as any, id, containerName, cssClass, undefined, URI.parse(icon)),
123-
);
124-
125-
Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions).registerWorkbenchAction(
126-
new SyncActionDescriptor(OpenCustomViewletAction as any, id, localize("showViewlet", "Show {0}", containerName)),
127-
"View: Show {0}",
128-
localize("view", "View"),
129-
);
130-
131-
// Generate CSS to show the icon in the activity bar.
132-
const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${cssClass}`;
133-
createCSSRule(iconClass, `-webkit-mask: url('${icon}') no-repeat 50% 50%`);
134-
135-
const container = Registry.as<IViewContainersRegistry>(ViewsExtensions.ViewContainersRegistry).registerViewContainer(containerId);
136-
Registry.as<IViewsRegistry>(ViewsExtensions.ViewsRegistry).registerViews([{
137-
id: viewId,
138-
name: viewName,
139-
ctorDescriptor: { ctor: CustomTreeViewPanel },
140-
treeView: getService(IInstantiationService).createInstance(CustomTreeView as any, viewId, container),
141-
}] as ITreeViewDescriptor[], container);
142-
},
143-
menuRegistry: MenuRegistry as any,
144-
statusbarService: getService(IStatusbarService) as any,
145-
notificationService: getService(INotificationService),
146-
terminalService: getService(ITerminalService),
147-
onFileCreate: (cb): void => {
148-
getService<IFileService>(IFileService).onAfterOperation((e) => {
149-
if (e.operation === FileOperation.CREATE) {
150-
cb(e.resource.path);
151-
}
152-
});
153-
},
154-
onFileMove: (cb): void => {
155-
getService<IFileService>(IFileService).onAfterOperation((e) => {
156-
if (e.operation === FileOperation.MOVE) {
157-
cb(e.resource.path, e.target ? e.target.resource.path : undefined!);
158-
}
159-
});
160-
},
161-
onFileDelete: (cb): void => {
162-
getService<IFileService>(IFileService).onAfterOperation((e) => {
163-
if (e.operation === FileOperation.DELETE) {
164-
cb(e.resource.path);
165-
}
166-
});
167-
},
168-
onFileSaved: (cb): void => {
169-
getService<ITextFileService>(ITextFileService).models.onModelSaved((e) => {
170-
cb(e.resource.path);
171-
});
172-
},
173-
onFileCopy: (cb): void => {
174-
getService<IFileService>(IFileService).onAfterOperation((e) => {
175-
if (e.operation === FileOperation.COPY) {
176-
cb(e.resource.path, e.target ? e.target.resource.path : undefined!);
177-
}
178-
});
179-
},
180-
onModelAdded: (cb): void => {
181-
getService<IModelService>(IModelService).onModelAdded((e) => {
182-
cb(e.uri.path, e.getLanguageIdentifier().language);
183-
});
184-
},
185-
onModelRemoved: (cb): void => {
186-
getService<IModelService>(IModelService).onModelRemoved((e) => {
187-
cb(e.uri.path, e.getLanguageIdentifier().language);
188-
});
189-
},
190-
onModelLanguageChange: (cb): void => {
191-
getService<IModelService>(IModelService).onModelModeChanged((e) => {
192-
cb(e.model.uri.path, e.model.getLanguageIdentifier().language, e.oldModeId);
193-
});
194-
},
195-
onTerminalAdded: (cb): void => {
196-
getService<ITerminalService>(ITerminalService).onInstanceCreated(() => cb());
197-
},
198-
onTerminalRemoved: (cb): void => {
199-
getService<ITerminalService>(ITerminalService).onInstanceDisposed(() => cb());
200-
},
115+
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(
116+
new ViewletDescriptor(CustomViewlet as any, id, containerName, cssClass, undefined, URI.parse(icon)),
117+
);
118+
119+
Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions).registerWorkbenchAction(
120+
new SyncActionDescriptor(OpenCustomViewletAction as any, id, localize("showViewlet", "Show {0}", containerName)),
121+
"View: Show {0}",
122+
localize("view", "View"),
123+
);
124+
125+
// Generate CSS to show the icon in the activity bar.
126+
const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${cssClass}`;
127+
createCSSRule(iconClass, `-webkit-mask: url('${icon}') no-repeat 50% 50%`);
128+
129+
const container = Registry.as<IViewContainersRegistry>(ViewsExtensions.ViewContainersRegistry).registerViewContainer(containerId);
130+
Registry.as<IViewsRegistry>(ViewsExtensions.ViewsRegistry).registerViews([{
131+
id: viewId,
132+
name: viewName,
133+
ctorDescriptor: { ctor: CustomTreeViewPanel },
134+
treeView: getService(IInstantiationService).createInstance(CustomTreeView as any, viewId, container),
135+
}] as ITreeViewDescriptor[], container);
201136
},
202-
// @ts-ignore
203-
MenuId: MenuId,
204-
Severity: Severity,
205-
// @ts-ignore
206-
StatusbarAlignment: StatusbarAlignment,
207137
};
208138
};
209139

@@ -282,3 +212,59 @@ class FileSystemProvider implements IFileSystemProvider {
282212
throw new Error("not implemented");
283213
}
284214
}
215+
216+
class TreeViewDataProvider<T> implements ITreeViewDataProvider {
217+
private readonly root = Symbol("root");
218+
private readonly values = new Map<string, T>();
219+
private readonly children = new Map<T | Symbol, ITreeItem[]>();
220+
221+
public constructor(private readonly provider: vscode.TreeDataProvider<T>) {}
222+
223+
public async getChildren(item?: ITreeItem): Promise<ITreeItem[]> {
224+
const value = item && this.itemToValue(item);
225+
const children = await Promise.all(
226+
(await this.provider.getChildren(value) || [])
227+
.map(async (childValue) => {
228+
const treeItem = await this.provider.getTreeItem(childValue);
229+
const handle = this.createHandle(treeItem);
230+
this.values.set(handle, childValue);
231+
return {
232+
handle,
233+
collapsibleState: TreeItemCollapsibleState.Collapsed,
234+
};
235+
})
236+
);
237+
238+
this.clear(value || this.root, item);
239+
this.children.set(value || this.root, children);
240+
241+
return children;
242+
}
243+
244+
public dispose(): void {
245+
throw new Error("not implemented");
246+
}
247+
248+
private itemToValue(item: ITreeItem): T {
249+
if (!this.values.has(item.handle)) {
250+
throw new Error(`No element found with handle ${item.handle}`);
251+
}
252+
return this.values.get(item.handle)!;
253+
}
254+
255+
private clear(value: T | Symbol, item?: ITreeItem): void {
256+
if (this.children.has(value)) {
257+
this.children.get(value)!.map((c) => this.clear(this.itemToValue(c), c));
258+
this.children.delete(value);
259+
}
260+
if (item) {
261+
this.values.delete(item.handle);
262+
}
263+
}
264+
265+
private createHandle(item: vscode.TreeItem): string {
266+
return item.id
267+
? `coder-tree-item-id/${item.id}`
268+
: `coder-tree-item-uuid/${generateUuid()}`;
269+
}
270+
}

src/client.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,18 @@
1-
import 'vs/css!./media/firefox';
1+
import { coderApi, vscodeApi } from "vs/server/src/api";
2+
import "vs/css!./media/firefox";
3+
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
4+
5+
/**
6+
* This is called by vs/workbench/browser/web.main.ts after the workbench has
7+
* been initialized so we can initialize our own client-side code.
8+
*/
9+
export const initialize = (services: ServiceCollection): void => {
10+
const target = window as any;
11+
target.ide = coderApi(services);
12+
target.vscode = vscodeApi(services);
13+
14+
const event = new CustomEvent('ide-ready');
15+
(event as any).ide = target.ide;
16+
(event as any).vscode = target.vscode;
17+
window.dispatchEvent(event);
18+
};

0 commit comments

Comments
 (0)