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.
This commit is contained in:
Asher 2019-07-31 12:32:04 -05:00
parent 0315b004a7
commit 62719ab544
No known key found for this signature in database
GPG Key ID: D63C1EF81242354A
4 changed files with 138 additions and 355 deletions

View File

@ -573,34 +573,27 @@ index 5a758eb786..7fcacb5ca7 100644
templateData.actionBar.context = (<TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle }); templateData.actionBar.context = (<TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle });
templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false }); templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false });
diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts
index 1986fb6642..a3e4cbdb56 100644 index 1986fb6642..453d3e3e48 100644
--- a/src/vs/workbench/browser/web.main.ts --- a/src/vs/workbench/browser/web.main.ts
+++ b/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts
@@ -35,6 +35,7 @@ import { SignService } from 'vs/platform/sign/browser/signService'; @@ -35,6 +35,7 @@ import { SignService } from 'vs/platform/sign/browser/signService';
import { hash } from 'vs/base/common/hash'; import { hash } from 'vs/base/common/hash';
import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api';
import { ProductService } from 'vs/platform/product/browser/productService'; import { ProductService } from 'vs/platform/product/browser/productService';
+import { coderApi, vscodeApi } from 'vs/server/src/api'; +import { initialize } from 'vs/server/src/client';
class CodeRendererMain extends Disposable { class CodeRendererMain extends Disposable {
@@ -71,6 +72,15 @@ class CodeRendererMain extends Disposable { @@ -71,6 +72,8 @@ class CodeRendererMain extends Disposable {
// Startup // Startup
this.workbench.startup(); this.workbench.startup();
+ +
+ const target = window as any; + initialize(services.serviceCollection);
+ target.ide = coderApi(services.serviceCollection);
+ target.vscode = vscodeApi(services.serviceCollection);
+
+ const event = new CustomEvent('ide-ready');
+ (event as any).ide = target.ide;
+ (event as any).vscode = target.vscode;
+ window.dispatchEvent(event);
} }
private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService }> { private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService }> {
@@ -114,7 +124,8 @@ class CodeRendererMain extends Disposable { @@ -114,7 +117,8 @@ class CodeRendererMain extends Disposable {
const channel = connection.getChannel<IChannel>(REMOTE_FILE_SYSTEM_CHANNEL_NAME); const channel = connection.getChannel<IChannel>(REMOTE_FILE_SYSTEM_CHANNEL_NAME);
const remoteFileSystemProvider = this._register(new RemoteExtensionsFileSystemProvider(channel, remoteAgentService.getEnvironment())); const remoteFileSystemProvider = this._register(new RemoteExtensionsFileSystemProvider(channel, remoteAgentService.getEnvironment()));
@ -1422,7 +1415,7 @@ index 306d58f915..58c603ad3d 100644
if (definition.fontCharacter || definition.fontColor) { if (definition.fontCharacter || definition.fontColor) {
let body = ''; let body = '';
diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts
index c28adc0ad9..3d1adba3d9 100644 index c28adc0ad9..4517c308da 100644
--- a/src/vs/workbench/workbench.web.main.ts --- a/src/vs/workbench/workbench.web.main.ts
+++ b/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts
@@ -128,7 +128,7 @@ import 'vs/workbench/services/extensions/browser/extensionService'; @@ -128,7 +128,7 @@ import 'vs/workbench/services/extensions/browser/extensionService';
@ -1447,9 +1440,3 @@ index c28adc0ad9..3d1adba3d9 100644
// Output Panel // Output Panel
import 'vs/workbench/contrib/output/browser/output.contribution'; import 'vs/workbench/contrib/output/browser/output.contribution';
@@ -356,3 +356,5 @@ import 'vs/workbench/contrib/outline/browser/outline.contribution';
// import 'vs/workbench/contrib/issue/electron-browser/issue.contribution';
//#endregion
+
+import 'vs/server/src/client';

View File

@ -1,25 +1,19 @@
import * as vscode from "vscode"; import * as vscode from "vscode";
import { localize } from "vs/nls"; import { localize } from "vs/nls";
import { Action } from "vs/base/common/actions"; import { SyncActionDescriptor } from "vs/platform/actions/common/actions";
import { SyncActionDescriptor, MenuRegistry, MenuId } from "vs/platform/actions/common/actions";
import { Registry } from "vs/platform/registry/common/platform"; import { Registry } from "vs/platform/registry/common/platform";
import { IWorkbenchActionRegistry, Extensions as ActionExtensions} from "vs/workbench/common/actions"; import { IWorkbenchActionRegistry, Extensions as ActionExtensions} from "vs/workbench/common/actions";
import { CommandsRegistry, ICommandService } from "vs/platform/commands/common/commands"; import { CommandsRegistry, ICommandService } from "vs/platform/commands/common/commands";
import { IStat, IWatchOptions, FileOverwriteOptions, FileDeleteOptions, FileOpenOptions, IFileChange, FileWriteOptions, FileSystemProviderCapabilities, IFileService, FileType, FileOperation, IFileSystemProvider } from "vs/platform/files/common/files"; import { IStat, IWatchOptions, FileOverwriteOptions, FileDeleteOptions, FileOpenOptions, IFileChange, FileWriteOptions, FileSystemProviderCapabilities, IFileService, FileType, IFileSystemProvider } from "vs/platform/files/common/files";
import { ITextFileService } from "vs/workbench/services/textfile/common/textfiles";
import { IModelService } from "vs/editor/common/services/modelService";
import { ITerminalService } from "vs/workbench/contrib/terminal/common/terminal";
import { IStorageService } from "vs/platform/storage/common/storage"; import { IStorageService } from "vs/platform/storage/common/storage";
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection"; import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
import { INotificationService } from "vs/platform/notification/common/notification"; import { INotificationService } from "vs/platform/notification/common/notification";
import { IStatusbarService, StatusbarAlignment } from "vs/platform/statusbar/common/statusbar";
import Severity from "vs/base/common/severity";
import { Emitter, Event } from "vs/base/common/event"; import { Emitter, Event } from "vs/base/common/event";
import * as extHostTypes from "vs/workbench/api/common/extHostTypes"; import * as extHostTypes from "vs/workbench/api/common/extHostTypes";
import { ServiceIdentifier, IInstantiationService } from "vs/platform/instantiation/common/instantiation"; import { ServiceIdentifier, IInstantiationService } from "vs/platform/instantiation/common/instantiation";
import { URI } from "vs/base/common/uri"; import { URI } from "vs/base/common/uri";
import { ITreeViewDataProvider, IViewsRegistry, ITreeViewDescriptor, Extensions as ViewsExtensions, IViewContainersRegistry } from "vs/workbench/common/views"; import { ITreeItem, ITreeViewDataProvider, IViewsRegistry, ITreeViewDescriptor, Extensions as ViewsExtensions, IViewContainersRegistry, TreeItemCollapsibleState } from "vs/workbench/common/views";
import { CustomTreeViewPanel, CustomTreeView } from "vs/workbench/browser/parts/views/customView"; import { CustomTreeViewPanel, CustomTreeView } from "vs/workbench/browser/parts/views/customView";
import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction } from "vs/workbench/browser/viewlet"; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction } from "vs/workbench/browser/viewlet";
import { IExtensionService } from "vs/workbench/services/extensions/common/extensions"; import { IExtensionService } from "vs/workbench/services/extensions/common/extensions";
@ -35,6 +29,7 @@ import { IViewletService } from "vs/workbench/services/viewlet/browser/viewlet";
import { IEditorGroupsService } from "vs/workbench/services/editor/common/editorGroupsService"; import { IEditorGroupsService } from "vs/workbench/services/editor/common/editorGroupsService";
import { createCSSRule } from "vs/base/browser/dom"; import { createCSSRule } from "vs/base/browser/dom";
import { IDisposable } from "vs/base/common/lifecycle"; import { IDisposable } from "vs/base/common/lifecycle";
import { generateUuid } from "vs/base/common/uuid";
/** /**
* Client-side implementation of VS Code's API. * Client-side implementation of VS Code's API.
@ -42,7 +37,7 @@ import { IDisposable } from "vs/base/common/lifecycle";
* TODO: Implement menu items for views (for item actions). * TODO: Implement menu items for views (for item actions).
* TODO: File system provider doesn't work. * TODO: File system provider doesn't work.
*/ */
export const vscodeApi = (serviceCollection: ServiceCollection): typeof vscode => { export const vscodeApi = (serviceCollection: ServiceCollection): Partial<typeof vscode> => {
const getService = <T>(id: ServiceIdentifier<T>): T => serviceCollection.get<T>(id) as T; const getService = <T>(id: ServiceIdentifier<T>): T => serviceCollection.get<T>(id) as T;
const commandService = getService(ICommandService); const commandService = getService(ICommandService);
const notificationService = getService(INotificationService); const notificationService = getService(INotificationService);
@ -61,43 +56,42 @@ export const vscodeApi = (serviceCollection: ServiceCollection): typeof vscode =
FileType: FileType, FileType: FileType,
Uri: URI, Uri: URI,
commands: { commands: {
executeCommand: (commandId: string, ...args: any[]): any => { executeCommand: <T = any>(commandId: string, ...args: any[]): Promise<T | undefined> => {
return commandService.executeCommand(commandId, ...args); return commandService.executeCommand(commandId, ...args);
}, },
registerCommand: (id: string, command: () => void): any => { registerCommand: (id: string, command: (...args: any[]) => any): IDisposable => {
return CommandsRegistry.registerCommand(id, command); return CommandsRegistry.registerCommand(id, command);
}, },
}, } as Partial<typeof vscode.commands>,
window: { window: {
registerTreeDataProvider: (id: string, dataProvider: ITreeViewDataProvider): void => { registerTreeDataProvider: <T>(id: string, dataProvider: vscode.TreeDataProvider<T>): IDisposable => {
const tree = new TreeViewDataProvider(dataProvider);
const view = viewsRegistry.getView(id); const view = viewsRegistry.getView(id);
if (view) { (view as ITreeViewDescriptor).treeView.dataProvider = tree;
(view as ITreeViewDescriptor).treeView.dataProvider = dataProvider; return {
} dispose: () => tree.dispose(),
};
}, },
showErrorMessage: (message: string): void => { showErrorMessage: async (message: string): Promise<string | undefined> => {
notificationService.error(message); notificationService.error(message);
return undefined;
}, },
}, } as Partial<typeof vscode.window>,
workspace: { workspace: {
registerFileSystemProvider: (scheme: string, provider: vscode.FileSystemProvider): IDisposable => { registerFileSystemProvider: (scheme: string, provider: vscode.FileSystemProvider): IDisposable => {
return fileService.registerProvider(scheme, new FileSystemProvider(provider)); return fileService.registerProvider(scheme, new FileSystemProvider(provider));
}, },
}, } as Partial<typeof vscode.workspace>,
} as any; } as Partial<typeof vscode>; // Without this it complains that the type isn't `| undefined`.
}; };
/** /**
* Coder API. * Coder API. This should only provide functionality that can't be made
* available through the VS Code API.
*/ */
export const coderApi = (serviceCollection: ServiceCollection): typeof coder => { export const coderApi = (serviceCollection: ServiceCollection): typeof coder => {
const getService = <T>(id: ServiceIdentifier<T>): T => serviceCollection.get<T>(id) as T; const getService = <T>(id: ServiceIdentifier<T>): T => serviceCollection.get<T>(id) as T;
return { return {
workbench: {
action: Action,
syncActionDescriptor: SyncActionDescriptor,
commandRegistry: CommandsRegistry,
actionsRegistry: Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions),
registerView: (viewId, viewName, containerId, containerName, icon): void => { registerView: (viewId, viewName, containerId, containerName, icon): void => {
const cssClass = `extensionViewlet-${containerId}`; const cssClass = `extensionViewlet-${containerId}`;
const id = `workbench.view.extension.${containerId}`; const id = `workbench.view.extension.${containerId}`;
@ -140,70 +134,6 @@ export const coderApi = (serviceCollection: ServiceCollection): typeof coder =>
treeView: getService(IInstantiationService).createInstance(CustomTreeView as any, viewId, container), treeView: getService(IInstantiationService).createInstance(CustomTreeView as any, viewId, container),
}] as ITreeViewDescriptor[], container); }] as ITreeViewDescriptor[], container);
}, },
menuRegistry: MenuRegistry as any,
statusbarService: getService(IStatusbarService) as any,
notificationService: getService(INotificationService),
terminalService: getService(ITerminalService),
onFileCreate: (cb): void => {
getService<IFileService>(IFileService).onAfterOperation((e) => {
if (e.operation === FileOperation.CREATE) {
cb(e.resource.path);
}
});
},
onFileMove: (cb): void => {
getService<IFileService>(IFileService).onAfterOperation((e) => {
if (e.operation === FileOperation.MOVE) {
cb(e.resource.path, e.target ? e.target.resource.path : undefined!);
}
});
},
onFileDelete: (cb): void => {
getService<IFileService>(IFileService).onAfterOperation((e) => {
if (e.operation === FileOperation.DELETE) {
cb(e.resource.path);
}
});
},
onFileSaved: (cb): void => {
getService<ITextFileService>(ITextFileService).models.onModelSaved((e) => {
cb(e.resource.path);
});
},
onFileCopy: (cb): void => {
getService<IFileService>(IFileService).onAfterOperation((e) => {
if (e.operation === FileOperation.COPY) {
cb(e.resource.path, e.target ? e.target.resource.path : undefined!);
}
});
},
onModelAdded: (cb): void => {
getService<IModelService>(IModelService).onModelAdded((e) => {
cb(e.uri.path, e.getLanguageIdentifier().language);
});
},
onModelRemoved: (cb): void => {
getService<IModelService>(IModelService).onModelRemoved((e) => {
cb(e.uri.path, e.getLanguageIdentifier().language);
});
},
onModelLanguageChange: (cb): void => {
getService<IModelService>(IModelService).onModelModeChanged((e) => {
cb(e.model.uri.path, e.model.getLanguageIdentifier().language, e.oldModeId);
});
},
onTerminalAdded: (cb): void => {
getService<ITerminalService>(ITerminalService).onInstanceCreated(() => cb());
},
onTerminalRemoved: (cb): void => {
getService<ITerminalService>(ITerminalService).onInstanceDisposed(() => cb());
},
},
// @ts-ignore
MenuId: MenuId,
Severity: Severity,
// @ts-ignore
StatusbarAlignment: StatusbarAlignment,
}; };
}; };
@ -282,3 +212,59 @@ class FileSystemProvider implements IFileSystemProvider {
throw new Error("not implemented"); throw new Error("not implemented");
} }
} }
class TreeViewDataProvider<T> implements ITreeViewDataProvider {
private readonly root = Symbol("root");
private readonly values = new Map<string, T>();
private readonly children = new Map<T | Symbol, ITreeItem[]>();
public constructor(private readonly provider: vscode.TreeDataProvider<T>) {}
public async getChildren(item?: ITreeItem): Promise<ITreeItem[]> {
const value = item && this.itemToValue(item);
const children = await Promise.all(
(await this.provider.getChildren(value) || [])
.map(async (childValue) => {
const treeItem = await this.provider.getTreeItem(childValue);
const handle = this.createHandle(treeItem);
this.values.set(handle, childValue);
return {
handle,
collapsibleState: TreeItemCollapsibleState.Collapsed,
};
})
);
this.clear(value || this.root, item);
this.children.set(value || this.root, children);
return children;
}
public dispose(): void {
throw new Error("not implemented");
}
private itemToValue(item: ITreeItem): T {
if (!this.values.has(item.handle)) {
throw new Error(`No element found with handle ${item.handle}`);
}
return this.values.get(item.handle)!;
}
private clear(value: T | Symbol, item?: ITreeItem): void {
if (this.children.has(value)) {
this.children.get(value)!.map((c) => this.clear(this.itemToValue(c), c));
this.children.delete(value);
}
if (item) {
this.values.delete(item.handle);
}
}
private createHandle(item: vscode.TreeItem): string {
return item.id
? `coder-tree-item-id/${item.id}`
: `coder-tree-item-uuid/${generateUuid()}`;
}
}

