Merge from vscode 4636be2b71c87bfb0bfe3c94278b447a5efcc1f1 (#8722)

* Merge from vscode 4636be2b71c87bfb0bfe3c94278b447a5efcc1f1

* remove tests that aren't working
This commit is contained in:
Anthony Dresser
2019-12-18 00:14:28 -08:00
committed by GitHub
parent 0fd870d156
commit 30d9e9c141
289 changed files with 5537 additions and 3039 deletions

View File

@@ -3,14 +3,14 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI, UriComponents } from 'vs/base/common/uri';
import { URI } from 'vs/base/common/uri';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, getScopes } from 'vs/platform/configuration/common/configurationRegistry';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { MainThreadConfigurationShape, MainContext, ExtHostContext, IExtHostContext, IConfigurationInitData } from '../common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationTarget, IConfigurationService, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@extHostNamedCustomer(MainContext.MainThreadConfiguration)
@@ -45,25 +45,45 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape {
this._configurationListener.dispose();
}
$updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, resourceUriComponenets: UriComponents | undefined): Promise<void> {
const resource = resourceUriComponenets ? URI.revive(resourceUriComponenets) : null;
return this.writeConfiguration(target, key, value, resource);
$updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise<void> {
overrides = { resource: overrides?.resource ? URI.revive(overrides.resource) : undefined, overrideIdentifier: overrides?.overrideIdentifier };
return this.writeConfiguration(target, key, value, overrides, scopeToLanguage);
}
$removeConfigurationOption(target: ConfigurationTarget | null, key: string, resourceUriComponenets: UriComponents | undefined): Promise<void> {
const resource = resourceUriComponenets ? URI.revive(resourceUriComponenets) : null;
return this.writeConfiguration(target, key, undefined, resource);
$removeConfigurationOption(target: ConfigurationTarget | null, key: string, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise<void> {
overrides = { resource: overrides?.resource ? URI.revive(overrides.resource) : undefined, overrideIdentifier: overrides?.overrideIdentifier };
return this.writeConfiguration(target, key, undefined, overrides, scopeToLanguage);
}
private writeConfiguration(target: ConfigurationTarget | null, key: string, value: any, resource: URI | null): Promise<void> {
target = target !== null && target !== undefined ? target : this.deriveConfigurationTarget(key, resource);
return this.configurationService.updateValue(key, value, { resource }, target, true);
private writeConfiguration(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides, scopeToLanguage: boolean | undefined): Promise<void> {
target = target !== null && target !== undefined ? target : this.deriveConfigurationTarget(key, overrides);
const configurationValue = this.configurationService.inspect(key, overrides);
switch (target) {
case ConfigurationTarget.MEMORY:
return this._updateValue(key, value, target, configurationValue?.memory?.override, overrides, scopeToLanguage);
case ConfigurationTarget.WORKSPACE_FOLDER:
return this._updateValue(key, value, target, configurationValue?.workspaceFolder?.override, overrides, scopeToLanguage);
case ConfigurationTarget.WORKSPACE:
return this._updateValue(key, value, target, configurationValue?.workspace?.override, overrides, scopeToLanguage);
case ConfigurationTarget.USER_REMOTE:
return this._updateValue(key, value, target, configurationValue?.userRemote?.override, overrides, scopeToLanguage);
default:
return this._updateValue(key, value, target, configurationValue?.userLocal?.override, overrides, scopeToLanguage);
}
}
private deriveConfigurationTarget(key: string, resource: URI | null): ConfigurationTarget {
if (resource && this._workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
private _updateValue(key: string, value: any, configurationTarget: ConfigurationTarget, overriddenValue: any | undefined, overrides: IConfigurationOverrides, scopeToLanguage: boolean | undefined): Promise<void> {
overrides = scopeToLanguage === true ? overrides
: scopeToLanguage === false ? { resource: overrides.resource }
: overrides.overrideIdentifier && overriddenValue !== undefined ? overrides
: { resource: overrides.resource };
return this.configurationService.updateValue(key, value, overrides, configurationTarget);
}
private deriveConfigurationTarget(key: string, overrides: IConfigurationOverrides): ConfigurationTarget {
if (overrides.resource && this._workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
const configurationProperties = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
if (configurationProperties[key] && configurationProperties[key].scope === ConfigurationScope.RESOURCE) {
if (configurationProperties[key] && (configurationProperties[key].scope === ConfigurationScope.RESOURCE || configurationProperties[key].scope === ConfigurationScope.RESOURCE_LANGUAGE)) {
return ConfigurationTarget.WORKSPACE_FOLDER;
}
}

View File

@@ -506,13 +506,17 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
this._registrations.set(handle, callh.CallHierarchyProviderRegistry.register(selector, {
prepareCallHierarchy: async (document, position, token) => {
const item = await this._proxy.$prepareCallHierarchy(handle, document.uri, position, token);
if (!item) {
const items = await this._proxy.$prepareCallHierarchy(handle, document.uri, position, token);
if (!items) {
return undefined;
}
return {
dispose: () => this._proxy.$releaseCallHierarchy(handle, item._sessionId),
root: MainThreadLanguageFeatures._reviveCallHierarchyItemDto(item)
dispose: () => {
for (const item of items) {
this._proxy.$releaseCallHierarchy(handle, item._sessionId);
}
},
roots: items.map(MainThreadLanguageFeatures._reviveCallHierarchyItemDto)
};
},

View File

@@ -514,15 +514,19 @@ export class MainThreadTask implements MainThreadTaskShape {
if (TaskHandleDTO.is(value)) {
const workspaceFolder = this._workspaceContextServer.getWorkspaceFolder(URI.revive(value.workspaceFolder));
if (workspaceFolder) {
this._taskService.getTask(workspaceFolder, value.id, true).then((task: Task) => {
this._taskService.run(task).then(undefined, reason => {
// eat the error, it has already been surfaced to the user and we don't care about it here
});
const result: TaskExecutionDTO = {
id: value.id,
task: TaskDTO.from(task)
};
resolve(result);
this._taskService.getTask(workspaceFolder, value.id, true).then((task: Task | undefined) => {
if (!task) {
reject(new Error('Task not found'));
} else {
this._taskService.run(task).then(undefined, reason => {
// eat the error, it has already been surfaced to the user and we don't care about it here
});
const result: TaskExecutionDTO = {
id: value.id,
task: TaskDTO.from(task)
};
resolve(result);
}
}, (_error) => {
reject(new Error('Task not found'));
});

View File

@@ -7,6 +7,7 @@ import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostCont
import { TunnelOptions, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ITunnelProvider, ITunnelService } from 'vs/platform/remote/common/tunnel';
@extHostNamedCustomer(MainContext.MainThreadTunnelService)
export class MainThreadTunnelService implements MainThreadTunnelServiceShape {
@@ -14,7 +15,8 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape {
constructor(
extHostContext: IExtHostContext,
@IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService
@IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService,
@ITunnelService private readonly tunnelService: ITunnelService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTunnelService);
}
@@ -22,7 +24,7 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape {
async $openTunnel(tunnelOptions: TunnelOptions): Promise<TunnelDto | undefined> {
const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remote.port, tunnelOptions.localPort, tunnelOptions.name);
if (tunnel) {
return { remote: { host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }, localAddress: tunnel.localAddress };
return TunnelDto.fromServiceTunnel(tunnel);
}
return undefined;
}
@@ -31,14 +33,32 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape {
return this.remoteExplorerService.close(remotePort);
}
$addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[]): Promise<void> {
return Promise.resolve(this.remoteExplorerService.addDetected(tunnels));
}
async $registerCandidateFinder(): Promise<void> {
this.remoteExplorerService.registerCandidateFinder(() => this._proxy.$findCandidatePorts());
}
async $setTunnelProvider(): Promise<void> {
const tunnelProvider: ITunnelProvider = {
forwardPort: (tunnelOptions: TunnelOptions) => {
const forward = this._proxy.$forwardPort(tunnelOptions);
if (forward) {
return forward.then(tunnel => {
return {
tunnelRemotePort: tunnel.remote.port,
tunnelRemoteHost: tunnel.remote.host,
localAddress: tunnel.localAddress,
dispose: () => {
this._proxy.$closeTunnel({ host: tunnel.remote.host, port: tunnel.remote.port });
}
};
});
}
return undefined;
}
};
this.tunnelService.setTunnelProvider(tunnelProvider);
}
dispose(): void {
//
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { onUnexpectedError } from 'vs/base/common/errors';
import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { isWeb } from 'vs/base/common/platform';
import { startsWith } from 'vs/base/common/strings';
@@ -20,7 +20,7 @@ import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } fr
import { IEditorInput } from 'vs/workbench/common/editor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput';
import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput';
import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService';
@@ -95,6 +95,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
private readonly _webviewInputs = new WebviewInputStore();
private readonly _revivers = new Map<string, IDisposable>();
private readonly _editorProviders = new Map<string, IDisposable>();
private readonly _customEditorModels = new Map<ICustomEditorModel, { referenceCount: number }>();
constructor(
context: extHostProtocol.IExtHostContext,
@@ -252,7 +253,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
this._revivers.delete(viewType);
}
public $registerEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void {
public $registerEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: readonly extHostProtocol.WebviewEditorCapabilities[]): void {
if (this._editorProviders.has(viewType)) {
throw new Error(`Provider for ${viewType} already registered`);
}
@@ -270,15 +271,16 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
webviewInput.webview.options = options;
webviewInput.webview.extension = extension;
const resource = webviewInput.getResource();
const model = await this.getModel(webviewInput);
const model = await this.retainCustomEditorModel(webviewInput, resource, viewType, capabilities);
webviewInput.onDisposeWebview(() => {
this._customEditorService.models.disposeModel(model);
this.releaseCustomEditorModel(model);
});
try {
await this._proxy.$resolveWebviewEditor(
{ resource: webviewInput.getResource(), edits: model.currentEdits },
resource,
handle,
viewType,
webviewInput.getTitle(),
@@ -304,43 +306,62 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
this._editorProviders.delete(viewType);
}
public async $registerCapabilities(handle: extHostProtocol.WebviewPanelHandle, capabilities: readonly extHostProtocol.WebviewEditorCapabilities[]): Promise<void> {
const webviewInput = this.getWebviewInput(handle);
const model = await this.getModel(webviewInput);
private async retainCustomEditorModel(webviewInput: WebviewInput, resource: URI, viewType: string, capabilities: readonly extHostProtocol.WebviewEditorCapabilities[]) {
const model = await this._customEditorService.models.loadOrCreate(webviewInput.getResource(), webviewInput.viewType);
const existingEntry = this._customEditorModels.get(model);
if (existingEntry) {
++existingEntry.referenceCount;
// no need to hook up listeners again
return model;
}
this._customEditorModels.set(model, { referenceCount: 1 });
const capabilitiesSet = new Set(capabilities);
if (capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.Editable)) {
model.onUndo(edits => { this._proxy.$undoEdits(handle, edits.map(x => x.data)); });
model.onUndo(edits => {
this._proxy.$undoEdits(resource, viewType, edits.map(x => x.data));
});
model.onApplyEdit(edits => {
const editsToApply = edits.filter(x => x.source !== webviewInput).map(x => x.data);
const editsToApply = edits.filter(x => x.source !== model).map(x => x.data);
if (editsToApply.length) {
this._proxy.$applyEdits(handle, editsToApply);
this._proxy.$applyEdits(resource, viewType, editsToApply);
}
});
model.onWillSave(e => { e.waitUntil(this._proxy.$onSave(handle)); });
model.onWillSave(e => {
e.waitUntil(this._proxy.$onSave(resource.toJSON(), viewType));
});
model.onWillSaveAs(e => { e.waitUntil(this._proxy.$onSaveAs(handle, e.resource.toJSON(), e.targetResource.toJSON())); });
model.onWillSaveAs(e => {
e.waitUntil(this._proxy.$onSaveAs(e.resource.toJSON(), viewType, e.targetResource.toJSON()));
});
}
return model;
}
private async releaseCustomEditorModel(model: ICustomEditorModel) {
const entry = this._customEditorModels.get(model);
if (!entry) {
return;
}
--entry.referenceCount;
if (entry.referenceCount <= 0) {
this._customEditorService.models.disposeModel(model);
this._customEditorModels.delete(model);
}
}
private getModel(webviewInput: WebviewInput) {
return this._customEditorService.models.loadOrCreate(webviewInput.getResource(), webviewInput.viewType);
}
public $onEdit(handle: extHostProtocol.WebviewPanelHandle, editData: any): void {
const webview = this.getWebviewInput(handle);
if (!(webview instanceof CustomFileEditorInput)) {
throw new Error('Webview is not a webview editor');
}
const model = this._customEditorService.models.get(webview.getResource(), webview.viewType);
public $onEdit(resource: UriComponents, viewType: string, editData: any): void {
const model = this._customEditorService.models.get(URI.revive(resource), viewType);
if (!model) {
throw new Error('Could not find model for webview editor');
}
model.makeEdit({ source: webview, data: editData });
model.pushEdit({ source: model, data: editData });
}
private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) {

View File

@@ -8,7 +8,7 @@ import { forEach } from 'vs/base/common/collections';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import * as resources from 'vs/base/common/resources';
import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor } from 'vs/workbench/common/views';
import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation } from 'vs/workbench/common/views';
import { CustomTreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/customView';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { coalesce, } from 'vs/base/common/arrays';
@@ -313,7 +313,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
if (!viewContainer) {
viewContainer = this.viewContainersRegistry.registerViewContainer(id, true, extensionId);
viewContainer = this.viewContainersRegistry.registerViewContainer(id, ViewContainerLocation.Sidebar, true, extensionId);
class CustomViewPaneContainer extends ViewPaneContainer {
constructor(

View File

@@ -8,7 +8,7 @@ import * as objects from 'vs/base/common/objects';
import { Registry } from 'vs/platform/registry/common/platform';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { IConfigurationNode, IConfigurationRegistry, Extensions, editorConfigurationSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { workspaceSettingsSchemaId, launchSchemaId, tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { isObject } from 'vs/base/common/types';
@@ -48,7 +48,7 @@ const configurationEntrySchema: IJSONSchema = {
nls.localize('scope.resource.description', "Configuration that can be configured in the user, remote, workspace or folder settings."),
nls.localize('scope.machine-overridable.description', "Machine configuration that can be configured also in workspace or folder settings.")
],
description: nls.localize('scope.description', "Scope in which the configuration is applicable. Available scopes are `application`, `machine`, `window`, `resource` and `machine-overridable`.")
description: nls.localize('scope.description', "Scope in which the configuration is applicable. Available scopes are `application`, `machine`, `window`, `resource`, and `machine-overridable`.")
},
enumDescriptions: {
type: 'array',
@@ -57,12 +57,12 @@ const configurationEntrySchema: IJSONSchema = {
},
description: nls.localize('scope.enumDescriptions', 'Descriptions for enum values')
},
markdownEnumDescription: {
markdownEnumDescriptions: {
type: 'array',
items: {
type: 'string',
},
description: nls.localize('scope.markdownEnumDescription', 'Descriptions for enum values in the markdown format.')
description: nls.localize('scope.markdownEnumDescriptions', 'Descriptions for enum values in the markdown format.')
},
markdownDescription: {
type: 'string',
@@ -90,7 +90,7 @@ const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint<I
'\\[.*\\]$': {
type: 'object',
default: {},
$ref: editorConfigurationSchemaId,
$ref: resourceLanguageSettingsSchemaId,
}
}
}
@@ -218,6 +218,8 @@ function validateProperties(configuration: IConfigurationNode, extension: IExten
propertyConfiguration.scope = ConfigurationScope.RESOURCE;
} else if (propertyConfiguration.scope.toString() === 'machine-overridable') {
propertyConfiguration.scope = ConfigurationScope.MACHINE_OVERRIDABLE;
} else if (propertyConfiguration.scope.toString() === 'resource-language') {
propertyConfiguration.scope = ConfigurationScope.RESOURCE_LANGUAGE;
} else {
propertyConfiguration.scope = ConfigurationScope.WINDOW;
}

View File

@@ -538,9 +538,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
registerWebviewPanelSerializer: (viewType: string, serializer: vscode.WebviewPanelSerializer) => {
return extHostWebviews.registerWebviewPanelSerializer(extension, viewType, serializer);
},
registerWebviewEditorProvider: (viewType: string, provider: vscode.WebviewEditorProvider, options?: vscode.WebviewPanelOptions) => {
registerWebviewCustomEditorProvider: (viewType: string, provider: vscode.WebviewCustomEditorProvider, options?: vscode.WebviewPanelOptions) => {
checkProposedApiEnabled(extension);
return extHostWebviews.registerWebviewEditorProvider(extension, viewType, provider, options);
return extHostWebviews.registerWebviewCustomEditorProvider(extension, viewType, provider, options);
},
registerDecorationProvider(provider: vscode.DecorationProvider) {
checkProposedApiEnabled(extension);
@@ -670,9 +670,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
onDidChangeConfiguration: (listener: (_: any) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) => {
return configProvider.onDidChangeConfiguration(listener, thisArgs, disposables);
},
getConfiguration(section?: string, resource?: vscode.Uri): vscode.WorkspaceConfiguration {
resource = arguments.length === 1 ? undefined : resource;
return configProvider.getConfiguration(section, resource, extension.identifier);
getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null): vscode.WorkspaceConfiguration {
scope = arguments.length === 1 ? undefined : scope;
return configProvider.getConfiguration(section, scope, extension.identifier);
},
registerTextDocumentContentProvider(scheme: string, provider: vscode.TextDocumentContentProvider) {
return extHostDocumentContentProviders.registerTextDocumentContentProvider(scheme, provider);

View File

@@ -22,7 +22,7 @@ import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel';
import * as modes from 'vs/editor/common/modes';
import { CharacterPair, CommentRule, EnterAction } from 'vs/editor/common/modes/languageConfiguration';
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
import { ConfigurationTarget, IConfigurationData, IConfigurationChange } from 'vs/platform/configuration/common/configuration';
import { ConfigurationTarget, IConfigurationData, IConfigurationChange, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import * as files from 'vs/platform/files/common/files';
@@ -151,8 +151,8 @@ export interface MainThreadCommentsShape extends IDisposable {
}
export interface MainThreadConfigurationShape extends IDisposable {
$updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, resource: UriComponents | undefined): Promise<void>;
$removeConfigurationOption(target: ConfigurationTarget | null, key: string, resource: UriComponents | undefined): Promise<void>;
$updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise<void>;
$removeConfigurationOption(target: ConfigurationTarget | null, key: string, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise<void>;
}
export interface MainThreadDiagnosticsShape extends IDisposable {
@@ -576,11 +576,10 @@ export interface MainThreadWebviewsShape extends IDisposable {
$registerSerializer(viewType: string): void;
$unregisterSerializer(viewType: string): void;
$registerEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void;
$registerEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: readonly WebviewEditorCapabilities[]): void;
$unregisterEditorProvider(viewType: string): void;
$registerCapabilities(handle: WebviewPanelHandle, capabilities: readonly WebviewEditorCapabilities[]): void;
$onEdit(handle: WebviewPanelHandle, editJson: any): void;
$onEdit(resource: UriComponents, viewType: string, editJson: any): void;
}
export interface WebviewPanelViewStateData {
@@ -598,13 +597,13 @@ export interface ExtHostWebviewsShape {
$onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise<void>;
$deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
$resolveWebviewEditor(input: { resource: UriComponents, edits: readonly any[] }, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
$resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
$undoEdits(handle: WebviewPanelHandle, edits: readonly any[]): void;
$applyEdits(handle: WebviewPanelHandle, edits: readonly any[]): void;
$undoEdits(resource: UriComponents, viewType: string, edits: readonly any[]): void;
$applyEdits(resource: UriComponents, viewType: string, edits: readonly any[]): void;
$onSave(handle: WebviewPanelHandle): Promise<void>;
$onSaveAs(handle: WebviewPanelHandle, resource: UriComponents, targetResource: UriComponents): Promise<void>;
$onSave(resource: UriComponents, viewType: string): Promise<void>;
$onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise<void>;
}
export interface MainThreadUrlsShape extends IDisposable {
@@ -780,8 +779,8 @@ export interface MainThreadWindowShape extends IDisposable {
export interface MainThreadTunnelServiceShape extends IDisposable {
$openTunnel(tunnelOptions: TunnelOptions): Promise<TunnelDto | undefined>;
$closeTunnel(remotePort: number): Promise<void>;
$addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[]): Promise<void>;
$registerCandidateFinder(): Promise<void>;
$setTunnelProvider(): Promise<void>;
}
// -- extension host
@@ -1194,7 +1193,7 @@ export interface ExtHostLanguageFeaturesShape {
$provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo, token: CancellationToken): Promise<modes.IColorPresentation[] | undefined>;
$provideFoldingRanges(handle: number, resource: UriComponents, context: modes.FoldingContext, token: CancellationToken): Promise<modes.FoldingRange[] | undefined>;
$provideSelectionRanges(handle: number, resource: UriComponents, positions: IPosition[], token: CancellationToken): Promise<modes.SelectionRange[][]>;
$prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<ICallHierarchyItemDto | undefined>;
$prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<ICallHierarchyItemDto[] | undefined>;
$provideCallHierarchyIncomingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise<IIncomingCallDto[] | undefined>;
$provideCallHierarchyOutgoingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise<IOutgoingCallDto[] | undefined>;
$releaseCallHierarchy(handle: number, sessionId: string): void;
@@ -1401,6 +1400,8 @@ export interface ExtHostStorageShape {
export interface ExtHostTunnelServiceShape {
$findCandidatePorts(): Promise<{ port: number, detail: string }[]>;
$forwardPort(tunnelOptions: TunnelOptions): Promise<TunnelDto> | undefined;
$closeTunnel(remote: { host: string, port: number }): Promise<void>;
}
// --- proxy identifiers

View File

@@ -4,13 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import { mixin, deepClone } from 'vs/base/common/objects';
import { URI } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import * as vscode from 'vscode';
import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { ExtHostConfigurationShape, MainThreadConfigurationShape, IConfigurationInitData, MainContext } from './extHost.protocol';
import { ConfigurationTarget as ExtHostConfigurationTarget } from './extHostTypes';
import { ConfigurationTarget, IConfigurationChange, IConfigurationData } from 'vs/platform/configuration/common/configuration';
import { ConfigurationTarget, IConfigurationChange, IConfigurationData, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration';
import { Configuration, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels';
import { ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
import { isObject } from 'vs/base/common/types';
@@ -20,6 +19,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { ILogService } from 'vs/platform/log/common/log';
import { Workspace } from 'vs/platform/workspace/common/workspace';
import { URI } from 'vs/base/common/uri';
function lookUp(tree: any, key: string) {
if (key) {
@@ -34,12 +34,57 @@ function lookUp(tree: any, key: string) {
type ConfigurationInspect<T> = {
key: string;
defaultValue?: T;
globalValue?: T;
workspaceValue?: T;
workspaceFolderValue?: T;
workspaceValue?: T,
workspaceFolderValue?: T,
defaultLanguageValue?: T;
userLanguageValue?: T;
workspaceLanguageValue?: T;
workspaceFolderLanguageValue?: T;
};
function isTextDocument(thing: any): thing is vscode.TextDocument {
return thing
&& thing.uri instanceof URI
&& (!thing.languageId || typeof thing.languageId === 'string');
}
function isWorkspaceFolder(thing: any): thing is vscode.WorkspaceFolder {
return thing
&& thing.uri instanceof URI
&& (!thing.name || typeof thing.name === 'string')
&& (!thing.index || typeof thing.index === 'number');
}
function isUri(thing: any): thing is vscode.Uri {
return thing instanceof URI;
}
function isResourceLanguage(thing: any): thing is { resource: URI, languageId: string } {
return thing
&& thing.resource instanceof URI
&& (!thing.languageId || typeof thing.languageId === 'string');
}
function scopeToOverrides(scope: vscode.ConfigurationScope | undefined | null): IConfigurationOverrides | undefined {
if (isUri(scope)) {
return { resource: scope };
}
if (isWorkspaceFolder(scope)) {
return { resource: scope.uri };
}
if (isTextDocument(scope)) {
return { resource: scope.uri, overrideIdentifier: scope.languageId };
}
if (isResourceLanguage(scope)) {
return scope;
}
return undefined;
}
export class ExtHostConfiguration implements ExtHostConfigurationShape {
readonly _serviceBrand: undefined;
@@ -104,13 +149,14 @@ export class ExtHostConfigProvider {
this._onDidChangeConfiguration.fire(this._toConfigurationChangeEvent(change, previous));
}
getConfiguration(section?: string, resource?: URI, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration {
getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration {
const overrides = scopeToOverrides(scope) || {};
const config = this._toReadonlyValue(section
? lookUp(this._configuration.getValue(undefined, { resource }, this._extHostWorkspace.workspace), section)
: this._configuration.getValue(undefined, { resource }, this._extHostWorkspace.workspace));
? lookUp(this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace), section)
: this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace));
if (section) {
this._validateConfigurationAccess(section, resource, extensionId);
this._validateConfigurationAccess(section, overrides, extensionId);
}
function parseConfigurationTarget(arg: boolean | ExtHostConfigurationTarget): ConfigurationTarget | null {
@@ -133,7 +179,7 @@ export class ExtHostConfigProvider {
return typeof lookUp(config, key) !== 'undefined';
},
get: <T>(key: string, defaultValue?: T) => {
this._validateConfigurationAccess(section ? `${section}.${key}` : key, resource, extensionId);
this._validateConfigurationAccess(section ? `${section}.${key}` : key, overrides, extensionId);
let result = lookUp(config, key);
if (typeof result === 'undefined') {
result = defaultValue;
@@ -189,25 +235,31 @@ export class ExtHostConfigProvider {
}
return result;
},
update: (key: string, value: any, arg: ExtHostConfigurationTarget | boolean) => {
update: (key: string, value: any, extHostConfigurationTarget: ExtHostConfigurationTarget | boolean, scopeToLanguage?: boolean) => {
key = section ? `${section}.${key}` : key;
const target = parseConfigurationTarget(arg);
const target = parseConfigurationTarget(extHostConfigurationTarget);
if (value !== undefined) {
return this._proxy.$updateConfigurationOption(target, key, value, resource);
return this._proxy.$updateConfigurationOption(target, key, value, overrides, scopeToLanguage);
} else {
return this._proxy.$removeConfigurationOption(target, key, resource);
return this._proxy.$removeConfigurationOption(target, key, overrides, scopeToLanguage);
}
},
inspect: <T>(key: string): ConfigurationInspect<T> | undefined => {
key = section ? `${section}.${key}` : key;
const config = deepClone(this._configuration.inspect<T>(key, { resource }, this._extHostWorkspace.workspace));
const config = deepClone(this._configuration.inspect<T>(key, overrides, this._extHostWorkspace.workspace));
if (config) {
return {
key,
defaultValue: config.default,
globalValue: config.user,
workspaceValue: config.workspace,
workspaceFolderValue: config.workspaceFolder
defaultValue: config.defaultValue,
globalValue: config.userValue,
workspaceValue: config.workspaceValue,
workspaceFolderValue: config.workspaceFolderValue,
defaultLanguageValue: config.default?.override,
userLanguageValue: config.user?.override,
workspaceLanguageValue: config.workspace?.override,
workspaceFolderLanguageValue: config.workspaceFolder?.override,
};
}
return undefined;
@@ -237,17 +289,17 @@ export class ExtHostConfigProvider {
return readonlyProxy(result);
}
private _validateConfigurationAccess(key: string, resource: URI | undefined, extensionId?: ExtensionIdentifier): void {
private _validateConfigurationAccess(key: string, overrides?: IConfigurationOverrides, extensionId?: ExtensionIdentifier): void {
const scope = OVERRIDE_PROPERTY_PATTERN.test(key) ? ConfigurationScope.RESOURCE : this._configurationScopes.get(key);
const extensionIdText = extensionId ? `[${extensionId.value}] ` : '';
if (ConfigurationScope.RESOURCE === scope) {
if (resource === undefined) {
if (overrides?.resource) {
this._logService.warn(`${extensionIdText}Accessing a resource scoped configuration without providing a resource is not expected. To get the effective value for '${key}', provide the URI of a resource or 'null' for any resource.`);
}
return;
}
if (ConfigurationScope.WINDOW === scope) {
if (resource) {
if (overrides?.resource) {
this._logService.warn(`${extensionIdText}Accessing a window scoped configuration for a resource is not expected. To associate '${key}' to a resource, define its scope to 'resource' in configuration contributions in 'package.json'.`);
}
return;
@@ -257,7 +309,7 @@ export class ExtHostConfigProvider {
private _toConfigurationChangeEvent(change: IConfigurationChange, previous: { data: IConfigurationData, workspace: Workspace | undefined }): vscode.ConfigurationChangeEvent {
const event = new ConfigurationChangeEvent(change, previous, this._configuration, this._extHostWorkspace.workspace);
return Object.freeze({
affectsConfiguration: (section: string, resource?: URI) => event.affectsConfiguration(section, resource ? { resource } : undefined)
affectsConfiguration: (section: string, scope?: vscode.ConfigurationScope) => event.affectsConfiguration(section, scopeToOverrides(scope))
});
}

View File

@@ -645,6 +645,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
try {
const result = await resolver.resolve(remoteAuthority, { resolveAttempt });
this._disposables.add(await this._extHostTunnelService.setForwardPortProvider(resolver));
// Split merged API result into separate authority/options
const authority: ResolvedAuthority = {
@@ -656,13 +657,12 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
extensionHostEnv: result.extensionHostEnv
};
await this._extHostTunnelService.addDetected(result.detectedTunnels);
return {
type: 'ok',
value: {
authority,
options
options,
tunnelInformation: { detectedTunnels: result.detectedTunnels }
}
};
} catch (err) {

View File

@@ -1200,18 +1200,23 @@ class CallHierarchyAdapter {
private readonly _provider: vscode.CallHierarchyProvider
) { }
async prepareSession(uri: URI, position: IPosition, token: CancellationToken): Promise<extHostProtocol.ICallHierarchyItemDto | undefined> {
async prepareSession(uri: URI, position: IPosition, token: CancellationToken): Promise<extHostProtocol.ICallHierarchyItemDto[] | undefined> {
const doc = this._documents.getDocument(uri);
const pos = typeConvert.Position.to(position);
const item = await this._provider.prepareCallHierarchy(doc, pos, token);
if (!item) {
const items = await this._provider.prepareCallHierarchy(doc, pos, token);
if (!items) {
return undefined;
}
const sessionId = this._idPool.nextId();
const sessionId = this._idPool.nextId();
this._cache.set(sessionId, new Map());
return this._cacheAndConvertItem(sessionId, item);
if (Array.isArray(items)) {
return items.map(item => this._cacheAndConvertItem(sessionId, item));
} else {
return [this._cacheAndConvertItem(sessionId, items)];
}
}
async provideCallsTo(sessionId: string, itemId: string, token: CancellationToken): Promise<extHostProtocol.IIncomingCallDto[] | undefined> {
@@ -1727,7 +1732,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
return this._createDisposable(handle);
}
$prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<extHostProtocol.ICallHierarchyItemDto | undefined> {
$prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<extHostProtocol.ICallHierarchyItemDto[] | undefined> {
return this._withAdapter(handle, CallHierarchyAdapter, adapter => Promise.resolve(adapter.prepareSession(URI.revive(resource), position, token)), undefined);
}

View File

@@ -6,6 +6,8 @@
import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import * as vscode from 'vscode';
import { RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { IDisposable } from 'vs/base/common/lifecycle';
export interface TunnelOptions {
remote: { port: number, host: string };
@@ -19,10 +21,38 @@ export interface TunnelDto {
localAddress: string;
}
export namespace TunnelDto {
export function fromApiTunnel(tunnel: vscode.Tunnel): TunnelDto {
return { remote: tunnel.remote, localAddress: tunnel.localAddress };
}
export function fromServiceTunnel(tunnel: RemoteTunnel): TunnelDto {
return { remote: { host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }, localAddress: tunnel.localAddress };
}
}
export interface Tunnel extends vscode.Disposable {
remote: { port: number, host: string };
localAddress: string;
}
export interface IExtHostTunnelService extends ExtHostTunnelServiceShape {
readonly _serviceBrand: undefined;
makeTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined>;
addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[] | undefined): Promise<void>;
setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable>;
}
export const IExtHostTunnelService = createDecorator<IExtHostTunnelService>('IExtHostTunnelService');
export class ExtHostTunnelService implements IExtHostTunnelService {
_serviceBrand: undefined;
async makeTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined> {
return undefined;
}
async $findCandidatePorts(): Promise<{ port: number; detail: string; }[]> {
return [];
}
async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> { return { dispose: () => { } }; }
$forwardPort(tunnelOptions: TunnelOptions): Promise<TunnelDto> | undefined { return undefined; }
async $closeTunnel(remote: { host: string, port: number }): Promise<void> { }
}

View File

@@ -14,7 +14,7 @@ import { generateUuid } from 'vs/base/common/uuid';
import * as vscode from 'vscode';
import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files';
import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { markdownUnescapeCodicons, escapeCodicons } from 'vs/base/common/codicons';
import { escapeCodicons } from 'vs/base/common/codicons';
function es5ClassCompat(target: Function): any {
///@ts-ignore
@@ -1234,17 +1234,16 @@ export class MarkdownString {
isTrusted?: boolean;
readonly supportThemeIcons?: boolean;
constructor(value?: string, { supportThemeIcons }: { supportThemeIcons?: boolean } = {}) {
constructor(value?: string, supportThemeIcons: boolean = false) {
this.value = value ?? '';
this.supportThemeIcons = supportThemeIcons ?? false;
this.supportThemeIcons = supportThemeIcons;
}
appendText(value: string): MarkdownString {
// escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
value = value
this.value += (this.supportThemeIcons ? escapeCodicons(value) : value)
.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&')
.replace('\n', '\n\n');
this.value += this.supportThemeIcons ? markdownUnescapeCodicons(value) : value;
return this;
}
@@ -1263,10 +1262,6 @@ export class MarkdownString {
this.value += '\n```\n';
return this;
}
static escapeThemeIcons(value: string): string {
return escapeCodicons(value);
}
}
@es5ClassCompat

View File

@@ -5,7 +5,6 @@
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { assertIsDefined } from 'vs/base/common/types';
import { URI, UriComponents } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import * as modes from 'vs/editor/common/modes';
@@ -16,7 +15,7 @@ import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor';
import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview';
import * as vscode from 'vscode';
import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewStateData, WebviewEditorCapabilities } from './extHost.protocol';
import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewEditorCapabilities, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol';
import { Disposable as VSCodeDisposable } from './extHostTypes';
type IconPath = URI | { light: URI, dark: URI };
@@ -117,8 +116,6 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa
readonly _onDidChangeViewStateEmitter = this._register(new Emitter<vscode.WebviewPanelOnDidChangeViewStateEvent>());
public readonly onDidChangeViewState: Event<vscode.WebviewPanelOnDidChangeViewStateEvent> = this._onDidChangeViewStateEmitter.event;
public _capabilities?: vscode.WebviewEditorCapabilities;
constructor(
handle: WebviewPanelHandle,
proxy: MainThreadWebviewsShape,
@@ -239,31 +236,6 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa
});
}
_setCapabilities(capabilities: vscode.WebviewEditorCapabilities) {
this._capabilities = capabilities;
if (capabilities.editingCapability) {
this._register(capabilities.editingCapability.onEdit(edit => {
this._proxy.$onEdit(this._handle, edit);
}));
}
}
_undoEdits(edits: readonly any[]): void {
assertIsDefined(this._capabilities).editingCapability?.undoEdits(edits);
}
_redoEdits(edits: readonly any[]): void {
assertIsDefined(this._capabilities).editingCapability?.applyEdits(edits);
}
async _onSave(): Promise<void> {
await assertIsDefined(this._capabilities?.editingCapability)?.save();
}
async _onSaveAs(resource: vscode.Uri, targetResource: vscode.Uri): Promise<void> {
await assertIsDefined(this._capabilities?.editingCapability)?.saveAs(resource, targetResource);
}
private assertNotDisposed() {
if (this._isDisposed) {
throw new Error('Webview is disposed');
@@ -280,7 +252,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
private readonly _proxy: MainThreadWebviewsShape;
private readonly _webviewPanels = new Map<WebviewPanelHandle, ExtHostWebviewEditor>();
private readonly _serializers = new Map<string, { readonly serializer: vscode.WebviewPanelSerializer, readonly extension: IExtensionDescription }>();
private readonly _editorProviders = new Map<string, { readonly provider: vscode.WebviewEditorProvider, readonly extension: IExtensionDescription }>();
private readonly _editorProviders = new Map<string, { readonly provider: vscode.WebviewCustomEditorProvider, readonly extension: IExtensionDescription }>();
constructor(
mainContext: IMainContext,
@@ -331,10 +303,10 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
});
}
public registerWebviewEditorProvider(
public registerWebviewCustomEditorProvider(
extension: IExtensionDescription,
viewType: string,
provider: vscode.WebviewEditorProvider,
provider: vscode.WebviewCustomEditorProvider,
options?: vscode.WebviewPanelOptions,
): vscode.Disposable {
if (this._editorProviders.has(viewType)) {
@@ -342,7 +314,10 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
}
this._editorProviders.set(viewType, { extension, provider, });
this._proxy.$registerEditorProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, options || {});
this._proxy.$registerEditorProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, options || {}, this.getCapabilites(provider));
provider?.editingDelegate?.onEdit(({ edit, resource }) => {
this._proxy.$onEdit(resource, viewType, edit);
});
return new VSCodeDisposable(() => {
this._editorProviders.delete(viewType);
@@ -431,7 +406,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
}
async $resolveWebviewEditor(
input: { resource: UriComponents, edits: readonly any[] },
resource: UriComponents,
handle: WebviewPanelHandle,
viewType: string,
title: string,
@@ -447,46 +422,44 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension, this._logService);
const revivedPanel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview);
this._webviewPanels.set(handle, revivedPanel);
const capabilities = await provider.resolveWebviewEditor({ resource: URI.revive(input.resource) }, revivedPanel);
revivedPanel._setCapabilities(capabilities);
this.registerCapabilites(handle, capabilities);
// TODO: the first set of edits should likely be passed when resolving
if (input.edits.length) {
revivedPanel._redoEdits(input.edits);
}
const revivedResource = URI.revive(resource);
await provider.resolveWebviewEditor(revivedResource, revivedPanel);
}
$undoEdits(handle: WebviewPanelHandle, edits: readonly any[]): void {
const panel = this.getWebviewPanel(handle);
panel?._undoEdits(edits);
$undoEdits(resource: UriComponents, viewType: string, edits: readonly any[]): void {
const provider = this.getEditorProvider(viewType);
provider?.editingDelegate?.undoEdits(URI.revive(resource), edits);
}
$applyEdits(handle: WebviewPanelHandle, edits: readonly any[]): void {
const panel = this.getWebviewPanel(handle);
panel?._redoEdits(edits);
$applyEdits(resource: UriComponents, viewType: string, edits: readonly any[]): void {
const provider = this.getEditorProvider(viewType);
provider?.editingDelegate?.applyEdits(URI.revive(resource), edits);
}
async $onSave(handle: WebviewPanelHandle): Promise<void> {
const panel = this.getWebviewPanel(handle);
return panel?._onSave();
async $onSave(resource: UriComponents, viewType: string): Promise<void> {
const provider = this.getEditorProvider(viewType);
return provider?.editingDelegate?.save(URI.revive(resource));
}
async $onSaveAs(handle: WebviewPanelHandle, resource: UriComponents, targetResource: UriComponents): Promise<void> {
const panel = this.getWebviewPanel(handle);
return panel?._onSaveAs(URI.revive(resource), URI.revive(targetResource));
async $onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise<void> {
const provider = this.getEditorProvider(viewType);
return provider?.editingDelegate?.saveAs(URI.revive(resource), URI.revive(targetResource));
}
private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined {
return this._webviewPanels.get(handle);
}
private registerCapabilites(handle: WebviewPanelHandle, capabilities: vscode.WebviewEditorCapabilities) {
private getEditorProvider(viewType: string): vscode.WebviewCustomEditorProvider | undefined {
return this._editorProviders.get(viewType)?.provider;
}
private getCapabilites(capabilities: vscode.WebviewCustomEditorProvider) {
const declaredCapabilites: WebviewEditorCapabilities[] = [];
if (capabilities.editingCapability) {
if (capabilities.editingDelegate) {
declaredCapabilites.push(WebviewEditorCapabilities.Editable);
}
this._proxy.$registerCapabilities(handle, declaredCapabilites);
return declaredCapabilites;
}
}

View File

@@ -356,11 +356,8 @@ commandsExtensionPoint.setHandler(extensions => {
let absoluteIcon: { dark: URI; light?: URI; } | ThemeIcon | undefined;
if (icon) {
if (typeof icon === 'string') {
if (extension.description.enableProposedApi) {
absoluteIcon = ThemeIcon.fromString(icon) || { dark: resources.joinPath(extension.description.extensionLocation, icon) };
} else {
absoluteIcon = { dark: resources.joinPath(extension.description.extensionLocation, icon) };
}
absoluteIcon = ThemeIcon.fromString(icon) || { dark: resources.joinPath(extension.description.extensionLocation, icon) };
} else {
absoluteIcon = {
dark: resources.joinPath(extension.description.extensionLocation, icon.dark),

View File

@@ -6,18 +6,37 @@
import { MainThreadTunnelServiceShape, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import * as vscode from 'vscode';
import { Disposable } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { URI } from 'vs/base/common/uri';
import { exec } from 'child_process';
import * as resources from 'vs/base/common/resources';
import * as fs from 'fs';
import { isLinux } from 'vs/base/common/platform';
import { IExtHostTunnelService, TunnelOptions } from 'vs/workbench/api/common/extHostTunnelService';
import { IExtHostTunnelService, TunnelOptions, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { asPromise } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event';
class ExtensionTunnel implements vscode.Tunnel {
private _onDispose: Emitter<void> = new Emitter();
onDispose: Event<void> = this._onDispose.event;
constructor(
public readonly remote: { port: number; host: string; },
public readonly localAddress: string,
private readonly _dispose: () => void) { }
dispose(): void {
this._onDispose.fire();
this._dispose();
}
}
export class ExtHostTunnelService extends Disposable implements IExtHostTunnelService {
readonly _serviceBrand: undefined;
private readonly _proxy: MainThreadTunnelServiceShape;
private _forwardPortProvider: ((tunnelOptions: TunnelOptions) => Thenable<vscode.Tunnel> | undefined) | undefined;
private _extensionTunnels: Map<string, Map<number, vscode.Tunnel>> = new Map();
constructor(
@IExtHostRpcService extHostRpc: IExtHostRpcService,
@@ -32,29 +51,59 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
async makeTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined> {
const tunnel = await this._proxy.$openTunnel(forward);
if (tunnel) {
const disposableTunnel: vscode.Tunnel = {
remote: tunnel.remote,
localAddress: tunnel.localAddress,
dispose: () => {
return this._proxy.$closeTunnel(tunnel.remote.port);
}
};
const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remote, tunnel.localAddress, () => {
return this._proxy.$closeTunnel(tunnel.remote.port);
});
this._register(disposableTunnel);
return disposableTunnel;
}
return undefined;
}
async addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[] | undefined): Promise<void> {
if (tunnels) {
return this._proxy.$addDetected(tunnels);
}
}
registerCandidateFinder(): Promise<void> {
return this._proxy.$registerCandidateFinder();
}
async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> {
if (provider && provider.forwardPort) {
this._forwardPortProvider = provider.forwardPort;
await this._proxy.$setTunnelProvider();
} else {
this._forwardPortProvider = undefined;
}
return toDisposable(() => {
this._forwardPortProvider = undefined;
});
}
async $closeTunnel(remote: { host: string, port: number }): Promise<void> {
if (this._extensionTunnels.has(remote.host)) {
const hostMap = this._extensionTunnels.get(remote.host)!;
if (hostMap.has(remote.port)) {
hostMap.get(remote.port)!.dispose();
hostMap.delete(remote.port);
}
}
}
$forwardPort(tunnelOptions: TunnelOptions): Promise<TunnelDto> | undefined {
if (this._forwardPortProvider) {
const providedPort = this._forwardPortProvider!(tunnelOptions);
if (providedPort !== undefined) {
return asPromise(() => providedPort).then(tunnel => {
if (!this._extensionTunnels.has(tunnelOptions.remote.host)) {
this._extensionTunnels.set(tunnelOptions.remote.host, new Map());
}
this._extensionTunnels.get(tunnelOptions.remote.host)!.set(tunnelOptions.remote.port, tunnel);
this._register(tunnel.onDispose(() => this._proxy.$closeTunnel(tunnel.remote.port)));
return Promise.resolve(TunnelDto.fromApiTunnel(tunnel));
});
}
}
return undefined;
}
async $findCandidatePorts(): Promise<{ port: number, detail: string }[]> {
if (!isLinux) {
return [];