View File

@ -1 +1,18 @@
import 'vs/css!./media/firefox'; import { coderApi, vscodeApi } from "vs/server/src/api";
import "vs/css!./media/firefox";
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
/**
* This is called by vs/workbench/browser/web.main.ts after the workbench has
* been initialized so we can initialize our own client-side code.
*/
export const initialize = (services: ServiceCollection): void => {
const target = window as any;
target.ide = coderApi(services);
target.vscode = vscodeApi(services);
const event = new CustomEvent('ide-ready');
(event as any).ide = target.ide;
(event as any).vscode = target.vscode;
window.dispatchEvent(event);
};

207
typings/coder.d.ts vendored
View File

@ -1,210 +1,3 @@
declare namespace coder { declare namespace coder {
export interface IDisposable {
dispose(): void;
}
export interface Disposer extends IDisposable {
onDidDispose: (cb: () => void) => void;
}
export interface Event<T> {
(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable;
}
export interface IStatusbarEntry {
readonly text: string;
readonly tooltip?: string;
readonly color?: string;
readonly command?: string;
readonly arguments?: any[];
readonly showBeak?: boolean;
}
export interface IStatusbarService {
addEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority?: number): IDisposable;
setStatusMessage(message: string, autoDisposeAfter?: number, delayBy?: number): IDisposable;
}
export interface IAction extends IDisposable {
id: string;
label: string;
tooltip: string;
class: string | undefined;
enabled: boolean;
checked: boolean;
radio: boolean;
run(event?: any): Promise<any>;
}
export type NotificationMessage = string | Error;
export interface INotificationProperties {
sticky?: boolean;
silent?: boolean;
}
export interface INotificationActions {
primary?: IAction[];
secondary?: IAction[];
}
export interface INotificationProgress {
infinite(): void;
total(value: number): void;
worked(value: number): void;
done(): void;
}
export interface IPromptChoice {
label: string;
isSecondary?: boolean;
keepOpen?: boolean;
run: () => void;
}
export interface IPromptOptions extends INotificationProperties {
onCancel?: () => void;
}
export interface ISerializableCommandAction extends IBaseCommandAction {
// iconLocation?: { dark: UriComponents; light?: UriComponents; };
}
export interface IMenuItem {
command: ICommandAction;
alt?: ICommandAction;
// when?: ContextKeyExpr;
group?: "navigation" | string;
order?: number;
}
export interface IMenuRegistry {
appendMenuItem(menu: MenuId, item: IMenuItem): IDisposable;
}
export interface IBaseCommandAction {
id: string;
title: string;
category?: string;
}
export interface ICommandAction extends IBaseCommandAction {
// iconLocation?: { dark: URI; light?: URI; };
// precondition?: ContextKeyExpr;
// toggled?: ContextKeyExpr;
}
export interface ICommandHandler {
(accessor: any, ...args: any[]): void;
}
export interface ICommand {
id: string;
handler: ICommandHandler;
description?: ICommandHandlerDescription | null;
}
export interface ICommandHandlerDescription {
description: string;
args: { name: string; description?: string; }[];
returns?: string;
}
export interface ICommandRegistry {
registerCommand(command: ICommand): IDisposable;
}
export interface INotification extends INotificationProperties {
severity: Severity;
message: NotificationMessage;
source?: string;
actions?: INotificationActions;
}
export interface INotificationHandle {
readonly onDidClose: Event<void>;
readonly progress: INotificationProgress;
updateSeverity(severity: Severity): void;
updateMessage(message: NotificationMessage): void;
updateActions(actions?: INotificationActions): void;
close(): void;
}
export interface INotificationService {
notify(notification: INotification): INotificationHandle;
info(message: NotificationMessage | NotificationMessage[]): void;
warn(message: NotificationMessage | NotificationMessage[]): void;
error(message: NotificationMessage | NotificationMessage[]): void;
prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle;
}
export namespace client {}
export namespace workbench {
// TODO: these types won't actually be included in the package if we try to
// import them. We'll need to recreate them.
export const action: any; // import { Action } from "vs/base/common/actions";
export const syncActionDescriptor: any; // import { SyncActionDescriptor } from "vs/platform/actions/common/actions";
export const statusbarService: IStatusbarService;
export const actionsRegistry: any; // import { IWorkbenchActionRegistry } from "vs/workbench/common/actions";
export const notificationService: INotificationService;
export const menuRegistry: IMenuRegistry;
export const commandRegistry: ICommandRegistry;
export const terminalService: any; // import { ITerminalService } from "vs/workbench/contrib/terminal/common/terminal";
export const registerView: (viewId: string, viewName: string, containerId: string, containerName: string, icon: string) => void; export const registerView: (viewId: string, viewName: string, containerId: string, containerName: string, icon: string) => void;
export const onFileCreate: (cb: (path: string) => void) => void;
export const onFileMove: (cb: (path: string, target: string) => void) => void;
export const onFileDelete: (cb: (path: string) => void) => void;
export const onFileSaved: (cb: (path: string) => void) => void;
export const onFileCopy: (cb: (path: string, target: string) => void) => void;
export const onModelAdded: (cb: (path: string, languageId: string) => void) => void;
export const onModelRemoved: (cb: (path: string, languageId: string) => void) => void;
export const onModelLanguageChange: (cb: (path: string, languageId: string, oldLanguageId: string) => void) => void;
export const onTerminalAdded: (cb: () => void) => void;
export const onTerminalRemoved: (cb: () => void) => void;
}
export enum Severity {
Ignore = 0,
Info = 1,
Warning = 2,
Error = 3,
}
export enum StatusbarAlignment {
LEFT, RIGHT,
}
export enum MenuId {
CommandPalette,
DebugBreakpointsContext,
DebugCallStackContext,
DebugConsoleContext,
DebugVariablesContext,
DebugWatchContext,
DebugToolBar,
EditorContext,
EditorTitle,
EditorTitleContext,
EmptyEditorGroupContext,
ExplorerContext,
MenubarAppearanceMenu,
MenubarDebugMenu,
MenubarEditMenu,
MenubarFileMenu,
MenubarGoMenu,
MenubarHelpMenu,
MenubarLayoutMenu,
MenubarNewBreakpointMenu,
MenubarPreferencesMenu,
MenubarRecentMenu,
MenubarSelectionMenu,
MenubarSwitchEditorMenu,
MenubarSwitchGroupMenu,
MenubarTerminalMenu,
MenubarViewMenu,
OpenEditorsContext,
ProblemsPanelContext,
SCMChangeContext,
SCMResourceContext,
SCMResourceGroupContext,
SCMSourceControl,
SCMTitle,
SearchContext,
StatusBarWindowIndicatorMenu,
TouchBarContext,
ViewItemContext,
ViewTitle,
}
} }